From 3d687e8e54fb13530c15b2304acb19342e036c7e Mon Sep 17 00:00:00 2001 From: Ricky Zhou Date: Fri, 7 Mar 2008 00:19:19 -0500 Subject: [PATCH] Beginning of email verification. Just need to clean up, add field validation, and allow users to set {primary, bugzilla, ...} emails to stuff from the person_email table. --- fas/TODO | 12 +- fas/dev.cfg | 2 +- fas/fas/model.py | 8 +- fas/fas/templates/user/email/add.html | 30 +++++ fas/fas/templates/user/email/manage.html | 6 +- fas/fas/user.py | 9 +- fas/fas/user_email.py | 153 +++++++++++++++++++++-- 7 files changed, 191 insertions(+), 29 deletions(-) create mode 100644 fas/fas/templates/user/email/add.html diff --git a/fas/TODO b/fas/TODO index 5413554..ce80c2d 100644 --- a/fas/TODO +++ b/fas/TODO @@ -1,10 +1,4 @@ -Things to Fix in FAS2 before declaring it done +Things to Fix in FAS2 before declaring it done: - -safasprovider.py ----------------- -validate_password(): - We'll want to change this to something that allows for longer passwords - (like md5). The one thing about that is we have to figure out how system - passwords use salt, etc. That way we'll be able to use this with - make_shell_accounts. +Nice-to-have things: + * Easy searching within groups (and sponsor/admin interface) diff --git a/fas/dev.cfg b/fas/dev.cfg index 417e454..1347860 100644 --- a/fas/dev.cfg +++ b/fas/dev.cfg @@ -54,7 +54,7 @@ tg.strict_parameters = True server.webpath='/accounts' base_url_filter.on=True -base_url_filter.base_url = "https://publictest10.fedoraproject.org/accounts" +base_url_filter.base_url = "https://publictest10.fedoraproject.org" # 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 c108d1b..14a72fa 100644 --- a/fas/fas/model.py +++ b/fas/fas/model.py @@ -423,15 +423,15 @@ mapper(People, PeopleTable, properties = { 'email_purposes': relation(EmailPurposes, backref = 'person', collection_class = column_mapped_collection( EmailPurposesTable.c.purpose)), + 'person_emails': relation(PersonEmails, backref = 'person', + collection_class = column_mapped_collection( + PersonEmailsTable.c.email)), 'approved_roles': relation(ApprovedRoles, backref='member', primaryjoin = PeopleTable.c.id==ApprovedRoles.c.person_id), 'unapproved_roles': relation(UnApprovedRoles, backref='member', primaryjoin = PeopleTable.c.id==UnApprovedRoles.c.person_id) }) -mapper(PersonEmails, PersonEmailsTable, properties = { - 'person': relation(People, backref = 'person_emails', uselist = False, - primaryjoin = PeopleTable.c.id==PersonEmailsTable.c.person_id) - }) +mapper(PersonEmails, PersonEmailsTable) mapper(EmailPurposes, EmailPurposesTable, properties = { 'person_email': relation(PersonEmails, uselist = False, primaryjoin = PersonEmailsTable.c.id==EmailPurposesTable.c.email_id) diff --git a/fas/fas/templates/user/email/add.html b/fas/fas/templates/user/email/add.html new file mode 100644 index 0000000..9f063a3 --- /dev/null +++ b/fas/fas/templates/user/email/add.html @@ -0,0 +1,30 @@ + + + + + ${_('Add Email')} + + +

${_('Add Email')}

+
+
+ + + + +
+
+ + + + +
+ +
+ + diff --git a/fas/fas/templates/user/email/manage.html b/fas/fas/templates/user/email/manage.html index 0d449b1..639df7d 100644 --- a/fas/fas/templates/user/email/manage.html +++ b/fas/fas/templates/user/email/manage.html @@ -7,7 +7,7 @@ ${_('Manage Emails')} -

${_('Managing Emails for %s') % person.username}

+

${_('Managing Emails for %s') % target.username}

Available Emails

