diff --git a/core/Controller/TwoFactorChallengeController.php b/core/Controller/TwoFactorChallengeController.php
index 7405e66cdf..e2a0b5423a 100644
--- a/core/Controller/TwoFactorChallengeController.php
+++ b/core/Controller/TwoFactorChallengeController.php
@@ -32,6 +32,7 @@ use OC_Util;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\Authentication\TwoFactorAuth\IProvidesCustomCSP;
use OCP\Authentication\TwoFactorAuth\TwoFactorException;
@@ -107,6 +108,7 @@ class TwoFactorChallengeController extends Controller {
$providerSet = $this->twoFactorManager->getProviderSet($user);
$allProviders = $providerSet->getProviders();
list($providers, $backupProvider) = $this->splitProvidersAndBackupCodes($allProviders);
+ $setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
$data = [
'providers' => $providers,
@@ -114,6 +116,7 @@ class TwoFactorChallengeController extends Controller {
'providerMissing' => $providerSet->isProviderMissing(),
'redirect_url' => $redirect_url,
'logout_url' => $this->getLogoutUrl(),
+ 'hasSetupProviders' => !empty($setupProviders),
];
return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest');
}
@@ -131,6 +134,7 @@ class TwoFactorChallengeController extends Controller {
$user = $this->userSession->getUser();
$providerSet = $this->twoFactorManager->getProviderSet($user);
$provider = $providerSet->getProvider($challengeProviderId);
+
if (is_null($provider)) {
return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
}
@@ -209,4 +213,67 @@ class TwoFactorChallengeController extends Controller {
]));
}
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ */
+ public function setupProviders() {
+ $user = $this->userSession->getUser();
+ $setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
+
+ $data = [
+ 'providers' => $setupProviders,
+ 'logout_url' => $this->getLogoutUrl(),
+ ];
+
+ $response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupselection', $data, 'guest');
+ return $response;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ */
+ public function setupProvider(string $providerId) {
+ $user = $this->userSession->getUser();
+ $providers = $this->twoFactorManager->getLoginSetupProviders($user);
+
+ $provider = null;
+ foreach ($providers as $p) {
+ if ($p->getId() === $providerId) {
+ $provider = $p;
+ break;
+ }
+ }
+
+ if ($provider === null) {
+ return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
+ }
+
+ /** @var IActivatableAtLogin $provider */
+ $tmpl = $provider->getLoginSetup($user)->getBody();
+ $data = [
+ 'provider' => $provider,
+ 'logout_url' => $this->getLogoutUrl(),
+ 'template' => $tmpl->fetchPage(),
+ ];
+ $response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupchallenge', $data, 'guest');
+ return $response;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ *
+ * @todo handle the extreme edge case of an invalid provider ID and redirect to the provider selection page
+ */
+ public function confirmProviderSetup(string $providerId) {
+ return new RedirectResponse($this->urlGenerator->linkToRoute(
+ 'core.TwoFactorChallenge.showChallenge',
+ [
+ 'challengeProviderId' => $providerId,
+ ]
+ ));
+ }
+
}
diff --git a/core/Middleware/TwoFactorMiddleware.php b/core/Middleware/TwoFactorMiddleware.php
index 167545b0df..7b32c0dd89 100644
--- a/core/Middleware/TwoFactorMiddleware.php
+++ b/core/Middleware/TwoFactorMiddleware.php
@@ -36,6 +36,7 @@ use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Utility\IControllerMethodReflector;
+use OCP\Authentication\TwoFactorAuth\ALoginSetupController;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
@@ -87,6 +88,12 @@ class TwoFactorMiddleware extends Middleware {
return;
}
+ if ($controller instanceof ALoginSetupController
+ && $this->userSession->getUser() !== null
+ && $this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) {
+ return;
+ }
+
if ($controller instanceof LoginController && $methodName === 'logout') {
// Don't block the logout page, to allow canceling the 2FA
return;
@@ -95,7 +102,6 @@ class TwoFactorMiddleware extends Middleware {
if ($this->userSession->isLoggedIn()) {
$user = $this->userSession->getUser();
-
if ($this->session->exists('app_password') || $this->twoFactorManager->isTwoFactorAuthenticated($user)) {
$this->checkTwoFactor($controller, $methodName, $user);
} else if ($controller instanceof TwoFactorChallengeController) {
diff --git a/core/routes.php b/core/routes.php
index 1544fd67e0..79db0c6ccd 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -67,6 +67,9 @@ $application->registerRoutes($this, [
['name' => 'TwoFactorChallenge#selectChallenge', 'url' => '/login/selectchallenge', 'verb' => 'GET'],
['name' => 'TwoFactorChallenge#showChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'GET'],
['name' => 'TwoFactorChallenge#solveChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'POST'],
+ ['name' => 'TwoFactorChallenge#setupProviders', 'url' => 'login/setupchallenge', 'verb' => 'GET'],
+ ['name' => 'TwoFactorChallenge#setupProvider', 'url' => 'login/setupchallenge/{providerId}', 'verb' => 'GET'],
+ ['name' => 'TwoFactorChallenge#confirmProviderSetup', 'url' => 'login/setupchallenge/{providerId}', 'verb' => 'POST'],
['name' => 'OCJS#getConfig', 'url' => '/core/js/oc.js', 'verb' => 'GET'],
['name' => 'Preview#getPreviewByFileId', 'url' => '/core/preview', 'verb' => 'GET'],
['name' => 'Preview#getPreview', 'url' => '/core/preview.png', 'verb' => 'GET'],
diff --git a/core/templates/twofactorselectchallenge.php b/core/templates/twofactorselectchallenge.php
index 65691f5857..8508039268 100644
--- a/core/templates/twofactorselectchallenge.php
+++ b/core/templates/twofactorselectchallenge.php
@@ -15,9 +15,20 @@ $noProviders = empty($_['providers']);
- t('Two-factor authentication is enforced but has not been configured on your account. Contact your admin for assistance.')) ?>
+
+ t('Two-factor authentication is enforced but has not been configured on your account. Contact your admin for assistance.')) ?>
+
+ t('Two-factor authentication is enforced but has not been configured on your account. Please continue to setup two-factor authentication.')) ?>
+
+ t('Set up two-factor authentication')) ?>
+
+
- t('Two-factor authentication is enforced but has not been configured on your account. Use one of your backup codes to log in or contact your admin for assistance.')) ?>
+ t('Two-factor authentication is enforced but has not been configured on your account. Use one of your backup codes to log in or contact your admin for assistance.')) ?>
diff --git a/core/templates/twofactorsetupchallenge.php b/core/templates/twofactorsetupchallenge.php
new file mode 100644
index 0000000000..9c182db171
--- /dev/null
+++ b/core/templates/twofactorsetupchallenge.php
@@ -0,0 +1,16 @@
+
+
+
diff --git a/core/templates/twofactorsetupselection.php b/core/templates/twofactorsetupselection.php
new file mode 100644
index 0000000000..7d689b89af
--- /dev/null
+++ b/core/templates/twofactorsetupselection.php
@@ -0,0 +1,58 @@
+
+ *
+ * @author Roeland Jago Douma
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+?>
+
+
+ t('Enhanced security is enforced for your account. Choose wich provider to set up:')) ?>
+
+
+ t('Cancel log in')) ?>
+
+
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 1d71a209dd..e8bdc89515 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -77,8 +77,11 @@ return array(
'OCP\\Authentication\\IApacheBackend' => $baseDir . '/lib/public/Authentication/IApacheBackend.php',
'OCP\\Authentication\\LoginCredentials\\ICredentials' => $baseDir . '/lib/public/Authentication/LoginCredentials/ICredentials.php',
'OCP\\Authentication\\LoginCredentials\\IStore' => $baseDir . '/lib/public/Authentication/LoginCredentials/IStore.php',
+ 'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php',
+ 'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php',
'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php',
'OCP\\Authentication\\TwoFactorAuth\\IDeactivatableByAdmin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IDeactivatableByAdmin.php',
+ 'OCP\\Authentication\\TwoFactorAuth\\ILoginSetupProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php',
'OCP\\Authentication\\TwoFactorAuth\\IPersonalProviderSettings' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php',
'OCP\\Authentication\\TwoFactorAuth\\IProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvider.php',
'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 1628c87632..d5dccfc102 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -107,8 +107,11 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\Authentication\\IApacheBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IApacheBackend.php',
'OCP\\Authentication\\LoginCredentials\\ICredentials' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/ICredentials.php',
'OCP\\Authentication\\LoginCredentials\\IStore' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/IStore.php',
+ 'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php',
+ 'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php',
'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php',
'OCP\\Authentication\\TwoFactorAuth\\IDeactivatableByAdmin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IDeactivatableByAdmin.php',
+ 'OCP\\Authentication\\TwoFactorAuth\\ILoginSetupProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php',
'OCP\\Authentication\\TwoFactorAuth\\IPersonalProviderSettings' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php',
'OCP\\Authentication\\TwoFactorAuth\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvider.php',
'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php',
diff --git a/lib/private/Authentication/Login/TwoFactorCommand.php b/lib/private/Authentication/Login/TwoFactorCommand.php
index 2825dc1763..7a1daa0ad5 100644
--- a/lib/private/Authentication/Login/TwoFactorCommand.php
+++ b/lib/private/Authentication/Login/TwoFactorCommand.php
@@ -28,6 +28,7 @@ namespace OC\Authentication\Login;
use function array_pop;
use function count;
use OC\Authentication\TwoFactorAuth\Manager;
+use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\IURLGenerator;
@@ -36,12 +37,17 @@ class TwoFactorCommand extends ALoginCommand {
/** @var Manager */
private $twoFactorManager;
+ /** @var MandatoryTwoFactor */
+ private $mandatoryTwoFactor;
+
/** @var IURLGenerator */
private $urlGenerator;
public function __construct(Manager $twoFactorManager,
+ MandatoryTwoFactor $mandatoryTwoFactor,
IURLGenerator $urlGenerator) {
$this->twoFactorManager = $twoFactorManager;
+ $this->mandatoryTwoFactor = $mandatoryTwoFactor;
$this->urlGenerator = $urlGenerator;
}
@@ -52,9 +58,18 @@ class TwoFactorCommand extends ALoginCommand {
$this->twoFactorManager->prepareTwoFactorLogin($loginData->getUser(), $loginData->isRememberLogin());
- $providers = $this->twoFactorManager->getProviderSet($loginData->getUser())->getPrimaryProviders();
- if (count($providers) === 1) {
- // Single provider, hence we can redirect to that provider's challenge page directly
+ $providerSet = $this->twoFactorManager->getProviderSet($loginData->getUser());
+ $loginProviders = $this->twoFactorManager->getLoginSetupProviders($loginData->getUser());
+ $providers = $providerSet->getPrimaryProviders();
+ if (empty($providers)
+ && !$providerSet->isProviderMissing()
+ && !empty($loginProviders)
+ && $this->mandatoryTwoFactor->isEnforcedFor($loginData->getUser())) {
+ // No providers set up, but 2FA is enforced and setup providers are available
+ $url = 'core.TwoFactorChallenge.setupProviders';
+ $urlParams = [];
+ } else if (!$providerSet->isProviderMissing() && count($providers) === 1) {
+ // Single provider (and no missing ones), hence we can redirect to that provider's challenge page directly
/* @var $provider IProvider */
$provider = array_pop($providers);
$url = 'core.TwoFactorChallenge.showChallenge';
diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php
index ef95184aba..17f8479f25 100644
--- a/lib/private/Authentication/TwoFactorAuth/Manager.php
+++ b/lib/private/Authentication/TwoFactorAuth/Manager.php
@@ -36,6 +36,8 @@ use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\IProvider as TokenProvider;
use OCP\Activity\IManager;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
+use OCP\Authentication\TwoFactorAuth\ILoginSetupProvider;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\Authentication\TwoFactorAuth\IRegistry;
use OCP\IConfig;
@@ -133,6 +135,18 @@ class Manager {
return $providers[$challengeProviderId] ?? null;
}
+ /**
+ * @param IUser $user
+ * @return IActivatableAtLogin[]
+ * @throws Exception
+ */
+ public function getLoginSetupProviders(IUser $user): array {
+ $providers = $this->providerLoader->getProviders($user);
+ return array_filter($providers, function(IProvider $provider) {
+ return ($provider instanceof IActivatableAtLogin);
+ });
+ }
+
/**
* Check if the persistant mapping of enabled/disabled state of each available
* provider is missing an entry and add it to the registry in that case.
diff --git a/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php b/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php
new file mode 100644
index 0000000000..8914295d61
--- /dev/null
+++ b/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php
@@ -0,0 +1,34 @@
+
+ *
+ * @author Roeland Jago Douma
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCP\Authentication\TwoFactorAuth;
+
+use OCP\AppFramework\Controller;
+
+/**
+ * @since 17.0.0
+ */
+abstract class ALoginSetupController extends Controller {
+
+}
diff --git a/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php b/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php
new file mode 100644
index 0000000000..22d5c6d144
--- /dev/null
+++ b/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php
@@ -0,0 +1,43 @@
+
+ *
+ * @author Roeland Jago Douma
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCP\Authentication\TwoFactorAuth;
+
+use OCP\IUser;
+
+/**
+ * @since 17.0.0
+ */
+interface IActivatableAtLogin extends IProvider {
+
+ /**
+ * @param IUser $user
+ *
+ * @return ILoginSetupProvider
+ *
+ * @since 17.0.0
+ */
+ public function getLoginSetup(IUser $user): ILoginSetupProvider;
+
+}
diff --git a/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php b/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php
new file mode 100644
index 0000000000..7815f60b66
--- /dev/null
+++ b/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php
@@ -0,0 +1,41 @@
+
+ *
+ * @author Roeland Jago Douma
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCP\Authentication\TwoFactorAuth;
+
+use OCP\Template;
+
+/**
+ * @since 17.0.0
+ */
+interface ILoginSetupProvider {
+
+ /**
+ * @return Template
+ *
+ * @since 17.0.0
+ */
+ public function getBody(): Template;
+
+}
diff --git a/tests/Core/Controller/TwoFactorChallengeControllerTest.php b/tests/Core/Controller/TwoFactorChallengeControllerTest.php
index a405914cc4..73b035a408 100644
--- a/tests/Core/Controller/TwoFactorChallengeControllerTest.php
+++ b/tests/Core/Controller/TwoFactorChallengeControllerTest.php
@@ -28,6 +28,8 @@ use OC\Core\Controller\TwoFactorChallengeController;
use OC_Util;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
+use OCP\Authentication\TwoFactorAuth\ILoginSetupProvider;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\Authentication\TwoFactorAuth\TwoFactorException;
use OCP\IRequest;
@@ -86,11 +88,15 @@ class TwoFactorChallengeControllerTest extends TestCase {
public function testSelectChallenge() {
$user = $this->getMockBuilder(IUser::class)->getMock();
- $p1 = $this->createMock(IProvider::class);
+ $p1 = $this->createMock(IActivatableAtLogin::class);
$p1->method('getId')->willReturn('p1');
$backupProvider = $this->createMock(IProvider::class);
$backupProvider->method('getId')->willReturn('backup_codes');
$providerSet = new ProviderSet([$p1, $backupProvider], true);
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($user)
+ ->willReturn([$p1]);
$this->userSession->expects($this->once())
->method('getUser')
@@ -108,7 +114,8 @@ class TwoFactorChallengeControllerTest extends TestCase {
'backupProvider' => $backupProvider,
'redirect_url' => '/some/url',
'logout_url' => 'logoutAttribute',
- ], 'guest');
+ 'hasSetupProviders' => true,
+ ], 'guest');
$this->assertEquals($expected, $this->controller->selectChallenge('/some/url'));
}
@@ -159,7 +166,7 @@ class TwoFactorChallengeControllerTest extends TestCase {
'template' => '',
'redirect_url' => '/re/dir/ect/url',
'error_message' => null,
- ], 'guest');
+ ], 'guest');
$this->assertEquals($expected, $this->controller->showChallenge('myprovider', '/re/dir/ect/url'));
}
@@ -323,4 +330,118 @@ class TwoFactorChallengeControllerTest extends TestCase {
$this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token', '/url'));
}
+ public function testSetUpProviders() {
+ $user = $this->createMock(IUser::class);
+ $this->userSession->expects($this->once())
+ ->method('getUser')
+ ->will($this->returnValue($user));
+ $provider = $this->createMock(IActivatableAtLogin::class);
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($user)
+ ->willReturn([
+ $provider,
+ ]);
+ $expected = new StandaloneTemplateResponse(
+ 'core',
+ 'twofactorsetupselection',
+ [
+ 'providers' => [
+ $provider,
+ ],
+ 'logout_url' => 'logoutAttribute',
+ ],
+ 'guest'
+ );
+
+ $response = $this->controller->setupProviders();
+
+ $this->assertEquals($expected, $response);
+ }
+
+ public function testSetUpInvalidProvider() {
+ $user = $this->createMock(IUser::class);
+ $this->userSession->expects($this->once())
+ ->method('getUser')
+ ->will($this->returnValue($user));
+ $provider = $this->createMock(IActivatableAtLogin::class);
+ $provider->expects($this->any())
+ ->method('getId')
+ ->willReturn('prov1');
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($user)
+ ->willReturn([
+ $provider,
+ ]);
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with('core.TwoFactorChallenge.selectChallenge')
+ ->willReturn('2fa/select/page');
+ $expected = new RedirectResponse('2fa/select/page');
+
+ $response = $this->controller->setupProvider('prov2');
+
+ $this->assertEquals($expected, $response);
+ }
+
+ public function testSetUpProvider() {
+ $user = $this->createMock(IUser::class);
+ $this->userSession->expects($this->once())
+ ->method('getUser')
+ ->will($this->returnValue($user));
+ $provider = $this->createMock(IActivatableAtLogin::class);
+ $provider->expects($this->any())
+ ->method('getId')
+ ->willReturn('prov1');
+ $this->twoFactorManager->expects($this->once())
+ ->method('getLoginSetupProviders')
+ ->with($user)
+ ->willReturn([
+ $provider,
+ ]);
+ $loginSetup = $this->createMock(ILoginSetupProvider::class);
+ $provider->expects($this->any())
+ ->method('getLoginSetup')
+ ->with($user)
+ ->willReturn($loginSetup);
+ $tmpl = $this->createMock(Template::class);
+ $loginSetup->expects($this->once())
+ ->method('getBody')
+ ->willReturn($tmpl);
+ $tmpl->expects($this->once())
+ ->method('fetchPage')
+ ->willReturn('tmpl');
+ $expected = new StandaloneTemplateResponse(
+ 'core',
+ 'twofactorsetupchallenge',
+ [
+ 'provider' => $provider,
+ 'logout_url' => 'logoutAttribute',
+ 'template' => 'tmpl',
+ ],
+ 'guest'
+ );
+
+ $response = $this->controller->setupProvider('prov1');
+
+ $this->assertEquals($expected, $response);
+ }
+
+ public function testConfirmProviderSetup() {
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with(
+ 'core.TwoFactorChallenge.showChallenge',
+ [
+ 'challengeProviderId' => 'totp',
+ ])
+ ->willReturn('2fa/select/page');
+ $expected = new RedirectResponse('2fa/select/page');
+
+ $response = $this->controller->confirmProviderSetup('totp');
+
+ $this->assertEquals($expected, $response);
+ }
+
}
diff --git a/tests/Core/Middleware/TwoFactorMiddlewareTest.php b/tests/Core/Middleware/TwoFactorMiddlewareTest.php
index eb72b3e679..7056676018 100644
--- a/tests/Core/Middleware/TwoFactorMiddlewareTest.php
+++ b/tests/Core/Middleware/TwoFactorMiddlewareTest.php
@@ -28,20 +28,35 @@ use OC\AppFramework\Http\Request;
use OC\User\Session;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Utility\IControllerMethodReflector;
+use OCP\Authentication\TwoFactorAuth\ALoginSetupController;
use OCP\IConfig;
+use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
+use OCP\IUserSession;
use OCP\Security\ISecureRandom;
+use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class TwoFactorMiddlewareTest extends TestCase {
+ /** @var Manager|MockObject */
private $twoFactorManager;
+
+ /** @var IUserSession|MockObject */
private $userSession;
+
+ /** @var ISession|MockObject */
private $session;
+
+ /** @var IURLGenerator|MockObject */
private $urlGenerator;
+
+ /** @var IControllerMethodReflector|MockObject */
private $reflector;
+
+ /** @var IRequest|MockObject */
private $request;
/** @var TwoFactorMiddleware */
@@ -102,6 +117,25 @@ class TwoFactorMiddlewareTest extends TestCase {
$this->middleware->beforeController($this->controller, 'create');
}
+ public function testBeforeSetupController() {
+ $user = $this->createMock(IUser::class);
+ $controller = $this->createMock(ALoginSetupController::class);
+ $this->reflector->expects($this->once())
+ ->method('hasAnnotation')
+ ->with('PublicPage')
+ ->willReturn(false);
+ $this->userSession->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->twoFactorManager->expects($this->once())
+ ->method('needsSecondFactor')
+ ->willReturn(true);
+ $this->userSession->expects($this->never())
+ ->method('isLoggedIn');
+
+ $this->middleware->beforeController($controller, 'create');
+ }
+
public function testBeforeControllerNoTwoFactorCheckNeeded() {
$user = $this->createMock(IUser::class);
diff --git a/tests/lib/Authentication/Login/TwoFactorCommandTest.php b/tests/lib/Authentication/Login/TwoFactorCommandTest.php
index a5c1c8e352..5f91d81252 100644
--- a/tests/lib/Authentication/Login/TwoFactorCommandTest.php
+++ b/tests/lib/Authentication/Login/TwoFactorCommandTest.php
@@ -27,7 +27,9 @@ namespace lib\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;
@@ -37,6 +39,9 @@ class TwoFactorCommandTest extends ALoginCommandTest {
/** @var Manager|MockObject */
private $twoFactorManager;
+ /** @var MandatoryTwoFactor|MockObject */
+ private $mandatoryTwoFactor;
+
/** @var IURLGenerator|MockObject */
private $urlGenerator;
@@ -44,10 +49,12 @@ class TwoFactorCommandTest extends ALoginCommandTest {
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
);
}
@@ -82,6 +89,14 @@ class TwoFactorCommandTest extends ALoginCommandTest {
->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');
@@ -101,6 +116,47 @@ class TwoFactorCommandTest extends ALoginCommandTest {
$this->assertEquals('two/factor/url', $result->getRedirectUrl());
}
+ public function testProcessMissingProviders() {
+ $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() {
$data = $this->getLoggedInLoginData();
$this->twoFactorManager->expects($this->once())
@@ -126,6 +182,122 @@ class TwoFactorCommandTest extends ALoginCommandTest {
$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() {
+ $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() {
+ $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() {
+ $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(
@@ -156,6 +328,14 @@ class TwoFactorCommandTest extends ALoginCommandTest {
->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');
diff --git a/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php
index 0f09691bc1..e836e8d316 100644
--- a/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php
+++ b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php
@@ -31,6 +31,7 @@ use OC\Authentication\TwoFactorAuth\ProviderLoader;
use OCP\Activity\IEvent;
use OCP\Activity\IManager;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\Authentication\TwoFactorAuth\IRegistry;
use OCP\IConfig;
@@ -38,6 +39,7 @@ use OCP\ILogger;
use OCP\ISession;
use OCP\IUser;
use PHPUnit\Framework\MockObject\MockObject;
+use function reset;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Test\TestCase;
@@ -297,6 +299,23 @@ class ManagerTest extends TestCase {
$this->assertNull($provider);
}
+ public function testGetLoginSetupProviders() {
+ $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() {
$this->providerRegistry->expects($this->once())
->method('getProviderStates')