Skip to content

Commit 332eaec

Browse files
authored
Merge pull request #1447 from nextcloud/password-confirmation-for-some-actions
Password confirmation for some actions
2 parents faee255 + 3ffd9a7 commit 332eaec

35 files changed

Lines changed: 513 additions & 106 deletions

apps/twofactor_backupcodes/js/settingsview.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@
8989
}.bind(this));
9090
},
9191
_onGenerateBackupCodes: function () {
92+
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
93+
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._onGenerateBackupCodes, this));
94+
return;
95+
}
96+
9297
// Hide old codes
9398
this._enabled = false;
9499
this.render();

apps/twofactor_backupcodes/lib/Controller/SettingsController.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,17 @@ public function state() {
5959

6060
/**
6161
* @NoAdminRequired
62+
* @PasswordConfirmationRequired
63+
*
6264
* @return JSONResponse
6365
*/
6466
public function createCodes() {
6567
$user = $this->userSession->getUser();
6668
$codes = $this->storage->createCodes($user);
67-
return [
68-
'codes' => $codes,
69-
'state' => $this->storage->getBackupCodesState($user),
70-
];
69+
return new JSONResponse([
70+
'codes' => $codes,
71+
'state' => $this->storage->getBackupCodesState($user),
72+
]);
7173
}
7274

7375
}

apps/twofactor_backupcodes/tests/Unit/Controller/SettingsControllerTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
use OCA\TwoFactorBackupCodes\Controller\SettingsController;
2626
use OCA\TwoFactorBackupCodes\Service\BackupCodeStorage;
27+
use OCP\AppFramework\Http\JSONResponse;
2728
use OCP\IRequest;
2829
use OCP\IUser;
2930
use OCP\IUserSession;
@@ -89,7 +90,9 @@ public function testCreateCodes() {
8990
'codes' => $codes,
9091
'state' => 'state',
9192
];
92-
$this->assertEquals($expected, $this->controller->createCodes());
93+
$response = $this->controller->createCodes();
94+
$this->assertInstanceOf(JSONResponse::class, $response);
95+
$this->assertEquals($expected, $response->getData());
9396
}
9497

9598
}

apps/workflowengine/js/admin.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@
163163
}
164164
},
165165
delete: function() {
166+
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
167+
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.delete, this));
168+
return;
169+
}
170+
166171
this.model.destroy();
167172
this.remove();
168173
},
@@ -173,6 +178,11 @@
173178
this.render();
174179
},
175180
save: function() {
181+
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
182+
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.save, this));
183+
return;
184+
}
185+
176186
var success = function(model, response, options) {
177187
this.saving = false;
178188
this.originalModel = JSON.parse(JSON.stringify(this.model));

apps/workflowengine/lib/Controller/FlowOperations.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public function getOperations($class) {
5858
}
5959

6060
/**
61+
* @PasswordConfirmationRequired
62+
*
6163
* @param string $class
6264
* @param string $name
6365
* @param array[] $checks
@@ -75,6 +77,8 @@ public function addOperation($class, $name, $checks, $operation) {
7577
}
7678

7779
/**
80+
* @PasswordConfirmationRequired
81+
*
7882
* @param int $id
7983
* @param string $name
8084
* @param array[] $checks
@@ -92,6 +96,8 @@ public function updateOperation($id, $name, $checks, $operation) {
9296
}
9397

9498
/**
99+
* @PasswordConfirmationRequired
100+
*
95101
* @param int $id
96102
* @return JSONResponse
97103
*/

core/Controller/LoginController.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22
/**
3+
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
34
* @copyright Copyright (c) 2016, ownCloud, Inc.
45
*
56
* @author Christoph Wurst <christoph@owncloud.com>
@@ -31,6 +32,8 @@
3132
use OC_App;
3233
use OC_Util;
3334
use OCP\AppFramework\Controller;
35+
use OCP\AppFramework\Http;
36+
use OCP\AppFramework\Http\DataResponse;
3437
use OCP\AppFramework\Http\RedirectResponse;
3538
use OCP\AppFramework\Http\TemplateResponse;
3639
use OCP\Authentication\TwoFactorAuth\IProvider;
@@ -242,6 +245,8 @@ public function tryLogin($user, $password, $redirect_url, $remember_login = fals
242245
// User has successfully logged in, now remove the password reset link, when it is available
243246
$this->config->deleteUserValue($loginResult->getUID(), 'core', 'lostpassword');
244247

248+
$this->session->set('last-password-confirm', $loginResult->getLastLogin());
249+
245250
if ($this->twoFactorManager->isTwoFactorAuthenticated($loginResult)) {
246251
$this->twoFactorManager->prepareTwoFactorLogin($loginResult, $remember_login);
247252

@@ -273,4 +278,36 @@ public function tryLogin($user, $password, $redirect_url, $remember_login = fals
273278
return $this->generateRedirect($redirect_url);
274279
}
275280

281+
/**
282+
* @NoAdminRequired
283+
* @UseSession
284+
*
285+
* @license GNU AGPL version 3 or any later version
286+
*
287+
* @param string $password
288+
* @return DataResponse
289+
*/
290+
public function confirmPassword($password) {
291+
$currentDelay = $this->throttler->getDelay($this->request->getRemoteAddress());
292+
$this->throttler->sleepDelay($this->request->getRemoteAddress());
293+
294+
$user = $this->userSession->getUser();
295+
if (!$user instanceof IUser) {
296+
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
297+
}
298+
299+
$loginResult = $this->userManager->checkPassword($user->getUID(), $password);
300+
if ($loginResult === false) {
301+
$this->throttler->registerAttempt('sudo', $this->request->getRemoteAddress(), ['user' => $user->getUID()]);
302+
if ($currentDelay === 0) {
303+
$this->throttler->sleepDelay($this->request->getRemoteAddress());
304+
}
305+
306+
return new DataResponse([], Http::STATUS_FORBIDDEN);
307+
}
308+
309+
$confirmTimestamp = time();
310+
$this->session->set('last-password-confirm', $confirmTimestamp);
311+
return new DataResponse(['lastLogin' => $confirmTimestamp], Http::STATUS_OK);
312+
}
276313
}

