Add email-confirmed password changing.
This commit is contained in:
parent
d4b8fb66da
commit
ef1c94e140
5 changed files with 102 additions and 47 deletions
|
@ -10,7 +10,7 @@ legal_cla_email = "nobody@fedoraproject.org"
|
||||||
email_host = "fedoraproject.org" # as in, web-members@email_host
|
email_host = "fedoraproject.org" # as in, web-members@email_host
|
||||||
|
|
||||||
gpgexec = "/usr/bin/gpg"
|
gpgexec = "/usr/bin/gpg"
|
||||||
gpghome = "/srv/fedora-infrastructure/fas/gnupg"
|
gpghome = "/home/ricky/work/fedora/fedora-infrastructure/fas/gnupg"
|
||||||
gpg_fingerprint = "C199 1E25 D00A D200 2D2E 54D1 BF7F 1647 C54E 8410"
|
gpg_fingerprint = "C199 1E25 D00A D200 2D2E 54D1 BF7F 1647 C54E 8410"
|
||||||
gpg_passphrase = "m00!s@ysth3c0w"
|
gpg_passphrase = "m00!s@ysth3c0w"
|
||||||
gpg_keyserver = "hkp://subkeys.pgp.net"
|
gpg_keyserver = "hkp://subkeys.pgp.net"
|
||||||
|
@ -83,7 +83,7 @@ tg.strict_parameters = True
|
||||||
server.webpath='/accounts'
|
server.webpath='/accounts'
|
||||||
base_url_filter.on = True
|
base_url_filter.on = True
|
||||||
base_url_filter.use_x_forwarded_host = True
|
base_url_filter.use_x_forwarded_host = True
|
||||||
base_url_filter.base_url = "https://publictest3.fedoraproject.org/accounts"
|
base_url_filter.base_url = "http://localhost:8088/accounts"
|
||||||
|
|
||||||
# Make the session cookie only return to the host over an SSL link
|
# Make the session cookie only return to the host over an SSL link
|
||||||
# Disabled for testing.
|
# Disabled for testing.
|
||||||
|
|
|
@ -247,17 +247,16 @@ class People(SABase):
|
||||||
# Only admins can see internal_comments
|
# Only admins can see internal_comments
|
||||||
del props['internal_comments']
|
del props['internal_comments']
|
||||||
del props['emailtoken']
|
del props['emailtoken']
|
||||||
|
del props['passwordtoken']
|
||||||
if identity.current.anonymous:
|
if identity.current.anonymous:
|
||||||
# Anonymous users can't see any of these
|
# Anonymous users can't see any of these
|
||||||
del props['email']
|
del props['email']
|
||||||
del props['emailtoken']
|
|
||||||
del props['unverified_email']
|
del props['unverified_email']
|
||||||
del props['ssh_key']
|
del props['ssh_key']
|
||||||
del props['gpg_keyid']
|
del props['gpg_keyid']
|
||||||
del props['affiliation']
|
del props['affiliation']
|
||||||
del props['certificate_serial']
|
del props['certificate_serial']
|
||||||
del props['password']
|
del props['password']
|
||||||
del props['passwordtoken']
|
|
||||||
del props['password_changed']
|
del props['password_changed']
|
||||||
del props['postal_address']
|
del props['postal_address']
|
||||||
del props['telephone']
|
del props['telephone']
|
||||||
|
@ -267,7 +266,6 @@ class People(SABase):
|
||||||
# Only an admin or the user themselves can see these fields
|
# Only an admin or the user themselves can see these fields
|
||||||
del props['unverified_email']
|
del props['unverified_email']
|
||||||
del props['password']
|
del props['password']
|
||||||
del props['passwordtoken']
|
|
||||||
del props['postal_address']
|
del props['postal_address']
|
||||||
del props['password_changed']
|
del props['password_changed']
|
||||||
del props['telephone']
|
del props['telephone']
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>${_('Reset Password')}</h2>
|
<h2>${_('Reset Password')}</h2>
|
||||||
<form action="${tg.url('/user/sendpass')}" method="post">
|
<form action="${tg.url('/user/sendtoken')}" method="post">
|
||||||
<ul>
|
<ul>
|
||||||
<div class="field"><label for="username">${_('Username:')}</label> <input type="text" id="username" name="username" /></div>
|
<div class="field"><label for="username">${_('Username:')}</label> <input type="text" id="username" name="username" /></div>
|
||||||
<div class="field"><label for="email">${_('Email:')}</label> <input type="text" id="email" name="email" /></div>
|
<div class="field"><label for="email">${_('Email:')}</label> <input type="text" id="email" name="email" /></div>
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
<p>
|
<p>
|
||||||
Do you really want to change your email to: ${person.unverified_email} ?
|
Do you really want to change your email to: ${person.unverified_email} ?
|
||||||
</p>
|
</p>
|
||||||
<input type="submit" name="confirmation" id="confirmation" value="${_('Confirm')}" />
|
<input type="submit" value="${_('Confirm')}" />
|
||||||
<input type="submit" value="${_('Cancel')}" />
|
<a href="${tg.url('/user/verifyemail/%s/%s/cancel') % (person.username, token)}">${_('Cancel')}</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|
131
fas/fas/user.py
131
fas/fas/user.py
|
@ -125,6 +125,12 @@ class UserSetPassword(validators.Schema):
|
||||||
passwordcheck = validators.String
|
passwordcheck = validators.String
|
||||||
chained_validators = [validators.FieldsMatch('password', 'passwordcheck')]
|
chained_validators = [validators.FieldsMatch('password', 'passwordcheck')]
|
||||||
|
|
||||||
|
class UserResetPassword(validators.Schema):
|
||||||
|
# TODO (after we're done with most testing): Add complexity requirements?
|
||||||
|
password = validators.String(min=8)
|
||||||
|
passwordcheck = validators.String
|
||||||
|
chained_validators = [validators.FieldsMatch('password', 'passwordcheck')]
|
||||||
|
|
||||||
class UserView(validators.Schema):
|
class UserView(validators.Schema):
|
||||||
username = KnownUser
|
username = KnownUser
|
||||||
|
|
||||||
|
@ -271,8 +277,7 @@ login with your Fedora account first):
|
||||||
|
|
||||||
https://admin.fedoraproject.org/accounts/user/verifyemail/%s
|
https://admin.fedoraproject.org/accounts/user/verifyemail/%s
|
||||||
''') % token
|
''') % token
|
||||||
emailflash = _(' Your email ')
|
emailflash = _(' Before your new email takes effect, you must confirm it. You should receive an email with instructions shortly.')
|
||||||
|
|
||||||
turbomail.enqueue(message)
|
turbomail.enqueue(message)
|
||||||
target.ircnick = ircnick
|
target.ircnick = ircnick
|
||||||
target.gpg_keyid = gpg_keyid
|
target.gpg_keyid = gpg_keyid
|
||||||
|
@ -326,7 +331,7 @@ https://admin.fedoraproject.org/accounts/user/verifyemail/%s
|
||||||
|
|
||||||
@identity.require(turbogears.identity.not_anonymous())
|
@identity.require(turbogears.identity.not_anonymous())
|
||||||
@expose(template='fas.templates.user.verifyemail')
|
@expose(template='fas.templates.user.verifyemail')
|
||||||
def verifyemail(self, token):
|
def verifyemail(self, token, cancel=False):
|
||||||
username = turbogears.identity.current.user_name
|
username = turbogears.identity.current.user_name
|
||||||
person = People.by_username(username)
|
person = People.by_username(username)
|
||||||
if not person.unverified_email:
|
if not person.unverified_email:
|
||||||
|
@ -334,38 +339,38 @@ https://admin.fedoraproject.org/accounts/user/verifyemail/%s
|
||||||
turbogears.redirect('/user/view/%s' % username)
|
turbogears.redirect('/user/view/%s' % username)
|
||||||
return dict()
|
return dict()
|
||||||
if person.emailtoken and (person.emailtoken != token):
|
if person.emailtoken and (person.emailtoken != token):
|
||||||
person.emailtoken = ''
|
|
||||||
turbogears.flash(_('Invalid email change token.'))
|
turbogears.flash(_('Invalid email change token.'))
|
||||||
turbogears.redirect('/user/view/%s' % username)
|
turbogears.redirect('/user/view/%s' % username)
|
||||||
return dict()
|
return dict()
|
||||||
|
if cancel:
|
||||||
|
person.emailtoken = ''
|
||||||
|
turbogears.flash(_('Your pending email change has been canceled. The email change token has been invalidated.'))
|
||||||
|
turbogears.redirect('/user/view/%s' % username)
|
||||||
|
return dict()
|
||||||
return dict(person=person, token=token)
|
return dict(person=person, token=token)
|
||||||
|
|
||||||
@identity.require(turbogears.identity.not_anonymous())
|
@identity.require(turbogears.identity.not_anonymous())
|
||||||
@expose(template='fas.templates.user.verifyemail')
|
@expose()
|
||||||
def setemail(self, token, confirmation=None):
|
def setemail(self, token):
|
||||||
username = turbogears.identity.current.user_name
|
username = turbogears.identity.current.user_name
|
||||||
person = People.by_username(username)
|
person = People.by_username(username)
|
||||||
if not person.unverified_email:
|
if not (person.unverified_email and person.emailtoken):
|
||||||
turbogears.flash(_('You do not have any pending email changes.'))
|
turbogears.flash(_('You do not have any pending email changes.'))
|
||||||
turbogears.redirect('/user/view/%s' % username)
|
turbogears.redirect('/user/view/%s' % username)
|
||||||
return dict()
|
return dict()
|
||||||
if person.emailtoken and (person.emailtoken != token):
|
if person.emailtoken != token:
|
||||||
person.emailtoken = ''
|
|
||||||
turbogears.flash(_('Invalid email change token.'))
|
turbogears.flash(_('Invalid email change token.'))
|
||||||
turbogears.redirect('/user/view/%s' % username)
|
turbogears.redirect('/user/view/%s' % username)
|
||||||
return dict()
|
return dict()
|
||||||
if confirmation:
|
''' Log this '''
|
||||||
''' Log this '''
|
oldEmail = person.email
|
||||||
oldEmail = person.email
|
person.email = person.unverified_email
|
||||||
person.email = person.unverified_email
|
Log(author_id=person.id, description='Email changed from %s to %s' % (oldEmail, person.email))
|
||||||
Log(author_id=person.id, description='Email changed from %s to %s' % (oldEmail, person.email))
|
person.unverified_email = ''
|
||||||
person.unverified_email = ''
|
session.flush()
|
||||||
session.flush()
|
turbogears.flash(_('You have successfully changed your email to \'%s\'') % person.email)
|
||||||
turbogears.flash(_('You have successfully changed your email to \'%s\'') % person.email)
|
turbogears.redirect('/user/view/%s' % username)
|
||||||
turbogears.redirect('/user/view/%s' % username)
|
return dict()
|
||||||
else:
|
|
||||||
turbogears.flash(_('Your pending email change has been canceled. The email change token has been invalidated.') % person.email)
|
|
||||||
turbogears.redirect('/user/view/%s' % username)
|
|
||||||
|
|
||||||
@expose(template='fas.templates.user.new')
|
@expose(template='fas.templates.user.new')
|
||||||
def new(self):
|
def new(self):
|
||||||
|
@ -397,7 +402,7 @@ https://admin.fedoraproject.org/accounts/user/verifyemail/%s
|
||||||
You have created a new Fedora account!
|
You have created a new Fedora account!
|
||||||
Your new password is: %s
|
Your new password is: %s
|
||||||
|
|
||||||
Please go to https://admin.fedoraproject.org/fas/ to change it.
|
Please go to https://admin.fedoraproject.org/accounts/ to change it.
|
||||||
|
|
||||||
Welcome to the Fedora Project. Now that you've signed up for an
|
Welcome to the Fedora Project. Now that you've signed up for an
|
||||||
account you're probably desperate to start contributing, and with that
|
account you're probably desperate to start contributing, and with that
|
||||||
|
@ -481,7 +486,7 @@ forward to working with you!
|
||||||
|
|
||||||
#TODO: Validate
|
#TODO: Validate
|
||||||
@expose(template="fas.templates.user.resetpass")
|
@expose(template="fas.templates.user.resetpass")
|
||||||
def sendpass(self, username, email, encrypted=False):
|
def sendtoken(self, username, email, encrypted=False):
|
||||||
import turbomail
|
import turbomail
|
||||||
# Logged in
|
# Logged in
|
||||||
if turbogears.identity.current.user_name:
|
if turbogears.identity.current.user_name:
|
||||||
|
@ -496,14 +501,13 @@ forward to working with you!
|
||||||
if email != person.email:
|
if email != person.email:
|
||||||
turbogears.flash(_("username + email combo unknown."))
|
turbogears.flash(_("username + email combo unknown."))
|
||||||
return dict()
|
return dict()
|
||||||
newpass = generate_password()
|
token = generate_token()
|
||||||
message = turbomail.Message(config.get('accounts_email'), email, _('Fedora Project Password Reset'))
|
message = turbomail.Message(config.get('accounts_email'), email, _('Fedora Project Password Reset'))
|
||||||
mail = _('''
|
mail = _('''
|
||||||
You have requested a password reset!
|
Somebody (hopefully you) has requested a password reset for your account!
|
||||||
Your new password is: %s
|
To change your password (or to cancel the request), please visit
|
||||||
|
https://admin.fedoraproject.org/accounts/user/verifypass/%(user)s/%(token)s
|
||||||
Please go to https://admin.fedoraproject.org/fas/ to change it.
|
''') % {'user': username, 'token': token}
|
||||||
''') % newpass['pass']
|
|
||||||
if encrypted:
|
if encrypted:
|
||||||
# TODO: Move this out to a single function (same as
|
# TODO: Move this out to a single function (same as
|
||||||
# CLA one), think of how to make sure this doesn't get
|
# CLA one), think of how to make sure this doesn't get
|
||||||
|
@ -517,7 +521,8 @@ Please go to https://admin.fedoraproject.org/fas/ to change it.
|
||||||
return dict()
|
return dict()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
plaintext = StringIO.StringIO(mail)
|
# This may not be the neatest fix, but gpgme gave an error when mail was unicode.
|
||||||
|
plaintext = StringIO.StringIO(mail.encode('utf-8'))
|
||||||
ciphertext = StringIO.StringIO()
|
ciphertext = StringIO.StringIO()
|
||||||
ctx = gpgme.Context()
|
ctx = gpgme.Context()
|
||||||
ctx.armor = True
|
ctx.armor = True
|
||||||
|
@ -533,17 +538,70 @@ Please go to https://admin.fedoraproject.org/fas/ to change it.
|
||||||
ciphertext)
|
ciphertext)
|
||||||
message.plain = ciphertext.getvalue()
|
message.plain = ciphertext.getvalue()
|
||||||
except:
|
except:
|
||||||
turbogears.flash(_('Your password reset email could not be encrypted. Your password has not been changed.'))
|
turbogears.flash(_('Your password reset email could not be encrypted.'))
|
||||||
return dict()
|
return dict()
|
||||||
else:
|
else:
|
||||||
message.plain = mail;
|
message.plain = mail;
|
||||||
turbomail.enqueue(message)
|
turbomail.enqueue(message)
|
||||||
try:
|
person.passwordtoken = token
|
||||||
person.password = newpass['hash']
|
turbogears.flash(_('A password reset URL has been emailed to you.'))
|
||||||
turbogears.flash(_('Your new password has been emailed to you.'))
|
turbogears.redirect('/login')
|
||||||
except:
|
return dict()
|
||||||
turbogears.flash(_('Your password could not be reset.'))
|
|
||||||
|
@expose(template="fas.templates.user.newpass")
|
||||||
|
# TODO: Validator
|
||||||
|
def newpass(self, username, token, password=None, passwordcheck=None):
|
||||||
|
person = People.by_username(username)
|
||||||
|
if not person.passwordtoken:
|
||||||
|
turbogears.flash(_('You do not have any pending password changes.'))
|
||||||
|
turbogears.redirect('/login')
|
||||||
return dict()
|
return dict()
|
||||||
|
if person.passwordtoken != token:
|
||||||
|
person.emailtoken = ''
|
||||||
|
turbogears.flash(_('Invalid password change token.'))
|
||||||
|
turbogears.redirect('/login')
|
||||||
|
return dict()
|
||||||
|
return dict(person=person, token=token)
|
||||||
|
|
||||||
|
@expose(template="fas.templates.user.verifypass")
|
||||||
|
# TODO: Validator
|
||||||
|
def verifypass(self, username, token, cancel=False):
|
||||||
|
person = People.by_username(username)
|
||||||
|
if not person.passwordtoken:
|
||||||
|
turbogears.flash(_('You do not have any pending password changes.'))
|
||||||
|
turbogears.redirect('/login')
|
||||||
|
return dict()
|
||||||
|
if person.passwordtoken != token:
|
||||||
|
turbogears.flash(_('Invalid password change token.'))
|
||||||
|
turbogears.redirect('/login')
|
||||||
|
return dict()
|
||||||
|
if cancel:
|
||||||
|
person.passwordtoken = ''
|
||||||
|
turbogears.flash(_('Your password reset has been canceled. The password change token has been invalidated.'))
|
||||||
|
turbogears.redirect('/login')
|
||||||
|
return dict()
|
||||||
|
return dict(person=person, token=token)
|
||||||
|
|
||||||
|
@expose()
|
||||||
|
@validate(validators=UserResetPassword())
|
||||||
|
def setnewpass(self, username, token, password, passwordcheck):
|
||||||
|
person = People.by_username(username)
|
||||||
|
if not person.passwordtoken:
|
||||||
|
turbogears.flash(_('You do not have any pending password changes.'))
|
||||||
|
turbogears.redirect('/login')
|
||||||
|
return dict()
|
||||||
|
if person.passwordtoken != token:
|
||||||
|
person.emailtoken = ''
|
||||||
|
turbogears.flash(_('Invalid password change token.'))
|
||||||
|
turbogears.redirect('/login')
|
||||||
|
return dict()
|
||||||
|
''' Log this '''
|
||||||
|
newpass = generate_password(password)
|
||||||
|
person.password = newpass['hash']
|
||||||
|
person.passwordtoken = ''
|
||||||
|
Log(author_id=person.id, description='Password changed')
|
||||||
|
session.flush()
|
||||||
|
turbogears.flash(_('You have successfully reset your password. You should now be able to login below.'))
|
||||||
turbogears.redirect('/login')
|
turbogears.redirect('/login')
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
|
@ -552,7 +610,6 @@ Please go to https://admin.fedoraproject.org/fas/ to change it.
|
||||||
def gencert(self):
|
def gencert(self):
|
||||||
username = turbogears.identity.current.user_name
|
username = turbogears.identity.current.user_name
|
||||||
person = People.by_username(username)
|
person = People.by_username(username)
|
||||||
|
|
||||||
if signedCLAPrivs(person):
|
if signedCLAPrivs(person):
|
||||||
person.certificate_serial = person.certificate_serial + 1
|
person.certificate_serial = person.certificate_serial + 1
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue