Woohoo, click-through CLA!

This commit is contained in:
Ricky Zhou (周家杰) 2008-03-14 13:05:01 -04:00
parent 6cf5c25e1e
commit 6e5e46d29e
4 changed files with 81 additions and 135 deletions

View file

@ -6,11 +6,12 @@ import cherrypy
from datetime import datetime
import re
import gpgme
import StringIO
import subprocess
import turbomail
from genshi.template import TemplateLoader
from genshi.template import TextTemplate
from fas.model import People
from fas.model import Log
from fas.auth import *
class CLA(controllers.Controller):
@ -39,27 +40,31 @@ class CLA(controllers.Controller):
turbogears.redirect('/')
return dict(tg_errors=tg_errors)
@identity.require(turbogears.identity.not_anonymous())
@error_handler(error)
@expose(template="genshi-text:fas.templates.cla.cla", format="text", content_type='text/plain; charset=utf-8')
def text(self, type=None):
'''View CLA as text'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
return dict(person=person, date=datetime.utcnow().ctime())
@identity.require(turbogears.identity.not_anonymous())
@error_handler(error)
@expose(template="fas.templates.cla.view")
def view(self, type=None):
def view(self):
'''View CLA'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
if not person.telephone or \
not person.postal_address or \
not person.gpg_keyid:
turbogears.flash(_('To sign the CLA we must have your telephone number, postal address and GPG key ID. Please ensure they have been filled out.'))
not person.postal_address:
turbogears.flash(_('To sign the CLA we must have your telephone number and postal address. Please ensure they have been filled out.'))
turbogears.redirect('/user/edit/%s' % username)
if type == 'sign':
if CLADone(person):
turbogears.flash(_('You have already signed the CLA.'))
turbogears.redirect('/cla/')
return dict()
elif type != None:
turbogears.redirect('/cla/')
return dict()
return dict(type=type, person=person, date=datetime.utcnow().ctime())
return dict(person=person, date=datetime.utcnow().ctime())
@identity.require(turbogears.identity.not_anonymous())
@error_handler(error)
@ -73,7 +78,7 @@ class CLA(controllers.Controller):
@identity.require(turbogears.identity.not_anonymous())
@error_handler(error)
@expose(template="fas.templates.cla.index")
def sign(self, signature):
def sign(self, agree=False):
'''Sign CLA'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
@ -84,65 +89,9 @@ class CLA(controllers.Controller):
return dict()
groupname = config.get('cla_fedora_group')
group = Groups.by_name(groupname)
ctx = gpgme.Context()
data = StringIO.StringIO(signature.file.read())
plaintext = StringIO.StringIO()
verified = False
keyid = re.sub('\s', '', person.gpg_keyid)
ret = subprocess.call([config.get('gpgexec'), '--keyserver', config.get('gpg_keyserver'), '--recv-keys', keyid])
if ret != 0:
turbogears.flash(_("Your key could not be retrieved from subkeys.pgp.net"))
turbogears.redirect('/cla/view/sign')
return dict()
#try:
# subprocess.check_call([config.get('gpgexec'), '--keyserver', config.get('gpg_keyserver'), '--recv-keys', keyid])
#except subprocess.CalledProcessError:
# turbogears.flash(_("Your key could not be retrieved from subkeys.pgp.net"))
# turbogears.redirect('/cla/view/sign')
# return dict()
else:
try:
sigs = ctx.verify(data, None, plaintext)
except gpgme.GpgmeError, e:
turbogears.flash(_("Your signature could not be verified: '%s'.") % e)
turbogears.redirect('/cla/view/sign')
return dict()
else: # Hm, I wonder how these nested ifs can be made more elegant...
if len(sigs):
sig = sigs[0]
# This might still assume a full fingerprint.
key = ctx.get_key(keyid)
fpr = key.subkeys[0].fpr
if sig.fpr != fpr:
turbogears.flash(_("Your signature's fingerprint did not match the fingerprint registered in FAS."))
turbogears.redirect('/cla/view/sign')
return dict()
emails = [];
for uid in key.uids:
emails.extend([uid.email])
if person.email in emails:
verified = True
else:
turbogears.flash(_('Your key did not match your email.'))
turbogears.redirect('/cla/view/sign')
return dict()
else:
# TODO: Find out what it means if verify() succeeded and len(sigs) == 0
turbogears.flash(_('len(sigs) == 0'))
turbogears.redirect('/cla/view/sign')
return dict()
# We got a properly signed CLA.
cla = plaintext.getvalue()
if cla.find('Contributor License Agreement (CLA)') < 0:
turbogears.flash(_('The GPG-signed part of the message did not contain a signed CLA.'))
turbogears.redirect('/cla/view/sign')
return dict()
if re.compile('If you agree to these terms and conditions, type "I agree" here: I agree', re.IGNORECASE).match(cla):
turbogears.flash(_('The text "I agree" was not found in the CLA.'))
turbogears.redirect('/cla/view/sign')
return dict()
if not agree:
turbogears.flash(_("You have not agreed to the CLA."))
turbogears.redirect('/cla/')
try:
# Everything is correct.
person.apply(group, person) # Apply...
@ -153,16 +102,31 @@ class CLA(controllers.Controller):
# to remove themselves from the CLA group before they can
# sign the CLA and go through the above try block again.
turbogears.flash(_("You could not be added to the '%s' group.") % group.name)
turbogears.redirect('/cla/view/sign')
turbogears.redirect('/cla/')
return dict()
else:
dt = datetime.utcnow()
Log(author_id=person.id, description='Signed CLA', changetime=dt)
message = turbomail.Message(config.get('accounts_email'), config.get('legal_cla_email'), 'Fedora ICLA completed')
message.plain = '''
Fedora user %(username)s has signed a completed ICLA using their published GPG key, ID %(gpg_keyid)s,
that is associated with e-mail address %(email)s. The full signed ICLA is attached.
''' % {'username': person.username, 'gpg_keyid': person.gpg_keyid, 'email': person.email}
signature.file.seek(0) # For another read()
message.attach(signature.file, signature.filename)
Fedora user %(username)s has signed a completed ICLA (below).
Username: %(username)s
Email: %(email)s
Date: %(date)s
=== CLA ===
''' % {'username': person.username,
'human_name': person.human_name,
'email': person.email,
'postal_address': person.postal_address,
'telephone': person.telephone,
'facsimile': person.facsimile,
'date': dt.ctime(),}
# Sigh.. if only there were a nicer way.
loader = TemplateLoader('fas/templates/cla')
template = loader.load('cla.txt', cls=TextTemplate)
message.plain += template.generate(person=person).render('text')
turbomail.enqueue(message)
turbogears.flash(_("You have successfully signed the CLA. You are now in the '%s' group.") % group.name)
turbogears.redirect('/cla/')

