parent
dec3f9ebcb
commit
dfb4d426c2
@ -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()])); |
||||
} |
||||
|
||||
} |
||||
@ -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')); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -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> |
||||
@ -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); ?> |
||||
@ -0,0 +1,29 @@ |
||||
<?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\Authentication\Exceptions; |
||||
|
||||
use Exception; |
||||
|
||||
class LoginRequiredException extends Exception { |
||||
|
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
<?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\Authentication\Exceptions; |
||||
|
||||
use Exception; |
||||
|
||||
class TwoFactorAuthRequiredException extends Exception { |
||||
|
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
<?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\Authentication\Exceptions; |
||||
|
||||
use Exception; |
||||
|
||||
class UserAlreadyLoggedInException extends Exception { |
||||
|
||||
} |
||||
@ -0,0 +1,141 @@ |
||||
<?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\Authentication\TwoFactorAuth; |
||||
|
||||
use OC; |
||||
use OC\App\AppManager; |
||||
use OCP\AppFramework\QueryException; |
||||
use OCP\Authentication\TwoFactorAuth\IProvider; |
||||
use OCP\ISession; |
||||
use OCP\IUser; |
||||
|
||||
class Manager { |
||||
|
||||
const SESSION_UID_KEY = 'two_factor_auth_uid'; |
||||
|
||||
/** @var AppManager */ |
||||
private $appManager; |
||||
|
||||
/** @var ISession */ |
||||
private $session; |
||||
|
||||
/** |
||||
* @param AppManager $appManager |
||||
* @param ISession $session |
||||
*/ |
||||
public function __construct(AppManager $appManager, ISession $session) { |
||||
$this->appManager = $appManager; |
||||
$this->session = $session; |
||||
} |
||||
|
||||
/** |
||||
* Determine whether the user must provide a second factor challenge |
||||
* |
||||
* @param IUser $user |
||||
* @return boolean |
||||
*/ |
||||
public function isTwoFactorAuthenticated(IUser $user) { |
||||
return count($this->getProviders($user)) > 0; |
||||
} |
||||
|
||||
/** |
||||
* Get a 2FA provider by its ID |
||||
* |
||||
* @param IUser $user |
||||
* @param string $challengeProviderId |
||||
* @return IProvider|null |
||||
*/ |
||||
public function getProvider(IUser $user, $challengeProviderId) { |
||||
$providers = $this->getProviders($user); |
||||
return isset($providers[$challengeProviderId]) ? $providers[$challengeProviderId] : null; |
||||
} |
||||
|
||||
/** |
||||
* Get the list of 2FA providers for the given user |
||||
* |
||||
* @param IUser $user |
||||
* @return IProvider[] |
||||
*/ |
||||
public function getProviders(IUser $user) { |
||||
$allApps = $this->appManager->getEnabledAppsForUser($user); |
||||
$providers = []; |
||||
|
||||
foreach ($allApps as $appId) { |
||||
$info = $this->appManager->getAppInfo($appId); |
||||
$providerClasses = $info['two-factor-providers']; |
||||
foreach ($providerClasses as $class) { |
||||
try { |
||||
$provider = OC::$server->query($class); |
||||
$providers[$provider->getId()] = $provider; |
||||
} catch (QueryException $exc) { |
||||
// Provider class can not be resolved, ignore it |
||||
} |
||||
} |
||||
} |
||||
|
||||
return array_filter($providers, function ($provider) use ($user) { |
||||
/* @var $provider IProvider */ |
||||
return $provider->isTwoFactorAuthEnabledForUser($user); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Verify the given challenge |
||||
* |
||||
* @param string $providerId |
||||
* @param IUser $user |
||||
* @param string $challenge |
||||
* @return boolean |
||||
*/ |
||||
public function verifyChallenge($providerId, IUser $user, $challenge) { |
||||
$provider = $this->getProvider($user, $providerId); |
||||
if (is_null($provider)) { |
||||
return false; |
||||
} |
||||
|
||||
$result = $provider->verifyChallenge($user, $challenge); |
||||
if ($result) { |
||||
$this->session->remove(self::SESSION_UID_KEY); |
||||
} |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Check if the currently logged in user needs to pass 2FA |
||||
* |
||||
* @return boolean |
||||
*/ |
||||
public function needsSecondFactor() { |
||||
return $this->session->exists(self::SESSION_UID_KEY); |
||||
} |
||||
|
||||
/** |
||||
* Prepare the 2FA login (set session value) |
||||
* |
||||
* @param IUser $user |
||||
*/ |
||||
public function prepareTwoFactorLogin(IUser $user) { |
||||
$this->session->set(self::SESSION_UID_KEY, $user->getUID()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,93 @@ |
||||
<?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 OCP\Authentication\TwoFactorAuth; |
||||
|
||||
use OCP\IUser; |
||||
use OCP\Template; |
||||
|
||||
/** |
||||
* @since 9.1.0 |
||||
*/ |
||||
interface IProvider { |
||||
|
||||
/** |
||||
* Get unique identifier of this 2FA provider |
||||
* |
||||
* @since 9.1.0 |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getId(); |
||||
|
||||
/** |
||||
* Get the display name for selecting the 2FA provider |
||||
* |
||||
* Example: "Email" |
||||
* |
||||
* @since 9.1.0 |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getDisplayName(); |
||||
|
||||
/** |
||||
* Get the description for selecting the 2FA provider |
||||
* |
||||
* Example: "Get a token via e-mail" |
||||
* |
||||
* @since 9.1.0 |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getDescription(); |
||||
|
||||
/** |
||||
* Get the template for rending the 2FA provider view |
||||
* |
||||
* @since 9.1.0 |
||||
* |
||||
* @param IUser $user |
||||
* @return Template |
||||
*/ |
||||
public function getTemplate(IUser $user); |
||||
|
||||
/** |
||||
* Verify the given challenge |
||||
* |
||||
* @since 9.1.0 |
||||
* |
||||
* @param IUser $user |
||||
* @param string $challenge |
||||
*/ |
||||
public function verifyChallenge(IUser $user, $challenge); |
||||
|
||||
/** |
||||
* Decides whether 2FA is enabled for the given user |
||||
* |
||||
* @since 9.1.0 |
||||
* |
||||
* @param IUser $user |
||||
* @return boolean |
||||
*/ |
||||
public function isTwoFactorAuthEnabledForUser(IUser $user); |
||||
} |
||||
@ -0,0 +1,219 @@ |
||||
<?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 Test\TestCase; |
||||
|
||||
class TwoFactorChallengeControllerTest extends TestCase { |
||||
|
||||
private $request; |
||||
private $twoFactorManager; |
||||
private $userSession; |
||||
private $session; |
||||
private $urlGenerator; |
||||
|
||||
/** TwoFactorChallengeController */ |
||||
private $controller; |
||||
|
||||
protected function setUp() { |
||||
parent::setUp(); |
||||
|
||||
$this->request = $this->getMock('\OCP\IRequest'); |
||||
$this->twoFactorManager = $this->getMockBuilder('\OC\Authentication\TwoFactorAuth\Manager') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
$this->userSession = $this->getMock('\OCP\IUserSession'); |
||||
$this->session = $this->getMock('\OCP\ISession'); |
||||
$this->urlGenerator = $this->getMock('\OCP\IURLGenerator'); |
||||
|
||||
$this->controller = new TwoFactorChallengeController( |
||||
'core', $this->request, $this->twoFactorManager, $this->userSession, $this->session, $this->urlGenerator |
||||
); |
||||
} |
||||
|
||||
public function testSelectChallenge() { |
||||
$user = $this->getMock('\OCP\IUser'); |
||||
$providers = [ |
||||
'prov1', |
||||
'prov2', |
||||
]; |
||||
|
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->will($this->returnValue($user)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('getProviders') |
||||
->with($user) |
||||
->will($this->returnValue($providers)); |
||||
|
||||
$expected = new \OCP\AppFramework\Http\TemplateResponse('core', 'twofactorselectchallenge', [ |
||||
'providers' => $providers, |
||||
], 'guest'); |
||||
|
||||
$this->assertEquals($expected, $this->controller->selectChallenge()); |
||||
} |
||||
|
||||
public function testShowChallenge() { |
||||
$user = $this->getMock('\OCP\IUser'); |
||||
$provider = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
$tmpl = $this->getMockBuilder('\OCP\Template') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
|
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->will($this->returnValue($user)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('getProvider') |
||||
->with($user, 'myprovider') |
||||
->will($this->returnValue($provider)); |
||||
|
||||
$this->session->expects($this->once()) |
||||
->method('exists') |
||||
->with('two_factor_auth_error') |
||||
->will($this->returnValue(true)); |
||||
$this->session->expects($this->once()) |
||||
->method('remove') |
||||
->with('two_factor_auth_error'); |
||||
$provider->expects($this->once()) |
||||
->method('getTemplate') |
||||
->with($user) |
||||
->will($this->returnValue($tmpl)); |
||||
$tmpl->expects($this->once()) |
||||
->method('fetchPage') |
||||
->will($this->returnValue('<html/>')); |
||||
|
||||
$expected = new \OCP\AppFramework\Http\TemplateResponse('core', 'twofactorshowchallenge', [ |
||||
'error' => true, |
||||
'provider' => $provider, |
||||
'template' => '<html/>', |
||||
], 'guest'); |
||||
|
||||
$this->assertEquals($expected, $this->controller->showChallenge('myprovider')); |
||||
} |
||||
|
||||
public function testShowInvalidChallenge() { |
||||
$user = $this->getMock('\OCP\IUser'); |
||||
|
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->will($this->returnValue($user)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('getProvider') |
||||
->with($user, 'myprovider') |
||||
->will($this->returnValue(null)); |
||||
$this->urlGenerator->expects($this->once()) |
||||
->method('linkToRoute') |
||||
->with('core.TwoFactorChallenge.selectChallenge') |
||||
->will($this->returnValue('select/challenge/url')); |
||||
|
||||
$expected = new \OCP\AppFramework\Http\RedirectResponse('select/challenge/url'); |
||||
|
||||
$this->assertEquals($expected, $this->controller->showChallenge('myprovider')); |
||||
} |
||||
|
||||
public function testSolveChallenge() { |
||||
$user = $this->getMock('\OCP\IUser'); |
||||
$provider = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
|
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->will($this->returnValue($user)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('getProvider') |
||||
->with($user, 'myprovider') |
||||
->will($this->returnValue($provider)); |
||||
|
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('verifyChallenge') |
||||
->with('myprovider', $user, 'token') |
||||
->will($this->returnValue(true)); |
||||
$this->urlGenerator->expects($this->once()) |
||||
->method('linkToRoute') |
||||
->with('files.view.index') |
||||
->will($this->returnValue('files/index/url')); |
||||
|
||||
$expected = new \OCP\AppFramework\Http\RedirectResponse('files/index/url'); |
||||
$this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token')); |
||||
} |
||||
|
||||
public function testSolveChallengeInvalidProvider() { |
||||
$user = $this->getMock('\OCP\IUser'); |
||||
|
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->will($this->returnValue($user)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('getProvider') |
||||
->with($user, 'myprovider') |
||||
->will($this->returnValue(null)); |
||||
$this->urlGenerator->expects($this->once()) |
||||
->method('linkToRoute') |
||||
->with('core.TwoFactorChallenge.selectChallenge') |
||||
->will($this->returnValue('select/challenge/url')); |
||||
|
||||
$expected = new \OCP\AppFramework\Http\RedirectResponse('select/challenge/url'); |
||||
|
||||
$this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token')); |
||||
} |
||||
|
||||
public function testSolveInvalidChallenge() { |
||||
$user = $this->getMock('\OCP\IUser'); |
||||
$provider = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
|
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->will($this->returnValue($user)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('getProvider') |
||||
->with($user, 'myprovider') |
||||
->will($this->returnValue($provider)); |
||||
|
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('verifyChallenge') |
||||
->with('myprovider', $user, 'token') |
||||
->will($this->returnValue(false)); |
||||
$this->session->expects($this->once()) |
||||
->method('set') |
||||
->with('two_factor_auth_error', true); |
||||
$this->urlGenerator->expects($this->once()) |
||||
->method('linkToRoute') |
||||
->with('core.TwoFactorChallenge.showChallenge', [ |
||||
'challengeProviderId' => 'myprovider', |
||||
]) |
||||
->will($this->returnValue('files/index/url')); |
||||
$provider->expects($this->once()) |
||||
->method('getId') |
||||
->will($this->returnValue('myprovider')); |
||||
|
||||
$expected = new \OCP\AppFramework\Http\RedirectResponse('files/index/url'); |
||||
$this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token')); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,182 @@ |
||||
<?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 Test\TestCase; |
||||
|
||||
class TwoFactorMiddlewareTest extends TestCase { |
||||
|
||||
private $twoFactorManager; |
||||
private $userSession; |
||||
private $session; |
||||
private $urlGenerator; |
||||
private $reflector; |
||||
|
||||
/** @var TwoFactorMiddleware */ |
||||
private $middleware; |
||||
|
||||
protected function setUp() { |
||||
parent::setUp(); |
||||
|
||||
$this->twoFactorManager = $this->getMockBuilder('\OC\Authentication\TwoFactorAuth\Manager') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
$this->userSession = $this->getMockBuilder('\OC\User\Session') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
$this->session = $this->getMock('\OCP\ISession'); |
||||
$this->urlGenerator = $this->getMock('\OCP\IURLGenerator'); |
||||
$this->reflector = $this->getMock('\OCP\AppFramework\Utility\IControllerMethodReflector'); |
||||
|
||||
$this->middleware = new TwoFactorMiddleware($this->twoFactorManager, $this->userSession, $this->session, $this->urlGenerator, $this->reflector); |
||||
} |
||||
|
||||
public function testBeforeControllerNotLoggedIn() { |
||||
$this->reflector->expects($this->once()) |
||||
->method('hasAnnotation') |
||||
->with('PublicPage') |
||||
->will($this->returnValue(false)); |
||||
$this->userSession->expects($this->once()) |
||||
->method('isLoggedIn') |
||||
->will($this->returnValue(false)); |
||||
|
||||
$this->userSession->expects($this->never()) |
||||
->method('getUser'); |
||||
|
||||
$this->middleware->beforeController(null, 'index'); |
||||
} |
||||
|
||||
public function testBeforeControllerPublicPage() { |
||||
$this->reflector->expects($this->once()) |
||||
->method('hasAnnotation') |
||||
->with('PublicPage') |
||||
->will($this->returnValue(true)); |
||||
$this->userSession->expects($this->never()) |
||||
->method('isLoggedIn'); |
||||
|
||||
$this->middleware->beforeController(null, 'create'); |
||||
} |
||||
|
||||
public function testBeforeControllerNoTwoFactorCheckNeeded() { |
||||
$user = $this->getMock('\OCP\IUser'); |
||||
|
||||
$this->reflector->expects($this->once()) |
||||
->method('hasAnnotation') |
||||
->with('PublicPage') |
||||
->will($this->returnValue(false)); |
||||
$this->userSession->expects($this->once()) |
||||
->method('isLoggedIn') |
||||
->will($this->returnValue(true)); |
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->will($this->returnValue($user)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('isTwoFactorAuthenticated') |
||||
->with($user) |
||||
->will($this->returnValue(false)); |
||||
|
||||
$this->middleware->beforeController(null, 'index'); |
||||
} |
||||
|
||||
/** |
||||
* @expectedException \OC\Authentication\Exceptions\TwoFactorAuthRequiredException |
||||
*/ |
||||
public function testBeforeControllerTwoFactorAuthRequired() { |
||||
$user = $this->getMock('\OCP\IUser'); |
||||
|
||||
$this->reflector->expects($this->once()) |
||||
->method('hasAnnotation') |
||||
->with('PublicPage') |
||||
->will($this->returnValue(false)); |
||||
$this->userSession->expects($this->once()) |
||||
->method('isLoggedIn') |
||||
->will($this->returnValue(true)); |
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->will($this->returnValue($user)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('isTwoFactorAuthenticated') |
||||
->with($user) |
||||
->will($this->returnValue(true)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('needsSecondFactor') |
||||
->will($this->returnValue(true)); |
||||
|
||||
$this->middleware->beforeController(null, 'index'); |
||||
} |
||||
|
||||
/** |
||||
* @expectedException \OC\Authentication\Exceptions\UserAlreadyLoggedInException |
||||
*/ |
||||
public function testBeforeControllerUserAlreadyLoggedIn() { |
||||
$user = $this->getMock('\OCP\IUser'); |
||||
|
||||
$this->reflector->expects($this->once()) |
||||
->method('hasAnnotation') |
||||
->with('PublicPage') |
||||
->will($this->returnValue(false)); |
||||
$this->userSession->expects($this->once()) |
||||
->method('isLoggedIn') |
||||
->will($this->returnValue(true)); |
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->will($this->returnValue($user)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('isTwoFactorAuthenticated') |
||||
->with($user) |
||||
->will($this->returnValue(true)); |
||||
$this->twoFactorManager->expects($this->once()) |
||||
->method('needsSecondFactor') |
||||
->will($this->returnValue(false)); |
||||
|
||||
$twoFactorChallengeController = $this->getMockBuilder('\OC\Core\Controller\TwoFactorChallengeController') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
$this->middleware->beforeController($twoFactorChallengeController, 'index'); |
||||
} |
||||
|
||||
public function testAfterExceptionTwoFactorAuthRequired() { |
||||
$ex = new \OC\Authentication\Exceptions\TwoFactorAuthRequiredException(); |
||||
|
||||
$this->urlGenerator->expects($this->once()) |
||||
->method('linkToRoute') |
||||
->with('core.TwoFactorChallenge.selectChallenge') |
||||
->will($this->returnValue('redirect/url')); |
||||
$expected = new \OCP\AppFramework\Http\RedirectResponse('redirect/url'); |
||||
|
||||
$this->assertEquals($expected, $this->middleware->afterException(null, 'index', $ex)); |
||||
} |
||||
|
||||
public function testAfterException() { |
||||
$ex = new \OC\Authentication\Exceptions\UserAlreadyLoggedInException(); |
||||
|
||||
$this->urlGenerator->expects($this->once()) |
||||
->method('linkToRoute') |
||||
->with('files.view.index') |
||||
->will($this->returnValue('redirect/url')); |
||||
$expected = new \OCP\AppFramework\Http\RedirectResponse('redirect/url'); |
||||
|
||||
$this->assertEquals($expected, $this->middleware->afterException(null, 'index', $ex)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,187 @@ |
||||
<?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 Test\Authentication\TwoFactorAuth; |
||||
|
||||
use Test\TestCase; |
||||
use OC\Authentication\TwoFactorAuth\Manager; |
||||
|
||||
class ManagerTest extends TestCase { |
||||
|
||||
/** @var OCP\IUser */ |
||||
private $user; |
||||
|
||||
/** @var OC\App\AppManager */ |
||||
private $appManager; |
||||
|
||||
/** @var OCP\ISession */ |
||||
private $session; |
||||
|
||||
/** @var Manager */ |
||||
private $manager; |
||||
|
||||
/** @var \OCP\IConfig */ |
||||
private $config; |
||||
|
||||
/** @var \OCP\Authentication\TwoFactorAuth\IProvider */ |
||||
private $fakeProvider; |
||||
|
||||
protected function setUp() { |
||||
parent::setUp(); |
||||
|
||||
$this->user = $this->getMock('\OCP\IUser'); |
||||
$this->appManager = $this->getMockBuilder('\OC\App\AppManager') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
$this->session = $this->getMock('\OCP\ISession'); |
||||
$this->config = $this->getMock('\OCP\IConfig'); |
||||
|
||||
$this->manager = new Manager($this->appManager, $this->session, $this->config); |
||||
|
||||
$this->fakeProvider = $this->getMock('\OCP\Authentication\TwoFactorAuth\IProvider'); |
||||
$this->fakeProvider->expects($this->any()) |
||||
->method('getId') |
||||
->will($this->returnValue('email')); |
||||
$this->fakeProvider->expects($this->any()) |
||||
->method('isTwoFactorAuthEnabledForUser') |
||||
->will($this->returnValue(true)); |
||||
\OC::$server->registerService('\OCA\MyCustom2faApp\FakeProvider', function() { |
||||
return $this->fakeProvider; |
||||
}); |
||||
} |
||||
|
||||
private function prepareProviders() { |
||||
$this->appManager->expects($this->any()) |
||||
->method('getEnabledAppsForUser') |
||||
->with($this->user) |
||||
->will($this->returnValue(['mycustom2faapp'])); |
||||
|
||||
$this->appManager->expects($this->once()) |
||||
->method('getAppInfo') |
||||
->with('mycustom2faapp') |
||||
->will($this->returnValue([ |
||||
'two-factor-providers' => [ |
||||
'\OCA\MyCustom2faApp\FakeProvider', |
||||
], |
||||
])); |
||||
} |
||||
|
||||
public function testIsTwoFactorAuthenticated() { |
||||
$this->prepareProviders(); |
||||
|
||||
$user = $this->getMock('\OCP\IUser'); |
||||
$user->expects($this->once()) |
||||
->method('getUID') |
||||
->will($this->returnValue('user123')); |
||||
$this->config->expects($this->once()) |
||||
->method('getUserValue') |
||||
->with('user123', 'core', 'two_factor_auth_disabled', 0) |
||||
->will($this->returnValue(0)); |
||||
|
||||
$this->assertTrue($this->manager->isTwoFactorAuthenticated($user)); |
||||
} |
||||
|
||||
public function testGetProvider() { |
||||
$this->prepareProviders(); |
||||
|
||||
$this->assertSame($this->fakeProvider, $this->manager->getProvider($this->user, 'email')); |
||||
} |
||||
|
||||
public function testGetInvalidProvider() { |
||||
$this->prepareProviders(); |
||||
|
||||
$this->assertSame(null, $this->manager->getProvider($this->user, 'nonexistent')); |
||||
} |
||||
|
||||
public function testGetProviders() { |
||||
$this->prepareProviders(); |
||||
$expectedProviders = [ |
||||
'email' => $this->fakeProvider, |
||||
]; |
||||
|
||||
$this->assertEquals($expectedProviders, $this->manager->getProviders($this->user)); |
||||
} |
||||
|
||||
public function testVerifyChallenge() { |
||||
$this->prepareProviders(); |
||||
|
||||
$challenge = 'passme'; |
||||
$this->fakeProvider->expects($this->once()) |
||||
->method('verifyChallenge') |
||||
->with($this->user, $challenge) |
||||
->will($this->returnValue(true)); |
||||
$this->session->expects($this->once()) |
||||
->method('remove') |
||||
->with('two_factor_auth_uid'); |
||||
|
||||
$this->assertEquals(true, $this->manager->verifyChallenge('email', $this->user, $challenge)); |
||||
} |
||||
|
||||
public function testVerifyChallengeInvalidProviderId() { |
||||
$this->prepareProviders(); |
||||
|
||||
$challenge = 'passme'; |
||||
$this->fakeProvider->expects($this->never()) |
||||
->method('verifyChallenge') |
||||
->with($this->user, $challenge); |
||||
$this->session->expects($this->never()) |
||||
->method('remove'); |
||||
|
||||
$this->assertEquals(false, $this->manager->verifyChallenge('dontexist', $this->user, $challenge)); |
||||
} |
||||
|
||||
public function testVerifyInvalidChallenge() { |
||||
$this->prepareProviders(); |
||||
|
||||
$challenge = 'dontpassme'; |
||||
$this->fakeProvider->expects($this->once()) |
||||
->method('verifyChallenge') |
||||
->with($this->user, $challenge) |
||||
->will($this->returnValue(false)); |
||||
$this->session->expects($this->never()) |
||||
->method('remove'); |
||||
|
||||
$this->assertEquals(false, $this->manager->verifyChallenge('email', $this->user, $challenge)); |
||||
} |
||||
|
||||
public function testNeedsSecondFactor() { |
||||
$this->session->expects($this->once()) |
||||
->method('exists') |
||||
->with('two_factor_auth_uid') |
||||
->will($this->returnValue(false)); |
||||
|
||||
$this->assertequals(false, $this->manager->needsSecondFactor()); |
||||
} |
||||
|
||||
public function testPrepareTwoFactorLogin() { |
||||
$this->user->expects($this->once()) |
||||
->method('getUID') |
||||
->will($this->returnValue('ferdinand')); |
||||
|
||||
$this->session->expects($this->once()) |
||||
->method('set') |
||||
->with('two_factor_auth_uid', 'ferdinand'); |
||||
|
||||
$this->manager->prepareTwoFactorLogin($this->user); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue