Skip to content
Browse files

Add support for changing user's password

  • Loading branch information...
1 parent 0e31865 commit f24a4d767a6625872417c3a3ccd80955ede8ed64 @jensl committed Nov 8, 2012
Showing with 153 additions and 17 deletions.
  1. +3 −0 auth.py
  2. +1 −0 critic.py
  3. +44 −4 operation/manipulateuser.py
  4. +22 −7 page/home.py
  5. +4 −0 request.py
  6. +1 −1 resources/basic.js
  7. +8 −2 resources/home.css
  8. +70 −3 resources/home.js
View
3 auth.py
@@ -29,3 +29,6 @@ def checkPassword(db, username, password):
if bcrypt.hashpw(password, hashed) == hashed: return
else: raise WrongPassword
+
+def hashPassword(password):
+ return bcrypt.hashpw(password, bcrypt.gensalt())
View
1 critic.py
@@ -769,6 +769,7 @@ def processcommits(req, db, user):
"setfullname": operation.manipulateuser.SetFullname(),
"setemail": operation.manipulateuser.SetEmail(),
"setgitemails": operation.manipulateuser.SetGitEmails(),
+ "changepassword": operation.manipulateuser.ChangePassword(),
"getassignedchanges": operation.manipulateassignments.GetAssignedChanges(),
"setassignedchanges": operation.manipulateassignments.SetAssignedChanges(),
"watchreview": watchreview,
View
48 operation/manipulateuser.py
@@ -16,8 +16,9 @@
import dbutils
import gitutils
+import auth
-from operation import Operation, OperationResult, OperationError, Optional
+from operation import Operation, OperationResult, OperationError, OperationFailure, Optional
class SetFullname(Operation):
def __init__(self):
@@ -26,7 +27,9 @@ def __init__(self):
def process(self, db, user, user_id, value):
if user.id != user_id and not user.hasRole(db, "administrator"):
- raise OperationError("operation not permitted")
+ raise OperationFailure(code="notallowed",
+ title="Not allowed!",
+ message="Operation not permitted.")
if not value.strip():
raise OperationError("empty display name is not allowed")
@@ -43,7 +46,9 @@ def __init__(self):
def process(self, db, user, user_id, value):
if user.id != user_id and not user.hasRole(db, "administrator"):
- raise OperationError("operation not permitted")
+ raise OperationFailure(code="notallowed",
+ title="Not allowed!",
+ message="Operation not permitted.")
if not value.strip():
raise OperationError("empty email address is not allowed")
@@ -62,7 +67,9 @@ def __init__(self):
def process(self, db, user, user_id, value):
if user.id != user_id and not user.hasRole(db, "administrator"):
- raise OperationError("operation not permitted")
+ raise OperationFailure(code="notallowed",
+ title="Not allowed!",
+ message="Operation not permitted.")
for address in value:
if not address.strip():
@@ -84,3 +91,36 @@ def process(self, db, user, user_id, value):
db.commit()
return OperationResult()
+
+class ChangePassword(Operation):
+ def __init__(self):
+ Operation.__init__(self, { "user_id": int,
+ "current_pw": Optional(str),
+ "new_pw": str })
+
+ def process(self, db, user, user_id, new_pw, current_pw=None):
+ if (user.id != user_id or current_pw is None) and not user.hasRole(db, "administrator"):
+ raise OperationFailure(code="notallowed",
+ title="Not allowed!",
+ message="Operation not permitted.")
+
+ subject = dbutils.User.fromId(db, user_id)
+
+ if current_pw is not None:
+ try: auth.checkPassword(db, subject.name, current_pw)
+ except auth.WrongPassword:
+ raise OperationFailure(code="wrongpassword",
+ title="Wrong password!",
+ message="The provided current password is not correct.")
+
+ if not new_pw:
+ raise OperationFailure(code="emptypassword",
+ title="Empty password!",
+ message="Setting an empty password is not allowed.")
+
+ cursor = db.cursor()
+ cursor.execute("UPDATE users SET password=%s WHERE id=%s", (auth.hashPassword(new_pw), user_id))
+
+ db.commit()
+
+ return OperationResult()
View
29 page/home.py
@@ -51,6 +51,10 @@ def renderHome(req, db, user):
document.addExternalScript("resource/home.js")
if repository: document.addInternalScript(repository.getJS())
else: document.addInternalScript("var repository = null;")
+ if user.name != req.user and req.getUser(db).hasRole(db, "administrator"):
+ document.addInternalScript("var administrator = true;")
+ else:
+ document.addInternalScript("var administrator = false;")
document.addInternalScript(user.getJS())
document.addInternalScript("user.gitEmails = %s;" % htmlutils.jsify(gitemails))
document.setTitle("%s Home" % title_fullname)
@@ -60,41 +64,52 @@ def renderHome(req, db, user):
basic = target.table('paleyellow basic', align='center')
basic.tr().td('h1', colspan=3).h1().text("%s Home" % title_fullname)
- def row(heading, value, help, status_id=None):
+ def row(heading, value, help=None, status_id=None):
main_row = basic.tr('line')
main_row.td('heading').text("%s:" % heading)
- if callable(value): value(main_row.td('value'))
- else: main_row.td('value').text(value)
- main_row.td('status', id=status_id)
- if help: basic.tr('help').td('help', colspan=3).text(help)
+ value_cell = main_row.td('value', colspan=2)
+ if callable(value): value(value_cell)
+ else: value_cell.text(value)
+ basic.tr('help').td('help', colspan=3).text(help)
def renderFullname(target):
if readonly: target.text(user.fullname)
else:
target.input("value", id="user_fullname", value=user.fullname)
+ target.span("status", id="status_fullname")
target.button(onclick="saveFullname();").text("Save")
target.button(onclick="resetFullname();").text("Reset")
def renderEmail(target):
if readonly: target.text(user.email)
else:
target.input("value", id="user_email", value=user.email)
+ target.span("status", id="status_email")
target.button(onclick="saveEmail();").text("Save")
target.button(onclick="resetEmail();").text("Reset")
def renderGitEmails(target):
if readonly: target.text(gitemails)
else:
target.input("value", id="user_gitemails", value=gitemails)
+ target.span("status", id="status_gitemails")
target.button(onclick="saveGitEmails();").text("Save")
target.button(onclick="resetGitEmails();").text("Reset")
- row("User ID", str(user.id), "This is the user ID in the database. It really doesn't matter.")
- row("User Name", user.name, "This is the user name. You probably already knew that.")
+ def renderPassword(target):
+ target.text("****")
+ if not readonly:
+ target.button(onclick="changePassword();").text("Change")
+
+ row("User ID", str(user.id))
+ row("User Name", user.name)
row("Display Name", renderFullname, "This is the name used when displaying commits or comments.", status_id="status_fullname")
row("Email", renderEmail, "This is the primary email address, to which emails are sent.", status_id="status_email")
row("Git Emails", renderGitEmails, "These email addresses are used to map Git commits to the user.", status_id="status_gitemails")
+ if configuration.base.AUTHENTICATION_MODE == "critic":
+ row("Password", renderPassword)
+
filters = target.table('paleyellow filters', align='center')
row = filters.tr()
row.td("h1", colspan=2).h1().text("Filters")
View
4 request.py
@@ -200,6 +200,10 @@ def __setUser(self, db, environ):
return
except auth.CheckFailed: pass
+ def getUser(self, db):
+ import dbutils
+ return dbutils.User.fromName(db, self.user)
+
def getParameter(self, name, default=NoDefault(), filter=lambda value: value):
"""\
Get URI query parameter.
View
2 resources/basic.js
@@ -59,7 +59,7 @@ function reportError(what, specifics, title, callback)
function showMessage(title, heading, message, callback)
{
- var content = $("<div title='" + title + "'><h1>" + heading + "</h1>" + message + "</div>");
+ var content = $("<div title='" + title + "'><h1>" + heading + "</h1>" + (message || "") + "</div>");
content.dialog({ width: 600, modal: true, buttons: { OK: function () { content.dialog("close"); if (callback) callback(); }}});
}
View
10 resources/home.css
@@ -23,11 +23,12 @@ div.main { text-align: center }
div.main table td.repositories { text-align: right; border-bottom: 1px solid #996 }
div.main table td.repositories select { background-color: #ffd }
-div.main table.basic tr.line td { padding-top: 0.5em }
+div.main table.basic tr.line td { padding-top: 0.5em; vertical-align: top }
div.main table.basic td.heading { font-family: serif; font-weight: bold; text-align: right; width: 20% }
div.main table.basic td.value { font-family: monospace; text-align: left }
div.main table.basic td.value input.value { font-family: monospace; width: 32em }
-div.main table.basic td.status { text-align: right; width: 20%; font-weight: bold }
+div.main table.basic td.value button { float: right }
+div.main table.basic td.value span.status { padding-left: 1em; font-weight: bold }
div.main table.basic td.help { font-style: italic; text-align: right; border-bottom: 1px solid #996 }
div.main table.filters td.help { font-style: italic; text-align: left; padding-top: 1em; border-bottom: 1px solid #996 }
@@ -50,3 +51,8 @@ div.main table.filters td.heading.type { text-align: right; width: 10%; font-wei
div.main table.filters td.heading.path { text-align: left; width: 50%; font-weight: bold; text-decoration: underline }
div.main table.filters td.heading.delegate { text-align: left; width: 30%; font-weight: bold; text-decoration: underline }
div.main table.filters td.heading.buttons { text-align: right; width: 10% }
+
+div.password input {
+ width: 100%;
+ font-family: monospace
+}
View
73 resources/home.js
@@ -79,7 +79,7 @@ function resetFullname()
var status = $("#status_fullname");
input.val(user.displayName);
- status.text();
+ status.text("");
}
function saveEmail()
@@ -114,7 +114,7 @@ function resetEmail()
var status = $("#status_email");
input.val(user.email);
- status.text();
+ status.text("");
}
function saveGitEmails()
@@ -149,7 +149,74 @@ function resetGitEmails()
var status = $("#status_gitemails");
input.val(user.gitEmails);
- status.text();
+ status.text("");
+}
+
+function changePassword()
+{
+ var dialog;
+
+ if (administrator)
+ dialog = $("<div class=password title='Change password'>"
+ + "<p><b>New password:</b><br>"
+ + "<input class=new1 type=password><br>"
+ + "<input class=new2 type=password>"
+ + "</p>"
+ + "</div>");
+ else
+ dialog = $("<div class=password title='Change password'>"
+ + "<p><b>Current password:</b><br>"
+ + "<input class=current type=password>"
+ + "</p>"
+ + "<p><b>New password:</b><br>"
+ + "<input class=new1 type=password><br>"
+ + "<input class=new2 type=password>"
+ + "</p>"
+ + "</div>");
+
+ function save()
+ {
+ var new1 = dialog.find("input.new1").val();
+ var new2 = dialog.find("input.new2").val();
+
+ if (new1 != new2)
+ {
+ showMessage("Invalid input", "New password mismatch!", "The new password must be input twice.");
+ return;
+ }
+
+ var data = { user_id: user.id, new_pw: new1 };
+
+ if (!administrator)
+ {
+ var current = dialog.find("input.current").val();
+
+ if (!current)
+ {
+ showMessage("Invalid input", "Current password empty!", "The current password must be input.");
+ return;
+ }
+
+ data.current_pw = current;
+ }
+
+ var operation = new Operation({ action: "change password",
+ url: "changepassword",
+ data: data });
+
+ if (operation.execute())
+ {
+ dialog.dialog("close");
+ showMessage("Success", "Password changed!");
+ }
+ }
+
+ function cancel()
+ {
+ dialog.dialog("close");
+ }
+
+ dialog.dialog({ width: 400, buttons: { "Save": save, "Cancel": cancel }});
}
function ModificationChecker(current, input, status)

0 comments on commit f24a4d7

Please sign in to comment.
Something went wrong with that request. Please try again.