From 9d4ad05d5764b39e67a03f4d0ed773e6efe274e7 Mon Sep 17 00:00:00 2001
From: Sonia Zorba <sonia.zorba@inaf.it>
Date: Fri, 5 Jul 2019 17:27:29 +0200
Subject: [PATCH] Changes for JWT tokens

---
 .gitignore                                    |   2 +
 README.md                                     |   6 +
 classes/IdTokenBuilder.php                    |  19 +--
 classes/JWKSHandler.php                       |  34 ++++--
 classes/OAuth2RequestHandler.php              | 115 +++++++++---------
 classes/datalayer/AccessTokenDAO.php          |   2 +
 .../datalayer/mysql/MySQLAccessTokenDAO.php   |  66 ++++++----
 classes/datalayer/mysql/MySQLJWKSDAO.php      |   2 +-
 classes/model/OAuth2Data.php                  |   1 +
 classes/model/User.php                        |   1 -
 include/front-controller.php                  |  28 ++++-
 phpunit.xml                                   |   7 ++
 tests/IdTokenBuilderTest.php                  |  15 ++-
 tests/OAuth2RequestHandlerTest.php            |  53 ++++++--
 14 files changed, 226 insertions(+), 125 deletions(-)
 create mode 100644 phpunit.xml

diff --git a/.gitignore b/.gitignore
index 5d63bee..c014544 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@ vendor/
 client-icons/
 /nbproject/
 *.pem
