diff options
Diffstat (limited to 'apps/oauth2/tests')
-rw-r--r-- | apps/oauth2/tests/Controller/LoginRedirectorControllerTest.php | 193 | ||||
-rw-r--r-- | apps/oauth2/tests/Controller/OauthApiControllerTest.php | 255 | ||||
-rw-r--r-- | apps/oauth2/tests/Controller/SettingsControllerTest.php | 127 | ||||
-rw-r--r-- | apps/oauth2/tests/Db/AccessTokenMapperTest.php | 37 | ||||
-rw-r--r-- | apps/oauth2/tests/Db/ClientMapperTest.php | 55 | ||||
-rw-r--r-- | apps/oauth2/tests/Settings/AdminTest.php | 46 |
6 files changed, 476 insertions, 237 deletions
diff --git a/apps/oauth2/tests/Controller/LoginRedirectorControllerTest.php b/apps/oauth2/tests/Controller/LoginRedirectorControllerTest.php index 7f45f9c5b4b..04ac0bfbd28 100644 --- a/apps/oauth2/tests/Controller/LoginRedirectorControllerTest.php +++ b/apps/oauth2/tests/Controller/LoginRedirectorControllerTest.php @@ -1,58 +1,42 @@ <?php + /** - * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> - * - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @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\Core\Controller\ClientFlowLoginController; use OCA\OAuth2\Controller\LoginRedirectorController; use OCA\OAuth2\Db\Client; use OCA\OAuth2\Db\ClientMapper; use OCA\OAuth2\Exceptions\ClientNotFoundException; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\TemplateResponse; +use OCP\IAppConfig; +use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; use OCP\ISession; use OCP\IURLGenerator; +use OCP\Security\ISecureRandom; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; /** * @group DB */ class LoginRedirectorControllerTest extends TestCase { - /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - private $urlGenerator; - /** @var ClientMapper|\PHPUnit\Framework\MockObject\MockObject */ - private $clientMapper; - /** @var ISession|\PHPUnit\Framework\MockObject\MockObject */ - private $session; - /** @var LoginRedirectorController */ - private $loginRedirectorController; - /** @var IL10N */ - private $l; + private IRequest&MockObject $request; + private IURLGenerator&MockObject $urlGenerator; + private ClientMapper&MockObject $clientMapper; + private ISession&MockObject $session; + private IL10N&MockObject $l; + private ISecureRandom&MockObject $random; + private IAppConfig&MockObject $appConfig; + private IConfig&MockObject $config; + + private LoginRedirectorController $loginRedirectorController; protected function setUp(): void { parent::setUp(); @@ -62,6 +46,9 @@ class LoginRedirectorControllerTest extends TestCase { $this->clientMapper = $this->createMock(ClientMapper::class); $this->session = $this->createMock(ISession::class); $this->l = $this->createMock(IL10N::class); + $this->random = $this->createMock(ISecureRandom::class); + $this->appConfig = $this->createMock(IAppConfig::class); + $this->config = $this->createMock(IConfig::class); $this->loginRedirectorController = new LoginRedirectorController( 'oauth2', @@ -69,11 +56,14 @@ class LoginRedirectorControllerTest extends TestCase { $this->urlGenerator, $this->clientMapper, $this->session, - $this->l + $this->l, + $this->random, + $this->appConfig, + $this->config, ); } - public function testAuthorize() { + public function testAuthorize(): void { $client = new Client(); $client->setClientIdentifier('MyClientIdentifier'); $this->clientMapper @@ -92,15 +82,74 @@ class LoginRedirectorControllerTest extends TestCase { 'core.ClientFlowLogin.showAuthPickerPage', [ 'clientIdentifier' => 'MyClientIdentifier', + 'providedRedirectUri' => '', ] ) ->willReturn('https://example.com/?clientIdentifier=foo'); + $this->config + ->expects($this->once()) + ->method('getSystemValueBool') + ->with('oauth2.enable_oc_clients', false) + ->willReturn(false); $expected = new RedirectResponse('https://example.com/?clientIdentifier=foo'); $this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'code')); } - public function testAuthorizeWrongResponseType() { + public function testAuthorizeSkipPicker(): void { + $client = new Client(); + $client->setName('MyClientName'); + $client->setClientIdentifier('MyClientIdentifier'); + $this->clientMapper + ->expects($this->once()) + ->method('getByIdentifier') + ->with('MyClientId') + ->willReturn($client); + $this->session + ->expects(static::exactly(2)) + ->method('set') + ->willReturnCallback(function (string $key, string $value): void { + switch ([$key, $value]) { + case ['oauth.state', 'MyState']: + case [ClientFlowLoginController::STATE_NAME, 'MyStateToken']: + /* Expected */ + break; + default: + throw new LogicException(); + } + }); + $this->appConfig + ->expects(static::once()) + ->method('getValueArray') + ->with('oauth2', 'skipAuthPickerApplications', []) + ->willReturn(['MyClientName']); + $this->random + ->expects(static::once()) + ->method('generate') + ->willReturn('MyStateToken'); + $this->urlGenerator + ->expects($this->once()) + ->method('linkToRouteAbsolute') + ->with( + 'core.ClientFlowLogin.grantPage', + [ + 'stateToken' => 'MyStateToken', + 'clientIdentifier' => 'MyClientIdentifier', + 'providedRedirectUri' => '', + ] + ) + ->willReturn('https://example.com/?clientIdentifier=foo'); + $this->config + ->expects($this->once()) + ->method('getSystemValueBool') + ->with('oauth2.enable_oc_clients', false) + ->willReturn(false); + + $expected = new RedirectResponse('https://example.com/?clientIdentifier=foo'); + $this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'code')); + } + + public function testAuthorizeWrongResponseType(): void { $client = new Client(); $client->setClientIdentifier('MyClientIdentifier'); $client->setRedirectUri('http://foo.bar'); @@ -118,7 +167,75 @@ class LoginRedirectorControllerTest extends TestCase { $this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'wrongcode')); } - public function testClientNotFound() { + public function testAuthorizeWithLegacyOcClient(): void { + $client = new Client(); + $client->setClientIdentifier('MyClientIdentifier'); + $client->setRedirectUri('http://localhost:*'); + $this->clientMapper + ->expects($this->once()) + ->method('getByIdentifier') + ->with('MyClientId') + ->willReturn($client); + $this->session + ->expects($this->once()) + ->method('set') + ->with('oauth.state', 'MyState'); + $this->urlGenerator + ->expects($this->once()) + ->method('linkToRouteAbsolute') + ->with( + 'core.ClientFlowLogin.showAuthPickerPage', + [ + 'clientIdentifier' => 'MyClientIdentifier', + 'providedRedirectUri' => 'http://localhost:30000', + ] + ) + ->willReturn('https://example.com/?clientIdentifier=foo&providedRedirectUri=http://localhost:30000'); + $this->config + ->expects($this->once()) + ->method('getSystemValueBool') + ->with('oauth2.enable_oc_clients', false) + ->willReturn(true); + + $expected = new RedirectResponse('https://example.com/?clientIdentifier=foo&providedRedirectUri=http://localhost:30000'); + $this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'code', 'http://localhost:30000')); + } + + public function testAuthorizeNotForwardingUntrustedURIs(): void { + $client = new Client(); + $client->setClientIdentifier('MyClientIdentifier'); + $this->clientMapper + ->expects($this->once()) + ->method('getByIdentifier') + ->with('MyClientId') + ->willReturn($client); + $this->session + ->expects($this->once()) + ->method('set') + ->with('oauth.state', 'MyState'); + $this->urlGenerator + ->expects($this->once()) + ->method('linkToRouteAbsolute') + ->with( + 'core.ClientFlowLogin.showAuthPickerPage', + [ + 'clientIdentifier' => 'MyClientIdentifier', + 'providedRedirectUri' => '', + ] + ) + ->willReturn('https://example.com/?clientIdentifier=foo'); + $this->config + ->expects($this->once()) + ->method('getSystemValueBool') + ->with('oauth2.enable_oc_clients', false) + ->willReturn(false); + + $expected = new RedirectResponse('https://example.com/?clientIdentifier=foo'); + $this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'code', 'http://untrusted-uri.com')); + } + + + public function testClientNotFound(): void { $clientNotFound = new ClientNotFoundException('could not find client test123', 0); $this->clientMapper ->expects($this->once()) diff --git a/apps/oauth2/tests/Controller/OauthApiControllerTest.php b/apps/oauth2/tests/Controller/OauthApiControllerTest.php index 24385e785e5..53dd8549196 100644 --- a/apps/oauth2/tests/Controller/OauthApiControllerTest.php +++ b/apps/oauth2/tests/Controller/OauthApiControllerTest.php @@ -1,27 +1,8 @@ <?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; @@ -29,7 +10,6 @@ use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Token\IProvider as TokenProvider; use OC\Authentication\Token\PublicKeyToken; -use OC\Security\Bruteforce\Throttler; use OCA\OAuth2\Controller\OauthApiController; use OCA\OAuth2\Db\AccessToken; use OCA\OAuth2\Db\AccessTokenMapper; @@ -41,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; @@ -60,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', @@ -86,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') @@ -110,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') @@ -122,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); @@ -141,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'], @@ -150,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); @@ -167,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); @@ -177,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); @@ -193,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) @@ -215,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); @@ -227,16 +326,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'); $appToken = new PublicKeyToken(); $appToken->setUid('userId'); @@ -250,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()) @@ -280,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'; }) ); @@ -307,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); @@ -319,16 +422,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'); $appToken = new PublicKeyToken(); $appToken->setUid('userId'); @@ -342,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()) @@ -372,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'; }) ); @@ -402,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); @@ -414,16 +521,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'); $appToken = new PublicKeyToken(); $appToken->setUid('userId'); @@ -437,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()) @@ -467,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'; }) ); diff --git a/apps/oauth2/tests/Controller/SettingsControllerTest.php b/apps/oauth2/tests/Controller/SettingsControllerTest.php index 4954d379f9d..030a220e3d7 100644 --- a/apps/oauth2/tests/Controller/SettingsControllerTest.php +++ b/apps/oauth2/tests/Controller/SettingsControllerTest.php @@ -1,28 +1,8 @@ <?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 rakekniven <mark.ziegler@rakekniven.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; @@ -32,11 +12,19 @@ use OCA\OAuth2\Db\Client; use OCA\OAuth2\Db\ClientMapper; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; +use OCP\Authentication\Token\IProvider as IAuthTokenProvider; use OCP\IL10N; use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Security\ICrypto; use OCP\Security\ISecureRandom; +use OCP\Server; use Test\TestCase; +/** + * @group DB + */ class SettingsControllerTest extends TestCase { /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ private $request; @@ -46,8 +34,16 @@ class SettingsControllerTest extends TestCase { private $secureRandom; /** @var AccessTokenMapper|\PHPUnit\Framework\MockObject\MockObject */ private $accessTokenMapper; + /** @var IAuthTokenProvider|\PHPUnit\Framework\MockObject\MockObject */ + private $authTokenProvider; + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + private $userManager; /** @var SettingsController */ private $settingsController; + /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + private $l; + /** @var ICrypto|\PHPUnit\Framework\MockObject\MockObject */ + private $crypto; protected function setUp(): void { parent::setUp(); @@ -56,46 +52,54 @@ class SettingsControllerTest extends TestCase { $this->clientMapper = $this->createMock(ClientMapper::class); $this->secureRandom = $this->createMock(ISecureRandom::class); $this->accessTokenMapper = $this->createMock(AccessTokenMapper::class); - $l = $this->createMock(IL10N::class); - $l->method('t') + $this->authTokenProvider = $this->createMock(IAuthTokenProvider::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->crypto = $this->createMock(ICrypto::class); + $this->l = $this->createMock(IL10N::class); + $this->l->method('t') ->willReturnArgument(0); - $this->settingsController = new SettingsController( 'oauth2', $this->request, $this->clientMapper, $this->secureRandom, $this->accessTokenMapper, - $l + $this->l, + $this->authTokenProvider, + $this->userManager, + $this->crypto ); + } - public function testAddClient() { + public function testAddClient(): void { $this->secureRandom - ->expects($this->at(0)) + ->expects($this->exactly(2)) ->method('generate') ->with(64, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') - ->willReturn('MySecret'); - $this->secureRandom - ->expects($this->at(1)) - ->method('generate') - ->with(64, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') - ->willReturn('MyClientIdentifier'); + ->willReturnOnConsecutiveCalls( + 'MySecret', + 'MyClientIdentifier'); + + $this->crypto + ->expects($this->once()) + ->method('calculateHMAC') + ->willReturn('MyHashedSecret'); $client = new Client(); $client->setName('My Client Name'); $client->setRedirectUri('https://example.com/'); - $client->setSecret('MySecret'); + $client->setSecret(bin2hex('MyHashedSecret')); $client->setClientIdentifier('MyClientIdentifier'); $this->clientMapper ->expects($this->once()) ->method('insert') ->with($this->callback(function (Client $c) { - return $c->getName() === 'My Client Name' && - $c->getRedirectUri() === 'https://example.com/' && - $c->getSecret() === 'MySecret' && - $c->getClientIdentifier() === 'MyClientIdentifier'; + return $c->getName() === 'My Client Name' + && $c->getRedirectUri() === 'https://example.com/' + && $c->getSecret() === bin2hex('MyHashedSecret') + && $c->getClientIdentifier() === 'MyClientIdentifier'; }))->willReturnCallback(function (Client $c) { $c->setId(42); return $c; @@ -115,12 +119,32 @@ class SettingsControllerTest extends TestCase { ], $data); } - public function testDeleteClient() { + public function testDeleteClient(): void { + + $userManager = Server::get(IUserManager::class); + // count other users in the db before adding our own + $count = 0; + $function = function (IUser $user) use (&$count): void { + if ($user->getLastLogin() > 0) { + $count++; + } + }; + $userManager->callForAllUsers($function); + $user1 = $userManager->createUser('test101', 'test101'); + $user1->updateLastLoginTimestamp(); + $tokenProviderMock = $this->getMockBuilder(IAuthTokenProvider::class)->getMock(); + + // expect one call per user and ensure the correct client name + $tokenProviderMock + ->expects($this->exactly($count + 1)) + ->method('invalidateTokensOfUser') + ->with($this->isType('string'), 'My Client Name'); + $client = new Client(); $client->setId(123); $client->setName('My Client Name'); $client->setRedirectUri('https://example.com/'); - $client->setSecret('MySecret'); + $client->setSecret(bin2hex('MyHashedSecret')); $client->setClientIdentifier('MyClientIdentifier'); $this->clientMapper @@ -132,15 +156,30 @@ class SettingsControllerTest extends TestCase { ->method('deleteByClientId') ->with(123); $this->clientMapper + ->expects($this->once()) ->method('delete') ->with($client); - $result = $this->settingsController->deleteClient(123); + $settingsController = new SettingsController( + 'oauth2', + $this->request, + $this->clientMapper, + $this->secureRandom, + $this->accessTokenMapper, + $this->l, + $tokenProviderMock, + $userManager, + $this->crypto + ); + + $result = $settingsController->deleteClient(123); $this->assertInstanceOf(JSONResponse::class, $result); $this->assertEquals([], $result->getData()); + + $user1->delete(); } - public function testInvalidRedirectUri() { + public function testInvalidRedirectUri(): void { $result = $this->settingsController->addClient('test', 'invalidurl'); $this->assertEquals(Http::STATUS_BAD_REQUEST, $result->getStatus()); diff --git a/apps/oauth2/tests/Db/AccessTokenMapperTest.php b/apps/oauth2/tests/Db/AccessTokenMapperTest.php index 013eb72919a..41a79fe725b 100644 --- a/apps/oauth2/tests/Db/AccessTokenMapperTest.php +++ b/apps/oauth2/tests/Db/AccessTokenMapperTest.php @@ -1,30 +1,17 @@ <?php + /** - * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> - * - * @author Lukas Reschke <lukas@statuscode.ch> - * @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\Db; use OCA\OAuth2\Db\AccessToken; use OCA\OAuth2\Db\AccessTokenMapper; +use OCA\OAuth2\Exceptions\AccessTokenNotFoundException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IDBConnection; +use OCP\Server; use Test\TestCase; /** @@ -36,10 +23,10 @@ class AccessTokenMapperTest extends TestCase { protected function setUp(): void { parent::setUp(); - $this->accessTokenMapper = new AccessTokenMapper(\OC::$server->getDatabaseConnection()); + $this->accessTokenMapper = new AccessTokenMapper(Server::get(IDBConnection::class), Server::get(ITimeFactory::class)); } - public function testGetByCode() { + public function testGetByCode(): void { $this->accessTokenMapper->deleteByClientId(1234); $token = new AccessToken(); $token->setClientId(1234); @@ -54,9 +41,9 @@ class AccessTokenMapperTest extends TestCase { $this->accessTokenMapper->delete($token); } - - public function testDeleteByClientId() { - $this->expectException(\OCA\OAuth2\Exceptions\AccessTokenNotFoundException::class); + + public function testDeleteByClientId(): void { + $this->expectException(AccessTokenNotFoundException::class); $this->accessTokenMapper->deleteByClientId(1234); $token = new AccessToken(); diff --git a/apps/oauth2/tests/Db/ClientMapperTest.php b/apps/oauth2/tests/Db/ClientMapperTest.php index fdc458fa301..2e8d20ad200 100644 --- a/apps/oauth2/tests/Db/ClientMapperTest.php +++ b/apps/oauth2/tests/Db/ClientMapperTest.php @@ -1,31 +1,16 @@ <?php + /** - * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> - * - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @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\Db; use OCA\OAuth2\Db\Client; use OCA\OAuth2\Db\ClientMapper; +use OCA\OAuth2\Exceptions\ClientNotFoundException; +use OCP\IDBConnection; +use OCP\Server; use Test\TestCase; /** @@ -37,17 +22,17 @@ class ClientMapperTest extends TestCase { protected function setUp(): void { parent::setUp(); - $this->clientMapper = new ClientMapper(\OC::$server->getDatabaseConnection()); + $this->clientMapper = new ClientMapper(Server::get(IDBConnection::class)); } protected function tearDown(): void { - $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $query = Server::get(IDBConnection::class)->getQueryBuilder(); $query->delete('oauth2_clients')->execute(); parent::tearDown(); } - public function testGetByIdentifier() { + public function testGetByIdentifier(): void { $client = new Client(); $client->setClientIdentifier('MyAwesomeClientIdentifier'); $client->setName('Client Name'); @@ -58,13 +43,13 @@ class ClientMapperTest extends TestCase { $this->assertEquals($client, $this->clientMapper->getByIdentifier('MyAwesomeClientIdentifier')); } - public function testGetByIdentifierNotExisting() { - $this->expectException(\OCA\OAuth2\Exceptions\ClientNotFoundException::class); + public function testGetByIdentifierNotExisting(): void { + $this->expectException(ClientNotFoundException::class); $this->clientMapper->getByIdentifier('MyTotallyNotExistingClient'); } - public function testGetByUid() { + public function testGetByUid(): void { $client = new Client(); $client->setClientIdentifier('MyNewClient'); $client->setName('Client Name'); @@ -75,13 +60,23 @@ class ClientMapperTest extends TestCase { $this->assertEquals($client, $this->clientMapper->getByUid($client->getId())); } - public function testGetByUidNotExisting() { - $this->expectException(\OCA\OAuth2\Exceptions\ClientNotFoundException::class); + public function testGetByUidNotExisting(): void { + $this->expectException(ClientNotFoundException::class); $this->clientMapper->getByUid(1234); } - public function testGetClients() { + public function testGetClients(): void { $this->assertSame('array', gettype($this->clientMapper->getClients())); } + + public function testInsertLongEncryptedSecret(): void { + $client = new Client(); + $client->setClientIdentifier('MyNewClient'); + $client->setName('Client Name'); + $client->setRedirectUri('https://example.com/'); + $client->setSecret('b81dc8e2dc178817bf28ca7b37265aa96559ca02e6dcdeb74b42221d096ed5ef63681e836ae0ba1077b5fb5e6c2fa7748c78463f66fe0110c8dcb8dd7eb0305b16d0cd993e2ae275879994a2abf88c68|e466d9befa6b0102341458e45ecd551a|013af9e277374483123437f180a3b0371a411ad4f34c451547909769181a7d7cc191f0f5c2de78376d124dd7751b8c9660aabdd913f5e071fc6b819ba2e3d919|3'); + $this->clientMapper->insert($client); + $this->assertTrue(true); + } } diff --git a/apps/oauth2/tests/Settings/AdminTest.php b/apps/oauth2/tests/Settings/AdminTest.php index 93a9c528420..0f08bb30276 100644 --- a/apps/oauth2/tests/Settings/AdminTest.php +++ b/apps/oauth2/tests/Settings/AdminTest.php @@ -1,33 +1,18 @@ <?php + /** - * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> - * - * @author Lukas Reschke <lukas@statuscode.ch> - * @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\Settings; use OCA\OAuth2\Db\ClientMapper; use OCA\OAuth2\Settings\Admin; use OCP\AppFramework\Http\TemplateResponse; -use OCP\IInitialStateService; +use OCP\AppFramework\Services\IInitialState; +use OCP\IURLGenerator; use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; use Test\TestCase; class AdminTest extends TestCase { @@ -35,8 +20,8 @@ class AdminTest extends TestCase { /** @var Admin|MockObject */ private $admin; - /** @var IInitialStateService|MockObject */ - private $initialStateService; + /** @var IInitialState|MockObject */ + private $initialState; /** @var ClientMapper|MockObject */ private $clientMapper; @@ -44,13 +29,18 @@ class AdminTest extends TestCase { protected function setUp(): void { parent::setUp(); - $this->initialStateService = $this->createMock(IInitialStateService::class); + $this->initialState = $this->createMock(IInitialState::class); $this->clientMapper = $this->createMock(ClientMapper::class); - $this->admin = new Admin($this->initialStateService, $this->clientMapper); + $this->admin = new Admin( + $this->initialState, + $this->clientMapper, + $this->createMock(IURLGenerator::class), + $this->createMock(LoggerInterface::class) + ); } - public function testGetForm() { + public function testGetForm(): void { $expected = new TemplateResponse( 'oauth2', 'admin', @@ -60,11 +50,11 @@ class AdminTest extends TestCase { $this->assertEquals($expected, $this->admin->getForm()); } - public function testGetSection() { + public function testGetSection(): void { $this->assertSame('security', $this->admin->getSection()); } - public function testGetPriority() { + public function testGetPriority(): void { $this->assertSame(100, $this->admin->getPriority()); } } |