Skip to content

Commit

Permalink
Merge pull request #15304 from nextcloud/enh/2fa_setup_at_login
Browse files Browse the repository at this point in the history
2FA setup during login
  • Loading branch information
rullzer committed May 17, 2019
2 parents f9d30b9 + 579162d commit 528eb1b
Show file tree
Hide file tree
Showing 17 changed files with 677 additions and 9 deletions.
67 changes: 67 additions & 0 deletions core/Controller/TwoFactorChallengeController.php
Expand Up @@ -32,6 +32,7 @@
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;
Expand Down Expand Up @@ -107,13 +108,15 @@ public function selectChallenge($redirect_url) {
$providerSet = $this->twoFactorManager->getProviderSet($user);
$allProviders = $providerSet->getProviders();
list($providers, $backupProvider) = $this->splitProvidersAndBackupCodes($allProviders);
$setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);

$data = [
'providers' => $providers,
'backupProvider' => $backupProvider,
'providerMissing' => $providerSet->isProviderMissing(),
'redirect_url' => $redirect_url,
'logout_url' => $this->getLogoutUrl(),
'hasSetupProviders' => !empty($setupProviders),
];
return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest');
}
Expand All @@ -131,6 +134,7 @@ public function showChallenge($challengeProviderId, $redirect_url) {
$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'));
}
Expand Down Expand Up @@ -209,4 +213,67 @@ public function solveChallenge($challengeProviderId, $challenge, $redirect_url =
]));
}

/**
* @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,
]
));
}

}
8 changes: 7 additions & 1 deletion core/Middleware/TwoFactorMiddleware.php
Expand Up @@ -36,6 +36,7 @@
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;
Expand Down Expand Up @@ -87,6 +88,12 @@ public function beforeController($controller, $methodName) {
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;
Expand All @@ -95,7 +102,6 @@ public function beforeController($controller, $methodName) {
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) {
Expand Down
3 changes: 3 additions & 0 deletions core/routes.php
Expand Up @@ -67,6 +67,9 @@
['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'],
Expand Down
15 changes: 13 additions & 2 deletions core/templates/twofactorselectchallenge.php
Expand Up @@ -15,9 +15,20 @@
<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: ?>
Expand Down
16 changes: 16 additions & 0 deletions core/templates/twofactorsetupchallenge.php
@@ -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>
58 changes: 58 additions & 0 deletions core/templates/twofactorsetupselection.php
@@ -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>
3 changes: 3 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Expand Up @@ -77,8 +77,11 @@
'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',
Expand Down
3 changes: 3 additions & 0 deletions lib/composer/composer/autoload_static.php
Expand Up @@ -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',
Expand Down
21 changes: 18 additions & 3 deletions lib/private/Authentication/Login/TwoFactorCommand.php
Expand Up @@ -28,6 +28,7 @@
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;

Expand All @@ -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;
}

Expand All @@ -52,9 +58,18 @@ public function process(LoginData $loginData): LoginResult {

$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';
Expand Down
14 changes: 14 additions & 0 deletions lib/private/Authentication/TwoFactorAuth/Manager.php
Expand Up @@ -36,6 +36,8 @@
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;
Expand Down Expand Up @@ -133,6 +135,18 @@ public function getProvider(IUser $user, string $challengeProviderId) {
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.
Expand Down
34 changes: 34 additions & 0 deletions lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php
@@ -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 comments on commit 528eb1b

Please sign in to comment.