Merge lp:~canonical-isd-hackers/canonical-identity-provider/reconfirm_sreg_fields into lp:~canonical-isd-hackers/canonical-identity-provider/stable

Proposed by Anthony Lenton
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: no longer in the source branch.
Merged at revision: 121
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/reconfirm_sreg_fields
Merge into: lp:~canonical-isd-hackers/canonical-identity-provider/stable
Diff against target: 363 lines (+77/-59)
7 files modified
identityprovider/const.py (+0/-2)
identityprovider/models/openidmodels.py (+11/-12)
identityprovider/templates/decide.html (+7/-3)
identityprovider/tests/functional/openid_/per_version/test_restricted_sreg.py (+33/-6)
identityprovider/tests/test_models_openidmodels.py (+21/-15)
identityprovider/tests/test_views_server.py (+3/-18)
identityprovider/views/server.py (+2/-3)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/reconfirm_sreg_fields
Reviewer Review Type Date Requested Status
Canonical ISD hackers Pending
Review via email: mp+69185@code.launchpad.net

Commit message

Require the user to re-confirm they want to sign in every time, for untrusted RPs.

Description of the change

Overview
========
This branch requires the user to re-confirm which details to send to the RP each time they sign in, for untrusted RPs

Details
=======
Three small improvements related to this:
 - OpenIDAuthorizations expire immediately, so that you always need to reconfirm that you wish to sign in to an untrusted RP, and which details to send.
 - Fixed a small bug so that even for untrusted RPs the form is prepopulated with defaults matching the details you sent last time.
 - Made the "Team membership" label work as a label for the team membership checkbox.

To post a comment you must log in.
Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

Looks really good. However I still have one question.

If we're ignoring the expiration date, because we're automatically expiring the authorizations, why not get rid of the column at all? (and all relevant checks)

Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