+/build/
+.phpunit.result.cache
diff --git a/README.md b/README.md
index 8003cd8..b14acbf 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,12 @@ Create the logs directory and assign ownership to the Apache user (usually www-d
     mkdir logs
     sudo chown www-data logs
 
+### Run Unit Tests and build code coverage report
+
+(XDebug or another code coverage driver needs to be installed; e.g. `sudo apt install php-xdebug`)
+
+    ./vendor/bin/phpunit --bootstrap vendor/autoload.php --coverage-html build/coverage-report tests/
+
 ## Additional information and developer guide
 
 See the wiki: https://www.ict.inaf.it/gitlab/zorba/rap-ia2/wikis/home
diff --git a/classes/IdTokenBuilder.php b/classes/IdTokenBuilder.php
index 9b35851..4150c0c 100644
--- a/classes/IdTokenBuilder.php
+++ b/classes/IdTokenBuilder.php
@@ -12,22 +12,13 @@ class IdTokenBuilder {
         $this->locator = $locator;
     }
 
-    public function getIdToken(AccessToken $accessToken, string $alg): string {
-
-        $head = array("alg" => $alg, "typ" => "JWT");
-
-        $header = base64_encode(json_encode($head));
+    public function getIdToken(AccessToken $accessToken): string {
 
         $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();
 
-        $payloadArr = $this->createPayloadArray($accessToken);
-        $payloadArr['kid'] = $keyPair->keyId;
-
-        $payload = base64_encode(json_encode($payloadArr));
-
-        $token_value = $header . "." . $payload;
+        $payload = $this->createPayloadArray($accessToken);
 
-        return JWT::encode($token_value, $keyPair->privateKey, $alg);
+        return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId);
     }
 
     private function createPayloadArray(AccessToken $accessToken) {
@@ -48,7 +39,9 @@ class IdTokenBuilder {
         if (in_array("profile", $accessToken->scope)) {
             $payloadArr['given_name'] = $user->getName();
             $payloadArr['family_name'] = $user->getSurname();
-            $payloadArr['org'] = $user->getInstitution();
+            if ($user->getInstitution() !== null) {
+                $payloadArr['org'] = $user->getInstitution();
+            }
         }
 
         return $payloadArr;
diff --git a/classes/JWKSHandler.php b/classes/JWKSHandler.php
index 18ec90a..fb7c87e 100644
--- a/classes/JWKSHandler.php
+++ b/classes/JWKSHandler.php
@@ -20,7 +20,7 @@ class JWKSHandler {
         $rsa = new RSA();
 
         $rsa->setPrivateKeyFormat(RSA::PRIVATE_FORMAT_PKCS1);
-        $rsa->setPublicKeyFormat(RSA::PUBLIC_FORMAT_PKCS1);
+        $rsa->setPublicKeyFormat(RSA::PUBLIC_FORMAT_PKCS8);
         $result = $rsa->createKey();
 
         $keyPair = new RSAKeyPair();
@@ -41,25 +41,37 @@ class JWKSHandler {
 
         $keyPairs = $dao->getRSAKeyPairs();
 
-        $jwks = [];
+        $keys = [];
         foreach ($keyPairs as $keyPair) {
 
-            $publicKey = str_replace("\n", "", $keyPair->publicKey);
-            $publicKey = str_replace("\r", "", $publicKey);
-            $publicKey = str_replace('-----BEGIN RSA PUBLIC KEY-----', '', $publicKey);
-            $publicKey = str_replace('-----END RSA PUBLIC KEY-----', '', $publicKey);
+            $rsa = new RSA();
+            $rsa->loadKey($keyPair->publicKey);
+            $rsa->setPublicKey();
+            $publicKeyXML = $rsa->getPublicKey(RSA::PUBLIC_FORMAT_XML);
+
+            $rsaModulus = $this->getTagContent($publicKeyXML, "Modulus");
+            $rsaExponent = $this->getTagContent($publicKeyXML, "Exponent");
 
             $jwk = [];
             $jwk['kty'] = "RSA";
-            $jwk['kid'] = $keyPair->id;
+            $jwk['kid'] = $keyPair->keyId;
             $jwk['use'] = "sig";
-            $jwk['n'] = $publicKey;
-            $jwk['e'] = "AQAB";
+            $jwk['n'] = $rsaModulus;
+            $jwk['e'] = $rsaExponent;
 
-            array_push($jwks, $jwk);
+            array_push($keys, $jwk);
         }
 
-        return $jwks;
+        return [
+            "keys" => $keys
+        ];
+    }
+
+    private function getTagContent(string $publicKeyXML, string $tagname): string {
+        $matches = [];
+        $pattern = "#<\s*?$tagname\b[^>]*>(.*?)</$tagname\b[^>]*>#s";
+        preg_match($pattern, $publicKeyXML, $matches);
+        return $matches[1];
     }
 
 }
diff --git a/classes/OAuth2RequestHandler.php b/classes/OAuth2RequestHandler.php
index 5a3bbd9..ab21bf2 100644
--- a/classes/OAuth2RequestHandler.php
+++ b/classes/OAuth2RequestHandler.php
@@ -10,44 +10,31 @@ class OAuth2RequestHandler {
         $this->locator = $locator;
     }
 
-    public function handleAuthorizeRequest() {
+    public function handleAuthorizeRequest($params) {
 
-        if (!isset($_REQUEST['client_id'])) {
+        if ($params['client_id'] === null) {
             throw new BadRequestException("Client id is required");
         }
 
-        if (!isset($_REQUEST['redirect_uri'])) {
+        if ($params['redirect_uri'] === null) {
             throw new BadRequestException("Redirect URI is required");
         }
 
-        $clientId = $_REQUEST['client_id'];
-        $redirectUrl = $_REQUEST['redirect_uri'];
-
-        $client = $this->locator->getDAO()->getOAuth2ClientByClientId($clientId);
+        $client = $this->locator->getDAO()->getOAuth2ClientByClientId($params['client_id']);
         if ($client === null) {
-            throw new BadRequestException("Invalid client id: " . $clientId);
+            throw new BadRequestException("Invalid client id: " . $params['client_id']);
         }
-        if ($client->redirectUrl !== $redirectUrl) {
-            throw new BadRequestException("Invalid client redirect URI: " . $redirectUrl);
+        if ($client->redirectUrl !== $params['redirect_uri']) {
+            throw new BadRequestException("Invalid client redirect URI: " . $params['redirect_uri']);
         }
 
-        $alg;
-        if (isset($_REQUEST['alg'])) {
-            $alg = $_REQUEST['alg'];
-        } else {
+        $alg = $params['alg'];
+        if ($alg === null) {
             $alg = "RS256";
         }
 
-        if (isset($_GET['code'])) {
-            
-        } else {
-            $this->executeStateFlow($client);
-        }
-    }
-
-    private function executeStateFlow(OAuth2Client $client) {
-
-        if (!isset($_REQUEST['state'])) {
+        $state = $params['state'];
+        if ($state === null) {
             throw new BadRequestException("State is required");
         }
 
@@ -55,7 +42,12 @@ class OAuth2RequestHandler {
         $oauth2Data = new \RAP\OAuth2Data();
         $oauth2Data->clientId = $client->client;
         $oauth2Data->redirectUrl = $client->redirectUrl;
-        $oauth2Data->state = $_REQUEST['state'];
+        $oauth2Data->state = $state;
+
+        $scope = $params['scope'];
+        if ($scope !== null) {
+            $oauth2Data->scope = explode(' ', $scope);
+        }
 
         $session = $this->locator->getSession();
         $session->setOAuth2Data($oauth2Data);
@@ -71,7 +63,7 @@ class OAuth2RequestHandler {
         $accessToken->userId = $session->user->id;
         $accessToken->clientId = $session->getOAuth2Data()->clientId;
         $accessToken->redirectUri = $session->getOAuth2Data()->redirectUrl;
-        //$accessToken->scope = 
+        $accessToken->scope = $session->getOAuth2Data()->scope;
 
         $this->locator->getAccessTokenDAO()->createAccessToken($accessToken);
 
@@ -83,46 +75,48 @@ class OAuth2RequestHandler {
         return $redirectUrl;
     }
 
-    public function handleAccessTokenRequest(): array {
+    public function handleAccessTokenRequest($params): array {
 
-        $this->validateAccessTokenRequest();
+        $this->validateAccessTokenRequest($params);
 
-        $code = filter_input(INPUT_POST, 'code', FILTER_SANITIZE_STRING);
-        $accessToken = $this->locator->getAccessTokenDAO()->retrieveAccessTokenFromCode($code);
-        
-        if($accessToken === null) {
+        $accessToken = $this->locator->getAccessTokenDAO()->retrieveAccessTokenFromCode($params['code']);
+
+        if ($accessToken === null) {
             throw new BadRequestException("No token for given code");
         }
-        
-        $this->validateParametersMatching();
+
+        if ($accessToken->redirectUri !== $params['redirect_uri']) {
+            throw new BadRequestException("Invalid redirect URI: " . $params['redirect_uri']);
+        }
 
         $token = [];
-        $token['access_token'] = $accessToken->token;
+        //$token['access_token'] = $accessToken->token;
         $token['token_type'] = 'bearer';
-        $token['expires_in'] = 300;
-        error_log($accessToken->creationTime);
-        error_log($accessToken->expirationTime);
+        $token['expires_in'] = $this->getExpiresIn($accessToken);
 
         if ($accessToken->scope !== null) {
-            $token['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken->userId, 'RS256');
+            $token['access_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken);
+            //$token['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken);
+        } else {
+            $token['access_token'] = $accessToken->token;
         }
 
         return $token;
     }
 
-    private function validateAccessTokenRequest() {
+    private function validateAccessTokenRequest($params) {
 
-        if (!isset($_POST['grant_type'])) {
-            throw new BadRequestException("Client id is required");
-        } else if ($_POST['grant_type'] !== 'authorization_code') {
+        if ($params['grant_type'] === null) {
+            throw new BadRequestException("grant_type is required");
+        } else if ($params['grant_type'] !== 'authorization_code') {
             throw new BadRequestException("grant_type must be authorization_code");
         }
 
-        if (!isset($_POST['code'])) {
-            throw new BadRequestException("Client id is required");
+        if ($params['code'] === null) {
+            throw new BadRequestException("code id is required");
         }
 
-        if (!isset($_POST['redirect_uri'])) {
+        if ($params['redirect_uri'] === null) {
             throw new BadRequestException("Redirect URI is required");
         }
 
@@ -130,27 +124,32 @@ class OAuth2RequestHandler {
         // however some clients don't send it
     }
 
-    private function validateParametersMatching() {
-        
-    }
-
-    public function handleCheckTokenRequest(): array {
+    public function handleCheckTokenRequest($token): array {
 
         if (!isset($_POST['token'])) {
             throw new BadRequestException("Access token id is required");
         }
 
-        $accessToken = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING);
-
-        //if($accessToken)
+        $accessToken = $this->locator->getAccessTokenDAO()->getAccessToken($token);
+        $user = $this->locator->getDAO()->findUserById($accessToken->userId);
 
         $result = [];
-        $result['exp'] = 3600;
-        $result['user_name'] = "test";
-        $result['client_id'] = "gms";
-        $result['scope'] = "profile";
+        $result['exp'] = $this->getExpiresIn($accessToken);
+        $result['user_name'] = $user->id;
+        $result['client_id'] = $accessToken->clientId;
+
+        if ($accessToken->scope !== null) {
+            $result['scope'] = $accessToken->scope;
+            $result['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken);
+        }
 
         return $result;
     }
 
+    private function getExpiresIn(AccessToken $accessToken) {
+        $expTime = strtotime($accessToken->expirationTime);
+        $now = time();
+        return $expTime - $now;
+    }
+
 }
diff --git a/classes/datalayer/AccessTokenDAO.php b/classes/datalayer/AccessTokenDAO.php
index e9f6033..9525d1c 100644
--- a/classes/datalayer/AccessTokenDAO.php
+++ b/classes/datalayer/AccessTokenDAO.php
@@ -13,6 +13,8 @@ interface AccessTokenDAO {
 
     function retrieveAccessTokenFromCode(string $code): ?AccessToken;
 
+    function getAccessToken(string $token): ?AccessToken;
+
     /**
      * Delete an access token from the database. This happens when the caller
      * application has received the token and used it for retrieving user
diff --git a/classes/datalayer/mysql/MySQLAccessTokenDAO.php b/classes/datalayer/mysql/MySQLAccessTokenDAO.php
index 6b90c12..3c757c7 100644
--- a/classes/datalayer/mysql/MySQLAccessTokenDAO.php
+++ b/classes/datalayer/mysql/MySQLAccessTokenDAO.php
@@ -17,7 +17,7 @@ class MySQLAccessTokenDAO extends BaseMySQLDAO implements AccessTokenDAO {
 
         $scope = null;
         if ($accessToken->scope !== null) {
-            $scope = join(',', $accessToken->scope);
+            $scope = join(' ', $accessToken->scope);
         }
 
         $params = array(
@@ -42,34 +42,54 @@ class MySQLAccessTokenDAO extends BaseMySQLDAO implements AccessTokenDAO {
         $dbh = $this->getDBHandler();
 
         // Access token can be retrieved from code in 1 minute from the creation
-        $stmt = $dbh->prepare("SELECT token, code, user_id, redirect_uri, client_id, creation_time, expiration_time"
-                . " FROM access_token WHERE code = :code AND CURRENT_TIMESTAMP < TIMESTAMPADD(MINUTE, 1, creation_time)");
+        $stmt = $dbh->prepare("SELECT token, code, user_id, redirect_uri, client_id, creation_time, expiration_time, scope"
+                . " FROM access_token WHERE code = :code"); // AND CURRENT_TIMESTAMP < TIMESTAMPADD(MINUTE, 1, creation_time)
         $stmt->bindParam(':code', $code);
 
         $stmt->execute();
 
-        foreach ($stmt->fetchAll() as $row) {
-            $token = new AccessToken();
-            $token->token = $row['token'];
-            $token->code = $row['code'];
-            $token->userId = $row['user_id'];
-            $token->redirectUri = $row['redirect_uri'];
-            $token->clientId = $row['client_id'];
-            $token->creationTime = $row['creation_time'];
-            $token->expirationTime = $row['expiration_time'];
-
-            $scope = null;
-            if (isset($row['scope'])) {
-                $scope = $row['scope'];
-            }
-            if ($scope !== null && $scope !== '') {
-                $token->scope = explode(',', $scope);
-            }
-
-            return $token;
+        $row = $stmt->fetch();
+        return $this->getAccessTokenFromRow($row);
+    }
+
+    public function getAccessToken(string $token): ?AccessToken {
+
+        $dbh = $this->getDBHandler();
+
+        $stmt = $dbh->prepare("SELECT token, code, user_id, redirect_uri, client_id, creation_time, expiration_time, scope"
+                . " FROM access_token WHERE token = :token");
+        $stmt->bindParam(':token', $token);
+
+        $stmt->execute();
+
+        $row = $stmt->fetch();
+        return $this->getAccessTokenFromRow($row);
+    }
+
+    private function getAccessTokenFromRow(?array $row): ?AccessToken {
+
+        if ($row === null) {
+            return null;
+        }
+
+        $token = new AccessToken();
+        $token->token = $row['token'];
+        $token->code = $row['code'];
+        $token->userId = $row['user_id'];
+        $token->redirectUri = $row['redirect_uri'];
+        $token->clientId = $row['client_id'];
+        $token->creationTime = $row['creation_time'];
+        $token->expirationTime = $row['expiration_time'];
+
+        $scope = null;
+        if (isset($row['scope'])) {
+            $scope = $row['scope'];
+        }
+        if ($scope !== null && $scope !== '') {
+            $token->scope = explode(' ', $scope);
         }
 
-        return null;
+        return $token;
     }
 
     public function deleteAccessToken($token): void {
diff --git a/classes/datalayer/mysql/MySQLJWKSDAO.php b/classes/datalayer/mysql/MySQLJWKSDAO.php
index 65618ca..cb4cf4b 100644
--- a/classes/datalayer/mysql/MySQLJWKSDAO.php
+++ b/classes/datalayer/mysql/MySQLJWKSDAO.php
@@ -57,7 +57,7 @@ class MySQLJWKSDAO extends BaseMySQLDAO implements JWKSDAO {
 
     private function getRSAKeyPairFromResultRow(array $row): RSAKeyPair {
         $keyPair = new RSAKeyPair();
-        $keyPair->id = $row['id'];
+        $keyPair->keyId = $row['id'];
         $keyPair->privateKey = $row['private_key'];
         $keyPair->publicKey = $row['public_key'];
         $keyPair->alg = $row['alg'];
diff --git a/classes/model/OAuth2Data.php b/classes/model/OAuth2Data.php
index 57de8c5..f4ae310 100644
--- a/classes/model/OAuth2Data.php
+++ b/classes/model/OAuth2Data.php
@@ -7,5 +7,6 @@ class OAuth2Data {
     public $clientId;
     public $redirectUrl;
     public $state;
+    public $scope;
 
 }
diff --git a/classes/model/User.php b/classes/model/User.php
index c1b11a3..8f27e9c 100644
--- a/classes/model/User.php
+++ b/classes/model/User.php
@@ -114,7 +114,6 @@ class User {
      */
     public function getInstitution(): ?string {
 
-
         $institution = null;
 
         foreach ($this->identities as $identity) {
diff --git a/include/front-controller.php b/include/front-controller.php
index efd208d..e9d7936 100644
--- a/include/front-controller.php
+++ b/include/front-controller.php
@@ -55,8 +55,16 @@ Flight::route('GET /auth/oauth2/authorize', function() {
     session_start();
     global $locator;
 
+    $params = [
+        "client_id" => filter_input(INPUT_GET, 'client_id', FILTER_SANITIZE_STRING),
+        "redirect_uri" => filter_input(INPUT_GET, 'redirect_uri', FILTER_SANITIZE_STRING),
+        "alg" => filter_input(INPUT_GET, 'alg', FILTER_SANITIZE_STRING),
+        "state" => filter_input(INPUT_GET, 'state', FILTER_SANITIZE_STRING),
+        "scope" => filter_input(INPUT_GET, 'scope', FILTER_SANITIZE_STRING)
+    ];
+
     $requestHandler = new \RAP\OAuth2RequestHandler($locator);
-    $requestHandler->handleAuthorizeRequest();
+    $requestHandler->handleAuthorizeRequest($params);
 
     Flight::redirect('/?action=oaut2client');
 });
@@ -65,24 +73,32 @@ Flight::route('POST /auth/oauth2/token', function() {
 
     global $locator;
 
+    $params = [
+        "grant_type" => filter_input(INPUT_POST, "grant_type", FILTER_SANITIZE_STRING),
+        "code" => filter_input(INPUT_POST, "code", FILTER_SANITIZE_STRING),
+        "redirect_uri" => filter_input(INPUT_POST, "redirect_uri", FILTER_SANITIZE_STRING)
+    ];
+
     $requestHandler = new \RAP\OAuth2RequestHandler($locator);
-    $token = $requestHandler->handleAccessTokenRequest();
+    $token = $requestHandler->handleAccessTokenRequest($params);
 
     Flight::json($token);
 });
 
 Flight::route('POST /auth/oauth2/check_token', function() {
-    
+
     global $locator;
 
+    $token = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING);
+    
     $requestHandler = new \RAP\OAuth2RequestHandler($locator);
-    $token = $requestHandler->handleCheckTokenRequest();
+    $result = $requestHandler->handleCheckTokenRequest($token);
 
-    Flight::json($token);
+    Flight::json($result);
 });
 
 Flight::route('GET /auth/oidc/jwks', function() {
-    
+
     global $locator;
 
     $jwksHandler = new \RAP\JWKSHandler($locator);
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..b5124ad
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,7 @@
+<phpunit colors="true">
+    <filter>
+        <whitelist processUncoveredFilesFromWhitelist="true">
+            <directory suffix=".php">./classes/</directory>
+        </whitelist>
+    </filter>
+</phpunit>
diff --git a/tests/IdTokenBuilderTest.php b/tests/IdTokenBuilderTest.php
index 015617c..aa79a56 100644
--- a/tests/IdTokenBuilderTest.php
+++ b/tests/IdTokenBuilderTest.php
@@ -1,6 +1,7 @@
 <?php
 
 use PHPUnit\Framework\TestCase;
+use \Firebase\JWT\JWT;
 
 final class IdTokenBuilderTest extends TestCase {
 
@@ -23,6 +24,7 @@ final class IdTokenBuilderTest extends TestCase {
         $identity->name = "Name";
         $identity->surname = "Surname";
         $identity->primary = true;
+        $identity->institution = "INAF";
         $user->addIdentity($identity);
 
         $daoStub = $this->createMock(\RAP\DAO::class);
@@ -37,9 +39,18 @@ final class IdTokenBuilderTest extends TestCase {
         $accessToken->userId = "user_id";
 
         $tokenBuilder = new \RAP\IdTokenBuilder($locatorStub);
-        $result = $tokenBuilder->getIdToken($accessToken, 'RS256');
+        $jwt = $tokenBuilder->getIdToken($accessToken);
 
-        $this->assertNotNull($result);
+        $this->assertNotNull($jwt);
+
+        $payload = JWT::decode($jwt, $keyPair->publicKey, [$keyPair->alg]);
+
+        $this->assertEquals("issuer", $payload->iss);
+        $this->assertEquals($user->id, $payload->sub);
+        $this->assertEquals($user->getCompleteName(), $payload->name);
+        $this->assertEquals($identity->name, $payload->given_name);
+        $this->assertEquals($identity->surname, $payload->family_name);
+        $this->assertEquals($identity->institution, $payload->org);
     }
 
 }
diff --git a/tests/OAuth2RequestHandlerTest.php b/tests/OAuth2RequestHandlerTest.php
index a380151..8095f1b 100644
--- a/tests/OAuth2RequestHandlerTest.php
+++ b/tests/OAuth2RequestHandlerTest.php
@@ -8,19 +8,27 @@ final class OAuth2RequestHandlerTest extends TestCase {
 
         $this->expectException(\RAP\BadRequestException::class);
 
+        $params = [
+            "client_id" => null
+        ];
+
         $locatorStub = $this->createMock(\RAP\Locator::class);
 
         $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
-        $requestHandler->handleAuthorizeRequest();
+        $requestHandler->handleAuthorizeRequest($params);
     }
 
     public function testInvalidClientRedirectURI(): void {
 
         $this->expectException(\RAP\BadRequestException::class);
 
-        $_REQUEST['client_id'] = "client_id";
-        $_REQUEST['redirect_uri'] = "redirect_uri";
-        $_REQUEST['state'] = "state";
+        $params = [
+            "client_id" => "client_id",
+            "redirect_uri" => "redirect_uri",
+            "state" => "state",
+            "alg" => null,
+            "scope" => "email%20profile"
+        ];
 
         $daoStub = $this->createMock(\RAP\DAO::class);
         $daoStub->method('getOAuth2ClientByClientId')->willReturn(new \RAP\OAuth2Client());
@@ -29,14 +37,18 @@ final class OAuth2RequestHandlerTest extends TestCase {
         $locatorStub->method('getDAO')->willReturn($daoStub);
 
         $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
-        $requestHandler->handleAuthorizeRequest();
+        $requestHandler->handleAuthorizeRequest($params);
     }
 
     public function testExecuteOAuthStateFlow(): void {
 
-        $_REQUEST['client_id'] = "client_id";
-        $_REQUEST['redirect_uri'] = "redirect_uri";
-        $_REQUEST['state'] = "state";
+        $params = [
+            "client_id" => "client_id",
+            "redirect_uri" => "redirect_uri",
+            "state" => "state",
+            "alg" => null,
+            "scope" => "email%20profile"
+        ];
 
         $daoStub = $this->createMock(\RAP\DAO::class);
         $client = new \RAP\OAuth2Client();
@@ -44,7 +56,6 @@ final class OAuth2RequestHandlerTest extends TestCase {
         $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);
@@ -54,7 +65,29 @@ final class OAuth2RequestHandlerTest extends TestCase {
                 ->method('setOAuth2Data')->with($this->anything());
 
         $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
-        $requestHandler->handleAuthorizeRequest();
+        $requestHandler->handleAuthorizeRequest($params);
+    }
+
+    public function testExpiresIn(): void {
+
+        $locatorStub = $this->createMock(\RAP\Locator::class);
+        $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
+
+        $accessToken = new \RAP\AccessToken();
+
+        $expDate = new \DateTime();
+        $expDate->add(new \DateInterval('PT1H'));
+
+        $accessToken->expirationTime = $expDate->format("Y-m-d H:i:s");
+
+        // testing private method using reflection
+        $reflection = new \ReflectionClass(get_class($requestHandler));
+        $method = $reflection->getMethod('getExpiresIn');
+        $method->setAccessible(true);
+
+        $exp = $method->invokeArgs($requestHandler, [$accessToken]);
+
+        $this->assertEquals(3600, $exp);
     }
 
 }
-- 
GitLab