From 052143cba4bc1c48d0d6c6ed2813104082d874c2 Mon Sep 17 00:00:00 2001
From: Sonia Zorba <sonia.zorba@inaf.it>
Date: Mon, 5 Oct 2020 17:44:12 +0200
Subject: [PATCH] Refactoring: moved OAuth2 configuration from db to config
 file; used yaml file for configuration

---
 .gitignore                                    |   1 +
 README.md                                     |   2 +-
 classes/ClientAuthChecker.php                 |  16 +-
 classes/ClientsLocator.php                    |  56 ++++
 classes/Locator.php                           |  12 +-
 classes/OAuth2RequestHandler.php              |  15 +-
 classes/TokenBuilder.php                      |   2 +-
 classes/datalayer/OAuth2ClientDAO.php         |  25 --
 .../datalayer/mysql/MySQLOAuth2ClientDAO.php  | 286 ------------------
 classes/model/AuthPageModel.php               |   2 +-
 .../{RAPClient.php => BrowserBasedClient.php} |   5 +-
 ...lient.php => BrowserBasedOAuth2Client.php} |  20 +-
 classes/model/CliClient.php                   |  11 -
 classes/model/CliOAuth2Client.php             |  22 ++
 classes/model/InternalClient.php              |   2 +-
 config-example.json                           |  61 ----
 config-example.yaml                           |  77 +++++
 exec/hash_password.php                        |   9 +
 include/admin.php                             |  43 +--
 include/front-controller.php                  |   7 +-
 include/init.php                              |   4 +-
 tests/OAuth2RequestHandlerTest.php            |  56 ++--
 ...enBuilderTest.php => TokenBuilderTest.php} |   2 +-
 23 files changed, 237 insertions(+), 499 deletions(-)
 create mode 100644 classes/ClientsLocator.php
 delete mode 100644 classes/datalayer/OAuth2ClientDAO.php
 delete mode 100644 classes/datalayer/mysql/MySQLOAuth2ClientDAO.php
 rename classes/model/{RAPClient.php => BrowserBasedClient.php} (64%)
 rename classes/model/{OAuth2Client.php => BrowserBasedOAuth2Client.php} (62%)
 delete mode 100644 classes/model/CliClient.php
 create mode 100644 classes/model/CliOAuth2Client.php
 delete mode 100644 config-example.json
 create mode 100644 config-example.yaml
 create mode 100644 exec/hash_password.php
 rename tests/{IdTokenBuilderTest.php => TokenBuilderTest.php} (97%)

diff --git a/.gitignore b/.gitignore
index c014544..4287e45 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 composer.lock
 config.php
 config.json
+config.yaml
 logs/
 vendor/
 client-icons/
diff --git a/README.md b/README.md
index 462ca07..ea6024c 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ Requirements:
 
 On Ubuntu:
 
-    sudo apt install apache2 mariadb-server libapache2-mod-php mariadb-server php7.2-xml php7.2-mbstring php-mysql php-curl
+    sudo apt install apache2 mariadb-server libapache2-mod-php mariadb-server php7.2-xml php7.2-mbstring php-mysql php-curl php-yaml
 
 ### PHP
 
diff --git a/classes/ClientAuthChecker.php b/classes/ClientAuthChecker.php
index 8bb33e2..497f9bb 100644
--- a/classes/ClientAuthChecker.php
+++ b/classes/ClientAuthChecker.php
@@ -22,27 +22,17 @@ class ClientAuthChecker {
         $clientId = $basic[0];
         $clientSecret = $basic[1];
 
-        $client = $this->locator->getOAuth2ClientDAO()->getOAuth2ClientByClientId($clientId);
-        if ($client === null) {
-            throw new UnauthorizedException("Client '$clientId' not configured");
-        }
-        if ($clientSecret !== $client->secret) {
-            throw new UnauthorizedException("Invalid client secret");
-        }
+        $this->locator->getBrowserBasedOAuth2ClientByIdAndSecret($clientId, $clientSecret);
     }
 
