Skip to content

Commit

Permalink
Add support for changing user's password
Browse files Browse the repository at this point in the history
  • Loading branch information
opera-jl committed Nov 11, 2012
1 parent 0e31865 commit f24a4d7
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 17 deletions.
3 changes: 3 additions & 0 deletions auth.py
Expand Up @@ -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())
1 change: 1 addition & 0 deletions critic.py
Expand Up @@ -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,
Expand Down
48 changes: 44 additions & 4 deletions operation/manipulateuser.py
Expand Up @@ -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):
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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():
Expand All @@ -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()
29 changes: 22 additions & 7 deletions page/home.py
Expand Up @@ -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)
Expand All @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions request.py
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion resources/basic.js
Expand Up @@ -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(); }}});
}
Expand Down
10 changes: 8 additions & 2 deletions resources/home.css
Expand Up @@ -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 }
Expand All @@ -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
}
73 changes: 70 additions & 3 deletions resources/home.js
Expand Up @@ -79,7 +79,7 @@ function resetFullname()
var status = $("#status_fullname");

input.val(user.displayName);
status.text();
status.text("");
}

function saveEmail()
Expand Down Expand Up @@ -114,7 +114,7 @@ function resetEmail()
var status = $("#status_email");

input.val(user.email);
status.text();
status.text("");
}

function saveGitEmails()
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit f24a4d7

Please sign in to comment.