Merge branch 'master' into contactsmenu
Signed-off-by: Jan-Christoph Borchardt <hey@jancborchardt.net>
This commit is contained in:
commit
241e397326
|
@ -60,6 +60,23 @@ class UserManagement extends Action {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log enabling of users
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
*/
|
||||||
|
public function change(array $params) {
|
||||||
|
if ($params['feature'] === 'enabled') {
|
||||||
|
$this->log(
|
||||||
|
$params['value'] === 'true' ? 'User enabled: "%s"' : 'User disabled: "%s"',
|
||||||
|
['user' => $params['user']->getUID()],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs changing of the user scope
|
* Logs changing of the user scope
|
||||||
*
|
*
|
||||||
|
|
|
@ -90,6 +90,7 @@ class AuditLogger {
|
||||||
|
|
||||||
Util::connectHook('OC_User', 'post_createUser', $userActions, 'create');
|
Util::connectHook('OC_User', 'post_createUser', $userActions, 'create');
|
||||||
Util::connectHook('OC_User', 'post_deleteUser', $userActions, 'delete');
|
Util::connectHook('OC_User', 'post_deleteUser', $userActions, 'delete');
|
||||||
|
Util::connectHook('OC_User', 'changeUser', $userActions, 'change');
|
||||||
$this->userSession->listen('\OC\User', 'postSetPassword', [$userActions, 'setPassword']);
|
$this->userSession->listen('\OC\User', 'postSetPassword', [$userActions, 'setPassword']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,238 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OC\Core\Controller;
|
||||||
|
|
||||||
|
use OC\Authentication\Exceptions\InvalidTokenException;
|
||||||
|
use OC\Authentication\Exceptions\PasswordlessTokenException;
|
||||||
|
use OC\Authentication\Token\IProvider;
|
||||||
|
use OC\Authentication\Token\IToken;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\Response;
|
||||||
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
|
use OCP\Defaults;
|
||||||
|
use OCP\IL10N;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\ISession;
|
||||||
|
use OCP\IURLGenerator;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
use OCP\Security\ISecureRandom;
|
||||||
|
use OCP\Session\Exceptions\SessionNotAvailableException;
|
||||||
|
|
||||||
|
class ClientFlowLoginController extends Controller {
|
||||||
|
/** @var IUserSession */
|
||||||
|
private $userSession;
|
||||||
|
/** @var IL10N */
|
||||||
|
private $l10n;
|
||||||
|
/** @var Defaults */
|
||||||
|
private $defaults;
|
||||||
|
/** @var ISession */
|
||||||
|
private $session;
|
||||||
|
/** @var IProvider */
|
||||||
|
private $tokenProvider;
|
||||||
|
/** @var ISecureRandom */
|
||||||
|
private $random;
|
||||||
|
/** @var IURLGenerator */
|
||||||
|
private $urlGenerator;
|
||||||
|
|
||||||
|
const stateName = 'client.flow.state.token';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $appName
|
||||||
|
* @param IRequest $request
|
||||||
|
* @param IUserSession $userSession
|
||||||
|
* @param IL10N $l10n
|
||||||
|
* @param Defaults $defaults
|
||||||
|
* @param ISession $session
|
||||||
|
* @param IProvider $tokenProvider
|
||||||
|
* @param ISecureRandom $random
|
||||||
|
* @param IURLGenerator $urlGenerator
|
||||||
|
*/
|
||||||
|
public function __construct($appName,
|
||||||
|
IRequest $request,
|
||||||
|
IUserSession $userSession,
|
||||||
|
IL10N $l10n,
|
||||||
|
Defaults $defaults,
|
||||||
|
ISession $session,
|
||||||
|
IProvider $tokenProvider,
|
||||||
|
ISecureRandom $random,
|
||||||
|
IURLGenerator $urlGenerator) {
|
||||||
|
parent::__construct($appName, $request);
|
||||||
|
$this->userSession = $userSession;
|
||||||
|
$this->l10n = $l10n;
|
||||||
|
$this->defaults = $defaults;
|
||||||
|
$this->session = $session;
|
||||||
|
$this->tokenProvider = $tokenProvider;
|
||||||
|
$this->random = $random;
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getClientName() {
|
||||||
|
return $this->request->getHeader('USER_AGENT') !== null ? $this->request->getHeader('USER_AGENT') : 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $stateToken
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isValidToken($stateToken) {
|
||||||
|
$currentToken = $this->session->get(self::stateName);
|
||||||
|
if(!is_string($stateToken) || !is_string($currentToken)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return hash_equals($currentToken, $stateToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return TemplateResponse
|
||||||
|
*/
|
||||||
|
private function stateTokenForbiddenResponse() {
|
||||||
|
$response = new TemplateResponse(
|
||||||
|
$this->appName,
|
||||||
|
'403',
|
||||||
|
[
|
||||||
|
'file' => $this->l10n->t('State token does not match'),
|
||||||
|
],
|
||||||
|
'guest'
|
||||||
|
);
|
||||||
|
$response->setStatus(Http::STATUS_FORBIDDEN);
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @PublicPage
|
||||||
|
* @NoCSRFRequired
|
||||||
|
* @UseSession
|
||||||
|
*
|
||||||
|
* @return TemplateResponse
|
||||||
|
*/
|
||||||
|
public function showAuthPickerPage() {
|
||||||
|
if($this->userSession->isLoggedIn()) {
|
||||||
|
return new TemplateResponse(
|
||||||
|
$this->appName,
|
||||||
|
'403',
|
||||||
|
[
|
||||||
|
'file' => $this->l10n->t('Auth flow can only be started unauthenticated.'),
|
||||||
|
],
|
||||||
|
'guest'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stateToken = $this->random->generate(
|
||||||
|
64,
|
||||||
|
ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
|
||||||
|
);
|
||||||
|
$this->session->set(self::stateName, $stateToken);
|
||||||
|
|
||||||
|
return new TemplateResponse(
|
||||||
|
$this->appName,
|
||||||
|
'loginflow/authpicker',
|
||||||
|
[
|
||||||
|
'client' => $this->getClientName(),
|
||||||
|
'instanceName' => $this->defaults->getName(),
|
||||||
|
'urlGenerator' => $this->urlGenerator,
|
||||||
|
'stateToken' => $stateToken,
|
||||||
|
'serverHost' => $this->request->getServerHost(),
|
||||||
|
],
|
||||||
|
'guest'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
* @UseSession
|
||||||
|
*
|
||||||
|
* @param string $stateToken
|
||||||
|
* @return TemplateResponse
|
||||||
|
*/
|
||||||
|
public function redirectPage($stateToken = '') {
|
||||||
|
if(!$this->isValidToken($stateToken)) {
|
||||||
|
return $this->stateTokenForbiddenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TemplateResponse(
|
||||||
|
$this->appName,
|
||||||
|
'loginflow/redirect',
|
||||||
|
[
|
||||||
|
'urlGenerator' => $this->urlGenerator,
|
||||||
|
'stateToken' => $stateToken,
|
||||||
|
],
|
||||||
|
'empty'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @UseSession
|
||||||
|
*
|
||||||
|
* @param string $stateToken
|
||||||
|
* @return Http\RedirectResponse|Response
|
||||||
|
*/
|
||||||
|
public function generateAppPassword($stateToken) {
|
||||||
|
if(!$this->isValidToken($stateToken)) {
|
||||||
|
$this->session->remove(self::stateName);
|
||||||
|
return $this->stateTokenForbiddenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->session->remove(self::stateName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sessionId = $this->session->getId();
|
||||||
|
} catch (SessionNotAvailableException $ex) {
|
||||||
|
$response = new Response();
|
||||||
|
$response->setStatus(Http::STATUS_FORBIDDEN);
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sessionToken = $this->tokenProvider->getToken($sessionId);
|
||||||
|
$loginName = $sessionToken->getLoginName();
|
||||||
|
try {
|
||||||
|
$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
|
||||||
|
} catch (PasswordlessTokenException $ex) {
|
||||||
|
$password = null;
|
||||||
|
}
|
||||||
|
} catch (InvalidTokenException $ex) {
|
||||||
|
$response = new Response();
|
||||||
|
$response->setStatus(Http::STATUS_FORBIDDEN);
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->random->generate(72);
|
||||||
|
$this->tokenProvider->generateToken(
|
||||||
|
$token,
|
||||||
|
$this->userSession->getUser()->getUID(),
|
||||||
|
$loginName,
|
||||||
|
$password,
|
||||||
|
$this->getClientName(),
|
||||||
|
IToken::PERMANENT_TOKEN,
|
||||||
|
IToken::DO_NOT_REMEMBER
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Http\RedirectResponse('nc://' . urlencode($loginName) . ':' . urlencode($token) . '@' . $this->request->getServerHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
.picker-window {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background-color: rgba(0,0,0,.3);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
|
@ -1515,7 +1515,6 @@ function initCore() {
|
||||||
var appList = $('#appmenu li');
|
var appList = $('#appmenu li');
|
||||||
var availableWidth = $('#header-left').width() - $('#nextcloud').width() - 44;
|
var availableWidth = $('#header-left').width() - $('#nextcloud').width() - 44;
|
||||||
var appCount = Math.floor((availableWidth)/44);
|
var appCount = Math.floor((availableWidth)/44);
|
||||||
console.log(appCount);
|
|
||||||
// show a maximum of 8 apps
|
// show a maximum of 8 apps
|
||||||
if(appCount >= maxApps) {
|
if(appCount >= maxApps) {
|
||||||
appCount = maxApps;
|
appCount = maxApps;
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
jQuery(document).ready(function() {
|
||||||
|
$('#app-token-login').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$(this).addClass('hidden');
|
||||||
|
$('#redirect-link').addClass('hidden');
|
||||||
|
$('#app-token-login-field').removeClass('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#submit-app-token-login').click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
window.location.href = 'nc://' + encodeURIComponent($('#user').val()) + ':' + encodeURIComponent($('#password').val()) + '@' + encodeURIComponent($('#serverHost').val());
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
jQuery(document).ready(function() {
|
||||||
|
$('#submit-redirect-form').trigger('click');
|
||||||
|
});
|
|
@ -49,6 +49,9 @@ $application->registerRoutes($this, [
|
||||||
['name' => 'login#confirmPassword', 'url' => '/login/confirm', 'verb' => 'POST'],
|
['name' => 'login#confirmPassword', 'url' => '/login/confirm', 'verb' => 'POST'],
|
||||||
['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'],
|
['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'],
|
||||||
['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'],
|
['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'],
|
||||||
|
['name' => 'ClientFlowLogin#showAuthPickerPage', 'url' => '/login/flow', 'verb' => 'GET'],
|
||||||
|
['name' => 'ClientFlowLogin#redirectPage', 'url' => '/login/flow/redirect', 'verb' => 'GET'],
|
||||||
|
['name' => 'ClientFlowLogin#generateAppPassword', 'url' => '/login/flow', 'verb' => 'POST'],
|
||||||
['name' => 'TwoFactorChallenge#selectChallenge', 'url' => '/login/selectchallenge', 'verb' => 'GET'],
|
['name' => 'TwoFactorChallenge#selectChallenge', 'url' => '/login/selectchallenge', 'verb' => 'GET'],
|
||||||
['name' => 'TwoFactorChallenge#showChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'GET'],
|
['name' => 'TwoFactorChallenge#showChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'GET'],
|
||||||
['name' => 'TwoFactorChallenge#solveChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'POST'],
|
['name' => 'TwoFactorChallenge#solveChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'POST'],
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
*
|
||||||
|
* @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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
script('core', 'login/authpicker');
|
||||||
|
style('core', 'login/authpicker');
|
||||||
|
|
||||||
|
/** @var array $_ */
|
||||||
|
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||||
|
$urlGenerator = $_['urlGenerator'];
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="picker-window">
|
||||||
|
<p class="info">
|
||||||
|
<?php p($l->t('You are about to grant "%s" access to your %s account.', [$_['client'], $_['instanceName']])) ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<p id="redirect-link">
|
||||||
|
<a href="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.redirectPage', ['stateToken' => $_['stateToken']])) ?>">
|
||||||
|
<input type="submit" class="login primary icon-confirm-white" value="<?php p('Grant access') ?>">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<fieldset id="app-token-login-field" class="hidden">
|
||||||
|
<p class="grouptop">
|
||||||
|
<input type="text" name="user" id="user" placeholder="<?php p($l->t('Username')) ?>">
|
||||||
|
<label for="user" class="infield"><?php p($l->t('Username')) ?></label>
|
||||||
|
</p>
|
||||||
|
<p class="groupbottom">
|
||||||
|
<input type="password" name="password" id="password" placeholder="<?php p($l->t('App token')) ?>">
|
||||||
|
<label for="password" class="infield"><?php p($l->t('Password')) ?></label>
|
||||||
|
</p>
|
||||||
|
<input type="hidden" id="serverHost" value="<?php p($_['serverHost']) ?>" />
|
||||||
|
<input id="submit-app-token-login" type="submit" class="login primary icon-confirm-white" value="<?php p('Grant access') ?>">
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="app-token-login" class="warning" href="#"><?php p($l->t('Alternative login using app token')) ?></a>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
*
|
||||||
|
* @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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
script('core', 'login/redirect');
|
||||||
|
style('core', 'login/authpicker');
|
||||||
|
|
||||||
|
/** @var array $_ */
|
||||||
|
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||||
|
$urlGenerator = $_['urlGenerator'];
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="picker-window">
|
||||||
|
<p class="info"><?php p($l->t('Redirecting …')) ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" action="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.generateAppPassword')) ?>">
|
||||||
|
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
|
||||||
|
<input type="hidden" name="stateToken" value="<?php p($_['stateToken']) ?>" />
|
||||||
|
<input id="submit-redirect-form" type="submit" class="hidden "/>
|
||||||
|
</form>
|
|
@ -456,6 +456,7 @@ return array(
|
||||||
'OC\\Core\\Command\\User\\ResetPassword' => $baseDir . '/core/Command/User/ResetPassword.php',
|
'OC\\Core\\Command\\User\\ResetPassword' => $baseDir . '/core/Command/User/ResetPassword.php',
|
||||||
'OC\\Core\\Command\\User\\Setting' => $baseDir . '/core/Command/User/Setting.php',
|
'OC\\Core\\Command\\User\\Setting' => $baseDir . '/core/Command/User/Setting.php',
|
||||||
'OC\\Core\\Controller\\AvatarController' => $baseDir . '/core/Controller/AvatarController.php',
|
'OC\\Core\\Controller\\AvatarController' => $baseDir . '/core/Controller/AvatarController.php',
|
||||||
|
'OC\\Core\\Controller\\ClientFlowLoginController' => $baseDir . '/core/Controller/ClientFlowLoginController.php',
|
||||||
'OC\\Core\\Controller\\ContactsMenuController' => $baseDir . '/core/Controller/ContactsMenuController.php',
|
'OC\\Core\\Controller\\ContactsMenuController' => $baseDir . '/core/Controller/ContactsMenuController.php',
|
||||||
'OC\\Core\\Controller\\CssController' => $baseDir . '/core/Controller/CssController.php',
|
'OC\\Core\\Controller\\CssController' => $baseDir . '/core/Controller/CssController.php',
|
||||||
'OC\\Core\\Controller\\JsController' => $baseDir . '/core/Controller/JsController.php',
|
'OC\\Core\\Controller\\JsController' => $baseDir . '/core/Controller/JsController.php',
|
||||||
|
|
|
@ -486,6 +486,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
||||||
'OC\\Core\\Command\\User\\ResetPassword' => __DIR__ . '/../../..' . '/core/Command/User/ResetPassword.php',
|
'OC\\Core\\Command\\User\\ResetPassword' => __DIR__ . '/../../..' . '/core/Command/User/ResetPassword.php',
|
||||||
'OC\\Core\\Command\\User\\Setting' => __DIR__ . '/../../..' . '/core/Command/User/Setting.php',
|
'OC\\Core\\Command\\User\\Setting' => __DIR__ . '/../../..' . '/core/Command/User/Setting.php',
|
||||||
'OC\\Core\\Controller\\AvatarController' => __DIR__ . '/../../..' . '/core/Controller/AvatarController.php',
|
'OC\\Core\\Controller\\AvatarController' => __DIR__ . '/../../..' . '/core/Controller/AvatarController.php',
|
||||||
|
'OC\\Core\\Controller\\ClientFlowLoginController' => __DIR__ . '/../../..' . '/core/Controller/ClientFlowLoginController.php',
|
||||||
'OC\\Core\\Controller\\ContactsMenuController' => __DIR__ . '/../../..' . '/core/Controller/ContactsMenuController.php',
|
'OC\\Core\\Controller\\ContactsMenuController' => __DIR__ . '/../../..' . '/core/Controller/ContactsMenuController.php',
|
||||||
'OC\\Core\\Controller\\CssController' => __DIR__ . '/../../..' . '/core/Controller/CssController.php',
|
'OC\\Core\\Controller\\CssController' => __DIR__ . '/../../..' . '/core/Controller/CssController.php',
|
||||||
'OC\\Core\\Controller\\JsController' => __DIR__ . '/../../..' . '/core/Controller/JsController.php',
|
'OC\\Core\\Controller\\JsController' => __DIR__ . '/../../..' . '/core/Controller/JsController.php',
|
||||||
|
|
|
@ -64,7 +64,12 @@ class CryptoSessionData implements \ArrayAccess, ISession {
|
||||||
* Close session if class gets destructed
|
* Close session if class gets destructed
|
||||||
*/
|
*/
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
$this->close();
|
try {
|
||||||
|
$this->close();
|
||||||
|
} catch (SessionNotAvailableException $e){
|
||||||
|
// This exception can occur if session is already closed
|
||||||
|
// So it is safe to ignore it and let the garbage collector to proceed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function initializeSession() {
|
protected function initializeSession() {
|
||||||
|
|
|
@ -151,7 +151,7 @@ class Internal extends Session {
|
||||||
*/
|
*/
|
||||||
private function validateSession() {
|
private function validateSession() {
|
||||||
if ($this->sessionClosed) {
|
if ($this->sessionClosed) {
|
||||||
throw new \Exception('Session has been closed - no further changes to the session are allowed');
|
throw new SessionNotAvailableException('Session has been closed - no further changes to the session are allowed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,9 +342,13 @@ class User implements IUser {
|
||||||
* @param bool $enabled
|
* @param bool $enabled
|
||||||
*/
|
*/
|
||||||
public function setEnabled($enabled) {
|
public function setEnabled($enabled) {
|
||||||
|
$oldStatus = $this->isEnabled();
|
||||||
$this->enabled = $enabled;
|
$this->enabled = $enabled;
|
||||||
$enabled = ($enabled) ? 'true' : 'false';
|
$enabled = ($enabled) ? 'true' : 'false';
|
||||||
$this->config->setUserValue($this->uid, 'core', 'enabled', $enabled);
|
if ($oldStatus !== $this->enabled) {
|
||||||
|
$this->triggerChange('enabled', $enabled);
|
||||||
|
$this->config->setUserValue($this->uid, 'core', 'enabled', $enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,408 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Tests\Core\Controller;
|
||||||
|
|
||||||
|
use OC\Authentication\Exceptions\InvalidTokenException;
|
||||||
|
use OC\Authentication\Exceptions\PasswordlessTokenException;
|
||||||
|
use OC\Authentication\Token\IProvider;
|
||||||
|
use OC\Authentication\Token\IToken;
|
||||||
|
use OC\Core\Controller\ClientFlowLoginController;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
|
use OCP\Defaults;
|
||||||
|
use OCP\IL10N;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\ISession;
|
||||||
|
use OCP\IURLGenerator;
|
||||||
|
use OCP\IUser;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
use OCP\Security\ISecureRandom;
|
||||||
|
use OCP\Session\Exceptions\SessionNotAvailableException;
|
||||||
|
use Test\TestCase;
|
||||||
|
|
||||||
|
class ClientFlowLoginControllerTest extends TestCase {
|
||||||
|
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $request;
|
||||||
|
/** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $userSession;
|
||||||
|
/** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $l10n;
|
||||||
|
/** @var Defaults|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $defaults;
|
||||||
|
/** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $session;
|
||||||
|
/** @var IProvider|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $tokenProvider;
|
||||||
|
/** @var ISecureRandom|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $random;
|
||||||
|
/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $urlGenerator;
|
||||||
|
/** @var ClientFlowLoginController */
|
||||||
|
private $clientFlowLoginController;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->request = $this->createMock(IRequest::class);
|
||||||
|
$this->userSession = $this->createMock(IUserSession::class);
|
||||||
|
$this->l10n = $this->createMock(IL10N::class);
|
||||||
|
$this->l10n
|
||||||
|
->expects($this->any())
|
||||||
|
->method('t')
|
||||||
|
->will($this->returnCallback(function($text, $parameters = array()) {
|
||||||
|
return vsprintf($text, $parameters);
|
||||||
|
}));
|
||||||
|
$this->defaults = $this->createMock(Defaults::class);
|
||||||
|
$this->session = $this->createMock(ISession::class);
|
||||||
|
$this->tokenProvider = $this->createMock(IProvider::class);
|
||||||
|
$this->random = $this->createMock(ISecureRandom::class);
|
||||||
|
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||||
|
|
||||||
|
$this->clientFlowLoginController = new ClientFlowLoginController(
|
||||||
|
'core',
|
||||||
|
$this->request,
|
||||||
|
$this->userSession,
|
||||||
|
$this->l10n,
|
||||||
|
$this->defaults,
|
||||||
|
$this->session,
|
||||||
|
$this->tokenProvider,
|
||||||
|
$this->random,
|
||||||
|
$this->urlGenerator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShowAuthPickerPageNotAuthenticated() {
|
||||||
|
$this->userSession
|
||||||
|
->expects($this->once())
|
||||||
|
->method('isLoggedIn')
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
|
$expected = new TemplateResponse(
|
||||||
|
'core',
|
||||||
|
'403',
|
||||||
|
[
|
||||||
|
'file' => 'Auth flow can only be started unauthenticated.',
|
||||||
|
],
|
||||||
|
'guest'
|
||||||
|
);
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->showAuthPickerPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShowAuthPickerPage() {
|
||||||
|
$this->userSession
|
||||||
|
->expects($this->once())
|
||||||
|
->method('isLoggedIn')
|
||||||
|
->willReturn(false);
|
||||||
|
$this->random
|
||||||
|
->expects($this->once())
|
||||||
|
->method('generate')
|
||||||
|
->with(
|
||||||
|
64,
|
||||||
|
ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
|
||||||
|
)
|
||||||
|
->willReturn('StateToken');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('set')
|
||||||
|
->with('client.flow.state.token', 'StateToken');
|
||||||
|
$this->request
|
||||||
|
->expects($this->exactly(2))
|
||||||
|
->method('getHeader')
|
||||||
|
->with('USER_AGENT')
|
||||||
|
->willReturn('Mac OS X Sync Client');
|
||||||
|
$this->defaults
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getName')
|
||||||
|
->willReturn('ExampleCloud');
|
||||||
|
$this->request
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getServerHost')
|
||||||
|
->willReturn('example.com');
|
||||||
|
|
||||||
|
$expected = new TemplateResponse(
|
||||||
|
'core',
|
||||||
|
'loginflow/authpicker',
|
||||||
|
[
|
||||||
|
'client' => 'Mac OS X Sync Client',
|
||||||
|
'instanceName' => 'ExampleCloud',
|
||||||
|
'urlGenerator' => $this->urlGenerator,
|
||||||
|
'stateToken' => 'StateToken',
|
||||||
|
'serverHost' => 'example.com',
|
||||||
|
],
|
||||||
|
'guest'
|
||||||
|
);
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->showAuthPickerPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRedirectPageWithInvalidToken() {
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('client.flow.state.token')
|
||||||
|
->willReturn('OtherToken');
|
||||||
|
|
||||||
|
$expected = new TemplateResponse(
|
||||||
|
'core',
|
||||||
|
'403',
|
||||||
|
[
|
||||||
|
'file' => 'State token does not match',
|
||||||
|
],
|
||||||
|
'guest'
|
||||||
|
);
|
||||||
|
$expected->setStatus(Http::STATUS_FORBIDDEN);
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->redirectPage('MyStateToken'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRedirectPageWithoutToken() {
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('client.flow.state.token')
|
||||||
|
->willReturn(null);
|
||||||
|
|
||||||
|
$expected = new TemplateResponse(
|
||||||
|
'core',
|
||||||
|
'403',
|
||||||
|
[
|
||||||
|
'file' => 'State token does not match',
|
||||||
|
],
|
||||||
|
'guest'
|
||||||
|
);
|
||||||
|
$expected->setStatus(Http::STATUS_FORBIDDEN);
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->redirectPage('MyStateToken'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRedirectPage() {
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('client.flow.state.token')
|
||||||
|
->willReturn('MyStateToken');
|
||||||
|
|
||||||
|
$expected = new TemplateResponse(
|
||||||
|
'core',
|
||||||
|
'loginflow/redirect',
|
||||||
|
[
|
||||||
|
'urlGenerator' => $this->urlGenerator,
|
||||||
|
'stateToken' => 'MyStateToken',
|
||||||
|
],
|
||||||
|
'empty'
|
||||||
|
);
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->redirectPage('MyStateToken'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGenerateAppPasswordWithInvalidToken() {
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('client.flow.state.token')
|
||||||
|
->willReturn('OtherToken');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('remove')
|
||||||
|
->with('client.flow.state.token');
|
||||||
|
|
||||||
|
$expected = new TemplateResponse(
|
||||||
|
'core',
|
||||||
|
'403',
|
||||||
|
[
|
||||||
|
'file' => 'State token does not match',
|
||||||
|
],
|
||||||
|
'guest'
|
||||||
|
);
|
||||||
|
$expected->setStatus(Http::STATUS_FORBIDDEN);
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGenerateAppPasswordWithSessionNotAvailableException() {
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('client.flow.state.token')
|
||||||
|
->willReturn('MyStateToken');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('remove')
|
||||||
|
->with('client.flow.state.token');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getId')
|
||||||
|
->willThrowException(new SessionNotAvailableException());
|
||||||
|
|
||||||
|
$expected = new Http\Response();
|
||||||
|
$expected->setStatus(Http::STATUS_FORBIDDEN);
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGenerateAppPasswordWithInvalidTokenException() {
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('client.flow.state.token')
|
||||||
|
->willReturn('MyStateToken');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('remove')
|
||||||
|
->with('client.flow.state.token');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getId')
|
||||||
|
->willReturn('SessionId');
|
||||||
|
$this->tokenProvider
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getToken')
|
||||||
|
->with('SessionId')
|
||||||
|
->willThrowException(new InvalidTokenException());
|
||||||
|
|
||||||
|
$expected = new Http\Response();
|
||||||
|
$expected->setStatus(Http::STATUS_FORBIDDEN);
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGeneratePasswordWithPassword() {
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('client.flow.state.token')
|
||||||
|
->willReturn('MyStateToken');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('remove')
|
||||||
|
->with('client.flow.state.token');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getId')
|
||||||
|
->willReturn('SessionId');
|
||||||
|
$myToken = $this->createMock(IToken::class);
|
||||||
|
$myToken
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getLoginName')
|
||||||
|
->willReturn('MyLoginName');
|
||||||
|
$this->tokenProvider
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getToken')
|
||||||
|
->with('SessionId')
|
||||||
|
->willReturn($myToken);
|
||||||
|
$this->tokenProvider
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getPassword')
|
||||||
|
->with($myToken, 'SessionId')
|
||||||
|
->willReturn('MyPassword');
|
||||||
|
$this->random
|
||||||
|
->expects($this->once())
|
||||||
|
->method('generate')
|
||||||
|
->with(72)
|
||||||
|
->willReturn('MyGeneratedToken');
|
||||||
|
$user = $this->createMock(IUser::class);
|
||||||
|
$user
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getUID')
|
||||||
|
->willReturn('MyUid');
|
||||||
|
$this->userSession
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getUser')
|
||||||
|
->willReturn($user);
|
||||||
|
$this->tokenProvider
|
||||||
|
->expects($this->once())
|
||||||
|
->method('generateToken')
|
||||||
|
->with(
|
||||||
|
'MyGeneratedToken',
|
||||||
|
'MyUid',
|
||||||
|
'MyLoginName',
|
||||||
|
'MyPassword',
|
||||||
|
'unknown',
|
||||||
|
IToken::PERMANENT_TOKEN,
|
||||||
|
IToken::DO_NOT_REMEMBER
|
||||||
|
);
|
||||||
|
$this->request
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getServerHost')
|
||||||
|
->willReturn('example.com');
|
||||||
|
|
||||||
|
$expected = new Http\RedirectResponse('nc://MyLoginName:MyGeneratedToken@example.com');
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGeneratePasswordWithoutPassword() {
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('client.flow.state.token')
|
||||||
|
->willReturn('MyStateToken');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('remove')
|
||||||
|
->with('client.flow.state.token');
|
||||||
|
$this->session
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getId')
|
||||||
|
->willReturn('SessionId');
|
||||||
|
$myToken = $this->createMock(IToken::class);
|
||||||
|
$myToken
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getLoginName')
|
||||||
|
->willReturn('MyLoginName');
|
||||||
|
$this->tokenProvider
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getToken')
|
||||||
|
->with('SessionId')
|
||||||
|
->willReturn($myToken);
|
||||||
|
$this->tokenProvider
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getPassword')
|
||||||
|
->with($myToken, 'SessionId')
|
||||||
|
->willThrowException(new PasswordlessTokenException());
|
||||||
|
$this->random
|
||||||
|
->expects($this->once())
|
||||||
|
->method('generate')
|
||||||
|
->with(72)
|
||||||
|
->willReturn('MyGeneratedToken');
|
||||||
|
$user = $this->createMock(IUser::class);
|
||||||
|
$user
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getUID')
|
||||||
|
->willReturn('MyUid');
|
||||||
|
$this->userSession
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getUser')
|
||||||
|
->willReturn($user);
|
||||||
|
$this->tokenProvider
|
||||||
|
->expects($this->once())
|
||||||
|
->method('generateToken')
|
||||||
|
->with(
|
||||||
|
'MyGeneratedToken',
|
||||||
|
'MyUid',
|
||||||
|
'MyLoginName',
|
||||||
|
null,
|
||||||
|
'unknown',
|
||||||
|
IToken::PERMANENT_TOKEN,
|
||||||
|
IToken::DO_NOT_REMEMBER
|
||||||
|
);
|
||||||
|
$this->request
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getServerHost')
|
||||||
|
->willReturn('example.com');
|
||||||
|
|
||||||
|
$expected = new Http\RedirectResponse('nc://MyLoginName:MyGeneratedToken@example.com');
|
||||||
|
$this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -705,7 +705,55 @@ class UserTest extends TestCase {
|
||||||
'false'
|
'false'
|
||||||
);
|
);
|
||||||
|
|
||||||
$user = new User('foo', $backend, null, $config);
|
$user = $this->getMockBuilder(User::class)
|
||||||
|
->setConstructorArgs([
|
||||||
|
'foo',
|
||||||
|
$backend,
|
||||||
|
null,
|
||||||
|
$config,
|
||||||
|
])
|
||||||
|
->setMethods(['isEnabled', 'triggerChange'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$user->expects($this->once())
|
||||||
|
->method('isEnabled')
|
||||||
|
->willReturn(true);
|
||||||
|
$user->expects($this->once())
|
||||||
|
->method('triggerChange')
|
||||||
|
->with(
|
||||||
|
'enabled',
|
||||||
|
'false'
|
||||||
|
);
|
||||||
|
|
||||||
|
$user->setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetDisabledAlreadyDisabled() {
|
||||||
|
/**
|
||||||
|
* @var Backend | \PHPUnit_Framework_MockObject_MockObject $backend
|
||||||
|
*/
|
||||||
|
$backend = $this->createMock(\Test\Util\User\Dummy::class);
|
||||||
|
|
||||||
|
$config = $this->createMock(IConfig::class);
|
||||||
|
$config->expects($this->never())
|
||||||
|
->method('setUserValue');
|
||||||
|
|
||||||
|
$user = $this->getMockBuilder(User::class)
|
||||||
|
->setConstructorArgs([
|
||||||
|
'foo',
|
||||||
|
$backend,
|
||||||
|
null,
|
||||||
|
$config,
|
||||||
|
])
|
||||||
|
->setMethods(['isEnabled', 'triggerChange'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$user->expects($this->once())
|
||||||
|
->method('isEnabled')
|
||||||
|
->willReturn(false);
|
||||||
|
$user->expects($this->never())
|
||||||
|
->method('triggerChange');
|
||||||
|
|
||||||
$user->setEnabled(false);
|
$user->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue