Add a login chain to reduce the complexity of LoginController::tryLogin

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Christoph Wurst 2019-05-03 15:09:19 +02:00
parent 5893f218c2
commit 170582d4f5
No known key found for this signature in database
GPG Key ID: CC42AC2A7F0E56D8
34 changed files with 2487 additions and 456 deletions

View File

@ -33,7 +33,8 @@
namespace OC\Core\Controller;
use OC\Authentication\Token\IToken;
use OC\Authentication\Login\Chain;
use OC\Authentication\Login\LoginData;
use OC\Authentication\TwoFactorAuth\Manager;
use OC\Security\Bruteforce\Throttler;
use OC\User\Session;
@ -44,17 +45,14 @@ use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\Defaults;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use OC\Hooks\PublicEmitter;
use OCP\Util;
class LoginController extends Controller {
@ -74,27 +72,14 @@ class LoginController extends Controller {
private $urlGenerator;
/** @var ILogger */
private $logger;
/** @var Manager */
private $twoFactorManager;
/** @var Defaults */
private $defaults;
/** @var Throttler */
private $throttler;
/** @var Chain */
private $loginChain;
/**
* @param string $appName
* @param IRequest $request
* @param IUserManager $userManager
* @param IConfig $config
* @param ISession $session
* @param IUserSession $userSession
* @param IURLGenerator $urlGenerator
* @param ILogger $logger
* @param Manager $twoFactorManager
* @param Defaults $defaults
* @param Throttler $throttler
*/
public function __construct($appName,
public function __construct(?string $appName,
IRequest $request,
IUserManager $userManager,
IConfig $config,
@ -102,9 +87,9 @@ class LoginController extends Controller {
IUserSession $userSession,
IURLGenerator $urlGenerator,
ILogger $logger,
Manager $twoFactorManager,
Defaults $defaults,
Throttler $throttler) {
Throttler $throttler,
Chain $loginChain) {
parent::__construct($appName, $request);
$this->userManager = $userManager;
$this->config = $config;
@ -112,9 +97,9 @@ class LoginController extends Controller {
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->twoFactorManager = $twoFactorManager;
$this->defaults = $defaults;
$this->throttler = $throttler;
$this->loginChain = $loginChain;
}
/**
@ -226,8 +211,8 @@ class LoginController extends Controller {
* @param array $parameters
* @return array
*/
private function setPasswordResetParameters(
string $user = null, array $parameters): array {
private function setPasswordResetParameters(?string $user,
array $parameters): array {
if ($user !== null && $user !== '') {
$userObj = $this->userManager->get($user);
} else {
@ -250,12 +235,8 @@ class LoginController extends Controller {
return $parameters;
}
/**
* @param string $redirectUrl
* @return RedirectResponse
*/
private function generateRedirect($redirectUrl) {
if (!is_null($redirectUrl) && $this->userSession->isLoggedIn()) {
private function generateRedirect(?string $redirectUrl): RedirectResponse {
if ($redirectUrl !== null && $this->userSession->isLoggedIn()) {
$location = $this->urlGenerator->getAbsoluteURL(urldecode($redirectUrl));
// Deny the redirect if the URL contains a @
// This prevents unvalidated redirects like ?redirect_url=:user@domain.com
@ -275,16 +256,15 @@ class LoginController extends Controller {
* @param string $user
* @param string $password
* @param string $redirect_url
* @param boolean $remember_login
* @param string $timezone
* @param string $timezone_offset
* @return RedirectResponse
*/
public function tryLogin($user, $password, $redirect_url, $remember_login = true, $timezone = '', $timezone_offset = '') {
if(!is_string($user)) {
throw new \InvalidArgumentException('Username must be string');
}
public function tryLogin(string $user,
string $password,
string $redirect_url = null,
string $timezone = '',
string $timezone_offset = ''): RedirectResponse {
// If the user is already logged in and the CSRF check does not pass then
// simply redirect the user to the correct page as required. This is the
// case when an user has already logged-in, in another tab.
@ -292,96 +272,27 @@ class LoginController extends Controller {
return $this->generateRedirect($redirect_url);
}
if ($this->userManager instanceof PublicEmitter) {
$this->userManager->emit('\OC\User', 'preLogin', array($user, $password));
$data = new LoginData(
$this->request,
$user,
$password,
$redirect_url,
$timezone,
$timezone_offset
);
$result = $this->loginChain->process($data);
if (!$result->isSuccess()) {
return $this->createLoginFailedResponse(
$data->getUsername(),
$user,
$redirect_url,
$result->getErrorMessage()
);
}
$originalUser = $user;
$userObj = $this->userManager->get($user);
if ($userObj !== null && $userObj->isEnabled() === false) {
$this->logger->warning('Login failed: \''. $user . '\' disabled' .
' (Remote IP: \''. $this->request->getRemoteAddress(). '\')',
['app' => 'core']);
return $this->createLoginFailedResponse($user, $originalUser,
$redirect_url, self::LOGIN_MSG_USERDISABLED);
if ($result->getRedirectUrl() !== null) {
return new RedirectResponse($result->getRedirectUrl());
}
// TODO: Add all the insane error handling
/* @var $loginResult IUser */
$loginResult = $this->userManager->checkPasswordNoLogging($user, $password);
if ($loginResult === false) {
$users = $this->userManager->getByEmail($user);
// we only allow login by email if unique
if (count($users) === 1) {
$previousUser = $user;
$user = $users[0]->getUID();
if($user !== $previousUser) {
$loginResult = $this->userManager->checkPassword($user, $password);
}
}
}
if ($loginResult === false) {
$this->logger->warning('Login failed: \''. $user .
'\' (Remote IP: \''. $this->request->getRemoteAddress(). '\')',
['app' => 'core']);
return $this->createLoginFailedResponse($user, $originalUser,
$redirect_url, self::LOGIN_MSG_INVALIDPASSWORD);
}
// TODO: remove password checks from above and let the user session handle failures
// requires https://github.com/owncloud/core/pull/24616
$this->userSession->completeLogin($loginResult, ['loginName' => $user, 'password' => $password]);
$tokenType = IToken::REMEMBER;
if ((int)$this->config->getSystemValue('remember_login_cookie_lifetime', 60*60*24*15) === 0) {
$remember_login = false;
$tokenType = IToken::DO_NOT_REMEMBER;
}
$this->userSession->createSessionToken($this->request, $loginResult->getUID(), $user, $password, $tokenType);
$this->userSession->updateTokens($loginResult->getUID(), $password);
// User has successfully logged in, now remove the password reset link, when it is available
$this->config->deleteUserValue($loginResult->getUID(), 'core', 'lostpassword');
$this->session->set('last-password-confirm', $loginResult->getLastLogin());
if ($timezone_offset !== '') {
$this->config->setUserValue($loginResult->getUID(), 'core', 'timezone', $timezone);
$this->session->set('timezone', $timezone_offset);
}
if ($this->twoFactorManager->isTwoFactorAuthenticated($loginResult)) {
$this->twoFactorManager->prepareTwoFactorLogin($loginResult, $remember_login);
$providers = $this->twoFactorManager->getProviderSet($loginResult)->getPrimaryProviders();
if (count($providers) === 1) {
// Single provider, hence we can redirect to that provider's challenge page directly
/* @var $provider IProvider */
$provider = array_pop($providers);
$url = 'core.TwoFactorChallenge.showChallenge';
$urlParams = [
'challengeProviderId' => $provider->getId(),
];
} else {
$url = 'core.TwoFactorChallenge.selectChallenge';
$urlParams = [];
}
if (!is_null($redirect_url)) {
$urlParams['redirect_url'] = $redirect_url;
}
return new RedirectResponse($this->urlGenerator->linkToRoute($url, $urlParams));
}
if ($remember_login) {
$this->userSession->createRememberMeToken($loginResult);
}
return $this->generateRedirect($redirect_url);
}
@ -398,8 +309,8 @@ class LoginController extends Controller {
$user, $originalUser, $redirect_url, string $loginMessage) {
// Read current user and append if possible we need to
// return the unmodified user otherwise we will leak the login name
$args = !is_null($user) ? ['user' => $originalUser] : [];
if (!is_null($redirect_url)) {
$args = $user !== null ? ['user' => $originalUser] : [];
if ($redirect_url !== null) {
$args['redirect_url'] = $redirect_url;
}
$response = new RedirectResponse(

View File

@ -506,6 +506,22 @@ return array(
'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => $baseDir . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php',
'OC\\Authentication\\LoginCredentials\\Credentials' => $baseDir . '/lib/private/Authentication/LoginCredentials/Credentials.php',
'OC\\Authentication\\LoginCredentials\\Store' => $baseDir . '/lib/private/Authentication/LoginCredentials/Store.php',
'OC\\Authentication\\Login\\ALoginCommand' => $baseDir . '/lib/private/Authentication/Login/ALoginCommand.php',
'OC\\Authentication\\Login\\Chain' => $baseDir . '/lib/private/Authentication/Login/Chain.php',
'OC\\Authentication\\Login\\ClearLostPasswordTokensCommand' => $baseDir . '/lib/private/Authentication/Login/ClearLostPasswordTokensCommand.php',
'OC\\Authentication\\Login\\CompleteLoginCommand' => $baseDir . '/lib/private/Authentication/Login/CompleteLoginCommand.php',
'OC\\Authentication\\Login\\CreateSessionTokenCommand' => $baseDir . '/lib/private/Authentication/Login/CreateSessionTokenCommand.php',
'OC\\Authentication\\Login\\EmailLoginCommand' => $baseDir . '/lib/private/Authentication/Login/EmailLoginCommand.php',
'OC\\Authentication\\Login\\FinishRememberedLoginCommand' => $baseDir . '/lib/private/Authentication/Login/FinishRememberedLoginCommand.php',
'OC\\Authentication\\Login\\LoggedInCheckCommand' => $baseDir . '/lib/private/Authentication/Login/LoggedInCheckCommand.php',
'OC\\Authentication\\Login\\LoginData' => $baseDir . '/lib/private/Authentication/Login/LoginData.php',
'OC\\Authentication\\Login\\LoginResult' => $baseDir . '/lib/private/Authentication/Login/LoginResult.php',
'OC\\Authentication\\Login\\PreLoginHookCommand' => $baseDir . '/lib/private/Authentication/Login/PreLoginHookCommand.php',
'OC\\Authentication\\Login\\SetUserTimezoneCommand' => $baseDir . '/lib/private/Authentication/Login/SetUserTimezoneCommand.php',
'OC\\Authentication\\Login\\TwoFactorCommand' => $baseDir . '/lib/private/Authentication/Login/TwoFactorCommand.php',
'OC\\Authentication\\Login\\UidLoginCommand' => $baseDir . '/lib/private/Authentication/Login/UidLoginCommand.php',
'OC\\Authentication\\Login\\UpdateLastPasswordConfirmCommand' => $baseDir . '/lib/private/Authentication/Login/UpdateLastPasswordConfirmCommand.php',
'OC\\Authentication\\Login\\UserDisabledCheckCommand' => $baseDir . '/lib/private/Authentication/Login/UserDisabledCheckCommand.php',
'OC\\Authentication\\Token\\DefaultToken' => $baseDir . '/lib/private/Authentication/Token/DefaultToken.php',
'OC\\Authentication\\Token\\DefaultTokenCleanupJob' => $baseDir . '/lib/private/Authentication/Token/DefaultTokenCleanupJob.php',
'OC\\Authentication\\Token\\DefaultTokenMapper' => $baseDir . '/lib/private/Authentication/Token/DefaultTokenMapper.php',

View File

@ -536,6 +536,22 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php',
'OC\\Authentication\\LoginCredentials\\Credentials' => __DIR__ . '/../../..' . '/lib/private/Authentication/LoginCredentials/Credentials.php',
'OC\\Authentication\\LoginCredentials\\Store' => __DIR__ . '/../../..' . '/lib/private/Authentication/LoginCredentials/Store.php',
'OC\\Authentication\\Login\\ALoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/ALoginCommand.php',
'OC\\Authentication\\Login\\Chain' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/Chain.php',
'OC\\Authentication\\Login\\ClearLostPasswordTokensCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/ClearLostPasswordTokensCommand.php',
'OC\\Authentication\\Login\\CompleteLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/CompleteLoginCommand.php',
'OC\\Authentication\\Login\\CreateSessionTokenCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/CreateSessionTokenCommand.php',
'OC\\Authentication\\Login\\EmailLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/EmailLoginCommand.php',
'OC\\Authentication\\Login\\FinishRememberedLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/FinishRememberedLoginCommand.php',
'OC\\Authentication\\Login\\LoggedInCheckCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoggedInCheckCommand.php',
'OC\\Authentication\\Login\\LoginData' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoginData.php',
'OC\\Authentication\\Login\\LoginResult' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoginResult.php',
'OC\\Authentication\\Login\\PreLoginHookCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/PreLoginHookCommand.php',
'OC\\Authentication\\Login\\SetUserTimezoneCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/SetUserTimezoneCommand.php',
'OC\\Authentication\\Login\\TwoFactorCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/TwoFactorCommand.php',
'OC\\Authentication\\Login\\UidLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UidLoginCommand.php',
'OC\\Authentication\\Login\\UpdateLastPasswordConfirmCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UpdateLastPasswordConfirmCommand.php',
'OC\\Authentication\\Login\\UserDisabledCheckCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UserDisabledCheckCommand.php',
'OC\\Authentication\\Token\\DefaultToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultToken.php',
'OC\\Authentication\\Token\\DefaultTokenCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultTokenCleanupJob.php',
'OC\\Authentication\\Token\\DefaultTokenMapper' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultTokenMapper.php',

View File

@ -0,0 +1,47 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
abstract class ALoginCommand {
/** @var ALoginCommand */
protected $next;
public function setNext(ALoginCommand $next): ALoginCommand {
$this->next = $next;
return $next;
}
protected function processNextOrFinishSuccessfully(LoginData $loginData): LoginResult {
if ($this->next !== null) {
return $this->next->process($loginData);
} else {
return LoginResult::success($loginData);
}
}
public abstract function process(LoginData $loginData): LoginResult;
}

View File

@ -0,0 +1,111 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
class Chain {
/** @var PreLoginHookCommand */
private $preLoginHookCommand;
/** @var UserDisabledCheckCommand */
private $userDisabledCheckCommand;
/** @var UidLoginCommand */
private $uidLoginCommand;
/** @var EmailLoginCommand */
private $emailLoginCommand;
/** @var LoggedInCheckCommand */
private $loggedInCheckCommand;
/** @var CompleteLoginCommand */
private $completeLoginCommand;
/** @var CreateSessionTokenCommand */
private $createSessionTokenCommand;
/** @var ClearLostPasswordTokensCommand */
private $clearLostPasswordTokensCommand;
/** @var UpdateLastPasswordConfirmCommand */
private $updateLastPasswordConfirmCommand;
/** @var SetUserTimezoneCommand */
private $setUserTimezoneCommand;
/** @var TwoFactorCommand */
private $twoFactorCommand;
/** @var FinishRememberedLoginCommand */
private $finishRememberedLoginCommand;
public function __construct(PreLoginHookCommand $preLoginHookCommand,
UserDisabledCheckCommand $userDisabledCheckCommand,
UidLoginCommand $uidLoginCommand,
EmailLoginCommand $emailLoginCommand,
LoggedInCheckCommand $loggedInCheckCommand,
CompleteLoginCommand $completeLoginCommand,
CreateSessionTokenCommand $createSessionTokenCommand,
ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
SetUserTimezoneCommand $setUserTimezoneCommand,
TwoFactorCommand $twoFactorCommand,
FinishRememberedLoginCommand $finishRememberedLoginCommand
) {
$this->preLoginHookCommand = $preLoginHookCommand;
$this->userDisabledCheckCommand = $userDisabledCheckCommand;
$this->uidLoginCommand = $uidLoginCommand;
$this->emailLoginCommand = $emailLoginCommand;
$this->loggedInCheckCommand = $loggedInCheckCommand;
$this->completeLoginCommand = $completeLoginCommand;
$this->createSessionTokenCommand = $createSessionTokenCommand;
$this->clearLostPasswordTokensCommand = $clearLostPasswordTokensCommand;
$this->updateLastPasswordConfirmCommand = $updateLastPasswordConfirmCommand;
$this->setUserTimezoneCommand = $setUserTimezoneCommand;
$this->twoFactorCommand = $twoFactorCommand;
$this->finishRememberedLoginCommand = $finishRememberedLoginCommand;
}
public function process(LoginData $loginData): LoginResult {
$chain = $this->preLoginHookCommand;
$chain
->setNext($this->userDisabledCheckCommand)
->setNext($this->uidLoginCommand)
->setNext($this->emailLoginCommand)
->setNext($this->loggedInCheckCommand)
->setNext($this->completeLoginCommand)
->setNext($this->createSessionTokenCommand)
->setNext($this->clearLostPasswordTokensCommand)
->setNext($this->updateLastPasswordConfirmCommand)
->setNext($this->setUserTimezoneCommand)
->setNext($this->twoFactorCommand)
->setNext($this->finishRememberedLoginCommand);
return $chain->process($loginData);
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OCP\IConfig;
class ClearLostPasswordTokensCommand extends ALoginCommand {
/** @var IConfig */
private $config;
public function __construct(IConfig $config) {
$this->config = $config;
}
/**
* User has successfully logged in, now remove the password reset link, when it is available
*/
public function process(LoginData $loginData): LoginResult {
$this->config->deleteUserValue(
$loginData->getUser()->getUID(),
'core',
'lostpassword'
);
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OC\User\Session;
class CompleteLoginCommand extends ALoginCommand {
/** @var Session */
private $userSession;
public function __construct(Session $userSession) {
$this->userSession = $userSession;
}
public function process(LoginData $loginData): LoginResult {
$this->userSession->completeLogin(
$loginData->getUser(),
[
'loginName' => $loginData->getUsername(),
'password' => $loginData->getPassword(),
]
);
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OC\Authentication\Token\IToken;
use OC\User\Session;
use OCP\IConfig;
class CreateSessionTokenCommand extends ALoginCommand {
/** @var IConfig */
private $config;
/** @var Session */
private $userSession;
public function __construct(IConfig $config,
Session $userSession) {
$this->config = $config;
$this->userSession = $userSession;
}
public function process(LoginData $loginData): LoginResult {
$tokenType = IToken::REMEMBER;
if ((int)$this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15) === 0) {
$loginData->setRememberLogin(false);
$tokenType = IToken::DO_NOT_REMEMBER;
}
$this->userSession->createSessionToken(
$loginData->getRequest(),
$loginData->getUser()->getUID(),
$loginData->getUsername(),
$loginData->getPassword(),
$tokenType
);
$this->userSession->updateTokens(
$loginData->getUser()->getUID(),
$loginData->getUsername()
);
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OCP\IUserManager;
class EmailLoginCommand extends ALoginCommand {
/** @var IUserManager */
private $userManager;
public function __construct(IUserManager $userManager) {
$this->userManager = $userManager;
}
public function process(LoginData $loginData): LoginResult {
if ($loginData->getUser() === false) {
$users = $this->userManager->getByEmail($loginData->getUsername());
// we only allow login by email if unique
if (count($users) === 1) {
$username = $users[0]->getUID();
if ($username !== $loginData->getUsername()) {
$user = $this->userManager->checkPassword(
$username,
$loginData->getPassword()
);
if ($user !== false) {
$loginData->setUser($user);
$loginData->setUsername($username);
}
}
}
}
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OC\User\Session;
class FinishRememberedLoginCommand extends ALoginCommand {
/** @var Session */
private $userSession;
public function __construct(Session $userSession) {
$this->userSession = $userSession;
}
public function process(LoginData $loginData): LoginResult {
if ($loginData->isRememberLogin()) {
$this->userSession->createRememberMeToken($loginData->getUser());
}
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OC\Core\Controller\LoginController;
use OCP\ILogger;
class LoggedInCheckCommand extends ALoginCommand {
/** @var ILogger */
private $logger;
public function __construct(ILogger $logger) {
$this->logger = $logger;
}
public function process(LoginData $loginData): LoginResult {
if ($loginData->getUser() === false) {
$username = $loginData->getUsername();
$ip = $loginData->getRequest()->getRemoteAddress();
$this->logger->warning("Login failed: $username (Remote IP: $ip)");
return LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
}
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,121 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OCP\IRequest;
use OCP\IUser;
class LoginData {
/** @var IRequest */
private $request;
/** @var string */
private $username;
/** @var string */
private $password;
/** @var string */
private $redirectUrl;
/** @var string */
private $timeZone;
/** @var string */
private $timeZoneOffset;
/** @var IUser|false|null */
private $user = null;
/** @var bool */
private $rememberLogin = true;
public function __construct(IRequest $request,
string $username,
string $password,
string $redirectUrl = null,
string $timeZone = '',
string $timeZoneOffset = '') {
$this->request = $request;
$this->username = $username;
$this->password = $password;
$this->redirectUrl = $redirectUrl;
$this->timeZone = $timeZone;
$this->timeZoneOffset = $timeZoneOffset;
}
public function getRequest(): IRequest {
return $this->request;
}
public function setUsername(string $username): void {
$this->username = $username;
}
public function getUsername(): string {
return $this->username;
}
public function getPassword(): string {
return $this->password;
}
public function getRedirectUrl(): ?string {
return $this->redirectUrl;
}
public function getTimeZone(): string {
return $this->timeZone;
}
public function getTimeZoneOffset(): string {
return $this->timeZoneOffset;
}
/**
* @param IUser|false|null $user
*/
public function setUser($user) {
$this->user = $user;
}
/**
* @return false|IUser|null
*/
public function getUser() {
return $this->user;
}
public function setRememberLogin(bool $rememberLogin): void {
$this->rememberLogin = $rememberLogin;
}
public function isRememberLogin(): bool {
return $this->rememberLogin;
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
class LoginResult {
/** @var bool */
private $success;
/** @var LoginData */
private $loginData;
/** @var string|null */
private $redirectUrl;
/** @var string|null */
private $errorMessage;
private function __construct(bool $success, LoginData $loginData) {
$this->success = $success;
$this->loginData = $loginData;
}
private function setRedirectUrl(string $url) {
$this->redirectUrl = $url;
}
private function setErrorMessage(string $msg) {
$this->errorMessage = $msg;
}
public static function success(LoginData $data, ?string $redirectUrl = null) {
$result = new static(true, $data);
if ($redirectUrl !== null) {
$result->setRedirectUrl($redirectUrl);
}
return $result;
}
public static function failure(LoginData $data, string $msg = null): LoginResult {
$result = new static(false, $data);
if ($msg !== null) {
$result->setErrorMessage($msg);
}
return $result;
}
public function isSuccess(): bool {
return $this->success;
}
public function getRedirectUrl(): ?string {
return $this->redirectUrl;
}
public function getErrorMessage(): ?string {
return $this->errorMessage;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OC\Hooks\PublicEmitter;
use OCP\IUserManager;
class PreLoginHookCommand extends ALoginCommand {
/** @var IUserManager */
private $userManager;
public function __construct(IUserManager $userManager) {
$this->userManager = $userManager;
}
public function process(LoginData $loginData): LoginResult {
if ($this->userManager instanceof PublicEmitter) {
$this->userManager->emit(
'\OC\User',
'preLogin',
[
$loginData->getUsername(),
$loginData->getPassword(),
]
);
}
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OCP\IConfig;
use OCP\ISession;
class SetUserTimezoneCommand extends ALoginCommand {
/** @var IConfig */
private $config;
/** @var ISession */
private $session;
public function __construct(IConfig $config,
ISession $session) {
$this->config = $config;
$this->session = $session;
}
public function process(LoginData $loginData): LoginResult {
if ($loginData->getTimeZoneOffset() !== '') {
$this->config->setUserValue(
$loginData->getUser()->getUID(),
'core',
'timezone',
$loginData->getTimeZone()
);
$this->session->set(
'timezone',
$loginData->getTimeZoneOffset()
);
}
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use function array_pop;
use function count;
use OC\Authentication\TwoFactorAuth\Manager;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\IURLGenerator;
class TwoFactorCommand extends ALoginCommand {
/** @var Manager */
private $twoFactorManager;
/** @var IURLGenerator */
private $urlGenerator;
public function __construct(Manager $twoFactorManager,
IURLGenerator $urlGenerator) {
$this->twoFactorManager = $twoFactorManager;
$this->urlGenerator = $urlGenerator;
}
public function process(LoginData $loginData): LoginResult {
if (!$this->twoFactorManager->isTwoFactorAuthenticated($loginData->getUser())) {
return $this->processNextOrFinishSuccessfully($loginData);
}
$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
/* @var $provider IProvider */
$provider = array_pop($providers);
$url = 'core.TwoFactorChallenge.showChallenge';
$urlParams = [
'challengeProviderId' => $provider->getId(),
];
} else {
$url = 'core.TwoFactorChallenge.selectChallenge';
$urlParams = [];
}
if ($loginData->getRedirectUrl() !== null) {
$urlParams['redirect_url'] = $loginData->getRedirectUrl();
}
return LoginResult::success(
$loginData,
$this->urlGenerator->linkToRoute($url, $urlParams)
);
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OC\User\Manager;
use OCP\IUser;
class UidLoginCommand extends ALoginCommand {
/** @var Manager */
private $userManager;
public function __construct(Manager $userManager) {
$this->userManager = $userManager;
}
/**
* @param LoginData $loginData
*
* @return LoginResult
*/
public function process(LoginData $loginData): LoginResult {
/* @var $loginResult IUser */
$user = $this->userManager->checkPasswordNoLogging(
$loginData->getUsername(),
$loginData->getPassword()
);
$loginData->setUser($user);
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OCP\ISession;
class UpdateLastPasswordConfirmCommand extends ALoginCommand {
/** @var ISession */
private $session;
public function __construct(ISession $session) {
$this->session = $session;
}
public function process(LoginData $loginData): LoginResult {
$this->session->set(
'last-password-confirm',
$loginData->getUser()->getLastLogin()
);
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace OC\Authentication\Login;
use OC\Core\Controller\LoginController;
use OCP\ILogger;
use OCP\IUserManager;
class UserDisabledCheckCommand extends ALoginCommand {
/** @var IUserManager */
private $userManager;
/** @var ILogger */
private $logger;
public function __construct(IUserManager $userManager,
ILogger $logger) {
$this->userManager = $userManager;
$this->logger = $logger;
}
public function process(LoginData $loginData): LoginResult {
$user = $this->userManager->get($loginData->getUsername());
if ($user !== null && $user->isEnabled() === false) {
$username = $loginData->getUsername();
$ip = $loginData->getRequest()->getRemoteAddress();
$this->logger->warning("Login failed: $username disabled (Remote IP: $ip)");
return LoginResult::failure($loginData, LoginController::LOGIN_MSG_USERDISABLED);
}
return $this->processNextOrFinishSuccessfully($loginData);
}
}

View File

@ -196,7 +196,7 @@ class Manager extends PublicEmitter implements IUserManager {
* @internal
* @param string $loginName
* @param string $password
* @return mixed the User object on success, false otherwise
* @return IUser|false the User object on success, false otherwise
*/
public function checkPasswordNoLogging($loginName, $password) {
$loginName = str_replace("\0", '', $loginName);

View File

@ -21,6 +21,9 @@
namespace Tests\Core\Controller;
use OC\Authentication\Login\Chain as LoginChain;
use OC\Authentication\Login\LoginData;
use OC\Authentication\Login\LoginResult;
use OC\Authentication\Token\IToken;
use OC\Authentication\TwoFactorAuth\Manager;
use OC\Authentication\TwoFactorAuth\ProviderSet;
@ -39,32 +42,47 @@ use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class LoginControllerTest extends TestCase {
/** @var LoginController */
private $loginController;
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
/** @var IRequest|MockObject */
private $request;
/** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
/** @var IUserManager|MockObject */
private $userManager;
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
/** @var IConfig|MockObject */
private $config;
/** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
/** @var ISession|MockObject */
private $session;
/** @var Session|\PHPUnit_Framework_MockObject_MockObject */
/** @var Session|MockObject */
private $userSession;
/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
/** @var IURLGenerator|MockObject */
private $urlGenerator;
/** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
/** @var ILogger|MockObject */
private $logger;
/** @var Manager|\PHPUnit_Framework_MockObject_MockObject */
/** @var Manager|MockObject */
private $twoFactorManager;
/** @var Defaults|\PHPUnit_Framework_MockObject_MockObject */
/** @var Defaults|MockObject */
private $defaults;
/** @var Throttler|\PHPUnit_Framework_MockObject_MockObject */
/** @var Throttler|MockObject */
private $throttler;
/** @var LoginChain|MockObject */
private $chain;
public function setUp() {
parent::setUp();
$this->request = $this->createMock(IRequest::class);
@ -77,6 +95,7 @@ class LoginControllerTest extends TestCase {
$this->twoFactorManager = $this->createMock(Manager::class);
$this->defaults = $this->createMock(Defaults::class);
$this->throttler = $this->createMock(Throttler::class);
$this->chain = $this->createMock(LoginChain::class);
$this->request->method('getRemoteAddress')
->willReturn('1.2.3.4');
@ -95,9 +114,9 @@ class LoginControllerTest extends TestCase {
$this->userSession,
$this->urlGenerator,
$this->logger,
$this->twoFactorManager,
$this->defaults,
$this->throttler
$this->throttler,
$this->chain
);
}
@ -292,51 +311,6 @@ class LoginControllerTest extends TestCase {
$this->assertEquals($expectedResponse, $this->loginController->showLoginForm('LdapUser', '', ''));
}
/**
* Asserts that a disabled user can't login and gets the expected response.
*/
public function testLoginForDisabledUser() {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
$user = $this->createMock(IUser::class);
$user->method('getUID')
->willReturn('uid');
$user->method('isEnabled')
->willReturn(false);
$this->request
->expects($this->once())
->method('passesCSRFCheck')
->willReturn(true);
$this->userSession
->method('isLoggedIn')
->willReturn(false);
$loginName = 'iMDisabled';
$password = 'secret';
$this->session
->expects($this->once())
->method('set')
->with('loginMessages', [
[LoginController::LOGIN_MSG_USERDISABLED], []
]);
$this->userManager
->expects($this->once())
->method('get')
->with($loginName)
->willReturn($user);
$expected = new RedirectResponse('');
$expected->throttle(['user' => $loginName]);
$response = $this->loginController->tryLogin(
$loginName, $password, null, false, 'Europe/Berlin', '1'
);
$this->assertEquals($expected, $response);
}
public function testShowLoginFormForUserNamed0() {
$this->userSession
->expects($this->once())
@ -386,43 +360,34 @@ class LoginControllerTest extends TestCase {
->expects($this->once())
->method('passesCSRFCheck')
->willReturn(true);
$this->userManager->expects($this->once())
->method('checkPasswordNoLogging')
->will($this->returnValue(false));
$this->userManager->expects($this->once())
->method('getByEmail')
->with($user)
->willReturn([]);
$loginData = new LoginData(
$this->request,
$user,
$password,
'/apps/files'
);
$loginResult = LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
$this->chain->expects($this->once())
->method('process')
->with($this->equalTo($loginData))
->willReturn($loginResult);
$this->urlGenerator->expects($this->once())
->method('linkToRoute')
->with('core.login.showLoginForm', [
'user' => 'MyUserName',
'user' => $user,
'redirect_url' => '/apps/files',
])
->will($this->returnValue($loginPageUrl));
$this->userSession->expects($this->never())
->method('createSessionToken');
$this->userSession->expects($this->never())
->method('createRememberMeToken');
$this->config->expects($this->never())
->method('deleteUserValue');
$expected = new \OCP\AppFramework\Http\RedirectResponse($loginPageUrl);
$expected->throttle(['user' => 'MyUserName']);
$this->assertEquals($expected, $this->loginController->tryLogin($user, $password, '/apps/files'));
$response = $this->loginController->tryLogin($user, $password, '/apps/files');
$this->assertEquals($expected, $response);
}
public function testLoginWithValidCredentials() {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->will($this->returnValue('uid'));
$loginName = 'loginli';
$user->expects($this->any())
->method('getLastLogin')
->willReturn(123456);
$user = 'MyUserName';
$password = 'secret';
$indexPageUrl = \OC_Util::getDefaultPageUrl();
@ -430,87 +395,25 @@ class LoginControllerTest extends TestCase {
->expects($this->once())
->method('passesCSRFCheck')
->willReturn(true);
$this->userManager->expects($this->once())
->method('checkPasswordNoLogging')
->will($this->returnValue($user));
$this->userSession->expects($this->once())
->method('completeLogin')
->with($user, ['loginName' => $loginName, 'password' => $password]);
$this->userSession->expects($this->once())
->method('createSessionToken')
->with($this->request, $user->getUID(), $loginName, $password, IToken::REMEMBER);
$this->twoFactorManager->expects($this->once())
->method('isTwoFactorAuthenticated')
->with($user)
->will($this->returnValue(false));
$this->config->expects($this->once())
->method('deleteUserValue')
->with('uid', 'core', 'lostpassword');
$this->config->expects($this->once())
->method('setUserValue')
->with('uid', 'core', 'timezone', 'Europe/Berlin');
$this->config
->method('getSystemValue')
->with('remember_login_cookie_lifetime')
->willReturn(1234);
$this->userSession->expects($this->never())
->method('createRememberMeToken');
$this->session->expects($this->exactly(2))
->method('set')
->withConsecutive(
['last-password-confirm', 123456],
['timezone', '1']
);
$loginData = new LoginData(
$this->request,
$user,
$password
);
$loginResult = LoginResult::success($loginData);
$this->chain->expects($this->once())
->method('process')
->with($this->equalTo($loginData))
->willReturn($loginResult);
$expected = new \OCP\AppFramework\Http\RedirectResponse($indexPageUrl);
$this->assertEquals($expected, $this->loginController->tryLogin($loginName, $password, null, false, 'Europe/Berlin', '1'));
}
public function testLoginWithValidCredentialsAndRememberMe() {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->will($this->returnValue('uid'));
$loginName = 'loginli';
$password = 'secret';
$indexPageUrl = \OC_Util::getDefaultPageUrl();
$response = $this->loginController->tryLogin($user, $password);
$this->request
->expects($this->once())
->method('passesCSRFCheck')
->willReturn(true);
$this->userManager->expects($this->once())
->method('checkPasswordNoLogging')
->will($this->returnValue($user));
$this->userSession->expects($this->once())
->method('completeLogin')
->with($user, ['loginName' => $loginName, 'password' => $password]);
$this->userSession->expects($this->once())
->method('createSessionToken')
->with($this->request, $user->getUID(), $loginName, $password, true);
$this->twoFactorManager->expects($this->once())
->method('isTwoFactorAuthenticated')
->with($user)
->will($this->returnValue(false));
$this->config->expects($this->once())
->method('deleteUserValue')
->with('uid', 'core', 'lostpassword');
$this->config
->method('getSystemValue')
->with('remember_login_cookie_lifetime')
->willReturn(1234);
$this->userSession->expects($this->once())
->method('createRememberMeToken')
->with($user);
$expected = new \OCP\AppFramework\Http\RedirectResponse($indexPageUrl);
$this->assertEquals($expected, $this->loginController->tryLogin($loginName, $password, null, true));
$this->assertEquals($expected, $response);
}
public function testLoginWithoutPassedCsrfCheckAndNotLoggedIn() {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
/** @var IUser|MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
@ -536,7 +439,7 @@ class LoginControllerTest extends TestCase {
}
public function testLoginWithoutPassedCsrfCheckAndLoggedIn() {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
/** @var IUser|MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
@ -571,196 +474,76 @@ class LoginControllerTest extends TestCase {
}
public function testLoginWithValidCredentialsAndRedirectUrl() {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->will($this->returnValue('jane'));
$user = 'MyUserName';
$password = 'secret';
$originalUrl = 'another%20url';
$redirectUrl = 'http://localhost/another url';
$indexPageUrl = \OC_Util::getDefaultPageUrl();
$redirectUrl = 'https://next.cloud/apps/mail';
$this->request
->expects($this->once())
->method('passesCSRFCheck')
->willReturn(true);
$this->userManager->expects($this->once())
->method('checkPasswordNoLogging')
->with('Jane', $password)
->will($this->returnValue($user));
$this->userSession->expects($this->once())
->method('createSessionToken')
->with($this->request, $user->getUID(), 'Jane', $password, IToken::REMEMBER);
$loginData = new LoginData(
$this->request,
$user,
$password,
'%2Fapps%2Fmail'
);
$loginResult = LoginResult::success($loginData);
$this->chain->expects($this->once())
->method('process')
->with($this->equalTo($loginData))
->willReturn($loginResult);
$this->userSession->expects($this->once())
->method('isLoggedIn')
->with()
->will($this->returnValue(true));
->willReturn(true);
$this->urlGenerator->expects($this->once())
->method('getAbsoluteURL')
->with(urldecode($originalUrl))
->with(urldecode('/apps/mail'))
->will($this->returnValue($redirectUrl));
$this->config->expects($this->once())
->method('deleteUserValue')
->with('jane', 'core', 'lostpassword');
$this->config
->method('getSystemValue')
->with('remember_login_cookie_lifetime')
->willReturn(1234);
$expected = new \OCP\AppFramework\Http\RedirectResponse($redirectUrl);
$expected = new \OCP\AppFramework\Http\RedirectResponse(urldecode($redirectUrl));
$this->assertEquals($expected, $this->loginController->tryLogin('Jane', $password, $originalUrl));
}
$response = $this->loginController->tryLogin($user, $password, '%2Fapps%2Fmail');
public function testLoginWithOneTwoFactorProvider() {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->will($this->returnValue('john'));
$password = 'secret';
$challengeUrl = 'challenge/url';
$provider1 = $this->createMock(IProvider::class);
$provider1->method('getId')->willReturn('u2f');
$provider2 = $this->createMock(BackupCodesProvider::class);
$provider2->method('getId')->willReturn('backup');
$this->request
->expects($this->once())
->method('passesCSRFCheck')
->willReturn(true);
$this->userManager->expects($this->once())
->method('checkPasswordNoLogging')
->will($this->returnValue($user));
$this->userSession->expects($this->once())
->method('completeLogin')
->with($user, ['loginName' => 'john@doe.com', 'password' => $password]);
$this->userSession->expects($this->once())
->method('createSessionToken')
->with($this->request, $user->getUID(), 'john@doe.com', $password, IToken::REMEMBER);
$this->twoFactorManager->expects($this->once())
->method('isTwoFactorAuthenticated')
->with($user)
->will($this->returnValue(true));
$this->twoFactorManager->expects($this->once())
->method('prepareTwoFactorLogin')
->with($user);
$providerSet = new ProviderSet([$provider1, $provider2], false);
$this->twoFactorManager->expects($this->once())
->method('getProviderSet')
->with($user)
->willReturn($providerSet);
$this->urlGenerator->expects($this->once())
->method('linkToRoute')
->with('core.TwoFactorChallenge.showChallenge', [
'challengeProviderId' => 'u2f',
])
->will($this->returnValue($challengeUrl));
$this->config->expects($this->once())
->method('deleteUserValue')
->with('john', 'core', 'lostpassword');
$this->config
->method('getSystemValue')
->with('remember_login_cookie_lifetime')
->willReturn(1234);
$this->userSession->expects($this->never())
->method('createRememberMeToken');
$expected = new RedirectResponse($challengeUrl);
$this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', $password, null));
}
public function testLoginWithMultipleTwoFactorProviders() {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->will($this->returnValue('john'));
$password = 'secret';
$challengeUrl = 'challenge/url';
$provider1 = $this->createMock(IProvider::class);
$provider2 = $this->createMock(IProvider::class);
$provider1->method('getId')->willReturn('prov1');
$provider2->method('getId')->willReturn('prov2');
$this->request
->expects($this->once())
->method('passesCSRFCheck')
->willReturn(true);
$this->userManager->expects($this->once())
->method('checkPasswordNoLogging')
->will($this->returnValue($user));
$this->userSession->expects($this->once())
->method('completeLogin')
->with($user, ['loginName' => 'john@doe.com', 'password' => $password]);
$this->userSession->expects($this->once())
->method('createSessionToken')
->with($this->request, $user->getUID(), 'john@doe.com', $password, IToken::REMEMBER);
$this->twoFactorManager->expects($this->once())
->method('isTwoFactorAuthenticated')
->with($user)
->will($this->returnValue(true));
$this->twoFactorManager->expects($this->once())
->method('prepareTwoFactorLogin')
->with($user);
$providerSet = new ProviderSet([$provider1, $provider2], false);
$this->twoFactorManager->expects($this->once())
->method('getProviderSet')
->with($user)
->willReturn($providerSet);
$this->urlGenerator->expects($this->once())
->method('linkToRoute')
->with('core.TwoFactorChallenge.selectChallenge')
->will($this->returnValue($challengeUrl));
$this->config->expects($this->once())
->method('deleteUserValue')
->with('john', 'core', 'lostpassword');
$this->config
->method('getSystemValue')
->with('remember_login_cookie_lifetime')
->willReturn(1234);
$this->userSession->expects($this->never())
->method('createRememberMeToken');
$expected = new RedirectResponse($challengeUrl);
$this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', $password, null));
$this->assertEquals($expected, $response);
}
public function testToNotLeakLoginName() {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->will($this->returnValue('john'));
$this->userManager->expects($this->once())
->method('checkPasswordNoLogging')
->with('john@doe.com', 'just wrong')
->willReturn(false);
$this->userManager->expects($this->once())
->method('checkPassword')
->with('john', 'just wrong')
->willReturn(false);
$this->userManager->expects($this->once())
->method('getByEmail')
->with('john@doe.com')
->willReturn([$user]);
$this->urlGenerator->expects($this->once())
->method('linkToRoute')
->with('core.login.showLoginForm', ['user' => 'john@doe.com'])
->will($this->returnValue(''));
$this->request
->expects($this->once())
->method('passesCSRFCheck')
->willReturn(true);
$this->config->expects($this->never())
->method('deleteUserValue');
$this->userSession->expects($this->never())
->method('createRememberMeToken');
$expected = new RedirectResponse('');
$loginPageUrl = '/login?redirect_url=/apps/files';
$loginData = new LoginData(
$this->request,
'john@doe.com',
'just wrong',
'/apps/files'
);
$loginResult = LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
$this->chain->expects($this->once())
->method('process')
->with($this->equalTo($loginData))
->willReturnCallback(function(LoginData $data) use ($loginResult) {
$data->setUsername('john');
return $loginResult;
});
$this->urlGenerator->expects($this->once())
->method('linkToRoute')
->with('core.login.showLoginForm', [
'user' => 'john@doe.com',
'redirect_url' => '/apps/files',
])
->will($this->returnValue($loginPageUrl));
$expected = new \OCP\AppFramework\Http\RedirectResponse($loginPageUrl);
$expected->throttle(['user' => 'john']);
$this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', 'just wrong', null));
$response = $this->loginController->tryLogin(
'john@doe.com',
'just wrong',
'/apps/files'
);
$this->assertEquals($expected, $response);
}
}

View File

@ -0,0 +1,122 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\ALoginCommand;
use OC\Authentication\Login\LoginData;
use OCP\IRequest;
use OCP\IUser;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
abstract class ALoginCommandTest extends TestCase {
/** @var IRequest|MockObject */
protected $request;
/** @var string */
protected $username = 'user123';
/** @var string */
protected $password = '123456';
/** @var string */
protected $redirectUrl = '/apps/contacts';
/** @var string */
protected $timezone = 'Europe/Vienna';
protected $timeZoneOffset = '2';
/** @var IUser|MockObject */
protected $user;
/** @var ALoginCommand */
protected $cmd;
protected function setUp() {
parent::setUp();
$this->request = $this->createMock(IRequest::class);
$this->user = $this->createMock(IUser::class);
}
protected function getBasicLoginData(): LoginData {
return new LoginData(
$this->request,
$this->username,
$this->password
);
}
protected function getInvalidLoginData(): LoginData {
return new LoginData(
$this->request,
$this->username,
$this->password
);
}
protected function getFailedLoginData(): LoginData {
$data = new LoginData(
$this->request,
$this->username,
$this->password
);
$data->setUser(false);
return $data;
}
protected function getLoggedInLoginData(): LoginData {
$basic = $this->getBasicLoginData();
$basic->setUser($this->user);
return $basic;
}
protected function getLoggedInLoginDataWithRedirectUrl(): LoginData {
$data = new LoginData(
$this->request,
$this->username,
$this->password,
$this->redirectUrl
);
$data->setUser($this->user);
return $data;
}
protected function getLoggedInLoginDataWithTimezone(): LoginData {
$data = new LoginData(
$this->request,
$this->username,
$this->password,
null,
$this->timezone,
$this->timeZoneOffset
);
$data->setUser($this->user);
return $data;
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace Test\Authentication\Login;
use lib\Authentication\Login\ALoginCommandTest;
use OC\Authentication\Login\ClearLostPasswordTokensCommand;
use OC\Authentication\Login\LoginData;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
class ClearLostPasswordTokensCommandTest extends ALoginCommandTest {
/** @var IConfig|MockObject */
private $config;
protected function setUp() {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->cmd = new ClearLostPasswordTokensCommand(
$this->config
);
}
public function testProcess() {
$data = $this->getLoggedInLoginData();
$this->user->expects($this->once())
->method('getUID')
->willReturn($this->username);
$this->config->expects($this->once())
->method('deleteUserValue')
->with(
$this->username,
'core',
'lostpassword'
);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\CompleteLoginCommand;
use OC\User\Session;
use PHPUnit\Framework\MockObject\MockObject;
class CompleteLoginCommandTest extends ALoginCommandTest {
/** @var Session|MockObject */
private $session;
protected function setUp() {
parent::setUp();
$this->session = $this->createMock(Session::class);
$this->cmd = new CompleteLoginCommand(
$this->session
);
}
public function testProcess() {
$data = $this->getLoggedInLoginData();
$this->session->expects($this->once())
->method('completeLogin')
->with(
$this->user,
$this->equalTo(
[
'loginName' => $this->username,
'password' => $this->password,
]
)
);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
}

View File

@ -0,0 +1,121 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\CreateSessionTokenCommand;
use OC\Authentication\Token\IToken;
use OC\User\Session;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
class CreateSessionTokenCommandTest extends ALoginCommandTest {
/** @var IConfig|MockObject */
private $config;
/** @var Session|MockObject */
private $userSession;
protected function setUp() {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->userSession = $this->createMock(Session::class);
$this->cmd = new CreateSessionTokenCommand(
$this->config,
$this->userSession
);
}
public function testProcess() {
$data = $this->getLoggedInLoginData();
$this->config->expects($this->once())
->method('getSystemValue')
->with(
'remember_login_cookie_lifetime',
60 * 60 * 24 * 15
)
->willReturn(100);
$this->user->expects($this->any())
->method('getUID')
->willReturn($this->username);
$this->userSession->expects($this->once())
->method('createSessionToken')
->with(
$this->request,
$this->username,
$this->username,
$this->password,
IToken::REMEMBER
);
$this->userSession->expects($this->once())
->method('updateTokens')
->with(
$this->username,
$this->username
);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
public function testProcessDoNotRemember() {
$data = $this->getLoggedInLoginData();
$this->config->expects($this->once())
->method('getSystemValue')
->with(
'remember_login_cookie_lifetime',
60 * 60 * 24 * 15
)
->willReturn(0);
$this->user->expects($this->any())
->method('getUID')
->willReturn($this->username);
$this->userSession->expects($this->once())
->method('createSessionToken')
->with(
$this->request,
$this->username,
$this->username,
$this->password,
IToken::DO_NOT_REMEMBER
);
$this->userSession->expects($this->once())
->method('updateTokens')
->with(
$this->username,
$this->username
);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
$this->assertFalse($data->isRememberLogin());
}
}

View File

@ -0,0 +1,165 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\EmailLoginCommand;
use OCP\IUser;
use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;
class EmailLoginCommandTest extends ALoginCommandTest {
/** @var IUserManager|MockObject */
private $userManager;
protected function setUp() {
parent::setUp();
$this->userManager = $this->createMock(IUserManager::class);
$this->cmd = new EmailLoginCommand(
$this->userManager
);
}
public function testProcessAlreadyLoggedIn() {
$data = $this->getLoggedInLoginData();
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
public function testProcessNotAnEmailLogin() {
$data = $this->getFailedLoginData();
$this->userManager->expects($this->once())
->method('getByEmail')
->with($this->username)
->willReturn([]);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
public function testProcessDuplicateEmailLogin() {
$data = $this->getFailedLoginData();
$this->userManager->expects($this->once())
->method('getByEmail')
->with($this->username)
->willReturn([
$this->createMock(IUser::class),
$this->createMock(IUser::class),
]);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
public function testProcessUidIsEmail() {
$email = 'user@domain.com';
$data = $this->getFailedLoginData();
$data->setUsername($email);
$emailUser = $this->createMock(IUser::class);
$emailUser->expects($this->any())
->method('getUID')
->willReturn($email);
$this->userManager->expects($this->once())
->method('getByEmail')
->with($email)
->willReturn([
$emailUser,
]);
$this->userManager->expects($this->never())
->method('checkPassword');
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
$this->assertFalse($data->getUser());
$this->assertEquals($email, $data->getUsername());
}
public function testProcessWrongPassword() {
$email = 'user@domain.com';
$data = $this->getFailedLoginData();
$data->setUsername($email);
$emailUser = $this->createMock(IUser::class);
$emailUser->expects($this->any())
->method('getUID')
->willReturn('user2');
$this->userManager->expects($this->once())
->method('getByEmail')
->with($email)
->willReturn([
$emailUser,
]);
$this->userManager->expects($this->once())
->method('checkPassword')
->with(
'user2',
$this->password
)
->willReturn(false);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
$this->assertFalse($data->getUser());
$this->assertEquals($email, $data->getUsername());
}
public function testProcess() {
$email = 'user@domain.com';
$data = $this->getFailedLoginData();
$data->setUsername($email);
$emailUser = $this->createMock(IUser::class);
$emailUser->expects($this->any())
->method('getUID')
->willReturn('user2');
$this->userManager->expects($this->once())
->method('getByEmail')
->with($email)
->willReturn([
$emailUser,
]);
$this->userManager->expects($this->once())
->method('checkPassword')
->with(
'user2',
$this->password
)
->willReturn($emailUser);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
$this->assertEquals($emailUser, $data->getUser());
$this->assertEquals('user2', $data->getUsername());
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\FinishRememberedLoginCommand;
use OC\User\Session;
use PHPUnit\Framework\MockObject\MockObject;
class FinishRememberedLoginCommandTest extends ALoginCommandTest {
/** @var Session|MockObject */
private $userSession;
protected function setUp() {
parent::setUp();
$this->userSession = $this->createMock(Session::class);
$this->cmd = new FinishRememberedLoginCommand(
$this->userSession
);
}
public function testProcessNotRememberedLogin() {
$data = $this->getLoggedInLoginData();
$data->setRememberLogin(false);
$this->userSession->expects($this->never())
->method('createRememberMeToken');
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
public function testProcess() {
$data = $this->getLoggedInLoginData();
$this->userSession->expects($this->once())
->method('createRememberMeToken')
->with($this->user);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\LoggedInCheckCommand;
use OC\Core\Controller\LoginController;
use OCP\ILogger;
use PHPUnit\Framework\MockObject\MockObject;
class LoggedInCheckCommandTest extends ALoginCommandTest {
/** @var ILogger|MockObject */
private $logger;
protected function setUp() {
parent::setUp();
$this->logger = $this->createMock(ILogger::class);
$this->cmd = new LoggedInCheckCommand(
$this->logger
);
}
public function testProcessSuccessfulLogin() {
$data = $this->getLoggedInLoginData();
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
public function testProcessFailedLogin() {
$data = $this->getFailedLoginData();
$this->logger->expects($this->once())
->method('warning');
$result = $this->cmd->process($data);
$this->assertFalse($result->isSuccess());
$this->assertSame(LoginController::LOGIN_MSG_INVALIDPASSWORD, $result->getErrorMessage());
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\PreLoginHookCommand;
use OC\User\Manager;
use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;
class PreLoginHookCommandTest extends ALoginCommandTest {
/** @var IUserManager|MockObject */
private $userManager;
protected function setUp() {
parent::setUp();
$this->userManager = $this->createMock(Manager::class);
$this->cmd = new PreLoginHookCommand(
$this->userManager
);
}
public function testProcess() {
$data = $this->getBasicLoginData();
$this->userManager->expects($this->once())
->method('emit')
->with(
'\OC\User',
'preLogin',
[
$this->username,
$this->password,
]
);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\SetUserTimezoneCommand;
use OCP\IConfig;
use OCP\ISession;
use PHPUnit\Framework\MockObject\MockObject;
class SetUserTimezoneCommandTest extends ALoginCommandTest {
/** @var IConfig|MockObject */
private $config;
/** @var ISession|MockObject */
private $session;
protected function setUp() {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->session = $this->createMock(ISession::class);
$this->cmd = new SetUserTimezoneCommand(
$this->config,
$this->session
);
}
public function testProcessNoTimezoneSet() {
$data = $this->getLoggedInLoginData();
$this->config->expects($this->never())
->method('setUserValue');
$this->session->expects($this->never())
->method('set');
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
public function testProcess() {
$data = $this->getLoggedInLoginDataWithTimezone();
$this->user->expects($this->once())
->method('getUID')
->willReturn($this->username);
$this->config->expects($this->once())
->method('setUserValue')
->with(
$this->username,
'core',
'timezone',
$this->timezone
);
$this->session->expects($this->once())
->method('set')
->with(
'timezone',
$this->timeZoneOffset
);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
}

View File

@ -0,0 +1,179 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\TwoFactorCommand;
use OC\Authentication\TwoFactorAuth\Manager;
use OC\Authentication\TwoFactorAuth\ProviderSet;
use OCP\Authentication\TwoFactorAuth\IProvider as ITwoFactorAuthProvider;
use OCP\IURLGenerator;
use PHPUnit\Framework\MockObject\MockObject;
class TwoFactorCommandTest extends ALoginCommandTest {
/** @var Manager|MockObject */
private $twoFactorManager;
/** @var IURLGenerator|MockObject */
private $urlGenerator;
protected function setUp() {
parent::setUp();
$this->twoFactorManager = $this->createMock(Manager::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->cmd = new TwoFactorCommand(
$this->twoFactorManager,
$this->urlGenerator
);
}
public function testNotTwoFactorAuthenticated() {
$data = $this->getLoggedInLoginData();
$this->twoFactorManager->expects($this->once())
->method('isTwoFactorAuthenticated')
->willReturn(false);
$this->twoFactorManager->expects($this->never())
->method('prepareTwoFactorLogin');
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
public function testProcessOneActiveProvider() {
$data = $this->getLoggedInLoginData();
$this->twoFactorManager->expects($this->once())
->method('isTwoFactorAuthenticated')
->willReturn(true);
$this->twoFactorManager->expects($this->once())
->method('prepareTwoFactorLogin')
->with(
$this->user,
$data->isRememberLogin()
);
$provider = $this->createMock(ITwoFactorAuthProvider::class);
$this->twoFactorManager->expects($this->once())
->method('getProviderSet')
->willReturn(new ProviderSet([
$provider,
], false));
$provider->expects($this->once())
->method('getId')
->willReturn('test');
$this->urlGenerator->expects($this->once())
->method('linkToRoute')
->with(
'core.TwoFactorChallenge.showChallenge',
[
'challengeProviderId' => 'test'
]
)
->willReturn('two/factor/url');
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
$this->assertEquals('two/factor/url', $result->getRedirectUrl());
}
public function testProcessTwoActiveProviders() {
$data = $this->getLoggedInLoginData();
$this->twoFactorManager->expects($this->once())
->method('isTwoFactorAuthenticated')
->willReturn(true);
$this->twoFactorManager->expects($this->once())
->method('prepareTwoFactorLogin')
->with(
$this->user,
$data->isRememberLogin()
);
$provider1 = $this->createMock(ITwoFactorAuthProvider::class);
$provider2 = $this->createMock(ITwoFactorAuthProvider::class);
$provider1->expects($this->once())
->method('getId')
->willReturn('test1');
$provider2->expects($this->once())
->method('getId')
->willReturn('test2');
$this->twoFactorManager->expects($this->once())
->method('getProviderSet')
->willReturn(new ProviderSet([
$provider1,
$provider2,
], false));
$this->urlGenerator->expects($this->once())
->method('linkToRoute')
->with(
'core.TwoFactorChallenge.selectChallenge'
)
->willReturn('two/factor/url');
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
$this->assertEquals('two/factor/url', $result->getRedirectUrl());
}
public function testProcessWithRedirectUrl() {
$data = $this->getLoggedInLoginDataWithRedirectUrl();
$this->twoFactorManager->expects($this->once())
->method('isTwoFactorAuthenticated')
->willReturn(true);
$this->twoFactorManager->expects($this->once())
->method('prepareTwoFactorLogin')
->with(
$this->user,
$data->isRememberLogin()
);
$provider = $this->createMock(ITwoFactorAuthProvider::class);
$this->twoFactorManager->expects($this->once())
->method('getProviderSet')
->willReturn(new ProviderSet([
$provider,
], false));
$provider->expects($this->once())
->method('getId')
->willReturn('test');
$this->urlGenerator->expects($this->once())
->method('linkToRoute')
->with(
'core.TwoFactorChallenge.showChallenge',
[
'challengeProviderId' => 'test',
'redirect_url' => $this->redirectUrl,
]
)
->willReturn('two/factor/url');
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
$this->assertEquals('two/factor/url', $result->getRedirectUrl());
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\UidCheckCommand;
use OC\Authentication\Login\UidLoginCommand;
use OC\User\Manager;
use PHPUnit\Framework\MockObject\MockObject;
class UidLoginCommandTest extends ALoginCommandTest {
/** @var Manager|MockObject */
private $userManager;
protected function setUp() {
parent::setUp();
$this->userManager = $this->createMock(Manager::class);
$this->cmd = new UidLoginCommand(
$this->userManager
);
}
public function testProcessFailingLogin() {
$data = $this->getBasicLoginData();
$this->userManager->expects($this->once())
->method('checkPasswordNoLogging')
->with(
$this->username,
$this->password
)
->willReturn(false);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
$this->assertFalse($data->getUser());
}
public function testProcess() {
$data = $this->getBasicLoginData();
$this->userManager->expects($this->once())
->method('checkPasswordNoLogging')
->with(
$this->username,
$this->password
)
->willReturn($this->user);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
$this->assertEquals($this->user, $data->getUser());
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\UpdateLastPasswordConfirmCommand;
use OCP\ISession;
use PHPUnit\Framework\MockObject\MockObject;
class UpdateLastPasswordConfirmCommandTest extends ALoginCommandTest {
/** @var ISession|MockObject */
private $session;
protected function setUp() {
parent::setUp();
$this->session = $this->createMock(ISession::class);
$this->cmd = new UpdateLastPasswordConfirmCommand(
$this->session
);
}
public function testProcess() {
$data = $this->getLoggedInLoginData();
$this->user->expects($this->once())
->method('getLastLogin')
->willReturn(1234);
$this->session->expects($this->once())
->method('set')
->with(
'last-password-confirm',
1234
);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
}

View File

@ -0,0 +1,97 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
declare(strict_types=1);
namespace lib\Authentication\Login;
use OC\Authentication\Login\UserDisabledCheckCommand;
use OC\Core\Controller\LoginController;
use OCP\ILogger;
use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;
class UserDisabledCheckCommandTest extends ALoginCommandTest {
/** @var IUserManager|MockObject */
private $userManager;
/** @var ILogger|MockObject */
private $logger;
protected function setUp() {
parent::setUp();
$this->userManager = $this->createMock(IUserManager::class);
$this->logger = $this->createMock(ILogger::class);
$this->cmd = new UserDisabledCheckCommand(
$this->userManager,
$this->logger
);
}
public function testProcessNonExistingUser() {
$data = $this->getBasicLoginData();
$this->userManager->expects($this->once())
->method('get')
->with($this->username)
->willReturn(null);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
public function testProcessDisabledUser() {
$data = $this->getBasicLoginData();
$this->userManager->expects($this->once())
->method('get')
->with($this->username)
->willReturn($this->user);
$this->user->expects($this->once())
->method('isEnabled')
->willReturn(false);
$result = $this->cmd->process($data);
$this->assertFalse($result->isSuccess());
$this->assertSame(LoginController::LOGIN_MSG_USERDISABLED, $result->getErrorMessage());
}
public function testProcess() {
$data = $this->getBasicLoginData();
$this->userManager->expects($this->once())
->method('get')
->with($this->username)
->willReturn($this->user);
$this->user->expects($this->once())
->method('isEnabled')
->willReturn(true);
$result = $this->cmd->process($data);
$this->assertTrue($result->isSuccess());
}
}