Merge pull request #1447 from nextcloud/password-confirmation-for-some-actions

Password confirmation for some actions
This commit is contained in:
Morris Jobke 2016-11-18 15:42:30 +01:00 committed by GitHub
commit 332eaec4c0
35 changed files with 515 additions and 108 deletions

View File

@ -89,6 +89,11 @@
}.bind(this));
},
_onGenerateBackupCodes: function () {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._onGenerateBackupCodes, this));
return;
}
// Hide old codes
this._enabled = false;
this.render();

View File

@ -59,15 +59,17 @@ class SettingsController extends Controller {
/**
* @NoAdminRequired
* @PasswordConfirmationRequired
*
* @return JSONResponse
*/
public function createCodes() {
$user = $this->userSession->getUser();
$codes = $this->storage->createCodes($user);
return [
'codes' => $codes,
'state' => $this->storage->getBackupCodesState($user),
];
return new JSONResponse([
'codes' => $codes,
'state' => $this->storage->getBackupCodesState($user),
]);
}
}

View File

@ -24,6 +24,7 @@ namespace OCA\TwoFactorBackupCodes\Tests\Unit\Controller;
use OCA\TwoFactorBackupCodes\Controller\SettingsController;
use OCA\TwoFactorBackupCodes\Service\BackupCodeStorage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
@ -89,7 +90,9 @@ class SettingsControllerTest extends TestCase {
'codes' => $codes,
'state' => 'state',
];
$this->assertEquals($expected, $this->controller->createCodes());
$response = $this->controller->createCodes();
$this->assertInstanceOf(JSONResponse::class, $response);
$this->assertEquals($expected, $response->getData());
}
}

View File

