From 0c1f9fdb02559e8650fd486583bbbe11517ef4c7 Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Wed, 5 May 2021 12:41:08 -0400 Subject: [PATCH] fix(web): restore support of ppolicy OpenLDAP overlay --- UI/MainUI/SOGoRootPage.m | 122 +++++++++++------- UI/Templates/MainUI/SOGoRootPage.wox | 64 +++++++++ .../js/Common/Authentication.service.js | 61 ++++++--- UI/WebServerResources/js/Main/Main.app.js | 88 ++++++++++++- .../js/Preferences/PreferencesController.js | 4 +- 5 files changed, 269 insertions(+), 70 deletions(-) diff --git a/UI/MainUI/SOGoRootPage.m b/UI/MainUI/SOGoRootPage.m index 04cf81551..9caa6293c 100644 --- a/UI/MainUI/SOGoRootPage.m +++ b/UI/MainUI/SOGoRootPage.m @@ -166,9 +166,8 @@ { NSDictionary *jsonError; - jsonError - = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: error] - forKey: @"LDAPPasswordPolicyError"]; + jsonError = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: error] + forKey: @"LDAPPasswordPolicyError"]; return [self responseWithStatus: 403 andJSONRepresentation: jsonError]; } @@ -649,57 +648,90 @@ request = [context request]; message = [[request contentAsString] objectFromJSONString]; - auth = [[WOApplication application] - authenticatorInContext: context]; - value = [[context request] - cookieValueForKey: [auth cookieNameInContext: context]]; - creds = [auth parseCredentials: value]; + auth = [[WOApplication application] authenticatorInContext: context]; + value = [[context request] cookieValueForKey: [auth cookieNameInContext: context]]; + creds = nil; + username = nil; - [SOGoSession decodeValue: [SOGoSession valueForSessionKey: [creds objectAtIndex: 1]] - usingKey: [creds objectAtIndex: 0] - login: &username - domain: &domain - password: &password]; + if (value) + { + // User is logged in; extract username from session + creds = [auth parseCredentials: value]; + + [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"]; // overwrite the value from the session to compare the actual input password = [message objectForKey: @"oldPassword"]; - um = [SOGoUserManager sharedUserManager]; - - // This will also update the cached password in memcached. - if ([um changePasswordForLogin: username - inDomain: domain - oldPassword: password - newPassword: newPassword - perr: &error]) + // Validate required parameters + if (!username) { - // 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]; + response = [self responseWithStatus: 403 + andString: @"Missing 'username' parameter"]; + } + else if (!password) + { + response = [self responseWithStatus: 403 + andString: @"Missing 'oldPassword' parameter"]; + } + else if (!newPassword) + { + response = [self responseWithStatus: 403 + andString: @"Missing 'newPassword' parameter"]; } 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; } diff --git a/UI/Templates/MainUI/SOGoRootPage.wox b/UI/Templates/MainUI/SOGoRootPage.wox index 7912d1d62..0c6f7f617 100644 --- a/UI/Templates/MainUI/SOGoRootPage.wox +++ b/UI/Templates/MainUI/SOGoRootPage.wox @@ -156,6 +156,59 @@ + +
+ watch_later +
+ +
+
+
+ + + + + + + + + + + +
+
+
+
+
+
+ + + +
+
+
+ + +
+ warning +
+ {{app.cn}} +
+
+ {{app.errorMessage}} + +
+ +
+
@@ -165,6 +218,17 @@
+
+ done +
+ {{app.errorMessage}} +
+ +
+
diff --git a/UI/WebServerResources/js/Common/Authentication.service.js b/UI/WebServerResources/js/Common/Authentication.service.js index 9d2fd4e6b..c801fdcb1 100644 --- a/UI/WebServerResources/js/Common/Authentication.service.js +++ b/UI/WebServerResources/js/Common/Authentication.service.js @@ -100,46 +100,68 @@ // Check password policy else if (typeof data.expire != 'undefined' && typeof data.grace != 'undefined') { if (data.expire < 0 && data.grace > 0) { - d.reject({grace: data.grace}); - //showPasswordDialog('grace', createPasswordGraceDialog, data['grace']); + d.reject({ + cn: data.cn, + url: redirectUrl(username, domain), + grace: data.grace + }); } else if (data.expire > 0 && data.grace == -1) { - d.reject({expire: data.expire}); - //showPasswordDialog('expiration', createPasswordExpirationDialog, data['expire']); + d.reject({ + cn: data.cn, + url: redirectUrl(username, domain), + expire: data.expire + }); } else { - d.resolve({ cn: data.cn, url: redirectUrl(username, domain) }); + d.resolve({ + cn: data.cn, + url: redirectUrl(username, domain) + }); } } else { d.resolve({ url: redirectUrl(username, domain) }); } } - }, function(response) { - var msg, perr, data = response.data; + }, function(error) { + var response, perr, data = error.data; 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; if (perr == passwordPolicyConfig.PolicyNoError) { - msg = l('Wrong username or password.'); + response = {error: l('Wrong username or password.')}; } 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 { - msg = l('Login failed due to unhandled error case: ') + perr; + response = {error: l('Login failed due to unhandled error case: ') + perr}; } } else { - msg = l('Unhandled error response'); + response = {error: l('Unhandled error response')}; } - d.reject({error: msg}); + d.reject(response); }); return d.promise; }, // login: function(data) { ... - changePassword: function(newPassword, oldPassword) { + changePassword: function(userName, domain, newPassword, oldPassword) { var d = $q.defer(), xsrfCookie = $cookies.get('XSRF-TOKEN'); @@ -151,8 +173,10 @@ headers: { 'X-XSRF-TOKEN' : xsrfCookie }, - data: { newPassword: newPassword, oldPassword: oldPassword } - }).then(d.resolve, function(response) { + data: { userName: userName, newPassword: newPassword, oldPassword: oldPassword } + }).then(function() { + d.resolve({url: redirectUrl(userName, domain)}); + }, function(response) { var error, data = response.data, perr = data.LDAPPasswordPolicyError; @@ -161,7 +185,8 @@ perr = passwordPolicyConfig.PolicyPasswordSystemUnknown; error = _("Unhandled error response"); } - else if (perr == passwordPolicyConfig.PolicyNoError) { + else if (perr == passwordPolicyConfig.PolicyNoError || + perr == passwordPolicyConfig.PolicyPasswordUnknown) { error = l("Password change failed"); } else if (perr == passwordPolicyConfig.PolicyPasswordModNotAllowed) { error = l("Password change failed - Permission denied"); diff --git a/UI/WebServerResources/js/Main/Main.app.js b/UI/WebServerResources/js/Main/Main.app.js index a3e37a969..7996420b1 100644 --- a/UI/WebServerResources/js/Main/Main.app.js +++ b/UI/WebServerResources/js/Main/Main.app.js @@ -17,6 +17,7 @@ this.creds = { username: $window.cookieUsername, password: null, + domain: null, rememberLogin: angular.isDefined($window.cookieUsername) && $window.cookieUsername.length > 0 }; // Send selected language only if user has changed it @@ -27,6 +28,9 @@ // Code pattern for Google verification code this.verificationCodePattern = '\\d{6}'; + // Password policy - change expired password + this.passwords = { newPassword: null, newPasswordConfirmation: null, oldPassword: null }; + // Show login once everything is initialized this.showLogin = false; $timeout(function() { vm.showLogin = true; }, 100); @@ -43,18 +47,55 @@ else { vm.loginState = 'logged'; vm.cn = data.cn; + vm.url = data.url; // Let the user see the succesfull message before reloading the page $timeout(function() { - if ($window.location.href === data.url) - $window.location.reload(true); - else - $window.location.href = data.url; + vm.continueLogin(); }, 1000); } }, function(msg) { 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; }; @@ -64,6 +105,13 @@ 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) { $mdDialog.show({ targetEvent: $event, @@ -83,6 +131,36 @@ // Reload page $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 diff --git a/UI/WebServerResources/js/Preferences/PreferencesController.js b/UI/WebServerResources/js/Preferences/PreferencesController.js index f563f3e54..73cb7ed09 100644 --- a/UI/WebServerResources/js/Preferences/PreferencesController.js +++ b/UI/WebServerResources/js/Preferences/PreferencesController.js @@ -484,7 +484,7 @@ }; 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({ title: l('Password'), textContent: l('The password was changed successfully.'), @@ -497,7 +497,7 @@ }, function(msg) { var alert = $mdDialog.alert({ title: l('Password'), - content: msg, + textContent: msg, ok: l('OK') }); $mdDialog.show( alert )