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

two factor auth #24559

Merged
merged 2 commits into from
May 23, 2016
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
1 change: 1 addition & 0 deletions apps/dav/lib/Connector/Sabre/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ protected function validateUserPass($username, $password) {
return true;
} else {
\OC_Util::setUpFS(); //login hooks may need early access to the filesystem
// TODO: do not allow basic auth if the user is 2FA enforced
if($this->userSession->login($username, $password)) {
$this->userSession->createSessionToken($this->request, $username, $password);
\OC_Util::setUpFS($this->userSession->getUser()->getUID());
Expand Down
16 changes: 15 additions & 1 deletion core/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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;
Expand Down Expand Up @@ -101,9 +102,19 @@ public function __construct(array $urlParams=array()){
$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'),
Expand Down Expand Up @@ -168,6 +179,9 @@ public function __construct(array $urlParams=array()){
$container->registerService('DefaultEmailAddress', function() {
return Util::getDefaultEmailAddress('lostpassword-noreply');
});
$container->registerService('TwoFactorAuthManager', function(SimpleContainer $c) {
return $c->query('ServerContainer')->getTwoFactorAuthManager();
});
}

}
65 changes: 65 additions & 0 deletions core/Command/TwoFactorAuth/Disable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

/**
* @author Christoph Wurst <[email protected]>
*
* @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\Command\TwoFactorAuth;

use OC\Authentication\TwoFactorAuth\Manager;
use OC\User\Manager as UserManager;
use OC\Core\Command\Base;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Disable extends Base {

/** @var Manager */
private $manager;

/** @var UserManager */
private $userManager;

public function __construct(Manager $manager, UserManager $userManager) {
parent::__construct('twofactorauth:disable');
$this->manager = $manager;
$this->userManager = $userManager;
}

protected function configure() {
parent::configure();

$this->setName('twofactorauth:disable');
$this->setDescription('Disable two-factor authentication for a user');
$this->addArgument('uid', InputArgument::REQUIRED);
}

protected function execute(InputInterface $input, OutputInterface $output) {
$uid = $input->getArgument('uid');
$user = $this->userManager->get($uid);
if (is_null($user)) {
$output->writeln("<error>Invalid UID</error>");
return;
}
$this->manager->disableTwoFactorAuthentication($user);
$output->writeln("Two-factor authentication disabled for user $uid");
}

}
65 changes: 65 additions & 0 deletions core/Command/TwoFactorAuth/Enable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

/**
* @author Christoph Wurst <[email protected]>
*
* @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\Command\TwoFactorAuth;

use OC\Authentication\TwoFactorAuth\Manager;
use OC\User\Manager as UserManager;
use OC\Core\Command\Base;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Enable extends Base {

/** @var Manager */
private $manager;

/** @var UserManager */
private $userManager;

public function __construct(Manager $manager, UserManager $userManager) {
parent::__construct('twofactorauth:enable');
$this->manager = $manager;
$this->userManager = $userManager;
}

protected function configure() {
parent::configure();

$this->setName('twofactorauth:enable');
$this->setDescription('Enable two-factor authentication for a user');
$this->addArgument('uid', InputArgument::REQUIRED);
}

protected function execute(InputInterface $input, OutputInterface $output) {
$uid = $input->getArgument('uid');
$user = $this->userManager->get($uid);
if (is_null($user)) {
$output->writeln("<error>Invalid UID</error>");
return;
}
$this->manager->enableTwoFactorAuthentication($user);
$output->writeln("Two-factor authentication enabled for user $uid");
}

}
16 changes: 14 additions & 2 deletions core/Controller/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -54,6 +54,9 @@ class LoginController extends Controller {
/** @var IURLGenerator */
private $urlGenerator;

/** @var Manager */
private $twoFactorManager;

/**
* @param string $appName
* @param IRequest $request
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -167,6 +172,7 @@ public function showLoginForm($user, $redirect_url, $remember_login) {
*/
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);
Expand All @@ -185,6 +191,12 @@ public function tryLogin($user, $password, $redirect_url) {
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 @
Expand Down
134 changes: 134 additions & 0 deletions core/Controller/TwoFactorChallengeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

/**
* @author Christoph Wurst <[email protected]>
*
* @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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHPDoc :)

ISession $session, IURLGenerator $urlGenerator) {
parent::__construct($appName, $request);
$this->twoFactorManager = $twoFactorManager;
$this->userSession = $userSession;
$this->session = $session;
$this->urlGenerator = $urlGenerator;
}

/**
* @NoCSRFRequired
* @PublicPage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a new annotation like @TwoFactorRequired or so. No need that these routes are accessible by everyone, just will be confusing.

Or add some more handling in the controller to ensure that not-logged in users won't see it and logged-in users only if the state is required.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. That's what the TwoFactorMiddleware.php should do. Gotcha.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. Anything I need to change here?

Copy link
Member

@LukasReschke LukasReschke May 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at the moment :)

*
* @return TemplateResponse
*/
public function selectChallenge() {
$user = $this->userSession->getUser();
$providers = $this->twoFactorManager->getProviders($user);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which provider wins if there are multiple ones ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None. The users chooses between all providers enabled for the user

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so the challenge page will display multiple boxes, one for each provider

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


$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()]));
}

}
Loading