-    public function validateCliClientAuth(): CliClient {
+    public function validateCliClientAuth(): CliOAuth2Client {
 
         $basic = $this->getBasicAuthArray();
 
         $clientId = $basic[0];
         $clientSecret = $basic[1];
 
-        $client = $this->locator->getOAuth2ClientDAO()->getCliClient($clientId, $clientSecret);
-        if ($client === null) {
-            throw new UnauthorizedException("Client '$clientId' not configured or wrong password");
-        }
-        return $client;
+        return $this->locator->getCliClientByIdAndSecret($clientId, $clientSecret);
     }
 
     private function getBasicAuthArray(): array {
diff --git a/classes/ClientsLocator.php b/classes/ClientsLocator.php
new file mode 100644
index 0000000..5ebe623
--- /dev/null
+++ b/classes/ClientsLocator.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace RAP;
+
+trait ClientsLocator {
+
+    public function getBrowserBasedOAuth2Clients(): array {
+        return array_map(function($clientConfig) {
+            return new BrowserBasedOAuth2Client($clientConfig);
+        }, $this->config->clients);
+    }
+
+    public function getBrowserBasedOAuth2ClientById(string $clientId, bool $allowNull = false): ?BrowserBasedOAuth2Client {
+        $clientConfig = $this->getClientConfigFromListById($this->config->clients, $clientId, $allowNull);
+        if ($allowNull && $clientConfig === null) {
+            return null;
+        }
+        return new BrowserBasedOAuth2Client($clientConfig);
+    }
+
+    public function getBrowserBasedOAuth2ClientByIdAndSecret(string $clientId, string $secret): BrowserBasedOAuth2Client {
+        $clientConfig = $this->getClientConfigFromListByIdAndSecret($this->config->clients, $clientId, $secret);
+        return new BrowserBasedOAuth2Client($clientConfig);
+    }
+
+    public function getCliClientByIdAndSecret(string $clientId, string $secret): CliOAuth2Client {
+        $clientConfig = $this->getClientConfigFromListByIdAndSecret($this->config->cliClients, $clientId, $secret);
+        return new CliOAuth2Client($clientConfig);
+    }
+
+    private function getClientConfigFromListByIdAndSecret(array $clients, string $clientId, string $secret): object {
+        $client = $this->getClientConfigFromListById($clients, $clientId);
+        $secretHash = hash('sha256', $secret);
+        if ($client->secretHash !== $secretHash) {
+            throw new UnauthorizedException("Wrong secret provided for client '$clientId'");
+        }
+        return $client;
+    }
+
+    private function getClientConfigFromListById(array $clients, string $clientId, bool $allowNull = false): ?object {
+        $client = null;
+        foreach ($clients as $c) {
+            if ($c->id === $clientId) {
+                if ($client !== null) {
+                    throw new ServerErrorException("Found multiple clients having id '$clientId'");
+                }
+                $client = $c;
+            }
+        }
+        if ($client === null && !$allowNull) {
+            throw new BadRequestException("Client '$clientId' not configured");
+        }
+        return $client;
+    }
+
+}
diff --git a/classes/Locator.php b/classes/Locator.php
index d032f70..956b273 100644
--- a/classes/Locator.php
+++ b/classes/Locator.php
@@ -7,6 +7,8 @@ namespace RAP;
  */
 class Locator {
 
+    use ClientsLocator;
+
     public $config;
     private $serviceLogger;
     private $auditLogger;
@@ -42,16 +44,6 @@ class Locator {
         }
     }
 
-    public function getOAuth2ClientDAO(): OAuth2ClientDAO {
-        $databaseConfig = $this->config->databaseConfig;
-        switch ($databaseConfig->dbtype) {
-            case 'MySQL':
-                return new MySQLOAuth2ClientDAO($this);
-            default:
-                throw new \Exception($databaseConfig->dbtype . ' not supported yet');
-        }
-    }
-
     public function getJWKSDAO(): JWKSDAO {
         $databaseConfig = $this->config->databaseConfig;
         switch ($databaseConfig->dbtype) {
diff --git a/classes/OAuth2RequestHandler.php b/classes/OAuth2RequestHandler.php
index 8adc551..084d43c 100644
--- a/classes/OAuth2RequestHandler.php
+++ b/classes/OAuth2RequestHandler.php
@@ -20,10 +20,7 @@ class OAuth2RequestHandler {
             throw new BadRequestException("Redirect URI is required");
         }
 
-        $client = $this->locator->getOAuth2ClientDAO()->getOAuth2ClientByClientId($params['client_id']);
-        if ($client === null) {
-            throw new BadRequestException("Invalid client id: " . $params['client_id']);
-        }
+        $client = $this->locator->getBrowserBasedOAuth2ClientById($params['client_id']);
         if ($client->redirectUrl !== $params['redirect_uri']) {
             throw new BadRequestException("Invalid client redirect URI: " . $params['redirect_uri']);
         }
@@ -219,7 +216,7 @@ class OAuth2RequestHandler {
      * with some libraries (e.g. Spring Security) but it could be removed in the
      * future.
      */
-    public function handleCheckTokenRequest(): array {
+    public function handleCheckTokenRequest(array $headers): array {
 
         $jwt = $this->locator->getTokenChecker()->validateToken();
         $tokenData = $this->getTokenDataFromJwtObject($jwt);
@@ -228,7 +225,8 @@ class OAuth2RequestHandler {
         $result['exp'] = $tokenData->expirationTime - time();
         $result['user_name'] = $tokenData->userId;
         $result['client_id'] = $tokenData->clientId;
-        $result['access_token'] = $this->copyReceivedAccessToken();
+        // copy received access token
+        $result['access_token'] = explode(" ", $headers['Authorization'])[1];
         $result['refresh_token'] = $this->buildRefreshToken($tokenData);
 
         if (isset($tokenData->scope) && count($tokenData->scope) > 0) {
@@ -241,11 +239,6 @@ class OAuth2RequestHandler {
         return $result;
     }
 
-    private function copyReceivedAccessToken(): string {
-        $headers = apache_request_headers();
-        return explode(" ", $headers['Authorization'])[1];
-    }
-
     private function getTokenDataFromJwtObject($jwt): AccessTokenData {
 
         $tokenData = new AccessTokenData();
diff --git a/classes/TokenBuilder.php b/classes/TokenBuilder.php
index 27bf313..e64162d 100644
--- a/classes/TokenBuilder.php
+++ b/classes/TokenBuilder.php
@@ -87,7 +87,7 @@ class TokenBuilder {
             return $this->getAudienceClaim($tokenData->audience);
         }
 
-        $client = $this->locator->getOAuth2ClientDAO()->getOAuth2ClientByClientId($tokenData->clientId);
+        $client = $this->locator->getBrowserBasedOAuth2ClientById($tokenData->clientId, true);
         if ($client === null) {
             // CLI client without audience
             return null;
diff --git a/classes/datalayer/OAuth2ClientDAO.php b/classes/datalayer/OAuth2ClientDAO.php
deleted file mode 100644
index 944ff6e..0000000
--- a/classes/datalayer/OAuth2ClientDAO.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-namespace RAP;
-
-/**
- * CRUD methods for OAuth2Clients (used by admin interface).
- */
-interface OAuth2ClientDAO {
-
-    function getOAuth2Clients(): array;
-
-    function createOAuth2Client(OAuth2Client $client): OAuth2Client;
-
-    function updateOAuth2Client(OAuth2Client $client): OAuth2Client;
-
-    function deleteOAuth2Client($clientId);
-
-    /**
-     * Retrieve the client from the configured client id (the one associated to
-     * the secret, not the database id).
-     */
-    function getOAuth2ClientByClientId($clientId): ?OAuth2Client;
-    
-    function getCliClient(string $clientId, string $secret): ?CliClient; 
-}
diff --git a/classes/datalayer/mysql/MySQLOAuth2ClientDAO.php b/classes/datalayer/mysql/MySQLOAuth2ClientDAO.php
deleted file mode 100644
index c005b69..0000000
--- a/classes/datalayer/mysql/MySQLOAuth2ClientDAO.php
+++ /dev/null
@@ -1,286 +0,0 @@
-<?php
-
-namespace RAP;
-
-class MySQLOAuth2ClientDAO extends BaseMySQLDAO implements OAuth2ClientDAO {
-
-    public function __construct($config) {
-        parent::__construct($config);
-    }
-
-    public function getOAuth2Clients(): array {
-
-        $dbh = $this->getDBHandler();
-
-        $clientsMap = $this->getClientsMap($dbh);
-        $this->loadAuthenticationMethods($dbh, $clientsMap);
-        $this->loadScopeAudienceMapping($dbh, $clientsMap);
-
-        $clients = [];
-        foreach ($clientsMap as $id => $client) {
-            array_push($clients, $client);
-        }
-
-        return $clients;
-    }
-
-    private function getClientsMap(\PDO $dbh): array {
-
-        // Load clients info
-        $queryClient = "SELECT id, title, icon, client, secret, redirect_url, scope, home_page, show_in_home FROM oauth2_client";
-        $stmtClients = $dbh->prepare($queryClient);
-        $stmtClients->execute();
-
-        $clientsMap = [];
-
-        foreach ($stmtClients->fetchAll() as $row) {
-            $client = new OAuth2Client();
-            $client->id = $row['id'];
-            $client->title = $row['title'];
-            $client->icon = $row['icon'];
-            $client->client = $row['client'];
-            $client->secret = $row['secret'];
-            $client->redirectUrl = $row['redirect_url'];
-            $client->scope = $row['scope'];
-            $client->homePage = $row['home_page'];
-            $client->showInHome = boolval($row['show_in_home']);
-            $clientsMap[$client->id] = $client;
-        }
-
-        return $clientsMap;
-    }
-
-    private function loadAuthenticationMethods(\PDO $dbh, array $clientsMap): void {
-
-        $queryAuthNMethods = "SELECT client_id, auth_method FROM oauth2_client_auth_methods";
-
-        $stmtAuthNMethods = $dbh->prepare($queryAuthNMethods);
-        $stmtAuthNMethods->execute();
-
-        foreach ($stmtAuthNMethods->fetchAll() as $row) {
-            $id = $row['client_id'];
-            array_push($clientsMap[$id]->authMethods, $row['auth_method']);
-        }
-    }
-
-    private function loadScopeAudienceMapping(\PDO $dbh, array $clientsMap): void {
-
-        $query = "SELECT client_id, scope, audience FROM oauth2_client_scope_audience_mapping";
-
-        $stmt = $dbh->prepare($query);
-
-        foreach ($stmt->fetchAll() as $row) {
-            $id = $row['client_id'];
-
-            if (array_key_exists($id, $clientsMap)) {
-                $client = $clientsMap[$id];
-                $client->scopeAudienceMap[$row['scope']] = $row['audience'];
-            }
-
-            array_push($clientsMap[$id]->authMethods, $row['auth_method']);
-        }
-    }
-
-    function createOAuth2Client(OAuth2Client $client): OAuth2Client {
-        $dbh = $this->getDBHandler();
-
-        try {
-            $dbh->beginTransaction();
-
-            $stmt = $dbh->prepare("INSERT INTO `oauth2_client`(`title`, `icon`, `client`, `secret`, `redirect_url`, `scope`, home_page, show_in_home)"
-                    . " VALUES(:title, :icon, :client, :secret, :redirect_url, :scope, :home_page, :show_in_home)");
-
-            $stmt->bindParam(':title', $client->title);
-            $stmt->bindParam(':icon', $client->icon);
-            $stmt->bindParam(':client', $client->client);
-            $stmt->bindParam(':secret', $client->secret);
-            $stmt->bindParam(':redirect_url', $client->redirectUrl);
-            $stmt->bindParam(':scope', $client->scope);
-            $stmt->bindParam(':home_page', $client->homePage);
-            $stmt->bindParam(':show_in_home', $client->showInHome, \PDO::PARAM_INT);
-
-            $stmt->execute();
-
-            $client->id = $dbh->lastInsertId();
-
-            foreach ($client->authMethods as $method) {
-                $stmt = $dbh->prepare("INSERT INTO `oauth2_client_auth_methods`(`client_id`, `auth_method`)"
-                        . " VALUES(:client_id, :auth_method)");
-
-                $stmt->bindParam(':client_id', $client->id);
-                $stmt->bindParam(':auth_method', $method);
-
-                $stmt->execute();
-            }
-
-            $dbh->commit();
-        } catch (Exception $ex) {
-            $dbh->rollBack();
-            throw $ex;
-        }
-
-        return $client;
-    }
-
-    function updateOAuth2Client(OAuth2Client $client): OAuth2Client {
-        $dbh = $this->getDBHandler();
-
-        try {
-            $dbh->beginTransaction();
-
-            $stmt = $dbh->prepare("UPDATE `oauth2_client` SET `title` = :title, `icon` = :icon, "
-                    . " `client` = :client, `secret` = :secret, `redirect_url` = :redirect_url, `scope` = :scope, "
-                    . " `home_page` = :home_page, `show_in_home` = :show_in_home"
-                    . " WHERE id = :id");
-
-            $stmt->bindParam(':title', $client->title);
-            $stmt->bindParam(':icon', $client->icon);
-            $stmt->bindParam(':client', $client->client);
-            $stmt->bindParam(':secret', $client->secret);
-            $stmt->bindParam(':redirect_url', $client->redirectUrl);
-            $stmt->bindParam(':scope', $client->scope);
-            $stmt->bindParam(':home_page', $client->homePage);
-            $stmt->bindParam(':show_in_home', $client->showInHome, \PDO::PARAM_INT);
-            $stmt->bindParam(':id', $client->id);
-
-            $stmt->execute();
-
-            // Delete old authentication methods
-            $stmt = $dbh->prepare("DELETE FROM oauth2_client_auth_methods WHERE client_id = :id");
-            $stmt->bindParam(':id', $client->id);
-
-            $stmt->execute();
-
-            // Re-add authentication methods
-            foreach ($client->authMethods as $method) {
-                $stmt = $dbh->prepare("INSERT INTO `oauth2_client_auth_methods`(`client_id`, `auth_method`)"
-                        . " VALUES(:client_id, :auth_method)");
-
-                $stmt->bindParam(':client_id', $client->id);
-                $stmt->bindParam(':auth_method', $method);
-
-                $stmt->execute();
-            }
-
-            $dbh->commit();
-        } catch (Exception $ex) {
-            $dbh->rollBack();
-            throw $ex;
-        }
-
-        return $client;
-    }
-
-    function deleteOAuth2Client($clientId) {
-        $dbh = $this->getDBHandler();
-        try {
-            $dbh->beginTransaction();
-
-            $stmt = $dbh->prepare("DELETE FROM `oauth2_client_auth_methods` WHERE client_id = :id");
-            $stmt->bindParam(':id', $clientId);
-            $stmt->execute();
-
-            $stmt = $dbh->prepare("DELETE FROM `oauth2_client` WHERE id = :id");
-            $stmt->bindParam(':id', $clientId);
-            $stmt->execute();
-
-            $dbh->commit();
-        } catch (Exception $ex) {
-            $dbh->rollBack();
-            throw $ex;
-        }
-    }
-
-    function getOAuth2ClientByClientId($clientId): ?OAuth2Client {
-        $dbh = $this->getDBHandler();
-
-        // Load clients info
-        $queryClient = "SELECT id, title, icon, client, secret, redirect_url, scope, home_page, show_in_home FROM oauth2_client WHERE client = :client";
-        $stmtClient = $dbh->prepare($queryClient);
-        $stmtClient->bindParam(':client', $clientId);
-        $stmtClient->execute();
-
-        $result = $stmtClient->fetchAll();
-
-        if (count($result) === 0) {
-            return null;
-        }
-        if (count($result) > 1) {
-            throw new \Exception("Found multiple clients associated to the same client id!");
-        }
-
-        $row = $result[0];
-
-        $client = new OAuth2Client();
-        $client->id = $row['id'];
-        $client->title = $row['title'];
-        $client->icon = $row['icon'];
-        $client->client = $row['client'];
-        $client->secret = $row['secret'];
-        $client->redirectUrl = $row['redirect_url'];
-        $client->scope = $row['scope'];
-        $client->homePage = $row['home_page'];
-        $client->showInHome = $row['show_in_home'];
-
-        // Load authentication methods info
-        $queryAuthNMethods = "SELECT auth_method FROM oauth2_client_auth_methods WHERE client_id = :id";
-
-        $stmtAuthNMethods = $dbh->prepare($queryAuthNMethods);
-        $stmtAuthNMethods->bindParam(':id', $client->id);
-        $stmtAuthNMethods->execute();
-
-        foreach ($stmtAuthNMethods->fetchAll() as $row) {
-            array_push($client->authMethods, $row['auth_method']);
-        }
-
-        // Load scope-audience mapping
-        $queryAudienceMapping = "SELECT scope, audience FROM oauth2_client_scope_audience_mapping WHERE client_id = :id";
-        $stmtAudienceMapping = $dbh->prepare($queryAudienceMapping);
-        $stmtAudienceMapping->bindParam(':id', $client->id);
-        $stmtAudienceMapping->execute();
-
-        foreach ($stmtAudienceMapping->fetchAll() as $row) {
-            $client->scopeAudienceMap[$row['scope']] = $row['audience'];
-        }
-
-        return $client;
-    }
-
-    function getCliClient(string $clientId, string $secret): ?CliClient {
-
-        $dbh = $this->getDBHandler();
-
-        // Load clients info
-        $queryClient = "SELECT scope, audience FROM cli_client WHERE client_id = :client AND client_secret = PASSWORD(:secret)";
-        $stmtClient = $dbh->prepare($queryClient);
-        $stmtClient->bindParam(':client', $clientId);
-        $stmtClient->bindParam(':secret', $secret);
-        $stmtClient->execute();
-
-        $result = $stmtClient->fetchAll();
-
-        if (count($result) === 0) {
-            return null;
-        }
-        if (count($result) > 1) {
-            throw new \Exception("Found multiple clients associated to the same client id!");
-        }
-
-        $row = $result[0];
-
-        $client = new CliClient();
-        $client->id = $clientId;
-        if ($row['scope'] !== null) {
-            $client->scope = explode(' ', $row['scope']);
-        } else {
-            $client->scope = [];
-        }
-        if ($row['audience'] !== null) {
-            $client->audience = explode(' ', $row['audience']);
-        } else {
-            $client->audience = [];
-        }
-        return $client;
-    }
-
-}
diff --git a/classes/model/AuthPageModel.php b/classes/model/AuthPageModel.php
index bf90c89..05ed340 100644
--- a/classes/model/AuthPageModel.php
+++ b/classes/model/AuthPageModel.php
@@ -20,7 +20,7 @@ class AuthPageModel {
     public $clientTitle;
     public $localIdPConfig;
 
-    public function __construct(\RAP\Locator $locator, \RAP\RAPClient $client) {
+    public function __construct(\RAP\Locator $locator, \RAP\BrowserBasedClient $client) {
 
         $config = $locator->config;
 
diff --git a/classes/model/RAPClient.php b/classes/model/BrowserBasedClient.php
similarity index 64%
rename from classes/model/RAPClient.php
rename to classes/model/BrowserBasedClient.php
index e6a55e8..437ec3d 100644
--- a/classes/model/RAPClient.php
+++ b/classes/model/BrowserBasedClient.php
@@ -2,7 +2,10 @@
 
 namespace RAP;
 
-abstract class RAPClient {
+/**
+ * Represents a client that connects to RAP from the browser.
+ */
+abstract class BrowserBasedClient {
 
     public $title;
     public $icon;
diff --git a/classes/model/OAuth2Client.php b/classes/model/BrowserBasedOAuth2Client.php
similarity index 62%
rename from classes/model/OAuth2Client.php
rename to classes/model/BrowserBasedOAuth2Client.php
index acad698..fce73fa 100644
--- a/classes/model/OAuth2Client.php
+++ b/classes/model/BrowserBasedOAuth2Client.php
@@ -27,17 +27,31 @@ namespace RAP;
 /**
  * Data model for storing information about a RAP client connecting using OAuth2.
  */
-class OAuth2Client extends RAPClient {
+class BrowserBasedOAuth2Client extends BrowserBasedClient {
 
-    public $id;
     public $client;
-    public $secret;
+    public $secretHash;
     public $redirectUrl;
     public $scope;
     public $homePage;
     public $showInHome;
+    public $jwks;
     public $scopeAudienceMap = [];
 
+    public function __construct(object $config) {
+        $this->client = $config->id;
+        $this->secretHash = $config->secret;
+        $this->title = isset($config->label) ? $config->label : null;
+        $this->icon = isset($config->icon) ? $config->icon : null;
+        $this->redirectUrl = $config->redirect;
+        $this->scope = $config->scope;
+        $this->homePage = isset($config->home) ? $config->home : null;
+        $this->showInHome = isset($config->showInHome) ? $config->showInHome : false;
+        $this->authMethods = $config->methods;
+        $this->jwks = isset($config->jwks) ? $config->jwks : null;
+        $this->scopeAudienceMap = isset($config->scopeAudienceMap) ? $config->scopeAudienceMap : null;
+    }
+
     public function getIconBasePath() {
         return 'client-icons/';
     }
diff --git a/classes/model/CliClient.php b/classes/model/CliClient.php
deleted file mode 100644
index d10f719..0000000
--- a/classes/model/CliClient.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-namespace RAP;
-
-class CliClient {
-
-    public $id;
-    public $scope;
-    public $audience;
-
-}
diff --git a/classes/model/CliOAuth2Client.php b/classes/model/CliOAuth2Client.php
new file mode 100644
index 0000000..aabba3e
--- /dev/null
+++ b/classes/model/CliOAuth2Client.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace RAP;
+
+/**
+ * OAuth2 client using the client credentials OAuth2 flow for command line usage.
+ */
+class CliOAuth2Client {
+
+    public $id;
+    public $secretHash;
+    public $scope;
+    public $audience;
+
+    public function __construct(object $config) {
+        $this->id = $config->id;
+        $this->secretHash = $config->secret;
+        $this->scope = $config->scope;
+        $this->audience = $config->audience;
+    }
+
+}
diff --git a/classes/model/InternalClient.php b/classes/model/InternalClient.php
index f91f7d0..a7b4b52 100644
--- a/classes/model/InternalClient.php
+++ b/classes/model/InternalClient.php
@@ -6,7 +6,7 @@ namespace RAP;
  * Represents a client that connects to parts of RAP itself (e.g. Account Manager).
  * It doesn't use OAuth2, instead stores data directly into the PHP session.
  */
-class InternalClient extends RAPClient {
+class InternalClient extends BrowserBasedClient {
 
     public $action;
 
diff --git a/config-example.json b/config-example.json
deleted file mode 100644
index adc90f4..0000000
--- a/config-example.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
-    "contextRoot": "/rap-ia2",
-    "serviceLogFile": "/var/www/html/rap-ia2/logs/rap-service.log",
-    "auditLogFile": "/var/www/html/rap-ia2/logs/rap-audit.log",
-    "timeZone": "Europe/Rome",
-    "logLevel": "DEBUG",
-    "jwtIssuer": "sso.ia2.inaf.it",
-    "databaseConfig": {
-        "dbtype": "MySQL",
-        "hostname": "localhost",
-        "port": 3306,
-        "username": "rap",
-        "password": "XXXXXX",
-        "dbname": "rap"
-    },
-    "authenticationMethods": {
-        "eduGAIN": {},
-        "Google": {
-            "id": "XXXXXX",
-            "secret": "XXXXXX",
-            "callback": "/auth/social/google"
-        },
-        "Facebook": {
-            "id": "XXXXXX",
-            "secret": "XXXXXX",
-            "version": "v3.0",
-            "callback": "/auth/social/facebook/token"
-        },
-        "LinkedIn": {
-            "id": "XXXXXX",
-            "secret": "XXXXXX",
-            "callback": "/auth/social/linkedin/token"
-        },
-        "X509": {},
-        "LocalIdP": {
-            "url": "https://sso.ia2.inaf.it/Shibboleth.sso/Login?entityID=https://sso.ia2.inaf.it/idp/shibboleth&target=https://sso.ia2.inaf.it/rap-ia2-v2/auth/eduGAIN",
-            "logo": "img/ia2-logo-60x60.png",
-            "logoAlt": "IA2 logo",
-            "description": "Use the IA2 Logo to Login if you have an account provided by IA2 or self registered"
-        },
-        "OrcID": {
-            "id": "",
-            "callback": "/auth/orcid",
-            "secret": ""
-        }
-    },
-    "gms": {
-        "id": "gms",
-        "joinEndpoint": "http://localhost:8082/gms/ws/jwt/join"
-    },
-    "tokenIssuer": {
-        "services": [{
-                "id": "fileserver",
-                "label": "File Server",
-                "aud": ["fileserver", "rap"],
-                "scope": "read:fileserver read:rap"
-            }],
-        "lifespans": [1, 6, 12, 24]
-    },
-    "jwksUrls": ["http://service/jwks"]
-}
diff --git a/config-example.yaml b/config-example.yaml
new file mode 100644
index 0000000..fcd8cdb
--- /dev/null
+++ b/config-example.yaml
@@ -0,0 +1,77 @@
+---
+contextRoot: "/rap-ia2"
+serviceLogFile: "/var/www/html/rap-ia2/logs/rap-service.log"
+auditLogFile: "/var/www/html/rap-ia2/logs/rap-audit.log"
+timeZone: "Europe/Rome"
+logLevel: "DEBUG"
+jwtIssuer: "sso.ia2.inaf.it"
+contactEmail: "ia2@inaf.it"
+contactLabel: "IA2 Team"
+databaseConfig:
+  dbtype: "MySQL"
+  hostname: "localhost"
+  port: 3306
+  username: "rap"
+  password: "XXXXXX"
+  dbname: "rap"
+authenticationMethods:
+  eduGAIN:
+  Google:
+    id: "XXXXXX"
+    secret: "XXXXXX"
+    callback: "/auth/social/google"
+  Facebook:
+    id: "XXXXXX"
+    secret: "XXXXXX"
+    version: "v3.0"
+    callback: "/auth/social/facebook/token"
+  LinkedIn:
+    id: "XXXXXX"
+    secret: "XXXXXX"
+    callback: "/auth/social/linkedin/token"
+  X509:
+  LocalIdP:
+    url: "https://sso.ia2.inaf.it/Shibboleth.sso/Login?entityID=https://sso.ia2.inaf.it/idp/shibboleth&target=https://sso.ia2.inaf.it/rap-ia2-v2/auth/eduGAIN"
+    logo: "img/ia2-logo-60x60.png"
+    logoAlt: "IA2 logo"
+    description: "Use the IA2 Logo to Login if you have an account provided by IA2 or self registered"
+  OrcID:
+    id: "XXXXXX"
+    callback: "/auth/orcid"
+    secret: "XXXXXX"
+gms:
+  id: "gms"
+  joinEndpoint: "https://sso.ia2.inaf.it/gms/ws/jwt/join"
+tokenIssuer:
+  services:
+  - id: fileserver
+    label: File Server
+    aud: [fileserver, rap]
+    scope: "read:fileserver read:rap"
+  lifespans: [1, 6, 12, 24]
+clients:
+  - label: "GMS Test (localhost)"
+    id: gms
+    secret: "XXXXXX"
+    redirect: http://localhost:8082/gms/login
+    scope: "openid email"
+    home: http://localhost:8082/gms
+    icon: 
+    showInHome: true
+    methods: [eduGAIN, Google, Facebook, LinkedIn, X.509, LocalIdP]
+    jwks:
+  - label: "Asiago Astrophysical Observatory (localhost)"
+    id: aao-dev
+    secret: "XXXXXX"
+    redirect: http://localhost:8081/aao/login
+    scope: "openid read:userspace write:userspace read:fileserver write:fileserver read:gms"
+    home: http://localhost:8081/aao
+    icon: asiago.gif
+    showInHome: true
+    methods: [eduGAIN, Google, Facebook, LinkedIn, X.509, LocalIdP]
+    jwks: http://localhost:8081/aao/jwks
+cliClients:
+  - id: gms_cli
+    secret: "XXXXXX"
+    scope: "read:gms write:gms read:rap"
+    audience: gms
\ No newline at end of file
diff --git a/exec/hash_password.php b/exec/hash_password.php
new file mode 100644
index 0000000..26c64ed
--- /dev/null
+++ b/exec/hash_password.php
@@ -0,0 +1,9 @@
+<?php
+
+if($argc !== 2) {
+    echo "Usage: php $argv[0] <password>\n";
+    echo "Returns the hashed password.\n";
+    exit(1);
+}
+
+echo hash('sha256', $argv[1]) . "\n";
diff --git a/include/admin.php b/include/admin.php
index 8aab087..b1380f1 100644
--- a/include/admin.php
+++ b/include/admin.php
@@ -78,47 +78,6 @@ Flight::route('GET /admin-search', function() {
     Flight::json($users);
 });
 
-Flight::route('GET /admin/oauth2_clients', function() {
-
-    checkUser();
-    global $locator;
-
-    $clients = $locator->getOAuth2ClientDAO()->getOAuth2Clients();
-
-    Flight::json($clients);
-});
-
-Flight::route('POST /admin/oauth2_clients', function() {
-
-    checkUser();
-    global $locator;
-
-    $client = $locator->getOAuth2ClientDAO()->createOAuth2Client(buildOAuth2ClientFromData());
-
-    Flight::json($client);
-});
-
-Flight::route('PUT /admin/oauth2_clients', function() {
-
-    checkUser();
-    global $locator;
-
-    $client = $locator->getOAuth2ClientDAO()->updateOAuth2Client(buildOAuth2ClientFromData());
-
-    Flight::json($client);
-});
-
-Flight::route('DELETE /admin/oauth2_clients/@id', function($id) {
-
-    checkUser();
-    global $locator;
-
-    $locator->getOAuth2ClientDAO()->deleteOAuth2Client($id);
-
-    // Return no content
-    Flight::halt(204);
-});
-
 Flight::route('POST /admin/keypair', function() {
 
     checkUser();
@@ -133,7 +92,7 @@ Flight::route('POST /admin/keypair', function() {
 function buildOAuth2ClientFromData() {
 
     $data = Flight::request()->data;
-    $client = new \RAP\OAuth2Client();
+    $client = new \RAP\BrowserBasedOAuth2Client();
 
     if (isset($data)) {
         if (isset($data['id'])) {
diff --git a/include/front-controller.php b/include/front-controller.php
index 07fec11..b7f9b2c 100644
--- a/include/front-controller.php
+++ b/include/front-controller.php
@@ -32,7 +32,7 @@ Flight::route('/', function() {
     switch ($action) {
         case "oauth2client":
             $clientId = $locator->getSession()->getOAuth2RequestData()->clientId;
-            $client = $locator->getOAuth2ClientDAO()->getOAuth2ClientByClientId($clientId);
+            $client = $locator->getBrowserBasedOAuth2ClientById($clientId);
             $authPageModel = new \RAP\AuthPageModel($locator, $client);
             renderMainPage($authPageModel);
             break;
@@ -51,7 +51,7 @@ Flight::route('/', function() {
             break;
         default:
             session_destroy();
-            $clients = $locator->getOAuth2ClientDAO()->getOAuth2Clients();
+            $clients = $locator->getBrowserBasedOAuth2Clients();
             Flight::render('services-list.php', array('title' => 'RAP',
                 'version' => $locator->getVersion(),
                 'contextRoot' => $locator->config->contextRoot,
@@ -130,7 +130,8 @@ Flight::route('POST /auth/oauth2/check_token', function() {
     global $locator;
 
     $requestHandler = new \RAP\OAuth2RequestHandler($locator);
-    $result = $requestHandler->handleCheckTokenRequest();
+    $headers = apache_request_headers();
+    $result = $requestHandler->handleCheckTokenRequest($headers);
 
     Flight::json($result);
 });
diff --git a/include/init.php b/include/init.php
index de9808c..88a97c3 100644
--- a/include/init.php
+++ b/include/init.php
@@ -50,7 +50,9 @@ spl_autoload_register(function ($class) {
 include ROOT . '/vendor/autoload.php';
 
 // Loading configuration
-$config = json_decode(file_get_contents(ROOT . '/config.json'));
+$config = yaml_parse_file(ROOT . '/config.yaml');
+// Transforming array to object:
+$config = json_decode(json_encode($config), FALSE);
 
 // Generating locator (global registry)
 $locator = new \RAP\Locator($config);
diff --git a/tests/OAuth2RequestHandlerTest.php b/tests/OAuth2RequestHandlerTest.php
index e24171a..f970eed 100644
--- a/tests/OAuth2RequestHandlerTest.php
+++ b/tests/OAuth2RequestHandlerTest.php
@@ -24,17 +24,21 @@ final class OAuth2RequestHandlerTest extends TestCase {
 
         $params = [
             "client_id" => "client_id",
-            "redirect_uri" => "redirect_uri",
+            "redirect_uri" => "invalid_redirect_uri",
             "state" => "state",
             "alg" => null,
+            "nonce" => null,
             "scope" => "email%20profile"
         ];
 
-        $daoStub = $this->createMock(\RAP\OAuth2ClientDAO::class);
-        $daoStub->method('getOAuth2ClientByClientId')->willReturn(new \RAP\OAuth2Client());
-
         $locatorStub = $this->createMock(\RAP\Locator::class);
-        $locatorStub->method('getOAuth2ClientDAO')->willReturn($daoStub);
+        $locatorStub->method('getBrowserBasedOAuth2ClientById')->willReturn(new \RAP\BrowserBasedOAuth2Client((object) [
+                            "id" => "client_id",
+                            "secret" => hash('sha256', "foo"),
+                            "redirect" => "redirect_uri",
+                            "scope" => "email profile",
+                            "methods" => []
+        ]));
 
         $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
         $requestHandler->handleAuthorizeRequest($params);
@@ -51,15 +55,19 @@ final class OAuth2RequestHandlerTest extends TestCase {
             "scope" => "email%20profile"
         ];
 
-        $daoStub = $this->createMock(\RAP\OAuth2ClientDAO::class);
-        $client = new \RAP\OAuth2Client();
+        $client = new \RAP\BrowserBasedOAuth2Client((object) [
+                    "id" => "client_id",
+                    "secret" => hash('sha256', "foo"),
+                    "redirect" => "redirect_uri",
+                    "scope" => "email profile",
+                    "methods" => []
+        ]);
         $client->redirectUrl = "redirect_uri";
-        $daoStub->method('getOAuth2ClientByClientId')->willReturn($client);
 
         $sessionStub = $this->createMock(\RAP\SessionData::class);
 
         $locatorStub = $this->createMock(\RAP\Locator::class);
-        $locatorStub->method('getOAuth2ClientDAO')->willReturn($daoStub);
+        $locatorStub->method('getBrowserBasedOAuth2ClientById')->willReturn($client);
         $locatorStub->method('getSession')->willReturn($sessionStub);
 
         $sessionStub->expects($this->once())
@@ -71,31 +79,25 @@ final class OAuth2RequestHandlerTest extends TestCase {
 
     public function testHandleCheckTokenRequest(): void {
 
-        $accessToken = new \RAP\AccessToken();
-        $accessToken->clientId = 'my-client';
-        $accessToken->scope = ['openid', 'email'];
-        $accessToken->userId = '123';
-        $accessToken->expirationTime = time() + 3600;
-
-        $tokenDaoStub = $this->createMock(\RAP\AccessTokenDAO::class);
-        $tokenDaoStub->method('getAccessToken')->willReturn($accessToken);
-
-        $user = new \RAP\User();
-        $user->id = '123';
-        $userDaoStub = $this->createMock(\RAP\UserDAO::class);
-        $userDaoStub->method('findUserById')->willReturn($user);
-
         $tokenBuilderStub = $this->createMock(\RAP\TokenBuilder::class);
         $tokenBuilderStub->method('getIdToken')->willReturn('id-token');
 
+        $tokenCheckerStub = $this->createMock(\RAP\TokenChecker::class);
+        $tokenCheckerStub->method('validateToken')->willReturn((object) [
+                    "sub" => "123",
+                    "iat" => time(),
+                    "exp" => time() + 3600,
+                    "scope" => "openid email",
+                    "aud" => "my-client"
+        ]);
+
         $locatorStub = $this->createMock(\RAP\Locator::class);
-        $locatorStub->method('getAccessTokenDAO')->willReturn($tokenDaoStub);
-        $locatorStub->method('getUserDAO')->willReturn($userDaoStub);
-        $locatorStub->method('getIdTokenBuilder')->willReturn($tokenBuilderStub);
+        $locatorStub->method('getTokenChecker')->willReturn($tokenCheckerStub);
+        $locatorStub->method('getTokenBuilder')->willReturn($tokenBuilderStub);
 
         $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
 
-        $result = $requestHandler->handleCheckTokenRequest('abc');
+        $result = $requestHandler->handleCheckTokenRequest(['Authorization' => 'Bearer: <token>']);
 
         $this->assertEquals(3600, $result['exp']);
         $this->assertEquals('123', $result['user_name']);
diff --git a/tests/IdTokenBuilderTest.php b/tests/TokenBuilderTest.php
similarity index 97%
rename from tests/IdTokenBuilderTest.php
rename to tests/TokenBuilderTest.php
index 2328f2c..6161887 100644
--- a/tests/IdTokenBuilderTest.php
+++ b/tests/TokenBuilderTest.php
@@ -33,7 +33,7 @@ final class TokenBuilderTest extends TestCase {
 
         $locatorStub->config = json_decode('{"jwtIssuer": "issuer"}');
 
-        $accessToken = new \RAP\AccessToken();
+        $accessToken = new \RAP\AccessTokenData();
         $accessToken->token = "ttt";
         $accessToken->scope = ["email", "profile"];
         $accessToken->userId = "user_id";
-- 
GitLab