aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Authentication/TwoFactorAuth
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Authentication/TwoFactorAuth')
-rw-r--r--tests/lib/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDaoTest.php159
-rw-r--r--tests/lib/Authentication/TwoFactorAuth/EnforcementStateTest.php51
-rw-r--r--tests/lib/Authentication/TwoFactorAuth/ManagerTest.php795
-rw-r--r--tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php179
-rw-r--r--tests/lib/Authentication/TwoFactorAuth/ProviderLoaderTest.php120
-rw-r--r--tests/lib/Authentication/TwoFactorAuth/ProviderManagerTest.php136
-rw-r--r--tests/lib/Authentication/TwoFactorAuth/ProviderSetTest.php75
-rw-r--r--tests/lib/Authentication/TwoFactorAuth/RegistryTest.php144
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');
+ }
+}