From 9fc7299232bca2c71917f314a47c533ac6845d8e Mon Sep 17 00:00:00 2001 From: Barnaby Walters Date: Thu, 10 Jun 2021 18:18:49 +0200 Subject: [PATCH] Added auth request parameter validation, tests. Started work on exchange methods --- src/Server.php | 17 ++++++++-- src/Storage/FilesystemJsonStorage.php | 6 +++- tests/ServerTest.php | 46 ++++++++++++++++++++++++++- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/Server.php b/src/Server.php index ce5e574..c41b931 100644 --- a/src/Server.php +++ b/src/Server.php @@ -179,10 +179,21 @@ class Server { if (is_null($token)) { $this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams); - + return new Response(400, ['content-type' => 'application/json'], json_encode([ + 'error' => 'invalid_grant', + 'error_description' => 'The provided credentials were not valid.' + ])); } // Verify that it was issued for the same client_id and redirect_uri + if ($token->getData()['client_id'] !== $bodyParams['client_id'] + || $token->getData()['redirect_uri'] !== $bodyParams['redirect_uri']) { + $this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token."); + return new Response(400, ['content-type' => 'application/json'], json_encode([ + 'error' => 'invalid_grant', + 'error_description' => 'The provided credentials were not valid.' + ])); + } // Check that the supplied code_verifier hashes to the stored code_challenge @@ -435,11 +446,11 @@ class Server { } } - return new Response(400, ['contet-type' => 'application/json'], json_encode([ + return new Response(400, ['content-type' => 'application/json'], json_encode([ 'error' => 'invalid_request', 'error_description' => 'Request to token endpoint was not a valid code exchange request.' ])); - + // This is a request to redeem an authorization_code for an access_token. // Verify that the authorization code is valid and has not yet been used. diff --git a/src/Storage/FilesystemJsonStorage.php b/src/Storage/FilesystemJsonStorage.php index b3c8389..064459d 100644 --- a/src/Storage/FilesystemJsonStorage.php +++ b/src/Storage/FilesystemJsonStorage.php @@ -55,6 +55,10 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa public function createAuthCode(array $data): ?Token { $authCode = generateRandomString(self::TOKEN_LENGTH); $accessToken = $this->hash($authCode); + + if (!array_key_exists('valid_until', $data)) { + $data['valid_until'] = time() + $this->authCodeTtl; + } if (!$this->put($accessToken, $data)) { return null; @@ -82,7 +86,7 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa if ($data['exchanged_at'] ?? false) { return null; } // Make sure the auth code isn’t expired. - if ($data['valid_until'] ?? 0 < time()) { return null; } + if (($data['valid_until'] ?? 0) < time()) { return null; }echo 'h'; // If the access token is valid, mark it as redeemed and set a new expiry time. $data['exchanged_at'] = time(); diff --git a/tests/ServerTest.php b/tests/ServerTest.php index d558061..80d9b18 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -8,6 +8,7 @@ use Nyholm\Psr7\Response; use Nyholm\Psr7\ServerRequest; use Nyholm\Psr7\Request; use PHPUnit\Framework\TestCase; +use PHPUnit\TextUI\XmlConfiguration\File; use Psr\Http\Message\ServerRequestInterface; use Taproot\IndieAuth\Callback\DefaultAuthorizationForm; use Taproot\IndieAuth\Callback\SingleUserPasswordAuthenticationCallback; @@ -16,6 +17,8 @@ use Taproot\IndieAuth\Server; use Taproot\IndieAuth\Storage\FilesystemJsonStorage; use Taproot\IndieAuth\Storage\TokenStorageInterface; +use function Taproot\IndieAuth\generatePKCECodeChallenge; +use function Taproot\IndieAuth\generateRandomString; use function Taproot\IndieAuth\hashAuthorizationRequestParameters; use function Taproot\IndieAuth\urlComponentsMatch; @@ -505,7 +508,7 @@ EOT * Test Authorization Token Exchange Requests */ - public function testBothExchangePathsReturnErrorsIfParametersAreMissing() { + public function testExchangePathsReturnErrorsIfParametersAreMissing() { $s = $this->getDefaultServer(); $req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([ @@ -523,6 +526,47 @@ EOT $this->assertEquals('invalid_request', $tokenEndpointJson['error']); } + public function testExchangePathsReturnErrorOnInvalidParameters() { + $s = $this->getDefaultServer(); + $storage = new FilesystemJsonStorage(TOKEN_STORAGE_PATH, SERVER_SECRET); + + $testCases = [ + 'Mismatched client_id' => ['client_id' => 'https://invalid-client.example.com/'], + 'Mismatched redirect_uri' => ['redirect_uri' => 'https://invalid-client.example.com/auth'], + 'Invalid code_verifier' => ['code_verifier' => 'definitely_not_the_randomly_generated_string'], + ]; + + foreach ($testCases as $name => $params) { + // Create an auth code. + $codeVerifier = generateRandomString(32); + $authCode = $storage->createAuthCode([ + 'client_id' => 'https://client.example.com/', + 'redirect_uri' => 'https://client.example.com/auth', + 'code_challenge' => generatePKCECodeChallenge($codeVerifier), + 'state' => '12345', + 'code_challenge_method' => 'S256' + ]); + + $req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody(array_merge([ + 'grant_type' => 'authorization_code', + 'code' => $authCode->getKey(), + 'client_id' => $authCode->getData()['client_id'], + 'redirect_uri' => $authCode->getData()['redirect_uri'], + 'code_verifier' => $codeVerifier + ], $params)); + + $authEndpointResponse = $s->handleAuthorizationEndpointRequest($req); + $this->assertEquals(400, $authEndpointResponse->getStatusCode()); + $authEndpointJson = json_decode((string) $authEndpointResponse->getBody(), true); + $this->assertEquals('invalid_grant', $authEndpointJson['error']); + + $tokenEndpointResponse = $s->handleAuthorizationEndpointRequest($req); + $this->assertEquals(400, $tokenEndpointResponse->getStatusCode()); + $tokenEndpointJson = json_decode((string) $tokenEndpointResponse->getBody(), true); + $this->assertEquals('invalid_grant', $tokenEndpointJson['error']); + } + } + /** * Test Non-Indieauth Requests. */