Woohoo, click-through CLA!
This commit is contained in:
parent
6cf5c25e1e
commit
6e5e46d29e
4 changed files with 81 additions and 135 deletions
154
fas/fas/cla.py
154
fas/fas/cla.py
|
@ -6,11 +6,12 @@ import cherrypy
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import re
|
import re
|
||||||
import gpgme
|
|
||||||
import StringIO
|
|
||||||
import subprocess
|
|
||||||
import turbomail
|
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 *
|
from fas.auth import *
|
||||||
|
|
||||||
class CLA(controllers.Controller):
|
class CLA(controllers.Controller):
|
||||||
|
@ -39,27 +40,31 @@ class CLA(controllers.Controller):
|
||||||
turbogears.redirect('/')
|
turbogears.redirect('/')
|
||||||
return dict(tg_errors=tg_errors)
|
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())
|
@identity.require(turbogears.identity.not_anonymous())
|
||||||
@error_handler(error)
|
@error_handler(error)
|
||||||
@expose(template="fas.templates.cla.view")
|
@expose(template="fas.templates.cla.view")
|
||||||
def view(self, type=None):
|
def view(self):
|
||||||
'''View CLA'''
|
'''View CLA'''
|
||||||
username = turbogears.identity.current.user_name
|
username = turbogears.identity.current.user_name
|
||||||
person = People.by_username(username)
|
person = People.by_username(username)
|
||||||
if not person.telephone or \
|
if not person.telephone or \
|
||||||
not person.postal_address or \
|
not person.postal_address:
|
||||||
not person.gpg_keyid:
|
turbogears.flash(_('To sign the CLA we must have your telephone number and postal address. Please ensure they have been filled out.'))
|
||||||
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.'))
|
|
||||||
turbogears.redirect('/user/edit/%s' % username)
|
turbogears.redirect('/user/edit/%s' % username)
|
||||||
if type == 'sign':
|
if CLADone(person):
|
||||||
if CLADone(person):
|
turbogears.flash(_('You have already signed the CLA.'))
|
||||||
turbogears.flash(_('You have already signed the CLA.'))
|
|
||||||
turbogears.redirect('/cla/')
|
|
||||||
return dict()
|
|
||||||
elif type != None:
|
|
||||||
turbogears.redirect('/cla/')
|
turbogears.redirect('/cla/')
|
||||||
return dict()
|
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())
|
@identity.require(turbogears.identity.not_anonymous())
|
||||||
@error_handler(error)
|
@error_handler(error)
|
||||||
|
@ -73,7 +78,7 @@ class CLA(controllers.Controller):
|
||||||
@identity.require(turbogears.identity.not_anonymous())
|
@identity.require(turbogears.identity.not_anonymous())
|
||||||
@error_handler(error)
|
@error_handler(error)
|
||||||
@expose(template="fas.templates.cla.index")
|
@expose(template="fas.templates.cla.index")
|
||||||
def sign(self, signature):
|
def sign(self, agree=False):
|
||||||
'''Sign CLA'''
|
'''Sign CLA'''
|
||||||
username = turbogears.identity.current.user_name
|
username = turbogears.identity.current.user_name
|
||||||
person = People.by_username(username)
|
person = People.by_username(username)
|
||||||
|
@ -84,87 +89,46 @@ class CLA(controllers.Controller):
|
||||||
return dict()
|
return dict()
|
||||||
groupname = config.get('cla_fedora_group')
|
groupname = config.get('cla_fedora_group')
|
||||||
group = Groups.by_name(groupname)
|
group = Groups.by_name(groupname)
|
||||||
|
if not agree:
|
||||||
ctx = gpgme.Context()
|
turbogears.flash(_("You have not agreed to the CLA."))
|
||||||
data = StringIO.StringIO(signature.file.read())
|
turbogears.redirect('/cla/')
|
||||||
plaintext = StringIO.StringIO()
|
try:
|
||||||
verified = False
|
# Everything is correct.
|
||||||
keyid = re.sub('\s', '', person.gpg_keyid)
|
person.apply(group, person) # Apply...
|
||||||
ret = subprocess.call([config.get('gpgexec'), '--keyserver', config.get('gpg_keyserver'), '--recv-keys', keyid])
|
session.flush()
|
||||||
if ret != 0:
|
person.sponsor(group, person) # Sponsor!
|
||||||
turbogears.flash(_("Your key could not be retrieved from subkeys.pgp.net"))
|
except:
|
||||||
turbogears.redirect('/cla/view/sign')
|
# TODO: If apply succeeds and sponsor fails, the user has
|
||||||
|
# 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/')
|
||||||
return dict()
|
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:
|
else:
|
||||||
try:
|
dt = datetime.utcnow()
|
||||||
sigs = ctx.verify(data, None, plaintext)
|
Log(author_id=person.id, description='Signed CLA', changetime=dt)
|
||||||
except gpgme.GpgmeError, e:
|
message = turbomail.Message(config.get('accounts_email'), config.get('legal_cla_email'), 'Fedora ICLA completed')
|
||||||
turbogears.flash(_("Your signature could not be verified: '%s'.") % e)
|
message.plain = '''
|
||||||
turbogears.redirect('/cla/view/sign')
|
Fedora user %(username)s has signed a completed ICLA (below).
|
||||||
return dict()
|
Username: %(username)s
|
||||||
else: # Hm, I wonder how these nested ifs can be made more elegant...
|
Email: %(email)s
|
||||||
if len(sigs):
|
Date: %(date)s
|
||||||
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):
|
=== CLA ===
|
||||||
turbogears.flash(_('The text "I agree" was not found in the CLA.'))
|
|
||||||
turbogears.redirect('/cla/view/sign')
|
''' % {'username': person.username,
|
||||||
return dict()
|
'human_name': person.human_name,
|
||||||
try:
|
'email': person.email,
|
||||||
# Everything is correct.
|
'postal_address': person.postal_address,
|
||||||
person.apply(group, person) # Apply...
|
'telephone': person.telephone,
|
||||||
session.flush()
|
'facsimile': person.facsimile,
|
||||||
person.sponsor(group, person) # Sponsor!
|
'date': dt.ctime(),}
|
||||||
except:
|
# Sigh.. if only there were a nicer way.
|
||||||
# TODO: If apply succeeds and sponsor fails, the user has
|
loader = TemplateLoader('fas/templates/cla')
|
||||||
# to remove themselves from the CLA group before they can
|
template = loader.load('cla.txt', cls=TextTemplate)
|
||||||
# sign the CLA and go through the above try block again.
|
message.plain += template.generate(person=person).render('text')
|
||||||
turbogears.flash(_("You could not be added to the '%s' group.") % group.name)
|
turbomail.enqueue(message)
|
||||||
turbogears.redirect('/cla/view/sign')
|
turbogears.flash(_("You have successfully signed the CLA. You are now in the '%s' group.") % group.name)
|
||||||
return dict()
|
turbogears.redirect('/cla/')
|
||||||
else:
|
return dict()
|
||||||
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)
|
|
||||||
turbomail.enqueue(message)
|
|
||||||
turbogears.flash(_("You have successfully signed the CLA. You are now in the '%s' group.") % group.name)
|
|
||||||
turbogears.redirect('/cla/')
|
|
||||||
return dict()
|
|
||||||
|
|
||||||
|
|
|
@ -27,14 +27,14 @@
|
||||||
fedora-legal@redhat.com. Please read this document carefully before
|
fedora-legal@redhat.com. Please read this document carefully before
|
||||||
signing and keep a copy for your records.
|
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:
|
Address:
|
||||||
${person.postal_address}
|
${person.postal_address}
|
||||||
|
|
||||||
Telephone: ${person.telephone}
|
Telephone: ${person.telephone}
|
||||||
|
|
||||||
Facsimile: %(facsimile)s
|
Facsimile: ${person.facsimile}
|
||||||
|
|
||||||
You and the Project hereby accept and agree to the following terms and conditions:
|
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.
|
inaccurate in any respect.
|
||||||
|
|
||||||
9. The Project is under no obligations to accept and include every contribution.
|
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}
|
|
||||||
|
|
|
@ -8,18 +8,20 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>${_('Fedora Contributor License Agreement')}</h2>
|
<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">
|
<p py:if="cla">
|
||||||
${Markup(_('You have already sucessfully signed the <a href="%s">CLA</a>.') % tg.url('/cla/view'))}
|
${Markup(_('You have already sucessfully signed the <a href="%(url)s">CLA</a> (<a href="%(url)s">text</a>).') % {'url': tg.url('/cla/view')})}
|
||||||
</p>
|
</p>
|
||||||
|
<py:if test="not cla">
|
||||||
|
<p>
|
||||||
|
${_('Insert legal text here')}
|
||||||
|
</p>
|
||||||
|
${Markup(_('<a href="%(url)s">View the CLA</a> (<a href="%(url)s">text</a>).') % {'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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -8,23 +8,12 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>${_('Contributor License Agreement')}</h2>
|
<h2>${_('Contributor License Agreement')}</h2>
|
||||||
<py:if test="not type">
|
<p>
|
||||||
<xi:include href="cla.html" />
|
<a href="${tg.url('/cla/')}">${_('Return to the signing page.')}</a>
|
||||||
</py:if>
|
</p>
|
||||||
|
<xi:include href="cla.html" />
|
||||||
<py:if test="type == 'sign'">
|
<p>
|
||||||
<p>
|
<a href="${tg.url('/cla/')}">${_('Return to the signing page.')}</a>
|
||||||
${Markup(_('Use the below link to download/save the CLA as fedora-icla-%(username)s.txt, and run: <pre>gpg -as fedora-icla-%(username)s.txt</pre> After, upload fedora-icla-%(username)s.txt.asc in the form below.') % {'username': person.username})}
|
</p>
|
||||||
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue