From d4b8fb66da2c52b65512e18f7334ed235e64193b Mon Sep 17 00:00:00 2001 From: Ricky Zhou Date: Wed, 12 Mar 2008 08:53:47 -0400 Subject: [PATCH 1/4] Not sure why that flash was commented out... --- fas/fas/group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fas/fas/group.py b/fas/fas/group.py index 152b573..174af8d 100644 --- a/fas/fas/group.py +++ b/fas/fas/group.py @@ -281,8 +281,8 @@ class Group(controllers.Controller): group = Groups.by_name(groupname) if not canApplyGroup(person, group, target): - # turbogears.flash(_('%(user)s could not apply to %(group)s.') % \ - # {'user': target.username, 'group': group.name }) + turbogears.flash(_('%(user)s can not apply to %(group)s.') % \ + {'user': target.username, 'group': group.name }) turbogears.redirect('/group/view/%s' % group.name) return dict() else: From ef1c94e1406d273e6b803cce5fbeed77c352babd Mon Sep 17 00:00:00 2001 From: Ricky Zhou Date: Wed, 12 Mar 2008 11:35:38 -0400 Subject: [PATCH 2/4] Add email-confirmed password changing. --- fas/fas.cfg | 4 +- fas/fas/model.py | 4 +- fas/fas/templates/user/resetpass.html | 2 +- fas/fas/templates/user/verifyemail.html | 4 +- fas/fas/user.py | 135 +++++++++++++++++------- 5 files changed, 102 insertions(+), 47 deletions(-) diff --git a/fas/fas.cfg b/fas/fas.cfg index b229dd1..8942dbd 100644 --- a/fas/fas.cfg +++ b/fas/fas.cfg @@ -10,7 +10,7 @@ legal_cla_email = "nobody@fedoraproject.org" email_host = "fedoraproject.org" # as in, web-members@email_host 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_passphrase = "m00!s@ysth3c0w" gpg_keyserver = "hkp://subkeys.pgp.net" @@ -83,7 +83,7 @@ tg.strict_parameters = True server.webpath='/accounts' base_url_filter.on = 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 # Disabled for testing. diff --git a/fas/fas/model.py b/fas/fas/model.py index c345665..6b312ca 100644 --- a/fas/fas/model.py +++ b/fas/fas/model.py @@ -247,17 +247,16 @@ class People(SABase): # Only admins can see internal_comments del props['internal_comments'] del props['emailtoken'] + del props['passwordtoken'] if identity.current.anonymous: # Anonymous users can't see any of these del props['email'] - del props['emailtoken'] del props['unverified_email'] del props['ssh_key'] del props['gpg_keyid'] del props['affiliation'] del props['certificate_serial'] del props['password'] - del props['passwordtoken'] del props['password_changed'] del props['postal_address'] del props['telephone'] @@ -267,7 +266,6 @@ class People(SABase): # Only an admin or the user themselves can see these fields del props['unverified_email'] del props['password'] - del props['passwordtoken'] del props['postal_address'] del props['password_changed'] del props['telephone'] diff --git a/fas/fas/templates/user/resetpass.html b/fas/fas/templates/user/resetpass.html index 12c1a9e..d110add 100644 --- a/fas/fas/templates/user/resetpass.html +++ b/fas/fas/templates/user/resetpass.html @@ -8,7 +8,7 @@

${_('Reset Password')}

-
+
    diff --git a/fas/fas/templates/user/verifyemail.html b/fas/fas/templates/user/verifyemail.html index 945a33b..b1f69b6 100644 --- a/fas/fas/templates/user/verifyemail.html +++ b/fas/fas/templates/user/verifyemail.html @@ -13,8 +13,8 @@

    Do you really want to change your email to: ${person.unverified_email} ?

    - - + + ${_('Cancel')} diff --git a/fas/fas/user.py b/fas/fas/user.py index 5e8adb7..de1024b 100644 --- a/fas/fas/user.py +++ b/fas/fas/user.py @@ -125,6 +125,12 @@ class UserSetPassword(validators.Schema): passwordcheck = validators.String 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): username = KnownUser @@ -271,8 +277,7 @@ login with your Fedora account first): https://admin.fedoraproject.org/accounts/user/verifyemail/%s ''') % 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) target.ircnick = ircnick target.gpg_keyid = gpg_keyid @@ -326,7 +331,7 @@ https://admin.fedoraproject.org/accounts/user/verifyemail/%s @identity.require(turbogears.identity.not_anonymous()) @expose(template='fas.templates.user.verifyemail') - def verifyemail(self, token): + def verifyemail(self, token, cancel=False): username = turbogears.identity.current.user_name person = People.by_username(username) if not person.unverified_email: @@ -334,38 +339,38 @@ https://admin.fedoraproject.org/accounts/user/verifyemail/%s turbogears.redirect('/user/view/%s' % username) return dict() if person.emailtoken and (person.emailtoken != token): - person.emailtoken = '' turbogears.flash(_('Invalid email change token.')) turbogears.redirect('/user/view/%s' % username) 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) @identity.require(turbogears.identity.not_anonymous()) - @expose(template='fas.templates.user.verifyemail') - def setemail(self, token, confirmation=None): + @expose() + def setemail(self, token): username = turbogears.identity.current.user_name 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.redirect('/user/view/%s' % username) return dict() - if person.emailtoken and (person.emailtoken != token): - person.emailtoken = '' + if person.emailtoken != token: turbogears.flash(_('Invalid email change token.')) turbogears.redirect('/user/view/%s' % username) return dict() - if confirmation: - ''' Log this ''' - oldEmail = person.email - person.email = person.unverified_email - Log(author_id=person.id, description='Email changed from %s to %s' % (oldEmail, person.email)) - person.unverified_email = '' - session.flush() - turbogears.flash(_('You have successfully changed your email to \'%s\'') % person.email) - turbogears.redirect('/user/view/%s' % username) - 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) + ''' Log this ''' + oldEmail = person.email + person.email = person.unverified_email + Log(author_id=person.id, description='Email changed from %s to %s' % (oldEmail, person.email)) + person.unverified_email = '' + session.flush() + turbogears.flash(_('You have successfully changed your email to \'%s\'') % person.email) + turbogears.redirect('/user/view/%s' % username) + return dict() @expose(template='fas.templates.user.new') def new(self): @@ -397,7 +402,7 @@ https://admin.fedoraproject.org/accounts/user/verifyemail/%s You have created a new Fedora account! 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 account you're probably desperate to start contributing, and with that @@ -481,7 +486,7 @@ forward to working with you! #TODO: Validate @expose(template="fas.templates.user.resetpass") - def sendpass(self, username, email, encrypted=False): + def sendtoken(self, username, email, encrypted=False): import turbomail # Logged in if turbogears.identity.current.user_name: @@ -496,14 +501,13 @@ forward to working with you! if email != person.email: turbogears.flash(_("username + email combo unknown.")) return dict() - newpass = generate_password() + token = generate_token() message = turbomail.Message(config.get('accounts_email'), email, _('Fedora Project Password Reset')) mail = _(''' -You have requested a password reset! -Your new password is: %s - -Please go to https://admin.fedoraproject.org/fas/ to change it. -''') % newpass['pass'] +Somebody (hopefully you) has requested a password reset for your account! +To change your password (or to cancel the request), please visit +https://admin.fedoraproject.org/accounts/user/verifypass/%(user)s/%(token)s +''') % {'user': username, 'token': token} if encrypted: # TODO: Move this out to a single function (same as # 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() else: 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() ctx = gpgme.Context() ctx.armor = True @@ -533,26 +538,78 @@ Please go to https://admin.fedoraproject.org/fas/ to change it. ciphertext) message.plain = ciphertext.getvalue() 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() else: message.plain = mail; turbomail.enqueue(message) - try: - person.password = newpass['hash'] - turbogears.flash(_('Your new password has been emailed to you.')) - except: - turbogears.flash(_('Your password could not be reset.')) - return dict() + person.passwordtoken = token + turbogears.flash(_('A password reset URL has been emailed to you.')) turbogears.redirect('/login') return dict() + @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() + 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') + return dict() + @identity.require(turbogears.identity.not_anonymous()) @expose(template="genshi-text:fas.templates.user.cert", format="text", content_type='text/plain; charset=utf-8') def gencert(self): username = turbogears.identity.current.user_name - person = People.by_username(username) - + person = People.by_username(username) if signedCLAPrivs(person): person.certificate_serial = person.certificate_serial + 1 From f785f165972b1e4fa90c7eceeedbb63202fb4fde Mon Sep 17 00:00:00 2001 From: Ricky Zhou Date: Wed, 12 Mar 2008 12:03:04 -0400 Subject: [PATCH 3/4] Forgot to add verifypass.html --- fas/fas/templates/user/verifypass.html | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 fas/fas/templates/user/verifypass.html diff --git a/fas/fas/templates/user/verifypass.html b/fas/fas/templates/user/verifypass.html new file mode 100644 index 0000000..763dd54 --- /dev/null +++ b/fas/fas/templates/user/verifypass.html @@ -0,0 +1,22 @@ + + + + + ${_('Reset Password')} + + +

    ${_('Reset Password')}

    +
    +
      +
      +
      + +
    +
    + + From a5115b6bd14cb4a08d8ce93f9a0743124f045ca8 Mon Sep 17 00:00:00 2001 From: Ricky Zhou Date: Wed, 12 Mar 2008 12:05:45 -0400 Subject: [PATCH 4/4] Don't think we need this anymore. --- fas/moveover.py | 80 ------------------------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 fas/moveover.py diff --git a/fas/moveover.py b/fas/moveover.py deleted file mode 100644 index 44cf164..0000000 --- a/fas/moveover.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/python -import pgdb - -from turbogears.view import engines -import turbogears.view -import turbogears.util as tg_util -from turbogears import view, database, errorhandling, config -from itertools import izip -from inspect import isclass -from turbogears import update_config, start_server -import cherrypy -cherrypy.lowercase_api = True -from os.path import * -import sys -import time -import crypt -import random - -if len(sys.argv) > 1: - update_config(configfile=sys.argv[1], - modulename="fas.config") -elif exists(join(dirname(__file__), "setup.py")): - update_config(configfile="dev.cfg",modulename="fas.config") -else: - update_config(configfile="prod.cfg",modulename="fas.config") - -from sqlalchemy import * -from sqlalchemy.exceptions import * -from fas.model import * - - -db = pgdb.connect(dsn='localhost', user='fedora', password='test', database='fedorausers') - -c = db.cursor() - -c.execute('select id, name, owner_id, group_type, needs_sponsor, user_can_remove, prerequisite_id, joinmsg from project_group;') -bool_dict = {0 : False, 1 : True} -print "Creating Groups..." -admin = People.by_username('admin') -admin_id = admin.id -for group in c.fetchall(): - (id, name, owner_id, group_type, needs_sponsor, user_can_remove, prerequisite_id, joinmsg) = group - print "%i - %s" % (id, name) - try: - group = Groups.by_id(id) - #if prerequisite_id: - # group.prerequisite = Groups.by_id(prerequisite_id) - session.flush() - except IntegrityError, e: - print "\tERROR - The group: '%s' (%i) could not be created - %s" % (name, id, e) - except FlushError, e: - print "\tERROR - The group: '%s' (%i) could not be created - %s" % (name, id, e) - except InvalidRequestError, e: - print "\tERROR - The group: '%s' (%i) could not be created - %s" % (name, id, e) - - session.close() - -c.execute('select person_id, project_group_id, role_type, role_domain, role_status, internal_comments, sponsor_id, creation, approval from role order by person_id;') -print "Creating Role Maps..." -for role in c.fetchall(): - (person_id, project_group_id, role_type, role_domain, role_status, internal_comments, sponsor_id, creation, approval) = role - print "%s - %s" % (person_id, project_group_id) - try: - role = PersonRoles.query.filter_by(person_id=person_id, group_id=project_group_id) - if role_status == 'declined': - ''' No longer exists ''' - continue - # Do we need to do weird stuff to convert from no time zone to time zone? - role.creation = creation - session.flush() - except ProgrammingError, e: - print "\tERROR - The role %s -> %s could not be created - %s" % (person_id, project_group_id, e) - session.close() - except IntegrityError, e: - if e.message.find('dupilcate key'): - print "\tERROR - The role %s -> %s already exists! Skipping" % (person_id, project_group_id) - session.close() - continue - print "\tERROR - The role %s -> %s could not be created - %s" % (person_id, project_group_id, e) - session.close()