After talking via IRC, we cannot remove the expiration date field yet, because it's being used by the pre-authorize functionality.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/const.py'
2--- identityprovider/const.py 2010-09-14 22:32:05 +0000
3+++ identityprovider/const.py 2011-07-26 00:11:18 +0000
4@@ -5,8 +5,6 @@
5
6 LAUNCHPAD_TEAMS_NS = 'http://ns.launchpad.net/2007/openid-teams'
7
8-NEVER_EXPIRES = datetime.date(datetime.MAXYEAR, 12, 31)
9-
10 PERSON_VISIBILITY_PUBLIC = 1
11 PERSON_VISIBILITY_PRIVATE_MEMBERSHIP = 20
12 PERSON_VISIBILITY_PRIVATE = 30
13
14=== modified file 'identityprovider/models/openidmodels.py'
15--- identityprovider/models/openidmodels.py 2011-07-08 16:51:05 +0000
16+++ identityprovider/models/openidmodels.py 2011-07-26 00:11:18 +0000
17@@ -2,9 +2,8 @@
18 # GNU Affero General Public License version 3 (see the file LICENSE).
19
20 import base64
21-import datetime
22 import time
23-
24+from datetime import datetime
25 from openid.association import Association
26 import openid.store
27 import openid.store.nonce
28@@ -15,7 +14,7 @@
29 from django.utils import simplejson as json
30 from django.utils.translation import ugettext_lazy as _
31
32-from identityprovider.const import NEVER_EXPIRES, SREG_LABELS
33+from identityprovider.const import SREG_LABELS
34 from identityprovider.models import Account
35 from identityprovider.models.const import AccountCreationRationale
36
37@@ -45,17 +44,17 @@
38
39 class OpenIDAuthorizationManager(models.Manager):
40
41- def authorize(self, account, trust_root, expires, client_id=None):
42+ def authorize(self, account, trust_root, expires=None, client_id=None):
43 if settings.READ_ONLY_MODE:
44 return
45 if expires is None:
46- expires = NEVER_EXPIRES
47+ expires = datetime.utcnow()
48 try:
49 existing = OpenIDAuthorization.objects.get(
50 account=account,
51 client_id=client_id,
52 trust_root=trust_root)
53- existing.date_created = datetime.datetime.utcnow()
54+ existing.date_created = datetime.utcnow()
55 existing.date_expires = expires
56 existing.save()
57 except OpenIDAuthorization.DoesNotExist:
58@@ -70,7 +69,7 @@
59 account__exact=account,
60 trust_root__exact=trust_root,
61 client_id__exact=None,
62- date_expires__gte=datetime.datetime.utcnow()).count()
63+ date_expires__gte=datetime.utcnow()).count()
64 if client_none > 0:
65 return True
66 else:
67@@ -78,7 +77,7 @@
68 account__exact=account,
69 trust_root__exact=trust_root,
70 client_id__exact=client_id,
71- date_expires__gte=datetime.datetime.utcnow()).count()
72+ date_expires__gte=datetime.utcnow()).count()
73 if client_match > 0:
74 return True
75 return False
76@@ -87,7 +86,7 @@
77 class OpenIDAuthorization(models.Model):
78 account = models.ForeignKey(Account, db_column='account')
79 client_id = models.TextField(blank=True, null=True)
80- date_created = models.DateTimeField(default=datetime.datetime.utcnow,
81+ date_created = models.DateTimeField(default=datetime.utcnow,
82 blank=True, editable=False)
83 date_expires = models.DateTimeField()
84 trust_root = models.TextField()
85@@ -204,9 +203,9 @@
86 account = models.ForeignKey(Account, db_column='account')
87 openid_identifier = models.TextField()
88 trust_root = models.TextField()
89- date_created = models.DateTimeField(default=datetime.datetime.utcnow,
90+ date_created = models.DateTimeField(default=datetime.utcnow,
91 blank=True, editable=False)
92- date_last_used = models.DateTimeField(default=datetime.datetime.utcnow,
93+ date_last_used = models.DateTimeField(default=datetime.utcnow,
94 blank=True, editable=False)
95 total_logins = models.IntegerField(default=1)
96 approved_data = models.TextField(blank=True, null=True, default='')
97@@ -220,7 +219,7 @@
98
99 def increment(self):
100 self.total_logins += 1
101- self.date_last_used = datetime.datetime.utcnow()
102+ self.date_last_used = datetime.utcnow()
103 self.save()
104
105 def get_approved_data(self):
106
107=== modified file 'identityprovider/templates/decide.html'
108--- identityprovider/templates/decide.html 2011-07-01 19:47:04 +0000
109+++ identityprovider/templates/decide.html 2011-07-26 00:11:18 +0000
110@@ -44,10 +44,11 @@
111 {% if teams_form.should_display %}
112 {% ifequal teams_form.fields|length 1 %}
113 {% for field in teams_form %}
114- <li>{{ field|safe }} {% trans "Team membership:"%} {{ field.label_tag }}</li>
115+ <li>{{ field|safe }} <label for="id_{{ field.html_name }}">{% trans "Team membership:"%}</label> {{ field.label_tag }}</li>
116 {% endfor %}
117 {% else %}
118- <li id="teamslist_item">{% trans "Team membership:"%}
119+ <li id="teamslist_item">
120+ <span id="teamslist_label">{% trans "Team membership:"%}</span>
121 <ul class="teams-list" id="teamslist">
122 {% for field in teams_form %}
123 <li>{{ field|safe }} {{ field.label_tag }}</li>
124@@ -114,6 +115,7 @@
125 return;
126 }
127 Y.one('#teamslist_item').prepend('<input type="checkbox" id="checkme" /> ');
128+ Y.one('#teamslist_label').wrap('<label for="checkme"></label>');
129 Y.one('#checkme').on('change', setTeamStates);
130 Y.all('#teamslist li').on('change', refreshTeamStates);
131 refreshTeamStates();
132@@ -135,7 +137,9 @@
133 for (i = 0; i < c.size(); i++) {
134 buf.push(c.item(i).get('text'));
135 }
136- node.insert('<span id="reveal_wrapper">' + buf.join(', ') + ' <small style="font-size: 80%;">(<a href="#" id="reveal">details</a>)</small></span>', "before");
137+ node.insert('<span id="reveal_wrapper"><label for="checkme">' +
138+ buf.join(', ') + '</label> <small style="font-size: 80%;">' +
139+ '(<a href="#" id="reveal">details</a>)</small></span>', "before");
140 Y.one('#reveal').on('click', onClick);
141 }
142 });
143
144=== modified file 'identityprovider/tests/functional/openid_/per_version/test_restricted_sreg.py'
145--- identityprovider/tests/functional/openid_/per_version/test_restricted_sreg.py 2011-07-19 14:33:08 +0000
146+++ identityprovider/tests/functional/openid_/per_version/test_restricted_sreg.py 2011-07-26 00:11:18 +0000
147@@ -3,7 +3,11 @@
148 from openid.store.memstore import MemoryStore
149 from openid.consumer.discover import OPENID_2_0_TYPE as PROTOCOL_URI
150
151-from identityprovider.models.openidmodels import OpenIDRPConfig
152+from identityprovider.models.openidmodels import (
153+ OpenIDAuthorization,
154+ OpenIDRPConfig,
155+ OpenIDRPSummary,
156+ )
157
158 from ...openidhelpers import complete_from_browser, make_endpoint
159 from ...helpers import FunctionalTestCase
160@@ -29,10 +33,11 @@
161
162 # If a relying party attempts to request user details via the
163 # openid.sreg extension and Launchpad does not have a particular policy
164- # configured, then only the user's approved fields are returned in the response.
165+ # configured, then only the user's approved fields are returned in the
166+ # response.
167
168- # We will perform an OpenID authentication request asking for a few user
169- # details:
170+ # We will perform an OpenID authentication request asking for a few
171+ # user details:
172 openid_store = MemoryStore()
173 consumer = Consumer(session={}, store=openid_store)
174
175@@ -59,8 +64,10 @@
176 # authorize data
177 # required fields are checked by default. don't authorize
178 # anything
179- self.assertEqual(self.browser.getControl(name='email').disabled, False)
180- self.browser.getControl(name='email').value = False
181+ email = self.browser.getControl(name='email')
182+ self.assertEqual(email.disabled, False)
183+ self.assertTrue(email.mech_control.items[0].selected)
184+ email.value = False
185 self.browser.getControl(name='yes', index=0).click()
186
187 self.assertUrl('http://launchpad.dev/\+openid-consumer\?.*?')
188@@ -79,11 +86,31 @@
189
190 self.assertEquals(sreg_response, None)
191
192+ # If we attempt to authenticate again, we will be prompted to
193+ # confirm which fields we want to provide to the RP again,
194+ # but the defaults will be what we provided last time:
195+
196+ request = consumer.beginWithoutDiscovery(endpoint)
197+ request.addExtension(SRegRequest(
198+ required=['email', 'country'],
199+ optional=['fullname', 'nickname']))
200+ redirect_url = request.redirectURL(
201+ 'http://launchpad.dev/', 'http://launchpad.dev/+openid-consumer')
202+ self.assertRegexpMatches(redirect_url,
203+ 'http://openid.launchpad.dev/\+openid\?.*?')
204+ self.browser.open(redirect_url)
205+ # No log in needed this time, we're directed straight to the confirm
206+ # screen. Check that email is *not* selected by default this time:
207+ email = self.browser.getControl(name='email')
208+ self.assertFalse(email.mech_control.items[0].selected)
209+
210 # == Behaviour for known trust roots ==
211
212 # If we create a Relying Party configuration for the trust root, things
213 # play out a bit differently:
214
215+ OpenIDRPSummary.objects.all().delete()
216+ OpenIDAuthorization.objects.all().delete()
217
218 self.create_openid_rp_config(
219 trust_root='http://launchpad.dev/',
220
221=== modified file 'identityprovider/tests/test_models_openidmodels.py'
222--- identityprovider/tests/test_models_openidmodels.py 2011-07-25 16:47:59 +0000
223+++ identityprovider/tests/test_models_openidmodels.py 2011-07-26 00:11:18 +0000
224@@ -3,11 +3,11 @@
225
226 import base64
227 from datetime import datetime, timedelta
228+from mock import patch
229 from time import time
230 from openid.association import Association
231 from openid.store.nonce import SKEW
232
233-from identityprovider.const import NEVER_EXPIRES
234 from identityprovider.models.account import Account
235 from identityprovider.models.openidmodels import (DjangoOpenIDStore,
236 OpenIDAssociation, OpenIDAuthorization, OpenIDNonce, OpenIDRPConfig,
237@@ -176,29 +176,35 @@
238 finally:
239 rm.clear_readonly()
240
241- def test_authorize_expires(self):
242- OpenIDAuthorization.objects.authorize(self.account, self.trust_root,
243- None)
244+ @patch('identityprovider.models.openidmodels.datetime')
245+ def test_authorize_expires(self, mock_datetime):
246+ now = datetime.utcnow()
247+ mock_datetime.utcnow.return_value = now
248+
249+ OpenIDAuthorization.objects.authorize(self.account, self.trust_root)
250+
251 auth = OpenIDAuthorization.objects.get(account=self.account,
252 client_id=None, trust_root=self.trust_root)
253- self.assertEqual(auth.date_expires,
254- datetime.fromordinal(NEVER_EXPIRES.toordinal()))
255+ self.assertEqual(auth.date_expires, now)
256
257- def test_authorize_existing(self):
258+ @patch('identityprovider.models.openidmodels.datetime')
259+ def test_authorize_existing(self, mock_datetime):
260+ now = datetime.utcnow()
261+ mock_datetime.utcnow.return_value = now
262 auth1 = self.create_auth()
263- OpenIDAuthorization.objects.authorize(self.account, self.trust_root,
264- None)
265+ OpenIDAuthorization.objects.authorize(self.account, self.trust_root)
266 auth2 = OpenIDAuthorization.objects.get(pk=auth1.id)
267- self.assertEqual(auth2.date_expires,
268- datetime.fromordinal(NEVER_EXPIRES.toordinal()))
269+ self.assertEqual(auth2.date_expires, now)
270
271- def test_authorize_not_existing(self):
272+ @patch('identityprovider.models.openidmodels.datetime')
273+ def test_authorize_not_existing(self, mock_datetime):
274+ now = datetime.utcnow()
275+ mock_datetime.utcnow.return_value = now
276 OpenIDAuthorization.objects.authorize(self.account, self.trust_root,
277 None)
278 auth = OpenIDAuthorization.objects.get(account=self.account,
279 client_id=None, trust_root=self.trust_root)
280- self.assertEqual(auth.date_expires,
281- datetime.fromordinal(NEVER_EXPIRES.toordinal()))
282+ self.assertEqual(auth.date_expires, now)
283
284 def test_is_authorized_generic(self):
285 self.create_auth()
286@@ -209,7 +215,7 @@
287 self.assertFalse(OpenIDAuthorization.objects.is_authorized(self.account,
288 self.trust_root, 'client'))
289 OpenIDAuthorization.objects.authorize(self.account, self.trust_root,
290- None, client_id='client')
291+ expires=self.expires, client_id='client')
292 self.assertTrue(OpenIDAuthorization.objects.is_authorized(self.account,
293 self.trust_root, 'client'))
294
295
296=== modified file 'identityprovider/tests/test_views_server.py'
297--- identityprovider/tests/test_views_server.py 2011-07-25 17:53:04 +0000
298+++ identityprovider/tests/test_views_server.py 2011-07-26 00:11:18 +0000
299@@ -13,8 +13,6 @@
300 from openid.extensions import pape
301 from openid.extensions.sreg import SRegRequest
302
303-from mock import patch
304-
305 from openid.message import (Message, IDENTIFIER_SELECT, OPENID1_URL_LIMIT,
306 OPENID2_NS)
307 from openid.yadis.constants import YADIS_HEADER_NAME
308@@ -324,18 +322,6 @@
309 self.assertEqual(r.status_code, 302)
310 self.assertEqual(query['openid.mode'], 'id_res')
311
312- @patch('identityprovider.views.server.datetime')
313- def test_process_decide_use_utc(self, mock_datetime):
314- mock_datetime.utcnow.return_value = datetime.datetime.utcnow()
315- mock_datetime.side_effect = lambda *args, **kw: datetime(*args, **kw)
316-
317- r = self.client.post("/%s/+decide" % self.token, {'yes': 'yes'})
318- query = self.get_query(r)
319-
320- self.assertEqual(r.status_code, 302)
321- self.assertEqual(query['openid.mode'], 'id_res')
322- self.assertEqual(mock_datetime.utcnow.called, True)
323-
324 def test_decide_authenticated_with_post(self):
325 # Using a return-to URL that is more than OPENID1_URL_LIMIT
326 # characters long will force the assertion to be sent as a
327@@ -539,10 +525,9 @@
328 'Preferred language: en</label></li>')
329 # team data is enabled and *not checked*, the label is plain
330 team_html = ('<li><input type="checkbox" name="ubuntu-team" '
331- 'value="ubuntu-team" id="id_ubuntu-team" /> Team '
332- 'membership: <label for="id_ubuntu-team">ubuntu-team'
333- '</label></li>')
334-
335+ 'value="ubuntu-team" id="id_ubuntu-team" /> '
336+ '<label for="id_ubuntu-team">Team membership:</label> '
337+ '<label for="id_ubuntu-team">ubuntu-team</label></li>')
338 self.assertContains(response, username_html)
339 self.assertContains(response, email_html)
340 self.assertContains(response, language_html)
341
342=== modified file 'identityprovider/views/server.py'
343--- identityprovider/views/server.py 2011-07-25 17:53:04 +0000
344+++ identityprovider/views/server.py 2011-07-26 00:11:18 +0000
345@@ -234,7 +234,7 @@
346 try:
347 summary = OpenIDRPSummary.objects.get(
348 account=request.user, trust_root=orequest.trust_root,
349- openid_identifier=request.user.openid_identifier)
350+ openid_identifier=request.user.openid_identity_url)
351 approved_data = summary.get_approved_data()
352 except OpenIDRPSummary.DoesNotExist:
353 approved_data = {}
354@@ -552,8 +552,7 @@
355 OpenIDAuthorization.objects.authorize(
356 request.user,
357 orequest.trust_root,
358- datetime.utcnow(),
359- request.session.session_key)
360+ client_id=request.session.session_key)
361 _add_sreg(request, orequest, oresponse)
362 # if there's no submitted POST data, this is an auto-authorized
363 # (immediate) request

Subscribers

People subscribed via source and target branches