fix(core): Require current password on password change (#285)

Increase security by requiring the current password when changing the
password. This increases the security for cases such as XSS, or just a
forgotten browser window left open.

Fixes #4140
pull/287/head
Nicolas 2020-07-27 16:12:22 +02:00 committed by GitHub
parent 03d8ed5e92
commit 2300fe8aab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 27 additions and 17 deletions

View File

@ -133,7 +133,7 @@
} }
[loginCookie setPath: [NSString stringWithFormat: @"/%@/", appName]]; [loginCookie setPath: [NSString stringWithFormat: @"/%@/", appName]];
return loginCookie; return loginCookie;
} }
@ -188,14 +188,14 @@
NSDictionary *params; NSDictionary *params;
NSString *username, *password, *language, *domain, *remoteHost, *verificationCode; NSString *username, *password, *language, *domain, *remoteHost, *verificationCode;
NSArray *supportedLanguages, *creds; NSArray *supportedLanguages, *creds;
SOGoPasswordPolicyError err; SOGoPasswordPolicyError err;
int expire, grace; int expire, grace;
BOOL rememberLogin, b; BOOL rememberLogin, b;
err = PolicyNoError; err = PolicyNoError;
expire = grace = -1; expire = grace = -1;
auth = [[WOApplication application] authenticatorInContext: context]; auth = [[WOApplication application] authenticatorInContext: context];
request = [context request]; request = [context request];
params = [[request contentAsString] objectFromJSONString]; params = [[request contentAsString] objectFromJSONString];
@ -209,10 +209,10 @@
/* this will always be set to something more or less useful by /* this will always be set to something more or less useful by
* [WOHttpTransaction applyAdaptorHeadersWithHttpRequest] */ * [WOHttpTransaction applyAdaptorHeadersWithHttpRequest] */
remoteHost = [request headerForKey:@"x-webobjects-remote-host"]; remoteHost = [request headerForKey:@"x-webobjects-remote-host"];
if ((b = [auth checkLogin: username password: password domain: &domain if ((b = [auth checkLogin: username password: password domain: &domain
perr: &err expire: &expire grace: &grace useCache: NO]) perr: &err expire: &expire grace: &grace useCache: NO])
&& (err == PolicyNoError) && (err == PolicyNoError)
// no password policy // no password policy
&& ((expire < 0 && grace < 0) // no password policy or everything is alright && ((expire < 0 && grace < 0) // no password policy or everything is alright
|| (expire < 0 && grace > 0) // password expired, grace still permits login || (expire < 0 && grace > 0) // password expired, grace still permits login
@ -221,7 +221,7 @@
NSDictionary *json; NSDictionary *json;
[self logWithFormat: @"successful login from '%@' for user '%@' - expire = %d grace = %d", remoteHost, username, expire, grace]; [self logWithFormat: @"successful login from '%@' for user '%@' - expire = %d grace = %d", remoteHost, username, expire, grace];
// We get the proper username for cookie creation. If we are using a multidomain // We get the proper username for cookie creation. If we are using a multidomain
// environment with SOGoEnableDomainBasedUID, we could have to append the domain // environment with SOGoEnableDomainBasedUID, we could have to append the domain
// to the username. Also when SOGoEnableDomainBasedUID is enabled, we could be in // to the username. Also when SOGoEnableDomainBasedUID is enabled, we could be in
@ -648,7 +648,7 @@
request = [context request]; request = [context request];
message = [[request contentAsString] objectFromJSONString]; message = [[request contentAsString] objectFromJSONString];
auth = [[WOApplication application] auth = [[WOApplication application]
authenticatorInContext: context]; authenticatorInContext: context];
value = [[context request] value = [[context request]
@ -662,6 +662,8 @@
password: &password]; password: &password];
newPassword = [message objectForKey: @"newPassword"]; newPassword = [message objectForKey: @"newPassword"];
// overwrite the value from the session to compare the actual input
password = [message objectForKey: @"oldPassword"];
um = [SOGoUserManager sharedUserManager]; um = [SOGoUserManager sharedUserManager];
@ -673,7 +675,7 @@
perr: &error]) perr: &error])
{ {
// We delete the previous session // We delete the previous session
[SOGoSession deleteValueForSessionKey: [creds objectAtIndex: 1]]; [SOGoSession deleteValueForSessionKey: [creds objectAtIndex: 1]];
if ([domain isNotNull]) if ([domain isNotNull])
{ {
@ -682,7 +684,7 @@
[username rangeOfString: @"@"].location == NSNotFound) [username rangeOfString: @"@"].location == NSNotFound)
username = [NSString stringWithFormat: @"%@@%@", username, domain]; username = [NSString stringWithFormat: @"%@@%@", username, domain];
} }
response = [self responseWith204]; response = [self responseWith204];
authCookie = [auth cookieWithUsername: username authCookie = [auth cookieWithUsername: username
andPassword: newPassword andPassword: newPassword

View File

@ -238,6 +238,7 @@
"Additional Parameters" = "Additional Parameters"; "Additional Parameters" = "Additional Parameters";
/* password */ /* password */
"Current password" = "Current password";
"New password" = "New password"; "New password" = "New password";
"Confirmation" = "Confirmation"; "Confirmation" = "Confirmation";
"Change" = "Change"; "Change" = "Change";

View File

@ -233,6 +233,7 @@
"Additional Parameters" = "Zusätzliche Einstellungen"; "Additional Parameters" = "Zusätzliche Einstellungen";
/* password */ /* password */
"Current password" = "Aktuelles Passwort";
"New password" = "Neues Passwort"; "New password" = "Neues Passwort";
"Confirmation" = "Bestätigung"; "Confirmation" = "Bestätigung";
"Change" = "Ändern"; "Change" = "Ändern";

View File

@ -262,16 +262,21 @@
label:label="Password"> label:label="Password">
<md-content id="passwordView" class="md-padding"> <md-content id="passwordView" class="md-padding">
<div layout="row"> <div layout="row">
<md-input-container class="md-block" flex="50">
<label><var:string label:value="Current password"/>
</label>
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.oldPassword"/>
</md-input-container>
<md-input-container class="md-block" flex="50"> <md-input-container class="md-block" flex="50">
<label><var:string label:value="New password"/> <label><var:string label:value="New password"/>
</label> </label>
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.newPassword"/> <input type="password" sg-no-dirty-check="true" ng-model="app.passwords.newPassword"/>
</md-input-container> </md-input-container>
<md-input-container class="md-block" flex="50"> <md-input-container class="md-block" flex="50">
<label><var:string label:value="Confirmation"/> <label><var:string label:value="Confirmation"/>
</label> </label>
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.newPasswordConfirmation"/> <input type="password" sg-no-dirty-check="true" ng-model="app.passwords.newPasswordConfirmation"/>
</md-input-container> </md-input-container>
</div> </div>
<div layout="row" layout-align="end center"> <div layout="row" layout-align="end center">

View File

@ -139,7 +139,7 @@
return d.promise; return d.promise;
}, // login: function(data) { ... }, // login: function(data) { ...
changePassword: function(newPassword) { changePassword: function(newPassword, oldPassword) {
var d = $q.defer(), var d = $q.defer(),
xsrfCookie = $cookies.get('XSRF-TOKEN'); xsrfCookie = $cookies.get('XSRF-TOKEN');
@ -151,7 +151,7 @@
headers: { headers: {
'X-XSRF-TOKEN' : xsrfCookie 'X-XSRF-TOKEN' : xsrfCookie
}, },
data: { newPassword: newPassword } data: { newPassword: newPassword, oldPassword: oldPassword }
}).then(d.resolve, function(response) { }).then(d.resolve, function(response) {
var error, var error,
data = response.data, data = response.data,

View File

@ -13,7 +13,7 @@
this.$onInit = function() { this.$onInit = function() {
this.preferences = Preferences; this.preferences = Preferences;
this.passwords = { newPassword: null, newPasswordConfirmation: null }; this.passwords = { newPassword: null, newPasswordConfirmation: null, oldPassword: null };
this.timeZonesList = $window.timeZonesList; this.timeZonesList = $window.timeZonesList;
this.timeZonesSearchText = ''; this.timeZonesSearchText = '';
this.sieveVariablesCapability = ($window.sieveCapabilities.indexOf('variables') >= 0); this.sieveVariablesCapability = ($window.sieveCapabilities.indexOf('variables') >= 0);
@ -465,14 +465,15 @@
this.canChangePassword = function() { this.canChangePassword = function() {
if (this.passwords.newPassword && this.passwords.newPassword.length > 0 && if (this.passwords.newPassword && this.passwords.newPassword.length > 0 &&
this.passwords.newPasswordConfirmation && this.passwords.newPasswordConfirmation.length && this.passwords.newPasswordConfirmation && this.passwords.newPasswordConfirmation.length &&
this.passwords.newPassword == this.passwords.newPasswordConfirmation) this.passwords.newPassword == this.passwords.newPasswordConfirmation &&
this.passwords.oldPassword && this.passwords.oldPassword.length > 0)
return true; return true;
return false; return false;
}; };
this.changePassword = function() { this.changePassword = function() {
Authentication.changePassword(this.passwords.newPassword).then(function() { Authentication.changePassword(this.passwords.newPassword, this.passwords.oldPassword).then(function() {
var alert = $mdDialog.alert({ var alert = $mdDialog.alert({
title: l('Password'), title: l('Password'),
content: l('The password was changed successfully.'), content: l('The password was changed successfully.'),