From f7611ccc66d3e5c69e7b447ce3d221f4db4a2ab4 Mon Sep 17 00:00:00 2001 From: Sonia Zorba Date: Fri, 30 Aug 2019 17:07:40 +0200 Subject: [PATCH] Changed timestamp format using Unix timestamp in order to avoid time zone issues --- classes/Locator.php | 4 ++ classes/OAuth2RequestHandler.php | 14 +------ classes/datalayer/JWKSDAO.php | 2 +- .../datalayer/mysql/MySQLAccessTokenDAO.php | 9 ++--- classes/datalayer/mysql/MySQLJWKSDAO.php | 9 +++-- include/admin.php | 15 +++++++- include/front-controller.php | 4 ++ sql/setup-database.sql | 6 +-- tests/OAuth2RequestHandlerTest.php | 38 ++++++++++++------- 9 files changed, 62 insertions(+), 39 deletions(-) diff --git a/classes/Locator.php b/classes/Locator.php index 75e183d..826afd3 100644 --- a/classes/Locator.php +++ b/classes/Locator.php @@ -114,6 +114,10 @@ class Locator { return $this->auditLogger; } + public function getJWKSHandler(): JWKSHandler { + return new JWKSHandler($this); + } + private function setupLoggers() { // Monolog require timezone to be set date_default_timezone_set($this->config->timeZone); diff --git a/classes/OAuth2RequestHandler.php b/classes/OAuth2RequestHandler.php index bdee406..08c9967 100644 --- a/classes/OAuth2RequestHandler.php +++ b/classes/OAuth2RequestHandler.php @@ -94,7 +94,7 @@ class OAuth2RequestHandler { $result = []; $result['access_token'] = $accessToken->token; $result['token_type'] = 'Bearer'; - $result['expires_in'] = $this->getExpiresIn($accessToken); + $result['expires_in'] = $accessToken->expirationTime - time(); if ($accessToken->scope !== null && in_array('openid', $accessToken->scope)) { $result['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken); @@ -125,15 +125,11 @@ class OAuth2RequestHandler { public function handleCheckTokenRequest($token): array { - if (!isset($_POST['token'])) { - throw new BadRequestException("Access token id is required"); - } - $accessToken = $this->locator->getAccessTokenDAO()->getAccessToken($token); $user = $this->locator->getUserDAO()->findUserById($accessToken->userId); $result = []; - $result['exp'] = $this->getExpiresIn($accessToken); + $result['exp'] = $accessToken->expirationTime - time(); $result['user_name'] = $user->id; $result['client_id'] = $accessToken->clientId; @@ -147,12 +143,6 @@ class OAuth2RequestHandler { return $result; } - private function getExpiresIn(AccessToken $accessToken) { - $expTime = strtotime($accessToken->expirationTime); - $now = time(); - return $expTime - $now; - } - public function validateToken(): void { $headers = apache_request_headers(); diff --git a/classes/datalayer/JWKSDAO.php b/classes/datalayer/JWKSDAO.php index 54b0ce0..9cd83f4 100644 --- a/classes/datalayer/JWKSDAO.php +++ b/classes/datalayer/JWKSDAO.php @@ -10,5 +10,5 @@ interface JWKSDAO { public function insertRSAKeyPair(RSAKeyPair $keyPair): RSAKeyPair; - public function getNewestKeyPair(): RSAKeyPair; + public function getNewestKeyPair(): ?RSAKeyPair; } diff --git a/classes/datalayer/mysql/MySQLAccessTokenDAO.php b/classes/datalayer/mysql/MySQLAccessTokenDAO.php index ee55315..28c4fad 100644 --- a/classes/datalayer/mysql/MySQLAccessTokenDAO.php +++ b/classes/datalayer/mysql/MySQLAccessTokenDAO.php @@ -13,7 +13,7 @@ class MySQLAccessTokenDAO extends BaseMySQLDAO implements AccessTokenDAO { $dbh = $this->getDBHandler(); $stmt = $dbh->prepare("INSERT INTO access_token (token, code, user_id, redirect_uri, client_id, scope, expiration_time)" . " VALUES(:token, :code, :user_id, :redirect_uri, :client_id, :scope, " - . " TIMESTAMPADD(HOUR, 1, CURRENT_TIMESTAMP))"); + . " UNIX_TIMESTAMP(TIMESTAMPADD(HOUR, 1, CURRENT_TIMESTAMP)))"); $scope = null; if ($accessToken->scope !== null) { @@ -30,7 +30,6 @@ class MySQLAccessTokenDAO extends BaseMySQLDAO implements AccessTokenDAO { ); if ($stmt->execute($params)) { - $accessToken->expired = false; return $accessToken; } else { error_log($stmt->errorInfo()[2]); @@ -44,8 +43,8 @@ class MySQLAccessTokenDAO extends BaseMySQLDAO implements AccessTokenDAO { // 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, scope," - . " (expiration_time < CURRENT_TIMESTAMP) AS expired " - . " FROM access_token WHERE code = :code AND CURRENT_TIMESTAMP < TIMESTAMPADD(MINUTE, 1, creation_time)"); + . " (expiration_time < UNIX_TIMESTAMP()) AS expired " + . " FROM access_token WHERE code = :code AND UNIX_TIMESTAMP() < (creation_time + 60)"); $stmt->bindParam(':code', $code); $stmt->execute(); @@ -63,7 +62,7 @@ class MySQLAccessTokenDAO extends BaseMySQLDAO implements AccessTokenDAO { $dbh = $this->getDBHandler(); $stmt = $dbh->prepare("SELECT token, code, user_id, redirect_uri, client_id, creation_time, expiration_time, scope," - . " (expiration_time < CURRENT_TIMESTAMP) AS expired " + . " (expiration_time < UNIX_TIMESTAMP()) AS expired " . " FROM access_token WHERE token = :token"); $stmt->bindParam(':token', $token); diff --git a/classes/datalayer/mysql/MySQLJWKSDAO.php b/classes/datalayer/mysql/MySQLJWKSDAO.php index 4d6c713..8e7a83a 100644 --- a/classes/datalayer/mysql/MySQLJWKSDAO.php +++ b/classes/datalayer/mysql/MySQLJWKSDAO.php @@ -60,7 +60,7 @@ class MySQLJWKSDAO extends BaseMySQLDAO implements JWKSDAO { return null; } - public function getNewestKeyPair(): RSAKeyPair { + public function getNewestKeyPair(): ?RSAKeyPair { $dbh = $this->getDBHandler(); $query = "SELECT id, private_key, public_key, alg, creation_time FROM rsa_keypairs ORDER BY creation_time DESC LIMIT 1"; @@ -68,8 +68,11 @@ class MySQLJWKSDAO extends BaseMySQLDAO implements JWKSDAO { $stmt = $dbh->prepare($query); $stmt->execute(); - $row = $stmt->fetch(); - return $this->getRSAKeyPairFromResultRow($row); + foreach ($stmt->fetchAll() as $row) { + return $this->getRSAKeyPairFromResultRow($row); + } + + return null; } private function getRSAKeyPairFromResultRow(array $row): RSAKeyPair { diff --git a/include/admin.php b/include/admin.php index 06f6f87..2c344df 100644 --- a/include/admin.php +++ b/include/admin.php @@ -21,10 +21,10 @@ function checkUser() { Flight::route('GET /admin', function() { checkUser(); - + global $VERSION; Flight::render('admin/index.php', array('title' => 'Admin panel', - 'version' => $VERSION)); + 'version' => $VERSION)); }); Flight::route('GET /admin/oauth2_clients', function() { @@ -68,6 +68,17 @@ Flight::route('DELETE /admin/oauth2_clients/@id', function($id) { Flight::halt(204); }); +Flight::route('POST /admin/keypair', function() { + + checkUser(); + global $locator; + + $keyPair = $locator->getJWKSHandler()->generateKeyPair(); + Flight::json([ + "id" => $keyPair->keyId + ]); +}); + function buildOAuth2ClientFromData() { $data = Flight::request()->data; diff --git a/include/front-controller.php b/include/front-controller.php index f65a202..d47c05b 100644 --- a/include/front-controller.php +++ b/include/front-controller.php @@ -110,6 +110,10 @@ Flight::route('POST /auth/oauth2/check_token', function() { $token = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING); + if ($token === null) { + throw new BadRequestException("Access token id is required"); + } + $requestHandler = new \RAP\OAuth2RequestHandler($locator); $result = $requestHandler->handleCheckTokenRequest($token); diff --git a/sql/setup-database.sql b/sql/setup-database.sql index 4d3bf6b..f561c33 100644 --- a/sql/setup-database.sql +++ b/sql/setup-database.sql @@ -47,8 +47,8 @@ CREATE TABLE `access_token` ( `token` text NOT NULL, `user_id` text NOT NULL, `code` text NOT NULL, - `creation_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `expiration_time` timestamp, + `creation_time` BIGINT NOT NULL DEFAULT UNIX_TIMESTAMP(), + `expiration_time` BIGINT, `redirect_uri` text, `client_id` varchar(255), `scope` text, @@ -60,7 +60,7 @@ CREATE TABLE `rsa_keypairs` ( `public_key` text, `private_key` text, `alg` varchar(255), - `creation_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `creation_time` BIGINT NOT NULL DEFAULT UNIX_TIMESTAMP(), PRIMARY KEY (`id`) ); diff --git a/tests/OAuth2RequestHandlerTest.php b/tests/OAuth2RequestHandlerTest.php index 216c4d1..ec58c2f 100644 --- a/tests/OAuth2RequestHandlerTest.php +++ b/tests/OAuth2RequestHandlerTest.php @@ -68,26 +68,38 @@ final class OAuth2RequestHandlerTest extends TestCase { $requestHandler->handleAuthorizeRequest($params); } - public function testExpiresIn(): void { - - $locatorStub = $this->createMock(\RAP\Locator::class); - $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub); + public function testHandleCheckTokenRequest(): void { $accessToken = new \RAP\AccessToken(); + $accessToken->clientId = 'my-client'; + $accessToken->scope = ['openid', 'email']; + $accessToken->userId = '123'; + $accessToken->expirationTime = time() + 3600; + + $tokenDaoStub = $this->createMock(\RAP\AccessTokenDAO::class); + $tokenDaoStub->method('getAccessToken')->willReturn($accessToken); - $expDate = new \DateTime(); - $expDate->add(new \DateInterval('PT1H')); + $user = new \RAP\User(); + $user->id = '123'; + $userDaoStub = $this->createMock(\RAP\UserDAO::class); + $userDaoStub->method('findUserById')->willReturn($user); - $accessToken->expirationTime = $expDate->format("Y-m-d H:i:s"); + $idTokenBuilderStub = $this->createMock(\RAP\IdTokenBuilder::class); + $idTokenBuilderStub->method('getIdToken')->willReturn('id-token'); - // testing private method using reflection - $reflection = new \ReflectionClass(get_class($requestHandler)); - $method = $reflection->getMethod('getExpiresIn'); - $method->setAccessible(true); + $locatorStub = $this->createMock(\RAP\Locator::class); + $locatorStub->method('getAccessTokenDAO')->willReturn($tokenDaoStub); + $locatorStub->method('getUserDAO')->willReturn($userDaoStub); + $locatorStub->method('getIdTokenBuilder')->willReturn($idTokenBuilderStub); + + $requestHandler = new \RAP\OAuth2RequestHandler($locatorStub); - $exp = $method->invokeArgs($requestHandler, [$accessToken]); + $result = $requestHandler->handleCheckTokenRequest('abc'); - $this->assertEquals(3600, $exp); + $this->assertEquals(3600, $result['exp']); + $this->assertEquals('123', $result['user_name']); + $this->assertEquals('my-client', $result['client_id']); + $this->assertEquals('id-token', $result['id_token']); } } -- GitLab