From a3a886de8ff8f16571db38ee72fcb9dd9d9292a6 Mon Sep 17 00:00:00 2001
From: Sonia Zorba <sonia.zorba@inaf.it>
Date: Tue, 2 Jul 2019 17:30:42 +0200
Subject: [PATCH] Started refactoring for making the code more testable. Added
 first unit test

---
 .gitignore                                 |   1 +
 README.md                                  |   2 +-
 auth/oauth2/index.php                      |  53 -------
 classes/AuthPageModel.php                  |  63 +++++++++
 classes/AuthenticationMethods.php          |  27 ++++
 classes/DAO.php                            |   6 +-
 classes/InternalClient.php                 |  15 ++
 classes/Locator.php                        |  99 ++++++++++++++
 classes/MySQLDAO.php                       |  28 ++--
 classes/OAuth2Client.php                   |  10 +-
 classes/OAuth2Data.php                     |   3 +-
 classes/OAuth2RequestHandler.php           |  64 +++++++++
 classes/RAPClient.php                      |  13 ++
 classes/SessionData.php                    |  76 ++---------
 classes/exceptions/BadRequestException.php |  13 ++
 composer.json                              |  11 ++
 config-example.json                        |  41 ++++++
 include/front-controller.php               | 135 +++++++-----------
 include/init.php                           |  31 +----
 js/admin.js                                |   4 +-
 sql/setup-database.sql                     |   2 +-
 tests/OAuth2RequestHandlerTest.php         |  60 ++++++++
 version.txt                                |   1 +
 views/account-management.php               |  74 ++++++++++
 views/index.php                            | 152 ---------------------
 views/main-page.php                        |  83 +++++++++++
 26 files changed, 653 insertions(+), 414 deletions(-)
 delete mode 100644 auth/oauth2/index.php
 create mode 100644 classes/AuthPageModel.php
 create mode 100644 classes/AuthenticationMethods.php
 create mode 100644 classes/InternalClient.php
 create mode 100644 classes/Locator.php
 create mode 100644 classes/OAuth2RequestHandler.php
 create mode 100644 classes/RAPClient.php
 create mode 100644 classes/exceptions/BadRequestException.php
 create mode 100644 config-example.json
 create mode 100644 tests/OAuth2RequestHandlerTest.php
 create mode 100644 version.txt
 create mode 100644 views/account-management.php
 delete mode 100644 views/index.php
 create mode 100644 views/main-page.php

diff --git a/.gitignore b/.gitignore
index 13cd424..f70f2e1 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 6912d4d..8003cd8 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 9dc4277..0000000
--- 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 0000000..22756fa
--- /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 0000000..9933d59
--- /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 2f0f689..51b48f1 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 0000000..dfff4a3
--- /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 0000000..d4a6883
--- /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 13f0eb0..6033582 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 a00cf7a..ba6f43f 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 7bf507b..57de8c5 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 0000000..e947368
--- /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 0000000..e6a55e8
--- /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 343a65d..8784594 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 0000000..fb7576e
--- /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 a49af19..522fbbd 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 0000000..7cf6ab4
--- /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 5094c7d..0c2d79e 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 67fa2ba..b366300 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 ba5d8f0..fd34841 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 e65cb2a..c4faac8 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 0000000..a380151
--- /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 0000000..359a5b9
--- /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 0000000..d985159
--- /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">&times;</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">&times;</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 ebcc6d9..0000000
--- 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">&times;</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">&times;</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 0000000..36bc2d3
--- /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';
+
-- 
GitLab