fix(web): restore support of ppolicy OpenLDAP overlay

snyk-upgrade-0ec09bc7ae34af7c5d0348d49696b8f1
Francis Lachapelle 2021-05-05 12:41:08 -04:00
parent da366083e9
commit 0c1f9fdb02
5 changed files with 269 additions and 70 deletions

View File

@ -166,9 +166,8 @@
{ {
NSDictionary *jsonError; NSDictionary *jsonError;
jsonError jsonError = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: error]
= [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: error] forKey: @"LDAPPasswordPolicyError"];
forKey: @"LDAPPasswordPolicyError"];
return [self responseWithStatus: 403 return [self responseWithStatus: 403
andJSONRepresentation: jsonError]; andJSONRepresentation: jsonError];
} }
@ -649,57 +648,90 @@
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] cookieValueForKey: [auth cookieNameInContext: context]];
value = [[context request] creds = nil;
cookieValueForKey: [auth cookieNameInContext: context]]; username = nil;
creds = [auth parseCredentials: value];
[SOGoSession decodeValue: [SOGoSession valueForSessionKey: [creds objectAtIndex: 1]] if (value)
usingKey: [creds objectAtIndex: 0] {
login: &username // User is logged in; extract username from session
domain: &domain creds = [auth parseCredentials: value];
password: &password];
[SOGoSession decodeValue: [SOGoSession valueForSessionKey: [creds objectAtIndex: 1]]
usingKey: [creds objectAtIndex: 0]
login: &username
domain: &domain
password: &password];
}
else
{
// We are using ppolicy and changing the password upon login
username = [message objectForKey: @"userName"];
domain = [message objectForKey: @"domain"];
}
newPassword = [message objectForKey: @"newPassword"]; newPassword = [message objectForKey: @"newPassword"];
// overwrite the value from the session to compare the actual input // overwrite the value from the session to compare the actual input
password = [message objectForKey: @"oldPassword"]; password = [message objectForKey: @"oldPassword"];
um = [SOGoUserManager sharedUserManager]; // Validate required parameters
if (!username)
// This will also update the cached password in memcached.
if ([um changePasswordForLogin: username
inDomain: domain
oldPassword: password
newPassword: newPassword
perr: &error])
{ {
// We delete the previous session response = [self responseWithStatus: 403
[SOGoSession deleteValueForSessionKey: [creds objectAtIndex: 1]]; andString: @"Missing 'username' parameter"];
}
if ([domain isNotNull]) else if (!password)
{ {
sd = [SOGoSystemDefaults sharedSystemDefaults]; response = [self responseWithStatus: 403
if ([sd enableDomainBasedUID] && andString: @"Missing 'oldPassword' parameter"];
[username rangeOfString: @"@"].location == NSNotFound) }
username = [NSString stringWithFormat: @"%@@%@", username, domain]; else if (!newPassword)
} {
response = [self responseWithStatus: 403
response = [self responseWith204]; andString: @"Missing 'newPassword' parameter"];
authCookie = [auth cookieWithUsername: username
andPassword: newPassword
inContext: context];
[response addCookie: authCookie];
// We update the XSRF protection cookie
creds = [auth parseCredentials: [authCookie value]];
xsrfCookie = [WOCookie cookieWithName: @"XSRF-TOKEN"
value: [[SOGoSession valueForSessionKey: [creds lastObject]] asSHA1String]];
[xsrfCookie setPath: [NSString stringWithFormat: @"/%@/", [request applicationName]]];
[response addCookie: xsrfCookie];
} }
else else
response = [self _responseWithLDAPPolicyError: error]; {
um = [SOGoUserManager sharedUserManager];
// This will also update the cached password in memcached.
if ([um changePasswordForLogin: username
inDomain: domain
oldPassword: password
newPassword: newPassword
perr: &error])
{
if (creds)
{
// We delete the previous session
[SOGoSession deleteValueForSessionKey: [creds objectAtIndex: 1]];
}
if ([domain isNotNull])
{
sd = [SOGoSystemDefaults sharedSystemDefaults];
if ([sd enableDomainBasedUID] &&
[username rangeOfString: @"@"].location == NSNotFound)
username = [NSString stringWithFormat: @"%@@%@", username, domain];
}
response = [self responseWith204];
authCookie = [auth cookieWithUsername: username
andPassword: newPassword
inContext: context];
[response addCookie: authCookie];
// We update the XSRF protection cookie
creds = [auth parseCredentials: [authCookie value]];
xsrfCookie = [WOCookie cookieWithName: @"XSRF-TOKEN"
value: [[SOGoSession valueForSessionKey: [creds lastObject]] asSHA1String]];
[xsrfCookie setPath: [NSString stringWithFormat: @"/%@/", [request applicationName]]];
[response addCookie: xsrfCookie];
}
else
response = [self _responseWithLDAPPolicyError: error];
}
return response; return response;
} }

