Monotone-Parent: e3d309fa0c25170ca568f73dc1467d549a6a3209

Monotone-Revision: 9b6a87c288fd476ce2774933261d8fc081fc8334

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2010-03-08T20:52:37
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Wolfgang Sourdeau
2010-03-08 20:52:37 +00:00
parent e42e19b9d5
commit 17bb6a4c7a
10 changed files with 437 additions and 192 deletions
+17
View File
@@ -1,5 +1,22 @@
2010-03-08 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/WebServerResources/generic.js (createDialog): new method
designed to provide a normalized interface for creating popup
dialogs with DOM methods.
(createButton): same as above for buttons.
(SetLogMessage): new normalizing method for putting typed log
message in specified containers via DOM methods.
* UI/WebServerResources/SOGoRootPage.js: added code for popping up
a password change dialog whenever one is required by the server.
* UI/MainUI/SOGoRootPage.m (_responseWithLDAPPolicyError:): new
method for returning password change and login-based LDAP policy
errors.
* UI/WebServerResources/PasswordPolicy.js: new module that handle
the password change protocol and error codes.
* UI/WebServerResources/UIxPreferences.js
(onComposeMessagesTypeChange): we now do the proper widget
wizardry via DOM methods to alternate between the text-based or
+33 -18
View File
@@ -111,6 +111,22 @@
}
/* actions */
- (WOResponse *) _responseWithLDAPPolicyError: (int) error
{
WOResponse *response;
NSDictionary *jsonError;
jsonError
= [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: error]
forKey: @"LDAPPasswordPolicyError"];
response = [self responseWithStatus: 403
andJSONRepresentation: jsonError];
[response setHeader: @"application/json"
forKey: @"content-type"];
return response;
}
- (id <WOActionResults>) connectAction
{
WOResponse *response;
@@ -135,11 +151,8 @@
password = [request formValueForKey: @"password"];
language = [request formValueForKey: @"language"];
if ((b = [auth checkLogin: username
password: password
perr: &err
expire: &expire
grace: &grace])
if ((b = [auth checkLogin: username password: password
perr: &err expire: &expire grace: &grace])
&& (err == PolicyNoError || err == PolicyChangeAfterReset))
{
[self logWithFormat: @"successful login for user '%@'", username];
@@ -167,7 +180,7 @@
else
{
[self logWithFormat: @"Login for user '%@' might not have worked - password policy: %d bound: ", username, err, b];
if (err == PolicyNoError)
{
[self logWithFormat: @"failed login for user '%@' due to wrong password", username];
@@ -180,8 +193,8 @@
{
// The password MUST be changed - we need to ask for the old password and the new one here
}
response = [self responseWithStatus: 403];
response = [self _responseWithLDAPPolicyError: err];
}
return response;
@@ -418,7 +431,9 @@
SOGoPasswordPolicyError error;
WOResponse *response;
WORequest *request;
NSDictionary *message, *jsonError;
NSDictionary *message;
SOGoWebAuthenticator *auth;
WOCookie *authCookie;
request = [context request];
message = [NSMutableDictionary dictionaryWithJSONString: [request contentAsString]];
@@ -433,17 +448,17 @@
oldPassword: password
newPassword: newPassword
perr: &error])
response = [self responseWith204];
else
{
jsonError
= [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: error]
forKey: @"LDAPPasswordPolicyError"];
response = [self responseWithStatus: 403
andJSONRepresentation: jsonError];
[response setHeader: @"application/json"
forKey: @"content-type"];
response = [self responseWith204];
auth = [[WOApplication application]
authenticatorInContext: context];
authCookie = [self _cookieWithUsername: username
andPassword: newPassword
forAuthenticator: auth];
[response addCookie: authCookie];
}
else
response = [self _responseWithLDAPPolicyError: error];
return response;
}
+2 -3
View File
@@ -7,6 +7,7 @@
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:rsrc="OGo:url"
xmlns:label="OGo:label"
const:jsFiles="PasswordPolicy.js"
const:popup="YES"
><var:string var:value="doctype" const:escapeHTML="NO"/>
<div id="aboutBox" style="display:none;">
@@ -58,9 +59,7 @@
</tr>
<tr><td colspan="2"><label id="animation"><!-- busy.gif! --></label></td></tr>
</table>
<p id="noCookiesErrorMessage" style="display: none;"><var:string label:value="cookiesNotEnabled"/></p>
<p id="loginErrorMessage" style="display: none;"><var:string label:value="Wrong username or password."/></p>
<p id="errorMessage"><!-- space --></p>
</div>
</form
><img const:alt="*" id="preparedAnimation" rsrc:src="busy.gif"/>
@@ -10,7 +10,7 @@
className="UIxPageFrame"
title="title"
const:popup="YES"
const:jsFiles="ckeditor/ckeditor.js"
const:jsFiles="PasswordPolicy.js,ckeditor/ckeditor.js"
>
<form id="mainForm" var:href="ownPath">
<div class="tabsContainer" id="preferencesTabs">
+98
View File
@@ -0,0 +1,98 @@
var PolicyPasswordChangeUnsupported = -3;
var PolicyPasswordSystemUnknown = -2;
var PolicyPasswordUnknown = -1;
var PolicyPasswordExpired = 0;
var PolicyAccountLocked = 1;
var PolicyChangeAfterReset = 2;
var PolicyPasswordModNotAllowed = 3;
var PolicyMustSupplyOldPassword = 4;
var PolicyInsufficientPasswordQuality = 5;
var PolicyPasswordTooShort = 6;
var PolicyPasswordTooYoung = 7;
var PolicyPasswordInHistory = 8;
var PolicyNoError = 65535;
function _passwordPolicyAjaxCallback(http) {
if (http.readyState == 4) {
var policy = http.callbackData;
policy.callback(http);
}
}
function PasswordPolicy(userName, password) {
this.userName = userName;
this.password = password;
}
PasswordPolicy.prototype = {
userName: null,
password: null,
successCallback: null,
failureCallback: null,
setCallbacks: function(successCallback, failureCallback) {
this.successCallback = successCallback;
this.failureCallback = failureCallback;
},
changePassword: function (newPassword) {
var content = Object.toJSON({ userName: this.userName,
password: this.password,
newPassword: newPassword });
var urlParts = ApplicationBaseURL.split("/");
var url = urlParts[1] + "/so/changePassword";
triggerAjaxRequest(url, _passwordPolicyAjaxCallback, this,
content, {"content-type": "application/json"} );
},
callback: function(http) {
if (isHttpStatus204(http.status)) {
if (this.successCallback)
this.successCallback(_("The password was changed successfully."));
} else {
if (this.failureCallback) {
var perr = PolicyPasswordUnknown;
var error = "";
switch (http.status) {
case 403:
if (http.getResponseHeader("content-type")
== "application/json") {
var jsonResponse = http.responseText.evalJSON(false);
perr = jsonResponse["LDAPPasswordPolicyError"];
// Normal password change failed
if (perr == PolicyNoError) {
error = _("Password change failed");
} else if (perr == PolicyPasswordModNotAllowed) {
error = _("Password change failed - Permission denied");
} else if (perr == PolicyInsufficientPasswordQuality) {
error = _("Password change failed - Insufficient password quality");
} else if (perr == PolicyPasswordTooShort) {
error = _("Password change failed - Password is too short");
} else if (perr == PolicyPasswordTooYoung) {
error = _("Password change failed - Password is too young");
} else if (perr == PolicyPasswordInHistory) {
error = _("Password change failed - Password is in history");
} else {
error = _("Unhandled policy error: %{0}").formatted(perr);
perr = PolicyPasswordUnknown;
}
} else {
perr = PolicyPasswordSystemUnknown;
error = _("Unhandled error response");
}
break;
case 404:
perr = PolicyPasswordChangeUnsupported;
error = _("Password change is not supported.");
break;
default:
perr = PolicyPasswordSystemUnknown;
error = _("Unhandled HTTP error code: %{0]").formatted(http.status);
}
this.failureCallback(perr, error);
// showPasswordMessage(error);
}
}
}
};
+3 -2
View File
@@ -111,9 +111,10 @@ IMG#progressIndicator
margin-top: 20px;
margin-left: 5px; }
#noCookiesErrorMessage,
#loginErrorMessage
#errorMessage
{ color: #f00;
width: 400px;
margin: 0px auto;
text-align: center; }
P.browser
+189 -53
View File
@@ -1,5 +1,7 @@
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
var dialogs = {};
function initLogin() {
var date = new Date();
date.setTime(date.getTime() - 86400000);
@@ -13,10 +15,14 @@ function initLogin() {
var about = $("about");
if (about) {
about.observe("click", function(event) { $("aboutBox").show(); });
about.observe("click", function(event) {
$("aboutBox").show();
event.stop() });
var aboutClose = $("aboutClose");
aboutClose.observe("click", function(event) { $("aboutBox").hide(); });
aboutClose.observe("click", function(event) {
$("aboutBox").hide();
event.stop() });
}
var submit = $("submit");
@@ -52,8 +58,7 @@ function onLoginClick(event) {
var language = $("language");
if (userName.length > 0) {
$("loginErrorMessage").hide();
$("noCookiesErrorMessage").hide();
SetLogMessage("errorMessage", null);
this.disabled = true;
startAnimation($("animation"));
@@ -61,11 +66,14 @@ function onLoginClick(event) {
&& loginSuffix.length > 0
&& !userName.endsWith(loginSuffix))
userName += loginSuffix;
var url = $("connectForm").getAttribute("action");
var parameters = "userName=" + encodeURIComponent(userName) +
"&password=" + encodeURIComponent(password);
var parameters = ("userName=" + encodeURIComponent(userName)
+ "&password=" + encodeURIComponent(password));
if (language)
parameters += (language.value == "WONoSelectionString")?"":("&language=" + language.value);
parameters += ((language.value == "WONoSelectionString")
? ""
: ("&language=" + language.value));
/// Discarded as it seems to create a cookie for nothing. To discard
// a cookie in JS, have a look here: http://www.quirksmode.org/js/cookies.html
//document.cookie = "";
@@ -82,64 +90,192 @@ function onLoginClick(event) {
function onLoginCallback(http) {
if (http.readyState == 4) {
var noCookiesErrorMessage = $("noCookiesErrorMessage");
var loginErrorMessage = $("loginErrorMessage");
var submitBtn = $("submit");
if (isHttpStatus204(http.status)) {
// Make sure browser's cookies are enabled
var cookieExists = 0;
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf("0xHIGHFLYxSOGo=") == 0) {
cookieExists = 1;
break;
}
}
if (cookieExists === 0) {
loginErrorMessage.hide();
noCookiesErrorMessage.show();
var loginCookie = readLoginCookie();
if (!loginCookie) {
SetLogMessage("errorMessage", _("cookiesNotEnabled"));
submitBtn.disabled = false;
return false;
return;
}
// Redirect to proper page
var userName = $("userName").value;
if (typeof(loginSuffix) != "undefined"
&& loginSuffix.length > 0
&& !userName.endsWith(loginSuffix))
userName += loginSuffix;
var address = "" + window.location.href;
var baseAddress = ApplicationBaseURL + encodeURIComponent(userName);
var altBaseAddress;
if (baseAddress[0] == "/") {
var parts = address.split("/");
var hostpart = parts[2];
var protocol = parts[0];
baseAddress = protocol + "//" + hostpart + baseAddress;
}
var altBaseAddress;
var parts = baseAddress.split("/");
parts.splice(3, 0);
altBaseAddress = parts.join("/");
var newAddress;
if ((address.startsWith(baseAddress)
|| address.startsWith(altBaseAddress))
&& !address.endsWith("/logoff"))
newAddress = address;
else
newAddress = baseAddress;
window.location.href = newAddress;
redirectToUserPage();
}
else {
loginErrorMessage.show();
noCookiesErrorMessage.hide();
if (http.status == 403
&& http.getResponseHeader("content-type")
== "application/json") {
var jsonResponse = http.responseText.evalJSON(false);
handlePasswordError(jsonResponse);
} else {
SetLogMessage("errorMessage", _("An unhandled error occurred."));
}
submitBtn.disabled = false;
}
}
}
function redirectToUserPage() {
// Redirect to proper page
var userName = $("userName").value;
if (typeof(loginSuffix) != "undefined"
&& loginSuffix.length > 0
&& !userName.endsWith(loginSuffix))
userName += loginSuffix;
var address = "" + window.location.href;
var baseAddress = ApplicationBaseURL + encodeURIComponent(userName);
var altBaseAddress;
if (baseAddress[0] == "/") {
var parts = address.split("/");
var hostpart = parts[2];
var protocol = parts[0];
baseAddress = protocol + "//" + hostpart + baseAddress;
}
var altBaseAddress;
var parts = baseAddress.split("/");
parts.splice(3, 0);
altBaseAddress = parts.join("/");
var newAddress;
if ((address.startsWith(baseAddress)
|| address.startsWith(altBaseAddress))
&& !address.endsWith("/logoff"))
newAddress = address;
else
newAddress = baseAddress;
window.location.href = newAddress;
}
function handlePasswordError(jsonResponse) {
var perr = jsonResponse["LDAPPasswordPolicyError"];
if (perr == PolicyNoError) {
SetLogMessage("errorMessage", _("Wrong username or password."));
} else if (perr == PolicyAccountLocked) {
SetLogMessage("errorMessage",
_("Your account was locked due to too many"
+ " failed attempts."));
} else if (perr == PolicyChangeAfterReset
|| perr == PolicyPasswordExpired) {
showPasswordDialog("change", createPasswordChangeDialog, 5);
} else
SetLogMessage("errorMessage",
_("Login failed due to unhandled error case: " + perr));
}
function showPasswordDialog(dialogType, constructor, parameters) {
var dialog = dialogs[dialogType];
if (!dialog) {
dialog = constructor(parameters);
var form = $("connectForm");
form.appendChild(dialog);
dialogs[dialogType] = dialog;
}
var password = $("password");
var offsets = password.cumulativeOffset();
dialog.show();
var top = offsets[1] + 5;
var left = offsets[0] + password.clientWidth - dialog.clientWidth;
dialog.setStyle({ "top": top + "px", "left": left + "px"});
}
function createPasswordChangeDialog() {
var fields = createElement("p");
createElement("span", "passwordError", null, null, null, fields);
var fieldNames = [ "newPassword", "newPassword2" ];
var fieldLabels = [ _("New password:"), _("Confirmation:") ];
for (var i = 0; i < fieldNames.length; i++) {
var label = createElement("label", null, null, null, null, fields);
label.appendChild(document.createTextNode(fieldLabels[i]));
createElement("input", fieldNames[i], "textField",
{ "name": fieldNames[i], "type": "text" },
null, label);
createElement("br", null, null, null, null, fields);
}
var button = createButton("passwordOKButton", _("OK"), passwordDialogOK);
button.addClassName("actionButton");
fields.appendChild(button);
fields.appendChild(document.createTextNode(" "));
button = createButton("passwordCancelButton",
_("Cancel"), passwordDialogCancel);
fields.appendChild(button);
var dialog = createDialog("passwordChangeDialog",
_("Change your Password"),
_("Your password has expired, please"
+" enter a new one below:"),
fields,
"right");
return dialog;
}
function passwordDialogOK(event) {
var field = $("newPassword");
var confirmationField = $("newPassword2");
if (field && confirmationField) {
var password = field.value;
if (password == confirmationField.value) {
if (password.length > 0) {
var userName = $("userName");
var password = $("password");
var policy = new PasswordPolicy(userName.value,
password.value);
policy.setCallbacks(onPasswordChangeSuccess,
onPasswordChangeFailure);
policy.changePassword(password);
}
else
SetLogMessage("passwordError",
_("Password must not be empty."));
}
else {
SetLogMessage("passwordError",
_("The passwords do not match. Please try again."));
field.focus();
field.select();
}
}
event.stop();
}
function onPasswordChangeSuccess() {
SetLogMessage("passwordError", _("Please wait..."));
redirectToUserPage();
}
function onPasswordChangeFailre(code, message) {
SetLogMessage("passwordError", message);
}
function passwordDialogCancel(event) {
var dialog = $("passwordChangeDialog");
dialog.hide();
event.stop();
}
function createPasswordGraceDialog(tries) {
var button = createButton("graceOKButton", _("OK"));
button.observe("click", passwordGraceDialogOK);
button.addClassName("actionButton");
return createDialog("passwordGraceDialog",
_("Password Grace Period"),
_("You have %{0} logins remaining before your"
+ " password expires. Please change your"
+ " password in the preference dialog.")
.formatted(tries),
button,
"right");
}
function passwordGraceDialogOK(event) {
var dialog = $("passwordGraceDialog");
dialog.hide();
event.stop();
}
document.observe("dom:loaded", initLogin);
+14 -99
View File
@@ -536,14 +536,20 @@ function onChangePasswordClick(event) {
if (field && confirmationField) {
var password = field.value;
if (password == confirmationField.value) {
if (password.length > 0)
changePassword(password);
if (password.length > 0) {
var loginValues = readLoginCookie();
var policy = new PasswordPolicy(loginValues[0],
loginValues[1]);
policy.setCallbacks(onPasswordChangeSuccess,
onPasswordChangeFailure);
policy.changePassword(password);
}
else
showPasswordMessage(_("Password must not be empty."),
SetLogMessage("passwordError", _("Password must not be empty."),
"error");
}
else {
showPasswordMessage(_("The passwords do not match."
SetLogMessage("passwordError", _("The passwords do not match."
+ " Please try again."),
"error");
field.focus();
@@ -553,103 +559,12 @@ function onChangePasswordClick(event) {
event.stop();
}
/* TODO: this method could serve as a basis for a basic text container (for
example the log console. */
function showPasswordMessage(message, msgType) {
var para = $("passwordError");
if (para) {
if (!msgType)
msgType = "error";
var typeClass = msgType + "Message";
if (!para.typeClass || para.typeClass != typeClass) {
if (para.typeClass) {
para.removeClassName(para.typeClass);
}
para.typeClass = typeClass;
para.addClassName(typeClass);
}
if (!para.message || para.message != message) {
while (para.lastChild) {
para.removeChild(para.lastChild);
}
if (message) {
var sentences = message.split("\n");
para.appendChild(document.createTextNode(sentences[0]));
for (var i = 1; i < sentences.length; i++) {
para.appendChild(document.createElement("br"));
para.appendChild(document.createTextNode(sentences[i]));
}
para.message = message;
}
}
}
function onPasswordChangeSuccess(message) {
SetLogMessage("passwordError", message, "info");
}
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function changePassword(newPassword) {
var loginValues = readLoginCookie();
if (loginValues) {
var content = Object.toJSON({ userName: loginValues[0],
password: loginValues[1],
newPassword: newPassword });
var url = ApplicationBaseURL + "../changePassword";
triggerAjaxRequest(url, changePasswordCallback, loginValues[1], content,
{"content-type": "application/json"} );
}
}
function changePasswordCallback(http) {
if (http.readyState == 4) {
if (isHttpStatus204(http.status)) {
log("it worked");
showPasswordMessage(_("The password was changed successfully."), "info");
setLoginCookie(UserLogin, http.callbackData);
} else {
var error;
log("header: " + http.header);
switch(http.status) {
case 403:
if (http.getResponseHeader("content-type") == "application/json") {
var jsonResponse = http.responseText.evalJSON(false);
var perr = jsonResponse["LDAPPasswordPolicyError"];
// Normal password change failed
if (perr == 65535) {
error = _("Password change failed");
} else if (perr == 3) {
error = _("Password change failed - Permission denied");
} else if (perr == 5) {
error = _("Password change failed - Insufficient password quality");
} else if (perr == 6) {
error = _("Password change failed - Password is too short");
} else if (perr == 7) {
error = _("Password change failed - Password is too young");
} else if (perr == 8) {
error = _("Password change failed - Password is in history");
}
} else {
error = _("Unhandled error code: ") + http.status;
}
break;
case 404:
error = _("Password changing is not supported.");
break;
default:
error = _("Unhandled error code: ") + http.status;
}
showPasswordMessage(error);
}
}
function onPasswordChangeFailure(code, message) {
SetLogMessage("passwordError", message, "error");
}
document.observe("dom:loaded", initPreferences);
+1 -2
View File
@@ -568,14 +568,13 @@ DIV.resize-handle
DIV.dialog
{ position: absolute;
top: 100px;
left: 75px;
z-index: 50; }
DIV.dialog DIV
{ border: 1px solid #444;
background-color: #fff;
padding: 5px;
width: 350px;
padding-bottom: 26px; }
DIV.dialog.left
+79 -14
View File
@@ -1637,7 +1637,7 @@ function _(key) {
while (topWindow.opener)
topWindow = topWindow.opener;
}
if (topWindow && topWindow.clabels[key])
if (topWindow && topWindow.clabels && topWindow.clabels[key])
value = topWindow.clabels[key];
}
@@ -1694,18 +1694,58 @@ AIM = {
if (typeof(i.onComplete) == 'function')
i.onComplete(d.body.innerHTML);
}
};
function createDialog(id, title, legend, content, positionClass) {
if (!positionClass)
positionClass = "left";
var newDialog = createElement("div", id, ["dialog", positionClass]);
newDialog.setStyle({"display": "none"});
var subdiv = createElement("div", null, null, null, null, newDialog);
if (title && title.length > 0) {
var titleh3 = createElement("h3", null, null, null, null, subdiv);
titleh3.appendChild(document.createTextNode(title));
}
if (legend && legend.length > 0) {
var legendP = createElement("p", null, null, null, null, subdiv);
legendP.appendChild(document.createTextNode(legend));
}
if (content)
subdiv.appendChild(content);
return newDialog;
}
function createButton(id, caption, action) {
var newButton = createElement("a", id, "button", { "href": "#" });
if (caption && caption.length > 0) {
var span = createElement("span", null, null, null, null, newButton);
span.appendChild(document.createTextNode(caption));
}
if (action)
newButton.observe("click", action);
return newButton;
}
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
var foundCookie = null;
var prefix = name + "=";
var pairs = document.cookie.split(';');
for (var i = 0; !foundCookie && i < pairs.length; i++) {
var currentPair = pairs[i];
var start = 0;
while (currentPair.charAt(start) == " ")
start++;
if (start > 0)
currentPair = currentPair.substr(start);
if (currentPair.indexOf(prefix) == 0)
foundCookie = currentPair.substr(prefix.length);
}
return foundCookie;
}
function readLoginCookie() {
@@ -1719,10 +1759,35 @@ function readLoginCookie() {
return loginValues;
}
function setLoginCookie(username, password) {
var value = (username + ":" + password).base64encode();
var cookieValue = encodeURIComponent("basic " + value);
window.alert("0xHIGHFLYxSOGo=" + cookieValue);
/* logging widgets */
function SetLogMessage(containerId, message, msgType) {
var container = $(containerId);
if (container) {
if (!msgType)
msgType = "error";
var typeClass = msgType + "Message";
if (!container.typeClass || container.typeClass != typeClass) {
if (container.typeClass) {
container.removeClassName(container.typeClass);
}
container.typeClass = typeClass;
container.addClassName(typeClass);
}
if (!container.message || container.message != message) {
while (container.lastChild) {
container.removeChild(container.lastChild);
}
if (message) {
var sentences = message.split("\n");
container.appendChild(document.createTextNode(sentences[0]));
for (var i = 1; i < sentences.length; i++) {
container.appendChild(document.createElement("br"));
container.appendChild(document.createTextNode(sentences[i]));
}
container.message = message;
}
}
}
}
document.observe("dom:loaded", onLoadHandler);