diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php index a7f8c251ce..eceaed0c38 100644 --- a/lib/private/Template/JSConfigHelper.php +++ b/lib/private/Template/JSConfigHelper.php @@ -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'), diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index 7878737bde..8919f14216 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -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(), From 827b6a610e877969f8b6ab294f71a27657788ef0 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 19 Sep 2016 16:03:03 +0200 Subject: [PATCH 02/28] Introduce PasswordConfirmRequired annotation Signed-off-by: Joas Schilling --- .../DependencyInjection/DIContainer.php | 1 + .../Exceptions/NotConfirmedException.php | 37 +++++++++++++++++++ .../Security/SecurityMiddleware.php | 14 +++++++ 3 files changed, 52 insertions(+) create mode 100644 lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index e1516c47ed..48c9b6f458 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -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(), diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php new file mode 100644 index 0000000000..1ecd463b00 --- /dev/null +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php @@ -0,0 +1,37 @@ + + * + * @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 . + * + */ + +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); + } +} diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 183e55740e..81cc09c7f5 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -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()) { From 28ddf3abdbe481b8714bdd2bc9dad43c805680e4 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 19 Sep 2016 16:14:51 +0200 Subject: [PATCH 03/28] Require password confirmation for changing the email Signed-off-by: Joas Schilling --- settings/Controller/UsersController.php | 1 + settings/templates/personal.php | 1 + 2 files changed, 2 insertions(+) diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index fde29de359..4c732a94c9 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -495,6 +495,7 @@ class UsersController extends Controller { * * @NoAdminRequired * @NoSubadminRequired + * @PasswordConfirmationRequired * * @param string $id * @param string $mailAddress diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 69d3660477..94d4a39185 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -91,6 +91,7 @@ if($_['displayNameChangeSupported']) {
t('For password recovery and notifications'));?> From a0d6c6593a4c6f08c2c4dd516c85ad59eb639be2 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 19 Sep 2016 16:52:29 +0200 Subject: [PATCH 04/28] Allow to check it via the API Signed-off-by: Joas Schilling --- core/js/js.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/js/js.js b/core/js/js.js index bc99e1c77d..f498ed751c 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1521,6 +1521,7 @@ OC.PasswordConfirmation = { $background: null, $input: null, $submit: null, + callback: null, init: function() { this.$form = $('#sudo-login-form'); @@ -1533,9 +1534,16 @@ OC.PasswordConfirmation = { this.$submit.on('click', _.bind(this._submitPasswordConfirmation, this)); }, - requirePasswordConfirmation: function() { + requiresPasswordConfirmation: function() { var timeSinceLogin = moment.now() - nc_lastLogin * 1000; - if (timeSinceLogin > 30 * 60 * 1000) { // 30 minutes + return timeSinceLogin > 30 * 60 * 1000; // 30 minutes + }, + + /** + * @param {function} callback + */ + requirePasswordConfirmation: function(callback) { + if (this.requiresPasswordConfirmation()) { this.$form.removeClass('hidden'); this.$background.removeClass('hidden'); @@ -1544,6 +1552,8 @@ OC.PasswordConfirmation = { this.$input.val(''); } } + + this.callback = callback; }, _submitPasswordConfirmation: function() { @@ -1563,6 +1573,10 @@ OC.PasswordConfirmation = { self.$form.addClass('hidden'); self.$background.addClass('hidden'); + + if (!_.isUndefined(self.callback)) { + self.callback(); + } }, error: function() { OC.Notification.showTemporary(t('core', 'Failed to authenticate, try again')); From 86aa6197b881540b39d8a3c4475ee6b453b71aab Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 19 Sep 2016 16:58:38 +0200 Subject: [PATCH 05/28] Require confirmation when generating backup codes Signed-off-by: Joas Schilling --- apps/twofactor_backupcodes/js/settingsview.js | 5 +++++ .../lib/Controller/SettingsController.php | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/twofactor_backupcodes/js/settingsview.js b/apps/twofactor_backupcodes/js/settingsview.js index 224f5f4797..7639602312 100644 --- a/apps/twofactor_backupcodes/js/settingsview.js +++ b/apps/twofactor_backupcodes/js/settingsview.js @@ -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(); diff --git a/apps/twofactor_backupcodes/lib/Controller/SettingsController.php b/apps/twofactor_backupcodes/lib/Controller/SettingsController.php index fed7634643..9b0b0fc57b 100644 --- a/apps/twofactor_backupcodes/lib/Controller/SettingsController.php +++ b/apps/twofactor_backupcodes/lib/Controller/SettingsController.php @@ -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), + ]); } } From 68fa1e5dd8a588d3903dfee4dab5c740637e0cf6 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 19 Sep 2016 17:13:30 +0200 Subject: [PATCH 06/28] Require password confirmation for app password Signed-off-by: Joas Schilling --- settings/Controller/AuthSettingsController.php | 8 +++++--- settings/js/authtoken_view.js | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php index 4e3d05a14e..732f3abd1f 100644 --- a/settings/Controller/AuthSettingsController.php +++ b/settings/Controller/AuthSettingsController.php @@ -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() { diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index 0939913cc1..c45e4b7ec4 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -299,6 +299,11 @@ }, _addAppPassword: function () { + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._addAppPassword, this)); + return; + } + var _this = this; this._toggleAddingToken(true); From 6c3f0fd431c8e2dfa79a421c878bb5d15a42c1d5 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 19 Sep 2016 17:34:11 +0200 Subject: [PATCH 07/28] Fix error message displaying Signed-off-by: Joas Schilling --- settings/js/personal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/js/personal.js b/settings/js/personal.js index aef67f719c..c2cb437bd1 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -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); }); } From cc33f8695bdbbb2869608a78f9fb298eddc8929b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 19 Sep 2016 18:37:55 +0200 Subject: [PATCH 08/28] Make sure it is a function Signed-off-by: Joas Schilling --- core/js/js.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/js/js.js b/core/js/js.js index f498ed751c..efdb36187c 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1574,7 +1574,7 @@ OC.PasswordConfirmation = { self.$form.addClass('hidden'); self.$background.addClass('hidden'); - if (!_.isUndefined(self.callback)) { + if (_.isFunction(self.callback)) { self.callback(); } }, From 62855c08ffbc7697e43f8aeca42095add8b84986 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 19 Sep 2016 18:40:47 +0200 Subject: [PATCH 09/28] Require confirmation when changing the email settings Signed-off-by: Joas Schilling --- .../Controller/MailSettingsController.php | 6 +++ settings/js/admin.js | 51 ++++++++++++++----- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/settings/Controller/MailSettingsController.php b/settings/Controller/MailSettingsController.php index 84423aa8e2..108ac3f393 100644 --- a/settings/Controller/MailSettingsController.php +++ b/settings/Controller/MailSettingsController.php @@ -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 diff --git a/settings/js/admin.js b/settings/js/admin.js index 7c2b507280..094b12b2ba 100644 --- a/settings/js/admin.js +++ b/settings/js/admin.js @@ -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(); From 145da710a5cab401a0ac226b4c4141ba4f4ab6d9 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 25 Oct 2016 12:08:52 +0200 Subject: [PATCH 10/28] Require confirmation for app config changes Signed-off-by: Joas Schilling --- core/js/public/appconfig.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/js/public/appconfig.js b/core/js/public/appconfig.js index de04f334ca..5021f78e2d 100644 --- a/core/js/public/appconfig.js +++ b/core/js/public/appconfig.js @@ -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, arguments)); + return; + } $.ajax({ type: method.toUpperCase(), From 410e0fc28f25835c4b10e31066b641387cb53391 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 25 Oct 2016 12:20:02 +0200 Subject: [PATCH 11/28] Require password confirmation when changing workflow rules Signed-off-by: Joas Schilling --- apps/workflowengine/js/admin.js | 10 ++++++++++ apps/workflowengine/lib/Controller/FlowOperations.php | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/apps/workflowengine/js/admin.js b/apps/workflowengine/js/admin.js index 0357d741ab..7f9a709ec1 100644 --- a/apps/workflowengine/js/admin.js +++ b/apps/workflowengine/js/admin.js @@ -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)); diff --git a/apps/workflowengine/lib/Controller/FlowOperations.php b/apps/workflowengine/lib/Controller/FlowOperations.php index 94b8b9ddc7..753aa2c26a 100644 --- a/apps/workflowengine/lib/Controller/FlowOperations.php +++ b/apps/workflowengine/lib/Controller/FlowOperations.php @@ -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 */ From 2fd2e45e428b24f16b7724b7a31d660ba67d2ef1 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 25 Oct 2016 13:05:13 +0200 Subject: [PATCH 12/28] Require password confirmation for user management Signed-off-by: Joas Schilling --- .../Controller/ChangePasswordController.php | 1 + settings/Controller/GroupsController.php | 2 + settings/Controller/UsersController.php | 3 + settings/ajax/togglegroups.php | 7 ++ settings/ajax/togglesubadmins.php | 7 ++ settings/js/users/users.js | 68 +++++++++++++++---- 6 files changed, 74 insertions(+), 14 deletions(-) diff --git a/settings/Controller/ChangePasswordController.php b/settings/Controller/ChangePasswordController.php index e43d0d8f34..832cdbefdb 100644 --- a/settings/Controller/ChangePasswordController.php +++ b/settings/Controller/ChangePasswordController.php @@ -131,6 +131,7 @@ class ChangePasswordController extends Controller { /** * @NoAdminRequired + * @PasswordConfirmationRequired * * @param string $username * @param string $password diff --git a/settings/Controller/GroupsController.php b/settings/Controller/GroupsController.php index feed45b118..8985a76ec9 100644 --- a/settings/Controller/GroupsController.php +++ b/settings/Controller/GroupsController.php @@ -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 */ diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 4c732a94c9..89831a66ab 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -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 @@ -616,6 +618,7 @@ class UsersController extends Controller { * * @NoAdminRequired * @NoSubadminRequired + * @PasswordConfirmationRequired * * @param string $username * @param string $displayName diff --git a/settings/ajax/togglegroups.php b/settings/ajax/togglegroups.php index ff79861b81..b9958bef0c 100644 --- a/settings/ajax/togglegroups.php +++ b/settings/ajax/togglegroups.php @@ -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']; diff --git a/settings/ajax/togglesubadmins.php b/settings/ajax/togglesubadmins.php index 390e5c09ef..5658a38241 100644 --- a/settings/ajax/togglesubadmins.php +++ b/settings/ajax/togglesubadmins.php @@ -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']; diff --git a/settings/js/users/users.js b/settings/js/users/users.js index 3a357c0e9c..7f23f2dad3 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -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, arguments)); + 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, arguments)); + 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); + } } ); }; @@ -635,6 +656,27 @@ $(document).ready(function () { // TODO: move other init calls inside of initialize UserList.initialize($('#userlist')); + var _submitPasswordChange = function(uid, password, recoveryPasswordVal) { + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + OC.PasswordConfirmation.requirePasswordConfirmation(function() { + _submitPasswordChange(uid, password, recoveryPasswordVal); + }); + return; + } + + $.post( + OC.generateUrl('/settings/users/changepassword'), + {username: uid, password: password, 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)); + } + } + ); + }; + $userListBody.on('click', '.password', function (event) { event.stopPropagation(); @@ -657,17 +699,7 @@ $(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)); - } - } - ); + _submitPasswordChange(uid, $(this).val(), recoveryPasswordVal); $input.blur(); } else { $input.blur(); @@ -796,7 +828,14 @@ $(document).ready(function () { }); UserList._updateGroupListLabel($('#newuser .groups'), []); - $('#newuser').submit(function (event) { + var _submitNewUserForm = function (event) { + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + OC.PasswordConfirmation.requirePasswordConfirmation(function() { + _submitNewUserForm(event); + }); + return; + } + event.preventDefault(); var username = $('#newusername').val(); var password = $('#newuserpassword').val(); @@ -866,7 +905,8 @@ $(document).ready(function () { $('#newuser').get(0).reset(); }); }); - }); + } + $('#newuser').submit(_submitNewUserForm); if ($('#CheckboxStorageLocation').is(':checked')) { $("#userlist .storageLocation").show(); From 47f9574302ac30fc100ced10b978c49f1eea81c7 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 25 Oct 2016 15:46:24 +0200 Subject: [PATCH 13/28] Hide and show after updating Signed-off-by: Joas Schilling --- settings/js/users/users.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/settings/js/users/users.js b/settings/js/users/users.js index 7f23f2dad3..e96f6dd614 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -914,11 +914,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(); + } + }); } }); From 71c4edf827e52032fa31c0b49bdd8b55ef412b47 Mon Sep 17 00:00:00 2001 From: Morris Jobke Date: Tue, 25 Oct 2016 16:22:07 +0200 Subject: [PATCH 14/28] use proper method call for _.bind() Signed-off-by: Morris Jobke --- core/js/public/appconfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/js/public/appconfig.js b/core/js/public/appconfig.js index 5021f78e2d..d84ddaab40 100644 --- a/core/js/public/appconfig.js +++ b/core/js/public/appconfig.js @@ -34,7 +34,7 @@ OCP.AppConfig = { */ _call: function(method, endpoint, options) { if ((method === 'post' || method === 'delete') && OC.PasswordConfirmation.requiresPasswordConfirmation()) { - OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._call, this, arguments)); + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._call, this, method, endpoint, options)); return; } From 3dc3887deea9a54777abcf6c7537872937790078 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 25 Oct 2016 16:58:39 +0200 Subject: [PATCH 15/28] More fixes Signed-off-by: Joas Schilling --- settings/js/users/users.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings/js/users/users.js b/settings/js/users/users.js index e96f6dd614..0e500c328f 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -414,7 +414,7 @@ var UserList = { applyGroupSelect: function (element, user, checked) { if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { - OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applySubadminSelect, this, arguments)); + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applySubadminSelect, this, element, user, checked)); return; } @@ -481,7 +481,7 @@ var UserList = { applySubadminSelect: function (element, user, checked) { if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { - OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applySubadminSelect, this, arguments)); + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applySubadminSelect, this, element, user, checked)); return; } From 922f51e9014d8331e9a593911de152ab20bd4811 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 2 Nov 2016 14:15:28 +0100 Subject: [PATCH 16/28] Update class map Signed-off-by: Joas Schilling --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 69e8428fde..dafa46bc99 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -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', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index c960a35d95..5b8356785b 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -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', From b2d9c20aac35e92e7a7b6838e02c7170991ef352 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 2 Nov 2016 15:16:14 +0100 Subject: [PATCH 17/28] Fix unit tests Signed-off-by: Joas Schilling --- .../tests/Unit/Controller/SettingsControllerTest.php | 5 ++++- tests/Settings/Controller/AuthSettingsControllerTest.php | 4 +++- .../Middleware/Security/SecurityMiddlewareTest.php | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/twofactor_backupcodes/tests/Unit/Controller/SettingsControllerTest.php b/apps/twofactor_backupcodes/tests/Unit/Controller/SettingsControllerTest.php index c7ac33467b..8427c10e83 100644 --- a/apps/twofactor_backupcodes/tests/Unit/Controller/SettingsControllerTest.php +++ b/apps/twofactor_backupcodes/tests/Unit/Controller/SettingsControllerTest.php @@ -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()); } } diff --git a/tests/Settings/Controller/AuthSettingsControllerTest.php b/tests/Settings/Controller/AuthSettingsControllerTest.php index 782c9f644e..7fd985f29d 100644 --- a/tests/Settings/Controller/AuthSettingsControllerTest.php +++ b/tests/Settings/Controller/AuthSettingsControllerTest.php @@ -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() { diff --git a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php index 1fdcf485c2..480bff5f59 100644 --- a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php @@ -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, From a7feb91282744b2f52b1a9a9d4fba99fa1ad4a9a Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 3 Nov 2016 09:58:47 +0100 Subject: [PATCH 18/28] Confirm on create/delete group Signed-off-by: Joas Schilling --- settings/js/users/groups.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/settings/js/users/groups.js b/settings/js/users/groups.js index 8f4d95432a..cfe01c1753 100644 --- a/settings/js/users/groups.js +++ b/settings/js/users/groups.js @@ -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 () { From 309b21f5a09385f4813a0115076e4fccb0b7034d Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 3 Nov 2016 10:13:00 +0100 Subject: [PATCH 19/28] Require confirmation to change the displayname Signed-off-by: Joas Schilling --- settings/js/users/users.js | 46 +++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/settings/js/users/users.js b/settings/js/users/users.js index 0e500c328f..c2f1eb3c00 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -717,6 +717,36 @@ $(document).ready(function () { OC.Notification.hide(); }); + var _submitDisplayNameChange = function($tr, uid, displayName) { + 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); + }); + return; + } + + $.ajax({ + type: 'POST', + url: OC.generateUrl('/settings/users/{id}/displayName', {id: uid}), + data: { + username: uid, + displayName: displayName + } + }).success(function () { + if (result && result.status==='success' && $div.length){ + $div.avatar(result.data.username, 32); + } + $tr.data('displayname', displayName); + }).fail(function (result) { + OC.Notification.showTemporary(result.responseJSON.message); + }); + }; + $userListBody.on('click', '.displayName', function (event) { event.stopPropagation(); var $td = $(this).closest('td'); @@ -731,21 +761,7 @@ $(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); + _submitDisplayNameChange($tr, uid, $(this).val()); $input.blur(); } else { $input.blur(); From 05df523395e2c9a06378f23dfd6e5439df14dffe Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 10 Nov 2016 17:06:48 +0100 Subject: [PATCH 20/28] Empty the password field on submission of the form Signed-off-by: Joas Schilling --- core/js/js.js | 8 +++----- core/templates/layout.user.php | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/core/js/js.js b/core/js/js.js index efdb36187c..e8f6df4cfa 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1546,11 +1546,7 @@ OC.PasswordConfirmation = { if (this.requiresPasswordConfirmation()) { this.$form.removeClass('hidden'); this.$background.removeClass('hidden'); - - // Hack against firefox ignoring autocomplete="off" - if (this.$input.val() === ' ') { - this.$input.val(''); - } + this.$input.val(''); } this.callback = callback; @@ -1568,6 +1564,7 @@ OC.PasswordConfirmation = { }, type: 'POST', success: function(response) { + self.$input.val(''); nc_lastLogin = response.lastLogin; self.$submit.addClass('icon-confirm').removeClass('icon-loading-small'); @@ -1579,6 +1576,7 @@ OC.PasswordConfirmation = { } }, error: function() { + self.$input.val(''); OC.Notification.showTemporary(t('core', 'Failed to authenticate, try again')); self.$submit.addClass('icon-confirm').removeClass('icon-loading-small'); } diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 1b4f0bc003..8689bf859a 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -148,7 +148,7 @@ From a53c313878d04b71b383af7e5d013f30f07ae1e2 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 10 Nov 2016 17:18:12 +0100 Subject: [PATCH 21/28] Require password confirmation to change the Quota Signed-off-by: Joas Schilling --- settings/ajax/setquota.php | 7 +++++++ settings/js/users/users.js | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/settings/ajax/setquota.php b/settings/ajax/setquota.php index eee1de407b..0906102ec2 100644 --- a/settings/ajax/setquota.php +++ b/settings/ajax/setquota.php @@ -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; diff --git a/settings/js/users/users.js b/settings/js/users/users.js index c2f1eb3c00..6847f06a8b 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -539,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); } @@ -553,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); + } } } ); From 02ea134152a3362702adf3477833268bf45656f8 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 11 Nov 2016 09:43:43 +0100 Subject: [PATCH 22/28] Fix error displaying on email and add confirmation Signed-off-by: Joas Schilling --- settings/js/users/users.js | 58 ++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/settings/js/users/users.js b/settings/js/users/users.js index 6847f06a8b..3f8016bb86 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -784,6 +784,41 @@ $(document).ready(function () { }); }); + var _submitEmailChange = function($tr, $td, $input, uid, mailAddress) { + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + OC.PasswordConfirmation.requirePasswordConfirmation(function() { + _submitEmailChange($tr, $td, $input, uid, mailAddress); + }); + 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 + }).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'); + }); + }; + $userListBody.on('click', '.mailAddress', function (event) { event.stopPropagation(); var $td = $(this).closest('td'); @@ -799,29 +834,10 @@ $(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'); - }); + _submitEmailChange($tr, $td, $input, uid, $(this).val()); } }) .blur(function () { @@ -930,7 +946,7 @@ $(document).ready(function () { $('#newuser').get(0).reset(); }); }); - } + }; $('#newuser').submit(_submitNewUserForm); if ($('#CheckboxStorageLocation').is(':checked')) { From bb7787a157b7d8fc8c376e9ea745e00b3b0a9346 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 11 Nov 2016 09:44:20 +0100 Subject: [PATCH 23/28] Add the 15 seconds to the window, instead of removing Signed-off-by: Joas Schilling --- .../AppFramework/Middleware/Security/SecurityMiddleware.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 81cc09c7f5..d5f7a7660a 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -159,7 +159,7 @@ 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 + if ($lastConfirm < (time() - (30 * 60 + 15))) { // allow 15 seconds delay throw new NotConfirmedException(); } } From 80abb69b60699f04a3c4bc68a8cb28ec7c9c562b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 11 Nov 2016 09:59:20 +0100 Subject: [PATCH 24/28] Show a little explanation above the input field Signed-off-by: Joas Schilling --- core/css/styles.css | 10 +++++++--- core/templates/layout.user.php | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/css/styles.css b/core/css/styles.css index 7b2be87f61..6e0156d223 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -998,6 +998,8 @@ fieldset.warning legend + p, fieldset.update legend + p { top: 45%; left: 40%; border: 1px solid #e9322d; + color: #e9322d; + font-weight: bold; z-index: 1000; background: #e4b9c0; border-radius: 10px; @@ -1010,7 +1012,9 @@ fieldset.warning legend + p, fieldset.update legend + p { } #sudo-login-form .confirm { - position: absolute; - top: 25px; - right: 25px; + position: relative; + right: 32px; + border: none; + opacity: .3; + background-color: transparent; } diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 8689bf859a..1d0ac5fa14 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -148,6 +148,7 @@