View File

@ -156,6 +156,59 @@
</div> </div>
</var:if> </var:if>
<!-- Password policy: Password is expired -->
<div layout="column" layout-align="center center"
ng-switch-when="passwordexpired">
<md-icon class="md-accent md-hue-1 sg-icon--large">watch_later</md-icon>
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
<var:string label:value="Your password has expired, please enter a new one below"/>
</div>
<div flex="100">
<div layout="row" layout-xs="column">
<md-input-container class="md-block" flex="flex">
<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="flex">
<label><var:string label:value="New password"/>
</label>
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.newPassword"/>
</md-input-container>
<md-input-container class="md-block" flex="flex">
<label><var:string label:value="Confirmation"/>
</label>
<input type="password" name="newPasswordConfirmation" sg-no-dirty-check="true" ng-model="app.passwords.newPasswordConfirmation"/>
<div ng-messages="loginForm.newPasswordConfirmation.$error">
<div ng-message="newPasswordMismatch"><var:string label:value="Passwords don't match"/></div>
</div>
</md-input-container>
</div>
<div layout="row" layout-align="end center">
<md-button ng-click="app.changePassword()" type="button" ng-disabled="!app.canChangePassword(loginForm)">
<var:string label:value="Change"/>
</md-button>
</div>
</div>
</div>
<!-- Password policy: Grace period -->
<div layout="column" layout-align="center center"
ng-switch-when="passwordwillexpire">
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
<var:string label:value="Welcome"/> {{app.cn}}
</div>
<div class="md-default-theme md-warn md-hue-1 md-bg md-padding">
{{app.errorMessage}}
<md-button class="md-raised"
ng-click="app.loginState = 'passwordexpired'"><var:string label:value="Change your Password"/></md-button>
</div>
<md-button
ng-click="app.continueLogin()"
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
</div>
<!-- Logged in --> <!-- Logged in -->
<div layout="column" layout-align="center center" <div layout="column" layout-align="center center"
ng-switch-when="logged"> ng-switch-when="logged">
@ -165,6 +218,17 @@
</div> </div>
</div> </div>
<div layout="column" layout-align="center center"
ng-switch-when="message">
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
{{app.errorMessage}}
</div>
<md-button
ng-click="app.continueLogin()"
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
</div>
<!-- Error --> <!-- Error -->
<div layout="column" layout-align="center center" <div layout="column" layout-align="center center"
ng-switch-when="error"> ng-switch-when="error">

View File