@@ -18,7 +18,7 @@ - + @@ -36,7 +36,7 @@ - + diff --git a/fas/fas/user.py b/fas/fas/user.py index a86173b..560f23b 100644 --- a/fas/fas/user.py +++ b/fas/fas/user.py @@ -3,6 +3,8 @@ from turbogears import controllers, expose, paginate, identity, redirect, widget from turbogears.database import session import cherrypy +import turbomail + import os import re import gpgme @@ -221,7 +223,8 @@ class User(controllers.Controller): target = person if not canEditUser(person, target): turbogears.flash(_('You cannot edit %s') % target.username ) - username = turbogears.identity.current.username + turbogears.redirect('/user/view/%s', target.username) + return dict() return dict(target=target) @identity.require(turbogears.identity.not_anonymous()) @@ -236,7 +239,7 @@ class User(controllers.Controller): if not canEditUser(person, target): turbogears.flash(_("You do not have permission to edit '%s'") % target.username) - turbogears.redirect('/user/edit/%s', target.username) + turbogears.redirect('/user/view/%s', target.username) return dict() try: target.human_name = human_name @@ -286,7 +289,6 @@ class User(controllers.Controller): # Also, perhaps implement a timeout- delete account # if the e-mail is not verified (i.e. the person changes # their password) withing X days. - import turbomail try: person = People() person.username = username @@ -296,6 +298,7 @@ class User(controllers.Controller): person.status = 'active' session.flush() + # TODO: Handle properly if email has already been used. This might be painful, since the person already exists, at this point. person_email = PersonEmails() person_email.email = email person_email.person = person diff --git a/fas/fas/user_email.py b/fas/fas/user_email.py index 2ef95c7..415a4cd 100644 --- a/fas/fas/user_email.py +++ b/fas/fas/user_email.py @@ -3,11 +3,16 @@ from turbogears import controllers, expose, paginate, identity, redirect, widget from turbogears.database import session import cherrypy +import turbomail +import random + from fas.model import People from fas.model import PersonEmails from fas.model import EmailPurposes from fas.model import Log +from fas.auth import * + class NonFedoraEmail(validators.FancyValidator): '''Make sure that an email address is not @fedoraproject.org''' def _to_python(self, value, state): @@ -16,13 +21,20 @@ class NonFedoraEmail(validators.FancyValidator): if value.endswith('@fedoraproject.org'): raise validators.Invalid(_("To prevent email loops, your email address cannot be @fedoraproject.org."), value, state) -class EmailCreate(validators.Schema): +class EmailSave(validators.Schema): email = validators.All( validators.Email(not_empty=True, strip=True), NonFedoraEmail(not_empty=True, strip=True), ) - #fedoraPersonBugzillaMail = validators.Email(strip=True) - postal_address = validators.String(max=512) + description = validators.String(not_empty=True, max=512) + +def generate_validtoken(length=32): + ''' Generate Validation Token ''' + chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + token = '' + for i in xrange(length): + token += random.choice(chars) + return token class Email(controllers.Controller): @@ -34,7 +46,7 @@ class Email(controllers.Controller): def index(self): '''Redirect to manage ''' - turbogears.redirect('/user/email/manage/%s' % turbogears.identity.current.user_name) + turbogears.redirect('/user/email/manage') @expose(template="fas.templates.error") @@ -48,12 +60,135 @@ class Email(controllers.Controller): #@validate(validators=UserView()) @error_handler(error) @expose(template="fas.templates.user.email.manage", allow_json=True) - def manage(self, username=None): + def manage(self, targetname=None): ''' - Manage a person's emails. + Manage a person's emails ''' - if not username: - username = turbogears.identity.current.user_name + # TODO: Some sort of auth checking - other people should + # probably be limited to looking at a person's email through + # /user/view, although admins should probably be able to set + # emails (with/without verification?) + username = turbogears.identity.current.user_name person = People.by_username(username) - return dict(person=person) + + if targetname: + target = People.by_username(targetname) + else: + target = person + + return dict(target=target) + + @identity.require(turbogears.identity.not_anonymous()) + #@validate(validators=UserView()) + @error_handler(error) + @expose(template="fas.templates.user.email.add", allow_json=True) + def add(self, targetname=None): + ''' + Display the form to add an email + ''' + username = turbogears.identity.current.user_name + person = People.by_username(username) + + if targetname: + target = People.by_username(targetname) + else: + target = person + + if not canEditUser(person, target): + turbogears.flash(_('You cannot edit %s') % target.username ) + turbogears.redirect('/user/email/manage') + return dict() + + return dict(target=target) + + @identity.require(turbogears.identity.not_anonymous()) + @validate(validators=EmailSave()) + @error_handler(error) + @expose(template="fas.templates.user.email.add", allow_json=True) + def save(self, targetname, email, description): + ''' + Display the form to add an email + ''' + username = turbogears.identity.current.user_name + person = People.by_username(username) + + if targetname: + target = People.by_username(targetname) + else: + target = person + + if not canEditUser(person, target): + turbogears.flash(_('You cannot edit %s') % target.username ) + turbogears.redirect('/user/email/manage') + return dict() + + validtoken = generate_validtoken() + + try: + person_email = PersonEmails() + person_email.email = email + person_email.person = target + person_email.description = description + person_email.validtoken = validtoken + session.flush() + # Hmm, should this be checked in the validator or here? + except IntegrityError: + turbogears.flash(_('The email \'%s\' is already in used.') % email) + return dict(target=target) + else: + # TODO: Make this email more friendly. Maybe escape the @ in email too? + validurl = config.get('base_url_filter.base_url') + turbogears.url('/user/email/verify/%s/%s/%s') % (target.username, email, validtoken) + message = turbomail.Message(config.get('accounts_mail'), email, _('Confirm this email address')) + message.plain = _(''' +Go to this URL to verify that you own this email address: %s +''') % validurl + turbomail.enqueue(message) + turbogears.flash(_('Your email has been added. Before you can use this email, you must verify it. The email you added should receive a message with instructions shortly.')) + + return dict(target=target) + + return dict(target=target) + + @identity.require(turbogears.identity.not_anonymous()) + # TODO: Validation! + #@validate(validators=UserView()) + @error_handler(error) + @expose(allow_json=True) + def verify(self, targetname, email, validtoken): + ''' + Verify an email + ''' + username = turbogears.identity.current.user_name + person = People.by_username(username) + + if targetname: + target = People.by_username(targetname) + else: + target = person + + if not canEditUser(person, target): + turbogears.flash(_('You cannot edit %s') % target.username ) + turbogears.redirect('/user/email/manage') + return dict() + + if target.person_emails[email].verified: + turbogears.flash(_('The email provided has already been verified.')) + turbogears.redirect('/user/email/manage') + return dict() + + try: + if target.person_emails[email].validtoken == validtoken: + target.person_emails[email].validtoken = '' + target.person_emails[email].verified = True + turbogears.flash(_('Your email has been successfully verified.')) + turbogears.redirect('/user/email/manage') + return dict() + else: + turbogears.flash(_('The verification string did not match.')) + turbogears.redirect('/user/email/manage') + return dict() + except KeyError: + turbogears.flash(_('No such email is associated with your user.')) + turbogears.redirect('/user/email/manage') + return dict()
${email.email} ${email.description} ${_('Verified')}
${purpose.email} ${purpose.person_email.description} ${purpose.purpose}