core/Controller/OCJSController.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use OCP\IGroupManager;
3333
use OCP\IL10N;
3434
use OCP\IRequest;
35+
use OCP\ISession;
3536
use OCP\IURLGenerator;
3637
use OCP\IUserSession;
3738

@@ -48,7 +49,8 @@ class OCJSController extends Controller {
4849
* @param IL10N $l
4950
* @param \OC_Defaults $defaults
5051
* @param IAppManager $appManager
51-
* @param IUserSession $session
52+
* @param ISession $session
53+
* @param IUserSession $userSession
5254
* @param IConfig $config
5355
* @param IGroupManager $groupManager
5456
* @param IniGetWrapper $iniWrapper
@@ -59,7 +61,8 @@ public function __construct($appName,
5961
IL10N $l,
6062
\OC_Defaults $defaults,
6163
IAppManager $appManager,
62-
IUserSession $session,
64+
ISession $session,
65+
IUserSession $userSession,
6366
IConfig $config,
6467
IGroupManager $groupManager,
6568
IniGetWrapper $iniWrapper,
@@ -70,7 +73,8 @@ public function __construct($appName,
7073
$l,
7174
$defaults,
7275
$appManager,
73-
$session->getUser(),
76+
$session,
77+
$userSession->getUser(),
7478
$config,
7579
$groupManager,
7680
$iniWrapper,

core/css/jquery.ocdialog.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
.oc-dialog-buttonrow {
2323
display: block;
2424
background: transparent;
25-
position: absolute;
2625
right: 0;
2726
bottom: 0;
2827
padding: 10px;

core/js/js.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,8 +1512,80 @@ function initCore() {
15121512
$(this).text(OC.Util.relativeModifiedDate(parseInt($(this).attr('data-timestamp'), 10)));
15131513
});
15141514
}, 30 * 1000);
1515+
1516+
OC.PasswordConfirmation.init();
15151517
}
15161518

1519+
OC.PasswordConfirmation = {
1520+
callback: null,
1521+
1522+
init: function() {
1523+
$('.password-confirm-required').on('click', _.bind(this.requirePasswordConfirmation, this));
1524+
},
1525+
1526+
requiresPasswordConfirmation: function() {
1527+
var timeSinceLogin = moment.now() - nc_lastLogin * 1000;
1528+
return timeSinceLogin > 10 * 1000; // 30 minutes
1529+
return timeSinceLogin > 30 * 60 * 1000; // 30 minutes
1530+
},
1531+
1532+
/**
1533+
* @param {function} callback
1534+
*/
1535+
requirePasswordConfirmation: function(callback) {
1536+
var self = this;
1537+
1538+
if (this.requiresPasswordConfirmation()) {
1539+
OC.dialogs.prompt(
1540+
t(
1541+
'core',
1542+
'This action requires you to confirm your password'
1543+
),
1544+
t('core','Authentication required'),
1545+
function (result, password) {
1546+
if (result && password !== '') {
1547+
self._confirmPassword(password);
1548+
}
1549+
},
1550+
true,
1551+
t('core','Password'),
1552+
true
1553+
).then(function() {
1554+
var $dialog = $('.oc-dialog:visible');
1555+
$dialog.find('.ui-icon').remove();
1556+
1557+
var $buttons = $dialog.find('button');
1558+
$buttons.eq(0).text(t('core', 'Cancel'));
1559+
$buttons.eq(1).text(t('core', 'Confirm'));
1560+
});
1561+
}
1562+
1563+
this.callback = callback;
1564+
},
1565+
1566+
_confirmPassword: function(password) {
1567+
var self = this;
1568+
1569+
$.ajax({
1570+
url: OC.generateUrl('/login/confirm'),
1571+
data: {
1572+
password: password
1573+
},
1574+
type: 'POST',
1575+
success: function(response) {
1576+
nc_lastLogin = response.lastLogin;
1577+
1578+
if (_.isFunction(self.callback)) {
1579+
self.callback();
1580+
}
1581+
},
1582+
error: function() {
1583+
OC.Notification.showTemporary(t('core', 'Failed to authenticate, try again'));
1584+
}
1585+
});
1586+
}
1587+
};
1588+
15171589
$(document).ready(initCore);
15181590

15191591
/**

core/js/public/appconfig.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ OCP.AppConfig = {
3333
* @internal
3434
*/
3535
_call: function(method, endpoint, options) {
36+
if ((method === 'post' || method === 'delete') && OC.PasswordConfirmation.requiresPasswordConfirmation()) {
37+
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._call, this, method, endpoint, options));
38+
return;
39+
}
3640

3741
$.ajax({
3842
type: method.toUpperCase(),

0 commit comments

Comments
 (0)