diff options
Diffstat (limited to 'apps/oauth2/tests/Controller/OauthApiControllerTest.php')
-rw-r--r-- | apps/oauth2/tests/Controller/OauthApiControllerTest.php | 270 |
1 files changed, 190 insertions, 80 deletions
diff --git a/apps/oauth2/tests/Controller/OauthApiControllerTest.php b/apps/oauth2/tests/Controller/OauthApiControllerTest.php index 414bcd3e432..53dd8549196 100644 --- a/apps/oauth2/tests/Controller/OauthApiControllerTest.php +++ b/apps/oauth2/tests/Controller/OauthApiControllerTest.php @@ -1,36 +1,15 @@ <?php + /** - * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCA\OAuth2\Tests\Controller; use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; -use OC\Authentication\Token\DefaultToken; use OC\Authentication\Token\IProvider as TokenProvider; -use OC\Security\Bruteforce\Throttler; +use OC\Authentication\Token\PublicKeyToken; use OCA\OAuth2\Controller\OauthApiController; use OCA\OAuth2\Db\AccessToken; use OCA\OAuth2\Db\AccessTokenMapper; @@ -42,10 +21,17 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IRequest; +use OCP\Security\Bruteforce\IThrottler; use OCP\Security\ICrypto; use OCP\Security\ISecureRandom; +use Psr\Log\LoggerInterface; use Test\TestCase; +/* We have to use this to add a property to the mocked request and avoid warnings about dynamic properties on PHP>=8.2 */ +abstract class RequestMock implements IRequest { + public array $server = []; +} + class OauthApiControllerTest extends TestCase { /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ private $request; @@ -61,22 +47,28 @@ class OauthApiControllerTest extends TestCase { private $secureRandom; /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ private $time; - /** @var Throttler|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IThrottler|\PHPUnit\Framework\MockObject\MockObject */ private $throttler; + /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $logger; + /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ + private $timeFactory; /** @var OauthApiController */ private $oauthApiController; protected function setUp(): void { parent::setUp(); - $this->request = $this->createMock(IRequest::class); + $this->request = $this->createMock(RequestMock::class); $this->crypto = $this->createMock(ICrypto::class); $this->accessTokenMapper = $this->createMock(AccessTokenMapper::class); $this->clientMapper = $this->createMock(ClientMapper::class); $this->tokenProvider = $this->createMock(TokenProvider::class); $this->secureRandom = $this->createMock(ISecureRandom::class); $this->time = $this->createMock(ITimeFactory::class); - $this->throttler = $this->createMock(Throttler::class); + $this->throttler = $this->createMock(IThrottler::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); $this->oauthApiController = new OauthApiController( 'oauth2', @@ -87,22 +79,26 @@ class OauthApiControllerTest extends TestCase { $this->tokenProvider, $this->secureRandom, $this->time, - $this->throttler + $this->logger, + $this->throttler, + $this->timeFactory ); } - public function testGetTokenInvalidGrantType() { + public function testGetTokenInvalidGrantType(): void { $expected = new JSONResponse([ 'error' => 'invalid_grant', ], Http::STATUS_BAD_REQUEST); + $expected->throttle(['invalid_grant' => 'foo']); $this->assertEquals($expected, $this->oauthApiController->getToken('foo', null, null, null, null)); } - public function testGetTokenInvalidCode() { + public function testGetTokenInvalidCode(): void { $expected = new JSONResponse([ 'error' => 'invalid_request', ], Http::STATUS_BAD_REQUEST); + $expected->throttle(['invalid_request' => 'token not found', 'code' => 'invalidcode']); $this->accessTokenMapper->method('getByCode') ->with('invalidcode') @@ -111,10 +107,94 @@ class OauthApiControllerTest extends TestCase { $this->assertEquals($expected, $this->oauthApiController->getToken('authorization_code', 'invalidcode', null, null, null)); } - public function testGetTokenInvalidRefreshToken() { + public function testGetTokenExpiredCode(): void { + $codeCreatedAt = 100; + $expiredSince = 123; + $expected = new JSONResponse([ 'error' => 'invalid_request', ], Http::STATUS_BAD_REQUEST); + $expected->throttle(['invalid_request' => 'authorization_code_expired', 'expired_since' => $expiredSince]); + + $accessToken = new AccessToken(); + $accessToken->setClientId(42); + $accessToken->setCodeCreatedAt($codeCreatedAt); + + $this->accessTokenMapper->method('getByCode') + ->with('validcode') + ->willReturn($accessToken); + + $tsNow = $codeCreatedAt + OauthApiController::AUTHORIZATION_CODE_EXPIRES_AFTER + $expiredSince; + $dateNow = (new \DateTimeImmutable())->setTimestamp($tsNow); + $this->timeFactory->method('now') + ->willReturn($dateNow); + + $this->assertEquals($expected, $this->oauthApiController->getToken('authorization_code', 'validcode', null, null, null)); + } + + public function testGetTokenWithCodeForActiveToken(): void { + // if a token has already delivered oauth tokens, + // it should not be possible to get a new oauth token from a valid authorization code + $codeCreatedAt = 100; + + $expected = new JSONResponse([ + 'error' => 'invalid_request', + ], Http::STATUS_BAD_REQUEST); + $expected->throttle(['invalid_request' => 'authorization_code_received_for_active_token']); + + $accessToken = new AccessToken(); + $accessToken->setClientId(42); + $accessToken->setCodeCreatedAt($codeCreatedAt); + $accessToken->setTokenCount(1); + + $this->accessTokenMapper->method('getByCode') + ->with('validcode') + ->willReturn($accessToken); + + $tsNow = $codeCreatedAt + 1; + $dateNow = (new \DateTimeImmutable())->setTimestamp($tsNow); + $this->timeFactory->method('now') + ->willReturn($dateNow); + + $this->assertEquals($expected, $this->oauthApiController->getToken('authorization_code', 'validcode', null, null, null)); + } + + public function testGetTokenClientDoesNotExist(): void { + // In this test, the token's authorization code is valid and has not expired + // and we check what happens when the associated Oauth client does not exist + $codeCreatedAt = 100; + + $expected = new JSONResponse([ + 'error' => 'invalid_request', + ], Http::STATUS_BAD_REQUEST); + $expected->throttle(['invalid_request' => 'client not found', 'client_id' => 42]); + + $accessToken = new AccessToken(); + $accessToken->setClientId(42); + $accessToken->setCodeCreatedAt($codeCreatedAt); + + $this->accessTokenMapper->method('getByCode') + ->with('validcode') + ->willReturn($accessToken); + + // 'now' is before the token's authorization code expiration + $tsNow = $codeCreatedAt + OauthApiController::AUTHORIZATION_CODE_EXPIRES_AFTER - 1; + $dateNow = (new \DateTimeImmutable())->setTimestamp($tsNow); + $this->timeFactory->method('now') + ->willReturn($dateNow); + + $this->clientMapper->method('getByUid') + ->with(42) + ->willThrowException(new ClientNotFoundException()); + + $this->assertEquals($expected, $this->oauthApiController->getToken('authorization_code', 'validcode', null, null, null)); + } + + public function testRefreshTokenInvalidRefreshToken(): void { + $expected = new JSONResponse([ + 'error' => 'invalid_request', + ], Http::STATUS_BAD_REQUEST); + $expected->throttle(['invalid_request' => 'token not found', 'code' => 'invalidrefresh']); $this->accessTokenMapper->method('getByCode') ->with('invalidrefresh') @@ -123,10 +203,11 @@ class OauthApiControllerTest extends TestCase { $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'invalidrefresh', null, null)); } - public function testGetTokenClientDoesNotExist() { + public function testRefreshTokenClientDoesNotExist(): void { $expected = new JSONResponse([ 'error' => 'invalid_request', ], Http::STATUS_BAD_REQUEST); + $expected->throttle(['invalid_request' => 'client not found', 'client_id' => 42]); $accessToken = new AccessToken(); $accessToken->setClientId(42); @@ -142,7 +223,7 @@ class OauthApiControllerTest extends TestCase { $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', null, null)); } - public function invalidClientProvider() { + public static function invalidClientProvider() { return [ ['invalidClientId', 'invalidClientSecret'], ['clientId', 'invalidClientSecret'], @@ -151,15 +232,16 @@ class OauthApiControllerTest extends TestCase { } /** - * @dataProvider invalidClientProvider * * @param string $clientId * @param string $clientSecret */ - public function testGetTokenInvalidClient($clientId, $clientSecret) { + #[\PHPUnit\Framework\Attributes\DataProvider('invalidClientProvider')] + public function testRefreshTokenInvalidClient($clientId, $clientSecret): void { $expected = new JSONResponse([ 'error' => 'invalid_client', ], Http::STATUS_BAD_REQUEST); + $expected->throttle(['invalid_client' => 'client ID or secret does not match']); $accessToken = new AccessToken(); $accessToken->setClientId(42); @@ -168,9 +250,20 @@ class OauthApiControllerTest extends TestCase { ->with('validrefresh') ->willReturn($accessToken); + $this->crypto + ->method('calculateHMAC') + ->with($this->callback(function (string $text) { + return $text === 'clientSecret' || $text === 'invalidClientSecret'; + })) + ->willReturnCallback(function (string $text) { + return $text === 'clientSecret' + ? 'hashedClientSecret' + : 'hashedInvalidClientSecret'; + }); + $client = new Client(); $client->setClientIdentifier('clientId'); - $client->setSecret('clientSecret'); + $client->setSecret(bin2hex('hashedClientSecret')); $this->clientMapper->method('getByUid') ->with(42) ->willReturn($client); @@ -178,10 +271,11 @@ class OauthApiControllerTest extends TestCase { $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', $clientId, $clientSecret)); } - public function testGetTokenInvalidAppToken() { + public function testRefreshTokenInvalidAppToken(): void { $expected = new JSONResponse([ 'error' => 'invalid_request', ], Http::STATUS_BAD_REQUEST); + $expected->throttle(['invalid_request' => 'token is invalid']); $accessToken = new AccessToken(); $accessToken->setClientId(42); @@ -194,16 +288,20 @@ class OauthApiControllerTest extends TestCase { $client = new Client(); $client->setClientIdentifier('clientId'); - $client->setSecret('clientSecret'); + $client->setSecret(bin2hex('hashedClientSecret')); $this->clientMapper->method('getByUid') ->with(42) ->willReturn($client); - $this->crypto->method('decrypt') - ->with( - 'encryptedToken', - 'validrefresh' - )->willReturn('decryptedToken'); + $this->crypto + ->method('decrypt') + ->with('encryptedToken') + ->willReturn('decryptedToken'); + + $this->crypto + ->method('calculateHMAC') + ->with('clientSecret') + ->willReturn('hashedClientSecret'); $this->tokenProvider->method('getTokenById') ->with(1337) @@ -216,7 +314,7 @@ class OauthApiControllerTest extends TestCase { $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret')); } - public function testGetTokenValidAppToken() { + public function testRefreshTokenValidAppToken(): void { $accessToken = new AccessToken(); $accessToken->setClientId(42); $accessToken->setTokenId(1337); @@ -228,18 +326,22 @@ class OauthApiControllerTest extends TestCase { $client = new Client(); $client->setClientIdentifier('clientId'); - $client->setSecret('clientSecret'); + $client->setSecret(bin2hex('hashedClientSecret')); $this->clientMapper->method('getByUid') ->with(42) ->willReturn($client); - $this->crypto->method('decrypt') - ->with( - 'encryptedToken', - 'validrefresh' - )->willReturn('decryptedToken'); + $this->crypto + ->method('decrypt') + ->with('encryptedToken') + ->willReturn('decryptedToken'); - $appToken = new DefaultToken(); + $this->crypto + ->method('calculateHMAC') + ->with('clientSecret') + ->willReturn('hashedClientSecret'); + + $appToken = new PublicKeyToken(); $appToken->setUid('userId'); $this->tokenProvider->method('getTokenById') ->with(1337) @@ -251,7 +353,7 @@ class OauthApiControllerTest extends TestCase { $this->secureRandom->method('generate') ->willReturnCallback(function ($len) { - return 'random'.$len; + return 'random' . $len; }); $this->tokenProvider->expects($this->once()) @@ -268,7 +370,7 @@ class OauthApiControllerTest extends TestCase { $this->tokenProvider->expects($this->once()) ->method('updateToken') ->with( - $this->callback(function (DefaultToken $token) { + $this->callback(function (PublicKeyToken $token) { return $token->getExpires() === 4600; }) ); @@ -281,8 +383,8 @@ class OauthApiControllerTest extends TestCase { ->method('update') ->with( $this->callback(function (AccessToken $token) { - return $token->getHashedCode() === hash('sha512', 'random128') && - $token->getEncryptedToken() === 'newEncryptedToken'; + return $token->getHashedCode() === hash('sha512', 'random128') + && $token->getEncryptedToken() === 'newEncryptedToken'; }) ); @@ -308,7 +410,7 @@ class OauthApiControllerTest extends TestCase { $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret')); } - public function testGetTokenValidAppTokenBasicAuth() { + public function testRefreshTokenValidAppTokenBasicAuth(): void { $accessToken = new AccessToken(); $accessToken->setClientId(42); $accessToken->setTokenId(1337); @@ -320,18 +422,22 @@ class OauthApiControllerTest extends TestCase { $client = new Client(); $client->setClientIdentifier('clientId'); - $client->setSecret('clientSecret'); + $client->setSecret(bin2hex('hashedClientSecret')); $this->clientMapper->method('getByUid') ->with(42) ->willReturn($client); - $this->crypto->method('decrypt') - ->with( - 'encryptedToken', - 'validrefresh' - )->willReturn('decryptedToken'); + $this->crypto + ->method('decrypt') + ->with('encryptedToken') + ->willReturn('decryptedToken'); - $appToken = new DefaultToken(); + $this->crypto + ->method('calculateHMAC') + ->with('clientSecret') + ->willReturn('hashedClientSecret'); + + $appToken = new PublicKeyToken(); $appToken->setUid('userId'); $this->tokenProvider->method('getTokenById') ->with(1337) @@ -343,7 +449,7 @@ class OauthApiControllerTest extends TestCase { $this->secureRandom->method('generate') ->willReturnCallback(function ($len) { - return 'random'.$len; + return 'random' . $len; }); $this->tokenProvider->expects($this->once()) @@ -360,7 +466,7 @@ class OauthApiControllerTest extends TestCase { $this->tokenProvider->expects($this->once()) ->method('updateToken') ->with( - $this->callback(function (DefaultToken $token) { + $this->callback(function (PublicKeyToken $token) { return $token->getExpires() === 4600; }) ); @@ -373,8 +479,8 @@ class OauthApiControllerTest extends TestCase { ->method('update') ->with( $this->callback(function (AccessToken $token) { - return $token->getHashedCode() === hash('sha512', 'random128') && - $token->getEncryptedToken() === 'newEncryptedToken'; + return $token->getHashedCode() === hash('sha512', 'random128') + && $token->getEncryptedToken() === 'newEncryptedToken'; }) ); @@ -403,7 +509,7 @@ class OauthApiControllerTest extends TestCase { $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', null, null)); } - public function testGetTokenExpiredAppToken() { + public function testRefreshTokenExpiredAppToken(): void { $accessToken = new AccessToken(); $accessToken->setClientId(42); $accessToken->setTokenId(1337); @@ -415,18 +521,22 @@ class OauthApiControllerTest extends TestCase { $client = new Client(); $client->setClientIdentifier('clientId'); - $client->setSecret('clientSecret'); + $client->setSecret(bin2hex('hashedClientSecret')); $this->clientMapper->method('getByUid') ->with(42) ->willReturn($client); - $this->crypto->method('decrypt') - ->with( - 'encryptedToken', - 'validrefresh' - )->willReturn('decryptedToken'); + $this->crypto + ->method('decrypt') + ->with('encryptedToken') + ->willReturn('decryptedToken'); + + $this->crypto + ->method('calculateHMAC') + ->with('clientSecret') + ->willReturn('hashedClientSecret'); - $appToken = new DefaultToken(); + $appToken = new PublicKeyToken(); $appToken->setUid('userId'); $this->tokenProvider->method('getTokenById') ->with(1337) @@ -438,7 +548,7 @@ class OauthApiControllerTest extends TestCase { $this->secureRandom->method('generate') ->willReturnCallback(function ($len) { - return 'random'.$len; + return 'random' . $len; }); $this->tokenProvider->expects($this->once()) @@ -455,7 +565,7 @@ class OauthApiControllerTest extends TestCase { $this->tokenProvider->expects($this->once()) ->method('updateToken') ->with( - $this->callback(function (DefaultToken $token) { + $this->callback(function (PublicKeyToken $token) { return $token->getExpires() === 4600; }) ); @@ -468,8 +578,8 @@ class OauthApiControllerTest extends TestCase { ->method('update') ->with( $this->callback(function (AccessToken $token) { - return $token->getHashedCode() === hash('sha512', 'random128') && - $token->getEncryptedToken() === 'newEncryptedToken'; + return $token->getHashedCode() === hash('sha512', 'random128') + && $token->getEncryptedToken() === 'newEncryptedToken'; }) ); |