diff --git a/.gitignore b/.gitignore index c014544fac7bec4aa260424a239cd448dadaeb9a..4287e45c9b21e39a0ebb51ac4e6e2d866ec880d2 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 462ca07ab2fd345a295176f54a21d473458c9174..ea6024c3a918fce2eed5ad454bbcabedf002cb50 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 8bb33e2fdd76623d02559202b51209dab079d099..497f9bbc385eb2e8b3845cb6c8532b3474904f4b 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 0000000000000000000000000000000000000000..5ebe6230fff1552dd4193caba0c408fd84412b73 --- /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 d032f70b14cbb85d0c17ab8eeed2441df8935fad..956b273cc485b26d45af2e6da0d54c6ea75cd4cf 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 8adc55158db553395669c8bbaf91a969cad3c5c4..084d43c2c7f8cb7ef8353cfb73ab14ebc78c240a 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 27bf313791e01bbe4e4d2b766e7b4f84c50643b5..e64162d936305e9d695c2094a7c5f2d1ad469795 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 944ff6e7589e0a9ccb90844e7066a744782f23d0..0000000000000000000000000000000000000000 --- 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 c005b692b15461025ed51790ce3e756c130222d8..0000000000000000000000000000000000000000 --- 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 bf90c89f2b2d5baf68f675cfc45bd827dc580b5e..05ed340ea61e03be4d26b9c8315a2cb7ea085d88 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 e6a55e8fc30f0c4d777fe23ef9a4e33f400f92f2..437ec3df82902519cb3727bbe5bf87b5168ba3a0 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 acad698f384be71d3ae51df48f1d7389e514dd99..fce73fa665ffe1e72ad87792d86deb6ea38b7013 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 d10f719177f276a71e4cc62a7dcd41ced7ecca32..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..aabba3e09d9e61553eab9827b2faa3512e96441d --- /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 f91f7d0077244fca781c50469a6dcdbdc64e66d6..a7b4b5298cae43e7b0db8e944f2c7263ca71e5ca 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 adc90f42e72e9efc7a17b8463401905abccf4446..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..fcd8cdb97e965b761012abff8f2c7ed7d8b2904b --- /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 0000000000000000000000000000000000000000..26c64ed86f5d1ddd0edcb66a184a361e0d33404e --- /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 8aab087497561b437f627cb43f5452008286c53e..b1380f1abb8d48f6de7c083e1884dc47d5659627 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 07fec11511c56268ffe05b20481ad603704ab68f..b7f9b2cd17b324f0fd73ce0a21c356472e82a250 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 de9808cdb95cc9798dc81d7ee64f9c945cc11aea..88a97c3e360dae7c18ce425638cdad13872d0e3c 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 e24171a449a595b89b83a976c6197098ecb7d90e..f970eede895acc7deb2992b96d721b3b8f45b9e8 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 2328f2c7bcc72080a617297fb20d01fea8d95c70..61618878dd51cebe6623b302bf67e93ab09c6bfc 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";