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.

This commit is contained in:
Ricky Zhou (周家杰) 2008-03-07 00:19:19 -05:00
parent ea0b7949b8
commit 3d687e8e54
7 changed files with 191 additions and 29 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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)

View file

@ -0,0 +1,30 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="../../master.html" />
<head>
<title>${_('Add Email')}</title>
</head>
<body>
<h2>${_('Add Email')}</h2>
<form action="${tg.url('/user/email/save/%s' % target.username)}" method="post" enctype="multipart/form-data">
<div class="field">
<label for="email">${_('Email')}:</label>
<input type="text" id="email" name="email" />
<!-- TODO: More generic documentation for adding an email -->
<script type="text/javascript">var hb1 = new HelpBalloon({dataURL: '${tg.url('/help/get_help/user_primary_email')}'});</script>
</div>
<div class="field">
<label for="description">${_('Description')}:</label>
<input type="text" id="description" name="description" />
<!-- TODO: Correct documentation for this -->
<script type="text/javascript">var hb1 = new HelpBalloon({dataURL: '${tg.url('/help/get_help/user_primary_email')}'});</script>
</div>
<div class="field">
<input type="submit" value="${_('Save!')}" />
<a href="${tg.url('/user/email/manage')}">${_('Cancel')}</a>
</div>
</form>
</body>
</html>

View file

@ -7,7 +7,7 @@
<title>${_('Manage Emails')}</title>
</head>
<body>
<h2>${_('Managing Emails for %s') % person.username}</h2>
<h2>${_('Managing Emails for %s') % target.username}</h2>
<h3>Available Emails</h3>
<table>
<thead>
@ -18,7 +18,7 @@
</tr>
</thead>
<tbody>
<tr py:for="email in person.person_emails">
<tr py:for="email in target.person_emails.values()">
<td><a href="mailto:${email.email}">${email.email}</a></td>
<td>${email.description}</td>
<td py:if="email.verified"><span class="approved">${_('Verified')}</span></td>
@ -36,7 +36,7 @@
</tr>
</thead>
<tbody>
<tr py:for="purpose in person.email_purposes.values()">
<tr py:for="purpose in target.email_purposes.values()">
<td><a href="mailto:${purpose.email}">${purpose.email}</a></td>
<td>${purpose.person_email.description}</td>
<td>${purpose.purpose}</td>

View file

@ -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

View file

@ -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()