Merge branch 'master' of ssh://git.fedorahosted.org/git/fedora-infrastructure

This commit is contained in:
Michael McGrath 2008-03-06 23:28:46 -06:00
commit 486f631b27
10 changed files with 203 additions and 41 deletions

View file

@ -1,14 +1,6 @@
Things to Fix in FAS2 before declaring it done Things to Fix in FAS2 before declaring it done:
* fasClient.py: Proper logging
safasprovider.py Nice-to-have things:
---------------- * fas/group.py: Easy searching within groups (and sponsor/admin interface)
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.
fasClient.py
---------------
Proper logging

View file

@ -54,7 +54,7 @@ tg.strict_parameters = True
server.webpath='/accounts' server.webpath='/accounts'
base_url_filter.on=True 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 # Make the session cookie only return to the host over an SSL link
# Disabled for testing. # Disabled for testing.

View file

@ -314,7 +314,7 @@ class Groups(SABase):
A class method that can be used to search groups A class method that can be used to search groups
based on their email addresses since it is unique. based on their email addresses since it is unique.
''' '''
return cls.query.join(['group_email_purposes', 'group_email']).filter_by(email=email).one() return cls.query.join(['email_purposes', 'group_email']).filter_by(email=email).one()
by_email_address = classmethod(by_email_address) by_email_address = classmethod(by_email_address)
@ -423,15 +423,15 @@ mapper(People, PeopleTable, properties = {
'email_purposes': relation(EmailPurposes, backref = 'person', 'email_purposes': relation(EmailPurposes, backref = 'person',
collection_class = column_mapped_collection( collection_class = column_mapped_collection(
EmailPurposesTable.c.purpose)), EmailPurposesTable.c.purpose)),
'person_emails': relation(PersonEmails, backref = 'person',
collection_class = column_mapped_collection(
PersonEmailsTable.c.email)),
'approved_roles': relation(ApprovedRoles, backref='member', 'approved_roles': relation(ApprovedRoles, backref='member',
primaryjoin = PeopleTable.c.id==ApprovedRoles.c.person_id), primaryjoin = PeopleTable.c.id==ApprovedRoles.c.person_id),
'unapproved_roles': relation(UnApprovedRoles, backref='member', 'unapproved_roles': relation(UnApprovedRoles, backref='member',
primaryjoin = PeopleTable.c.id==UnApprovedRoles.c.person_id) primaryjoin = PeopleTable.c.id==UnApprovedRoles.c.person_id)
}) })
mapper(PersonEmails, PersonEmailsTable, properties = { mapper(PersonEmails, PersonEmailsTable)
'person': relation(People, backref = 'person_emails', uselist = False,
primaryjoin = PeopleTable.c.id==PersonEmailsTable.c.person_id)
})
mapper(EmailPurposes, EmailPurposesTable, properties = { mapper(EmailPurposes, EmailPurposesTable, properties = {
'person_email': relation(PersonEmails, uselist = False, 'person_email': relation(PersonEmails, uselist = False,
primaryjoin = PersonEmailsTable.c.id==EmailPurposesTable.c.email_id) primaryjoin = PersonEmailsTable.c.id==EmailPurposesTable.c.email_id)
@ -452,13 +452,13 @@ mapper(Groups, GroupsTable, properties = {
'email_purposes': relation(GroupEmailPurposes, backref = 'group', 'email_purposes': relation(GroupEmailPurposes, backref = 'group',
collection_class = column_mapped_collection( collection_class = column_mapped_collection(
GroupEmailPurposesTable.c.purpose)), GroupEmailPurposesTable.c.purpose)),
'group_emails': relation(GroupEmails, backref = 'group',
collection_class = column_mapped_collection(
GroupEmailsTable.c.email)),
'prerequisite': relation(Groups, uselist=False, 'prerequisite': relation(Groups, uselist=False,
primaryjoin = GroupsTable.c.prerequisite_id==GroupsTable.c.id) primaryjoin = GroupsTable.c.prerequisite_id==GroupsTable.c.id)
}) })
mapper(GroupEmails, GroupEmailsTable, properties = { mapper(GroupEmails, GroupEmailsTable)
'group': relation(Groups, backref = 'group_emails', uselist = False,
primaryjoin = GroupsTable.c.id==GroupEmailsTable.c.group_id)
})
mapper(GroupEmailPurposes, GroupEmailPurposesTable, properties = { mapper(GroupEmailPurposes, GroupEmailPurposesTable, properties = {
'group_email': relation(GroupEmails, uselist = False, 'group_email': relation(GroupEmails, uselist = False,
primaryjoin = GroupEmailsTable.c.id==GroupEmailPurposesTable.c.email_id) primaryjoin = GroupEmailsTable.c.id==GroupEmailPurposesTable.c.email_id)

View file

@ -2,7 +2,7 @@
<h3>The Fedora Project <h3>The Fedora Project
Individual Contributor License Agreement (CLA) Individual Contributor License Agreement (CLA)
</h3> </h3>
<a href="http://www.fedora.redhat.com/licenses/">http://www.fedora.redhat.com/licenses/</a> <a href="http://fedoraproject.org/wiki/Legal/Licenses/CLA">http://fedoraproject.org/wiki/Legal/Licenses/CLA</a>
<p> <p>
Thank you for your interest in The Fedora Project (the "Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, Red hat, Inc. ("Red Hat"), as maintainer of the Project, must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for Your protection as a Contributor as well as the protection of the Project and its users; it does not change your rights to use your own Contributions for any other purpose. Thank you for your interest in The Fedora Project (the "Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, Red hat, Inc. ("Red Hat"), as maintainer of the Project, must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for Your protection as a Contributor as well as the protection of the Project and its users; it does not change your rights to use your own Contributions for any other purpose.
</p> </p>

View file

@ -1,6 +1,6 @@
The Fedora Project The Fedora Project
Individual Contributor License Agreement (CLA) Individual Contributor License Agreement (CLA)
http://www.fedora.redhat.com/licenses/ http://fedoraproject.org/wiki/Legal/Licenses/CLA
Thank you for your interest in The Fedora Project (the Thank you for your interest in The Fedora Project (the
"Project"). In order to clarify the intellectual property license "Project"). In order to clarify the intellectual property license

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> <title>${_('Manage Emails')}</title>
</head> </head>
<body> <body>
<h2>${_('Managing Emails for %s') % person.username}</h2> <h2>${_('Managing Emails for %s') % target.username}</h2>
<h3>Available Emails</h3> <h3>Available Emails</h3>
<table> <table>
<thead> <thead>
@ -18,7 +18,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <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><a href="mailto:${email.email}">${email.email}</a></td>
<td>${email.description}</td> <td>${email.description}</td>
<td py:if="email.verified"><span class="approved">${_('Verified')}</span></td> <td py:if="email.verified"><span class="approved">${_('Verified')}</span></td>
@ -36,7 +36,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <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><a href="mailto:${purpose.email}">${purpose.email}</a></td>
<td>${purpose.person_email.description}</td> <td>${purpose.person_email.description}</td>
<td>${purpose.purpose}</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 from turbogears.database import session
import cherrypy import cherrypy
import turbomail
import os import os
import re import re
import gpgme import gpgme
@ -221,7 +223,8 @@ class User(controllers.Controller):
target = person target = person
if not canEditUser(person, target): if not canEditUser(person, target):
turbogears.flash(_('You cannot edit %s') % target.username ) 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) return dict(target=target)
@identity.require(turbogears.identity.not_anonymous()) @identity.require(turbogears.identity.not_anonymous())
@ -236,7 +239,7 @@ class User(controllers.Controller):
if not canEditUser(person, target): if not canEditUser(person, target):
turbogears.flash(_("You do not have permission to edit '%s'") % target.username) 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() return dict()
try: try:
target.human_name = human_name target.human_name = human_name
@ -286,7 +289,6 @@ class User(controllers.Controller):
# Also, perhaps implement a timeout- delete account # Also, perhaps implement a timeout- delete account
# if the e-mail is not verified (i.e. the person changes # if the e-mail is not verified (i.e. the person changes
# their password) withing X days. # their password) withing X days.
import turbomail
try: try:
person = People() person = People()
person.username = username person.username = username
@ -296,6 +298,7 @@ class User(controllers.Controller):
person.status = 'active' person.status = 'active'
session.flush() 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 = PersonEmails()
person_email.email = email person_email.email = email
person_email.person = person person_email.person = person

View file

@ -3,11 +3,16 @@ from turbogears import controllers, expose, paginate, identity, redirect, widget
from turbogears.database import session from turbogears.database import session
import cherrypy import cherrypy
import turbomail
import random
from fas.model import People from fas.model import People
from fas.model import PersonEmails from fas.model import PersonEmails
from fas.model import EmailPurposes from fas.model import EmailPurposes
from fas.model import Log from fas.model import Log
from fas.auth import *
class NonFedoraEmail(validators.FancyValidator): class NonFedoraEmail(validators.FancyValidator):
'''Make sure that an email address is not @fedoraproject.org''' '''Make sure that an email address is not @fedoraproject.org'''
def _to_python(self, value, state): def _to_python(self, value, state):
@ -16,13 +21,20 @@ class NonFedoraEmail(validators.FancyValidator):
if value.endswith('@fedoraproject.org'): if value.endswith('@fedoraproject.org'):
raise validators.Invalid(_("To prevent email loops, your email address cannot be @fedoraproject.org."), value, state) 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( email = validators.All(
validators.Email(not_empty=True, strip=True), validators.Email(not_empty=True, strip=True),
NonFedoraEmail(not_empty=True, strip=True), NonFedoraEmail(not_empty=True, strip=True),
) )
#fedoraPersonBugzillaMail = validators.Email(strip=True) description = validators.String(not_empty=True, max=512)
postal_address = validators.String(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): class Email(controllers.Controller):
@ -34,7 +46,7 @@ class Email(controllers.Controller):
def index(self): def index(self):
'''Redirect to manage '''Redirect to manage
''' '''
turbogears.redirect('/user/email/manage/%s' % turbogears.identity.current.user_name) turbogears.redirect('/user/email/manage')
@expose(template="fas.templates.error") @expose(template="fas.templates.error")
@ -48,12 +60,135 @@ class Email(controllers.Controller):
#@validate(validators=UserView()) #@validate(validators=UserView())
@error_handler(error) @error_handler(error)
@expose(template="fas.templates.user.email.manage", allow_json=True) @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: # TODO: Some sort of auth checking - other people should
username = turbogears.identity.current.user_name # 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) 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()

View file

@ -71,11 +71,12 @@ cluster people_status_idx on people;
CREATE TABLE person_emails ( CREATE TABLE person_emails (
id serial primary key, id serial primary key,
email text not null unique, email text not null,
person_id INTEGER NOT NULL references people(id), person_id INTEGER NOT NULL references people(id),
validtoken text, validtoken text,
description text, description text,
verified boolean NOT NULL DEFAULT false verified boolean NOT NULL DEFAULT false
unique (email, verified) --You can't "claim" an email before you verify it first
); );
create index person_emails_person_id_idx on person_emails(person_id); create index person_emails_person_id_idx on person_emails(person_id);
@ -141,11 +142,12 @@ cluster groups_group_type_idx on groups;
-- --
CREATE TABLE group_emails ( CREATE TABLE group_emails (
id serial primary key, id serial primary key,
email text not null unique, email text not null,
group_id INTEGER NOT NULL references groups(id), group_id INTEGER NOT NULL references groups(id),
validtoken text, validtoken text,
description text, description text,
verified boolean NOT NULL DEFAULT false verified boolean NOT NULL DEFAULT false
unique (email, verified) --You can't "claim" an email before you verify it first
); );
create index group_emails_group_id_idx on group_emails(group_id); create index group_emails_group_id_idx on group_emails(group_id);