aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Authentication
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Authentication')
-rw-r--r--tests/lib/Authentication/Events/RemoteWipeFinishedTest.php23
-rw-r--r--tests/lib/Authentication/Events/RemoteWipeStartedTest.php23
-rw-r--r--tests/lib/Authentication/Listeners/RemoteWipeActivityListenerTest.php137
-rw-r--r--tests/lib/Authentication/Listeners/RemoteWipeEmailListenerTest.php225
-rw-r--r--tests/lib/Authentication/Listeners/RemoteWipeNotificationsListenerTest.php134
-rw-r--r--tests/lib/Authentication/Listeners/UserDeletedTokenCleanupListenerTest.php103
-rw-r--r--tests/lib/Authentication/Login/ALoginTestCommand.php104
-rw-r--r--tests/lib/Authentication/Login/ClearLostPasswordTokensCommandTest.php47
-rw-r--r--tests/lib/Authentication/Login/CompleteLoginCommandTest.php48
-rw-r--r--tests/lib/Authentication/Login/CreateSessionTokenCommandTest.php103
-rw-r--r--tests/lib/Authentication/Login/FinishRememberedLoginCommandTest.php74
-rw-r--r--tests/lib/Authentication/Login/LoggedInCheckCommandTest.php55
-rw-r--r--tests/lib/Authentication/Login/PreLoginHookCommandTest.php48
-rw-r--r--tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php72
-rw-r--r--tests/lib/Authentication/Login/TwoFactorCommandTest.php341
-rw-r--r--tests/lib/Authentication/Login/UidLoginCommandTest.php61
-rw-r--r--tests/lib/Authentication/Login/UpdateLastPasswordConfirmCommandTest.php46
-rw-r--r--tests/lib/Authentication/Login/UserDisabledCheckCommandTest.php79
-rw-r--r--tests/lib/Authentication/LoginCredentials/CredentialsTest.php47
-rw-r--r--tests/lib/Authentication/LoginCredentials/StoreTest.php296
-rw-r--r--tests/lib/Authentication/Token/ManagerTest.php406
-rw-r--r--tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php265
-rw-r--r--tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php645
-rw-r--r--tests/lib/Authentication/Token/PublicKeyTokenTest.php29
-rw-r--r--tests/lib/Authentication/Token/RemoteWipeTest.php173
-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
33 files changed, 5243 insertions, 0 deletions
diff --git a/tests/lib/Authentication/Events/RemoteWipeFinishedTest.php b/tests/lib/Authentication/Events/RemoteWipeFinishedTest.php
new file mode 100644
index 00000000000..c89b1e4108f
--- /dev/null
+++ b/tests/lib/Authentication/Events/RemoteWipeFinishedTest.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Events;
+
+use OC\Authentication\Events\RemoteWipeFinished;
+use OC\Authentication\Token\IToken;
+use Test\TestCase;
+
+class RemoteWipeFinishedTest extends TestCase {
+ public function testGetToken(): void {
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeFinished($token);
+
+ $this->assertSame($token, $event->getToken());
+ }
+}
diff --git a/tests/lib/Authentication/Events/RemoteWipeStartedTest.php b/tests/lib/Authentication/Events/RemoteWipeStartedTest.php
new file mode 100644
index 00000000000..fc297f7c087
--- /dev/null
+++ b/tests/lib/Authentication/Events/RemoteWipeStartedTest.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Events;
+
+use OC\Authentication\Events\RemoteWipeStarted;
+use OC\Authentication\Token\IToken;
+use Test\TestCase;
+
+class RemoteWipeStartedTest extends TestCase {
+ public function testGetToken(): void {
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeStarted($token);
+
+ $this->assertSame($token, $event->getToken());
+ }
+}
diff --git a/tests/lib/Authentication/Listeners/RemoteWipeActivityListenerTest.php b/tests/lib/Authentication/Listeners/RemoteWipeActivityListenerTest.php
new file mode 100644
index 00000000000..e2f957ab69a
--- /dev/null
+++ b/tests/lib/Authentication/Listeners/RemoteWipeActivityListenerTest.php
@@ -0,0 +1,137 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Events;
+
+use OC\Activity\Event as IActivityEvent;
+use OC\Authentication\Events\RemoteWipeFinished;
+use OC\Authentication\Events\RemoteWipeStarted;
+use OC\Authentication\Listeners\RemoteWipeActivityListener;
+use OC\Authentication\Token\IToken;
+use OCP\Activity\IManager as IActivityManager;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class RemoteWipeActivityListenerTest extends TestCase {
+ /** @var IActivityManager|MockObject */
+ private $activityManager;
+
+ /** @var LoggerInterface|MockObject */
+ private $logger;
+
+ /** @var IEventListener */
+ private $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->activityManager = $this->createMock(IActivityManager::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->listener = new RemoteWipeActivityListener(
+ $this->activityManager,
+ $this->logger
+ );
+ }
+
+ public function testHandleUnrelated(): void {
+ $event = new Event();
+
+ $this->listener->handle($event);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testHandleRemoteWipeStarted(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeStarted($token);
+ $activityEvent = $this->createMock(IActivityEvent::class);
+ $this->activityManager->expects($this->once())
+ ->method('generateEvent')
+ ->willReturn($activityEvent);
+ $activityEvent->expects($this->once())
+ ->method('setApp')
+ ->with('core')
+ ->willReturnSelf();
+ $activityEvent->expects($this->once())
+ ->method('setType')
+ ->with('security')
+ ->willReturnSelf();
+ $token->method('getUID')->willReturn('user123');
+ $activityEvent->expects($this->once())
+ ->method('setAuthor')
+ ->with('user123')
+ ->willReturnSelf();
+ $activityEvent->expects($this->once())
+ ->method('setAffectedUser')
+ ->with('user123')
+ ->willReturnSelf();
+ $token->method('getName')->willReturn('Token 1');
+ $activityEvent->expects($this->once())
+ ->method('setSubject')
+ ->with('remote_wipe_start', ['name' => 'Token 1'])
+ ->willReturnSelf();
+ $this->activityManager->expects($this->once())
+ ->method('publish');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeStartedCanNotPublish(): void {
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeStarted($token);
+ $this->activityManager->expects($this->once())
+ ->method('generateEvent');
+ $this->activityManager->expects($this->once())
+ ->method('publish')
+ ->willThrowException(new \BadMethodCallException());
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeFinished(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeFinished($token);
+ $activityEvent = $this->createMock(IActivityEvent::class);
+ $this->activityManager->expects($this->once())
+ ->method('generateEvent')
+ ->willReturn($activityEvent);
+ $activityEvent->expects($this->once())
+ ->method('setApp')
+ ->with('core')
+ ->willReturnSelf();
+ $activityEvent->expects($this->once())
+ ->method('setType')
+ ->with('security')
+ ->willReturnSelf();
+ $token->method('getUID')->willReturn('user123');
+ $activityEvent->expects($this->once())
+ ->method('setAuthor')
+ ->with('user123')
+ ->willReturnSelf();
+ $activityEvent->expects($this->once())
+ ->method('setAffectedUser')
+ ->with('user123')
+ ->willReturnSelf();
+ $token->method('getName')->willReturn('Token 1');
+ $activityEvent->expects($this->once())
+ ->method('setSubject')
+ ->with('remote_wipe_finish', ['name' => 'Token 1'])
+ ->willReturnSelf();
+ $this->activityManager->expects($this->once())
+ ->method('publish');
+
+ $this->listener->handle($event);
+ }
+}
diff --git a/tests/lib/Authentication/Listeners/RemoteWipeEmailListenerTest.php b/tests/lib/Authentication/Listeners/RemoteWipeEmailListenerTest.php
new file mode 100644
index 00000000000..1c45add4e31
--- /dev/null
+++ b/tests/lib/Authentication/Listeners/RemoteWipeEmailListenerTest.php
@@ -0,0 +1,225 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace lib\Authentication\Listeners;
+
+use Exception;
+use OC\Authentication\Events\RemoteWipeFinished;
+use OC\Authentication\Events\RemoteWipeStarted;
+use OC\Authentication\Listeners\RemoteWipeEmailListener;
+use OC\Authentication\Token\IToken;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\IL10N;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\L10N\IFactory;
+use OCP\Mail\IMailer;
+use OCP\Mail\IMessage;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class RemoteWipeEmailListenerTest extends TestCase {
+ /** @var IMailer|MockObject */
+ private $mailer;
+
+ /** @var IUserManager|MockObject */
+ private $userManager;
+
+ /** @var IFactory|MockObject */
+ private $l10nFactory;
+
+ /** @var IL10N|MockObject */
+ private $l10n;
+
+ /** @var LoggerInterface|MockObject */
+ private $logger;
+
+ /** @var IEventListener */
+ private $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->mailer = $this->createMock(IMailer::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->l10nFactory = $this->createMock(IFactory::class);
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->l10nFactory->method('get')->with('core')->willReturn($this->l10n);
+ $this->l10n->method('t')->willReturnArgument(0);
+
+ $this->listener = new RemoteWipeEmailListener(
+ $this->mailer,
+ $this->userManager,
+ $this->l10nFactory,
+ $this->logger
+ );
+ }
+
+
+ public function testHandleUnrelated(): void {
+ $event = new Event();
+ $this->mailer->expects($this->never())->method('send');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeStartedInvalidUser(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeStarted($token);
+ $token->method('getUID')->willReturn('nope');
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with('nope')
+ ->willReturn(null);
+ $this->mailer->expects($this->never())->method('send');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeStartedNoEmailSet(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeStarted($token);
+ $token->method('getUID')->willReturn('nope');
+ $user = $this->createMock(IUser::class);
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with('nope')
+ ->willReturn($user);
+ $user->method('getEMailAddress')->willReturn(null);
+ $this->mailer->expects($this->never())->method('send');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeStartedTransmissionError(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeStarted($token);
+ $token->method('getUID')->willReturn('nope');
+ $user = $this->createMock(IUser::class);
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with('nope')
+ ->willReturn($user);
+ $user->method('getEMailAddress')->willReturn('user@domain.org');
+ $this->mailer->expects($this->once())
+ ->method('send')
+ ->willThrowException(new Exception());
+ $this->logger->expects($this->once())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeStarted(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeStarted($token);
+ $token->method('getUID')->willReturn('nope');
+ $user = $this->createMock(IUser::class);
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with('nope')
+ ->willReturn($user);
+ $user->method('getEMailAddress')->willReturn('user@domain.org');
+ $message = $this->createMock(IMessage::class);
+ $this->mailer->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($message);
+ $message->expects($this->once())
+ ->method('setTo')
+ ->with($this->equalTo(['user@domain.org']));
+ $this->mailer->expects($this->once())
+ ->method('send')
+ ->with($message);
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeFinishedInvalidUser(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeFinished($token);
+ $token->method('getUID')->willReturn('nope');
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with('nope')
+ ->willReturn(null);
+ $this->mailer->expects($this->never())->method('send');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeFinishedNoEmailSet(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeFinished($token);
+ $token->method('getUID')->willReturn('nope');
+ $user = $this->createMock(IUser::class);
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with('nope')
+ ->willReturn($user);
+ $user->method('getEMailAddress')->willReturn(null);
+ $this->mailer->expects($this->never())->method('send');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeFinishedTransmissionError(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeFinished($token);
+ $token->method('getUID')->willReturn('nope');
+ $user = $this->createMock(IUser::class);
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with('nope')
+ ->willReturn($user);
+ $user->method('getEMailAddress')->willReturn('user@domain.org');
+ $this->mailer->expects($this->once())
+ ->method('send')
+ ->willThrowException(new Exception());
+ $this->logger->expects($this->once())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeFinished(): void {
+ /** @var IToken|MockObject $token */
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeFinished($token);
+ $token->method('getUID')->willReturn('nope');
+ $user = $this->createMock(IUser::class);
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with('nope')
+ ->willReturn($user);
+ $user->method('getEMailAddress')->willReturn('user@domain.org');
+ $message = $this->createMock(IMessage::class);
+ $this->mailer->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($message);
+ $message->expects($this->once())
+ ->method('setTo')
+ ->with($this->equalTo(['user@domain.org']));
+ $this->mailer->expects($this->once())
+ ->method('send')
+ ->with($message);
+
+ $this->listener->handle($event);
+ }
+}
diff --git a/tests/lib/Authentication/Listeners/RemoteWipeNotificationsListenerTest.php b/tests/lib/Authentication/Listeners/RemoteWipeNotificationsListenerTest.php
new file mode 100644
index 00000000000..53fa502b199
--- /dev/null
+++ b/tests/lib/Authentication/Listeners/RemoteWipeNotificationsListenerTest.php
@@ -0,0 +1,134 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Events;
+
+use DateTime;
+use OC\Authentication\Events\RemoteWipeFinished;
+use OC\Authentication\Events\RemoteWipeStarted;
+use OC\Authentication\Listeners\RemoteWipeNotificationsListener;
+use OC\Authentication\Token\IToken;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Notification\IManager as INotificationManager;
+use OCP\Notification\INotification;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class RemoteWipeNotificationsListenerTest extends TestCase {
+ /** @var INotificationManager|MockObject */
+ private $notificationManager;
+
+ /** @var ITimeFactory|MockObject */
+ private $timeFactory;
+
+ /** @var IEventListener */
+ private $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->notificationManager = $this->createMock(INotificationManager::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+
+ $this->listener = new RemoteWipeNotificationsListener(
+ $this->notificationManager,
+ $this->timeFactory
+ );
+ }
+
+ public function testHandleUnrelated(): void {
+ $event = new Event();
+
+ $this->listener->handle($event);
+
+ $this->addToAssertionCount(1);
+ }
+
+ public function testHandleRemoteWipeStarted(): void {
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeStarted($token);
+ $notification = $this->createMock(INotification::class);
+ $this->notificationManager->expects($this->once())
+ ->method('createNotification')
+ ->willReturn($notification);
+ $notification->expects($this->once())
+ ->method('setApp')
+ ->with('auth')
+ ->willReturnSelf();
+ $token->method('getUID')->willReturn('user123');
+ $notification->expects($this->once())
+ ->method('setUser')
+ ->with('user123')
+ ->willReturnSelf();
+ $now = new DateTime();
+ $this->timeFactory->method('getDateTime')->willReturn($now);
+ $notification->expects($this->once())
+ ->method('setDateTime')
+ ->with($now)
+ ->willReturnSelf();
+ $token->method('getId')->willReturn(123);
+ $notification->expects($this->once())
+ ->method('setObject')
+ ->with('token', '123')
+ ->willReturnSelf();
+ $token->method('getName')->willReturn('Token 1');
+ $notification->expects($this->once())
+ ->method('setSubject')
+ ->with('remote_wipe_start', [
+ 'name' => 'Token 1'
+ ])
+ ->willReturnSelf();
+ $this->notificationManager->expects($this->once())
+ ->method('notify');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleRemoteWipeFinished(): void {
+ $token = $this->createMock(IToken::class);
+ $event = new RemoteWipeFinished($token);
+ $notification = $this->createMock(INotification::class);
+ $this->notificationManager->expects($this->once())
+ ->method('createNotification')
+ ->willReturn($notification);
+ $notification->expects($this->once())
+ ->method('setApp')
+ ->with('auth')
+ ->willReturnSelf();
+ $token->method('getUID')->willReturn('user123');
+ $notification->expects($this->once())
+ ->method('setUser')
+ ->with('user123')
+ ->willReturnSelf();
+ $now = new DateTime();
+ $this->timeFactory->method('getDateTime')->willReturn($now);
+ $notification->expects($this->once())
+ ->method('setDateTime')
+ ->with($now)
+ ->willReturnSelf();
+ $token->method('getId')->willReturn(123);
+ $notification->expects($this->once())
+ ->method('setObject')
+ ->with('token', '123')
+ ->willReturnSelf();
+ $token->method('getName')->willReturn('Token 1');
+ $notification->expects($this->once())
+ ->method('setSubject')
+ ->with('remote_wipe_finish', [
+ 'name' => 'Token 1'
+ ])
+ ->willReturnSelf();
+ $this->notificationManager->expects($this->once())
+ ->method('notify');
+
+ $this->listener->handle($event);
+ }
+}
diff --git a/tests/lib/Authentication/Listeners/UserDeletedTokenCleanupListenerTest.php b/tests/lib/Authentication/Listeners/UserDeletedTokenCleanupListenerTest.php
new file mode 100644
index 00000000000..1861a5b2150
--- /dev/null
+++ b/tests/lib/Authentication/Listeners/UserDeletedTokenCleanupListenerTest.php
@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Listeners;
+
+use Exception;
+use OC\Authentication\Listeners\UserDeletedTokenCleanupListener;
+use OC\Authentication\Token\IToken;
+use OC\Authentication\Token\Manager;
+use OCP\EventDispatcher\Event;
+use OCP\IUser;
+use OCP\User\Events\UserDeletedEvent;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class UserDeletedTokenCleanupListenerTest extends TestCase {
+ /** @var Manager|MockObject */
+ private $manager;
+
+ /** @var LoggerInterface|MockObject */
+ private $logger;
+
+ /** @var UserDeletedTokenCleanupListener */
+ private $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->manager = $this->createMock(Manager::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->listener = new UserDeletedTokenCleanupListener(
+ $this->manager,
+ $this->logger
+ );
+ }
+
+ public function testHandleUnrelated(): void {
+ $event = new Event();
+ $this->manager->expects($this->never())->method('getTokenByUser');
+ $this->logger->expects($this->never())->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleWithErrors(): void {
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('user123');
+ $event = new UserDeletedEvent($user);
+ $exception = new Exception('nope');
+ $this->manager->expects($this->once())
+ ->method('getTokenByUser')
+ ->with('user123')
+ ->willThrowException($exception);
+ $this->logger->expects($this->once())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandle(): void {
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('user123');
+ $event = new UserDeletedEvent($user);
+ $token1 = $this->createMock(IToken::class);
+ $token1->method('getId')->willReturn(1);
+ $token2 = $this->createMock(IToken::class);
+ $token2->method('getId')->willReturn(2);
+ $token3 = $this->createMock(IToken::class);
+ $token3->method('getId')->willReturn(3);
+ $this->manager->expects($this->once())
+ ->method('getTokenByUser')
+ ->with('user123')
+ ->willReturn([
+ $token1,
+ $token2,
+ $token3,
+ ]);
+
+ $calls = [
+ ['user123', 1],
+ ['user123', 2],
+ ['user123', 3],
+ ];
+ $this->manager->expects($this->exactly(3))
+ ->method('invalidateTokenById')
+ ->willReturnCallback(function () use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, func_get_args());
+ });
+ $this->logger->expects($this->never())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+}
diff --git a/tests/lib/Authentication/Login/ALoginTestCommand.php b/tests/lib/Authentication/Login/ALoginTestCommand.php
new file mode 100644
index 00000000000..b955b20beba
--- /dev/null
+++ b/tests/lib/Authentication/Login/ALoginTestCommand.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\LoginData;
+use OCP\IRequest;
+use OCP\IUser;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+abstract class ALoginTestCommand extends TestCase {
+ /** @var IRequest|MockObject */
+ protected $request;
+
+ /** @var string */
+ protected $username = 'user123';
+
+ /** @var string */
+ protected $password = '123456';
+
+ /** @var string */
+ protected $redirectUrl = '/apps/contacts';
+
+ /** @var string */
+ protected $timezone = 'Europe/Vienna';
+
+ protected $timeZoneOffset = '2';
+
+ /** @var IUser|MockObject */
+ protected $user;
+
+ /** @var ALoginTestCommand */
+ protected $cmd;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->request = $this->createMock(IRequest::class);
+ $this->user = $this->createMock(IUser::class);
+ }
+
+ protected function getBasicLoginData(): LoginData {
+ return new LoginData(
+ $this->request,
+ $this->username,
+ $this->password
+ );
+ }
+
+ protected function getInvalidLoginData(): LoginData {
+ return new LoginData(
+ $this->request,
+ $this->username,
+ $this->password
+ );
+ }
+
+ protected function getFailedLoginData(): LoginData {
+ $data = new LoginData(
+ $this->request,
+ $this->username,
+ $this->password
+ );
+ $data->setUser(false);
+ return $data;
+ }
+
+ protected function getLoggedInLoginData(): LoginData {
+ $basic = $this->getBasicLoginData();
+ $basic->setUser($this->user);
+ return $basic;
+ }
+
+ protected function getLoggedInLoginDataWithRedirectUrl(): LoginData {
+ $data = new LoginData(
+ $this->request,
+ $this->username,
+ $this->password,
+ $this->redirectUrl
+ );
+ $data->setUser($this->user);
+ return $data;
+ }
+
+ protected function getLoggedInLoginDataWithTimezone(): LoginData {
+ $data = new LoginData(
+ $this->request,
+ $this->username,
+ $this->password,
+ null,
+ $this->timezone,
+ $this->timeZoneOffset
+ );
+ $data->setUser($this->user);
+ return $data;
+ }
+}
diff --git a/tests/lib/Authentication/Login/ClearLostPasswordTokensCommandTest.php b/tests/lib/Authentication/Login/ClearLostPasswordTokensCommandTest.php
new file mode 100644
index 00000000000..5ff2da28946
--- /dev/null
+++ b/tests/lib/Authentication/Login/ClearLostPasswordTokensCommandTest.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\ClearLostPasswordTokensCommand;
+use OCP\IConfig;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class ClearLostPasswordTokensCommandTest extends ALoginTestCommand {
+ /** @var IConfig|MockObject */
+ private $config;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->config = $this->createMock(IConfig::class);
+
+ $this->cmd = new ClearLostPasswordTokensCommand(
+ $this->config
+ );
+ }
+
+ public function testProcess(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->user->expects($this->once())
+ ->method('getUID')
+ ->willReturn($this->username);
+ $this->config->expects($this->once())
+ ->method('deleteUserValue')
+ ->with(
+ $this->username,
+ 'core',
+ 'lostpassword'
+ );
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+}
diff --git a/tests/lib/Authentication/Login/CompleteLoginCommandTest.php b/tests/lib/Authentication/Login/CompleteLoginCommandTest.php
new file mode 100644
index 00000000000..5b08368671f
--- /dev/null
+++ b/tests/lib/Authentication/Login/CompleteLoginCommandTest.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\CompleteLoginCommand;
+use OC\User\Session;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class CompleteLoginCommandTest extends ALoginTestCommand {
+ /** @var Session|MockObject */
+ private $session;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->session = $this->createMock(Session::class);
+
+ $this->cmd = new CompleteLoginCommand(
+ $this->session
+ );
+ }
+
+ public function testProcess(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->session->expects($this->once())
+ ->method('completeLogin')
+ ->with(
+ $this->user,
+ $this->equalTo(
+ [
+ 'loginName' => $this->username,
+ 'password' => $this->password,
+ ]
+ )
+ );
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+}
diff --git a/tests/lib/Authentication/Login/CreateSessionTokenCommandTest.php b/tests/lib/Authentication/Login/CreateSessionTokenCommandTest.php
new file mode 100644
index 00000000000..668c0a6d6ea
--- /dev/null
+++ b/tests/lib/Authentication/Login/CreateSessionTokenCommandTest.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\CreateSessionTokenCommand;
+use OC\Authentication\Token\IToken;
+use OC\User\Session;
+use OCP\IConfig;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class CreateSessionTokenCommandTest extends ALoginTestCommand {
+ /** @var IConfig|MockObject */
+ private $config;
+
+ /** @var Session|MockObject */
+ private $userSession;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->userSession = $this->createMock(Session::class);
+
+ $this->cmd = new CreateSessionTokenCommand(
+ $this->config,
+ $this->userSession
+ );
+ }
+
+ public function testProcess(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->config->expects($this->once())
+ ->method('getSystemValueInt')
+ ->with(
+ 'remember_login_cookie_lifetime',
+ 60 * 60 * 24 * 15
+ )
+ ->willReturn(100);
+ $this->user->expects($this->any())
+ ->method('getUID')
+ ->willReturn($this->username);
+ $this->userSession->expects($this->once())
+ ->method('createSessionToken')
+ ->with(
+ $this->request,
+ $this->username,
+ $this->username,
+ $this->password,
+ IToken::REMEMBER
+ );
+ $this->userSession->expects($this->once())
+ ->method('updateTokens')
+ ->with(
+ $this->username,
+ $this->password
+ );
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+
+ public function testProcessDoNotRemember(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->config->expects($this->once())
+ ->method('getSystemValueInt')
+ ->with(
+ 'remember_login_cookie_lifetime',
+ 60 * 60 * 24 * 15
+ )
+ ->willReturn(0);
+ $this->user->expects($this->any())
+ ->method('getUID')
+ ->willReturn($this->username);
+ $this->userSession->expects($this->once())
+ ->method('createSessionToken')
+ ->with(
+ $this->request,
+ $this->username,
+ $this->username,
+ $this->password,
+ IToken::DO_NOT_REMEMBER
+ );
+ $this->userSession->expects($this->once())
+ ->method('updateTokens')
+ ->with(
+ $this->username,
+ $this->password
+ );
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertFalse($data->isRememberLogin());
+ }
+}
diff --git a/tests/lib/Authentication/Login/FinishRememberedLoginCommandTest.php b/tests/lib/Authentication/Login/FinishRememberedLoginCommandTest.php
new file mode 100644
index 00000000000..499abafa6e3
--- /dev/null
+++ b/tests/lib/Authentication/Login/FinishRememberedLoginCommandTest.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\FinishRememberedLoginCommand;
+use OC\User\Session;
+use OCP\IConfig;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class FinishRememberedLoginCommandTest extends ALoginTestCommand {
+ /** @var Session|MockObject */
+ private $userSession;
+ /** @var IConfig|MockObject */
+ private $config;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->userSession = $this->createMock(Session::class);
+ $this->config = $this->createMock(IConfig::class);
+
+ $this->cmd = new FinishRememberedLoginCommand(
+ $this->userSession,
+ $this->config
+ );
+ }
+
+ public function testProcessNotRememberedLogin(): void {
+ $data = $this->getLoggedInLoginData();
+ $data->setRememberLogin(false);
+ $this->userSession->expects($this->never())
+ ->method('createRememberMeToken');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+
+ public function testProcess(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->config->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('auto_logout', false)
+ ->willReturn(false);
+ $this->userSession->expects($this->once())
+ ->method('createRememberMeToken')
+ ->with($this->user);
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+
+ public function testProcessNotRemeberedLoginWithAutologout(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->config->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('auto_logout', false)
+ ->willReturn(true);
+ $this->userSession->expects($this->never())
+ ->method('createRememberMeToken');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+}
diff --git a/tests/lib/Authentication/Login/LoggedInCheckCommandTest.php b/tests/lib/Authentication/Login/LoggedInCheckCommandTest.php
new file mode 100644
index 00000000000..7b011d70673
--- /dev/null
+++ b/tests/lib/Authentication/Login/LoggedInCheckCommandTest.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\LoggedInCheckCommand;
+use OC\Core\Controller\LoginController;
+use OCP\EventDispatcher\IEventDispatcher;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+
+class LoggedInCheckCommandTest extends ALoginTestCommand {
+ /** @var LoggerInterface|MockObject */
+ private $logger;
+
+ /** @var IEventDispatcher|MockObject */
+ private $dispatcher;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->dispatcher = $this->createMock(IEventDispatcher::class);
+
+ $this->cmd = new LoggedInCheckCommand(
+ $this->logger,
+ $this->dispatcher
+ );
+ }
+
+ public function testProcessSuccessfulLogin(): void {
+ $data = $this->getLoggedInLoginData();
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+
+ public function testProcessFailedLogin(): void {
+ $data = $this->getFailedLoginData();
+ $this->logger->expects($this->once())
+ ->method('warning');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertFalse($result->isSuccess());
+ $this->assertSame(LoginController::LOGIN_MSG_INVALIDPASSWORD, $result->getErrorMessage());
+ }
+}
diff --git a/tests/lib/Authentication/Login/PreLoginHookCommandTest.php b/tests/lib/Authentication/Login/PreLoginHookCommandTest.php
new file mode 100644
index 00000000000..0e5096baf55
--- /dev/null
+++ b/tests/lib/Authentication/Login/PreLoginHookCommandTest.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\PreLoginHookCommand;
+use OC\User\Manager;
+use OCP\IUserManager;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class PreLoginHookCommandTest extends ALoginTestCommand {
+ /** @var IUserManager|MockObject */
+ private $userManager;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->userManager = $this->createMock(Manager::class);
+
+ $this->cmd = new PreLoginHookCommand(
+ $this->userManager
+ );
+ }
+
+ public function testProcess(): void {
+ $data = $this->getBasicLoginData();
+ $this->userManager->expects($this->once())
+ ->method('emit')
+ ->with(
+ '\OC\User',
+ 'preLogin',
+ [
+ $this->username,
+ $this->password,
+ ]
+ );
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+}
diff --git a/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php b/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php
new file mode 100644
index 00000000000..fb8240c4b1e
--- /dev/null
+++ b/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\SetUserTimezoneCommand;
+use OCP\IConfig;
+use OCP\ISession;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class SetUserTimezoneCommandTest extends ALoginTestCommand {
+ /** @var IConfig|MockObject */
+ private $config;
+
+ /** @var ISession|MockObject */
+ private $session;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->session = $this->createMock(ISession::class);
+
+ $this->cmd = new SetUserTimezoneCommand(
+ $this->config,
+ $this->session
+ );
+ }
+
+ public function testProcessNoTimezoneSet(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->config->expects($this->never())
+ ->method('setUserValue');
+ $this->session->expects($this->never())
+ ->method('set');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+
+ public function testProcess(): void {
+ $data = $this->getLoggedInLoginDataWithTimezone();
+ $this->user->expects($this->once())
+ ->method('getUID')
+ ->willReturn($this->username);
+ $this->config->expects($this->once())
+ ->method('setUserValue')
+ ->with(
+ $this->username,
+ 'core',
+ 'timezone',
+ $this->timezone
+ );
+ $this->session->expects($this->once())
+ ->method('set')
+ ->with(
+ 'timezone',
+ $this->timeZoneOffset
+ );
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+}
diff --git a/tests/lib/Authentication/Login/TwoFactorCommandTest.php b/tests/lib/Authentication/Login/TwoFactorCommandTest.php
new file mode 100644
index 00000000000..a95e4b50cbc
--- /dev/null
+++ b/tests/lib/Authentication/Login/TwoFactorCommandTest.php
@@ -0,0 +1,341 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\TwoFactorCommand;
+use OC\Authentication\TwoFactorAuth\Manager;
+use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
+use OC\Authentication\TwoFactorAuth\ProviderSet;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
+use OCP\Authentication\TwoFactorAuth\IProvider as ITwoFactorAuthProvider;
+use OCP\IURLGenerator;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class TwoFactorCommandTest extends ALoginTestCommand {
+ /** @var Manager|MockObject */
+ private $twoFactorManager;
+
+ /** @var MandatoryTwoFactor|MockObject */
+ private $mandatoryTwoFactor;
+
+ /** @var IURLGenerator|MockObject */
+ private $urlGenerator;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->twoFactorManager = $this->createMock(Manager::class);
+ $this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+
+ $this->cmd = new TwoFactorCommand(
+ $this->twoFactorManager,
+ $this->mandatoryTwoFactor,
+ $this->urlGenerator
+ );
+ }
+
+ public function testNotTwoFactorAuthenticated(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->twoFactorManager->expects($this->once())
+ ->method('isTwoFactorAuthenticated')
+ ->willReturn(false);
+ $this->twoFactorManager->expects($this->never())
+ ->method('prepareTwoFactorLogin');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+
+ public function testProcessOneActiveProvider(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->twoFactorManager->expects($this->once())
+ ->method('isTwoFactorAuthenticated')
+ ->willReturn(true);
+ $this->twoFactorManager->expects($this->once())
+ ->method('prepareTwoFactorLogin')
+ ->with(
+ $this->user,
+ $data->isRememberLogin()
+ );
+ $provider = $this->createMock(ITwoFactorAuthProvider::class);
+ $this->twoFactorManager->expects($this->once())
+ ->method('getProviderSet')
+ ->willReturn(new ProviderSet([
+ $provider,
+ ], false));
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($this->user)
+ ->willReturn([]);
+ $this->mandatoryTwoFactor->expects($this->any())
+ ->method('isEnforcedFor')
+ ->with($this->user)
+ ->willReturn(false);
+ $provider->expects($this->once())
+ ->method('getId')
+ ->willReturn('test');
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with(
+ 'core.TwoFactorChallenge.showChallenge',
+ [
+ 'challengeProviderId' => 'test'
+ ]
+ )
+ ->willReturn('two/factor/url');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertEquals('two/factor/url', $result->getRedirectUrl());
+ }
+
+ public function testProcessMissingProviders(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->twoFactorManager->expects($this->once())
+ ->method('isTwoFactorAuthenticated')
+ ->willReturn(true);
+ $this->twoFactorManager->expects($this->once())
+ ->method('prepareTwoFactorLogin')
+ ->with(
+ $this->user,
+ $data->isRememberLogin()
+ );
+ $provider = $this->createMock(ITwoFactorAuthProvider::class);
+ $provider->expects($this->once())
+ ->method('getId')
+ ->willReturn('test1');
+ $this->twoFactorManager->expects($this->once())
+ ->method('getProviderSet')
+ ->willReturn(new ProviderSet([
+ $provider,
+ ], true));
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($this->user)
+ ->willReturn([]);
+ $this->mandatoryTwoFactor->expects($this->any())
+ ->method('isEnforcedFor')
+ ->with($this->user)
+ ->willReturn(false);
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with(
+ 'core.TwoFactorChallenge.selectChallenge'
+ )
+ ->willReturn('two/factor/url');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertEquals('two/factor/url', $result->getRedirectUrl());
+ }
+
+ public function testProcessTwoActiveProviders(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->twoFactorManager->expects($this->once())
+ ->method('isTwoFactorAuthenticated')
+ ->willReturn(true);
+ $this->twoFactorManager->expects($this->once())
+ ->method('prepareTwoFactorLogin')
+ ->with(
+ $this->user,
+ $data->isRememberLogin()
+ );
+ $provider1 = $this->createMock(ITwoFactorAuthProvider::class);
+ $provider2 = $this->createMock(ITwoFactorAuthProvider::class);
+ $provider1->expects($this->once())
+ ->method('getId')
+ ->willReturn('test1');
+ $provider2->expects($this->once())
+ ->method('getId')
+ ->willReturn('test2');
+ $this->twoFactorManager->expects($this->once())
+ ->method('getProviderSet')
+ ->willReturn(new ProviderSet([
+ $provider1,
+ $provider2,
+ ], false));
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($this->user)
+ ->willReturn([]);
+ $this->mandatoryTwoFactor->expects($this->any())
+ ->method('isEnforcedFor')
+ ->with($this->user)
+ ->willReturn(false);
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with(
+ 'core.TwoFactorChallenge.selectChallenge'
+ )
+ ->willReturn('two/factor/url');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertEquals('two/factor/url', $result->getRedirectUrl());
+ }
+
+ public function testProcessFailingProviderAndEnforcedButNoSetupProviders(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->twoFactorManager->expects($this->once())
+ ->method('isTwoFactorAuthenticated')
+ ->willReturn(true);
+ $this->twoFactorManager->expects($this->once())
+ ->method('prepareTwoFactorLogin')
+ ->with(
+ $this->user,
+ $data->isRememberLogin()
+ );
+ $this->twoFactorManager->expects($this->once())
+ ->method('getProviderSet')
+ ->willReturn(new ProviderSet([], true));
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($this->user)
+ ->willReturn([]);
+ $this->mandatoryTwoFactor->expects($this->any())
+ ->method('isEnforcedFor')
+ ->with($this->user)
+ ->willReturn(true);
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with(
+ 'core.TwoFactorChallenge.selectChallenge'
+ )
+ ->willReturn('two/factor/url');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertEquals('two/factor/url', $result->getRedirectUrl());
+ }
+
+ public function testProcessFailingProviderAndEnforced(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->twoFactorManager->expects($this->once())
+ ->method('isTwoFactorAuthenticated')
+ ->willReturn(true);
+ $this->twoFactorManager->expects($this->once())
+ ->method('prepareTwoFactorLogin')
+ ->with(
+ $this->user,
+ $data->isRememberLogin()
+ );
+ $provider = $this->createMock(IActivatableAtLogin::class);
+ $this->twoFactorManager->expects($this->once())
+ ->method('getProviderSet')
+ ->willReturn(new ProviderSet([
+ $provider,
+ ], true));
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($this->user)
+ ->willReturn([]);
+ $this->mandatoryTwoFactor->expects($this->any())
+ ->method('isEnforcedFor')
+ ->with($this->user)
+ ->willReturn(true);
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with(
+ 'core.TwoFactorChallenge.selectChallenge'
+ )
+ ->willReturn('two/factor/url');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertEquals('two/factor/url', $result->getRedirectUrl());
+ }
+
+ public function testProcessNoProvidersButEnforced(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->twoFactorManager->expects($this->once())
+ ->method('isTwoFactorAuthenticated')
+ ->willReturn(true);
+ $this->twoFactorManager->expects($this->once())
+ ->method('prepareTwoFactorLogin')
+ ->with(
+ $this->user,
+ $data->isRememberLogin()
+ );
+ $this->twoFactorManager->expects($this->once())
+ ->method('getProviderSet')
+ ->willReturn(new ProviderSet([], false));
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($this->user)
+ ->willReturn([]);
+ $this->mandatoryTwoFactor->expects($this->any())
+ ->method('isEnforcedFor')
+ ->with($this->user)
+ ->willReturn(true);
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with(
+ 'core.TwoFactorChallenge.selectChallenge'
+ )
+ ->willReturn('two/factor/url');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertEquals('two/factor/url', $result->getRedirectUrl());
+ }
+
+ public function testProcessWithRedirectUrl(): void {
+ $data = $this->getLoggedInLoginDataWithRedirectUrl();
+ $this->twoFactorManager->expects($this->once())
+ ->method('isTwoFactorAuthenticated')
+ ->willReturn(true);
+ $this->twoFactorManager->expects($this->once())
+ ->method('prepareTwoFactorLogin')
+ ->with(
+ $this->user,
+ $data->isRememberLogin()
+ );
+ $provider = $this->createMock(ITwoFactorAuthProvider::class);
+ $this->twoFactorManager->expects($this->once())
+ ->method('getProviderSet')
+ ->willReturn(new ProviderSet([
+ $provider,
+ ], false));
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($this->user)
+ ->willReturn([]);
+ $this->mandatoryTwoFactor->expects($this->any())
+ ->method('isEnforcedFor')
+ ->with($this->user)
+ ->willReturn(false);
+ $provider->expects($this->once())
+ ->method('getId')
+ ->willReturn('test');
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with(
+ 'core.TwoFactorChallenge.showChallenge',
+ [
+ 'challengeProviderId' => 'test',
+ 'redirect_url' => $this->redirectUrl,
+ ]
+ )
+ ->willReturn('two/factor/url');
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertEquals('two/factor/url', $result->getRedirectUrl());
+ }
+}
diff --git a/tests/lib/Authentication/Login/UidLoginCommandTest.php b/tests/lib/Authentication/Login/UidLoginCommandTest.php
new file mode 100644
index 00000000000..daae34e2212
--- /dev/null
+++ b/tests/lib/Authentication/Login/UidLoginCommandTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\UidLoginCommand;
+use OC\User\Manager;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class UidLoginCommandTest extends ALoginTestCommand {
+ /** @var Manager|MockObject */
+ private $userManager;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->userManager = $this->createMock(Manager::class);
+
+ $this->cmd = new UidLoginCommand(
+ $this->userManager
+ );
+ }
+
+ public function testProcessFailingLogin(): void {
+ $data = $this->getBasicLoginData();
+ $this->userManager->expects($this->once())
+ ->method('checkPasswordNoLogging')
+ ->with(
+ $this->username,
+ $this->password
+ )
+ ->willReturn(false);
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertFalse($data->getUser());
+ }
+
+ public function testProcess(): void {
+ $data = $this->getBasicLoginData();
+ $this->userManager->expects($this->once())
+ ->method('checkPasswordNoLogging')
+ ->with(
+ $this->username,
+ $this->password
+ )
+ ->willReturn($this->user);
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ $this->assertEquals($this->user, $data->getUser());
+ }
+}
diff --git a/tests/lib/Authentication/Login/UpdateLastPasswordConfirmCommandTest.php b/tests/lib/Authentication/Login/UpdateLastPasswordConfirmCommandTest.php
new file mode 100644
index 00000000000..1a845a05c23
--- /dev/null
+++ b/tests/lib/Authentication/Login/UpdateLastPasswordConfirmCommandTest.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\UpdateLastPasswordConfirmCommand;
+use OCP\ISession;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class UpdateLastPasswordConfirmCommandTest extends ALoginTestCommand {
+ /** @var ISession|MockObject */
+ private $session;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->session = $this->createMock(ISession::class);
+
+ $this->cmd = new UpdateLastPasswordConfirmCommand(
+ $this->session
+ );
+ }
+
+ public function testProcess(): void {
+ $data = $this->getLoggedInLoginData();
+ $this->user->expects($this->once())
+ ->method('getLastLogin')
+ ->willReturn(1234);
+ $this->session->expects($this->once())
+ ->method('set')
+ ->with(
+ 'last-password-confirm',
+ 1234
+ );
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+}
diff --git a/tests/lib/Authentication/Login/UserDisabledCheckCommandTest.php b/tests/lib/Authentication/Login/UserDisabledCheckCommandTest.php
new file mode 100644
index 00000000000..ee4e171d443
--- /dev/null
+++ b/tests/lib/Authentication/Login/UserDisabledCheckCommandTest.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace Test\Authentication\Login;
+
+use OC\Authentication\Login\UserDisabledCheckCommand;
+use OC\Core\Controller\LoginController;
+use OCP\IUserManager;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+
+class UserDisabledCheckCommandTest extends ALoginTestCommand {
+ /** @var IUserManager|MockObject */
+ private $userManager;
+
+ /** @var LoggerInterface|MockObject */
+ private $logger;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->cmd = new UserDisabledCheckCommand(
+ $this->userManager,
+ $this->logger
+ );
+ }
+
+ public function testProcessNonExistingUser(): void {
+ $data = $this->getBasicLoginData();
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with($this->username)
+ ->willReturn(null);
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+
+ public function testProcessDisabledUser(): void {
+ $data = $this->getBasicLoginData();
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with($this->username)
+ ->willReturn($this->user);
+ $this->user->expects($this->once())
+ ->method('isEnabled')
+ ->willReturn(false);
+
+ $result = $this->cmd->process($data);
+
+ $this->assertFalse($result->isSuccess());
+ $this->assertSame(LoginController::LOGIN_MSG_USERDISABLED, $result->getErrorMessage());
+ }
+
+ public function testProcess(): void {
+ $data = $this->getBasicLoginData();
+ $this->userManager->expects($this->once())
+ ->method('get')
+ ->with($this->username)
+ ->willReturn($this->user);
+ $this->user->expects($this->once())
+ ->method('isEnabled')
+ ->willReturn(true);
+
+ $result = $this->cmd->process($data);
+
+ $this->assertTrue($result->isSuccess());
+ }
+}
diff --git a/tests/lib/Authentication/LoginCredentials/CredentialsTest.php b/tests/lib/Authentication/LoginCredentials/CredentialsTest.php
new file mode 100644
index 00000000000..a64ad6c9b76
--- /dev/null
+++ b/tests/lib/Authentication/LoginCredentials/CredentialsTest.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\LoginCredentials;
+
+use OC\Authentication\LoginCredentials\Credentials;
+use Test\TestCase;
+
+class CredentialsTest extends TestCase {
+ /** @var string */
+ private $uid;
+
+ /** @var string */
+ private $user;
+
+ /** @var string */
+ private $password;
+
+ /** @var Credentials */
+ private $credentials;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->uid = 'user123';
+ $this->user = 'User123';
+ $this->password = '123456';
+
+ $this->credentials = new Credentials($this->uid, $this->user, $this->password);
+ }
+
+ public function testGetUID(): void {
+ $this->assertEquals($this->uid, $this->credentials->getUID());
+ }
+
+ public function testGetUserName(): void {
+ $this->assertEquals($this->user, $this->credentials->getLoginName());
+ }
+
+ public function testGetPassword(): void {
+ $this->assertEquals($this->password, $this->credentials->getPassword());
+ }
+}
diff --git a/tests/lib/Authentication/LoginCredentials/StoreTest.php b/tests/lib/Authentication/LoginCredentials/StoreTest.php
new file mode 100644
index 00000000000..aca586b91ec
--- /dev/null
+++ b/tests/lib/Authentication/LoginCredentials/StoreTest.php
@@ -0,0 +1,296 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\LoginCredentials;
+
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Exceptions\PasswordlessTokenException;
+use OC\Authentication\LoginCredentials\Credentials;
+use OC\Authentication\LoginCredentials\Store;
+use OC\Authentication\Token\IProvider;
+use OC\Authentication\Token\IToken;
+use OCP\Authentication\Exceptions\CredentialsUnavailableException;
+use OCP\ISession;
+use OCP\Security\ICrypto;
+use OCP\Session\Exceptions\SessionNotAvailableException;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+use function json_encode;
+
+class StoreTest extends TestCase {
+ /** @var ISession|\PHPUnit\Framework\MockObject\MockObject */
+ private $session;
+
+ /** @var IProvider|\PHPUnit\Framework\MockObject\MockObject */
+ private $tokenProvider;
+
+ /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
+ private $logger;
+ /** @var ICrypto|\PHPUnit\Framework\MockObject\MockObject */
+ private $crypto;
+
+ /** @var Store */
+ private $store;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->session = $this->createMock(ISession::class);
+ $this->tokenProvider = $this->createMock(IProvider::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->crypto = $this->createMock(ICrypto::class);
+
+ $this->store = new Store($this->session, $this->logger, $this->crypto, $this->tokenProvider);
+ }
+
+ public function testAuthenticate(): void {
+ $params = [
+ 'run' => true,
+ 'uid' => 'user123',
+ 'password' => '123456',
+ ];
+
+ $this->session->expects($this->once())
+ ->method('set')
+ ->with($this->equalTo('login_credentials'), $this->equalTo(json_encode($params)));
+ $this->crypto->expects($this->once())
+ ->method('encrypt')
+ ->willReturn('123456');
+
+ $this->store->authenticate($params);
+ }
+
+ public function testSetSession(): void {
+ $session = $this->createMock(ISession::class);
+
+ $this->store->setSession($session);
+ $this->addToAssertionCount(1);
+ }
+
+ public function testGetLoginCredentialsNoTokenProvider(): void {
+ $this->store = new Store($this->session, $this->logger, $this->crypto, null);
+
+ $this->expectException(CredentialsUnavailableException::class);
+
+ $this->store->getLoginCredentials();
+ }
+
+ public function testGetLoginCredentials(): void {
+ $uid = 'uid';
+ $user = 'user123';
+ $password = 'passme';
+ $token = $this->createMock(IToken::class);
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willReturn('sess2233');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sess2233')
+ ->willReturn($token);
+ $token->expects($this->once())
+ ->method('getUID')
+ ->willReturn($uid);
+ $token->expects($this->once())
+ ->method('getLoginName')
+ ->willReturn($user);
+ $this->tokenProvider->expects($this->once())
+ ->method('getPassword')
+ ->with($token, 'sess2233')
+ ->willReturn($password);
+ $expected = new Credentials($uid, $user, $password);
+
+ $creds = $this->store->getLoginCredentials();
+
+ $this->assertEquals($expected, $creds);
+ }
+
+ public function testGetLoginCredentialsSessionNotAvailable(): void {
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willThrowException(new SessionNotAvailableException());
+ $this->expectException(CredentialsUnavailableException::class);
+
+ $this->store->getLoginCredentials();
+ }
+
+ public function testGetLoginCredentialsInvalidToken(): void {
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willReturn('sess2233');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sess2233')
+ ->willThrowException(new InvalidTokenException());
+ $this->expectException(CredentialsUnavailableException::class);
+
+ $this->store->getLoginCredentials();
+ }
+
+ public function testGetLoginCredentialsPartialCredentialsAndSessionName(): void {
+ $uid = 'id987';
+ $user = 'user987';
+ $password = '7389374';
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willReturn('sess2233');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sess2233')
+ ->willThrowException(new InvalidTokenException());
+ $this->session->expects($this->once())
+ ->method('exists')
+ ->with($this->equalTo('login_credentials'))
+ ->willReturn(true);
+ $this->crypto->expects($this->once())
+ ->method('decrypt')
+ ->willReturn($password);
+ $this->session->expects($this->exactly(2))
+ ->method('get')
+ ->willReturnMap([
+ [
+ 'login_credentials',
+ json_encode([
+ 'uid' => $uid,
+ 'password' => $password,
+ ])
+ ],
+ [
+ 'loginname',
+ $user,
+ ],
+ ]);
+ $expected = new Credentials($uid, $user, $password);
+
+ $actual = $this->store->getLoginCredentials();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testGetLoginCredentialsPartialCredentials(): void {
+ $uid = 'id987';
+ $password = '7389374';
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willReturn('sess2233');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sess2233')
+ ->willThrowException(new InvalidTokenException());
+ $this->session->expects($this->once())
+ ->method('exists')
+ ->with($this->equalTo('login_credentials'))
+ ->willReturn(true);
+ $this->crypto->expects($this->once())
+ ->method('decrypt')
+ ->willReturn($password);
+ $this->session->expects($this->exactly(2))
+ ->method('get')
+ ->willReturnMap([
+ [
+ 'login_credentials',
+ json_encode([
+ 'uid' => $uid,
+ 'password' => $password,
+ ])
+ ],
+ [
+ 'loginname',
+ null,
+ ],
+ ]);
+ $expected = new Credentials($uid, $uid, $password);
+
+ $actual = $this->store->getLoginCredentials();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testGetLoginCredentialsInvalidTokenLoginCredentials(): void {
+ $uid = 'id987';
+ $user = 'user987';
+ $password = '7389374';
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willReturn('sess2233');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sess2233')
+ ->willThrowException(new InvalidTokenException());
+ $this->session->expects($this->once())
+ ->method('exists')
+ ->with($this->equalTo('login_credentials'))
+ ->willReturn(true);
+ $this->crypto->expects($this->once())
+ ->method('decrypt')
+ ->willReturn($password);
+ $this->session->expects($this->once())
+ ->method('get')
+ ->with($this->equalTo('login_credentials'))
+ ->willReturn('{"run":true,"uid":"id987","loginName":"user987","password":"7389374"}');
+ $expected = new Credentials($uid, $user, $password);
+
+ $actual = $this->store->getLoginCredentials();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testGetLoginCredentialsPasswordlessToken(): void {
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willReturn('sess2233');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sess2233')
+ ->willThrowException(new PasswordlessTokenException());
+ $this->expectException(CredentialsUnavailableException::class);
+
+ $this->store->getLoginCredentials();
+ }
+
+ public function testAuthenticatePasswordlessToken(): void {
+ $user = 'user987';
+ $password = null;
+
+ $params = [
+ 'run' => true,
+ 'loginName' => $user,
+ 'uid' => $user,
+ 'password' => $password,
+ ];
+
+ $this->session->expects($this->once())
+ ->method('set')
+ ->with($this->equalTo('login_credentials'), $this->equalTo(json_encode($params)));
+
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->willReturn('sess2233');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sess2233')
+ ->willThrowException(new PasswordlessTokenException());
+
+ $this->session->expects($this->once())
+ ->method('exists')
+ ->with($this->equalTo('login_credentials'))
+ ->willReturn(true);
+ $this->session->expects($this->once())
+ ->method('get')
+ ->with($this->equalTo('login_credentials'))
+ ->willReturn(json_encode($params));
+
+ $this->store->authenticate($params);
+ $actual = $this->store->getLoginCredentials();
+
+ $expected = new Credentials($user, $user, $password);
+ $this->assertEquals($expected, $actual);
+ }
+}
diff --git a/tests/lib/Authentication/Token/ManagerTest.php b/tests/lib/Authentication/Token/ManagerTest.php
new file mode 100644
index 00000000000..58bbe236248
--- /dev/null
+++ b/tests/lib/Authentication/Token/ManagerTest.php
@@ -0,0 +1,406 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Token;
+
+use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Token\IToken;
+use OC\Authentication\Token\Manager;
+use OC\Authentication\Token\PublicKeyToken;
+use OC\Authentication\Token\PublicKeyTokenProvider;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class ManagerTest extends TestCase {
+ /** @var PublicKeyTokenProvider|MockObject */
+ private $publicKeyTokenProvider;
+ /** @var Manager */
+ private $manager;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->publicKeyTokenProvider = $this->createMock(PublicKeyTokenProvider::class);
+ $this->manager = new Manager(
+ $this->publicKeyTokenProvider
+ );
+ }
+
+ public function testGenerateToken(): void {
+ $token = new PublicKeyToken();
+
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('generateToken')
+ ->with(
+ 'token',
+ 'uid',
+ 'loginName',
+ 'password',
+ 'name',
+ IToken::TEMPORARY_TOKEN,
+ IToken::REMEMBER
+ )->willReturn($token);
+
+ $actual = $this->manager->generateToken(
+ 'token',
+ 'uid',
+ 'loginName',
+ 'password',
+ 'name',
+ IToken::TEMPORARY_TOKEN,
+ IToken::REMEMBER
+ );
+
+ $this->assertSame($token, $actual);
+ }
+
+ public function testGenerateConflictingToken(): void {
+ /** @var MockObject|UniqueConstraintViolationException $exception */
+ $exception = $this->createMock(UniqueConstraintViolationException::class);
+
+ $token = new PublicKeyToken();
+ $token->setUid('uid');
+
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('generateToken')
+ ->with(
+ 'token',
+ 'uid',
+ 'loginName',
+ 'password',
+ 'name',
+ IToken::TEMPORARY_TOKEN,
+ IToken::REMEMBER
+ )->willThrowException($exception);
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('token')
+ ->willReturn($token);
+
+ $actual = $this->manager->generateToken(
+ 'token',
+ 'uid',
+ 'loginName',
+ 'password',
+ 'name',
+ IToken::TEMPORARY_TOKEN,
+ IToken::REMEMBER
+ );
+
+ $this->assertSame($token, $actual);
+ }
+
+ public function testGenerateTokenTooLongName(): void {
+ $token = $this->createMock(IToken::class);
+ $token->method('getName')
+ ->willReturn(str_repeat('a', 120) . '…');
+
+
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('generateToken')
+ ->with(
+ 'token',
+ 'uid',
+ 'loginName',
+ 'password',
+ str_repeat('a', 120) . '…',
+ IToken::TEMPORARY_TOKEN,
+ IToken::REMEMBER
+ )->willReturn($token);
+
+ $actual = $this->manager->generateToken(
+ 'token',
+ 'uid',
+ 'loginName',
+ 'password',
+ str_repeat('a', 200),
+ IToken::TEMPORARY_TOKEN,
+ IToken::REMEMBER
+ );
+
+ $this->assertSame(121, mb_strlen($actual->getName()));
+ }
+
+ public static function tokenData(): array {
+ return [
+ [new PublicKeyToken()],
+ [IToken::class],
+ ];
+ }
+
+ protected function setNoCall(IToken $token) {
+ if (!($token instanceof PublicKeyToken)) {
+ $this->publicKeyTokenProvider->expects($this->never())
+ ->method($this->anything());
+ }
+ }
+
+ protected function setCall(IToken $token, string $function, $return = null) {
+ if ($token instanceof PublicKeyToken) {
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method($function)
+ ->with($token)
+ ->willReturn($return);
+ }
+ }
+
+ protected function setException(IToken $token) {
+ if (!($token instanceof PublicKeyToken)) {
+ $this->expectException(InvalidTokenException::class);
+ }
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('tokenData')]
+ public function testUpdateToken(IToken|string $token): void {
+ if (is_string($token)) {
+ $token = $this->createMock($token);
+ }
+
+ $this->setNoCall($token);
+ $this->setCall($token, 'updateToken');
+ $this->setException($token);
+
+ $this->manager->updateToken($token);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('tokenData')]
+ public function testUpdateTokenActivity(IToken|string $token): void {
+ if (is_string($token)) {
+ $token = $this->createMock($token);
+ }
+
+ $this->setNoCall($token);
+ $this->setCall($token, 'updateTokenActivity');
+ $this->setException($token);
+
+ $this->manager->updateTokenActivity($token);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('tokenData')]
+ public function testGetPassword(IToken|string $token): void {
+ if (is_string($token)) {
+ $token = $this->createMock($token);
+ }
+
+ $this->setNoCall($token);
+ $this->setCall($token, 'getPassword', 'password');
+ $this->setException($token);
+
+ $result = $this->manager->getPassword($token, 'tokenId', 'password');
+
+ $this->assertSame('password', $result);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('tokenData')]
+ public function testSetPassword(IToken|string $token): void {
+ if (is_string($token)) {
+ $token = $this->createMock($token);
+ }
+
+ $this->setNoCall($token);
+ $this->setCall($token, 'setPassword');
+ $this->setException($token);
+
+ $this->manager->setPassword($token, 'tokenId', 'password');
+ }
+
+ public function testInvalidateTokens(): void {
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('invalidateToken')
+ ->with('token');
+
+ $this->manager->invalidateToken('token');
+ }
+
+ public function testInvalidateTokenById(): void {
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('invalidateTokenById')
+ ->with('uid', 42);
+
+ $this->manager->invalidateTokenById('uid', 42);
+ }
+
+ public function testInvalidateOldTokens(): void {
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('invalidateOldTokens');
+
+ $this->manager->invalidateOldTokens();
+ }
+
+ public function testInvalidateLastUsedBefore(): void {
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('invalidateLastUsedBefore')
+ ->with('user', 946684800);
+
+ $this->manager->invalidateLastUsedBefore('user', 946684800);
+ }
+
+ public function testGetTokenByUser(): void {
+ $t1 = new PublicKeyToken();
+ $t2 = new PublicKeyToken();
+
+ $this->publicKeyTokenProvider
+ ->method('getTokenByUser')
+ ->willReturn([$t1, $t2]);
+
+ $result = $this->manager->getTokenByUser('uid');
+
+ $this->assertEquals([$t1, $t2], $result);
+ }
+
+ public function testRenewSessionTokenPublicKey(): void {
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('renewSessionToken')
+ ->with('oldId', 'newId');
+
+ $this->manager->renewSessionToken('oldId', 'newId');
+ }
+
+ public function testRenewSessionInvalid(): void {
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('renewSessionToken')
+ ->with('oldId', 'newId')
+ ->willThrowException(new InvalidTokenException());
+
+ $this->expectException(InvalidTokenException::class);
+ $this->manager->renewSessionToken('oldId', 'newId');
+ }
+
+ public function testGetTokenByIdPublicKey(): void {
+ $token = $this->createMock(IToken::class);
+
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('getTokenById')
+ ->with(42)
+ ->willReturn($token);
+
+ $this->assertSame($token, $this->manager->getTokenById(42));
+ }
+
+ public function testGetTokenByIdInvalid(): void {
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('getTokenById')
+ ->with(42)
+ ->willThrowException(new InvalidTokenException());
+
+ $this->expectException(InvalidTokenException::class);
+ $this->manager->getTokenById(42);
+ }
+
+ public function testGetTokenPublicKey(): void {
+ $token = new PublicKeyToken();
+
+ $this->publicKeyTokenProvider
+ ->method('getToken')
+ ->with('tokenId')
+ ->willReturn($token);
+
+ $this->assertSame($token, $this->manager->getToken('tokenId'));
+ }
+
+ public function testGetTokenInvalid(): void {
+ $this->publicKeyTokenProvider
+ ->method('getToken')
+ ->with('tokenId')
+ ->willThrowException(new InvalidTokenException());
+
+ $this->expectException(InvalidTokenException::class);
+ $this->manager->getToken('tokenId');
+ }
+
+ public function testRotateInvalid(): void {
+ $this->expectException(InvalidTokenException::class);
+ $this->manager->rotate($this->createMock(IToken::class), 'oldId', 'newId');
+ }
+
+ public function testRotatePublicKey(): void {
+ $token = new PublicKeyToken();
+
+ $this->publicKeyTokenProvider
+ ->method('rotate')
+ ->with($token, 'oldId', 'newId')
+ ->willReturn($token);
+
+ $this->assertSame($token, $this->manager->rotate($token, 'oldId', 'newId'));
+ }
+
+ public function testMarkPasswordInvalidPublicKey(): void {
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('markPasswordInvalid')
+ ->with($token, 'tokenId');
+
+ $this->manager->markPasswordInvalid($token, 'tokenId');
+ }
+
+ public function testMarkPasswordInvalidInvalidToken(): void {
+ $this->expectException(InvalidTokenException::class);
+
+ $this->manager->markPasswordInvalid($this->createMock(IToken::class), 'tokenId');
+ }
+
+ public function testUpdatePasswords(): void {
+ $this->publicKeyTokenProvider->expects($this->once())
+ ->method('updatePasswords')
+ ->with('uid', 'pass');
+
+ $this->manager->updatePasswords('uid', 'pass');
+ }
+
+ public function testInvalidateTokensOfUserNoClientName(): void {
+ $t1 = new PublicKeyToken();
+ $t2 = new PublicKeyToken();
+ $t1->setId(123);
+ $t2->setId(456);
+
+ $this->publicKeyTokenProvider
+ ->expects($this->once())
+ ->method('getTokenByUser')
+ ->with('theUser')
+ ->willReturn([$t1, $t2]);
+
+ $calls = [
+ ['theUser', 123],
+ ['theUser', 456],
+ ];
+ $this->publicKeyTokenProvider
+ ->expects($this->exactly(2))
+ ->method('invalidateTokenById')
+ ->willReturnCallback(function () use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, func_get_args());
+ });
+ $this->manager->invalidateTokensOfUser('theUser', null);
+ }
+
+ public function testInvalidateTokensOfUserClientNameGiven(): void {
+ $t1 = new PublicKeyToken();
+ $t2 = new PublicKeyToken();
+ $t3 = new PublicKeyToken();
+ $t1->setId(123);
+ $t1->setName('Firefox session');
+ $t2->setId(456);
+ $t2->setName('My Client Name');
+ $t3->setId(789);
+ $t3->setName('mobile client');
+
+ $this->publicKeyTokenProvider
+ ->expects($this->once())
+ ->method('getTokenByUser')
+ ->with('theUser')
+ ->willReturn([$t1, $t2, $t3]);
+ $this->publicKeyTokenProvider
+ ->expects($this->once())
+ ->method('invalidateTokenById')
+ ->with('theUser', 456);
+ $this->manager->invalidateTokensOfUser('theUser', 'My Client Name');
+ }
+}
diff --git a/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php
new file mode 100644
index 00000000000..d1585dadc26
--- /dev/null
+++ b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php
@@ -0,0 +1,265 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Token;
+
+use OC\Authentication\Token\PublicKeyToken;
+use OC\Authentication\Token\PublicKeyTokenMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\Authentication\Token\IToken;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\IUser;
+use OCP\Server;
+use Test\TestCase;
+
+/**
+ * @group DB
+ */
+class PublicKeyTokenMapperTest extends TestCase {
+ /** @var PublicKeyTokenMapper */
+ private $mapper;
+
+ /** @var IDBConnection */
+ private $dbConnection;
+
+ /** @var int */
+ private $time;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->dbConnection = Server::get(IDBConnection::class);
+ $this->time = time();
+ $this->resetDatabase();
+
+ $this->mapper = new PublicKeyTokenMapper($this->dbConnection);
+ }
+
+ private function resetDatabase() {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->delete('authtoken')->execute();
+ $qb->insert('authtoken')->values([
+ 'uid' => $qb->createNamedParameter('user1'),
+ 'login_name' => $qb->createNamedParameter('User1'),
+ 'password' => $qb->createNamedParameter('a75c7116460c082912d8f6860a850904|3nz5qbG1nNSLLi6V|c55365a0e54cfdfac4a175bcf11a7612aea74492277bba6e5d96a24497fa9272488787cb2f3ad34d8b9b8060934fce02f008d371df3ff3848f4aa61944851ff0'),
+ 'name' => $qb->createNamedParameter('Firefox on Linux'),
+ 'token' => $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'),
+ 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
+ 'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+ 'last_check' => $this->time - 60 * 10, // 10mins ago
+ 'public_key' => $qb->createNamedParameter('public key'),
+ 'private_key' => $qb->createNamedParameter('private key'),
+ 'version' => $qb->createNamedParameter(2),
+ ])->execute();
+ $qb->insert('authtoken')->values([
+ 'uid' => $qb->createNamedParameter('user2'),
+ 'login_name' => $qb->createNamedParameter('User2'),
+ 'password' => $qb->createNamedParameter('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f'),
+ 'name' => $qb->createNamedParameter('Firefox on Android'),
+ 'token' => $qb->createNamedParameter('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'),
+ 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
+ 'last_activity' => $qb->createNamedParameter($this->time - 60 * 60 * 24 * 3, IQueryBuilder::PARAM_INT), // Three days ago
+ 'last_check' => $this->time - 10, // 10secs ago
+ 'public_key' => $qb->createNamedParameter('public key'),
+ 'private_key' => $qb->createNamedParameter('private key'),
+ 'version' => $qb->createNamedParameter(2),
+ ])->execute();
+ $qb->insert('authtoken')->values([
+ 'uid' => $qb->createNamedParameter('user1'),
+ 'login_name' => $qb->createNamedParameter('User1'),
+ 'password' => $qb->createNamedParameter('063de945d6f6b26862d9b6f40652f2d5|DZ/z520tfdXPtd0T|395f6b89be8d9d605e409e20b9d9abe477fde1be38a3223f9e508f979bf906e50d9eaa4dca983ca4fb22a241eb696c3f98654e7775f78c4caf13108f98642b53'),
+ 'name' => $qb->createNamedParameter('Iceweasel on Linux'),
+ 'token' => $qb->createNamedParameter('47af8697ba590fb82579b5f1b3b6e8066773a62100abbe0db09a289a62f5d980dc300fa3d98b01d7228468d1ab05c1aa14c8d14bd5b6eee9cdf1ac14864680c3'),
+ 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
+ 'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+ 'last_check' => $this->time - 60 * 10, // 10mins ago
+ 'public_key' => $qb->createNamedParameter('public key'),
+ 'private_key' => $qb->createNamedParameter('private key'),
+ 'version' => $qb->createNamedParameter(2),
+ ])->execute();
+ $qb->insert('authtoken')->values([
+ 'uid' => $qb->createNamedParameter('user3'),
+ 'login_name' => $qb->createNamedParameter('User3'),
+ 'password' => $qb->createNamedParameter('063de945d6f6b26862d9b6f40652f2d5|DZ/z520tfdXPtd0T|395f6b89be8d9d605e409e20b9d9abe477fde1be38a3223f9e508f979bf906e50d9eaa4dca983ca4fb22a241eb696c3f98654e7775f78c4caf13108f98642b53'),
+ 'name' => $qb->createNamedParameter('Iceweasel on Linux'),
+ 'token' => $qb->createNamedParameter('6d9a290d239d09f2cc33a03cc54cccd46f7dc71630dcc27d39214824bd3e093f1feb4e2b55eb159d204caa15dee9556c202a5aa0b9d67806c3f4ec2cde11af67'),
+ 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
+ 'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+ 'last_check' => $this->time - 60 * 10, // 10mins ago
+ 'public_key' => $qb->createNamedParameter('public key'),
+ 'private_key' => $qb->createNamedParameter('private key'),
+ 'version' => $qb->createNamedParameter(2),
+ 'password_invalid' => $qb->createNamedParameter(1),
+ ])->execute();
+ $qb->insert('authtoken')->values([
+ 'uid' => $qb->createNamedParameter('user3'),
+ 'login_name' => $qb->createNamedParameter('User3'),
+ 'password' => $qb->createNamedParameter('063de945d6f6b26862d9b6f40652f2d5|DZ/z520tfdXPtd0T|395f6b89be8d9d605e409e20b9d9abe477fde1be38a3223f9e508f979bf906e50d9eaa4dca983ca4fb22a241eb696c3f98654e7775f78c4caf13108f98642b53'),
+ 'name' => $qb->createNamedParameter('Iceweasel on Linux'),
+ 'token' => $qb->createNamedParameter('84c5808c6445b6d65b8aa5b03840f09b27de603f0fb970906fb14ea4b115b7bf5ec53fada5c093fe46afdcd7bbc9617253a4d105f7dfb32719f9973d72412f31'),
+ 'type' => $qb->createNamedParameter(IToken::PERMANENT_TOKEN),
+ 'last_activity' => $qb->createNamedParameter($this->time - 60 * 3, IQueryBuilder::PARAM_INT), // Three minutes ago
+ 'last_check' => $this->time - 60 * 10, // 10mins ago
+ 'public_key' => $qb->createNamedParameter('public key'),
+ 'private_key' => $qb->createNamedParameter('private key'),
+ 'version' => $qb->createNamedParameter(2),
+ 'password_invalid' => $qb->createNamedParameter(1),
+ ])->execute();
+ }
+
+ private function getNumberOfTokens() {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $result = $qb->select($qb->func()->count('*', 'count'))
+ ->from('authtoken')
+ ->execute()
+ ->fetch();
+ return (int)$result['count'];
+ }
+
+ public function testInvalidate(): void {
+ $token = '9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206';
+
+ $this->mapper->invalidate($token);
+
+ $this->assertSame(4, $this->getNumberOfTokens());
+ }
+
+ public function testInvalidateInvalid(): void {
+ $token = 'youwontfindthisoneinthedatabase';
+
+ $this->mapper->invalidate($token);
+
+ $this->assertSame(5, $this->getNumberOfTokens());
+ }
+
+ public function testInvalidateOld(): void {
+ $olderThan = $this->time - 60 * 60; // One hour
+
+ $this->mapper->invalidateOld($olderThan);
+
+ $this->assertSame(4, $this->getNumberOfTokens());
+ }
+
+ public function testInvalidateLastUsedBefore(): void {
+ $before = $this->time - 60 * 2; // Two minutes
+
+ $this->mapper->invalidateLastUsedBefore('user3', $before);
+
+ $this->assertSame(4, $this->getNumberOfTokens());
+ }
+
+ public function testGetToken(): void {
+ $token = new PublicKeyToken();
+ $token->setUid('user2');
+ $token->setLoginName('User2');
+ $token->setPassword('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f');
+ $token->setName('Firefox on Android');
+ $token->setToken('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b');
+ $token->setType(IToken::TEMPORARY_TOKEN);
+ $token->setRemember(IToken::DO_NOT_REMEMBER);
+ $token->setLastActivity($this->time - 60 * 60 * 24 * 3);
+ $token->setLastCheck($this->time - 10);
+ $token->setPublicKey('public key');
+ $token->setPrivateKey('private key');
+ $token->setVersion(PublicKeyToken::VERSION);
+
+ $dbToken = $this->mapper->getToken($token->getToken());
+
+ $token->setId($dbToken->getId()); // We don't know the ID
+ $token->resetUpdatedFields();
+
+ $this->assertEquals($token, $dbToken);
+ }
+
+
+ public function testGetInvalidToken(): void {
+ $this->expectException(DoesNotExistException::class);
+
+ $token = 'thisisaninvalidtokenthatisnotinthedatabase';
+
+ $this->mapper->getToken($token);
+ }
+
+ public function testGetTokenById(): void {
+ $token = new PublicKeyToken();
+ $token->setUid('user2');
+ $token->setLoginName('User2');
+ $token->setPassword('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f');
+ $token->setName('Firefox on Android');
+ $token->setToken('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b');
+ $token->setType(IToken::TEMPORARY_TOKEN);
+ $token->setRemember(IToken::DO_NOT_REMEMBER);
+ $token->setLastActivity($this->time - 60 * 60 * 24 * 3);
+ $token->setLastCheck($this->time - 10);
+ $token->setPublicKey('public key');
+ $token->setPrivateKey('private key');
+ $token->setVersion(PublicKeyToken::VERSION);
+
+ $dbToken = $this->mapper->getToken($token->getToken());
+ $token->setId($dbToken->getId()); // We don't know the ID
+ $token->resetUpdatedFields();
+
+ $dbToken = $this->mapper->getTokenById($token->getId());
+ $this->assertEquals($token, $dbToken);
+ }
+
+
+ public function testGetTokenByIdNotFound(): void {
+ $this->expectException(DoesNotExistException::class);
+
+ $this->mapper->getTokenById(-1);
+ }
+
+
+ public function testGetInvalidTokenById(): void {
+ $this->expectException(DoesNotExistException::class);
+
+ $id = '42';
+
+ $this->mapper->getToken($id);
+ }
+
+ public function testGetTokenByUser(): void {
+ $this->assertCount(2, $this->mapper->getTokenByUser('user1'));
+ }
+
+ public function testGetTokenByUserNotFound(): void {
+ $this->assertCount(0, $this->mapper->getTokenByUser('user1000'));
+ }
+
+ public function testGetById(): void {
+ /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
+ $user = $this->createMock(IUser::class);
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('id')
+ ->from('authtoken')
+ ->where($qb->expr()->eq('token', $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206')));
+ $result = $qb->execute();
+ $id = $result->fetch()['id'];
+
+ $token = $this->mapper->getTokenById((int)$id);
+ $this->assertEquals('user1', $token->getUID());
+ }
+
+ public function testDeleteByName(): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('name')
+ ->from('authtoken')
+ ->where($qb->expr()->eq('token', $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206')));
+ $result = $qb->execute();
+ $name = $result->fetch()['name'];
+ $this->mapper->deleteByName($name);
+ $this->assertEquals(4, $this->getNumberOfTokens());
+ }
+
+ public function testHasExpiredTokens(): void {
+ $this->assertFalse($this->mapper->hasExpiredTokens('user1'));
+ $this->assertTrue($this->mapper->hasExpiredTokens('user3'));
+ }
+}
diff --git a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php
new file mode 100644
index 00000000000..7e7f949965f
--- /dev/null
+++ b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php
@@ -0,0 +1,645 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Token;
+
+use OC\Authentication\Exceptions\ExpiredTokenException;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Exceptions\PasswordlessTokenException;
+use OC\Authentication\Token\PublicKeyToken;
+use OC\Authentication\Token\PublicKeyTokenMapper;
+use OC\Authentication\Token\PublicKeyTokenProvider;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\Token\IToken;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Security\ICrypto;
+use OCP\Security\IHasher;
+use OCP\Server;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class PublicKeyTokenProviderTest extends TestCase {
+ /** @var PublicKeyTokenProvider|\PHPUnit\Framework\MockObject\MockObject */
+ private $tokenProvider;
+ /** @var PublicKeyTokenMapper|\PHPUnit\Framework\MockObject\MockObject */
+ private $mapper;
+ /** @var IHasher|\PHPUnit\Framework\MockObject\MockObject */
+ private $hasher;
+ /** @var ICrypto */
+ private $crypto;
+ /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
+ private $config;
+ /** @var IDBConnection|MockObject */
+ private IDBConnection $db;
+ /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
+ private $logger;
+ /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
+ private $timeFactory;
+ /** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */
+ private $cacheFactory;
+ /** @var int */
+ private $time;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->mapper = $this->createMock(PublicKeyTokenMapper::class);
+ $this->hasher = Server::get(IHasher::class);
+ $this->crypto = Server::get(ICrypto::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->config->method('getSystemValue')
+ ->willReturnMap([
+ ['openssl', [], []],
+ ]);
+ $this->config->method('getSystemValueString')
+ ->willReturnMap([
+ ['secret', '', '1f4h9s'],
+ ]);
+ $this->db = $this->createMock(IDBConnection::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->time = 1313131;
+ $this->timeFactory->method('getTime')
+ ->willReturn($this->time);
+ $this->cacheFactory = $this->createMock(ICacheFactory::class);
+
+ $this->tokenProvider = new PublicKeyTokenProvider(
+ $this->mapper,
+ $this->crypto,
+ $this->config,
+ $this->db,
+ $this->logger,
+ $this->timeFactory,
+ $this->hasher,
+ $this->cacheFactory,
+ );
+ }
+
+ public function testGenerateToken(): void {
+ $token = 'tokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = 'passme';
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $this->assertInstanceOf(PublicKeyToken::class, $actual);
+ $this->assertSame($uid, $actual->getUID());
+ $this->assertSame($user, $actual->getLoginName());
+ $this->assertSame($name, $actual->getName());
+ $this->assertSame(IToken::DO_NOT_REMEMBER, $actual->getRemember());
+ $this->assertSame($password, $this->tokenProvider->getPassword($actual, $token));
+ }
+
+ public function testGenerateTokenNoPassword(): void {
+ $token = 'tokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = 'passme';
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, false],
+ ]);
+ $this->expectException(PasswordlessTokenException::class);
+
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $this->assertInstanceOf(PublicKeyToken::class, $actual);
+ $this->assertSame($uid, $actual->getUID());
+ $this->assertSame($user, $actual->getLoginName());
+ $this->assertSame($name, $actual->getName());
+ $this->assertSame(IToken::DO_NOT_REMEMBER, $actual->getRemember());
+ $this->tokenProvider->getPassword($actual, $token);
+ }
+
+ public function testGenerateTokenLongPassword(): void {
+ $token = 'tokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = '';
+ for ($i = 0; $i < 500; $i++) {
+ $password .= 'e';
+ }
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+ $this->expectException(\RuntimeException::class);
+
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+ }
+
+ public function testGenerateTokenInvalidName(): void {
+ $token = 'tokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = 'passme';
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'
+ . 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'
+ . 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'
+ . 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $this->assertInstanceOf(PublicKeyToken::class, $actual);
+ $this->assertSame($uid, $actual->getUID());
+ $this->assertSame($user, $actual->getLoginName());
+ $this->assertSame('User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12User-Agent: Mozill…', $actual->getName());
+ $this->assertSame(IToken::DO_NOT_REMEMBER, $actual->getRemember());
+ $this->assertSame($password, $this->tokenProvider->getPassword($actual, $token));
+ }
+
+ public function testUpdateToken(): void {
+ $tk = new PublicKeyToken();
+ $this->mapper->expects($this->once())
+ ->method('updateActivity')
+ ->with($tk, $this->time);
+ $tk->setLastActivity($this->time - 200);
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+
+ $this->tokenProvider->updateTokenActivity($tk);
+
+ $this->assertEquals($this->time, $tk->getLastActivity());
+ }
+
+ public function testUpdateTokenDebounce(): void {
+ $tk = new PublicKeyToken();
+ $this->config->method('getSystemValueInt')
+ ->willReturnCallback(function ($value, $default) {
+ return $default;
+ });
+ $tk->setLastActivity($this->time - 30);
+
+ $this->mapper->expects($this->never())
+ ->method('updateActivity')
+ ->with($tk, $this->time);
+
+ $this->tokenProvider->updateTokenActivity($tk);
+ }
+
+ public function testGetTokenByUser(): void {
+ $this->mapper->expects($this->once())
+ ->method('getTokenByUser')
+ ->with('uid')
+ ->willReturn(['token']);
+
+ $this->assertEquals(['token'], $this->tokenProvider->getTokenByUser('uid'));
+ }
+
+ public function testGetPassword(): void {
+ $token = 'tokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = 'passme';
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $this->assertSame($password, $this->tokenProvider->getPassword($actual, $token));
+ }
+
+
+ public function testGetPasswordPasswordLessToken(): void {
+ $this->expectException(PasswordlessTokenException::class);
+
+ $token = 'token1234';
+ $tk = new PublicKeyToken();
+ $tk->setPassword(null);
+
+ $this->tokenProvider->getPassword($tk, $token);
+ }
+
+
+ public function testGetPasswordInvalidToken(): void {
+ $this->expectException(InvalidTokenException::class);
+
+ $token = 'tokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = 'passme';
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $this->tokenProvider->getPassword($actual, 'wrongtoken');
+ }
+
+ public function testSetPassword(): void {
+ $token = 'tokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = 'passme';
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $this->mapper->method('getTokenByUser')
+ ->with('user')
+ ->willReturn([$actual]);
+
+ $newpass = 'newpass';
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->with($this->callback(function ($token) use ($newpass) {
+ return $newpass === $this->tokenProvider->getPassword($token, 'tokentokentokentokentoken');
+ }));
+
+
+ $this->tokenProvider->setPassword($actual, $token, $newpass);
+
+ $this->assertSame($newpass, $this->tokenProvider->getPassword($actual, 'tokentokentokentokentoken'));
+ }
+
+
+ public function testSetPasswordInvalidToken(): void {
+ $this->expectException(InvalidTokenException::class);
+
+ $token = $this->createMock(IToken::class);
+ $tokenId = 'token123';
+ $password = '123456';
+
+ $this->tokenProvider->setPassword($token, $tokenId, $password);
+ }
+
+ public function testInvalidateToken(): void {
+ $calls = [
+ [hash('sha512', 'token7' . '1f4h9s')],
+ [hash('sha512', 'token7')]
+ ];
+
+ $this->mapper->expects($this->exactly(2))
+ ->method('invalidate')
+ ->willReturnCallback(function () use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, func_get_args());
+ });
+
+ $this->tokenProvider->invalidateToken('token7');
+ }
+
+ public function testInvalidateTokenById(): void {
+ $id = 123;
+
+ $this->mapper->expects($this->once())
+ ->method('getTokenById')
+ ->with($id);
+
+ $this->tokenProvider->invalidateTokenById('uid', $id);
+ }
+
+ public function testInvalidateOldTokens(): void {
+ $defaultSessionLifetime = 60 * 60 * 24;
+ $defaultRememberMeLifetime = 60 * 60 * 24 * 15;
+ $wipeTokenLifetime = 60 * 60 * 24 * 60;
+ $this->config->expects($this->exactly(4))
+ ->method('getSystemValueInt')
+ ->willReturnMap([
+ ['session_lifetime', $defaultSessionLifetime, 150],
+ ['remember_login_cookie_lifetime', $defaultRememberMeLifetime, 300],
+ ['token_auth_wipe_token_retention', $wipeTokenLifetime, 500],
+ ['token_auth_token_retention', 60 * 60 * 24 * 365, 800],
+ ]);
+
+ $calls = [
+ [$this->time - 150, IToken::TEMPORARY_TOKEN, IToken::DO_NOT_REMEMBER],
+ [$this->time - 300, IToken::TEMPORARY_TOKEN, IToken::REMEMBER],
+ [$this->time - 500, IToken::WIPE_TOKEN, null],
+ [$this->time - 800, IToken::PERMANENT_TOKEN, null],
+ ];
+ $this->mapper->expects($this->exactly(4))
+ ->method('invalidateOld')
+ ->willReturnCallback(function () use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, func_get_args());
+ });
+
+ $this->tokenProvider->invalidateOldTokens();
+ }
+
+ public function testInvalidateLastUsedBefore(): void {
+ $this->mapper->expects($this->once())
+ ->method('invalidateLastUsedBefore')
+ ->with('user', 946684800);
+
+ $this->tokenProvider->invalidateLastUsedBefore('user', 946684800);
+ }
+
+ public function testRenewSessionTokenWithoutPassword(): void {
+ $token = 'oldIdtokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = null;
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+
+ $oldToken = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $this->mapper
+ ->expects($this->once())
+ ->method('getToken')
+ ->with(hash('sha512', 'oldIdtokentokentokentoken' . '1f4h9s'))
+ ->willReturn($oldToken);
+ $this->mapper
+ ->expects($this->once())
+ ->method('insert')
+ ->with($this->callback(function (PublicKeyToken $token) use ($user, $uid, $name) {
+ return $token->getUID() === $uid
+ && $token->getLoginName() === $user
+ && $token->getName() === $name
+ && $token->getType() === IToken::DO_NOT_REMEMBER
+ && $token->getLastActivity() === $this->time
+ && $token->getPassword() === null;
+ }));
+ $this->mapper
+ ->expects($this->once())
+ ->method('delete')
+ ->with($this->callback(function ($token) use ($oldToken) {
+ return $token === $oldToken;
+ }));
+
+ $this->tokenProvider->renewSessionToken('oldIdtokentokentokentoken', 'newIdtokentokentokentoken');
+ }
+
+ public function testRenewSessionTokenWithPassword(): void {
+ $token = 'oldIdtokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = 'password';
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+ $oldToken = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $this->mapper
+ ->expects($this->once())
+ ->method('getToken')
+ ->with(hash('sha512', 'oldIdtokentokentokentoken' . '1f4h9s'))
+ ->willReturn($oldToken);
+ $this->mapper
+ ->expects($this->once())
+ ->method('insert')
+ ->with($this->callback(function (PublicKeyToken $token) use ($user, $uid, $name): bool {
+ return $token->getUID() === $uid
+ && $token->getLoginName() === $user
+ && $token->getName() === $name
+ && $token->getType() === IToken::DO_NOT_REMEMBER
+ && $token->getLastActivity() === $this->time
+ && $token->getPassword() !== null
+ && $this->tokenProvider->getPassword($token, 'newIdtokentokentokentoken') === 'password';
+ }));
+ $this->mapper
+ ->expects($this->once())
+ ->method('delete')
+ ->with($this->callback(function ($token) use ($oldToken): bool {
+ return $token === $oldToken;
+ }));
+
+ $this->tokenProvider->renewSessionToken('oldIdtokentokentokentoken', 'newIdtokentokentokentoken');
+ }
+
+ public function testGetToken(): void {
+ $token = new PublicKeyToken();
+
+ $this->config->method('getSystemValue')
+ ->with('secret')
+ ->willReturn('mysecret');
+
+ $this->mapper->method('getToken')
+ ->with(
+ $this->callback(function (string $token) {
+ return hash('sha512', 'unhashedTokentokentokentokentoken' . '1f4h9s') === $token;
+ })
+ )->willReturn($token);
+
+ $this->assertSame($token, $this->tokenProvider->getToken('unhashedTokentokentokentokentoken'));
+ }
+
+ public function testGetInvalidToken(): void {
+ $this->expectException(InvalidTokenException::class);
+
+ $calls = [
+ 'unhashedTokentokentokentokentoken' . '1f4h9s',
+ 'unhashedTokentokentokentokentoken',
+ ];
+ $this->mapper->expects($this->exactly(2))
+ ->method('getToken')
+ ->willReturnCallback(function (string $token) use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals(hash('sha512', $expected), $token);
+ throw new DoesNotExistException('nope');
+ });
+
+ $this->tokenProvider->getToken('unhashedTokentokentokentokentoken');
+ }
+
+ public function testGetExpiredToken(): void {
+ $token = 'tokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = 'passme';
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+ $actual->setExpires(42);
+
+ $this->mapper->method('getToken')
+ ->with(
+ $this->callback(function (string $token) {
+ return hash('sha512', 'tokentokentokentokentoken' . '1f4h9s') === $token;
+ })
+ )->willReturn($actual);
+
+ try {
+ $this->tokenProvider->getToken('tokentokentokentokentoken');
+ $this->fail();
+ } catch (ExpiredTokenException $e) {
+ $this->assertSame($actual, $e->getToken());
+ }
+ }
+
+ public function testGetTokenById(): void {
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $this->mapper->expects($this->once())
+ ->method('getTokenById')
+ ->with($this->equalTo(42))
+ ->willReturn($token);
+
+ $this->assertSame($token, $this->tokenProvider->getTokenById(42));
+ }
+
+ public function testGetInvalidTokenById(): void {
+ $this->expectException(InvalidTokenException::class);
+
+ $this->mapper->expects($this->once())
+ ->method('getTokenById')
+ ->with($this->equalTo(42))
+ ->willThrowException(new DoesNotExistException('nope'));
+
+ $this->tokenProvider->getTokenById(42);
+ }
+
+ public function testGetExpiredTokenById(): void {
+ $token = new PublicKeyToken();
+ $token->setExpires(42);
+
+ $this->mapper->expects($this->once())
+ ->method('getTokenById')
+ ->with($this->equalTo(42))
+ ->willReturn($token);
+
+ try {
+ $this->tokenProvider->getTokenById(42);
+ $this->fail();
+ } catch (ExpiredTokenException $e) {
+ $this->assertSame($token, $e->getToken());
+ }
+ }
+
+ public function testRotate(): void {
+ $token = 'oldtokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = 'password';
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $new = $this->tokenProvider->rotate($actual, 'oldtokentokentokentokentoken', 'newtokentokentokentokentoken');
+
+ $this->assertSame('password', $this->tokenProvider->getPassword($new, 'newtokentokentokentokentoken'));
+ }
+
+ public function testRotateNoPassword(): void {
+ $token = 'oldtokentokentokentokentoken';
+ $uid = 'user';
+ $user = 'User';
+ $password = null;
+ $name = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
+ $type = IToken::PERMANENT_TOKEN;
+
+ $actual = $this->tokenProvider->generateToken($token, $uid, $user, $password, $name, $type, IToken::DO_NOT_REMEMBER);
+
+ $oldPrivate = $actual->getPrivateKey();
+
+ $new = $this->tokenProvider->rotate($actual, 'oldtokentokentokentokentoken', 'newtokentokentokentokentoken');
+
+ $newPrivate = $new->getPrivateKey();
+
+ $this->assertNotSame($newPrivate, $oldPrivate);
+ $this->assertNull($new->getPassword());
+ }
+
+ public function testMarkPasswordInvalidInvalidToken(): void {
+ $token = $this->createMock(IToken::class);
+
+ $this->expectException(InvalidTokenException::class);
+
+ $this->tokenProvider->markPasswordInvalid($token, 'tokenId');
+ }
+
+ public function testMarkPasswordInvalid(): void {
+ $token = $this->createMock(PublicKeyToken::class);
+
+ $token->expects($this->once())
+ ->method('setPasswordInvalid')
+ ->with(true);
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->with($token);
+
+ $this->tokenProvider->markPasswordInvalid($token, 'tokenId');
+ }
+
+ public function testUpdatePasswords(): void {
+ $uid = 'myUID';
+ $token1 = $this->tokenProvider->generateToken(
+ 'foobetokentokentokentoken',
+ $uid,
+ $uid,
+ 'bar',
+ 'random1',
+ IToken::PERMANENT_TOKEN,
+ IToken::REMEMBER);
+ $token2 = $this->tokenProvider->generateToken(
+ 'foobartokentokentokentoken',
+ $uid,
+ $uid,
+ 'bar',
+ 'random2',
+ IToken::PERMANENT_TOKEN,
+ IToken::REMEMBER);
+ $this->config->method('getSystemValueBool')
+ ->willReturnMap([
+ ['auth.storeCryptedPassword', true, true],
+ ]);
+
+ $this->mapper->method('hasExpiredTokens')
+ ->with($uid)
+ ->willReturn(true);
+ $this->mapper->expects($this->once())
+ ->method('getTokenByUser')
+ ->with($uid)
+ ->willReturn([$token1, $token2]);
+ $this->mapper->expects($this->exactly(2))
+ ->method('update')
+ ->with($this->callback(function (PublicKeyToken $t) use ($token1, $token2) {
+ return $t === $token1 || $t === $token2;
+ }));
+
+ $this->tokenProvider->updatePasswords($uid, 'bar2');
+ }
+}
diff --git a/tests/lib/Authentication/Token/PublicKeyTokenTest.php b/tests/lib/Authentication/Token/PublicKeyTokenTest.php
new file mode 100644
index 00000000000..5f5f29c865f
--- /dev/null
+++ b/tests/lib/Authentication/Token/PublicKeyTokenTest.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Token;
+
+use OC\Authentication\Token\PublicKeyToken;
+use OCP\Authentication\Token\IToken;
+use Test\TestCase;
+
+class PublicKeyTokenTest extends TestCase {
+ public function testSetScopeAsArray(): void {
+ $scope = [IToken::SCOPE_FILESYSTEM => false];
+ $token = new PublicKeyToken();
+ $token->setScope($scope);
+ $this->assertEquals(json_encode($scope), $token->getScope());
+ $this->assertEquals($scope, $token->getScopeAsArray());
+ }
+
+ public function testDefaultScope(): void {
+ $scope = [IToken::SCOPE_FILESYSTEM => true];
+ $token = new PublicKeyToken();
+ $this->assertEquals($scope, $token->getScopeAsArray());
+ }
+}
diff --git a/tests/lib/Authentication/Token/RemoteWipeTest.php b/tests/lib/Authentication/Token/RemoteWipeTest.php
new file mode 100644
index 00000000000..ca09767c759
--- /dev/null
+++ b/tests/lib/Authentication/Token/RemoteWipeTest.php
@@ -0,0 +1,173 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Authentication\Token;
+
+use OC\Authentication\Events\RemoteWipeFinished;
+use OC\Authentication\Events\RemoteWipeStarted;
+use OC\Authentication\Exceptions\WipeTokenException;
+use OC\Authentication\Token\IProvider;
+use OC\Authentication\Token\IProvider as ITokenProvider;
+use OC\Authentication\Token\IToken;
+use OC\Authentication\Token\IWipeableToken;
+use OC\Authentication\Token\RemoteWipe;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IUser;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class RemoteWipeTest extends TestCase {
+ /** @var ITokenProvider|MockObject */
+ private $tokenProvider;
+
+ /** @var IEventDispatcher|MockObject */
+ private $eventDispatcher;
+
+ /** @var LoggerInterface|MockObject */
+ private $logger;
+
+ /** @var RemoteWipe */
+ private $remoteWipe;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->tokenProvider = $this->createMock(IProvider::class);
+ $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->remoteWipe = new RemoteWipe(
+ $this->tokenProvider,
+ $this->eventDispatcher,
+ $this->logger
+ );
+ }
+
+ public function testMarkNonWipableTokenForWipe(): void {
+ $token = $this->createMock(IToken::class);
+ $result = $this->remoteWipe->markTokenForWipe($token);
+ $this->assertFalse($result);
+ }
+
+ public function testMarkTokenForWipe(): void {
+ $token = $this->createMock(IWipeableToken::class);
+ $token->expects($this->once())
+ ->method('wipe');
+
+ $this->tokenProvider->expects($this->once())
+ ->method('updateToken')
+ ->with($token);
+
+ $result = $this->remoteWipe->markTokenForWipe($token);
+ $this->assertTrue($result);
+ }
+
+ public function testMarkAllTokensForWipeNoWipeableToken(): void {
+ /** @var IUser|MockObject $user */
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('user123');
+ $token1 = $this->createMock(IToken::class);
+ $token2 = $this->createMock(IToken::class);
+ $this->tokenProvider->expects($this->once())
+ ->method('getTokenByUser')
+ ->with('user123')
+ ->willReturn([$token1, $token2]);
+
+ $result = $this->remoteWipe->markAllTokensForWipe($user);
+
+ $this->assertFalse($result);
+ }
+
+ public function testMarkAllTokensForWipe(): void {
+ /** @var IUser|MockObject $user */
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('user123');
+ $token1 = $this->createMock(IToken::class);
+ $token2 = $this->createMock(IWipeableToken::class);
+ $this->tokenProvider->expects($this->once())
+ ->method('getTokenByUser')
+ ->with('user123')
+ ->willReturn([$token1, $token2]);
+ $token2->expects($this->once())
+ ->method('wipe');
+ $this->tokenProvider->expects($this->once())
+ ->method('updateToken')
+ ->with($token2);
+
+ $result = $this->remoteWipe->markAllTokensForWipe($user);
+
+ $this->assertTrue($result);
+ }
+
+ public function testStartWipingNotAWipeToken(): void {
+ $token = $this->createMock(IToken::class);
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('tk1')
+ ->willReturn($token);
+ $this->eventDispatcher->expects($this->never())
+ ->method('dispatch');
+
+ $result = $this->remoteWipe->start('tk1');
+
+ $this->assertFalse($result);
+ }
+
+ public function testStartWiping(): void {
+ $token = $this->createMock(IToken::class);
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('tk1')
+ ->willThrowException(new WipeTokenException($token));
+ $this->eventDispatcher->expects($this->once())
+ ->method('dispatch');
+ $this->eventDispatcher->expects($this->once())
+ ->method('dispatch')
+ ->with(RemoteWipeStarted::class, $this->equalTo(new RemoteWipeStarted($token)));
+
+ $result = $this->remoteWipe->start('tk1');
+
+ $this->assertTrue($result);
+ }
+
+ public function testFinishWipingNotAWipeToken(): void {
+ $token = $this->createMock(IToken::class);
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('tk1')
+ ->willReturn($token);
+ $this->eventDispatcher->expects($this->never())
+ ->method('dispatch');
+
+ $result = $this->remoteWipe->finish('tk1');
+
+ $this->assertFalse($result);
+ }
+
+ public function startFinishWiping() {
+ $token = $this->createMock(IToken::class);
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('tk1')
+ ->willThrowException(new WipeTokenException($token));
+ $this->eventDispatcher->expects($this->once())
+ ->method('dispatch');
+ $this->tokenProvider->expects($this->once())
+ ->method('invalidateToken')
+ ->with($token);
+ $this->eventDispatcher->expects($this->once())
+ ->method('dispatch')
+ ->with(RemoteWipeFinished::class, $this->equalTo(new RemoteWipeFinished($token)));
+
+ $result = $this->remoteWipe->finish('tk1');
+
+ $this->assertTrue($result);
+ }
+}
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');
+ }
+}