Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2FA setup during login #15304

Merged
merged 1 commit into from
May 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions core/Controller/TwoFactorChallengeController.php
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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 {

}
Loading