Merge lp:~canonical-isd-hackers/canonical-identity-provider/key-registration into lp:canonical-identity-provider/release

Proposed by Michael Foord
Status: Rejected
Rejected by: Natalia Bidart
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/key-registration
Merge into: lp:canonical-identity-provider/release
Diff against target: 434 lines (+247/-15)
9 files modified
identityprovider/forms.py (+33/-4)
identityprovider/models/__init__.py (+1/-0)
identityprovider/models/account.py (+4/-3)
identityprovider/models/yubi_key.py (+20/-0)
identityprovider/templates/account/delete_key.html (+34/-0)
identityprovider/templates/account/edit.html (+4/-0)
identityprovider/templates/account/keys.html (+82/-0)
identityprovider/urls.py (+3/-0)
identityprovider/views/account.py (+66/-8)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/key-registration
Reviewer Review Type Date Requested Status
Canonical ISD hackers Pending
Review via email: mp+60470@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Rejecting since this is pretty old.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/forms.py'
2--- identityprovider/forms.py 2010-09-21 22:04:10 +0000
3+++ identityprovider/forms.py 2011-05-11 08:50:23 +0000
4@@ -9,7 +9,12 @@
5 from django.utils.safestring import mark_safe
6 from django.utils.translation import ugettext as _
7
8-from identityprovider.models import Account, EmailAddress, verify_token_string
9+from identityprovider.models import (
10+ Account,
11+ EmailAddress,
12+ YubiKey,
13+ verify_token_string,
14+)
15 from identityprovider.models.const import EmailStatus
16 from identityprovider.utils import (
17 get_person_and_account_by_email, CannotResetPasswordException,
18@@ -182,6 +187,7 @@
19 'class': 'disableAutoComplete textType',
20 'size': '20'
21 }))
22+ require_key = fields.BooleanField(required=False)
23
24 def __init__(self, data=None, **kwargs):
25 # These keyword arguments are required to render the form successfully
26@@ -189,7 +195,8 @@
27 self.account = kwargs.pop('account')
28
29 if data is None:
30- kwargs['initial'] = {'displayname': self.account.displayname}
31+ kwargs['initial'] = {'displayname': self.account.displayname,
32+ 'require_key':self.account.require_key}
33
34 super(EditAccountForm, self).__init__(data, **kwargs)
35
36@@ -200,12 +207,13 @@
37
38 # Override the field to set initial value (current preferred email)
39 self.fields['preferred_email'] = PreferredEmailField(
40- queryset=EmailAddress.objects.filter(account=self.account).\
41+ queryset=EmailAddress.objects.filter(account=self.account).
42 exclude(status=EmailStatus.NEW).order_by('-status', 'email'),
43 initial=preferredemail_id,
44 widget=ROAwareSelect,
45 error_messages=email_errors,
46- empty_label=None)
47+ empty_label=None
48+ )
49
50 def clean_displayname(self):
51 name = self.cleaned_data['displayname'].strip()
52@@ -261,6 +269,7 @@
53 if self.is_valid():
54 self.account.displayname = self.cleaned_data['displayname']
55 self.account.preferredemail = self.cleaned_data['preferred_email']
56+ self.account.require_key = self.cleaned_data['require_key']
57 self.account.save()
58 password = self.cleaned_data['password']
59 if password:
60@@ -286,6 +295,26 @@
61 return data
62
63
64+class NewKeyForm(forms.ModelForm):
65+ newkey = fields.CharField()
66+
67+ def clean_newkey(self):
68+ data = self.cleaned_data['newkey']
69+ # Verify, if fail, then:
70+ #raise forms.ValidationError(_("Invalid Yubi key."))
71+ # 'cccccccitnicnibiidnjbrctldunhkiedndechgkjiku'[:12] = 'cccccccitnic'
72+ return data
73+
74+ def clean(self):
75+ if self.cleaned_data.has_key('newkey'):
76+ self.cleaned_data['public_id'] = self.cleaned_data['newkey'][:12]
77+ return self.cleaned_data
78+
79+ class Meta:
80+ model = YubiKey
81+ #exclude = ('public_id',)
82+
83+
84 class PreAuthorizeForm(forms.Form):
85 trust_root = forms.CharField(error_messages=default_errors)
86 callback = forms.CharField(error_messages=default_errors)
87
88=== modified file 'identityprovider/models/__init__.py'
89--- identityprovider/models/__init__.py 2010-12-22 15:19:08 +0000
90+++ identityprovider/models/__init__.py 2011-05-11 08:50:23 +0000
91@@ -5,6 +5,7 @@
92 from metamodels import *
93 from person import *
94 from emailaddress import *
95+from yubi_key import *
96 from authtoken import *
97 from openidmodels import *
98 from team import *
99
100=== modified file 'identityprovider/models/account.py'
101--- identityprovider/models/account.py 2011-02-23 15:45:28 +0000
102+++ identityprovider/models/account.py 2011-05-11 08:50:23 +0000
103@@ -75,9 +75,9 @@
104 class Account(models.Model):
105 date_created = models.DateTimeField(default=datetime.utcnow,
106 editable=False)
107- creation_rationale = \
108- models.IntegerField(
109- choices=AccountCreationRationale._get_choices())
110+ creation_rationale = models.IntegerField(
111+ choices=AccountCreationRationale._get_choices()
112+ )
113
114 status = models.IntegerField(choices=AccountStatus._get_choices())
115 date_status_set = models.DateTimeField(default=datetime.utcnow)
116@@ -86,6 +86,7 @@
117 status_comment = models.TextField(blank=True, null=True)
118 preferredlanguage = models.TextField(blank=True, null=True)
119 old_openid_identifier = models.TextField(blank=True, null=True)
120+ require_key = models.BooleanField(default=False)
121
122 objects = AccountManager()
123
124
125=== added file 'identityprovider/models/yubi_key.py'
126--- identityprovider/models/yubi_key.py 1970-01-01 00:00:00 +0000
127+++ identityprovider/models/yubi_key.py 2011-05-11 08:50:23 +0000
128@@ -0,0 +1,20 @@
129+# Copyright 2011 Canonical Ltd. This software is licensed under the
130+# GNU Affero General Public License version 3 (see the file LICENSE).
131+
132+from django.db import models
133+
134+from identityprovider.models import Account
135+from django.utils.translation import ugettext_lazy as _
136+
137+
138+__all__ = ['YubiKey']
139+
140+
141+class YubiKey(models.Model):
142+ public_id = models.CharField(primary_key=True, max_length=20, blank=True)
143+ name = models.CharField(max_length=30)
144+ account = models.ForeignKey(Account)
145+
146+ class Meta:
147+ app_label = 'identityprovider'
148+ db_table = u'yubi_key'
149
150=== added file 'identityprovider/templates/account/delete_key.html'
151--- identityprovider/templates/account/delete_key.html 1970-01-01 00:00:00 +0000
152+++ identityprovider/templates/account/delete_key.html 2011-05-11 08:50:23 +0000
153@@ -0,0 +1,34 @@
154+{% extends "base.html" %}
155+{% load i18n %}
156+
157+{% comment %}
158+Copyright 2010 Canonical Ltd. This software is licensed under the
159+GNU Affero General Public License version 3 (see the file LICENSE).
160+{% endcomment %}
161+
162+{% block title %}
163+ {% trans "Remove USB key" %}
164+{% endblock %}
165+
166+{% block text_title %}
167+ <h2 class="main">{% blocktrans %}Remove USB key?{% endblocktrans %}</h2>
168+{% endblock %}
169+
170+{% block content_id %}auth{% endblock %}
171+
172+{% block content %}
173+ <div class="info">
174+ <p>{% blocktrans %}Are you sure you want to remove this USB key?{% endblocktrans %}</p>
175+ </div>
176+
177+ <div class="actions">
178+ <form action="" method="POST">
179+ <p>
180+ <button type="submit" class="btn" name="delete"><span><span>{% trans "Yes, remove" %}</span></span></button>
181+ {% trans "or" %}
182+ <a href="/keys">{% trans "cancel" %}</a>
183+ </p>
184+ </form>
185+ </div>
186+ <br style="clear: both" />
187+{% endblock %}
188
189=== modified file 'identityprovider/templates/account/edit.html'
190--- identityprovider/templates/account/edit.html 2010-11-10 00:10:51 +0000
191+++ identityprovider/templates/account/edit.html 2011-05-11 08:50:23 +0000
192@@ -90,7 +90,11 @@
193 {{ form.preferred_email }}
194 </p>
195 {% if not embedded %}
196+ <p><label class="formLabel" for="id_require_key">
197+ {% trans "Require key authentication" %}{{ form.require_key }}
198+ </p>
199 <p><a href="/+emails">{% trans "Manage email addresses" %}</a></p>
200+ <p><a href="/keys">{% trans "Manage USB keys" %}</a></p>
201 {% endif %}
202 </div>
203 {% if not readonly %}
204
205=== added file 'identityprovider/templates/account/keys.html'
206--- identityprovider/templates/account/keys.html 1970-01-01 00:00:00 +0000
207+++ identityprovider/templates/account/keys.html 2011-05-11 08:50:23 +0000
208@@ -0,0 +1,82 @@
209+{% extends "base.html" %}
210+{% load i18n %}
211+
212+{% comment %}
213+Copyright 2010 Canonical Ltd. This software is licensed under the
214+GNU Affero General Public License version 3 (see the file LICENSE).
215+{% endcomment %}
216+
217+{% block title %}
218+ {% blocktrans %}{{ account_displayname }}'s USB keys{% endblocktrans %}
219+{% endblock %}
220+
221+{% block extra_header_top %}
222+<link rel="stylesheet" type="text/css" href="/assets/identityprovider/lazr-js/cssreset/reset-min.css"></link>
223+<link rel="stylesheet" type="text/css" href="/assets/identityprovider/lazr-js/cssfonts/fonts-min.css"></link>
224+<link rel="stylesheet" type="text/css" href="/assets/identityprovider/lazr-js/cssbase/base-min.css"></link>
225+<style type="text/css">
226+td.actions {
227+ text-align: right;
228+}
229+</style>
230+{% endblock %}
231+
232+{% block text_title %}
233+ <h1 class="main">{% trans "Your email addresses" %}</h1>
234+{% endblock %}
235+
236+{% block content %}
237+
238+{% if not yubi_keys %}
239+ <p>{% blocktrans %}You have no USB keys associated with your account.{% endblocktrans %}</p>
240+{% endif %}
241+
242+{% if yubi_keys %}
243+
244+ <h2>{% trans "USB keys" %}</h2>
245+
246+ <table class="listing hover">
247+ {% for key in yubi_keys %}
248+ <tr>
249+ <td>{{ key.name }}</td>
250+ <td>{{ key.public_id }}</td>
251+ {% if not readonly %}
252+ <td class="actions">
253+ <a href="/remove-key/{{ key.public_id }}"><img src="/assets/identityprovider/trash-icon.gif" width="14" height="14"> {% trans "Remove" %}</a>
254+ </td>
255+ {% endif %}
256+ </tr>
257+ {% endfor %}
258+ </table>
259+
260+{% endif %}
261+
262+{% if not readonly %}
263+
264+ <h2>{% trans "Add USB key" %}</h2>
265+
266+ <form action="new-key" method="post">
267+ <p class="input-row">
268+ <label for="id_name">{% trans "Name" %}</label><br>
269+ {{ form.name }}
270+ {% if form.name.errors %}
271+ <span class="error">{{ form.name.errors|first }}</span>
272+ {% endif %}
273+ </p>
274+ <p class="input-row">
275+ <label for="id_newkey">{% trans "Key" %}</label><br>
276+ {{ form.newkey }}
277+ {% if form.newkey.errors %}
278+ <span class="error">{{ form.newkey.errors|first }}</span>
279+ {% endif %}
280+ </p>
281+ <p class="actions">
282+ <button type="submit" class="btn" name="continue">
283+ <span><span>{% trans "Add key" %}</span></span>
284+ </button>
285+ </p>
286+ </form>
287+
288+{% endif %}
289+
290+{% endblock %}
291
292=== modified file 'identityprovider/urls.py'
293--- identityprovider/urls.py 2011-03-04 17:09:45 +0000
294+++ identityprovider/urls.py 2011-05-11 08:50:23 +0000
295@@ -65,12 +65,15 @@
296 urlpatterns += patterns('identityprovider.views.account',
297 (r'^%(optional_token)s\+edit$' % repls, 'index'),
298 (r'^\+emails$', 'account_emails'),
299+ (r'^keys$', 'account_keys'),
300 (r'^$', 'index'),
301 (r'^\+cookie$', 'cookie'),
302 (r'^%(optional_token)s\+index$' % repls, 'index'),
303 (r'^%(optional_token)s\+new-email$' % repls, 'new_email'),
304+ (r'^new-key$' % repls, 'new_key'),
305 (r'^%(optional_token)s\+verify-email$' % repls, 'verify_email'),
306 (r'^%(optional_token)s\+remove-email$' % repls, 'delete_email'),
307+ (r'^remove-key/(?P<public_id>.+)$' % repls, 'delete_key'),
308 )
309
310 if settings.BRAND.lower() == 'ubuntu':
311
312=== modified file 'identityprovider/views/account.py'
313--- identityprovider/views/account.py 2011-01-10 15:02:53 +0000
314+++ identityprovider/views/account.py 2011-05-11 08:50:23 +0000
315@@ -12,9 +12,17 @@
316 from django.utils.translation import ugettext as _
317
318 from identityprovider.decorators import check_readonly
319-from identityprovider.forms import EditAccountForm, LoginForm, NewEmailForm
320-from identityprovider.models import (send_validation_email_request,
321- EmailAddress)
322+from identityprovider.forms import (
323+ EditAccountForm,
324+ LoginForm,
325+ NewEmailForm,
326+ NewKeyForm,
327+)
328+from identityprovider.models import (
329+ EmailAddress,
330+ send_validation_email_request,
331+ YubiKey,
332+)
333 from oauth_backend.models import Consumer, Token
334 from identityprovider.models.const import EmailStatus
335 from identityprovider.views.utils import (redirection_url_for_token,
336@@ -55,7 +63,7 @@
337 if request.method == 'POST' and not settings.READ_ONLY_MODE:
338 form = EditAccountForm(request.POST, account=account)
339 if form.save_account():
340- request.session['message'] = _("Your account details have been "\
341+ request.session['message'] = _("Your account details have been "
342 "successfully updated")
343 return HttpResponseRedirect('.')
344 else:
345@@ -93,6 +101,22 @@
346
347
348 @login_required
349+def account_keys(request):
350+ account = request.user
351+ form = NewKeyForm()
352+ context = RequestContext(request, {
353+ 'current_section': 'emails',
354+ 'account': account,
355+ 'account_displayname': account.displayname,
356+ 'yubi_keys': account.yubikey_set.all(),
357+ 'form': form,
358+ 'message': request.session.pop('message', None),
359+ 'message_style': 'informational'
360+ })
361+ return render_to_response('account/keys.html', context)
362+
363+
364+@login_required
365 def account_deactivate(request, token=None):
366 request.token = token
367 import django.contrib.auth as auth
368@@ -117,7 +141,7 @@
369 # TODO: This makes use of `tokenid` if it's passed in, but the
370 # call to `send_validation_email_request` always generates a new
371 # token. Is this right?
372-
373+
374 # If there are any unverified emails that match they should be deleted.
375 EmailAddress.objects.filter(email__iexact=email,
376 status=EmailStatus.NEW).delete()
377@@ -163,6 +187,31 @@
378 return render_to_response('account/new_email.html', context)
379
380
381+
382+@login_required
383+@check_readonly
384+def new_key(request, key=None):
385+ #request.token = token
386+
387+ if request.method != 'POST' or settings.READ_ONLY_MODE:
388+ return
389+
390+ data = request.POST.copy()
391+ data['account'] = request.user.id
392+ form = NewKeyForm(data)
393+ if form.is_valid():
394+ form.save()
395+ # Send e-mail to notify user (so they can catch possible
396+ # security breaches
397+ return HttpResponseRedirect('/keys')
398+
399+ context = RequestContext(request, {
400+ 'account_displayname': request.user.displayname,
401+ 'form': form
402+ })
403+ return render_to_response('account/keys.html', context)
404+
405+
406 @login_required
407 def verify_email(request, token=None):
408 request.token = token
409@@ -195,6 +244,18 @@
410
411
412 @login_required
413+def delete_key(request, public_id):
414+ key = get_object_or_404(request.user.yubikey_set, public_id=public_id)
415+ if request.method == 'POST':
416+ key.delete()
417+ request.session['message'] = _("The USB key was removed successfully")
418+ return HttpResponseRedirect('/keys')
419+ else:
420+ context = RequestContext(request)
421+ return render_to_response('account/delete_key.html', context)
422+
423+
424+@login_required
425 @check_readonly
426 def applications(request):
427 if request.method == 'POST':
428@@ -217,6 +278,3 @@
429 'message_style': 'informational',
430 })
431 return render_to_response('account/applications.html', context)
432-
433-
434-