@ -163,6 +163,11 @@
}
},
delete: function() {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.delete, this));
return;
}
this.model.destroy();
this.remove();
},
@ -173,6 +178,11 @@
this.render();
},
save: function() {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.save, this));
return;
}
var success = function(model, response, options) {
this.saving = false;
this.originalModel = JSON.parse(JSON.stringify(this.model));

View File

@ -58,6 +58,8 @@ class FlowOperations extends Controller {
}
/**
* @PasswordConfirmationRequired
*
* @param string $class
* @param string $name
* @param array[] $checks
@ -75,6 +77,8 @@ class FlowOperations extends Controller {
}
/**
* @PasswordConfirmationRequired
*
* @param int $id
* @param string $name
* @param array[] $checks
@ -92,6 +96,8 @@ class FlowOperations extends Controller {
}
/**
* @PasswordConfirmationRequired
*
* @param int $id
* @return JSONResponse
*/

View File

@ -1,5 +1,6 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@owncloud.com>
@ -31,6 +32,8 @@ use OC\User\Session;
use OC_App;
use OC_Util;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Authentication\TwoFactorAuth\IProvider;
@ -242,6 +245,8 @@ class LoginController extends Controller {
// 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 ($this->twoFactorManager->isTwoFactorAuthenticated($loginResult)) {
$this->twoFactorManager->prepareTwoFactorLogin($loginResult, $remember_login);
@ -273,4 +278,36 @@ class LoginController extends Controller {
return $this->generateRedirect($redirect_url);
}
/**
* @NoAdminRequired
* @UseSession
*
* @license GNU AGPL version 3 or any later version
*
* @param string $password
* @return DataResponse
*/
public function confirmPassword($password) {
$currentDelay = $this->throttler->getDelay($this->request->getRemoteAddress());
$this->throttler->sleepDelay($this->request->getRemoteAddress());
$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
}
$loginResult = $this->userManager->checkPassword($user->getUID(), $password);
if ($loginResult === false) {
$this->throttler->registerAttempt('sudo', $this->request->getRemoteAddress(), ['user' => $user->getUID()]);
if ($currentDelay === 0) {
$this->throttler->sleepDelay($this->request->getRemoteAddress());
}
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$confirmTimestamp = time();
$this->session->set('last-password-confirm', $confirmTimestamp);
return new DataResponse(['lastLogin' => $confirmTimestamp], Http::STATUS_OK);
}
}

View File

@ -32,6 +32,7 @@ use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUserSession;
@ -48,7 +49,8 @@ class OCJSController extends Controller {
* @param IL10N $l
* @param \OC_Defaults $defaults
* @param IAppManager $appManager
* @param IUserSession $session
* @param ISession $session
* @param IUserSession $userSession
* @param IConfig $config
* @param IGroupManager $groupManager
* @param IniGetWrapper $iniWrapper
@ -59,7 +61,8 @@ class OCJSController extends Controller {
IL10N $l,
\OC_Defaults $defaults,
IAppManager $appManager,
IUserSession $session,
ISession $session,
IUserSession $userSession,
IConfig $config,
IGroupManager $groupManager,
IniGetWrapper $iniWrapper,
@ -70,7 +73,8 @@ class OCJSController extends Controller {
$l,
$defaults,
$appManager,
$session->getUser(),
$session,
$userSession->getUser(),
$config,
$groupManager,
$iniWrapper,

View File

@ -22,7 +22,6 @@
.oc-dialog-buttonrow {
display: block;
background: transparent;
position: absolute;
right: 0;
bottom: 0;
padding: 10px;

View File

@ -1512,8 +1512,80 @@ function initCore() {
$(this).text(OC.Util.relativeModifiedDate(parseInt($(this).attr('data-timestamp'), 10)));
});
}, 30 * 1000);
OC.PasswordConfirmation.init();
}
OC.PasswordConfirmation = {
callback: null,
init: function() {
$('.password-confirm-required').on('click', _.bind(this.requirePasswordConfirmation, this));
},
requiresPasswordConfirmation: function() {
var timeSinceLogin = moment.now() - nc_lastLogin * 1000;
return timeSinceLogin > 10 * 1000; // 30 minutes
return timeSinceLogin > 30 * 60 * 1000; // 30 minutes
},
/**
* @param {function} callback
*/
requirePasswordConfirmation: function(callback) {
var self = this;
if (this.requiresPasswordConfirmation()) {
OC.dialogs.prompt(
t(
'core',
'This action requires you to confirm your password'
),
t('core','Authentication required'),
function (result, password) {
if (result && password !== '') {
self._confirmPassword(password);
}
},
true,
t('core','Password'),
true
).then(function() {
var $dialog = $('.oc-dialog:visible');
$dialog.find('.ui-icon').remove();
var $buttons = $dialog.find('button');
$buttons.eq(0).text(t('core', 'Cancel'));
$buttons.eq(1).text(t('core', 'Confirm'));
});
}
this.callback = callback;
},
_confirmPassword: function(password) {
var self = this;
$.ajax({
url: OC.generateUrl('/login/confirm'),
data: {
password: password
},
type: 'POST',
success: function(response) {
nc_lastLogin = response.lastLogin;
if (_.isFunction(self.callback)) {
self.callback();
}
},
error: function() {
OC.Notification.showTemporary(t('core', 'Failed to authenticate, try again'));
}
});
}
};
$(document).ready(initCore);
/**

View File

@ -33,6 +33,10 @@ OCP.AppConfig = {
* @internal
*/
_call: function(method, endpoint, options) {
if ((method === 'post' || method === 'delete') && OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._call, this, method, endpoint, options));
return;
}
$.ajax({
type: method.toUpperCase(),

View File

@ -46,6 +46,7 @@ $application->registerRoutes($this, [
['name' => 'avatar#getTmpAvatar', 'url' => '/avatar/tmp', 'verb' => 'GET'],
['name' => 'avatar#postAvatar', 'url' => '/avatar/', 'verb' => 'POST'],
['name' => 'login#tryLogin', 'url' => '/login', 'verb' => 'POST'],
['name' => 'login#confirmPassword', 'url' => '/login/confirm', 'verb' => 'POST'],
['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'],
['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'],
['name' => 'TwoFactorChallenge#selectChallenge', 'url' => '/login/selectchallenge', 'verb' => 'GET'],

View File

@ -146,6 +146,14 @@
</div>
</div></nav>
<div id="sudo-login-background" class="hidden"></div>
<div id="sudo-login-form" class="hidden">
<?php p($l->t('This action requires you to confirm your password:')); ?><br>
<input type="password" class="question" autocomplete="off" name="question" value=" <?php /* Hack against firefox ignoring autocomplete="off" */ ?>"
placeholder="<?php p($l->t('Confirm your password')); ?>" />
<input class="confirm icon-confirm" title="<?php p($l->t('Confirm')); ?>" value="" type="submit">
</div>
<div id="content-wrapper">
<div id="content" class="app-<?php p($_['appid']) ?>" role="main">
<?php print_unescaped($_['content']); ?>

View File

@ -265,6 +265,7 @@ return array(
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\AppNotEnabledException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\CrossSiteRequestForgeryException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotAdminException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotConfirmedException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotLoggedInException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\SecurityException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\StrictCookieMissingException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php',

View File

@ -295,6 +295,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\AppNotEnabledException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\CrossSiteRequestForgeryException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotAdminException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotConfirmedException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotLoggedInException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\SecurityException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\StrictCookieMissingException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php',

View File

@ -383,6 +383,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$app->getServer()->getNavigationManager(),
$app->getServer()->getURLGenerator(),
$app->getServer()->getLogger(),
$app->getServer()->getSession(),
$c['AppName'],
$app->isLoggedIn(),
$app->isAdminUser(),

View File

@ -0,0 +1,37 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @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\AppFramework\Middleware\Security\Exceptions;
use OCP\AppFramework\Http;
/**
* Class NotConfirmedException is thrown when a resource has been requested by a
* user that has not confirmed their password in the last 30 minutes.
*
* @package OC\AppFramework\Middleware\Security\Exceptions
*/
class NotConfirmedException extends SecurityException {
public function __construct() {
parent::__construct('Password confirmation is required', Http::STATUS_FORBIDDEN);
}
}

View File

@ -32,6 +32,7 @@ namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException;
use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException;
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException;
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
use OC\AppFramework\Utility\ControllerMethodReflector;
@ -47,6 +48,7 @@ use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\OCSController;
use OCP\INavigationManager;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IRequest;
use OCP\ILogger;
@ -73,6 +75,8 @@ class SecurityMiddleware extends Middleware {
private $urlGenerator;
/** @var ILogger */
private $logger;
/** @var ISession */
private $session;
/** @var bool */
private $isLoggedIn;
/** @var bool */
@ -90,6 +94,7 @@ class SecurityMiddleware extends Middleware {
* @param INavigationManager $navigationManager
* @param IURLGenerator $urlGenerator
* @param ILogger $logger
* @param ISession $session
* @param string $appName
* @param bool $isLoggedIn
* @param bool $isAdminUser
@ -102,6 +107,7 @@ class SecurityMiddleware extends Middleware {
INavigationManager $navigationManager,
IURLGenerator $urlGenerator,
ILogger $logger,
ISession $session,
$appName,
$isLoggedIn,
$isAdminUser,
@ -114,6 +120,7 @@ class SecurityMiddleware extends Middleware {
$this->appName = $appName;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->session = $session;
$this->isLoggedIn = $isLoggedIn;
$this->isAdminUser = $isAdminUser;
$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
@ -150,6 +157,13 @@ class SecurityMiddleware extends Middleware {
}
}
if ($this->reflector->hasAnnotation('PasswordConfirmationRequired')) {
$lastConfirm = (int) $this->session->get('last-password-confirm');
if ($lastConfirm < (time() - (30 * 60 + 15))) { // allow 15 seconds delay
throw new NotConfirmedException();
}
}
// Check for strict cookie requirement
if($this->reflector->hasAnnotation('StrictCookieRequired') || !$this->reflector->hasAnnotation('NoCSRFRequired')) {
if(!$this->request->passesStrictCookieCheck()) {

View File

@ -27,6 +27,7 @@ use OCP\App\IAppManager;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
@ -41,7 +42,10 @@ class JSConfigHelper {
/** @var IAppManager */
private $appManager;
/** @var IUser */
/** @var ISession */
private $session;
/** @var IUser|null */
private $currentUser;
/** @var IConfig */
@ -60,6 +64,7 @@ class JSConfigHelper {
* @param IL10N $l
* @param \OC_Defaults $defaults
* @param IAppManager $appManager
* @param ISession $session
* @param IUser|null $currentUser
* @param IConfig $config
* @param IGroupManager $groupManager
@ -69,6 +74,7 @@ class JSConfigHelper {
public function __construct(IL10N $l,
\OC_Defaults $defaults,
IAppManager $appManager,
ISession $session,
$currentUser,
IConfig $config,
IGroupManager $groupManager,
@ -77,6 +83,7 @@ class JSConfigHelper {
$this->l = $l;
$this->defaults = $defaults;
$this->appManager = $appManager;
$this->session = $session;
$this->currentUser = $currentUser;
$this->config = $config;
$this->groupManager = $groupManager;
@ -119,6 +126,16 @@ class JSConfigHelper {
$dataLocation = false;
}
if ($this->currentUser instanceof IUser) {
$lastConfirmTimestamp = $this->currentUser->getLastLogin();
$sessionTime = $this->session->get('last-password-confirm');
if (is_int($sessionTime)) {
$lastConfirmTimestamp = $sessionTime;
}
} else {
$lastConfirmTimestamp = 0;
}
$array = [
"oc_debug" => $this->config->getSystemValue('debug', false) ? 'true' : 'false',
"oc_isadmin" => $this->groupManager->isAdmin($uid) ? 'true' : 'false',
@ -126,6 +143,7 @@ class JSConfigHelper {
"oc_webroot" => "\"".\OC::$WEBROOT."\"",
"oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution
"datepickerFormatDate" => json_encode($this->l->l('jsdate', null)),
'nc_lastLogin' => $lastConfirmTimestamp,
"dayNames" => json_encode([
(string)$this->l->t('Sunday'),
(string)$this->l->t('Monday'),

View File

@ -148,6 +148,7 @@ class TemplateLayout extends \OC_Template {
\OC::$server->getL10N('core'),
\OC::$server->getThemingDefaults(),
\OC::$server->getAppManager(),
\OC::$server->getSession(),
\OC::$server->getUserSession()->getUser(),
\OC::$server->getConfig(),
\OC::$server->getGroupManager(),

View File

@ -111,7 +111,9 @@ class AuthSettingsController extends Controller {
/**
* @NoAdminRequired
* @NoSubadminRequired
* @PasswordConfirmationRequired
*
* @param string $name
* @return JSONResponse
*/
public function create($name) {
@ -138,11 +140,11 @@ class AuthSettingsController extends Controller {
$tokenData = $deviceToken->jsonSerialize();
$tokenData['canDelete'] = true;
return [
return new JSONResponse([
'token' => $token,
'loginName' => $loginName,
'deviceToken' => $tokenData
];
'deviceToken' => $tokenData,
]);
}
private function getServiceNotAvailableResponse() {

View File

@ -131,6 +131,7 @@ class ChangePasswordController extends Controller {
/**
* @NoAdminRequired
* @PasswordConfirmationRequired
*
* @param string $username
* @param string $password

View File

@ -95,6 +95,7 @@ class GroupsController extends Controller {
}
/**
* @PasswordConfirmationRequired
* @param string $id
* @return DataResponse
*/
@ -128,6 +129,7 @@ class GroupsController extends Controller {
}
/**
* @PasswordConfirmationRequired
* @param string $id
* @return DataResponse
*/

View File

@ -73,6 +73,9 @@ class MailSettingsController extends Controller {
/**
* Sets the email settings
*
* @PasswordConfirmationRequired
*
* @param string $mail_domain
* @param string $mail_from_address
* @param string $mail_smtpmode
@ -116,6 +119,9 @@ class MailSettingsController extends Controller {
/**
* Store the credentials used for SMTP in the config
*
* @PasswordConfirmationRequired
*
* @param string $mail_smtpname
* @param string $mail_smtppassword
* @return array

View File

@ -301,6 +301,7 @@ class UsersController extends Controller {
/**
* @NoAdminRequired
* @PasswordConfirmationRequired
*
* @param string $username
* @param string $password
@ -433,6 +434,7 @@ class UsersController extends Controller {
/**
* @NoAdminRequired
* @PasswordConfirmationRequired
*
* @param string $id
* @return DataResponse
@ -495,6 +497,7 @@ class UsersController extends Controller {
*
* @NoAdminRequired
* @NoSubadminRequired
* @PasswordConfirmationRequired
*
* @param string $id
* @param string $mailAddress
@ -615,6 +618,7 @@ class UsersController extends Controller {
*
* @NoAdminRequired
* @NoSubadminRequired
* @PasswordConfirmationRequired
*
* @param string $username
* @param string $displayName

View File

@ -32,6 +32,13 @@
OC_JSON::checkSubAdminUser();
OCP\JSON::callCheck();
$lastConfirm = (int) \OC::$server->getSession()->get('last-password-confirm');
if ($lastConfirm < (time() - 30 * 60 + 15)) { // allow 15 seconds delay
$l = \OC::$server->getL10N('core');
OC_JSON::error(array( 'data' => array( 'message' => $l->t('Password confirmation is required'))));
exit();
}
$username = isset($_POST["username"]) ? (string)$_POST["username"] : '';
$isUserAccessible = false;

View File

@ -28,6 +28,13 @@
OC_JSON::checkSubAdminUser();
OCP\JSON::callCheck();
$lastConfirm = (int) \OC::$server->getSession()->get('last-password-confirm');
if ($lastConfirm < (time() - 30 * 60 + 15)) { // allow 15 seconds delay
$l = \OC::$server->getL10N('core');
OC_JSON::error(array( 'data' => array( 'message' => $l->t('Password confirmation is required'))));
exit();
}
$success = true;
$username = (string)$_POST['username'];
$group = (string)$_POST['group'];

View File

@ -24,6 +24,13 @@
OC_JSON::checkAdminUser();
OCP\JSON::callCheck();
$lastConfirm = (int) \OC::$server->getSession()->get('last-password-confirm');
if ($lastConfirm < (time() - 30 * 60 + 15)) { // allow 15 seconds delay
$l = \OC::$server->getL10N('core');
OC_JSON::error(array( 'data' => array( 'message' => $l->t('Password confirmation is required'))));
exit();
}
$username = (string)$_POST['username'];
$group = (string)$_POST['group'];

View File

@ -172,21 +172,48 @@ $(document).ready(function(){
}
});
$('#mail_general_settings_form').change(function(){
OC.msg.startSaving('#mail_settings_msg');
var post = $( "#mail_general_settings_form" ).serialize();
$.post(OC.generateUrl('/settings/admin/mailsettings'), post, function(data){
OC.msg.finishedSaving('#mail_settings_msg', data);
});
});
var changeEmailSettings = function() {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(changeEmailSettings);
return;
}
$('#mail_credentials_settings_submit').click(function(){
OC.msg.startSaving('#mail_settings_msg');
var post = $( "#mail_credentials_settings" ).serialize();
$.post(OC.generateUrl('/settings/admin/mailsettings/credentials'), post, function(data){
OC.msg.finishedSaving('#mail_settings_msg', data);
$.ajax({
url: OC.generateUrl('/settings/admin/mailsettings'),
type: 'POST',
data: $('#mail_general_settings_form').serialize(),
success: function(data){
OC.msg.finishedSaving('#mail_settings_msg', data);
},
error: function(data){
OC.msg.finishedError('#mail_settings_msg', data.responseJSON.message);
}
});
});
};
var toggleEmailCredentials = function() {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(toggleEmailCredentials);
return;
}
OC.msg.startSaving('#mail_settings_msg');
$.ajax({
url: OC.generateUrl('/settings/admin/mailsettings/credentials'),
type: 'POST',
data: $('#mail_credentials_settings').serialize(),
success: function(data){
OC.msg.finishedSaving('#mail_settings_msg', data);
},
error: function(data){
OC.msg.finishedError('#mail_settings_msg', data.responseJSON.message);
}
});
};
$('#mail_general_settings_form').change(changeEmailSettings);
$('#mail_credentials_settings_submit').click(toggleEmailCredentials);
$('#sendtestemail').click(function(event){
event.preventDefault();

View File

@ -299,6 +299,11 @@
},
_addAppPassword: function () {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._addAppPassword, this));
return;
}
var _this = this;
this._toggleAddingToken(true);

View File

@ -65,7 +65,7 @@ function changeEmailAddress () {
// for failure the first parameter is the result object
OC.msg.finishedSaving('#lostpassword .msg', result);
}).fail(function(result){
OC.msg.finishedSaving('#lostpassword .msg', result.responseJSON);
OC.msg.finishedError('#lostpassword .msg', result.responseJSON.message);
});
}

View File

@ -128,6 +128,11 @@ GroupList = {
},
createGroup: function (groupname) {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.createGroup, this, groupname));
return;
}
$.post(
OC.generateUrl('/settings/users/groups'),
{
@ -278,10 +283,16 @@ GroupList = {
GroupList.show);
//when to mark user for delete
$userGroupList.on('click', '.delete', function () {
var deleteAction = function () {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(deleteAction, this));
return;
}
// Call function for handling delete/undo
GroupDeleteHandler.mark(GroupList.getElementGID(this));
});
};
$userGroupList.on('click', '.delete', deleteAction);
//delete a marked user when leaving the page
$(window).on('beforeunload', function () {

View File

@ -353,6 +353,14 @@ var UserList = {
$userListBody.on('click', '.delete', function () {
// Call function for handling delete/undo
var uid = UserList.getUID(this);
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(function() {
UserDeleteHandler.mark(uid);
});
return;
}
UserDeleteHandler.mark(uid);
});
@ -405,6 +413,11 @@ var UserList = {
},
applyGroupSelect: function (element, user, checked) {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applySubadminSelect, this, element, user, checked));
return;
}
var $element = $(element);
var checkHandler = null;
@ -467,6 +480,11 @@ var UserList = {
},
applySubadminSelect: function (element, user, checked) {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applySubadminSelect, this, element, user, checked));
return;
}
var $element = $(element);
var checkHandler = function (group) {
if (group === 'admin') {
@ -478,7 +496,10 @@ var UserList = {
username: user,
group: group
},
function () {
function (response) {
if (response.data.message) {
OC.Notification.show(response.data.message);
}
}
);
};
@ -518,7 +539,7 @@ var UserList = {
OC.Notification.showTemporary(t('core', 'Invalid quota value "{val}"', {val: quota}));
return;
}
UserList._updateQuota(uid, quota, function(returnedQuota){
UserList._updateQuota(uid, quota, function(returnedQuota) {
if (quota !== returnedQuota) {
$select.find(':selected').text(returnedQuota);
}
@ -532,12 +553,21 @@ var UserList = {
* @param {Function} ready callback after save
*/
_updateQuota: function(uid, quota, ready) {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._updateQuota, this, uid, quota, ready));
return;
}
$.post(
OC.filePath('settings', 'ajax', 'setquota.php'),
{username: uid, quota: quota},
function (result) {
if (ready) {
ready(result.data.quota);
if (result.status === 'error') {
OC.Notification.showTemporary(result.data.message);
} else {
if (ready) {
ready(result.data.quota);
}
}
}
);
@ -635,6 +665,28 @@ $(document).ready(function () {
// TODO: move other init calls inside of initialize
UserList.initialize($('#userlist'));
var _submitPasswordChange = function(uid, password, recoveryPasswordVal, blurFunction) {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(function() {
_submitPasswordChange(uid, password, recoveryPasswordVal, blurFunction);
});
return;
}
$.post(
OC.generateUrl('/settings/users/changepassword'),
{username: uid, password: password, recoveryPassword: recoveryPasswordVal},
function (result) {
blurFunction();
if (result.status === 'success') {
OC.Notification.showTemporary(t('admin', 'Password successfully changed'));
} else {
OC.Notification.showTemporary(t('admin', result.data.message));
}
}
).fail(blurFunction);
};
$userListBody.on('click', '.password', function (event) {
event.stopPropagation();
@ -643,6 +695,12 @@ $(document).ready(function () {
var uid = UserList.getUID($td);
var $input = $('<input type="password">');
var isRestoreDisabled = UserList.getRestoreDisabled($td) === true;
var blurFunction = function () {
$(this).replaceWith($('<span>●●●●●●●</span>'));
$td.find('img').show();
// remove highlight class from users without recovery ability
$tr.removeClass('row-warning');
};
if(isRestoreDisabled) {
$tr.addClass('row-warning');
// add tipsy if the password change could cause data loss - no recovery enabled
@ -657,34 +715,51 @@ $(document).ready(function () {
if (event.keyCode === 13) {
if ($(this).val().length > 0) {
var recoveryPasswordVal = $('input:password[id="recoveryPassword"]').val();
$.post(
OC.generateUrl('/settings/users/changepassword'),
{username: uid, password: $(this).val(), recoveryPassword: recoveryPasswordVal},
function (result) {
if (result.status === 'success') {
OC.Notification.showTemporary(t('admin', 'Password successfully changed'));
} else {
OC.Notification.showTemporary(t('admin', result.data.message));
}
}
);
$input.blur();
$input.off('blur');
_submitPasswordChange(uid, $(this).val(), recoveryPasswordVal, blurFunction);
} else {
$input.blur();
}
}
})
.blur(function () {
$(this).replaceWith($('<span>●●●●●●●</span>'));
$td.find('img').show();
// remove highlight class from users without recovery ability
$tr.removeClass('row-warning');
});
.blur(blurFunction);
});
$('input:password[id="recoveryPassword"]').keyup(function() {
OC.Notification.hide();
});
var _submitDisplayNameChange = function($tr, uid, displayName, blurFunction) {
var $div = $tr.find('div.avatardiv');
if ($div.length) {
$div.imageplaceholder(uid, displayName);
}
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(function() {
_submitDisplayNameChange($tr, uid, displayName, blurFunction);
});
return;
}
$.ajax({
type: 'POST',
url: OC.generateUrl('/settings/users/{id}/displayName', {id: uid}),
data: {
username: uid,
displayName: displayName
}
}).success(function (result) {
if (result && result.status==='success' && $div.length){
$div.avatar(result.data.username, 32);
}
$tr.data('displayname', displayName);
blurFunction();
}).fail(function (result) {
OC.Notification.showTemporary(result.responseJSON.message);
$tr.find('.displayName input').blur(blurFunction);
});
};
$userListBody.on('click', '.displayName', function (event) {
event.stopPropagation();
var $td = $(this).closest('td');
@ -692,6 +767,11 @@ $(document).ready(function () {
var uid = UserList.getUID($td);
var displayName = escapeHTML(UserList.getDisplayName($td));
var $input = $('<input type="text" value="' + displayName + '">');
var blurFunction = function() {
var displayName = $tr.data('displayname');
$input.replaceWith('<span>' + escapeHTML(displayName) + '</span>');
$td.find('img').show();
};
$td.find('img').hide();
$td.children('span').replaceWith($input);
$input
@ -699,34 +779,53 @@ $(document).ready(function () {
.keypress(function (event) {
if (event.keyCode === 13) {
if ($(this).val().length > 0) {
var $div = $tr.find('div.avatardiv');
if ($div.length) {
$div.imageplaceholder(uid, displayName);
}
$.post(
OC.generateUrl('/settings/users/{id}/displayName', {id: uid}),
{username: uid, displayName: $(this).val()},
function (result) {
if (result && result.status==='success' && $div.length){
$div.avatar(result.data.username, 32);
}
}
);
var displayName = $input.val();
$tr.data('displayname', displayName);
$input.blur();
$input.off('blur');
_submitDisplayNameChange($tr, uid, $(this).val(), blurFunction);
} else {
$input.blur();
}
}
})
.blur(function () {
var displayName = $tr.data('displayname');
$input.replaceWith('<span>' + escapeHTML(displayName) + '</span>');
$td.find('img').show();
});
.blur(blurFunction);
});
var _submitEmailChange = function($tr, $td, $input, uid, mailAddress, blurFunction) {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(function() {
_submitEmailChange($tr, $td, $input, uid, mailAddress, blurFunction);
});
return;
}
$.ajax({
type: 'PUT',
url: OC.generateUrl('/settings/users/{id}/mailAddress', {id: uid}),
data: {
mailAddress: mailAddress
}
}).success(function () {
// set data attribute to new value
// will in blur() be used to show the text instead of the input field
$tr.data('mailAddress', mailAddress);
$td.find('.loading-small').css('display', '');
$input.removeAttr('disabled')
.triggerHandler('blur'); // needed instead of $input.blur() for Firefox
blurFunction();
}).fail(function (result) {
if (!_.isUndefined(result.responseJSON.data)) {
OC.Notification.showTemporary(result.responseJSON.data.message);
} else if (!_.isUndefined(result.responseJSON.message)) {
OC.Notification.showTemporary(result.responseJSON.message);
} else {
OC.Notification.showTemporary(t('settings', 'Could not change the users email'));
}
$td.find('.loading-small').css('display', '');
$input.removeAttr('disabled')
.css('padding-right', '6px');
$input.blur(blurFunction);
});
};
$userListBody.on('click', '.mailAddress', function (event) {
event.stopPropagation();
var $td = $(this).closest('td');
@ -734,6 +833,15 @@ $(document).ready(function () {
var uid = UserList.getUID($td);
var mailAddress = escapeHTML(UserList.getMailAddress($td));
var $input = $('<input type="text">').val(mailAddress);
var blurFunction = function() {
if($td.find('.loading-small').css('display') === 'inline-block') {
// in Chrome the blur event is fired too early by the browser - even if the request is still running
return;
}
var $span = $('<span>').text($tr.data('mailAddress'));
$input.replaceWith($span);
$td.find('img').show();
};
$td.children('span').replaceWith($input);
$td.find('img').hide();
$input
@ -742,40 +850,14 @@ $(document).ready(function () {
if (event.keyCode === 13) {
// enter key
var mailAddress = $input.val();
$td.find('.loading-small').css('display', 'inline-block');
$input.css('padding-right', '26px');
$input.attr('disabled', 'disabled');
$.ajax({
type: 'PUT',
url: OC.generateUrl('/settings/users/{id}/mailAddress', {id: uid}),
data: {
mailAddress: $(this).val()
}
}).success(function () {
// set data attribute to new value
// will in blur() be used to show the text instead of the input field
$tr.data('mailAddress', mailAddress);
$td.find('.loading-small').css('display', '');
$input.removeAttr('disabled')
.triggerHandler('blur'); // needed instead of $input.blur() for Firefox
}).fail(function (result) {
OC.Notification.showTemporary(result.responseJSON.data.message);
$td.find('.loading-small').css('display', '');
$input.removeAttr('disabled')
.css('padding-right', '6px');
});
$input.off('blur');
_submitEmailChange($tr, $td, $input, uid, $(this).val(), blurFunction);
}
})
.blur(function () {
if($td.find('.loading-small').css('display') === 'inline-block') {
// in Chrome the blur event is fired too early by the browser - even if the request is still running
return;
}
var $span = $('<span>').text($tr.data('mailAddress'));
$input.replaceWith($span);
$td.find('img').show();
});
.blur(blurFunction);
});
$('#newuser .groupsListContainer').on('click', function (event) {
@ -796,8 +878,15 @@ $(document).ready(function () {
});
UserList._updateGroupListLabel($('#newuser .groups'), []);
$('#newuser').submit(function (event) {
var _submitNewUserForm = function (event) {
event.preventDefault();
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(function() {
_submitNewUserForm(event);
});
return;
}
var username = $('#newusername').val();
var password = $('#newuserpassword').val();
var email = $('#newemail').val();
@ -866,7 +955,8 @@ $(document).ready(function () {
$('#newuser').get(0).reset();
});
});
});
};
$('#newuser').submit(_submitNewUserForm);
if ($('#CheckboxStorageLocation').is(':checked')) {
$("#userlist .storageLocation").show();
@ -874,11 +964,17 @@ $(document).ready(function () {
// Option to display/hide the "Storage location" column
$('#CheckboxStorageLocation').click(function() {
if ($('#CheckboxStorageLocation').is(':checked')) {
$("#userlist .storageLocation").show();
OCP.AppConfig.setValue('core', 'umgmt_show_storage_location', 'true');
OCP.AppConfig.setValue('core', 'umgmt_show_storage_location', 'true', {
success: function () {
$("#userlist .storageLocation").show();
}
});
} else {
$("#userlist .storageLocation").hide();
OCP.AppConfig.setValue('core', 'umgmt_show_storage_location', 'false');
OCP.AppConfig.setValue('core', 'umgmt_show_storage_location', 'false', {
success: function () {
$("#userlist .storageLocation").hide();
}
});
}
});

View File

@ -65,7 +65,7 @@ if($_['displayNameChangeSupported']) {
<h2>
<label for="displayName"><?php echo $l->t('Full name');?></label>
</h2>
<input type="text" id="displayName" name="displayName"
<input type="text" id="displayName" name="displayName" class="password-confirm-required"
value="<?php p($_['displayName'])?>"
autocomplete="on" autocapitalize="off" autocorrect="off" />
<span class="msg"></span>
@ -91,6 +91,7 @@ if($_['displayNameChangeSupported']) {
</h2>
<input type="email" name="email" id="email" value="<?php p($_['email']); ?>"
placeholder="<?php p($l->t('Your email address'));?>"
class="password-confirm-required"
autocomplete="on" autocapitalize="off" autocorrect="off" />
<span class="msg"></span><br />
<em><?php p($l->t('For password recovery and notifications'));?></em>

View File

@ -153,7 +153,9 @@ class AuthSettingsControllerTest extends TestCase {
'deviceToken' => ['dummy' => 'dummy', 'canDelete' => true],
'loginName' => 'User13',
];
$this->assertEquals($expected, $this->controller->create($name));
$response = $this->controller->create($name);
$this->assertInstanceOf(JSONResponse::class, $response);
$this->assertEquals($expected, $response->getData());
}
public function testCreateSessionNotAvailable() {

View File

@ -49,6 +49,7 @@ use OCP\IConfig;
use OCP\ILogger;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\Security\ISecureRandom;
@ -63,6 +64,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
private $secException;
/** @var SecurityException */
private $secAjaxException;
/** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
private $session;
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
private $request;
/** @var ControllerMethodReflector */
@ -88,6 +91,7 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->logger = $this->createMock(ILogger::class);
$this->navigationManager = $this->createMock(INavigationManager::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->session = $this->createMock(ISession::class);
$this->request = $this->createMock(IRequest::class);
$this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class);
$this->csrfTokenManager = $this->createMock(CsrfTokenManager::class);
@ -109,6 +113,7 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->navigationManager,
$this->urlGenerator,
$this->logger,
$this->session,
'files',
$isLoggedIn,
$isAdminUser,