diff options
author | Christoph Wurst <christoph@owncloud.com> | 2016-05-11 11:23:25 +0200 |
---|---|---|
committer | Christoph Wurst <christoph@owncloud.com> | 2016-05-23 11:21:10 +0200 |
commit | dfb4d426c24c8cbb7e207a3dd92b5fcd894a1977 (patch) | |
tree | dc640b6bb84d032a6a45ca03ffe91e37d9c99ea9 /core | |
parent | dec3f9ebcbdeacf5bc483df93900b157a1a5e546 (diff) | |
download | nextcloud-server-dfb4d426c24c8cbb7e207a3dd92b5fcd894a1977.tar.gz nextcloud-server-dfb4d426c24c8cbb7e207a3dd92b5fcd894a1977.zip |
Add two factor auth to core
Diffstat (limited to 'core')
-rw-r--r-- | core/Application.php | 16 | ||||
-rw-r--r-- | core/Controller/LoginController.php | 16 | ||||
-rw-r--r-- | core/Controller/TwoFactorChallengeController.php | 134 | ||||
-rw-r--r-- | core/Middleware/TwoFactorMiddleware.php | 117 | ||||
-rw-r--r-- | core/css/styles.css | 6 | ||||
-rw-r--r-- | core/routes.php | 3 | ||||
-rw-r--r-- | core/templates/twofactorselectchallenge.php | 16 | ||||
-rw-r--r-- | core/templates/twofactorshowchallenge.php | 19 |
8 files changed, 324 insertions, 3 deletions
diff --git a/core/Application.php b/core/Application.php index a835dc7fbb2..e00f2c906da 100644 --- a/core/Application.php +++ b/core/Application.php @@ -33,6 +33,7 @@ use OC\Core\Controller\AvatarController; use OC\Core\Controller\LoginController; use OC\Core\Controller\LostController; use OC\Core\Controller\TokenController; +use OC\Core\Controller\TwoFactorChallengeController; use OC\Core\Controller\UserController; use OC_Defaults; use OCP\AppFramework\App; @@ -101,9 +102,19 @@ class Application extends App { $c->query('Config'), $c->query('Session'), $c->query('UserSession'), - $c->query('URLGenerator') + $c->query('URLGenerator'), + $c->query('TwoFactorAuthManager') ); }); + $container->registerService('TwoFactorChallengeController', function (SimpleContainer $c) { + return new TwoFactorChallengeController( + $c->query('AppName'), + $c->query('Request'), + $c->query('TwoFactorAuthManager'), + $c->query('UserSession'), + $c->query('Session'), + $c->query('URLGenerator')); + }); $container->registerService('TokenController', function(SimpleContainer $c) { return new TokenController( $c->query('AppName'), @@ -168,6 +179,9 @@ class Application extends App { $container->registerService('DefaultEmailAddress', function() { return Util::getDefaultEmailAddress('lostpassword-noreply'); }); + $container->registerService('TwoFactorAuthManager', function(SimpleContainer $c) { + return $c->query('ServerContainer')->getTwoFactorAuthManager(); + }); } } diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php index 59d40ca14e2..ea857bb57df 100644 --- a/core/Controller/LoginController.php +++ b/core/Controller/LoginController.php @@ -23,7 +23,7 @@ namespace OC\Core\Controller; -use OC; +use OC\Authentication\TwoFactorAuth\Manager; use OC\User\Session; use OC_App; use OC_Util; @@ -54,6 +54,9 @@ class LoginController extends Controller { /** @var IURLGenerator */ private $urlGenerator; + /** @var Manager */ + private $twoFactorManager; + /** * @param string $appName * @param IRequest $request @@ -62,15 +65,17 @@ class LoginController extends Controller { * @param ISession $session * @param Session $userSession * @param IURLGenerator $urlGenerator + * @param Manager $twoFactorManager */ function __construct($appName, IRequest $request, IUserManager $userManager, IConfig $config, ISession $session, - Session $userSession, IURLGenerator $urlGenerator) { + Session $userSession, IURLGenerator $urlGenerator, Manager $twoFactorManager) { parent::__construct($appName, $request); $this->userManager = $userManager; $this->config = $config; $this->session = $session; $this->userSession = $userSession; $this->urlGenerator = $urlGenerator; + $this->twoFactorManager = $twoFactorManager; } /** @@ -167,6 +172,7 @@ class LoginController extends Controller { */ public function tryLogin($user, $password, $redirect_url) { // TODO: Add all the insane error handling + /* @var $loginResult IUser */ $loginResult = $this->userManager->checkPassword($user, $password); if ($loginResult === false) { $users = $this->userManager->getByEmail($user); @@ -185,6 +191,12 @@ class LoginController extends Controller { return new RedirectResponse($this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)); } $this->userSession->createSessionToken($this->request, $loginResult->getUID(), $password); + + if ($this->twoFactorManager->isTwoFactorAuthenticated($loginResult)) { + $this->twoFactorManager->prepareTwoFactorLogin($loginResult); + return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); + } + if (!is_null($redirect_url) && $this->userSession->isLoggedIn()) { $location = $this->urlGenerator->getAbsoluteURL(urldecode($redirect_url)); // Deny the redirect if the URL contains a @ diff --git a/core/Controller/TwoFactorChallengeController.php b/core/Controller/TwoFactorChallengeController.php new file mode 100644 index 00000000000..73ccc731231 --- /dev/null +++ b/core/Controller/TwoFactorChallengeController.php @@ -0,0 +1,134 @@ +<?php + +/** + * @author Christoph Wurst <christoph@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Core\Controller; + +use OC\Authentication\TwoFactorAuth\Manager; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IRequest; +use OCP\ISession; +use OCP\IURLGenerator; +use OCP\IUserSession; + +class TwoFactorChallengeController extends Controller { + + /** @var Manager */ + private $twoFactorManager; + + /** @var IUserSession */ + private $userSession; + + /** @var ISession */ + private $session; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** + * @param string $appName + * @param IRequest $request + * @param Manager $twoFactorManager + * @param IUserSession $userSession + * @param ISession $session + * @param IURLGenerator $urlGenerator + */ + public function __construct($appName, IRequest $request, Manager $twoFactorManager, IUserSession $userSession, + ISession $session, IURLGenerator $urlGenerator) { + parent::__construct($appName, $request); + $this->twoFactorManager = $twoFactorManager; + $this->userSession = $userSession; + $this->session = $session; + $this->urlGenerator = $urlGenerator; + } + + /** + * @NoCSRFRequired + * @PublicPage + * + * @return TemplateResponse + */ + public function selectChallenge() { + $user = $this->userSession->getUser(); + $providers = $this->twoFactorManager->getProviders($user); + + $data = [ + 'providers' => $providers, + ]; + return new TemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest'); + } + + /** + * @NoCSRFRequired + * @PublicPage + * @UseSession + * + * @param string $challengeProviderId + * @return TemplateResponse + */ + public function showChallenge($challengeProviderId) { + $user = $this->userSession->getUser(); + $provider = $this->twoFactorManager->getProvider($user, $challengeProviderId); + if (is_null($provider)) { + return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); + } + + if ($this->session->exists('two_factor_auth_error')) { + $this->session->remove('two_factor_auth_error'); + $error = true; + } else { + $error = false; + } + $data = [ + 'error' => $error, + 'provider' => $provider, + 'template' => $provider->getTemplate($user)->fetchPage(), + ]; + return new TemplateResponse($this->appName, 'twofactorshowchallenge', $data, 'guest'); + } + + /** + * @NoCSRFRequired + * @PublicPage + * @UseSession + * + * @param string $challengeProviderId + * @param string $challenge + * @return RedirectResponse + */ + public function solveChallenge($challengeProviderId, $challenge) { + $user = $this->userSession->getUser(); + $provider = $this->twoFactorManager->getProvider($user, $challengeProviderId); + if (is_null($provider)) { + return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); + } + + if ($this->twoFactorManager->verifyChallenge($challengeProviderId, $user, $challenge)) { + return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index')); + } + + $this->session->set('two_factor_auth_error', true); + return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.showChallenge', ['challengeProviderId' => $provider->getId()])); + } + +} diff --git a/core/Middleware/TwoFactorMiddleware.php b/core/Middleware/TwoFactorMiddleware.php new file mode 100644 index 00000000000..ea25aa36ecd --- /dev/null +++ b/core/Middleware/TwoFactorMiddleware.php @@ -0,0 +1,117 @@ +<?php + +/** + * @author Christoph Wurst <christoph@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Core\Middleware; + +use Exception; +use OC\Authentication\Exceptions\TwoFactorAuthRequiredException; +use OC\Authentication\Exceptions\UserAlreadyLoggedInException; +use OC\Authentication\TwoFactorAuth\Manager; +use OC\Core\Controller\TwoFactorChallengeController; +use OC\User\Session; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Middleware; +use OCP\AppFramework\Utility\IControllerMethodReflector; +use OCP\ISession; +use OCP\IURLGenerator; + +class TwoFactorMiddleware extends Middleware { + + /** @var Manager */ + private $twoFactorManager; + + /** @var Session */ + private $userSession; + + /** @var ISession */ + private $session; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var IControllerMethodReflector */ + private $reflector; + + /** + * @param Manager $twoFactorManager + * @param Session $userSession + * @param ISession $session + * @param IURLGenerator $urlGenerator + */ + public function __construct(Manager $twoFactorManager, Session $userSession, ISession $session, + IURLGenerator $urlGenerator, IControllerMethodReflector $reflector) { + $this->twoFactorManager = $twoFactorManager; + $this->userSession = $userSession; + $this->session = $session; + $this->urlGenerator = $urlGenerator; + $this->reflector = $reflector; + } + + /** + * @param Controller $controller + * @param string $methodName + */ + public function beforeController($controller, $methodName) { + if ($this->reflector->hasAnnotation('PublicPage')) { + // Don't block public pages + return; + } + + if ($this->userSession->isLoggedIn()) { + $user = $this->userSession->getUser(); + + if ($this->twoFactorManager->isTwoFactorAuthenticated($user)) { + $this->checkTwoFactor($controller, $methodName); + } + } + // TODO: dont check/enforce 2FA if a auth token is used + } + + private function checkTwoFactor($controller, $methodName) { + // If two-factor auth is in progress disallow access to any controllers + // defined within "LoginController". + $needsSecondFactor = $this->twoFactorManager->needsSecondFactor(); + $twoFactor = $controller instanceof TwoFactorChallengeController; + + // Disallow access to any controller if 2FA needs to be checked + if ($needsSecondFactor && !$twoFactor) { + throw new TwoFactorAuthRequiredException(); + } + + // Allow access to the two-factor controllers only if two-factor authentication + // is in progress. + if (!$needsSecondFactor && $twoFactor) { + throw new UserAlreadyLoggedInException(); + } + } + + public function afterException($controller, $methodName, Exception $exception) { + if ($exception instanceof TwoFactorAuthRequiredException) { + return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); + } + if ($exception instanceof UserAlreadyLoggedInException) { + return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index')); + } + } + +} diff --git a/core/css/styles.css b/core/css/styles.css index 736ddc6be01..d8d53bd0324 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -31,6 +31,12 @@ body { background-size: cover; } +.two-factor-provider { + text-align: center; + width: 100%; + display: inline-block; +} + .float-spinner { height: 32px; display: none; diff --git a/core/routes.php b/core/routes.php index 70909352000..053c5d888a7 100644 --- a/core/routes.php +++ b/core/routes.php @@ -46,6 +46,9 @@ $application->registerRoutes($this, [ ['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'], ['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'], ['name' => 'token#generateToken', 'url' => '/token/generate', 'verb' => 'POST'], + ['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'], ], ]); diff --git a/core/templates/twofactorselectchallenge.php b/core/templates/twofactorselectchallenge.php new file mode 100644 index 00000000000..6db8c69d7ac --- /dev/null +++ b/core/templates/twofactorselectchallenge.php @@ -0,0 +1,16 @@ +<fieldset class="warning"> + <legend><strong><?php p($l->t('Two-step verification')) ?></strong></legend> + <p><?php p($l->t('Enhanced security has been enabled for your account. Please authenticate using a second factor.')) ?></p> +</fieldset> +<fieldset class="warning"> +<ul> +<?php foreach ($_['providers'] as $provider): ?> + <li> + <a class="two-factor-provider" + href="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.showChallenge', ['challengeProviderId' => $provider->getId()])) ?>"> + <?php p($provider->getDescription()) ?> + </a> + </li> +<?php endforeach; ?> +</ul> +</fieldset>
\ No newline at end of file diff --git a/core/templates/twofactorshowchallenge.php b/core/templates/twofactorshowchallenge.php new file mode 100644 index 00000000000..66f5ed312ec --- /dev/null +++ b/core/templates/twofactorshowchallenge.php @@ -0,0 +1,19 @@ +<?php +/** @var $l OC_L10N */ +/** @var $_ array */ +/* @var $error boolean */ +$error = $_['error']; +/* @var $provider OCP\Authentication\TwoFactorAuth\IProvider */ +$provider = $_['provider']; +/* @var $template string */ +$template = $_['template']; +?> + +<fieldset class="warning"> + <legend><strong><?php p($provider->getDisplayName()); ?></strong></legend> + <p><?php p($l->t('Please authenticate using the selected factor.')) ?></p> +</fieldset> +<?php if ($error): ?> +<span class="warning"><?php p($l->t('An error occured while verifying the token')); ?></span> +<?php endif; ?> +<?php print_unescaped($template); ?> |