Once 2FA is enforced for a user and they have no 2FA setup yet this will now prompt them with a setup screen. Given that providers are enabled that allow setup then. Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl> Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>tags/v17.0.0beta1
@@ -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, | |||
] | |||
)); | |||
} | |||
} |
@@ -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) { |
@@ -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'], |
@@ -15,9 +15,20 @@ $noProviders = empty($_['providers']); | |||
<img class="two-factor-icon" src="<?php p(image_path('core', 'actions/password-white.svg')) ?>" alt="" /> | |||
<p> | |||
<?php if (is_null($_['backupProvider'])): ?> | |||
<strong><?php p($l->t('Two-factor authentication is enforced but has not been configured on your account. Contact your admin for assistance.')) ?></strong> | |||
<?php if (!$_['hasSetupProviders']) { ?> | |||
<strong><?php p($l->t('Two-factor authentication is enforced but has not been configured on your account. Contact your admin for assistance.')) ?></strong> | |||
<?php } else { ?> | |||
<strong><?php p($l->t('Two-factor authentication is enforced but has not been configured on your account. Please continue to setup two-factor authentication.')) ?></strong> | |||
<a class="button primary two-factor-primary" href="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.setupProviders', | |||
[ | |||
'redirect_url' => $_['redirect_url'], | |||
] | |||
)) ?>"> | |||
<?php p($l->t('Set up two-factor authentication')) ?> | |||
</a> | |||
<?php } ?> | |||
<?php else: ?> | |||
<strong><?php p($l->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.')) ?></strong> | |||
<strong><?php p($l->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.')) ?></strong> | |||
<?php endif; ?> | |||
</p> | |||
<?php else: ?> |
@@ -0,0 +1,16 @@ | |||
<?php | |||
/** @var $l \OCP\IL10N */ | |||
/** @var $_ array */ | |||
/* @var $provider OCP\Authentication\TwoFactorAuth\IProvider */ | |||
$provider = $_['provider']; | |||
/* @var $template string */ | |||
$template = $_['template']; | |||
?> | |||
<div class="body-login-container update"> | |||
<h2 class="two-factor-header"><?php p($provider->getDisplayName()); ?></h2> | |||
<?php print_unescaped($template); ?> | |||
<p><a class="two-factor-secondary" href="<?php print_unescaped($_['logout_url']); ?>"> | |||
<?php p($l->t('Cancel log in')) ?> | |||
</a></p> | |||
</div> |
@@ -0,0 +1,58 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> | |||
* | |||
* @author Roeland Jago Douma <roeland@famdouma.nl> | |||
* | |||
* @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 <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
?> | |||
<div class="body-login-container update"> | |||
<h2 class="two-factor-header"><?php p($l->t('Setup two-factor authentication')) ?></h2> | |||
<?php p($l->t('Enhanced security is enforced for your account. Choose wich provider to set up:')) ?> | |||
<ul> | |||
<?php foreach ($_['providers'] as $provider): ?> | |||
<li> | |||
<a class="two-factor-provider" | |||
href="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.setupProvider', | |||
[ | |||
'providerId' => $provider->getId(), | |||
'redirect_url' => $_['redirect_url'], | |||
] | |||
)) ?>"> | |||
<?php | |||
if ($provider instanceof \OCP\Authentication\TwoFactorAuth\IProvidesIcons) { | |||
$icon = $provider->getLightIcon(); | |||
} else { | |||
$icon = image_path('core', 'actions/password-white.svg'); | |||
} | |||
?> | |||
<img src="<?php p($icon) ?>" alt="" /> | |||
<div> | |||
<h3><?php p($provider->getDisplayName()) ?></h3> | |||
<p><?php p($provider->getDescription()) ?></p> | |||
</div> | |||
</a> | |||
</li> | |||
<?php endforeach; ?> | |||
</ul> | |||
<p><a class="two-factor-secondary" href="<?php print_unescaped($_['logout_url']); ?>"> | |||
<?php p($l->t('Cancel log in')) ?> | |||
</a></p> | |||
</div> |
@@ -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', |
@@ -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', |
@@ -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'; |
@@ -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. |
@@ -0,0 +1,34 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> | |||
* | |||
* @author Roeland Jago Douma <roeland@famdouma.nl> | |||
* | |||
* @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 <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OCP\Authentication\TwoFactorAuth; | |||
use OCP\AppFramework\Controller; | |||
/** | |||
* @since 17.0.0 | |||
*/ | |||
abstract class ALoginSetupController extends Controller { | |||
} |
@@ -0,0 +1,43 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> | |||
* | |||
* @author Roeland Jago Douma <roeland@famdouma.nl> | |||
* | |||
* @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 <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
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; | |||
} |
@@ -0,0 +1,41 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> | |||
* | |||
* @author Roeland Jago Douma <roeland@famdouma.nl> | |||
* | |||
* @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 <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OCP\Authentication\TwoFactorAuth; | |||
use OCP\Template; | |||
/** | |||
* @since 17.0.0 | |||
*/ | |||
interface ILoginSetupProvider { | |||
/** | |||
* @return Template | |||
* | |||
* @since 17.0.0 | |||
*/ | |||
public function getBody(): Template; | |||
} |
@@ -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' => '<html/>', | |||
'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); | |||
} | |||
} |
@@ -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); | |||
@@ -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'); |
@@ -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') |