diff options
Diffstat (limited to 'tests/lib/Authentication/Token')
-rw-r--r-- | tests/lib/Authentication/Token/ManagerTest.php | 406 | ||||
-rw-r--r-- | tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php | 265 | ||||
-rw-r--r-- | tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php | 645 | ||||
-rw-r--r-- | tests/lib/Authentication/Token/PublicKeyTokenTest.php | 29 | ||||
-rw-r--r-- | tests/lib/Authentication/Token/RemoteWipeTest.php | 173 |
5 files changed, 1518 insertions, 0 deletions
diff --git a/tests/lib/Authentication/Token/ManagerTest.php b/tests/lib/Authentication/Token/ManagerTest.php new file mode 100644 index 00000000000..58bbe236248 --- /dev/null +++ b/tests/lib/Authentication/Token/ManagerTest.php @@ -0,0 +1,406 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Authentication\Token; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Token\IToken; +use OC\Authentication\Token\Manager; +use OC\Authentication\Token\PublicKeyToken; +use OC\Authentication\Token\PublicKeyTokenProvider; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class ManagerTest extends TestCase { + /** @var PublicKeyTokenProvider|MockObject */ + private $publicKeyTokenProvider; + /** @var Manager */ + private $manager; + + protected function setUp(): void { + parent::setUp(); + + $this->publicKeyTokenProvider = $this->createMock(PublicKeyTokenProvider::class); + $this->manager = new Manager( + $this->publicKeyTokenProvider + ); + } + + public function testGenerateToken(): void { + $token = new PublicKeyToken(); + + $this->publicKeyTokenProvider->expects($this->once()) + ->method('generateToken') + ->with( + 'token', + 'uid', + 'loginName', + 'password', + 'name', + IToken::TEMPORARY_TOKEN, + IToken::REMEMBER + )->willReturn($token); + + $actual = $this->manager->generateToken( + 'token', + 'uid', + 'loginName', + 'password', + 'name', + IToken::TEMPORARY_TOKEN, + IToken::REMEMBER + ); + + $this->assertSame($token, $actual); + } + + public function testGenerateConflictingToken(): void { + /** @var MockObject|UniqueConstraintViolationException $exception */ + $exception = $this->createMock(UniqueConstraintViolationException::class); + + $token = new PublicKeyToken(); + $token->setUid('uid'); + + $this->publicKeyTokenProvider->expects($this->once()) + ->method('generateToken') + ->with( + 'token', + 'uid', + 'loginName', + 'password', + 'name', + IToken::TEMPORARY_TOKEN, + IToken::REMEMBER + )->willThrowException($exception); + $this->publicKeyTokenProvider->expects($this->once()) + ->method('getToken') + ->with('token') + ->willReturn($token); + + $actual = $this->manager->generateToken( + 'token', + 'uid', + 'loginName', + 'password', + 'name', + IToken::TEMPORARY_TOKEN, + IToken::REMEMBER + ); + + $this->assertSame($token, $actual); + } + + public function testGenerateTokenTooLongName(): void { + $token = $this->createMock(IToken::class); + $token->method('getName') + ->willReturn(str_repeat('a', 120) . '…'); + + + $this->publicKeyTokenProvider->expects($this->once()) + ->method('generateToken') + ->with( + 'token', + 'uid', + 'loginName', + 'password', + str_repeat('a', 120) . '…', + IToken::TEMPORARY_TOKEN, + IToken::REMEMBER + )->willReturn($token); + + $actual = $this->manager->generateToken( + 'token', + 'uid', + 'loginName', + 'password', + str_repeat('a', 200), + IToken::TEMPORARY_TOKEN, + IToken::REMEMBER + ); + + $this->assertSame(121, mb_strlen($actual->getName())); + } + + public static function tokenData(): array { + return [ + [new PublicKeyToken()], + [IToken::class], + ]; + } + + protected function setNoCall(IToken $token) { + if (!($token instanceof PublicKeyToken)) { + $this->publicKeyTokenProvider->expects($this->never()) + ->method($this->anything()); + } + } + + protected function setCall(IToken $token, string $function, $return = null) { + if ($token instanceof PublicKeyToken) { + $this->publicKeyTokenProvider->expects($this->once()) + ->method($function) + ->with($token) + ->willReturn($return); + } + } + + protected function setException(IToken $token) { + if (!($token instanceof PublicKeyToken)) { + $this->expectException(InvalidTokenException::class); + } + } + + #[\PHPUnit\Framework\Attributes\DataProvider('tokenData')] + public function testUpdateToken(IToken|string $token): void { + if (is_string($token)) { + $token = $this->createMock($token); + } + + $this->setNoCall($token); + $this->setCall($token, 'updateToken'); + $this->setException($token); + + $this->manager->updateToken($token); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('tokenData')] + public function testUpdateTokenActivity(IToken|string $token): void { + if (is_string($token)) { + $token = $this->createMock($token); + } + + $this->setNoCall($token); + $this->setCall($token, 'updateTokenActivity'); + $this->setException($token); + + $this->manager->updateTokenActivity($token); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('tokenData')] + public function testGetPassword(IToken|string $token): void { + if (is_string($token)) { + $token = $this->createMock($token); + } + + $this->setNoCall($token); + $this->setCall($token, 'getPassword', 'password'); + $this->setException($token); + + $result = $this->manager->getPassword($token, 'tokenId', 'password'); + + $this->assertSame('password', $result); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('tokenData')] + public function testSetPassword(IToken|string $token): void { + if (is_string($token)) { + $token = $this->createMock($token); + } + + $this->setNoCall($token); + $this->setCall($token, 'setPassword'); + $this->setException($token); + + $this->manager->setPassword($token, 'tokenId', 'password'); + } + + public function testInvalidateTokens(): void { + $this->publicKeyTokenProvider->expects($this->once()) + ->method('invalidateToken') + ->with('token'); + + $this->manager->invalidateToken('token'); + } + + public function testInvalidateTokenById(): void { + $this->publicKeyTokenProvider->expects($this->once()) + ->method('invalidateTokenById') + ->with('uid', 42); + + $this->manager->invalidateTokenById('uid', 42); + } + + public function testInvalidateOldTokens(): void { + $this->publicKeyTokenProvider->expects($this->once()) + ->method('invalidateOldTokens'); + + $this->manager->invalidateOldTokens(); + } + + public function testInvalidateLastUsedBefore(): void { + $this->publicKeyTokenProvider->expects($this->once()) + ->method('invalidateLastUsedBefore') + ->with('user', 946684800); + + $this->manager->invalidateLastUsedBefore('user', 946684800); + } + + public function testGetTokenByUser(): void { + $t1 = new PublicKeyToken(); + $t2 = new PublicKeyToken(); + + $this->publicKeyTokenProvider + ->method('getTokenByUser') + ->willReturn([$t1, $t2]); + + $result = $this->manager->getTokenByUser('uid'); + + $this->assertEquals([$t1, $t2], $result); + } + + public function testRenewSessionTokenPublicKey(): void { + $this->publicKeyTokenProvider->expects($this->once()) + ->method('renewSessionToken') + ->with('oldId', 'newId'); + + $this->manager->renewSessionToken('oldId', 'newId'); + } + + public function testRenewSessionInvalid(): void { + $this->publicKeyTokenProvider->expects($this->once()) + ->method('renewSessionToken') + ->with('oldId', 'newId') + ->willThrowException(new InvalidTokenException()); + + $this->expectException(InvalidTokenException::class); + $this->manager->renewSessionToken('oldId', 'newId'); + } + + public function testGetTokenByIdPublicKey(): void { + $token = $this->createMock(IToken::class); + + $this->publicKeyTokenProvider->expects($this->once()) + ->method('getTokenById') + ->with(42) + ->willReturn($token); + + $this->assertSame($token, $this->manager->getTokenById(42)); + } + + public function testGetTokenByIdInvalid(): void { + $this->publicKeyTokenProvider->expects($this->once()) + ->method('getTokenById') + ->with(42) + ->willThrowException(new InvalidTokenException()); + + $this->expectException(InvalidTokenException::class); + $this->manager->getTokenById(42); + } + + public function testGetTokenPublicKey(): void { + $token = new PublicKeyToken(); + + $this->publicKeyTokenProvider + ->method('getToken') + ->with('tokenId') + ->willReturn($token); + + $this->assertSame($token, $this->manager->getToken('tokenId')); + } + + public function testGetTokenInvalid(): void { + $this->publicKeyTokenProvider + ->method('getToken') + ->with('tokenId') + ->willThrowException(new InvalidTokenException()); + + $this->expectException(InvalidTokenException::class); + $this->manager->getToken('tokenId'); + } + + public function testRotateInvalid(): void { + $this->expectException(InvalidTokenException::class); + $this->manager->rotate($this->createMock(IToken::class), 'oldId', 'newId'); + } + + public function testRotatePublicKey(): void { + $token = new PublicKeyToken(); + + $this->publicKeyTokenProvider + ->method('rotate') + ->with($token, 'oldId', 'newId') + ->willReturn($token); + + $this->assertSame($token, $this->manager->rotate($token, 'oldId', 'newId')); + } + + public function testMarkPasswordInvalidPublicKey(): void { + $token = $this->createMock(PublicKeyToken::class); + + $this->publicKeyTokenProvider->expects($this->once()) + ->method('markPasswordInvalid') + ->with($token, 'tokenId'); + + $this->manager->markPasswordInvalid($token, 'tokenId'); + } + + public function testMarkPasswordInvalidInvalidToken(): void { + $this->expectException(InvalidTokenException::class); + + $this->manager->markPasswordInvalid($this->createMock(IToken::class), 'tokenId'); + } + + public function testUpdatePasswords(): void { + $this->publicKeyTokenProvider->expects($this->once()) + ->method('updatePasswords') + ->with('uid', 'pass'); + + $this->manager->updatePasswords('uid', 'pass'); + } + + public function testInvalidateTokensOfUserNoClientName(): void { + $t1 = new PublicKeyToken(); + $t2 = new PublicKeyToken(); + $t1->setId(123); + $t2->setId(456); + + $this->publicKeyTokenProvider + ->expects($this->once()) + ->method('getTokenByUser') + ->with('theUser') + ->willReturn([$t1, $t2]); + + $calls = [ + ['theUser', 123], + ['theUser', 456], + ]; + $this->publicKeyTokenProvider + ->expects($this->exactly(2)) + ->method('invalidateTokenById') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + $this->manager->invalidateTokensOfUser('theUser', null); + } + + public function testInvalidateTokensOfUserClientNameGiven(): void { + $t1 = new PublicKeyToken(); + $t2 = new PublicKeyToken(); + $t3 = new PublicKeyToken(); + $t1->setId(123); + $t1->setName('Firefox session'); + $t2->setId(456); + $t2->setName('My Client Name'); + $t3->setId(789); + $t3->setName('mobile client'); + + $this->publicKeyTokenProvider + ->expects($this->once()) + ->method('getTokenByUser') + ->with('theUser') + ->willReturn([$t1, $t2, $t3]); + $this->publicKeyTokenProvider + ->expects($this->once()) + ->method('invalidateTokenById') + ->with('theUser', 456); + $this->manager->invalidateTokensOfUser('theUser', 'My Client Name'); + } +} diff --git a/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php new file mode 100644 index 00000000000..d1585dadc26 --- /dev/null +++ b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php @@ -0,0 +1,265 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Authentication\Token; + +use OC\Authentication\Token\PublicKeyToken; +use OC\Authentication\Token\PublicKeyTokenMapper; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\Authentication\Token\IToken; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IUser; +use OCP\Server; +use Test\TestCase; + +/** + * @group DB + */ +class PublicKeyTokenMapperTest extends TestCase { + /** @var PublicKeyTokenMapper */ + private $mapper; + + /** @var IDBConnection */ + private $dbConnection; + + /** @var int */ + private $time; + + protected function setUp(): void { + parent::setUp(); + + $this->dbConnection = Server::get(IDBConnection::class); + $this->time = time(); + $this->resetDatabase(); + + $this->mapper = new PublicKeyTokenMapper($this->dbConnection); + } + + private function resetDatabase() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete('authtoken')->execute(); + $qb->insert('authtoken')->values([ + 'uid' => $qb->createNamedParameter('user1'), + 'login_name' => $qb->createNamedParameter('User1'), + 'password' => $qb->createNamedParameter('a75c7116460c082912d8f6860a850904|3nz5qbG1nNSLLi6V|c55365a0e54cfdfac4a175bcf11a7612aea74492277bba6e5d96a24497fa9272488787cb2f3ad34d8b9b8060934fce02f008d371df3ff3848f4aa61944851ff0'), + 'name' => $qb->createNamedParameter('Firefox on Linux'), + 'token' => $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'), + 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN), + 'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago + 'last_check' => $this->time - 60 * 10, // 10mins ago + 'public_key' => $qb->createNamedParameter('public key'), + 'private_key' => $qb->createNamedParameter('private key'), + 'version' => $qb->createNamedParameter(2), + ])->execute(); + $qb->insert('authtoken')->values([ + 'uid' => $qb->createNamedParameter('user2'), + 'login_name' => $qb->createNamedParameter('User2'), + 'password' => $qb->createNamedParameter('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f'), + 'name' => $qb->createNamedParameter('Firefox on Android'), + 'token' => $qb->createNamedParameter('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'), + 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN), + 'last_activity' => $qb->createNamedParameter($this->time - 60 * 60 * 24 * 3, IQueryBuilder::PARAM_INT), // Three days ago + 'last_check' => $this->time - 10, // 10secs ago + 'public_key' => $qb->createNamedParameter('public key'), + 'private_key' => $qb->createNamedParameter('private key'), + 'version' => $qb->createNamedParameter(2), + ])->execute(); + $qb->insert('authtoken')->values([ + 'uid' => $qb->createNamedParameter('user1'), + 'login_name' => $qb->createNamedParameter('User1'), + 'password' => $qb->createNamedParameter('063de945d6f6b26862d9b6f40652f2d5|DZ/z520tfdXPtd0T|395f6b89be8d9d605e409e20b9d9abe477fde1be38a3223f9e508f979bf906e50d9eaa4dca983ca4fb22a241eb696c3f98654e7775f78c4caf13108f98642b53'), + 'name' => $qb->createNamedParameter('Iceweasel on Linux'), + 'token' => $qb->createNamedParameter('47af8697ba590fb82579b5f1b3b6e8066773a62100abbe0db09a289a62f5d980dc300fa3d98b01d7228468d1ab05c1aa14c8d14bd5b6eee9cdf1ac14864680c3'), + 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN), + 'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago + 'last_check' => $this->time - 60 * 10, // 10mins ago + 'public_key' => $qb->createNamedParameter('public key'), + 'private_key' => $qb->createNamedParameter('private key'), + 'version' => $qb->createNamedParameter(2), + ])->execute(); + $qb->insert('authtoken')->values([ + 'uid' => $qb->createNamedParameter('user3'), + 'login_name' => $qb->createNamedParameter('User3'), + 'password' => $qb->createNamedParameter('063de945d6f6b26862d9b6f40652f2d5|DZ/z520tfdXPtd0T|395f6b89be8d9d605e409e20b9d9abe477fde1be38a3223f9e508f979bf906e50d9eaa4dca983ca4fb22a241eb696c3f98654e7775f78c4caf13108f98642b53'), + 'name' => $qb->createNamedParameter('Iceweasel on Linux'), + 'token' => $qb->createNamedParameter('6d9a290d239d09f2cc33a03cc54cccd46f7dc71630dcc27d39214824bd3e093f1feb4e2b55eb159d204caa15dee9556c202a5aa0b9d67806c3f4ec2cde11af67'), + 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN), + 'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago + 'last_check' => $this->time - 60 * 10, // 10mins ago + 'public_key' => $qb->createNamedParameter('public key'), + 'private_key' => $qb->createNamedParameter('private key'), + 'version' => $qb->createNamedParameter(2), + 'password_invalid' => $qb->createNamedParameter(1), + ])->execute(); + $qb->insert('authtoken')->values([ + 'uid' => $qb->createNamedParameter('user3'), + 'login_name' => $qb->createNamedParameter('User3'), + 'password' => $qb->createNamedParameter('063de945d6f6b26862d9b6f40652f2d5|DZ/z520tfdXPtd0T|395f6b89be8d9d605e409e20b9d9abe477fde1be38a3223f9e508f979bf906e50d9eaa4dca983ca4fb22a241eb696c3f98654e7775f78c4caf13108f98642b53'), + 'name' => $qb->createNamedParameter('Iceweasel on Linux'), + 'token' => $qb->createNamedParameter('84c5808c6445b6d65b8aa5b03840f09b27de603f0fb970906fb14ea4b115b7bf5ec53fada5c093fe46afdcd7bbc9617253a4d105f7dfb32719f9973d72412f31'), + 'type' => $qb->createNamedParameter(IToken::PERMANENT_TOKEN), + 'last_activity' => $qb->createNamedParameter($this->time - 60 * 3, IQueryBuilder::PARAM_INT), // Three minutes ago + 'last_check' => $this->time - 60 * 10, // 10mins ago + 'public_key' => $qb->createNamedParameter('public key'), + 'private_key' => $qb->createNamedParameter('private key'), + 'version' => $qb->createNamedParameter(2), + 'password_invalid' => $qb->createNamedParameter(1), + ])->execute(); + } + + private function getNumberOfTokens() { + $qb = $this->dbConnection->getQueryBuilder(); + $result = $qb->select($qb->func()->count('*', 'count')) + ->from('authtoken') + ->execute() + ->fetch(); + return (int)$result['count']; + } + + public function testInvalidate(): void { + $token = '9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'; + + $this->mapper->invalidate($token); + + $this->assertSame(4, $this->getNumberOfTokens()); + } + + public function testInvalidateInvalid(): void { + $token = 'youwontfindthisoneinthedatabase'; + + $this->mapper->invalidate($token); + + $this->assertSame(5, $this->getNumberOfTokens()); + } + + public function testInvalidateOld(): void { + $olderThan = $this->time - 60 * 60; // One hour + + $this->mapper->invalidateOld($olderThan); + + $this->assertSame(4, $this->getNumberOfTokens()); + } + + public function testInvalidateLastUsedBefore(): void { + $before = $this->time - 60 * 2; // Two minutes + + $this->mapper->invalidateLastUsedBefore('user3', $before); + + $this->assertSame(4, $this->getNumberOfTokens()); + } + + public function testGetToken(): void { + $token = new PublicKeyToken(); + $token->setUid('user2'); + $token->setLoginName('User2'); + $token->setPassword('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f'); + $token->setName('Firefox on Android'); + $token->setToken('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'); + $token->setType(IToken::TEMPORARY_TOKEN); + $token->setRemember(IToken::DO_NOT_REMEMBER); + $token->setLastActivity($this->time - 60 * 60 * 24 * 3); + $token->setLastCheck($this->time - 10); + $token->setPublicKey('public key'); + $token->setPrivateKey('private key'); + $token->setVersion(PublicKeyToken::VERSION); + + $dbToken = $this->mapper->getToken($token->getToken()); + + $token->setId($dbToken->getId()); // We don't know the ID + $token->resetUpdatedFields(); + + $this->assertEquals($token, $dbToken); + } + + + public function testGetInvalidToken(): void { + $this->expectException(DoesNotExistException::class); + + $token = 'thisisaninvalidtokenthatisnotinthedatabase'; + + $this->mapper->getToken($token); + } + + public function testGetTokenById(): void { + $token = new PublicKeyToken(); + $token->setUid('user2'); + $token->setLoginName('User2'); + $token->setPassword('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f'); + $token->setName('Firefox on Android'); + $token->setToken('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'); + $token->setType(IToken::TEMPORARY_TOKEN); + $token->setRemember(IToken::DO_NOT_REMEMBER); + $token->setLastActivity($this->time - 60 * 60 * 24 * 3); + $token->setLastCheck($this->time - 10); + $token->setPublicKey('public key'); + $token->setPrivateKey('private key'); + $token->setVersion(PublicKeyToken::VERSION); + + $dbToken = $this->mapper->getToken($token->getToken()); + $token->setId($dbToken->getId()); // We don't know the ID + $token->resetUpdatedFields(); + + $dbToken = $this->mapper->getTokenById($token->getId()); + $this->assertEquals($token, $dbToken); + } + + + public function testGetTokenByIdNotFound(): void { + $this->expectException(DoesNotExistException::class); + + $this->mapper->getTokenById(-1); + } + + + public function testGetInvalidTokenById(): void { + $this->expectException(DoesNotExistException::class); + + $id = '42'; + + $this->mapper->getToken($id); + } + + public function testGetTokenByUser(): void { + $this->assertCount(2, $this->mapper->getTokenByUser('user1')); + } + + public function testGetTokenByUserNotFound(): void { + $this->assertCount(0, $this->mapper->getTokenByUser('user1000')); + } + + public function testGetById(): void { + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */ + $user = $this->createMock(IUser::class); + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('id') + ->from('authtoken') + ->where($qb->expr()->eq('token', $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'))); + $result = $qb->execute(); + $id = $result->fetch()['id']; + + $token = $this->mapper->getTokenById((int)$id); + $this->assertEquals('user1', $token->getUID()); + } + + public function testDeleteByName(): void { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('name') + ->from('authtoken') + ->where($qb->expr()->eq('token', $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'))); + $result = $qb->execute(); + $name = $result->fetch()['name']; + $this->mapper->deleteByName($name); + $this->assertEquals(4, $this->getNumberOfTokens()); + } + + public function testHasExpiredTokens(): void { + $this->assertFalse($this->mapper->hasExpiredTokens('user1')); + $this->assertTrue($this->mapper->hasExpiredTokens('user3')); + } +} diff --git a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php new file mode 100644 index 00000000000..7e7f949965f --- /dev/null +++ b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php @@ -0,0 +1,645 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Authentication\Token; + +use OC\Authentication\Exceptions\ExpiredTokenException; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; +use OC\Authentication\Token\PublicKeyToken; +use OC\Authentication\Token\PublicKeyTokenMapper; +use OC\Authentication\Token\PublicKeyTokenProvider; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\Token\IToken; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\Security\ICrypto; +use OCP\Security\IHasher; +use OCP\Server; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +class PublicKeyTokenProviderTest extends TestCase { + /** @var PublicKeyTokenProvider|\PHPUnit\Framework\MockObject\MockObject */ + private $tokenProvider; + /** @var PublicKeyTokenMapper|\PHPUnit\Framework\MockObject\MockObject */ + private $mapper; + /** @var IHasher|\PHPUnit\Framework\MockObject\MockObject */ + private $hasher; + /** @var ICrypto */ + private $crypto; + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + private $config; + /** @var IDBConnection|MockObject */ + private IDBConnection $db; + /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $logger; + /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ + private $timeFactory; + /** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */ + private $cacheFactory; + /** @var int */ + private $time; + + protected function setUp(): void { + parent::setUp(); + + $this->mapper = $this->createMock(PublicKeyTokenMapper::class); + $this->hasher = Server::get(IHasher::class); + $this->crypto = Server::get(ICrypto::class); + $this->config = $this->createMock(IConfig::class); + $this->config->method('getSystemValue') + ->willReturnMap([ + ['openssl', [], []], + ]); + $this->config->method('getSystemValueString') + ->willReturnMap([ + ['secret', '', '1f4h9s'], + ]); + $this->db = $this->createMock(IDBConnection::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->time = 1313131; + $this->timeFactory->method('getTime') + ->willReturn($this->time); + $this->cacheFactory = $this->createMock(ICacheFactory::class); + + $this->tokenProvider = new PublicKeyTokenProvider( + $this->mapper, + $this->crypto, + $this->config, + $this->db, + $this->logger, + $this->timeFactory, + $this->hasher, + $this->cacheFactory, + ); + } + + public function testGenerateToken(): void { + $token = 'tokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = 'passme'; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $this->assertInstanceOf(PublicKeyToken::class, $actual); + $this->assertSame($uid, $actual->getUID()); + $this->assertSame($user, $actual->getLoginName()); + $this->assertSame($name, $actual->getName()); + $this->assertSame(IToken::DO_NOT_REMEMBER, $actual->getRemember()); + $this->assertSame($password, $this->tokenProvider->getPassword($actual, $token)); + } + + public function testGenerateTokenNoPassword(): void { + $token = 'tokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = 'passme'; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, false], + ]); + $this->expectException(PasswordlessTokenException::class); + + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $this->assertInstanceOf(PublicKeyToken::class, $actual); + $this->assertSame($uid, $actual->getUID()); + $this->assertSame($user, $actual->getLoginName()); + $this->assertSame($name, $actual->getName()); + $this->assertSame(IToken::DO_NOT_REMEMBER, $actual->getRemember()); + $this->tokenProvider->getPassword($actual, $token); + } + + public function testGenerateTokenLongPassword(): void { + $token = 'tokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = ''; + for ($i = 0; $i < 500; $i++) { + $password .= 'e'; + } + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + $this->expectException(\RuntimeException::class); + + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + } + + public function testGenerateTokenInvalidName(): void { + $token = 'tokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = 'passme'; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12' + . 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12' + . 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12' + . 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $this->assertInstanceOf(PublicKeyToken::class, $actual); + $this->assertSame($uid, $actual->getUID()); + $this->assertSame($user, $actual->getLoginName()); + $this->assertSame('User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12User-Agent: Mozill…', $actual->getName()); + $this->assertSame(IToken::DO_NOT_REMEMBER, $actual->getRemember()); + $this->assertSame($password, $this->tokenProvider->getPassword($actual, $token)); + } + + public function testUpdateToken(): void { + $tk = new PublicKeyToken(); + $this->mapper->expects($this->once()) + ->method('updateActivity') + ->with($tk, $this->time); + $tk->setLastActivity($this->time - 200); + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + + $this->tokenProvider->updateTokenActivity($tk); + + $this->assertEquals($this->time, $tk->getLastActivity()); + } + + public function testUpdateTokenDebounce(): void { + $tk = new PublicKeyToken(); + $this->config->method('getSystemValueInt') + ->willReturnCallback(function ($value, $default) { + return $default; + }); + $tk->setLastActivity($this->time - 30); + + $this->mapper->expects($this->never()) + ->method('updateActivity') + ->with($tk, $this->time); + + $this->tokenProvider->updateTokenActivity($tk); + } + + public function testGetTokenByUser(): void { + $this->mapper->expects($this->once()) + ->method('getTokenByUser') + ->with('uid') + ->willReturn(['token']); + + $this->assertEquals(['token'], $this->tokenProvider->getTokenByUser('uid')); + } + + public function testGetPassword(): void { + $token = 'tokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = 'passme'; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $this->assertSame($password, $this->tokenProvider->getPassword($actual, $token)); + } + + + public function testGetPasswordPasswordLessToken(): void { + $this->expectException(PasswordlessTokenException::class); + + $token = 'token1234'; + $tk = new PublicKeyToken(); + $tk->setPassword(null); + + $this->tokenProvider->getPassword($tk, $token); + } + + + public function testGetPasswordInvalidToken(): void { + $this->expectException(InvalidTokenException::class); + + $token = 'tokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = 'passme'; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $this->tokenProvider->getPassword($actual, 'wrongtoken'); + } + + public function testSetPassword(): void { + $token = 'tokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = 'passme'; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $this->mapper->method('getTokenByUser') + ->with('user') + ->willReturn([$actual]); + + $newpass = 'newpass'; + $this->mapper->expects($this->once()) + ->method('update') + ->with($this->callback(function ($token) use ($newpass) { + return $newpass === $this->tokenProvider->getPassword($token, 'tokentokentokentokentoken'); + })); + + + $this->tokenProvider->setPassword($actual, $token, $newpass); + + $this->assertSame($newpass, $this->tokenProvider->getPassword($actual, 'tokentokentokentokentoken')); + } + + + public function testSetPasswordInvalidToken(): void { + $this->expectException(InvalidTokenException::class); + + $token = $this->createMock(IToken::class); + $tokenId = 'token123'; + $password = '123456'; + + $this->tokenProvider->setPassword($token, $tokenId, $password); + } + + public function testInvalidateToken(): void { + $calls = [ + [hash('sha512', 'token7' . '1f4h9s')], + [hash('sha512', 'token7')] + ]; + + $this->mapper->expects($this->exactly(2)) + ->method('invalidate') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + + $this->tokenProvider->invalidateToken('token7'); + } + + public function testInvalidateTokenById(): void { + $id = 123; + + $this->mapper->expects($this->once()) + ->method('getTokenById') + ->with($id); + + $this->tokenProvider->invalidateTokenById('uid', $id); + } + + public function testInvalidateOldTokens(): void { + $defaultSessionLifetime = 60 * 60 * 24; + $defaultRememberMeLifetime = 60 * 60 * 24 * 15; + $wipeTokenLifetime = 60 * 60 * 24 * 60; + $this->config->expects($this->exactly(4)) + ->method('getSystemValueInt') + ->willReturnMap([ + ['session_lifetime', $defaultSessionLifetime, 150], + ['remember_login_cookie_lifetime', $defaultRememberMeLifetime, 300], + ['token_auth_wipe_token_retention', $wipeTokenLifetime, 500], + ['token_auth_token_retention', 60 * 60 * 24 * 365, 800], + ]); + + $calls = [ + [$this->time - 150, IToken::TEMPORARY_TOKEN, IToken::DO_NOT_REMEMBER], + [$this->time - 300, IToken::TEMPORARY_TOKEN, IToken::REMEMBER], + [$this->time - 500, IToken::WIPE_TOKEN, null], + [$this->time - 800, IToken::PERMANENT_TOKEN, null], + ]; + $this->mapper->expects($this->exactly(4)) + ->method('invalidateOld') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + + $this->tokenProvider->invalidateOldTokens(); + } + + public function testInvalidateLastUsedBefore(): void { + $this->mapper->expects($this->once()) + ->method('invalidateLastUsedBefore') + ->with('user', 946684800); + + $this->tokenProvider->invalidateLastUsedBefore('user', 946684800); + } + + public function testRenewSessionTokenWithoutPassword(): void { + $token = 'oldIdtokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = null; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + + $oldToken = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $this->mapper + ->expects($this->once()) + ->method('getToken') + ->with(hash('sha512', 'oldIdtokentokentokentoken' . '1f4h9s')) + ->willReturn($oldToken); + $this->mapper + ->expects($this->once()) + ->method('insert') + ->with($this->callback(function (PublicKeyToken $token) use ($user, $uid, $name) { + return $token->getUID() === $uid + && $token->getLoginName() === $user + && $token->getName() === $name + && $token->getType() === IToken::DO_NOT_REMEMBER + && $token->getLastActivity() === $this->time + && $token->getPassword() === null; + })); + $this->mapper + ->expects($this->once()) + ->method('delete') + ->with($this->callback(function ($token) use ($oldToken) { + return $token === $oldToken; + })); + + $this->tokenProvider->renewSessionToken('oldIdtokentokentokentoken', 'newIdtokentokentokentoken'); + } + + public function testRenewSessionTokenWithPassword(): void { + $token = 'oldIdtokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = 'password'; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + $oldToken = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $this->mapper + ->expects($this->once()) + ->method('getToken') + ->with(hash('sha512', 'oldIdtokentokentokentoken' . '1f4h9s')) + ->willReturn($oldToken); + $this->mapper + ->expects($this->once()) + ->method('insert') + ->with($this->callback(function (PublicKeyToken $token) use ($user, $uid, $name): bool { + return $token->getUID() === $uid + && $token->getLoginName() === $user + && $token->getName() === $name + && $token->getType() === IToken::DO_NOT_REMEMBER + && $token->getLastActivity() === $this->time + && $token->getPassword() !== null + && $this->tokenProvider->getPassword($token, 'newIdtokentokentokentoken') === 'password'; + })); + $this->mapper + ->expects($this->once()) + ->method('delete') + ->with($this->callback(function ($token) use ($oldToken): bool { + return $token === $oldToken; + })); + + $this->tokenProvider->renewSessionToken('oldIdtokentokentokentoken', 'newIdtokentokentokentoken'); + } + + public function testGetToken(): void { + $token = new PublicKeyToken(); + + $this->config->method('getSystemValue') + ->with('secret') + ->willReturn('mysecret'); + + $this->mapper->method('getToken') + ->with( + $this->callback(function (string $token) { + return hash('sha512', 'unhashedTokentokentokentokentoken' . '1f4h9s') === $token; + }) + )->willReturn($token); + + $this->assertSame($token, $this->tokenProvider->getToken('unhashedTokentokentokentokentoken')); + } + + public function testGetInvalidToken(): void { + $this->expectException(InvalidTokenException::class); + + $calls = [ + 'unhashedTokentokentokentokentoken' . '1f4h9s', + 'unhashedTokentokentokentokentoken', + ]; + $this->mapper->expects($this->exactly(2)) + ->method('getToken') + ->willReturnCallback(function (string $token) use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals(hash('sha512', $expected), $token); + throw new DoesNotExistException('nope'); + }); + + $this->tokenProvider->getToken('unhashedTokentokentokentokentoken'); + } + + public function testGetExpiredToken(): void { + $token = 'tokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = 'passme'; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + $actual->setExpires(42); + + $this->mapper->method('getToken') + ->with( + $this->callback(function (string $token) { + return hash('sha512', 'tokentokentokentokentoken' . '1f4h9s') === $token; + }) + )->willReturn($actual); + + try { + $this->tokenProvider->getToken('tokentokentokentokentoken'); + $this->fail(); + } catch (ExpiredTokenException $e) { + $this->assertSame($actual, $e->getToken()); + } + } + + public function testGetTokenById(): void { + $token = $this->createMock(PublicKeyToken::class); + + $this->mapper->expects($this->once()) + ->method('getTokenById') + ->with($this->equalTo(42)) + ->willReturn($token); + + $this->assertSame($token, $this->tokenProvider->getTokenById(42)); + } + + public function testGetInvalidTokenById(): void { + $this->expectException(InvalidTokenException::class); + + $this->mapper->expects($this->once()) + ->method('getTokenById') + ->with($this->equalTo(42)) + ->willThrowException(new DoesNotExistException('nope')); + + $this->tokenProvider->getTokenById(42); + } + + public function testGetExpiredTokenById(): void { + $token = new PublicKeyToken(); + $token->setExpires(42); + + $this->mapper->expects($this->once()) + ->method('getTokenById') + ->with($this->equalTo(42)) + ->willReturn($token); + + try { + $this->tokenProvider->getTokenById(42); + $this->fail(); + } catch (ExpiredTokenException $e) { + $this->assertSame($token, $e->getToken()); + } + } + + public function testRotate(): void { + $token = 'oldtokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = 'password'; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $new = $this->tokenProvider->rotate($actual, 'oldtokentokentokentokentoken', 'newtokentokentokentokentoken'); + + $this->assertSame('password', $this->tokenProvider->getPassword($new, 'newtokentokentokentokentoken')); + } + + public function testRotateNoPassword(): void { + $token = 'oldtokentokentokentokentoken'; + $uid = 'user'; + $user = 'User'; + $password = null; + $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'; + $type = IToken::PERMANENT_TOKEN; + + $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER); + + $oldPrivate = $actual->getPrivateKey(); + + $new = $this->tokenProvider->rotate($actual, 'oldtokentokentokentokentoken', 'newtokentokentokentokentoken'); + + $newPrivate = $new->getPrivateKey(); + + $this->assertNotSame($newPrivate, $oldPrivate); + $this->assertNull($new->getPassword()); + } + + public function testMarkPasswordInvalidInvalidToken(): void { + $token = $this->createMock(IToken::class); + + $this->expectException(InvalidTokenException::class); + + $this->tokenProvider->markPasswordInvalid($token, 'tokenId'); + } + + public function testMarkPasswordInvalid(): void { + $token = $this->createMock(PublicKeyToken::class); + + $token->expects($this->once()) + ->method('setPasswordInvalid') + ->with(true); + $this->mapper->expects($this->once()) + ->method('update') + ->with($token); + + $this->tokenProvider->markPasswordInvalid($token, 'tokenId'); + } + + public function testUpdatePasswords(): void { + $uid = 'myUID'; + $token1 = $this->tokenProvider->generateToken( + 'foobetokentokentokentoken', + $uid, + $uid, + 'bar', + 'random1', + IToken::PERMANENT_TOKEN, + IToken::REMEMBER); + $token2 = $this->tokenProvider->generateToken( + 'foobartokentokentokentoken', + $uid, + $uid, + 'bar', + 'random2', + IToken::PERMANENT_TOKEN, + IToken::REMEMBER); + $this->config->method('getSystemValueBool') + ->willReturnMap([ + ['auth.storeCryptedPassword', true, true], + ]); + + $this->mapper->method('hasExpiredTokens') + ->with($uid) + ->willReturn(true); + $this->mapper->expects($this->once()) + ->method('getTokenByUser') + ->with($uid) + ->willReturn([$token1, $token2]); + $this->mapper->expects($this->exactly(2)) + ->method('update') + ->with($this->callback(function (PublicKeyToken $t) use ($token1, $token2) { + return $t === $token1 || $t === $token2; + })); + + $this->tokenProvider->updatePasswords($uid, 'bar2'); + } +} diff --git a/tests/lib/Authentication/Token/PublicKeyTokenTest.php b/tests/lib/Authentication/Token/PublicKeyTokenTest.php new file mode 100644 index 00000000000..5f5f29c865f --- /dev/null +++ b/tests/lib/Authentication/Token/PublicKeyTokenTest.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Authentication\Token; + +use OC\Authentication\Token\PublicKeyToken; +use OCP\Authentication\Token\IToken; +use Test\TestCase; + +class PublicKeyTokenTest extends TestCase { + public function testSetScopeAsArray(): void { + $scope = [IToken::SCOPE_FILESYSTEM => false]; + $token = new PublicKeyToken(); + $token->setScope($scope); + $this->assertEquals(json_encode($scope), $token->getScope()); + $this->assertEquals($scope, $token->getScopeAsArray()); + } + + public function testDefaultScope(): void { + $scope = [IToken::SCOPE_FILESYSTEM => true]; + $token = new PublicKeyToken(); + $this->assertEquals($scope, $token->getScopeAsArray()); + } +} diff --git a/tests/lib/Authentication/Token/RemoteWipeTest.php b/tests/lib/Authentication/Token/RemoteWipeTest.php new file mode 100644 index 00000000000..ca09767c759 --- /dev/null +++ b/tests/lib/Authentication/Token/RemoteWipeTest.php @@ -0,0 +1,173 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Authentication\Token; + +use OC\Authentication\Events\RemoteWipeFinished; +use OC\Authentication\Events\RemoteWipeStarted; +use OC\Authentication\Exceptions\WipeTokenException; +use OC\Authentication\Token\IProvider; +use OC\Authentication\Token\IProvider as ITokenProvider; +use OC\Authentication\Token\IToken; +use OC\Authentication\Token\IWipeableToken; +use OC\Authentication\Token\RemoteWipe; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IUser; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +class RemoteWipeTest extends TestCase { + /** @var ITokenProvider|MockObject */ + private $tokenProvider; + + /** @var IEventDispatcher|MockObject */ + private $eventDispatcher; + + /** @var LoggerInterface|MockObject */ + private $logger; + + /** @var RemoteWipe */ + private $remoteWipe; + + protected function setUp(): void { + parent::setUp(); + + $this->tokenProvider = $this->createMock(IProvider::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->remoteWipe = new RemoteWipe( + $this->tokenProvider, + $this->eventDispatcher, + $this->logger + ); + } + + public function testMarkNonWipableTokenForWipe(): void { + $token = $this->createMock(IToken::class); + $result = $this->remoteWipe->markTokenForWipe($token); + $this->assertFalse($result); + } + + public function testMarkTokenForWipe(): void { + $token = $this->createMock(IWipeableToken::class); + $token->expects($this->once()) + ->method('wipe'); + + $this->tokenProvider->expects($this->once()) + ->method('updateToken') + ->with($token); + + $result = $this->remoteWipe->markTokenForWipe($token); + $this->assertTrue($result); + } + + public function testMarkAllTokensForWipeNoWipeableToken(): void { + /** @var IUser|MockObject $user */ + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('user123'); + $token1 = $this->createMock(IToken::class); + $token2 = $this->createMock(IToken::class); + $this->tokenProvider->expects($this->once()) + ->method('getTokenByUser') + ->with('user123') + ->willReturn([$token1, $token2]); + + $result = $this->remoteWipe->markAllTokensForWipe($user); + + $this->assertFalse($result); + } + + public function testMarkAllTokensForWipe(): void { + /** @var IUser|MockObject $user */ + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('user123'); + $token1 = $this->createMock(IToken::class); + $token2 = $this->createMock(IWipeableToken::class); + $this->tokenProvider->expects($this->once()) + ->method('getTokenByUser') + ->with('user123') + ->willReturn([$token1, $token2]); + $token2->expects($this->once()) + ->method('wipe'); + $this->tokenProvider->expects($this->once()) + ->method('updateToken') + ->with($token2); + + $result = $this->remoteWipe->markAllTokensForWipe($user); + + $this->assertTrue($result); + } + + public function testStartWipingNotAWipeToken(): void { + $token = $this->createMock(IToken::class); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('tk1') + ->willReturn($token); + $this->eventDispatcher->expects($this->never()) + ->method('dispatch'); + + $result = $this->remoteWipe->start('tk1'); + + $this->assertFalse($result); + } + + public function testStartWiping(): void { + $token = $this->createMock(IToken::class); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('tk1') + ->willThrowException(new WipeTokenException($token)); + $this->eventDispatcher->expects($this->once()) + ->method('dispatch'); + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with(RemoteWipeStarted::class, $this->equalTo(new RemoteWipeStarted($token))); + + $result = $this->remoteWipe->start('tk1'); + + $this->assertTrue($result); + } + + public function testFinishWipingNotAWipeToken(): void { + $token = $this->createMock(IToken::class); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('tk1') + ->willReturn($token); + $this->eventDispatcher->expects($this->never()) + ->method('dispatch'); + + $result = $this->remoteWipe->finish('tk1'); + + $this->assertFalse($result); + } + + public function startFinishWiping() { + $token = $this->createMock(IToken::class); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('tk1') + ->willThrowException(new WipeTokenException($token)); + $this->eventDispatcher->expects($this->once()) + ->method('dispatch'); + $this->tokenProvider->expects($this->once()) + ->method('invalidateToken') + ->with($token); + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with(RemoteWipeFinished::class, $this->equalTo(new RemoteWipeFinished($token))); + + $result = $this->remoteWipe->finish('tk1'); + + $this->assertTrue($result); + } +} |