Merge lp:~canonical-isd-hackers/canonical-identity-provider/key-registration into lp:canonical-identity-provider/release
- key-registration
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical ISD hackers | Pending | ||
Review via email: mp+60470@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote : | # |
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 | - |
Rejecting since this is pretty old.