View file

@ -27,14 +27,14 @@
fedora-legal@redhat.com. Please read this document carefully before
signing and keep a copy for your records.
Full name: ${'%28s' % person.human_name} E-Mail: ${'%17s' % person.email}
Full name: ${person.human_name} E-Mail: ${person.email}
Address:
${person.postal_address}
Telephone: ${person.telephone}
Facsimile: %(facsimile)s
Facsimile: ${person.facsimile}
You and the Project hereby accept and agree to the following terms and conditions:
@ -143,12 +143,3 @@ ${person.postal_address}
inaccurate in any respect.
9. The Project is under no obligations to accept and include every contribution.
If you agree to these terms and conditions, type "I agree" here:
Enter your full name here:
E-mail: ${person.email}
Date: ${date}

View file

@ -8,18 +8,20 @@
</head>
<body>
<h2>${_('Fedora Contributor License Agreement')}</h2>
<p>
<!-- TODO: Better text there -->
${Markup(_('In order to become a full Fedora contributor, you must sign the CLA.'))}
</p>
<br/>
<p>
<ul py:if="not cla">
<li><a href="${tg.url('/cla/view/sign')}">${_('Sign Contributor License Agreement (CLA)')}</a></li>
</ul>
</p>
<p py:if="cla">
${Markup(_('You have already sucessfully signed the &lt;a href="%s"&gt;CLA&lt;/a&gt;.') % tg.url('/cla/view'))}
${Markup(_('You have already sucessfully signed the &lt;a href="%(url)s"&gt;CLA&lt;/a&gt; (&lt;a href="%(url)s"&gt;text&lt;/a&gt;).') % {'url': tg.url('/cla/view')})}
</p>
<py:if test="not cla">
<p>
${_('Insert legal text here')}
</p>
${Markup(_('&lt;a href="%(url)s"&gt;View the CLA&lt;/a&gt; (&lt;a href="%(url)s"&gt;text&lt;/a&gt;).') % {'url': tg.url('/cla/view')})}
<form action="${tg.url('/cla/sign')}" method="post">
<div>
<input type="submit" id="agree" name="agree" value="${_('I agree')}" />
<input type="submit" value="${_('I do not agree')}" />
</div>
</form>
</py:if>
</body>
</html>

View file

@ -8,23 +8,12 @@
</head>
<body>
<h2>${_('Contributor License Agreement')}</h2>
<py:if test="not type">
<p>
<a href="${tg.url('/cla/')}">${_('Return to the signing page.')}</a>
</p>
<xi:include href="cla.html" />
</py:if>
<py:if test="type == 'sign'">
<p>
${Markup(_('Use the below link to download/save the CLA as fedora-icla-%(username)s.txt, and run: &lt;pre&gt;gpg -as fedora-icla-%(username)s.txt&lt;/pre&gt; After, upload fedora-icla-%(username)s.txt.asc in the form below.') % {'username': person.username})}
<a href="${tg.url('/cla/')}">${_('Return to the signing page.')}</a>
</p>
<p>
<a href="${tg.url('/cla/download')}">${_('Download the CLA text file here!')}</a>
</p>
<form action="${tg.url('/cla/sign')}" method="post" enctype="multipart/form-data">
<div>
<label for="signature">${_('Signed CLA:')}</label> <input type="file" id="signature" name="signature" /><br />
<input type="submit" value="${_('Submit CLA')}" />
</div>
</form>
</py:if>
</body>
</html>