diff options
Diffstat (limited to 'tests/lib/Authentication/TwoFactorAuth')
8 files changed, 1659 insertions, 0 deletions
diff --git a/tests/lib/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDaoTest.php b/tests/lib/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDaoTest.php new file mode 100644 index 00000000000..b59ef876ffd --- /dev/null +++ b/tests/lib/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDaoTest.php @@ -0,0 +1,159 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Authentication\TwoFactorAuth\Db; + +use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; +use OCP\IDBConnection; +use OCP\Server; +use Test\TestCase; + +/** + * @group DB + */ +class ProviderUserAssignmentDaoTest extends TestCase { + /** @var IDBConnection */ + private $dbConn; + + /** @var ProviderUserAssignmentDao */ + private $dao; + + protected function setUp(): void { + parent::setUp(); + + $this->dbConn = Server::get(IDBConnection::class); + $qb = $this->dbConn->getQueryBuilder(); + $q = $qb->delete(ProviderUserAssignmentDao::TABLE_NAME); + $q->execute(); + + $this->dao = new ProviderUserAssignmentDao($this->dbConn); + } + + public function testGetState(): void { + $qb = $this->dbConn->getQueryBuilder(); + $q1 = $qb->insert(ProviderUserAssignmentDao::TABLE_NAME)->values([ + 'provider_id' => $qb->createNamedParameter('twofactor_u2f'), + 'uid' => $qb->createNamedParameter('user123'), + 'enabled' => $qb->createNamedParameter(1), + ]); + $q1->execute(); + $q2 = $qb->insert(ProviderUserAssignmentDao::TABLE_NAME)->values([ + 'provider_id' => $qb->createNamedParameter('twofactor_totp'), + 'uid' => $qb->createNamedParameter('user123'), + 'enabled' => $qb->createNamedParameter(0), + ]); + $q2->execute(); + $expected = [ + 'twofactor_u2f' => true, + 'twofactor_totp' => false, + ]; + + $state = $this->dao->getState('user123'); + + $this->assertEquals($expected, $state); + } + + public function testPersist(): void { + $qb = $this->dbConn->getQueryBuilder(); + + $this->dao->persist('twofactor_totp', 'user123', 0); + + $q = $qb + ->select('*') + ->from(ProviderUserAssignmentDao::TABLE_NAME) + ->where($qb->expr()->eq('provider_id', $qb->createNamedParameter('twofactor_totp'))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter('user123'))) + ->andWhere($qb->expr()->eq('enabled', $qb->createNamedParameter(0))); + $res = $q->execute(); + $data = $res->fetchAll(); + $res->closeCursor(); + $this->assertCount(1, $data); + } + + public function testPersistTwice(): void { + $qb = $this->dbConn->getQueryBuilder(); + + $this->dao->persist('twofactor_totp', 'user123', 0); + $this->dao->persist('twofactor_totp', 'user123', 1); + + $q = $qb + ->select('*') + ->from(ProviderUserAssignmentDao::TABLE_NAME) + ->where($qb->expr()->eq('provider_id', $qb->createNamedParameter('twofactor_totp'))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter('user123'))) + ->andWhere($qb->expr()->eq('enabled', $qb->createNamedParameter(1))); + $res = $q->execute(); + $data = $res->fetchAll(); + $res->closeCursor(); + + $this->assertCount(1, $data); + } + + public function testPersistSameStateTwice(): void { + $qb = $this->dbConn->getQueryBuilder(); + + $this->dao->persist('twofactor_totp', 'user123', 1); + $this->dao->persist('twofactor_totp', 'user123', 1); + + $q = $qb + ->select('*') + ->from(ProviderUserAssignmentDao::TABLE_NAME) + ->where($qb->expr()->eq('provider_id', $qb->createNamedParameter('twofactor_totp'))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter('user123'))) + ->andWhere($qb->expr()->eq('enabled', $qb->createNamedParameter(1))); + $res = $q->execute(); + $data = $res->fetchAll(); + $res->closeCursor(); + + $this->assertCount(1, $data); + } + + public function testDeleteByUser(): void { + $this->dao->persist('twofactor_fail', 'user1', 1); + $this->dao->persist('twofactor_u2f', 'user1', 1); + $this->dao->persist('twofactor_fail', 'user2', 0); + $this->dao->persist('twofactor_u2f', 'user2', 0); + + $deleted = $this->dao->deleteByUser('user1'); + + $this->assertEquals( + [ + [ + 'uid' => 'user1', + 'provider_id' => 'twofactor_fail', + 'enabled' => true, + ], + [ + 'uid' => 'user1', + 'provider_id' => 'twofactor_u2f', + 'enabled' => true, + ], + ], + $deleted + ); + $statesUser1 = $this->dao->getState('user1'); + $statesUser2 = $this->dao->getState('user2'); + $this->assertCount(0, $statesUser1); + $this->assertCount(2, $statesUser2); + } + + public function testDeleteAll(): void { + $this->dao->persist('twofactor_fail', 'user1', 1); + $this->dao->persist('twofactor_u2f', 'user1', 1); + $this->dao->persist('twofactor_fail', 'user2', 0); + $this->dao->persist('twofactor_u2f', 'user1', 0); + + $this->dao->deleteAll('twofactor_fail'); + + $statesUser1 = $this->dao->getState('user1'); + $statesUser2 = $this->dao->getState('user2'); + $this->assertCount(1, $statesUser1); + $this->assertCount(0, $statesUser2); + } +} diff --git a/tests/lib/Authentication/TwoFactorAuth/EnforcementStateTest.php b/tests/lib/Authentication/TwoFactorAuth/EnforcementStateTest.php new file mode 100644 index 00000000000..f1d38c10801 --- /dev/null +++ b/tests/lib/Authentication/TwoFactorAuth/EnforcementStateTest.php @@ -0,0 +1,51 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Created by PhpStorm. + * User: christoph + * Date: 11.10.18 + * Time: 13:01 + */ + +namespace Tests\Authentication\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\EnforcementState; +use Test\TestCase; + +class EnforcementStateTest extends TestCase { + public function testIsEnforced(): void { + $state = new EnforcementState(true); + + $this->assertTrue($state->isEnforced()); + } + + public function testGetEnforcedGroups(): void { + $state = new EnforcementState(true, ['twofactorers']); + + $this->assertEquals(['twofactorers'], $state->getEnforcedGroups()); + } + + public function testGetExcludedGroups(): void { + $state = new EnforcementState(true, [], ['yoloers']); + + $this->assertEquals(['yoloers'], $state->getExcludedGroups()); + } + + public function testJsonSerialize(): void { + $state = new EnforcementState(true, ['twofactorers'], ['yoloers']); + $expected = [ + 'enforced' => true, + 'enforcedGroups' => ['twofactorers'], + 'excludedGroups' => ['yoloers'], + ]; + + $json = $state->jsonSerialize(); + + $this->assertEquals($expected, $json); + } +} diff --git a/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php new file mode 100644 index 00000000000..a2bed8a3652 --- /dev/null +++ b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php @@ -0,0 +1,795 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Authentication\TwoFactorAuth; + +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Token\IProvider as TokenProvider; +use OC\Authentication\Token\IToken; +use OC\Authentication\TwoFactorAuth\Manager; +use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; +use OC\Authentication\TwoFactorAuth\ProviderLoader; +use OCP\Activity\IEvent; +use OCP\Activity\IManager; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; +use OCP\ISession; +use OCP\IUser; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\TestCase; +use function reset; + +class ManagerTest extends TestCase { + /** @var IUser|MockObject */ + private $user; + + /** @var ProviderLoader|MockObject */ + private $providerLoader; + + /** @var IRegistry|MockObject */ + private $providerRegistry; + + /** @var MandatoryTwoFactor|MockObject */ + private $mandatoryTwoFactor; + + /** @var ISession|MockObject */ + private $session; + + /** @var Manager */ + private $manager; + + /** @var IConfig|MockObject */ + private $config; + + /** @var IManager|MockObject */ + private $activityManager; + + /** @var LoggerInterface|MockObject */ + private $logger; + + /** @var IProvider|MockObject */ + private $fakeProvider; + + /** @var IProvider|MockObject */ + private $backupProvider; + + /** @var TokenProvider|MockObject */ + private $tokenProvider; + + /** @var ITimeFactory|MockObject */ + private $timeFactory; + + /** @var IEventDispatcher|MockObject */ + private $dispatcher; + + protected function setUp(): void { + parent::setUp(); + + $this->user = $this->createMock(IUser::class); + $this->providerLoader = $this->createMock(ProviderLoader::class); + $this->providerRegistry = $this->createMock(IRegistry::class); + $this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class); + $this->session = $this->createMock(ISession::class); + $this->config = $this->createMock(IConfig::class); + $this->activityManager = $this->createMock(IManager::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->tokenProvider = $this->createMock(TokenProvider::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->dispatcher = $this->createMock(IEventDispatcher::class); + + $this->manager = new Manager( + $this->providerLoader, + $this->providerRegistry, + $this->mandatoryTwoFactor, + $this->session, + $this->config, + $this->activityManager, + $this->logger, + $this->tokenProvider, + $this->timeFactory, + $this->dispatcher, + ); + + $this->fakeProvider = $this->createMock(IProvider::class); + $this->fakeProvider->method('getId')->willReturn('email'); + + $this->backupProvider = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider')->getMock(); + $this->backupProvider->method('getId')->willReturn('backup_codes'); + $this->backupProvider->method('isTwoFactorAuthEnabledForUser')->willReturn(true); + } + + private function prepareNoProviders() { + $this->providerLoader->method('getProviders') + ->with($this->user) + ->willReturn([]); + } + + private function prepareProviders() { + $this->providerRegistry->expects($this->once()) + ->method('getProviderStates') + ->with($this->user) + ->willReturn([ + $this->fakeProvider->getId() => true, + ]); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($this->user) + ->willReturn([$this->fakeProvider]); + } + + private function prepareProvidersWitBackupProvider() { + $this->providerLoader->method('getProviders') + ->with($this->user) + ->willReturn([ + $this->fakeProvider, + $this->backupProvider, + ]); + } + + public function testIsTwoFactorAuthenticatedEnforced(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforcedFor') + ->with($this->user) + ->willReturn(true); + + $enabled = $this->manager->isTwoFactorAuthenticated($this->user); + + $this->assertTrue($enabled); + } + + public function testIsTwoFactorAuthenticatedNoProviders(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforcedFor') + ->with($this->user) + ->willReturn(false); + $this->providerRegistry->expects($this->once()) + ->method('getProviderStates') + ->willReturn([]); // No providers registered + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->willReturn([]); // No providers loadable + + $this->assertFalse($this->manager->isTwoFactorAuthenticated($this->user)); + } + + public function testIsTwoFactorAuthenticatedOnlyBackupCodes(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforcedFor') + ->with($this->user) + ->willReturn(false); + $this->providerRegistry->expects($this->once()) + ->method('getProviderStates') + ->willReturn([ + 'backup_codes' => true, + ]); + $backupCodesProvider = $this->createMock(IProvider::class); + $backupCodesProvider + ->method('getId') + ->willReturn('backup_codes'); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->willReturn([ + $backupCodesProvider, + ]); + + $this->assertFalse($this->manager->isTwoFactorAuthenticated($this->user)); + } + + public function testIsTwoFactorAuthenticatedFailingProviders(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforcedFor') + ->with($this->user) + ->willReturn(false); + $this->providerRegistry->expects($this->once()) + ->method('getProviderStates') + ->willReturn([ + 'twofactor_totp' => true, + 'twofactor_u2f' => false, + ]); // Two providers registered, but … + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->willReturn([]); // … none of them is able to load, however … + + // … 2FA is still enforced + $this->assertTrue($this->manager->isTwoFactorAuthenticated($this->user)); + } + + public static function providerStatesFixData(): array { + return [ + [false, false], + [true, true], + ]; + } + + /** + * If the 2FA registry has not been populated when a user logs in, + * the 2FA manager has to first fix the state before it checks for + * enabled providers. + * + * If any of these providers is active, 2FA is enabled + */ + #[\PHPUnit\Framework\Attributes\DataProvider('providerStatesFixData')] + public function testIsTwoFactorAuthenticatedFixesProviderStates(bool $providerEnabled, bool $expected): void { + $this->providerRegistry->expects($this->once()) + ->method('getProviderStates') + ->willReturn([]); // Nothing registered yet + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->willReturn([ + $this->fakeProvider + ]); + $this->fakeProvider->expects($this->once()) + ->method('isTwoFactorAuthEnabledForUser') + ->with($this->user) + ->willReturn($providerEnabled); + if ($providerEnabled) { + $this->providerRegistry->expects($this->once()) + ->method('enableProviderFor') + ->with( + $this->fakeProvider, + $this->user + ); + } else { + $this->providerRegistry->expects($this->once()) + ->method('disableProviderFor') + ->with( + $this->fakeProvider, + $this->user + ); + } + + $this->assertEquals($expected, $this->manager->isTwoFactorAuthenticated($this->user)); + } + + public function testGetProvider(): void { + $this->providerRegistry->expects($this->once()) + ->method('getProviderStates') + ->with($this->user) + ->willReturn([ + $this->fakeProvider->getId() => true, + ]); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($this->user) + ->willReturn([$this->fakeProvider]); + + $provider = $this->manager->getProvider($this->user, $this->fakeProvider->getId()); + + $this->assertSame($this->fakeProvider, $provider); + } + + public function testGetInvalidProvider(): void { + $this->providerRegistry->expects($this->once()) + ->method('getProviderStates') + ->with($this->user) + ->willReturn([]); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($this->user) + ->willReturn([]); + + $provider = $this->manager->getProvider($this->user, 'nonexistent'); + + $this->assertNull($provider); + } + + public function testGetLoginSetupProviders(): void { + $provider1 = $this->createMock(IProvider::class); + $provider2 = $this->createMock(IActivatableAtLogin::class); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($this->user) + ->willReturn([ + $provider1, + $provider2, + ]); + + $providers = $this->manager->getLoginSetupProviders($this->user); + + $this->assertCount(1, $providers); + $this->assertSame($provider2, reset($providers)); + } + + public function testGetProviders(): void { + $this->providerRegistry->expects($this->once()) + ->method('getProviderStates') + ->with($this->user) + ->willReturn([ + $this->fakeProvider->getId() => true, + ]); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($this->user) + ->willReturn([$this->fakeProvider]); + $expectedProviders = [ + 'email' => $this->fakeProvider, + ]; + + $providerSet = $this->manager->getProviderSet($this->user); + $providers = $providerSet->getProviders(); + + $this->assertEquals($expectedProviders, $providers); + $this->assertFalse($providerSet->isProviderMissing()); + } + + public function testGetProvidersOneMissing(): void { + $this->providerRegistry->expects($this->once()) + ->method('getProviderStates') + ->with($this->user) + ->willReturn([ + $this->fakeProvider->getId() => true, + ]); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($this->user) + ->willReturn([]); + $expectedProviders = [ + 'email' => $this->fakeProvider, + ]; + + $providerSet = $this->manager->getProviderSet($this->user); + + $this->assertTrue($providerSet->isProviderMissing()); + } + + public function testVerifyChallenge(): void { + $this->prepareProviders(); + + $challenge = 'passme'; + $event = $this->createMock(IEvent::class); + $this->fakeProvider->expects($this->once()) + ->method('verifyChallenge') + ->with($this->user, $challenge) + ->willReturn(true); + $this->session->expects($this->once()) + ->method('get') + ->with('two_factor_remember_login') + ->willReturn(false); + + $calls = [ + ['two_factor_auth_uid'], + ['two_factor_remember_login'], + ]; + $this->session->expects($this->exactly(2)) + ->method('remove') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + + $this->session->expects($this->once()) + ->method('set') + ->with(Manager::SESSION_UID_DONE, 'jos'); + $this->session->method('getId') + ->willReturn('mysessionid'); + $this->activityManager->expects($this->once()) + ->method('generateEvent') + ->willReturn($event); + $this->user->expects($this->any()) + ->method('getUID') + ->willReturn('jos'); + $event->expects($this->once()) + ->method('setApp') + ->with($this->equalTo('core')) + ->willReturnSelf(); + $event->expects($this->once()) + ->method('setType') + ->with($this->equalTo('security')) + ->willReturnSelf(); + $event->expects($this->once()) + ->method('setAuthor') + ->with($this->equalTo('jos')) + ->willReturnSelf(); + $event->expects($this->once()) + ->method('setAffectedUser') + ->with($this->equalTo('jos')) + ->willReturnSelf(); + $this->fakeProvider + ->method('getDisplayName') + ->willReturn('Fake 2FA'); + $event->expects($this->once()) + ->method('setSubject') + ->with($this->equalTo('twofactor_success'), $this->equalTo([ + 'provider' => 'Fake 2FA', + ])) + ->willReturnSelf(); + $token = $this->createMock(IToken::class); + $this->tokenProvider->method('getToken') + ->with('mysessionid') + ->willReturn($token); + $token->method('getId') + ->willReturn(42); + $this->config->expects($this->once()) + ->method('deleteUserValue') + ->with('jos', 'login_token_2fa', '42'); + + $result = $this->manager->verifyChallenge('email', $this->user, $challenge); + + $this->assertTrue($result); + } + + public function testVerifyChallengeInvalidProviderId(): void { + $this->prepareProviders(); + + $challenge = 'passme'; + $this->fakeProvider->expects($this->never()) + ->method('verifyChallenge') + ->with($this->user, $challenge); + $this->session->expects($this->never()) + ->method('remove'); + + $this->assertFalse($this->manager->verifyChallenge('dontexist', $this->user, $challenge)); + } + + public function testVerifyInvalidChallenge(): void { + $this->prepareProviders(); + + $challenge = 'dontpassme'; + $event = $this->createMock(IEvent::class); + $this->fakeProvider->expects($this->once()) + ->method('verifyChallenge') + ->with($this->user, $challenge) + ->willReturn(false); + $this->session->expects($this->never()) + ->method('remove'); + $this->activityManager->expects($this->once()) + ->method('generateEvent') + ->willReturn($event); + $this->user->expects($this->any()) + ->method('getUID') + ->willReturn('jos'); + $event->expects($this->once()) + ->method('setApp') + ->with($this->equalTo('core')) + ->willReturnSelf(); + $event->expects($this->once()) + ->method('setType') + ->with($this->equalTo('security')) + ->willReturnSelf(); + $event->expects($this->once()) + ->method('setAuthor') + ->with($this->equalTo('jos')) + ->willReturnSelf(); + $event->expects($this->once()) + ->method('setAffectedUser') + ->with($this->equalTo('jos')) + ->willReturnSelf(); + $this->fakeProvider + ->method('getDisplayName') + ->willReturn('Fake 2FA'); + $event->expects($this->once()) + ->method('setSubject') + ->with($this->equalTo('twofactor_failed'), $this->equalTo([ + 'provider' => 'Fake 2FA', + ])) + ->willReturnSelf(); + + $this->assertFalse($this->manager->verifyChallenge('email', $this->user, $challenge)); + } + + public function testNeedsSecondFactor(): void { + $user = $this->createMock(IUser::class); + + $calls = [ + ['app_password'], + ['two_factor_auth_uid'], + [Manager::SESSION_UID_DONE], + ]; + $this->session->expects($this->exactly(3)) + ->method('exists') + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + return false; + }); + + $this->session->method('getId') + ->willReturn('mysessionid'); + $token = $this->createMock(IToken::class); + $this->tokenProvider->method('getToken') + ->with('mysessionid') + ->willReturn($token); + $token->method('getId') + ->willReturn(42); + + $user->method('getUID') + ->willReturn('user'); + $this->config->method('getUserKeys') + ->with('user', 'login_token_2fa') + ->willReturn([ + '42' + ]); + + $manager = $this->getMockBuilder(Manager::class) + ->setConstructorArgs([ + $this->providerLoader, + $this->providerRegistry, + $this->mandatoryTwoFactor, + $this->session, + $this->config, + $this->activityManager, + $this->logger, + $this->tokenProvider, + $this->timeFactory, + $this->dispatcher, + ]) + ->onlyMethods(['isTwoFactorAuthenticated'])// Do not actually load the apps + ->getMock(); + + $manager->method('isTwoFactorAuthenticated') + ->with($user) + ->willReturn(true); + + $this->assertTrue($manager->needsSecondFactor($user)); + } + + public function testNeedsSecondFactorUserIsNull(): void { + $user = null; + $this->session->expects($this->never()) + ->method('exists'); + + $this->assertFalse($this->manager->needsSecondFactor($user)); + } + + public function testNeedsSecondFactorWithNoProviderAvailableAnymore(): void { + $this->prepareNoProviders(); + + $user = null; + $this->session->expects($this->never()) + ->method('exists') + ->with('two_factor_auth_uid') + ->willReturn(true); + $this->session->expects($this->never()) + ->method('remove') + ->with('two_factor_auth_uid'); + + $this->assertFalse($this->manager->needsSecondFactor($user)); + } + + public function testPrepareTwoFactorLogin(): void { + $this->user->method('getUID') + ->willReturn('ferdinand'); + + $calls = [ + ['two_factor_auth_uid', 'ferdinand'], + ['two_factor_remember_login', true], + ]; + $this->session->expects($this->exactly(2)) + ->method('set') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + + $this->session->method('getId') + ->willReturn('mysessionid'); + $token = $this->createMock(IToken::class); + $this->tokenProvider->method('getToken') + ->with('mysessionid') + ->willReturn($token); + $token->method('getId') + ->willReturn(42); + + $this->timeFactory->method('getTime') + ->willReturn(1337); + + $this->config->method('setUserValue') + ->with('ferdinand', 'login_token_2fa', '42', '1337'); + + + $this->manager->prepareTwoFactorLogin($this->user, true); + } + + public function testPrepareTwoFactorLoginDontRemember(): void { + $this->user->method('getUID') + ->willReturn('ferdinand'); + + $calls = [ + ['two_factor_auth_uid', 'ferdinand'], + ['two_factor_remember_login', false], + ]; + $this->session->expects($this->exactly(2)) + ->method('set') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + + $this->session->method('getId') + ->willReturn('mysessionid'); + $token = $this->createMock(IToken::class); + $this->tokenProvider->method('getToken') + ->with('mysessionid') + ->willReturn($token); + $token->method('getId') + ->willReturn(42); + + $this->timeFactory->method('getTime') + ->willReturn(1337); + + $this->config->method('setUserValue') + ->with('ferdinand', 'login_token_2fa', '42', '1337'); + + $this->manager->prepareTwoFactorLogin($this->user, false); + } + + public function testNeedsSecondFactorSessionAuth(): void { + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('user'); + + $this->session->method('exists') + ->willReturnCallback(function ($var) { + if ($var === Manager::SESSION_UID_KEY) { + return false; + } elseif ($var === 'app_password') { + return false; + } elseif ($var === 'app_api') { + return false; + } + return true; + }); + $this->session->method('get') + ->willReturnCallback(function ($var) { + if ($var === Manager::SESSION_UID_KEY) { + return 'user'; + } elseif ($var === 'app_api') { + return true; + } + return null; + }); + $this->session->expects($this->once()) + ->method('get') + ->willReturnMap([ + [Manager::SESSION_UID_DONE, 'user'], + ['app_api', true] + ]); + + $this->assertFalse($this->manager->needsSecondFactor($user)); + } + + public function testNeedsSecondFactorSessionAuthFailDBPass(): void { + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('user'); + + $this->session->method('exists') + ->willReturn(false); + $this->session->method('getId') + ->willReturn('mysessionid'); + + $token = $this->createMock(IToken::class); + $token->method('getId') + ->willReturn(40); + + $this->tokenProvider->method('getToken') + ->with('mysessionid') + ->willReturn($token); + + $this->config->method('getUserKeys') + ->with('user', 'login_token_2fa') + ->willReturn([ + '42', '43', '44' + ]); + + $this->session->expects($this->once()) + ->method('set') + ->with(Manager::SESSION_UID_DONE, 'user'); + + $this->assertFalse($this->manager->needsSecondFactor($user)); + } + + public function testNeedsSecondFactorInvalidToken(): void { + $this->prepareNoProviders(); + + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('user'); + + $this->session->method('exists') + ->willReturn(false); + $this->session->method('getId') + ->willReturn('mysessionid'); + + $this->tokenProvider->method('getToken') + ->with('mysessionid') + ->willThrowException(new InvalidTokenException()); + + $this->config->method('getUserKeys')->willReturn([]); + + $this->assertFalse($this->manager->needsSecondFactor($user)); + } + + public function testNeedsSecondFactorAppPassword(): void { + $user = $this->createMock(IUser::class); + $this->session->method('exists') + ->willReturnMap([ + ['app_password', true], + ['app_api', true] + ]); + + $this->assertFalse($this->manager->needsSecondFactor($user)); + } + + public function testClearTwoFactorPending() { + $this->config->method('getUserKeys') + ->with('theUserId', 'login_token_2fa') + ->willReturn([ + '42', '43', '44' + ]); + + $deleteUserValueCalls = [ + ['theUserId', 'login_token_2fa', '42'], + ['theUserId', 'login_token_2fa', '43'], + ['theUserId', 'login_token_2fa', '44'], + ]; + $this->config->expects($this->exactly(3)) + ->method('deleteUserValue') + ->willReturnCallback(function () use (&$deleteUserValueCalls): void { + $expected = array_shift($deleteUserValueCalls); + $this->assertEquals($expected, func_get_args()); + }); + + $invalidateCalls = [ + ['theUserId', 42], + ['theUserId', 43], + ['theUserId', 44], + ]; + $this->tokenProvider->expects($this->exactly(3)) + ->method('invalidateTokenById') + ->willReturnCallback(function () use (&$invalidateCalls): void { + $expected = array_shift($invalidateCalls); + $this->assertEquals($expected, func_get_args()); + }); + + $this->manager->clearTwoFactorPending('theUserId'); + } + + public function testClearTwoFactorPendingTokenDoesNotExist() { + $this->config->method('getUserKeys') + ->with('theUserId', 'login_token_2fa') + ->willReturn([ + '42', '43', '44' + ]); + + $deleteUserValueCalls = [ + ['theUserId', 'login_token_2fa', '42'], + ['theUserId', 'login_token_2fa', '43'], + ['theUserId', 'login_token_2fa', '44'], + ]; + $this->config->expects($this->exactly(3)) + ->method('deleteUserValue') + ->willReturnCallback(function () use (&$deleteUserValueCalls): void { + $expected = array_shift($deleteUserValueCalls); + $this->assertEquals($expected, func_get_args()); + }); + + $invalidateCalls = [ + ['theUserId', 42], + ['theUserId', 43], + ['theUserId', 44], + ]; + $this->tokenProvider->expects($this->exactly(3)) + ->method('invalidateTokenById') + ->willReturnCallback(function ($user, $tokenId) use (&$invalidateCalls): void { + $expected = array_shift($invalidateCalls); + $this->assertEquals($expected, func_get_args()); + if ($tokenId === 43) { + throw new DoesNotExistException('token does not exist'); + } + }); + + $this->manager->clearTwoFactorPending('theUserId'); + } +} diff --git a/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php b/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php new file mode 100644 index 00000000000..d2ecd3c509c --- /dev/null +++ b/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php @@ -0,0 +1,179 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Tests\Authentication\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\EnforcementState; +use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUser; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class MandatoryTwoFactorTest extends TestCase { + /** @var IConfig|MockObject */ + private $config; + + /** @var IGroupManager|MockObject */ + private $groupManager; + + /** @var MandatoryTwoFactor */ + private $mandatoryTwoFactor; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + $this->groupManager = $this->createMock(IGroupManager::class); + + $this->mandatoryTwoFactor = new MandatoryTwoFactor($this->config, $this->groupManager); + } + + public function testIsNotEnforced(): void { + $this->config + ->method('getSystemValue') + ->willReturnMap([ + ['twofactor_enforced', 'false', 'false'], + ['twofactor_enforced_groups', [], []], + ['twofactor_enforced_excluded_groups', [], []], + ]); + + $state = $this->mandatoryTwoFactor->getState(); + + $this->assertFalse($state->isEnforced()); + } + + public function testIsEnforced(): void { + $this->config + ->method('getSystemValue') + ->willReturnMap([ + ['twofactor_enforced', 'false', 'true'], + ['twofactor_enforced_groups', [], []], + ['twofactor_enforced_excluded_groups', [], []], + ]); + + $state = $this->mandatoryTwoFactor->getState(); + + $this->assertTrue($state->isEnforced()); + } + + public function testIsNotEnforcedForAnybody(): void { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('user123'); + $this->config + ->method('getSystemValue') + ->willReturnMap([ + ['twofactor_enforced', 'false', 'false'], + ['twofactor_enforced_groups', [], []], + ['twofactor_enforced_excluded_groups', [], []], + ]); + + $isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user); + + $this->assertFalse($isEnforced); + } + + public function testIsEnforcedForAGroupMember(): void { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('user123'); + $this->config + ->method('getSystemValue') + ->willReturnMap([ + ['twofactor_enforced', 'false', 'true'], + ['twofactor_enforced_groups', [], ['twofactorers']], + ['twofactor_enforced_excluded_groups', [], []], + ]); + $this->groupManager->method('isInGroup') + ->willReturnCallback(function ($user, $group) { + return $user === 'user123' && $group === 'twofactorers'; + }); + + $isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user); + + $this->assertTrue($isEnforced); + } + + public function testIsEnforcedForOtherGroups(): void { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('user123'); + $this->config + ->method('getSystemValue') + ->willReturnMap([ + ['twofactor_enforced', 'false', 'true'], + ['twofactor_enforced_groups', [], ['twofactorers']], + ['twofactor_enforced_excluded_groups', [], []], + ]); + $this->groupManager->method('isInGroup') + ->willReturn(false); + + $isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user); + + $this->assertFalse($isEnforced); + } + + public function testIsEnforcedButMemberOfExcludedGroup(): void { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('user123'); + $this->config + ->method('getSystemValue') + ->willReturnMap([ + ['twofactor_enforced', 'false', 'true'], + ['twofactor_enforced_groups', [], []], + ['twofactor_enforced_excluded_groups', [], ['yoloers']], + ]); + $this->groupManager->method('isInGroup') + ->willReturnCallback(function ($user, $group) { + return $user === 'user123' && $group === 'yoloers'; + }); + + $isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user); + + $this->assertFalse($isEnforced); + } + + public function testSetEnforced(): void { + $this->config + ->expects($this->exactly(3)) + ->method('setSystemValue') + ->willReturnMap([ + ['twofactor_enforced', 'true'], + ['twofactor_enforced_groups', []], + ['twofactor_enforced_excluded_groups', []], + ]); + + $this->mandatoryTwoFactor->setState(new EnforcementState(true)); + } + + public function testSetEnforcedForGroups(): void { + $this->config + ->expects($this->exactly(3)) + ->method('setSystemValue') + ->willReturnMap([ + ['twofactor_enforced', 'true'], + ['twofactor_enforced_groups', ['twofactorers']], + ['twofactor_enforced_excluded_groups', ['yoloers']], + ]); + + $this->mandatoryTwoFactor->setState(new EnforcementState(true, ['twofactorers'], ['yoloers'])); + } + + public function testSetNotEnforced(): void { + $this->config + ->expects($this->exactly(3)) + ->method('setSystemValue') + ->willReturnMap([ + ['twofactor_enforced', 'false'], + ['twofactor_enforced_groups', []], + ['twofactor_enforced_excluded_groups', []], + ]); + + $this->mandatoryTwoFactor->setState(new EnforcementState(false)); + } +} diff --git a/tests/lib/Authentication/TwoFactorAuth/ProviderLoaderTest.php b/tests/lib/Authentication/TwoFactorAuth/ProviderLoaderTest.php new file mode 100644 index 00000000000..6eb3b7dfb26 --- /dev/null +++ b/tests/lib/Authentication/TwoFactorAuth/ProviderLoaderTest.php @@ -0,0 +1,120 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace lib\Authentication\TwoFactorAuth; + +use OC\AppFramework\Bootstrap\Coordinator; +use OC\AppFramework\Bootstrap\RegistrationContext; +use OC\AppFramework\Bootstrap\ServiceRegistration; +use OC\Authentication\TwoFactorAuth\ProviderLoader; +use OCP\App\IAppManager; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\IUser; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class ProviderLoaderTest extends TestCase { + /** @var IAppManager|MockObject */ + private $appManager; + + /** @var IUser|MockObject */ + private $user; + + /** @var RegistrationContext|MockObject */ + private $registrationContext; + + /** @var ProviderLoader */ + private $loader; + + protected function setUp(): void { + parent::setUp(); + + $this->appManager = $this->createMock(IAppManager::class); + $this->user = $this->createMock(IUser::class); + + $this->registrationContext = $this->createMock(RegistrationContext::class); + $coordinator = $this->createMock(Coordinator::class); + $coordinator->method('getRegistrationContext') + ->willReturn($this->registrationContext); + + $this->loader = new ProviderLoader($this->appManager, $coordinator); + } + + + public function testFailHardIfProviderCanNotBeLoaded(): void { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Could not load two-factor auth provider \\OCA\\MyFaulty2faApp\\DoesNotExist'); + + $this->appManager->expects($this->once()) + ->method('getEnabledAppsForUser') + ->with($this->user) + ->willReturn(['mail', 'twofactor_totp']); + $this->appManager + ->method('getAppInfo') + ->willReturnMap([ + ['mail', false, null, []], + ['twofactor_totp', false, null, [ + 'two-factor-providers' => [ + '\\OCA\\MyFaulty2faApp\\DoesNotExist', + ], + ]], + ]); + + $this->loader->getProviders($this->user); + } + + public function testGetProviders(): void { + $provider = $this->createMock(IProvider::class); + $provider->method('getId')->willReturn('test'); + \OC::$server->registerService('\\OCA\\TwoFactorTest\\Provider', function () use ($provider) { + return $provider; + }); + $this->appManager->expects($this->once()) + ->method('getEnabledAppsForUser') + ->with($this->user) + ->willReturn(['twofactor_test']); + $this->appManager + ->method('getAppInfo') + ->with('twofactor_test') + ->willReturn(['two-factor-providers' => [ + '\\OCA\\TwoFactorTest\\Provider', + ]]); + + $providers = $this->loader->getProviders($this->user); + + $this->assertCount(1, $providers); + $this->assertArrayHasKey('test', $providers); + $this->assertSame($provider, $providers['test']); + } + + public function testGetProvidersBootstrap(): void { + $provider = $this->createMock(IProvider::class); + $provider->method('getId')->willReturn('test'); + + \OC::$server->registerService('\\OCA\\TwoFactorTest\\Provider', function () use ($provider) { + return $provider; + }); + + $this->appManager->expects($this->once()) + ->method('getEnabledAppsForUser') + ->with($this->user) + ->willReturn([]); + + $this->registrationContext->method('getTwoFactorProviders') + ->willReturn([ + new ServiceRegistration('twofactor_test', '\\OCA\\TwoFactorTest\\Provider') + ]); + + $providers = $this->loader->getProviders($this->user); + + $this->assertCount(1, $providers); + $this->assertArrayHasKey('test', $providers); + $this->assertSame($provider, $providers['test']); + } +} diff --git a/tests/lib/Authentication/TwoFactorAuth/ProviderManagerTest.php b/tests/lib/Authentication/TwoFactorAuth/ProviderManagerTest.php new file mode 100644 index 00000000000..a1f2a6fa69a --- /dev/null +++ b/tests/lib/Authentication/TwoFactorAuth/ProviderManagerTest.php @@ -0,0 +1,136 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace lib\Authentication\TwoFactorAuth; + +use OC\Authentication\Exceptions\InvalidProviderException; +use OC\Authentication\TwoFactorAuth\ProviderLoader; +use OC\Authentication\TwoFactorAuth\ProviderManager; +use OCP\Authentication\TwoFactorAuth\IActivatableByAdmin; +use OCP\Authentication\TwoFactorAuth\IDeactivatableByAdmin; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\IUser; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class ProviderManagerTest extends TestCase { + /** @var ProviderLoader|MockObject */ + private $providerLoader; + + /** @var IRegistry|MockObject */ + private $registry; + + /** @var ProviderManager */ + private $providerManager; + + protected function setUp(): void { + parent::setUp(); + + $this->providerLoader = $this->createMock(ProviderLoader::class); + $this->registry = $this->createMock(IRegistry::class); + + $this->providerManager = new ProviderManager( + $this->providerLoader, + $this->registry + ); + } + + + public function testTryEnableInvalidProvider(): void { + $this->expectException(InvalidProviderException::class); + + $user = $this->createMock(IUser::class); + $this->providerManager->tryEnableProviderFor('none', $user); + } + + public function testTryEnableUnsupportedProvider(): void { + $user = $this->createMock(IUser::class); + $provider = $this->createMock(IProvider::class); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($user) + ->willReturn([ + 'u2f' => $provider, + ]); + $this->registry->expects($this->never()) + ->method('enableProviderFor'); + + $res = $this->providerManager->tryEnableProviderFor('u2f', $user); + + $this->assertFalse($res); + } + + public function testTryEnableProvider(): void { + $user = $this->createMock(IUser::class); + $provider = $this->createMock(IActivatableByAdmin::class); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($user) + ->willReturn([ + 'u2f' => $provider, + ]); + $provider->expects($this->once()) + ->method('enableFor') + ->with($user); + $this->registry->expects($this->once()) + ->method('enableProviderFor') + ->with($provider, $user); + + $res = $this->providerManager->tryEnableProviderFor('u2f', $user); + + $this->assertTrue($res); + } + + + public function testTryDisableInvalidProvider(): void { + $this->expectException(InvalidProviderException::class); + + $user = $this->createMock(IUser::class); + $this->providerManager->tryDisableProviderFor('none', $user); + } + + public function testTryDisableUnsupportedProvider(): void { + $user = $this->createMock(IUser::class); + $provider = $this->createMock(IProvider::class); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($user) + ->willReturn([ + 'u2f' => $provider, + ]); + $this->registry->expects($this->never()) + ->method('disableProviderFor'); + + $res = $this->providerManager->tryDisableProviderFor('u2f', $user); + + $this->assertFalse($res); + } + + public function testTryDisableProvider(): void { + $user = $this->createMock(IUser::class); + $provider = $this->createMock(IDeactivatableByAdmin::class); + $this->providerLoader->expects($this->once()) + ->method('getProviders') + ->with($user) + ->willReturn([ + 'u2f' => $provider, + ]); + $provider->expects($this->once()) + ->method('disableFor') + ->with($user); + $this->registry->expects($this->once()) + ->method('disableProviderFor') + ->with($provider, $user); + + $res = $this->providerManager->tryDisableProviderFor('u2f', $user); + + $this->assertTrue($res); + } +} diff --git a/tests/lib/Authentication/TwoFactorAuth/ProviderSetTest.php b/tests/lib/Authentication/TwoFactorAuth/ProviderSetTest.php new file mode 100644 index 00000000000..568b83567f8 --- /dev/null +++ b/tests/lib/Authentication/TwoFactorAuth/ProviderSetTest.php @@ -0,0 +1,75 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Authentication\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\ProviderSet; +use OCA\TwoFactorBackupCodes\Provider\BackupCodesProvider; +use OCP\Authentication\TwoFactorAuth\IProvider; +use Test\TestCase; + +class ProviderSetTest extends TestCase { + /** @var ProviderSet */ + private $providerSet; + + public function testIndexesProviders(): void { + $p1 = $this->createMock(IProvider::class); + $p1->method('getId')->willReturn('p1'); + $p2 = $this->createMock(IProvider::class); + $p2->method('getId')->willReturn('p2'); + $expected = [ + 'p1' => $p1, + 'p2' => $p2, + ]; + + $set = new ProviderSet([$p2, $p1], false); + + $this->assertEquals($expected, $set->getProviders()); + } + + public function testGet3rdPartyProviders(): void { + $p1 = $this->createMock(IProvider::class); + $p1->method('getId')->willReturn('p1'); + $p2 = $this->createMock(IProvider::class); + $p2->method('getId')->willReturn('p2'); + $p3 = $this->createMock(BackupCodesProvider::class); + $p3->method('getId')->willReturn('p3'); + $expected = [ + 'p1' => $p1, + 'p2' => $p2, + ]; + + $set = new ProviderSet([$p2, $p1], false); + + $this->assertEquals($expected, $set->getPrimaryProviders()); + } + + public function testGetProvider(): void { + $p1 = $this->createMock(IProvider::class); + $p1->method('getId')->willReturn('p1'); + + $set = new ProviderSet([$p1], false); + $provider = $set->getProvider('p1'); + + $this->assertEquals($p1, $provider); + } + + public function testGetProviderNotFound(): void { + $set = new ProviderSet([], false); + $provider = $set->getProvider('p1'); + + $this->assertNull($provider); + } + + public function testIsProviderMissing(): void { + $set = new ProviderSet([], true); + + $this->assertTrue($set->isProviderMissing()); + } +} diff --git a/tests/lib/Authentication/TwoFactorAuth/RegistryTest.php b/tests/lib/Authentication/TwoFactorAuth/RegistryTest.php new file mode 100644 index 00000000000..2018dc1a634 --- /dev/null +++ b/tests/lib/Authentication/TwoFactorAuth/RegistryTest.php @@ -0,0 +1,144 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Authentication\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; +use OC\Authentication\TwoFactorAuth\Registry; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\Authentication\TwoFactorAuth\RegistryEvent; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderDisabled; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserRegistered; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserUnregistered; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderUserDeleted; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IUser; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class RegistryTest extends TestCase { + /** @var ProviderUserAssignmentDao|MockObject */ + private $dao; + + /** @var IEventDispatcher|MockObject */ + private $dispatcher; + + /** @var Registry */ + private $registry; + + protected function setUp(): void { + parent::setUp(); + + $this->dao = $this->createMock(ProviderUserAssignmentDao::class); + $this->dispatcher = $this->createMock(IEventDispatcher::class); + + $this->registry = new Registry($this->dao, $this->dispatcher); + } + + public function testGetProviderStates(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->once())->method('getUID')->willReturn('user123'); + $state = [ + 'twofactor_totp' => true, + ]; + $this->dao->expects($this->once())->method('getState')->willReturn($state); + + $actual = $this->registry->getProviderStates($user); + + $this->assertEquals($state, $actual); + } + + public function testEnableProvider(): void { + $user = $this->createMock(IUser::class); + $provider = $this->createMock(IProvider::class); + $user->expects($this->once())->method('getUID')->willReturn('user123'); + $provider->expects($this->once())->method('getId')->willReturn('p1'); + $this->dao->expects($this->once())->method('persist')->with('p1', 'user123', + true); + + $this->dispatcher->expects($this->once()) + ->method('dispatch') + ->with( + $this->equalTo(IRegistry::EVENT_PROVIDER_ENABLED), + $this->callback(function (RegistryEvent $e) use ($user, $provider) { + return $e->getUser() === $user && $e->getProvider() === $provider; + }) + ); + $this->dispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new TwoFactorProviderForUserRegistered( + $user, + $provider, + )); + + $this->registry->enableProviderFor($provider, $user); + } + + public function testDisableProvider(): void { + $user = $this->createMock(IUser::class); + $provider = $this->createMock(IProvider::class); + $user->expects($this->once())->method('getUID')->willReturn('user123'); + $provider->expects($this->once())->method('getId')->willReturn('p1'); + $this->dao->expects($this->once())->method('persist')->with('p1', 'user123', + false); + + + $this->dispatcher->expects($this->once()) + ->method('dispatch') + ->with( + $this->equalTo(IRegistry::EVENT_PROVIDER_DISABLED), + $this->callback(function (RegistryEvent $e) use ($user, $provider) { + return $e->getUser() === $user && $e->getProvider() === $provider; + }) + ); + $this->dispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new TwoFactorProviderForUserUnregistered( + $user, + $provider, + )); + + $this->registry->disableProviderFor($provider, $user); + } + + public function testDeleteUserData(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->once())->method('getUID')->willReturn('user123'); + $this->dao->expects($this->once()) + ->method('deleteByUser') + ->with('user123') + ->willReturn([ + [ + 'provider_id' => 'twofactor_u2f', + ] + ]); + + $calls = [ + [new TwoFactorProviderDisabled('twofactor_u2f')], + [new TwoFactorProviderUserDeleted($user, 'twofactor_u2f')], + ]; + $this->dispatcher->expects($this->exactly(2)) + ->method('dispatchTyped') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + + $this->registry->deleteUserData($user); + } + + public function testCleanUp(): void { + $this->dao->expects($this->once()) + ->method('deleteAll') + ->with('twofactor_u2f'); + + $this->registry->cleanUp('twofactor_u2f'); + } +} |