diff --git a/.gitignore b/.gitignore index 13cd4245dc140b4152008469b035ec904a5f00a8..f70f2e112eb1ef6f2f6d9ac0bc3ad73689ff02d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ composer.lock config.php +config.json logs/ vendor/ client-icons/ diff --git a/README.md b/README.md index 6912d4dd51b13a41c66293c1c65ee19e86b6f72b..8003cd82a3865dbffa5e224505f1a57b2a102674 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 + sudo apt install apache2 mariadb-server libapache2-mod-php mariadb-server php7.2-xml ### PHP diff --git a/auth/oauth2/index.php b/auth/oauth2/index.php deleted file mode 100644 index 9dc4277afb870e40f4213f24b4af8f05f23c85d8..0000000000000000000000000000000000000000 --- a/auth/oauth2/index.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -include '../../include/init.php'; -startSession(); -global $dao; - -if (!isset($_REQUEST['client_id'])) { - http_response_code(400); - die("Client id is required"); -} - -if (!isset($_REQUEST['redirect_uri'])) { - http_response_code(400); - die("Redirect URI is required"); -} - -$clientId = $_REQUEST['client_id']; -$redirectUrl = $_REQUEST['redirect_uri']; - -$client = $dao->getOAuth2ClientByClientId($clientId); -if ($client === null) { - http_response_code(400); - die("Invalid client id: " . $clientId); -} -if ($client->redirectUrl !== $redirectUrl) { - http_response_code(400); - die("Invalid client redirect URI: " . $redirectUrl); -} - -$alg; -if (isset($_REQUEST['alg'])) { - $alg = $_REQUEST['alg']; -} else { - $alg = "RS256"; -} - -if (isset($_GET['code'])) { - -} else { - if (!isset($_REQUEST['state'])) { - http_response_code(400); - die("State is required"); - } -} - -$oauth2Data = new \RAP\OAuth2Data(); -$oauth2Data->clientName = $client->name; -$oauth2Data->clientIcon = $client->icon; -$oauth2Data->clientId = $client->id; -$oauth2Data->redirectUrl = $client->redirectUrl; - -global $session; -$session->setOAuth2Data($oauth2Data); \ No newline at end of file diff --git a/classes/AuthPageModel.php b/classes/AuthPageModel.php new file mode 100644 index 0000000000000000000000000000000000000000..22756fa0f3b91bd844c1cfdbe5907e5b4a589099 --- /dev/null +++ b/classes/AuthPageModel.php @@ -0,0 +1,63 @@ +<?php + +namespace RAP; + +/** + * Model for the main RAP page (authentication method choice). + */ +class AuthPageModel { + + // boolean flags + public $eduGAIN; + public $orcid; + public $x509; + public $google; + public $facebook; + public $linkedIn; + public $localIdP; + // + public $clientIcon; + public $clientTitle; + public $localIdPConfig; + + public function __construct(\RAP\Locator $locator, \RAP\RAPClient $client) { + + $config = $locator->config; + + $this->setupAuthenticationMethodFlags($config, $client); + + if ($this->localIdP) { + $this->localIdPConfig = $config->authenticationMethods->LocalIdP; + } + + if (isset($client->icon)) { + $this->clientIcon = $client->getIconBasePath() . $client->icon; + } + $this->clientTitle = $client->title; + } + + private function setupAuthenticationMethodFlags($config, $client) { + + $this->eduGAIN = isset($config->authenticationMethods->eduGAIN) && + in_array(AuthenticationMethods::EDU_GAIN, $client->authMethods); + + $this->orcid = isset($config->authenticationMethods->Orcid) && + in_array(AuthenticationMethods::ORCID, $client->authMethods); + + $this->x509 = isset($config->authenticationMethods->X509) && + in_array(AuthenticationMethods::X509, $client->authMethods); + + $this->google = isset($config->authenticationMethods->Google) && + in_array(AuthenticationMethods::GOOGLE, $client->authMethods); + + $this->facebook = isset($config->authenticationMethods->Facebook) && + in_array(AuthenticationMethods::FACEBOOK, $client->authMethods); + + $this->linkedIn = isset($config->authenticationMethods->LinkedIn) && + in_array(AuthenticationMethods::LINKED_IN, $client->authMethods); + + $this->localIdP = isset($config->authenticationMethods->LocalIdP) && + in_array(AuthenticationMethods::LOCAL_IDP, $client->authMethods); + } + +} diff --git a/classes/AuthenticationMethods.php b/classes/AuthenticationMethods.php new file mode 100644 index 0000000000000000000000000000000000000000..9933d594077efc5a5d299d924b3b3bd9c58379fb --- /dev/null +++ b/classes/AuthenticationMethods.php @@ -0,0 +1,27 @@ +<?php + +namespace RAP; + +abstract class AuthenticationMethods { + + const EDU_GAIN = "eduGAIN"; + const ORCID = "Orcid"; + const X509 = "X.509"; + const GOOGLE = "Google"; + const LINKED_IN = "LinkedIn"; + const FACEBOOK = "Facebook"; + const LOCAL_IDP = "LocalIdP"; + + public static function getAllMethods() { + return [ + AuthenticationMethods::EDU_GAIN, + AuthenticationMethods::ORCID, + AuthenticationMethods::X509, + AuthenticationMethods::GOOGLE, + AuthenticationMethods::LINKED_IN, + AuthenticationMethods::FACEBOOK, + AuthenticationMethods::LOCAL_IDP + ]; + } + +} diff --git a/classes/DAO.php b/classes/DAO.php index 2f0f68922461b8b84d93aeca19d8359fc494e5ca..51b48f141ff15a6649111fc1ad135862b81adc41 100644 --- a/classes/DAO.php +++ b/classes/DAO.php @@ -130,9 +130,9 @@ interface DAO { */ function getOAuth2Clients(); - function createOAuth2Client($client); + function createOAuth2Client($client) : OAuth2Client; - function updateOAuth2Client($client); + function updateOAuth2Client($client) : OAuth2Client; function deleteOAuth2Client($clientId); @@ -140,5 +140,5 @@ interface DAO { * Retrieve the client from the configured client id (the one associated to * the secret, not the database id). */ - function getOAuth2ClientByClientId($clientId); + function getOAuth2ClientByClientId($clientId) : ?OAuth2Client; } diff --git a/classes/InternalClient.php b/classes/InternalClient.php new file mode 100644 index 0000000000000000000000000000000000000000..dfff4a31f8ff8c1ca346ae155bde1f09cfe314b3 --- /dev/null +++ b/classes/InternalClient.php @@ -0,0 +1,15 @@ +<?php + +namespace RAP; + +class InternalClient extends RAPClient { + + public function __construct() { + $this->authMethods = AuthenticationMethods::getAllMethods(); + } + + public function getIconBasePath() { + return 'service-logos/'; + } + +} diff --git a/classes/Locator.php b/classes/Locator.php new file mode 100644 index 0000000000000000000000000000000000000000..d4a6883e3e61bb152667a240eb8c403c0b8229dd --- /dev/null +++ b/classes/Locator.php @@ -0,0 +1,99 @@ +<?php + +namespace RAP; + +/** + * Class implementing the locator pattern in order to implement a rough dependency injection. + */ +class Locator { + + public $config; + private $serviceLogger; + private $auditLogger; + private $dao; + private $session; + private $version; + + public function __construct($config) { + $this->config = $config; + + $this->setupLoggers(); + $this->setupDAO(); + $this->version = file_get_contents(ROOT . '/version.txt'); + } + + public function getVersion(): string { + return $this->version; + } + + public function getProtocol(): string { + return stripos($_SERVER['SERVER_PROTOCOL'], 'https') ? 'https://' : 'http://'; + } + + public function getBasePath(): string { + return $this->getProtocol() . $_SERVER['HTTP_HOST'] . $this->config->contextRoot; + } + + public function getDAO(): DAO { + return $this->dao; + } + + public function getCallbackHandler(): CallbackHandler { + return new \RAP\CallbackHandler($dao, $this->getBasePath()); + } + + public function getUserHandler(): UserHandler { + return new \RAP\UserHandler($this->dao); + } + + public function getMailSender(): MailSender { + return new \RAP\MailSender($_SERVER['HTTP_HOST'], $this->getBasePath()); + } + + /** + * Retrieve the SessionData object from the $_SESSION PHP variable. Create a + * new one if it is necessary. + */ + public function getSession(): SessionData { + if (isset($_SESSION[\RAP\SessionData::KEY])) { + $this->session = $_SESSION[SessionData::KEY]; + } else { + $this->session = new \RAP\SessionData(); + $this->session->save(); + } + return $this->session; + } + + public function getServiceLogger() { + return $this->serviceLogger; + } + + public function getAuditLogger() { + return $this->auditLogger; + } + + private function setupLoggers() { + // Monolog require timezone to be set + date_default_timezone_set($this->config->timeZone); + + $logLevel = array_search($this->config->logLevel, \Monolog\Logger::getLevels()); + + $this->serviceLogger = new \Monolog\Logger('serviceLogger'); + $this->serviceLogger->pushHandler(new \Monolog\Handler\StreamHandler($this->config->serviceLogFile, $logLevel)); + + $this->auditLogger = new \Monolog\Logger('auditLogger'); + $this->auditLogger->pushHandler(new \Monolog\Handler\StreamHandler($this->config->auditLogFile, $logLevel)); + } + + private function setupDAO() { + $databaseConfig = $this->config->databaseConfig; + switch ($databaseConfig->dbtype) { + case 'MySQL': + $this->dao = new \RAP\MySQLDAO($databaseConfig); + break; + default: + throw new Exception($databaseConfig->dbtype . ' not supported yet'); + } + } + +} diff --git a/classes/MySQLDAO.php b/classes/MySQLDAO.php index 13f0eb0399207259902a0397fcee23784c4fad67..6033582830ae93c5bbeea71f95ef0ca05e926c38 100644 --- a/classes/MySQLDAO.php +++ b/classes/MySQLDAO.php @@ -38,8 +38,8 @@ class MySQLDAO implements DAO { } public function getDBHandler() { - $connectionString = "mysql:host=" . $this->config['hostname'] . ";dbname=" . $this->config['dbname']; - $dbh = new PDO($connectionString, $this->config['username'], $this->config['password']); + $connectionString = "mysql:host=" . $this->config->hostname . ";dbname=" . $this->config->dbname; + $dbh = new PDO($connectionString, $this->config->username, $this->config->password); // For transaction errors (see https://stackoverflow.com/a/9659366/771431) $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); return $dbh; @@ -336,7 +336,7 @@ class MySQLDAO implements DAO { $dbh = $this->getDBHandler(); // Load clients info - $queryClient = "SELECT id, name, icon, client, secret, redirect_url, scope FROM oauth2_client"; + $queryClient = "SELECT id, title, icon, client, secret, redirect_url, scope FROM oauth2_client"; $stmtClients = $dbh->prepare($queryClient); $stmtClients->execute(); @@ -345,7 +345,7 @@ class MySQLDAO implements DAO { foreach ($stmtClients->fetchAll() as $row) { $client = new OAuth2Client(); $client->id = $row['id']; - $client->name = $row['name']; + $client->title = $row['title']; $client->icon = $row['icon']; $client->client = $row['client']; $client->secret = $row['secret']; @@ -373,16 +373,16 @@ class MySQLDAO implements DAO { return $clients; } - function createOAuth2Client($client) { + function createOAuth2Client($client) : OAuth2Client { $dbh = $this->getDBHandler(); try { $dbh->beginTransaction(); - $stmt = $dbh->prepare("INSERT INTO `oauth2_client`(`name`, `icon`, `client`, `secret`, `redirect_url`, `scope`)" - . " VALUES(:name, :icon, :client, :secret, :redirect_url, :scope)"); + $stmt = $dbh->prepare("INSERT INTO `oauth2_client`(`title`, `icon`, `client`, `secret`, `redirect_url`, `scope`)" + . " VALUES(:title, :icon, :client, :secret, :redirect_url, :scope)"); - $stmt->bindParam(':name', $client->name); + $stmt->bindParam(':title', $client->title); $stmt->bindParam(':icon', $client->icon); $stmt->bindParam(':client', $client->client); $stmt->bindParam(':secret', $client->secret); @@ -412,17 +412,17 @@ class MySQLDAO implements DAO { return $client; } - function updateOAuth2Client($client) { + function updateOAuth2Client($client) : OAuth2Client { $dbh = $this->getDBHandler(); try { $dbh->beginTransaction(); - $stmt = $dbh->prepare("UPDATE `oauth2_client` SET `name` = :name, `icon` = :icon, " + $stmt = $dbh->prepare("UPDATE `oauth2_client` SET `title` = :title, `icon` = :icon, " . " `client` = :client, `secret` = :secret, `redirect_url` = :redirect_url, `scope` = :scope " . " WHERE id = :id"); - $stmt->bindParam(':name', $client->name); + $stmt->bindParam(':title', $client->title); $stmt->bindParam(':icon', $client->icon); $stmt->bindParam(':client', $client->client); $stmt->bindParam(':secret', $client->secret); @@ -478,11 +478,11 @@ class MySQLDAO implements DAO { } } - function getOAuth2ClientByClientId($clientId) { + function getOAuth2ClientByClientId($clientId) : ?OAuth2Client { $dbh = $this->getDBHandler(); // Load clients info - $queryClient = "SELECT id, name, icon, client, secret, redirect_url, scope FROM oauth2_client WHERE client = :client"; + $queryClient = "SELECT id, title, icon, client, secret, redirect_url, scope FROM oauth2_client WHERE client = :client"; $stmtClient = $dbh->prepare($queryClient); $stmtClient->bindParam(':client', $clientId); $stmtClient->execute(); @@ -500,7 +500,7 @@ class MySQLDAO implements DAO { $client = new OAuth2Client(); $client->id = $row['id']; - $client->name = $row['name']; + $client->title = $row['title']; $client->icon = $row['icon']; $client->client = $row['client']; $client->secret = $row['secret']; diff --git a/classes/OAuth2Client.php b/classes/OAuth2Client.php index a00cf7ad5b3db5cc334a115857a8cb7cc925c7d3..ba6f43f851a8f0732b3c690cba3571a54b36cbe7 100644 --- a/classes/OAuth2Client.php +++ b/classes/OAuth2Client.php @@ -27,16 +27,16 @@ namespace RAP; /** * Data model for storing information about a RAP client connecting using OAuth2. */ -class OAuth2Client { +class OAuth2Client extends RAPClient { public $id; - public $name; - public $icon; public $client; public $secret; public $redirectUrl; public $scope; - // list of AuthN methods - public $authMethods = []; + + public function getIconBasePath() { + return 'client-icons/'; + } } diff --git a/classes/OAuth2Data.php b/classes/OAuth2Data.php index 7bf507b7c76f58788317afeacf67299e100799fe..57de8c5950f342cc666ec302daecd9e6946e6e08 100644 --- a/classes/OAuth2Data.php +++ b/classes/OAuth2Data.php @@ -4,9 +4,8 @@ namespace RAP; class OAuth2Data { - public $clientName; - public $clientIcon; public $clientId; public $redirectUrl; + public $state; } diff --git a/classes/OAuth2RequestHandler.php b/classes/OAuth2RequestHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..e947368bf53c0ede8360ca2c69371ee262a7ac2c --- /dev/null +++ b/classes/OAuth2RequestHandler.php @@ -0,0 +1,64 @@ +<?php + +namespace RAP; + +class OAuth2RequestHandler { + + private $locator; + + public function __construct(\RAP\Locator $locator) { + $this->locator = $locator; + } + + public function handleAuthorizeRequest() { + + if (!isset($_REQUEST['client_id'])) { + throw new \RAP\BadRequestException("Client id is required"); + } + + if (!isset($_REQUEST['redirect_uri'])) { + throw new \RAP\BadRequestException("Redirect URI is required"); + } + + $clientId = $_REQUEST['client_id']; + $redirectUrl = $_REQUEST['redirect_uri']; + + $client = $this->locator->getDAO()->getOAuth2ClientByClientId($clientId); + if ($client === null) { + throw new \RAP\BadRequestException("Invalid client id: " . $clientId); + } + if ($client->redirectUrl !== $redirectUrl) { + throw new \RAP\BadRequestException("Invalid client redirect URI: " . $redirectUrl); + } + + $alg; + if (isset($_REQUEST['alg'])) { + $alg = $_REQUEST['alg']; + } else { + $alg = "RS256"; + } + + if (isset($_GET['code'])) { + + } else { + $this->executeStateFlow($client); + } + } + + private function executeStateFlow(OAuth2Client $client) { + + if (!isset($_REQUEST['state'])) { + throw new \RAP\BadRequestException("State is required"); + } + + // Storing OAuth2 data in session + $oauth2Data = new \RAP\OAuth2Data(); + $oauth2Data->clientId = $client->client; + $oauth2Data->redirectUrl = $client->redirectUrl; + $oauth2Data->state = $_REQUEST['state']; + + $session = $this->locator->getSession(); + $session->setOAuth2Data($oauth2Data); + } + +} diff --git a/classes/RAPClient.php b/classes/RAPClient.php new file mode 100644 index 0000000000000000000000000000000000000000..e6a55e8fc30f0c4d777fe23ef9a4e33f400f92f2 --- /dev/null +++ b/classes/RAPClient.php @@ -0,0 +1,13 @@ +<?php + +namespace RAP; + +abstract class RAPClient { + + public $title; + public $icon; + // list of AuthN methods supported by the client + public $authMethods = []; + + public abstract function getIconBasePath(); +} diff --git a/classes/SessionData.php b/classes/SessionData.php index 343a65ddfc53da4aad2ce14b5bfc826fd043199e..878459407f3ab67fbde751dcd905b8c7c919cba9 100644 --- a/classes/SessionData.php +++ b/classes/SessionData.php @@ -30,10 +30,8 @@ namespace RAP; */ class SessionData { - private $dao; - private $callbackURL; - private $callbackTitle; - private $callbackLogo; + const KEY = "SessionData"; + public $user; public $userSearchResults; public $x509DataToRegister; @@ -43,73 +41,11 @@ class SessionData { public $userToLogin; public $oauth2Data; - /** - * @todo: move DAO away from here - */ - public function __construct(DAO $dao) { - $this->dao = $dao; - } - /** * Store the data into the $_SESSION PHP variable */ public function save() { - $_SESSION['SessionData'] = $this; - } - - /** - * Retrieve the SessionData object from the $_SESSION PHP variable. Create a - * new one if it is necessary. - * @param \RAP\DAO $dao - * @return \RAP\SessionData the SessionData object - */ - public static function get(DAO $dao) { - - if (!isset($_SESSION['SessionData'])) { - $session = new SessionData($dao); - $session->save(); - } - return $_SESSION['SessionData']; - } - - public function setCallbackURL(CallbackHandler $callbackHandler, $callbackURL) { - $this->callbackURL = $callbackHandler->filterCallbackURL($callbackURL); - $this->callbackTitle = $callbackHandler->getCallbackTitle($callbackURL); - $this->callbackLogo = $callbackHandler->getCallbackLogo($callbackURL); - $this->save(); - } - - public function getCallbackURL() { - return $this->callbackURL; - } - - public function getCallbackTitle() { - return $this->callbackTitle; - } - - public function getCallbackLogo() { - return $this->callbackLogo; - } - - /** - * Perform a user search and store the results inside the session. This is - * used for achieving the user selection using the dropdown menu in the join - * request modal. - * @param string $searchText - */ - public function searchUser($searchText) { - $users = $this->dao->searchUser($searchText); - - $this->userSearchResults = []; - foreach ($users as $user) { - // this search shouldn't contains the user itself - if ($user->id !== $this->user->id) { - $searchResult = UserSearchResult::buildFromUser($user); - array_push($this->userSearchResults, $searchResult); - } - } - - $this->save(); + $_SESSION[SessionData::KEY] = $this; } /** @@ -124,9 +60,13 @@ class SessionData { } } - public function setOAuth2Data($oauth2Data) { + public function setOAuth2Data(OAuth2Data $oauth2Data) { $this->oauth2Data = $oauth2Data; $this->save(); } + public function getOAuth2Data(): ?OAuth2Data { + return $this->oauth2Data; + } + } diff --git a/classes/exceptions/BadRequestException.php b/classes/exceptions/BadRequestException.php new file mode 100644 index 0000000000000000000000000000000000000000..fb7576e6f534b692466d6c872a11dec8a18bb7bc --- /dev/null +++ b/classes/exceptions/BadRequestException.php @@ -0,0 +1,13 @@ +<?php + +namespace RAP; + +class BadRequestException extends \Exception { + + public $message; + + public function __construct($message) { + $this->message = $message; + } + +} diff --git a/composer.json b/composer.json index a49af190f691b89e0b02da0048ed489b96ba7a15..522fbbdebb644f899532e5366bb76d5c66be43b9 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,20 @@ { + "name": "ia2/rap", + "description": "Remote Authentication Portal", + "license": "GPL-3.0-or-later", "require": { "mikecao/flight": "1.3.2", "google/apiclient": "2.1.3", "facebook/graph-sdk": "^5.5", "monolog/monolog": "^1.22", "phpmailer/phpmailer": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "autoload": { + "classmap": [ + "classes/" + ] } } diff --git a/config-example.json b/config-example.json new file mode 100644 index 0000000000000000000000000000000000000000..7cf6ab4b58b1124f84a479c8314d5e30c08fc54c --- /dev/null +++ b/config-example.json @@ -0,0 +1,41 @@ +{ + "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", + "databaseConfig": { + "dbtype": "MySQL", + "hostname": "localhost", + "port": 3306, + "username": "rap", + "password": "XXXXXX", + "dbname": "rap" + }, + "authenticationMethods": { + "eduGAIN": {}, + "Google": { + "id": "XXXXXX", + "secret": "XXXXXX", + "callback": "/auth/social/google_token.php" + }, + "Facebook": { + "id": "XXXXXX", + "secret": "XXXXXX", + "version": "v3.0", + "callback": "/auth/social/facebook_token.php" + }, + "LinkedIn": { + "id": "XXXXXX", + "secret": "XXXXXX", + "callback": "/auth/social/linkedin_token.php" + }, + "X.509": {}, + "DirectIdP": { + "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/auth/saml2/aai.php", + "logo": "img/ia2-logo-60x60.png", + "logo_alt": "IA2 logo", + "description": "Use the IA2 Logo to Login if you have an account provided by IA2 or self registered" + } + } +} \ No newline at end of file diff --git a/include/front-controller.php b/include/front-controller.php index 5094c7d876325ce9034593d89004e06b1eedf6d2..0c2d79e14032daf4e1e57788cee28ac47fb95df9 100644 --- a/include/front-controller.php +++ b/include/front-controller.php @@ -3,7 +3,7 @@ /** * Front Controller using http://flightphp.com/ * In all these calls user session must exist, so we have to start it at the - * beginning using the startSession() function. + * beginning using the session_start() function. */ // @@ -22,25 +22,52 @@ function setCallback($callback) { * services list if a valid callback is not found */ Flight::route('/', function() { - startSession(); - $callback = setCallback(Flight::request()->data['callback']); - global $session, $callbackHandler, $BASE_PATH, $AUTHENTICATION_METHODS, $VERSION; - if ($callback === null && $session->user === null) { - Flight::render('services-list.php', array('title' => 'RAP', - 'version' => $VERSION, - 'action' => $BASE_PATH . '/')); - } else if ($callback !== null && $session->user !== null) { - $redirectURL = $callbackHandler->getLoginWithTokenURL($session->user->id, $callback); - Flight::redirect($redirectURL); - } else { - Flight::render('index.php', array('title' => 'RAP', - 'version' => $VERSION, - 'session' => $session, 'auth' => $AUTHENTICATION_METHODS)); + + session_start(); + global $locator; + + $action = Flight::request()->query['action']; + + switch ($action) { + case "oaut2client": + $clientId = $locator->getSession()->getOAuth2Data()->clientId; + $client = $locator->getDAO()->getOAuth2ClientByClientId($clientId); + $authPageModel = new \RAP\AuthPageModel($locator, $client); + renderMainPage($authPageModel); + break; + default: + Flight::render('services-list.php', array('title' => 'RAP', + 'version' => $locator->getVersion(), + 'action' => $locator->getBasePath() . '/')); + break; } }); +function renderMainPage(RAP\AuthPageModel $authPageModel) { + global $locator; + Flight::render('main-page.php', array('title' => 'RAP', + 'version' => $locator->getVersion(), 'model' => $authPageModel)); +} + +Flight::route('/oauth2/authorize', function() { + + session_start(); + global $locator; + + $requestHandler = new \RAP\OAuth2RequestHandler($locator); + $requestHandler->handleAuthorizeRequest(); + + Flight::redirect('/?action=oaut2client'); +}); + +Flight::route('GET /admin', function() { + + session_start(); + global $locator; +}); + Flight::route('GET /logout', function() { - startSession(); + session_start(); session_destroy(); Flight::redirect('/'); }); @@ -73,77 +100,13 @@ Flight::route('/x509', function() { sendAuthRedirect('/auth/x509/certlogin.php'); }); -Flight::route('/direct', function() { +Flight::route('/local', function() { global $AUTHENTICATION_METHODS; sendAuthRedirect($AUTHENTICATION_METHODS['DirectIdP']['url']); }); /** - * Render the join confirmation page (confirmation link received by email). - */ -Flight::route('GET /confirm-join', function() { - $token = Flight::request()->query['token']; - - if ($token === null) { - http_response_code(422); - die("Token not found"); - } - - global $dao, $VERSION; - - $userIds = $dao->findJoinRequest($token); - if ($userIds === null) { - http_response_code(422); - die("Invalid token"); - } - - $applicantUser = $dao->findUserById($userIds[0]); - $targetUser = $dao->findUserById($userIds[1]); - - Flight::render('confirm-join.php', array('title' => 'RAP', - 'version' => $VERSION, - 'token' => $token, - 'applicantUser' => $applicantUser, - 'targetUser' => $targetUser)); -}); - -/** - * Confirm a join and show the page containing the operation status. - */ -Flight::route('POST /confirm-join', function() { - - global $dao, $userHandler, $auditLog; - - $token = Flight::request()->data['token']; - - if ($token === null) { - http_response_code(422); - die("Token not found"); - } - - $userIds = $dao->findJoinRequest($token); - if ($userIds === null) { - http_response_code(422); - die("Invalid token"); - } - - $auditLog->info("JOIN," . $userIds[0] . "," . $userIds[1]); - - $userHandler->joinUsers($userIds[0], $userIds[1]); - $dao->deleteJoinRequest($token); - - // Force user to relogin to see changes to him/her identities - session_start(); - session_destroy(); - - global $BASE_PATH, $VERSION; - Flight::render('join-success.php', array('title' => 'Success - RAP Join Request', - 'version' => $VERSION, - 'basePath' => $BASE_PATH)); -}); - -/** - * Render the page for selecting th correct name and username from candidates + * Render the page for selecting the correct name and username from candidates * list during a X.509 registration. */ Flight::route('GET /x509-name-surname', function() { @@ -170,12 +133,12 @@ Flight::route('GET /x509-name-surname', function() { Flight::route('POST /submit-x509-name', function() { $selectedNameIndex = Flight::request()->data['selected-name']; - + error_log('index=' . $selectedNameIndex); - + startSession(); global $session, $BASE_PATH; - + if ($session->x509DataToRegister !== null) { $session->x509DataToRegister->selectCandidateName($selectedNameIndex); $session->userToLogin = $session->x509DataToRegister->toUser(); @@ -223,4 +186,4 @@ Flight::route('GET /register', function() { } }); -include 'admin.php'; \ No newline at end of file +include 'admin.php'; diff --git a/include/init.php b/include/init.php index 67fa2ba5683762c9f9005fa1797331d2c873501d..b366300b9c2c1283b3de92a6ebbfae1eab3ea0ff 100644 --- a/include/init.php +++ b/include/init.php @@ -24,7 +24,6 @@ /** * Initialization file called by all the other pages. - * Probably some global variables should be removed from here. */ define('ROOT', dirname(dirname(__FILE__))); @@ -40,31 +39,9 @@ spl_autoload_register(function ($class_name) { // Loading dependecy classes include ROOT . '/vendor/autoload.php'; -// Loading configuration -include ROOT . '/config.php'; - -// Setup logging -// Monolog require timezone to be set -date_default_timezone_set("Europe/Rome"); -$log = new Monolog\Logger('mainLogger'); -$log->pushHandler(new Monolog\Handler\StreamHandler($LOG_PATH, $LOG_LEVEL)); -$auditLog = new Monolog\Logger('auditLogger'); -$auditLog->pushHandler(new Monolog\Handler\StreamHandler($AUDIT_LOG_PATH, $LOG_LEVEL)); -switch ($DATABASE['dbtype']) { - case 'MySQL': - $dao = new RAP\MySQLDAO($DATABASE); - break; - default: - throw new Exception($DATABASE['dbtype'] . ' not supported yet'); -} - -$callbackHandler = new RAP\CallbackHandler($dao, $BASE_PATH, $CALLBACKS); -$userHandler = new RAP\UserHandler($dao, $GROUPER); -$mailSender = new RAP\MailSender($_SERVER['HTTP_HOST'], $BASE_PATH); +// Loading configuration +$config = json_decode(file_get_contents(ROOT . '/config.json')); -function startSession() { - session_start(); - global $session, $dao; - $session = RAP\SessionData::get($dao); -} +// Generating locator (global registry) +$locator = new \RAP\Locator($config); diff --git a/js/admin.js b/js/admin.js index ba5d8f0a4e46c9ba0e48df0d74e90b719f8d0c5b..fd34841bdcbdf9432db04207a2d7bb23ff1f168b 100644 --- a/js/admin.js +++ b/js/admin.js @@ -38,7 +38,7 @@ if (client.id === null) { deleteOAuth2Client(); } else { - $('#client-to-delete').text(client.name); + $('#client-to-delete').text(client.title); $('#confirm-delete-client-modal').modal('show'); } } @@ -48,7 +48,7 @@ function getNewClient() { var client = { id: null, - name: null, + title: null, icon: null, client: null, secret: null, diff --git a/sql/setup-database.sql b/sql/setup-database.sql index e65cb2acec3220cb38917baa3f44cbb24035caf4..c4faac8a8d7fdb6cf21fc46ecb74d4757e1aff6d 100644 --- a/sql/setup-database.sql +++ b/sql/setup-database.sql @@ -1,6 +1,6 @@ CREATE TABLE `oauth2_client` ( `id` int NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL, + `title` varchar(255) NOT NULL, `icon` varchar(255), `client` varchar(255) NOT NULL, `secret` varchar(255) NOT NULL, diff --git a/tests/OAuth2RequestHandlerTest.php b/tests/OAuth2RequestHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a380151b47fb3a5602f6eb0aa185fe1b8e25d1a5 --- /dev/null +++ b/tests/OAuth2RequestHandlerTest.php @@ -0,0 +1,60 @@ +<?php + +use PHPUnit\Framework\TestCase; + +final class OAuth2RequestHandlerTest extends TestCase { + + public function testBadRequestExceptionIfMissingClientId(): void { + + $this->expectException(\RAP\BadRequestException::class); + + $locatorStub = $this->createMock(\RAP\Locator::class); + + $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub); + $requestHandler->handleAuthorizeRequest(); + } + + public function testInvalidClientRedirectURI(): void { + + $this->expectException(\RAP\BadRequestException::class); + + $_REQUEST['client_id'] = "client_id"; + $_REQUEST['redirect_uri'] = "redirect_uri"; + $_REQUEST['state'] = "state"; + + $daoStub = $this->createMock(\RAP\DAO::class); + $daoStub->method('getOAuth2ClientByClientId')->willReturn(new \RAP\OAuth2Client()); + + $locatorStub = $this->createMock(\RAP\Locator::class); + $locatorStub->method('getDAO')->willReturn($daoStub); + + $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub); + $requestHandler->handleAuthorizeRequest(); + } + + public function testExecuteOAuthStateFlow(): void { + + $_REQUEST['client_id'] = "client_id"; + $_REQUEST['redirect_uri'] = "redirect_uri"; + $_REQUEST['state'] = "state"; + + $daoStub = $this->createMock(\RAP\DAO::class); + $client = new \RAP\OAuth2Client(); + $client->redirectUrl = "redirect_uri"; + $daoStub->method('getOAuth2ClientByClientId')->willReturn($client); + + $sessionStub = $this->createMock(\RAP\SessionData::class); + $sessionStub->method('setOAuth2Data'); //->willReturn(new \RAP\OAuth2Data()); + + $locatorStub = $this->createMock(\RAP\Locator::class); + $locatorStub->method('getDAO')->willReturn($daoStub); + $locatorStub->method('getSession')->willReturn($sessionStub); + + $sessionStub->expects($this->once()) + ->method('setOAuth2Data')->with($this->anything()); + + $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub); + $requestHandler->handleAuthorizeRequest(); + } + +} diff --git a/version.txt b/version.txt new file mode 100644 index 0000000000000000000000000000000000000000..359a5b952d49f3592571e2af081510656029298e --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +2.0.0 \ No newline at end of file diff --git a/views/account-management.php b/views/account-management.php new file mode 100644 index 0000000000000000000000000000000000000000..d985159160428c526fe0ca167bd94ef5c2896d61 --- /dev/null +++ b/views/account-management.php @@ -0,0 +1,74 @@ +<?php +include 'include/header.php'; +?> +<script src="js/index.js?v=<?php echo $version; ?>"></script> + +<div class="row"> + <div class="col-xs-12"> + <div class="alert alert-success hide" id="info-message-alert"> + <button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button> + <span class="glyphicon glyphicon-info-sign"></span> + <span class="info-message"></span> + </div> + </div> +</div> +<div class="row"> + <div class="col-sm-5 col-xs-12"> + <div class="panel panel-default" id="panel-identities"> + <div class="panel-heading"> + <h3 class="panel-title">Your identities</h3> + </div> + <div class="panel-body"> + <?php + $user = $session->user; + include 'include/user-data.php'; + ?> + </div> + </div> + </div> + <div class="col-sm-2 text-center"> + <button class="btn btn-success" type="button" data-toggle="modal" data-target="#search-user-modal"> + Join with another identity + </button> + </div> + <div class="col-sm-5"> + <a href="logout" class="btn btn-primary pull-right">Logout</a> + </div> +</div> + +<div class="modal fade" id="search-user-modal" tabindex="-1" role="dialog" aria-labelledby="search-user-modal-title"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="search-user-modal-title">Search user</h4> + </div> + <div class="modal-body"> + <form class="form-horizontal"> + <div class="form-group"> + <label for="user-search-text" class="col-xs-3 control-label">Search text</label> + <div class="col-xs-9"> + <input type="text" class="form-control" id="user-search-text" placeholder="Name, surname or email..."> + </div> + </div> + <div class="form-group hide" id="user-selector-group"> + <label for="user-selector" class="col-xs-3 control-label">Select user</label> + <div class="col-xs-9"> + <select id="user-selector" class="form-control"> + + </select> + </div> + </div> + </form> + </div> + <div class="modal-footer"> + <button class="btn btn-primary" type="button" id="send-join-request-btn"> + Send join request + </button> + </div> + </div> + </div> +</div> + +<?php +include 'include/footer.php'; \ No newline at end of file diff --git a/views/index.php b/views/index.php deleted file mode 100644 index ebcc6d96c47ac5b80f4f3c2e137ca99b7a99a938..0000000000000000000000000000000000000000 --- a/views/index.php +++ /dev/null @@ -1,152 +0,0 @@ -<?php -include 'include/header.php'; -?> -<script src="js/index.js?v=<?php echo $version; ?>"></script> - -<?php if ($session->user === null) { ?> - <div class="row"> - <div class="col-xs-12"> - <h1 class="text-center page-title"> - <?php - if ($session->getCallbackLogo() != null) { - ?> - <img class="service-logo" src="service-logos/<?php echo $session->getCallbackLogo(); ?>" alt="" /> - <?php - } - echo $session->getCallbackTitle(); - ?> - </h1> - </div> - </div> - <div class="row" id="auth-panel"> - <div class="col-xs-12 text-center"> - <?php if (isset($auth['eduGAIN'])) { ?> - <div class="home-box"> - <div class="img-wrapper"> - <a href="edugain?callback=<?php echo $session->getCallbackURL(); ?>"> - <img src="img/eduGain-200.png" alt="eduGAIN Logo" /> - </a> - </div> - Use the eduGAIN Logo to Login or Register to the RAP facility if you belong to an eduGAIN IdP. - </div> - <?php } ?> - <?php if (isset($auth['Google']) || isset($auth['Facebook']) || isset($auth['LinkedIn'])) { ?> - <div class="home-box"> - <div class="img-wrapper"> - <?php if (isset($auth['Google'])) { ?> - <a href="google?callback=<?php echo $session->getCallbackURL(); ?>" class="animated pulse"> - <img src="img/google-60.png" alt="Google Logo" /> - </a> - <?php } ?> - <?php if (isset($auth['Facebook'])) { ?> - <a href="facebook?callback=<?php echo $session->getCallbackURL(); ?>"> - <img src="img/facebook-60.png" alt="Facebook Logo" /> - </a> - <?php } ?> - <?php if (isset($auth['LinkedIn'])) { ?> - <a href="linkedin?callback=<?php echo $session->getCallbackURL(); ?>"> - <img src="img/linkedin-60.png" alt="LinkedIn Logo" /> - </a> - <?php } ?> - </div> - Use these Logos to Login or Register to the RAP facility with your social identity - </div> - <?php } ?> - <?php if (isset($auth['X.509'])) { ?> - <div class="home-box"> - <div class="img-wrapper"> - <a href="x509?callback=<?php echo $session->getCallbackURL(); ?>"> - <img src="img/x509-200.png" alt="X.509 Logo" /> - </a> - </div> - Use the X.509 Logo to Login with your personal certificate (IGTF and TERENA-TACAR, are allowed). - </div> - <?php } ?> - <?php if (isset($auth['DirectIdP'])) { ?> - <div class="home-box"> - <div class="img-wrapper"> - <a href="direct?callback=<?php echo $session->getCallbackURL(); ?>"> - <img src="<?php echo $auth['DirectIdP']['logo']; ?>" alt="<?php echo $auth['DirectIdP']['logo_alt']; ?>" /> - </a> - </div> - <?php echo $auth['DirectIdP']['description']; ?> - </div> - <?php } ?> - </div> - </div> -<?php } else { ?> - <div class="row"> - <div class="col-xs-12"> - <div class="alert alert-success hide" id="info-message-alert"> - <button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button> - <span class="glyphicon glyphicon-info-sign"></span> - <span class="info-message"></span> - </div> - </div> - </div> - <div class="row"> - <div class="col-sm-5 col-xs-12"> - <div class="panel panel-default" id="panel-identities"> - <div class="panel-heading"> - <h3 class="panel-title">Your identities</h3> - </div> - <div class="panel-body"> - <?php - $user = $session->user; - include 'include/user-data.php'; - ?> - </div> - </div> - </div> - <div class="col-sm-2 text-center"> - <button class="btn btn-success" type="button" data-toggle="modal" data-target="#search-user-modal"> - Join with another identity - </button> - </div> - <div class="col-sm-5"> - <a href="logout" class="btn btn-primary pull-right">Logout</a> - </div> - </div> - - <div class="modal fade" id="search-user-modal" tabindex="-1" role="dialog" aria-labelledby="search-user-modal-title"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 class="modal-title" id="search-user-modal-title">Search user</h4> - </div> - <div class="modal-body"> - <form class="form-horizontal"> - <div class="form-group"> - <label for="user-search-text" class="col-xs-3 control-label">Search text</label> - <div class="col-xs-9"> - <input type="text" class="form-control" id="user-search-text" placeholder="Name, surname or email..."> - </div> - </div> - <div class="form-group hide" id="user-selector-group"> - <label for="user-selector" class="col-xs-3 control-label">Select user</label> - <div class="col-xs-9"> - <select id="user-selector" class="form-control"> - - </select> - </div> - </div> - </form> - </div> - <div class="modal-footer"> - <button class="btn btn-primary" type="button" id="send-join-request-btn"> - Send join request - </button> - </div> - </div> - </div> - </div> -<?php } ?> - -<script> - $(document).on('click', '#auth-panel a', showWaiting); -</script> - -<?php -include 'include/footer.php'; - diff --git a/views/main-page.php b/views/main-page.php new file mode 100644 index 0000000000000000000000000000000000000000..36bc2d36b2711f5c0da4cde9c7a34c6ab994f0ce --- /dev/null +++ b/views/main-page.php @@ -0,0 +1,83 @@ +<?php +include 'include/header.php'; +?> +<script src="js/index.js?v=<?php echo $version; ?>"></script> + +<div class="row"> + <div class="col-xs-12"> + <h1 class="text-center page-title"> + <?php + if ($model->clientIcon != null) { + ?> + <img class="service-logo" src="<?php echo $model->clientIcon; ?>" alt="" /> + <?php + } + echo $model->clientTitle; + ?> + </h1> + </div> +</div> +<div class="row" id="auth-panel"> + <div class="col-xs-12 text-center"> + <?php if ($model->eduGAIN) { ?> + <div class="home-box"> + <div class="img-wrapper"> + <a href="edugain"> + <img src="img/eduGain-200.png" alt="eduGAIN Logo" /> + </a> + </div> + Use the eduGAIN Logo to Login or Register to the RAP facility if you belong to an eduGAIN IdP. + </div> + <?php } ?> + <?php if ($model->google || $model->facebook || $model->linkedIn) { ?> + <div class="home-box"> + <div class="img-wrapper"> + <?php if ($model->google) { ?> + <a href="google" class="animated pulse"> + <img src="img/google-60.png" alt="Google Logo" /> + </a> + <?php } ?> + <?php if ($model->facebook) { ?> + <a href="facebook"> + <img src="img/facebook-60.png" alt="Facebook Logo" /> + </a> + <?php } ?> + <?php if ($model->linkedIn) { ?> + <a href="linkedin"> + <img src="img/linkedin-60.png" alt="LinkedIn Logo" /> + </a> + <?php } ?> + </div> + Use these Logos to Login or Register to the RAP facility with your social identity + </div> + <?php } ?> + <?php if ($model->x509) { ?> + <div class="home-box"> + <div class="img-wrapper"> + <a href="x509"> + <img src="img/x509-200.png" alt="X.509 Logo" /> + </a> + </div> + Use the X.509 Logo to Login with your personal certificate (IGTF and TERENA-TACAR, are allowed). + </div> + <?php } ?> + <?php if ($model->localIdP) { ?> + <div class="home-box"> + <div class="img-wrapper"> + <a href="local"> + <img src="<?php echo $model->localIdPConfig->logo; ?>" alt="<?php echo $model->localIdPConfig->logoAlt; ?>" /> + </a> + </div> + <?php echo $model->localIdPConfig->description; ?> + </div> + <?php } ?> + </div> +</div> + +<script> + $(document).on('click', '#auth-panel a', showWaiting); +</script> + +<?php +include 'include/footer.php'; +