@ -100,46 +100,68 @@
// Check password policy // Check password policy
else if (typeof data.expire != 'undefined' && typeof data.grace != 'undefined') { else if (typeof data.expire != 'undefined' && typeof data.grace != 'undefined') {
if (data.expire < 0 && data.grace > 0) { if (data.expire < 0 && data.grace > 0) {
d.reject({grace: data.grace}); d.reject({
//showPasswordDialog('grace', createPasswordGraceDialog, data['grace']); cn: data.cn,
url: redirectUrl(username, domain),
grace: data.grace
});
} else if (data.expire > 0 && data.grace == -1) { } else if (data.expire > 0 && data.grace == -1) {
d.reject({expire: data.expire}); d.reject({
//showPasswordDialog('expiration', createPasswordExpirationDialog, data['expire']); cn: data.cn,
url: redirectUrl(username, domain),
expire: data.expire
});
} }
else { else {
d.resolve({ cn: data.cn, url: redirectUrl(username, domain) }); d.resolve({
cn: data.cn,
url: redirectUrl(username, domain)
});
} }
} }
else { else {
d.resolve({ url: redirectUrl(username, domain) }); d.resolve({ url: redirectUrl(username, domain) });
} }
} }
}, function(response) { }, function(error) {
var msg, perr, data = response.data; var response, perr, data = error.data;
if (data && data.GoogleAuthenticatorInvalidKey) { if (data && data.GoogleAuthenticatorInvalidKey) {
msg = l('You provided an invalid Google Authenticator key.'); response = {error: l('You provided an invalid Google Authenticator key.')};
} }
else if (data && data.LDAPPasswordPolicyError) { else if (data && angular.isDefined(data.LDAPPasswordPolicyError)) {
perr = data.LDAPPasswordPolicyError; perr = data.LDAPPasswordPolicyError;
if (perr == passwordPolicyConfig.PolicyNoError) { if (perr == passwordPolicyConfig.PolicyNoError) {
msg = l('Wrong username or password.'); response = {error: l('Wrong username or password.')};
} }
else if (perr == passwordPolicyConfig.PolicyAccountLocked) { else if (perr == passwordPolicyConfig.PolicyAccountLocked) {
msg = l('Your account was locked due to too many failed attempts.'); response = {error: l('Your account was locked due to too many failed attempts.')};
}
else if (perr == passwordPolicyConfig.PolicyPasswordExpired ||
perr == passwordPolicyConfig.PolicyChangeAfterReset) {
response = {
passwordexpired: 1,
url: redirectUrl(username, domain)
};
}
else if (perr == passwordPolicyConfig.PolicyChangeAfterReset) {
response = {
passwordexpired: 1,
url: redirectUrl(username, domain)
};
} }
else { else {
msg = l('Login failed due to unhandled error case: ') + perr; response = {error: l('Login failed due to unhandled error case: ') + perr};
} }
} }
else { else {
msg = l('Unhandled error response'); response = {error: l('Unhandled error response')};
} }
d.reject({error: msg}); d.reject(response);
}); });
return d.promise; return d.promise;
}, // login: function(data) { ... }, // login: function(data) { ...
changePassword: function(newPassword, oldPassword) { changePassword: function(userName, domain, newPassword, oldPassword) {
var d = $q.defer(), var d = $q.defer(),
xsrfCookie = $cookies.get('XSRF-TOKEN'); xsrfCookie = $cookies.get('XSRF-TOKEN');
@ -151,8 +173,10 @@
headers: { headers: {
'X-XSRF-TOKEN' : xsrfCookie 'X-XSRF-TOKEN' : xsrfCookie
}, },
data: { newPassword: newPassword, oldPassword: oldPassword } data: { userName: userName, newPassword: newPassword, oldPassword: oldPassword }
}).then(d.resolve, function(response) { }).then(function() {
d.resolve({url: redirectUrl(userName, domain)});
}, function(response) {
var error, var error,
data = response.data, data = response.data,
perr = data.LDAPPasswordPolicyError; perr = data.LDAPPasswordPolicyError;
@ -161,7 +185,8 @@
perr = passwordPolicyConfig.PolicyPasswordSystemUnknown; perr = passwordPolicyConfig.PolicyPasswordSystemUnknown;
error = _("Unhandled error response"); error = _("Unhandled error response");
} }
else if (perr == passwordPolicyConfig.PolicyNoError) { else if (perr == passwordPolicyConfig.PolicyNoError ||
perr == passwordPolicyConfig.PolicyPasswordUnknown) {
error = l("Password change failed"); error = l("Password change failed");
} else if (perr == passwordPolicyConfig.PolicyPasswordModNotAllowed) { } else if (perr == passwordPolicyConfig.PolicyPasswordModNotAllowed) {
error = l("Password change failed - Permission denied"); error = l("Password change failed - Permission denied");

View File

@ -17,6 +17,7 @@
this.creds = { this.creds = {
username: $window.cookieUsername, username: $window.cookieUsername,
password: null, password: null,
domain: null,
rememberLogin: angular.isDefined($window.cookieUsername) && $window.cookieUsername.length > 0 rememberLogin: angular.isDefined($window.cookieUsername) && $window.cookieUsername.length > 0
}; };
// Send selected language only if user has changed it // Send selected language only if user has changed it
@ -27,6 +28,9 @@
// Code pattern for Google verification code // Code pattern for Google verification code
this.verificationCodePattern = '\\d{6}'; this.verificationCodePattern = '\\d{6}';
// Password policy - change expired password
this.passwords = { newPassword: null, newPasswordConfirmation: null, oldPassword: null };
// Show login once everything is initialized // Show login once everything is initialized
this.showLogin = false; this.showLogin = false;
$timeout(function() { vm.showLogin = true; }, 100); $timeout(function() { vm.showLogin = true; }, 100);
@ -43,18 +47,55 @@
else { else {
vm.loginState = 'logged'; vm.loginState = 'logged';
vm.cn = data.cn; vm.cn = data.cn;
vm.url = data.url;
// Let the user see the succesfull message before reloading the page // Let the user see the succesfull message before reloading the page
$timeout(function() { $timeout(function() {
if ($window.location.href === data.url) vm.continueLogin();
$window.location.reload(true);
else
$window.location.href = data.url;
}, 1000); }, 1000);
} }
}, function(msg) { }, function(msg) {
vm.loginState = 'error'; vm.loginState = 'error';
vm.errorMessage = msg.error;
if (msg.error) {
vm.errorMessage = msg.error;
}
else if (msg.grace > 0) {
// Password is expired, grace logins limit is not yet reached
vm.loginState = 'passwordwillexpire';
vm.cn = msg.cn;
vm.url = msg.url;
vm.errorMessage = l('You have %{0} logins remaining before your account is locked. Please change your password in the preference dialog.', msg.grace);
}
else if (msg.expire > 0) {
// Password will soon expire
var value, string;
if (msg.expire > 86400) {
value = Math.round(msg.expire/86400);
string = l("days");
}
else if (msg.expire > 3600) {
value = Math.round(msg.expire/3600);
string = l("hours");
}
else if (msg.expire > 60) {
value = Math.round(msg.expire/60);
string = l("minutes");
}
else {
value = msg.expire;
string = l("seconds");
}
vm.loginState = 'passwordwillexpire';
vm.cn = msg.cn;
vm.url = msg.url;
vm.errorMessage = l('Your password is going to expire in %{0} %{1}.', value, string);
}
else if (msg.passwordexpired) {
vm.loginState = 'passwordexpired';
vm.url = msg.url;
}
}); });
return false; return false;
}; };
@ -64,6 +105,13 @@
delete vm.creds.verificationCode; delete vm.creds.verificationCode;
}; };
this.continueLogin = function() {
if ($window.location.href === vm.url)
$window.location.reload(true);
else
$window.location.href = vm.url;
};
this.showAbout = function($event) { this.showAbout = function($event) {
$mdDialog.show({ $mdDialog.show({
targetEvent: $event, targetEvent: $event,
@ -83,6 +131,36 @@
// Reload page // Reload page
$window.location.href = ApplicationBaseURL + 'login?language=' + this.creds.language; $window.location.href = ApplicationBaseURL + 'login?language=' + this.creds.language;
}; };
this.canChangePassword = function(form) {
if (this.passwords.newPasswordConfirmation && this.passwords.newPasswordConfirmation.length &&
this.passwords.newPassword != this.passwords.newPasswordConfirmation) {
form.newPasswordConfirmation.$setValidity('newPasswordMismatch', false);
return false;
}
else {
form.newPasswordConfirmation.$setValidity('newPasswordMismatch', true);
}
if (this.passwords.newPassword && this.passwords.newPassword.length > 0 &&
this.passwords.newPasswordConfirmation && this.passwords.newPasswordConfirmation.length &&
this.passwords.newPassword == this.passwords.newPasswordConfirmation &&
this.passwords.oldPassword && this.passwords.oldPassword.length > 0)
return true;
return false;
};
this.changePassword = function() {
Authentication.changePassword(this.creds.username, this.creds.domain, this.passwords.newPassword, this.passwords.oldPassword).then(function(data) {
vm.loginState = 'message';
vm.url = data.url;
vm.errorMessage = l('The password was changed successfully.');
}, function(msg) {
vm.loginState = 'error';
vm.errorMessage = msg;
});
};
} }
angular angular

View File

@ -484,7 +484,7 @@
}; };
this.changePassword = function() { this.changePassword = function() {
Authentication.changePassword(this.passwords.newPassword, this.passwords.oldPassword).then(function() { Authentication.changePassword(null, null, this.passwords.newPassword, this.passwords.oldPassword).then(function() {
var alert = $mdDialog.alert({ var alert = $mdDialog.alert({
title: l('Password'), title: l('Password'),
textContent: l('The password was changed successfully.'), textContent: l('The password was changed successfully.'),
@ -497,7 +497,7 @@
}, function(msg) { }, function(msg) {
var alert = $mdDialog.alert({ var alert = $mdDialog.alert({
title: l('Password'), title: l('Password'),
content: msg, textContent: msg,
ok: l('OK') ok: l('OK')
}); });
$mdDialog.show( alert ) $mdDialog.show( alert )