Merge lp:~canonical-isd-hackers/canonical-identity-provider/sreg_121533 into lp:canonical-identity-provider/release
- sreg_121533
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Ricardo Kirkner |
Approved revision: | no longer in the source branch. |
Merged at revision: | 169 |
Proposed branch: | lp:~canonical-isd-hackers/canonical-identity-provider/sreg_121533 |
Merge into: | lp:canonical-identity-provider/release |
Diff against target: |
2117 lines (+1188/-231) 21 files modified
.bzrignore (+2/-0) django_project/config_dev/urls.py (+1/-4) identityprovider/api10/handlers.py (+11/-6) identityprovider/fixtures/test.json (+128/-1) identityprovider/forms.py (+178/-6) identityprovider/media/ubuntu/narrow.css (+6/-1) identityprovider/media/ubuntu/styles.css (+14/-2) identityprovider/models/account.py (+1/-2) identityprovider/models/openidmodels.py (+19/-2) identityprovider/models/team.py (+19/-0) identityprovider/teams.py (+4/-2) identityprovider/templates/decide.html (+99/-12) identityprovider/tests/test_admin.py (+5/-2) identityprovider/tests/test_forms.py (+260/-4) identityprovider/tests/test_models_openidmodels.py (+18/-4) identityprovider/tests/test_models_team.py (+57/-3) identityprovider/tests/test_views_server.py (+171/-70) identityprovider/tests/utils.py (+51/-0) identityprovider/views/server.py (+133/-108) identityprovider/views/ui.py (+7/-1) payload/__init__.py (+4/-1) |
To merge this branch: | bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/sreg_121533 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ricardo Kirkner (community) | Needs Fixing | ||
Review via email: mp+66393@code.launchpad.net |
Commit message
Enhancements to the decide page as per bug 121533
Description of the change
This branch adds enhancements to the decide page. It provides checkboxes that let the user decide whether or not to send individual items requested by the consumer on to the consumer. It remembers the state of those choices for the next time the user is presented with those options. If also employs javascript to condense multiple teams that are requested with a single line and one checkbox for convenience. The user can choose to expand the list and deselect or select individual teams as desired. One part of the bug is not implemented because of another SSO bug and that is the section dealing with additional info requested from the consumer changing the behavior of auto login.
To test:
fab bootstrap
fab setup_postgresq
. .env/bin/activate
./django_
fab run
Go to http://
select whatever sreg data you wish to request.
for the teams you can enter the following: ubuntu-
Danny Tamez (zematynnad) wrote : | # |
Made all but a few changes::
#6 below. Fields is a dictionary not a list. Iterating over it returns each key. Trying to do something like teams_form.
#8 The point of the test was to show that if the method was GET (even if all the other conditions were the same) the approved fields wouldn't be shown.
#12 This is not using nose so it didn't make sense to merge it with the test() target.
question re: l. 318: I didn't write that portion but as best as I can tell it was only meant to be evaluated with the form's creation so I moved the logic to __init__(). (here and for teams as well)
question re: l. 651, We are using yui in other places and the page does "work" when using <script type="text/
ISD Branch Mangler (isd-branches-mangler) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2011-06-17 15:34:24 +0000 |
3 | +++ .bzrignore 2011-07-05 15:46:33 +0000 |
4 | @@ -11,3 +11,5 @@ |
5 | .coverage* |
6 | coverage |
7 | *.xml |
8 | +tags |
9 | +.backup |
10 | |
11 | === modified file 'django_project/config_dev/urls.py' |
12 | --- django_project/config_dev/urls.py 2011-06-17 12:59:08 +0000 |
13 | +++ django_project/config_dev/urls.py 2011-07-05 15:46:33 +0000 |
14 | @@ -5,8 +5,6 @@ |
15 | import preflight |
16 | import preflight.urls |
17 | |
18 | -from identityprovider.decorators import check_readonly |
19 | - |
20 | admin.autodiscover() |
21 | preflight.autodiscover() |
22 | |
23 | @@ -23,11 +21,10 @@ |
24 | (r'^preflight/', include(preflight.urls)), |
25 | (r'^', include('identityprovider.urls')), |
26 | ) |
27 | - |
28 | if settings.SERVE_STATIC_MEDIA: |
29 | urlpatterns += patterns('', |
30 | (r'assets/identityprovider/(.*)', 'django.views.static.serve', |
31 | {'document_root': settings.SSO_MEDIA_ROOT}), |
32 | (r'media/(.*)', 'django.views.static.serve', |
33 | {'document_root': settings.MEDIA_ROOT}), |
34 | - ) |
35 | +) |
36 | |
37 | === modified file 'identityprovider/api10/handlers.py' |
38 | --- identityprovider/api10/handlers.py 2011-06-15 22:24:43 +0000 |
39 | +++ identityprovider/api10/handlers.py 2011-07-05 15:46:33 +0000 |
40 | @@ -37,7 +37,7 @@ |
41 | NewCaptchaError, |
42 | VerifyCaptchaError, |
43 | ) |
44 | -from identityprovider.views.server import get_team_memberships |
45 | +from identityprovider.models.team import TeamMembership |
46 | from identityprovider.signals import ( |
47 | account_created, |
48 | account_email_validated, |
49 | @@ -78,6 +78,7 @@ |
50 | return seria |
51 | Emitter.register('lazr.restful', LazrRestfulEmitter, 'application/json') |
52 | |
53 | + |
54 | class LazrRestfulHandler(BaseHandler): |
55 | allowed_methods = ('GET', 'POST') |
56 | |
57 | @@ -140,6 +141,7 @@ |
58 | else: |
59 | return self.response |
60 | |
61 | + |
62 | class CaptchaHandler(LazrRestfulHandler): |
63 | response = { |
64 | "total_size": 0, |
65 | @@ -280,7 +282,7 @@ |
66 | 'status': 'ok', |
67 | 'message': "Password changed" |
68 | } |
69 | - |
70 | + |
71 | |
72 | def _serialize_account(user): |
73 | emails = EmailAddress.objects.filter(account=user, |
74 | @@ -354,7 +356,6 @@ |
75 | application_token_invalidated.send( |
76 | sender=self, openid_identifier=data['consumer_key']) |
77 | |
78 | - |
79 | @api_user_required |
80 | @named_operation |
81 | def team_memberships(self, request): |
82 | @@ -365,8 +366,9 @@ |
83 | |
84 | if len(accounts) == 1: |
85 | account = accounts[0] |
86 | - memberships = get_team_memberships(data['team_names'], |
87 | - account, False) |
88 | + memberships = ( |
89 | + TeamMembership.get_team_memberships_for_user( |
90 | + data['team_names'], account, False)) |
91 | return memberships |
92 | else: |
93 | return [] |
94 | @@ -402,7 +404,10 @@ |
95 | @named_operation |
96 | def team_memberships(self, request): |
97 | team_names = request.data['team_names'] |
98 | - memberships = get_team_memberships(team_names, request.user, True) |
99 | + |
100 | + memberships = ( |
101 | + TeamMembership.get_team_memberships( |
102 | + team_names, request.user, True)) |
103 | return memberships |
104 | |
105 | @named_operation |
106 | |
107 | === modified file 'identityprovider/fixtures/test.json' |
108 | --- identityprovider/fixtures/test.json 2011-03-08 14:31:20 +0000 |
109 | +++ identityprovider/fixtures/test.json 2011-07-05 15:46:33 +0000 |
110 | @@ -173,6 +173,28 @@ |
111 | } |
112 | }, |
113 | { |
114 | + "pk": 46, |
115 | + "model": "identityprovider.emailaddress", |
116 | + "fields": { |
117 | + "status": 4, |
118 | + "lp_person": 18, |
119 | + "account": null, |
120 | + "email": "support@kubuntu.com", |
121 | + "date_created": "2006-10-16 18:31:43.621341" |
122 | + } |
123 | + }, |
124 | + { |
125 | + "pk": 47, |
126 | + "model": "identityprovider.emailaddress", |
127 | + "fields": { |
128 | + "status": 4, |
129 | + "lp_person": 19, |
130 | + "account": null, |
131 | + "email": "support@isd.com", |
132 | + "date_created": "2006-10-16 18:31:43.621341" |
133 | + } |
134 | + }, |
135 | + { |
136 | "pk": 61, |
137 | "model": "identityprovider.emailaddress", |
138 | "fields": { |
139 | @@ -367,6 +389,88 @@ |
140 | } |
141 | }, |
142 | { |
143 | + "pk": 18, |
144 | + "model": "identityprovider.person", |
145 | + "fields": { |
146 | + "displayname": "Kubuntu Team", |
147 | + "teamowner": 1, |
148 | + "teamdescription": "This Team is responsible for the Kubuntu Distribution", |
149 | + "name": "kubuntu-team", |
150 | + "language": null, |
151 | + "fti": "'team':3A,5A 'kubuntu':2A,4A 'kubuntu-team':1A", |
152 | + "defaultmembershipperiod": null, |
153 | + "defaultrenewalperiod": null, |
154 | + "subscriptionpolicy": 1, |
155 | + "merged": null, |
156 | + "datecreated": "2005-06-06 08:59:51.60576", |
157 | + "addressline1": null, |
158 | + "addressline2": null, |
159 | + "organization": null, |
160 | + "city": null, |
161 | + "province": null, |
162 | + "country": null, |
163 | + "postcode": null, |
164 | + "phone": null, |
165 | + "homepage_content": null, |
166 | + "icon": null, |
167 | + "mugshot": null, |
168 | + "hide_email_addresses": false, |
169 | + "creation_rationale": null, |
170 | + "creation_comment": null, |
171 | + "registrant": null, |
172 | + "logo": null, |
173 | + "renewal_policy": 10, |
174 | + "personal_standing": 0, |
175 | + "personal_standing_reason": null, |
176 | + "mail_resumption_date": null, |
177 | + "mailing_list_auto_subscribe_policy": 1, |
178 | + "mailing_list_receive_duplicates": true, |
179 | + "visibility": 1, |
180 | + "verbose_bugnotifications": false |
181 | + } |
182 | + }, |
183 | + { |
184 | + "pk": 19, |
185 | + "model": "identityprovider.person", |
186 | + "fields": { |
187 | + "displayname": "ISD Team", |
188 | + "teamowner": 1, |
189 | + "teamdescription": "This Team is responsible for ISD", |
190 | + "name": "isd-team", |
191 | + "language": null, |
192 | + "fti": "'team':3A,5A 'isd':2A,4A 'isd-team':1A", |
193 | + "defaultmembershipperiod": null, |
194 | + "defaultrenewalperiod": null, |
195 | + "subscriptionpolicy": 1, |
196 | + "merged": null, |
197 | + "datecreated": "2005-06-06 08:59:51.60576", |
198 | + "addressline1": null, |
199 | + "addressline2": null, |
200 | + "organization": null, |
201 | + "city": null, |
202 | + "province": null, |
203 | + "country": null, |
204 | + "postcode": null, |
205 | + "phone": null, |
206 | + "homepage_content": null, |
207 | + "icon": null, |
208 | + "mugshot": null, |
209 | + "hide_email_addresses": false, |
210 | + "creation_rationale": null, |
211 | + "creation_comment": null, |
212 | + "registrant": null, |
213 | + "logo": null, |
214 | + "renewal_policy": 10, |
215 | + "personal_standing": 0, |
216 | + "personal_standing_reason": null, |
217 | + "mail_resumption_date": null, |
218 | + "mailing_list_auto_subscribe_policy": 1, |
219 | + "mailing_list_receive_duplicates": true, |
220 | + "visibility": 1, |
221 | + "verbose_bugnotifications": false |
222 | + } |
223 | + }, |
224 | + { |
225 | "pk": 243610, |
226 | "model": "identityprovider.person", |
227 | "fields": { |
228 | @@ -501,6 +605,22 @@ |
229 | } |
230 | }, |
231 | { |
232 | + "pk": 2, |
233 | + "model": "identityprovider.teamparticipation", |
234 | + "fields": { |
235 | + "team": 18, |
236 | + "person": 1 |
237 | + } |
238 | + }, |
239 | + { |
240 | + "pk": 3, |
241 | + "model": "identityprovider.teamparticipation", |
242 | + "fields": { |
243 | + "team": 19, |
244 | + "person": 1 |
245 | + } |
246 | + }, |
247 | + { |
248 | "pk": 158, |
249 | "model": "identityprovider.teamparticipation", |
250 | "fields": { |
251 | @@ -524,7 +644,14 @@ |
252 | "person": 243610 |
253 | } |
254 | }, |
255 | - |
256 | + { |
257 | + "pk": 164, |
258 | + "model": "identityprovider.teamparticipation", |
259 | + "fields": { |
260 | + "team": 18, |
261 | + "person": 243610 |
262 | + } |
263 | + }, |
264 | { |
265 | "pk": 5, |
266 | "model": "identityprovider.openidrpsummary", |
267 | |
268 | === modified file 'identityprovider/forms.py' |
269 | --- identityprovider/forms.py 2010-09-21 22:04:10 +0000 |
270 | +++ identityprovider/forms.py 2011-07-05 15:46:33 +0000 |
271 | @@ -5,17 +5,33 @@ |
272 | |
273 | from django import forms |
274 | from django.contrib import auth |
275 | -from django.forms import fields, widgets |
276 | +from django.forms import Form, fields, widgets |
277 | +from django.utils import translation |
278 | from django.utils.safestring import mark_safe |
279 | from django.utils.translation import ugettext as _ |
280 | |
281 | -from identityprovider.models import Account, EmailAddress, verify_token_string |
282 | +from identityprovider.const import ( |
283 | + SREG_DATA_FIELDS_ORDER, |
284 | + SREG_LABELS, |
285 | +) |
286 | +from identityprovider.models import ( |
287 | + Account, |
288 | + EmailAddress, |
289 | + verify_token_string, |
290 | + TeamMembership, |
291 | +) |
292 | from identityprovider.models.const import EmailStatus |
293 | from identityprovider.utils import ( |
294 | - get_person_and_account_by_email, CannotResetPasswordException, |
295 | - PersonAndAccountNotFoundException, encrypt_launchpad_password, |
296 | - password_policy_compliant) |
297 | -from identityprovider.widgets import ROAwareTextInput, ROAwareSelect |
298 | + get_person_and_account_by_email, |
299 | + CannotResetPasswordException, |
300 | + PersonAndAccountNotFoundException, |
301 | + encrypt_launchpad_password, |
302 | + password_policy_compliant, |
303 | +) |
304 | +from identityprovider.widgets import ( |
305 | + ROAwareTextInput, |
306 | + ROAwareSelect, |
307 | +) |
308 | |
309 | logger = logging.getLogger('sso') |
310 | |
311 | @@ -289,3 +305,159 @@ |
312 | class PreAuthorizeForm(forms.Form): |
313 | trust_root = forms.CharField(error_messages=default_errors) |
314 | callback = forms.CharField(error_messages=default_errors) |
315 | + |
316 | + |
317 | +class SRegRequestForm(Form): |
318 | + """A form object for user control over OpenID sreg data. |
319 | + """ |
320 | + fields = {} |
321 | + |
322 | + @property |
323 | + def data_approved_by_user(self): |
324 | + """Get the list of sreg data actually approved by the user for the |
325 | + request. |
326 | + """ |
327 | + if self.request_method == 'POST': |
328 | + return dict([(f, self.data[f]) for f in self.data |
329 | + if self.field_approved(f)]) |
330 | + else: |
331 | + return {} |
332 | + |
333 | + def __init__(self, request, sreg_request, rpconfig, approved_data=None): |
334 | + self.request = request |
335 | + self.request_method = request.META.get('REQUEST_METHOD') |
336 | + self.sreg_request = sreg_request |
337 | + self.rpconfig = rpconfig |
338 | + self.approved_data = approved_data |
339 | + sreg_fields = [f for f in SREG_DATA_FIELDS_ORDER if f in set( |
340 | + self.sreg_request.required + self.sreg_request.optional)] |
341 | + self.data = self._get_sreg_data_for_user(request.user, sreg_fields) |
342 | + |
343 | + super(SRegRequestForm, self).__init__(self.data) |
344 | + self._init_fields(self.data) |
345 | + self.should_display = len(self.data) > 0 |
346 | + |
347 | + def _get_sreg_data_for_user(self, user, sreg_fields): |
348 | + """Get the sreg data to ask about in the form based on the user's |
349 | + account record. |
350 | + """ |
351 | + values = {} |
352 | + values['fullname'] = user.displayname |
353 | + if user.preferredemail is not None: |
354 | + values['email'] = user.preferredemail.email |
355 | + if user.person is not None: |
356 | + values['nickname'] = user.person.name |
357 | + if user.person.time_zone is not None: |
358 | + values['timezone'] = user.person.time_zone |
359 | + if user.preferredlanguage is not None: |
360 | + values['language'] = user.preferredlanguage |
361 | + else: |
362 | + values['language'] = translation.get_language_from_request( |
363 | + self.request) |
364 | + logger.debug("values (sreg_fields) = " + str(values)) |
365 | + |
366 | + return dict([(f, values[f]) for f in sreg_fields if f in values]) |
367 | + |
368 | + def _init_fields(self, data): |
369 | + """Initialises form fields for the user's sreg data. |
370 | + """ |
371 | + for key, val in data.items(): |
372 | + label = "%s: %s" % (SREG_LABELS.get(key, key), val) |
373 | + attrs = {} |
374 | + if key in self.sreg_request.required: |
375 | + attrs['class'] = 'required' |
376 | + if self.rpconfig is not None: |
377 | + attrs['disabled'] = 'disabled' |
378 | + self.fields[key] = fields.BooleanField( |
379 | + label=label, widget=forms.CheckboxInput(attrs=attrs, |
380 | + check_test=self.check_test)) |
381 | + |
382 | + def check_test(self, value): |
383 | + """Determines if a checkbox should be pre-checked based on previously |
384 | + approved user data, openid request and relying party type. |
385 | + """ |
386 | + for k, v in self.data.items(): |
387 | + if value == v: |
388 | + value = k |
389 | + break |
390 | + |
391 | + if self.rpconfig and value in self.sreg_request.required: |
392 | + return True |
393 | + elif (self.approved_data and |
394 | + value in self.approved_data.get('requested', [])): |
395 | + return value in self.approved_data.get('approved', []) |
396 | + elif self.rpconfig: |
397 | + return True |
398 | + else: |
399 | + return value in self.sreg_request.required |
400 | + |
401 | + def field_approved(self, field): |
402 | + """Check if the field should be returned in the response based on user |
403 | + preferences and overridden for trusted relying parties. |
404 | + """ |
405 | + if self.rpconfig is not None and field in self.sreg_request.required: |
406 | + return True |
407 | + elif field in self.request.POST: |
408 | + return True |
409 | + else: |
410 | + return False |
411 | + |
412 | + |
413 | +class TeamsRequestForm(Form): |
414 | + """A form object for user control over OpenID teams data. |
415 | + """ |
416 | + |
417 | + fields = {} |
418 | + |
419 | + @property |
420 | + def teams_approved_by_user(self): |
421 | + """Get the list of teams actually approved by the user for the request. |
422 | + """ |
423 | + if self.request_method == 'POST': |
424 | + return [t for t in self.data if t in self.request.POST] |
425 | + else: |
426 | + return [] |
427 | + |
428 | + def __init__(self, request, teams_request, rpconfig, approved_data=None): |
429 | + self.request = request |
430 | + self.request_method = request.META.get('REQUEST_METHOD') |
431 | + self.teams_request = teams_request |
432 | + self.rpconfig = rpconfig |
433 | + self.approved_data = approved_data |
434 | + self.data = self._get_teams_for_user(request.user, |
435 | + rpconfig and rpconfig.can_query_any_team) |
436 | + |
437 | + super(TeamsRequestForm, self).__init__(self.data) |
438 | + self._init_fields(self.data) |
439 | + all_teams = self.teams_request.allRequestedTeams() |
440 | + membership = TeamMembership.get_team_memberships_for_user( |
441 | + all_teams, self.request.user, |
442 | + self.rpconfig and self.rpconfig.can_query_any_team) |
443 | + self.should_display = len(membership) > 0 |
444 | + |
445 | + def _get_teams_for_user(self, user, include_private=False): |
446 | + """Get the list of teams to ask about in the form based on the user's |
447 | + team membership. |
448 | + """ |
449 | + all_teams = self.teams_request.allRequestedTeams() |
450 | + return dict([(t, t) for t in |
451 | + TeamMembership.get_team_memberships_for_user(all_teams, user, |
452 | + include_private)]) |
453 | + |
454 | + def _init_fields(self, form_data): |
455 | + """Initialises form fields for the user's team memberships. |
456 | + """ |
457 | + for team in form_data: |
458 | + self.fields[team] = fields.BooleanField( |
459 | + label=team, widget=forms.CheckboxInput( |
460 | + check_test=self.check_test)) |
461 | + |
462 | + def check_test(self, value): |
463 | + """Determines if a checkbox should be pre-checked based on previously |
464 | + approved user data and relying party type. |
465 | + """ |
466 | + if (self.approved_data and |
467 | + value in self.approved_data.get('requested', [])): |
468 | + return value in self.approved_data.get('approved', []) |
469 | + else: |
470 | + return self.rpconfig != None |
471 | |
472 | === modified file 'identityprovider/media/ubuntu/narrow.css' |
473 | --- identityprovider/media/ubuntu/narrow.css 2010-09-18 05:38:59 +0000 |
474 | +++ identityprovider/media/ubuntu/narrow.css 2011-07-05 15:46:33 +0000 |
475 | @@ -36,7 +36,12 @@ |
476 | margin: 0px; |
477 | } |
478 | |
479 | -#mainbar form input[type="text"], #mainbar form input[type="password"], form textarea, #content input { |
480 | +#mainbar form input[type="text"], |
481 | +#mainbar form input[type="password"], |
482 | +form textarea, |
483 | +#content input[type="text"], |
484 | +#content input[type="password"] |
485 | +{ |
486 | width: 96%; |
487 | padding: 2px; |
488 | } |
489 | |
490 | === modified file 'identityprovider/media/ubuntu/styles.css' |
491 | --- identityprovider/media/ubuntu/styles.css 2010-11-16 15:02:08 +0000 |
492 | +++ identityprovider/media/ubuntu/styles.css 2011-07-05 15:46:33 +0000 |
493 | @@ -249,7 +249,14 @@ |
494 | ul li:last-child { |
495 | margin-bottom: 0; |
496 | } |
497 | - |
498 | +ul.teams-list { |
499 | + list-style: none; |
500 | + margin-left: 20px; |
501 | + margin-top: 5px; |
502 | +} |
503 | +.required + label { |
504 | + font-weight: bold; |
505 | +} |
506 | p.call-to-action { |
507 | color: #333; |
508 | } |
509 | @@ -711,7 +718,12 @@ |
510 | margin: 0px; |
511 | } |
512 | |
513 | - #mainbar form input[type="text"], #mainbar form input[type="password"], form textarea, #content input { |
514 | + #mainbar form input[type="text"], |
515 | + #mainbar form input[type="password"], |
516 | + form textarea, |
517 | + #content input[type="text"], |
518 | + #content input[type="password"] |
519 | + { |
520 | width: 96%; |
521 | padding: 2%; |
522 | } |
523 | |
524 | === modified file 'identityprovider/models/account.py' |
525 | --- identityprovider/models/account.py 2011-05-12 13:44:51 +0000 |
526 | +++ identityprovider/models/account.py 2011-07-05 15:46:33 +0000 |
527 | @@ -271,7 +271,6 @@ |
528 | except Consumer.DoesNotExist: |
529 | return [] |
530 | |
531 | - |
532 | def save(self, force_insert=False, force_update=False, **kwargs): |
533 | if settings.READ_ONLY_MODE: |
534 | return |
535 | @@ -321,4 +320,4 @@ |
536 | verbose_name_plural = _("LP OpenID Identifiers") |
537 | |
538 | def __unicode__(self): |
539 | - return _("LP OpenID Idetifier for %s") % unicode(self.account) |
540 | + return _("LP OpenID Identifier for %s") % unicode(self.lp_account) |
541 | |
542 | === modified file 'identityprovider/models/openidmodels.py' |
543 | --- identityprovider/models/openidmodels.py 2010-11-16 14:11:00 +0000 |
544 | +++ identityprovider/models/openidmodels.py 2011-07-05 15:46:33 +0000 |
545 | @@ -12,6 +12,7 @@ |
546 | |
547 | from django.conf import settings |
548 | from django.db import models |
549 | +from django.utils import simplejson as json |
550 | from django.utils.translation import ugettext_lazy as _ |
551 | |
552 | from identityprovider.const import NEVER_EXPIRES, SREG_LABELS |
553 | @@ -139,7 +140,7 @@ |
554 | select={'len': 'length(trust_root)'}, |
555 | where=[condition], |
556 | params=(url, url), |
557 | - order_by=['-len'] # Select the longest match |
558 | + order_by=['-len'] # Select the longest match |
559 | ) |
560 | return q[0] if q else None |
561 | |
562 | @@ -177,7 +178,8 @@ |
563 | |
564 | class OpenIDRPSummaryManager(models.Manager): |
565 | |
566 | - def record(self, account, trust_root, openid_identifier=None): |
567 | + def record(self, account, trust_root, openid_identifier=None, |
568 | + approved_data=None): |
569 | if settings.READ_ONLY_MODE: |
570 | return None |
571 | if openid_identifier is None: |
572 | @@ -193,6 +195,8 @@ |
573 | account=account, |
574 | trust_root=trust_root, |
575 | openid_identifier=openid_identifier) |
576 | + if approved_data: |
577 | + summary.set_approved_data(approved_data) |
578 | return summary |
579 | |
580 | |
581 | @@ -205,6 +209,7 @@ |
582 | date_last_used = models.DateTimeField(default=datetime.datetime.utcnow, |
583 | blank=True, editable=False) |
584 | total_logins = models.IntegerField(default=1) |
585 | + approved_data = models.TextField(blank=True, null=True, default='') |
586 | |
587 | objects = OpenIDRPSummaryManager() |
588 | |
589 | @@ -218,6 +223,18 @@ |
590 | self.date_last_used = datetime.datetime.utcnow() |
591 | self.save() |
592 | |
593 | + def get_approved_data(self): |
594 | + try: |
595 | + data = json.loads(self.approved_data) |
596 | + assert(type(data) is dict) |
597 | + return data |
598 | + except (AssertionError, TypeError, ValueError): |
599 | + return None |
600 | + |
601 | + def set_approved_data(self, approved_data): |
602 | + self.approved_data = json.dumps(approved_data) |
603 | + self.save() |
604 | + |
605 | |
606 | class DjangoOpenIDStore(OpenIDStore): |
607 | """ |
608 | |
609 | === modified file 'identityprovider/models/team.py' |
610 | --- identityprovider/models/team.py 2010-04-21 15:29:24 +0000 |
611 | +++ identityprovider/models/team.py 2011-07-05 15:46:33 +0000 |
612 | @@ -3,6 +3,7 @@ |
613 | |
614 | from django.db import models |
615 | |
616 | +from identityprovider.const import PERSON_VISIBILITY_PUBLIC |
617 | from identityprovider.models import Person |
618 | |
619 | __all__ = ( |
620 | @@ -47,6 +48,24 @@ |
621 | db_table = u'teammembership' |
622 | unique_together = ('person', 'team') |
623 | |
624 | + @staticmethod |
625 | + def get_team_memberships_for_user(team_names, user, include_private=False): |
626 | + memberships = [] |
627 | + for team_name in team_names: |
628 | + try: |
629 | + team = Person.objects.get(name=team_name) |
630 | + except Person.DoesNotExist: |
631 | + team = None |
632 | + if team is None or not team.is_team(): |
633 | + continue |
634 | + # Control access to private teams |
635 | + if (team.visibility != PERSON_VISIBILITY_PUBLIC and |
636 | + not include_private): |
637 | + continue |
638 | + if user.person_in_team(team): |
639 | + memberships.append(team_name) |
640 | + return memberships |
641 | + |
642 | |
643 | class TeamParticipation(models.Model): |
644 | team = models.ForeignKey(Person, db_column='team', |
645 | |
646 | === modified file 'identityprovider/teams.py' |
647 | --- identityprovider/teams.py 2010-04-21 15:29:24 +0000 |
648 | +++ identityprovider/teams.py 2011-07-05 15:46:33 +0000 |
649 | @@ -39,8 +39,10 @@ |
650 | @since: 2.1.1 |
651 | """ |
652 | |
653 | -from openid.message import registerNamespaceAlias, \ |
654 | - NamespaceAliasRegistrationError |
655 | +from openid.message import ( |
656 | + registerNamespaceAlias, |
657 | + NamespaceAliasRegistrationError, |
658 | +) |
659 | from openid.extension import Extension |
660 | from openid import oidutil |
661 | |
662 | |
663 | === modified file 'identityprovider/templates/decide.html' |
664 | --- identityprovider/templates/decide.html 2011-05-11 13:02:21 +0000 |
665 | +++ identityprovider/templates/decide.html 2011-07-05 15:46:33 +0000 |
666 | @@ -6,6 +6,11 @@ |
667 | GNU Affero General Public License version 3 (see the file LICENSE). |
668 | {% endcomment %} |
669 | |
670 | +{% block extra_header %} |
671 | +- <script src="http://yui.yahooapis.com/3.3.0/build/yui/yui-min.js"></script> |
672 | +{% endblock %} |
673 | + |
674 | + |
675 | {% block title %} |
676 | {% blocktrans %}Authenticate to {{ trust_root }}{% endblocktrans %} |
677 | {% endblock %} |
678 | @@ -27,25 +32,35 @@ |
679 | {% blocktrans %}If you proceed, the following information will be available to the website:{% endblocktrans %} |
680 | {% endif %} |
681 | |
682 | + <div class="actions"> |
683 | + <form action="{{ action }}" method="POST" name="decideform"> |
684 | + {% csrf_token %} |
685 | <div class="info-items"> |
686 | - {% if sreg_data or teams_data %} |
687 | + {% if sreg_form.should_display or teams_form.should_display %} |
688 | <ul class="list"> |
689 | - {% for key, value in sreg_data %} |
690 | - <li>{{ key }}: <b>{{ value }}</b></li> |
691 | - {% endfor %} |
692 | - {% if teams_data %} |
693 | - <li>{% trans "Team membership"%}: <b>{{ teams_data|join:", " }}</b></li> |
694 | - {% endif %} |
695 | + {% for field in sreg_form %} |
696 | + <li>{{ field|safe }} {{ field.label_tag }}</li> |
697 | + {% endfor %} |
698 | + {% if teams_form.should_display %} |
699 | + {% ifequal teams_form.fields|length 1 %} |
700 | + {% for field in teams_form %} |
701 | + <li>{{ field|safe }} {% trans "Team membership:"%} {{ field.label_tag }}</li> |
702 | + {% endfor %} |
703 | + {% else %} |
704 | + <li id="teamslist_item">{% trans "Team membership:"%} |
705 | + <ul class="teams-list" id="teamslist"> |
706 | + {% for field in teams_form %} |
707 | + <li>{{ field|safe }} {{ field.label_tag }}</li> |
708 | + {% endfor %} |
709 | + </ul> |
710 | + </li> |
711 | + {% endifequal %} |
712 | + {% endif %} |
713 | </ul> |
714 | - {% else %} |
715 | - <p>{% trans "Only your identity URL" %}</p> |
716 | {% endif %} |
717 | </div> |
718 | </div> |
719 | |
720 | - <div class="actions"> |
721 | - <form action="{{ action }}" method="POST" name="decideform"> |
722 | - {% csrf_token %} |
723 | <p> |
724 | <button type="submit" class="btn" name="yes"><span><span>{% trans "Yes, sign me in" %}</span></span></button> |
725 | {% trans "or" %} |
726 | @@ -54,6 +69,78 @@ |
727 | </form> |
728 | <script type="text/javascript"> |
729 | document.decideform.yes.focus(); |
730 | + |
731 | + YUI().use('anim-base', function(Y) { |
732 | + var anim = new Y.Anim({ |
733 | + node: '#teamslist', |
734 | + duration: 0.15, |
735 | + to: { height: 0 }, |
736 | + }); |
737 | + |
738 | + var onClick = function(e) { |
739 | + e.preventDefault(); |
740 | + Y.one('#reveal_wrapper').remove(); |
741 | + node = Y.one('#teamslist'); |
742 | + node.setStyle('height', '0'); |
743 | + node.setStyle('display', 'block'); |
744 | + anim.setAttrs({ 'to': { height: teamslist_height } }); |
745 | + anim.run(); |
746 | + }; |
747 | + |
748 | + var setTeamStates = function(e) { |
749 | + state = e.currentTarget.get('checked'); |
750 | + nl = Y.all('#teamslist li input').set('checked', state); |
751 | + } |
752 | + |
753 | + var refreshTeamStates = function(e) { |
754 | + nl = Y.all('#teamslist li input'); |
755 | + checked = 0; |
756 | + total = nl.size(); |
757 | + for (i = 0; i < nl.size(); i++) { |
758 | + if (nl.item(i).get('checked')) { |
759 | + checked ++; |
760 | + } |
761 | + } |
762 | + if (checked == 0) { |
763 | + Y.one('#checkme').set('checked', false); |
764 | + } else { |
765 | + Y.one('#checkme').set('checked', true); |
766 | + } |
767 | + } |
768 | + |
769 | + Y.on("domready", function() { |
770 | + // don't error out because no teams matched |
771 | + if (Y.one('#teamslist_item') == null) { |
772 | + return; |
773 | + } |
774 | + Y.one('#teamslist_item').prepend('<input type="checkbox" id="checkme" /> '); |
775 | + Y.one('#checkme').on('change', setTeamStates); |
776 | + Y.all('#teamslist li').on('change', refreshTeamStates); |
777 | + refreshTeamStates(); |
778 | + |
779 | + nl = Y.all('#teamslist li input'); |
780 | + checked = 0; |
781 | + total = nl.size(); |
782 | + for (i = 0; i < nl.size(); i++) { |
783 | + if (nl.item(i).get('checked')) { |
784 | + checked ++; |
785 | + } |
786 | + } |
787 | + if (checked == total || checked == 0) { |
788 | + node = Y.one('#teamslist'); |
789 | + teamslist_height = parseInt(node.getComputedStyle('height')); |
790 | + node.setStyle('display', 'none'); |
791 | + buf = new Array(); |
792 | + c = node.get('children'); |
793 | + for (i = 0; i < c.size(); i++) { |
794 | + buf.push(c.item(i).get('text')); |
795 | + } |
796 | + node.insert('<span id="reveal_wrapper">' + buf.join(', ') + ' <small style="font-size: 80%;">(<a href="#" id="reveal">details</a>)</small></span>', "before"); |
797 | + Y.one('#reveal').on('click', onClick); |
798 | + } |
799 | + }); |
800 | + }); |
801 | + |
802 | </script> |
803 | </div> |
804 | <br style="clear: both" /> |
805 | |
806 | === modified file 'identityprovider/tests/test_admin.py' |
807 | --- identityprovider/tests/test_admin.py 2011-01-03 21:58:15 +0000 |
808 | +++ identityprovider/tests/test_admin.py 2011-07-05 15:46:33 +0000 |
809 | @@ -19,6 +19,7 @@ |
810 | class AdminTestCase(BasicAccountTestCase): |
811 | |
812 | fixtures = ['admin.json', 'test.json'] |
813 | + pgsql_functions = ['generate_openid_identifier'] |
814 | |
815 | def setUp(self): |
816 | self.disable_csrf() |
817 | @@ -41,8 +42,10 @@ |
818 | 'allowed_sreg': 'fullname', |
819 | 'creation_rationale': '13', |
820 | } |
821 | - response = self.client.get('/admin/identityprovider/openidrpconfig/add/') |
822 | - response = self.client.post('/admin/identityprovider/openidrpconfig/add/', |
823 | + response = self.client.get( |
824 | + '/admin/identityprovider/openidrpconfig/add/') |
825 | + response = self.client.post( |
826 | + '/admin/identityprovider/openidrpconfig/add/', |
827 | data) |
828 | self.assertEquals(302, response.status_code) |
829 | # We don't get the ID back, so continue on to the next test to |
830 | |
831 | === modified file 'identityprovider/tests/test_forms.py' |
832 | --- identityprovider/tests/test_forms.py 2011-02-20 23:49:21 +0000 |
833 | +++ identityprovider/tests/test_forms.py 2011-07-05 15:46:33 +0000 |
834 | @@ -4,10 +4,20 @@ |
835 | # GNU Affero General Public License version 3 (see the file LICENSE). |
836 | |
837 | from django.conf import settings |
838 | +from django.http import HttpRequest |
839 | +from django_openid_auth.teams import TeamsRequest |
840 | +from openid.extensions.sreg import SRegRequest |
841 | + |
842 | from identityprovider.models.account import Account |
843 | -from identityprovider.forms import EditAccountForm, ResetPasswordForm |
844 | -from identityprovider.api10 import forms |
845 | +from identityprovider.forms import ( |
846 | + EditAccountForm, |
847 | + ResetPasswordForm, |
848 | + SRegRequestForm, |
849 | + TeamsRequestForm, |
850 | +) |
851 | from identityprovider.api10.forms import WebserviceCreateAccountForm |
852 | +from identityprovider.models.openidmodels import OpenIDRPConfig |
853 | + |
854 | from utils import BasicAccountTestCase, SQLCachedTestCase |
855 | |
856 | |
857 | @@ -51,7 +61,7 @@ |
858 | account = Account.objects.get(openid_identifier='name12_oid') |
859 | form = EditAccountForm(account=account, |
860 | data={'password': 'tooweak', |
861 | - 'passwordconfirm' : 'other'}) |
862 | + 'passwordconfirm': 'other'}) |
863 | self.assertFalse(form.is_valid()) |
864 | self.assertEqual(1, len(form.errors['password'])) |
865 | self.assertFalse('passwordconfirm' in form.errors) |
866 | @@ -61,7 +71,7 @@ |
867 | account = Account.objects.get(openid_identifier='name12_oid') |
868 | form = EditAccountForm(account=account, |
869 | data={'password': 'tooweak', |
870 | - 'passwordconfirm' : 'tooweak'}) |
871 | + 'passwordconfirm': 'tooweak'}) |
872 | self.assertFalse(form.is_valid()) |
873 | self.assertEqual(1, len(form.errors['password'])) |
874 | self.assertFalse('passwordconfirm' in form.errors) |
875 | @@ -94,3 +104,249 @@ |
876 | self.assertFalse(form.is_valid()) |
877 | self.assertEqual(form.errors['password'][0], |
878 | 'Invalid characters in password') |
879 | + |
880 | + |
881 | +class SRegRequestFormTest(SQLCachedTestCase): |
882 | + pgsql_functions = ['generate_openid_identifier'] |
883 | + |
884 | + def _get_request_with_post_args(self, args={}): |
885 | + request = HttpRequest() |
886 | + request.user = self.test_user |
887 | + request.POST = args |
888 | + request.META = {'REQUEST_METHOD': 'POST'} |
889 | + return request |
890 | + |
891 | + def setUp(self): |
892 | + self.test_user = Account.objects.create_account('My name', |
893 | + 'me@test.com', 'password') |
894 | + self.rpconfig = OpenIDRPConfig.objects.create( |
895 | + trust_root='http://localhost/', description="Some description") |
896 | + |
897 | + def test_no_approved_fields_without_post_request(self): |
898 | + """The server should not generate a list of approved fields when the |
899 | + request is not a POST request. |
900 | + """ |
901 | + request = self._get_request_with_post_args() |
902 | + request.META['REQUEST_METHOD'] = 'GET' |
903 | + form = SRegRequestForm( |
904 | + request, |
905 | + SRegRequest(required=['fullname', 'email']), |
906 | + self.rpconfig) |
907 | + self.assertEqual(len(form.data_approved_by_user), 0) |
908 | + |
909 | + def test_required_fields_for_trusted_site(self): |
910 | + """The server should always return values for required fields to trusted |
911 | + sites, regardless of the state of the checkbox in the UI. Optional |
912 | + fields should not be returned if the user has unchecked them. |
913 | + """ |
914 | + form = SRegRequestForm( |
915 | + self._get_request_with_post_args(), |
916 | + SRegRequest(required=['fullname'], optional=['email']), |
917 | + self.rpconfig) |
918 | + self.assertTrue('fullname' in form.data_approved_by_user) |
919 | + self.assertFalse('email' in form.data_approved_by_user) |
920 | + |
921 | + def test_optional_fields_for_trusted_site(self): |
922 | + """The server should return values for optional fields to trusted |
923 | + sites only when the user checks the checkbox in the UI. |
924 | + """ |
925 | + post_args = {'email': 'email'} |
926 | + form = SRegRequestForm( |
927 | + self._get_request_with_post_args(post_args), |
928 | + SRegRequest(optional=['fullname', 'email']), |
929 | + self.rpconfig) |
930 | + self.assertFalse('fullname' in form.data_approved_by_user) |
931 | + self.assertTrue('email' in form.data_approved_by_user) |
932 | + |
933 | + def test_required_fields_for_untrusted_site(self): |
934 | + """The server should return values for required fields to untrusted |
935 | + sites only when the user checks the checkbox in the UI. |
936 | + """ |
937 | + post_args = {'email': 'email'} |
938 | + form = SRegRequestForm( |
939 | + self._get_request_with_post_args(post_args), |
940 | + SRegRequest(required=['fullname', 'email']), |
941 | + None) |
942 | + self.assertFalse('fullname' in form.data_approved_by_user) |
943 | + self.assertTrue('email' in form.data_approved_by_user) |
944 | + |
945 | + def test_optional_fields_for_untrusted_site(self): |
946 | + """The server should return values for optional fields to untrusted |
947 | + sites only when the user checks the checkbox in the UI. |
948 | + """ |
949 | + post_args = {'fullname': 'fullname'} |
950 | + form = SRegRequestForm( |
951 | + self._get_request_with_post_args(post_args), |
952 | + SRegRequest(optional=['fullname', 'email']), |
953 | + None) |
954 | + self.assertTrue('fullname' in form.data_approved_by_user) |
955 | + self.assertFalse('email' in form.data_approved_by_user) |
956 | + |
957 | + def test_checkbox_status_for_trusted_site(self): |
958 | + """Checkboxes are always checked if the site is trusted |
959 | + """ |
960 | + form = SRegRequestForm( |
961 | + self._get_request_with_post_args(), |
962 | + SRegRequest(required=['fullname'], optional=['email']), |
963 | + self.rpconfig) |
964 | + self.assertTrue(form.check_test('fullname')) |
965 | + self.assertTrue(form.check_test('email')) |
966 | + |
967 | + def test_checkbox_status_for_trusted_site_with_approved_data(self): |
968 | + """If the user has previously approved sending data to a trusted site |
969 | + the same checkbox settings should be returned on the next request unless |
970 | + those conflict with the required fields. |
971 | + """ |
972 | + approved_data = { |
973 | + 'requested': ['fullname', 'email'], |
974 | + 'approved': ['email']} |
975 | + form1 = SRegRequestForm( |
976 | + self._get_request_with_post_args(), |
977 | + SRegRequest(required=['fullname'], optional=['email']), |
978 | + self.rpconfig, approved_data=approved_data) |
979 | + self.assertTrue(form1.check_test('fullname')) |
980 | + self.assertTrue(form1.check_test('email')) |
981 | + |
982 | + approved_data['approved'] = [] |
983 | + form2 = SRegRequestForm( |
984 | + self._get_request_with_post_args(), |
985 | + SRegRequest(required=['fullname'], optional=['email']), |
986 | + self.rpconfig, approved_data=approved_data) |
987 | + self.assertFalse(form2.check_test('email')) |
988 | + |
989 | + def test_checkbox_status_for_untrusted_site(self): |
990 | + """Checkboxes are only checked on untrusted site requests if the field |
991 | + is required |
992 | + """ |
993 | + form = SRegRequestForm( |
994 | + self._get_request_with_post_args(), |
995 | + SRegRequest(required=['fullname'], optional=['email']), |
996 | + None) |
997 | + self.assertTrue(form.check_test('fullname')) |
998 | + self.assertFalse(form.check_test('email')) |
999 | + |
1000 | + def test_checkbox_status_for_untrusted_site_with_approved_data(self): |
1001 | + """If the user has previously approved sending data to an untrusted site |
1002 | + the same checkbox settings should be returned on the next request. |
1003 | + """ |
1004 | + approved_data = { |
1005 | + 'requested': ['fullname', 'email'], |
1006 | + 'approved': ['email']} |
1007 | + form = SRegRequestForm( |
1008 | + self._get_request_with_post_args(), |
1009 | + SRegRequest(required=['fullname'], optional=['email']), |
1010 | + None, approved_data=approved_data) |
1011 | + self.assertFalse(form.check_test('fullname')) |
1012 | + self.assertTrue(form.check_test('email')) |
1013 | + |
1014 | + |
1015 | +class TeamsRequestFormTest(BasicAccountTestCase): |
1016 | + |
1017 | + fixtures = ["test"] |
1018 | + |
1019 | + def _get_request_with_post_args(self, args={}): |
1020 | + request = HttpRequest() |
1021 | + request.user = self.account |
1022 | + request.POST = args |
1023 | + request.META = {'REQUEST_METHOD': 'POST'} |
1024 | + return request |
1025 | + |
1026 | + def setUp(self): |
1027 | + self.account = Account.objects.get(pk=1) |
1028 | + self.rpconfig = OpenIDRPConfig.objects.create( |
1029 | + trust_root='http://localhost/', description="Some description", |
1030 | + can_query_any_team=True) |
1031 | + |
1032 | + def test_selected_teams_for_trusted_sites(self): |
1033 | + """If a user checks a requested team in the form for a trusted consumer, |
1034 | + it should be in the list of teams approved by the user. |
1035 | + """ |
1036 | + post_args = {'ubuntu-team': 'ubuntu-team'} |
1037 | + form = TeamsRequestForm( |
1038 | + self._get_request_with_post_args(post_args), |
1039 | + TeamsRequest(query_membership=['ubuntu-team']), |
1040 | + self.rpconfig) |
1041 | + self.assertTrue('ubuntu-team' in form.teams_approved_by_user) |
1042 | + |
1043 | + def test_unselected_teams_for_trusted_sites(self): |
1044 | + """If a user unchecks a requested team in the form for a trusted |
1045 | + consumer, it should not be in the list of teams approved by the user. |
1046 | + """ |
1047 | + form = TeamsRequestForm( |
1048 | + self._get_request_with_post_args(), |
1049 | + TeamsRequest(query_membership=['ubuntu-team']), |
1050 | + self.rpconfig) |
1051 | + self.assertFalse('ubuntu-team' in form.teams_approved_by_user) |
1052 | + |
1053 | + def test_selected_teams_for_untrusted_sites(self): |
1054 | + """If a user checks a requested team in the form for an untrusted |
1055 | + consumer, it should be in the list of teams approved by the user. |
1056 | + """ |
1057 | + post_args = {'ubuntu-team': 'ubuntu-team'} |
1058 | + form = TeamsRequestForm( |
1059 | + self._get_request_with_post_args(post_args), |
1060 | + TeamsRequest(query_membership=['ubuntu-team']), |
1061 | + None) |
1062 | + self.assertTrue('ubuntu-team' in form.teams_approved_by_user) |
1063 | + |
1064 | + def test_unselected_teams_for_untrusted_sites(self): |
1065 | + """If a user unchecks a requested team in the form for an untrusted |
1066 | + consumer, it should not be in the list of teams approved by the user. |
1067 | + """ |
1068 | + form = TeamsRequestForm( |
1069 | + self._get_request_with_post_args(), |
1070 | + TeamsRequest(query_membership=['ubuntu-team']), |
1071 | + None) |
1072 | + self.assertFalse('ubuntu-team' in form.teams_approved_by_user) |
1073 | + |
1074 | + def test_checkbox_status_for_trusted_site(self): |
1075 | + """Checkboxes should always be checked by default for trusted sites. |
1076 | + """ |
1077 | + form = TeamsRequestForm( |
1078 | + self._get_request_with_post_args(), |
1079 | + TeamsRequest(query_membership=['ubuntu-team']), |
1080 | + self.rpconfig) |
1081 | + self.assertTrue(form.check_test('ubuntu-team')) |
1082 | + |
1083 | + def test_checkbox_status_for_trusted_site_with_approved_data(self): |
1084 | + """Checkboxes should respect user preferences on trusted sites where |
1085 | + available. |
1086 | + """ |
1087 | + approved_data = { |
1088 | + 'requested': ['ubuntu-team', 'myteam'], |
1089 | + 'approved': ['myteam']} |
1090 | + form = TeamsRequestForm( |
1091 | + self._get_request_with_post_args(), |
1092 | + TeamsRequest(query_membership=['ubuntu-team', 'myteam']), |
1093 | + self.rpconfig, approved_data=approved_data) |
1094 | + self.assertFalse(form.check_test('ubuntu-team')) |
1095 | + self.assertTrue(form.check_test('myteam')) |
1096 | + |
1097 | + def test_checkbox_status_for_untrusted_site(self): |
1098 | + """Checkboxes should always be checked by default for trusted sites. |
1099 | + """ |
1100 | + form = TeamsRequestForm( |
1101 | + self._get_request_with_post_args(), |
1102 | + TeamsRequest(query_membership=['ubuntu-team']), |
1103 | + None) |
1104 | + self.assertFalse(form.check_test('ubuntu-team')) |
1105 | + |
1106 | + def test_checkbox_status_for_untrusted_site_with_approved_data(self): |
1107 | + """Checkboxes should respect user preferences on untrusted sited where |
1108 | + available. |
1109 | + """ |
1110 | + approved_data = { |
1111 | + 'requested': ['ubuntu-team'], |
1112 | + 'approved': ['ubuntu-team']} |
1113 | + form1 = TeamsRequestForm( |
1114 | + self._get_request_with_post_args(), |
1115 | + TeamsRequest(query_membership=['ubuntu-team', 'myteam']), |
1116 | + None, approved_data=approved_data) |
1117 | + self.assertTrue(form1.check_test('ubuntu-team')) |
1118 | + |
1119 | + approved_data['approved'] = [] |
1120 | + form2 = TeamsRequestForm( |
1121 | + self._get_request_with_post_args(), |
1122 | + TeamsRequest(query_membership=['ubuntu-team', 'myteam']), |
1123 | + None, approved_data=approved_data) |
1124 | + self.assertFalse(form2.check_test('ubuntu-team')) |
1125 | |
1126 | === modified file 'identityprovider/tests/test_models_openidmodels.py' |
1127 | --- identityprovider/tests/test_models_openidmodels.py 2010-07-09 13:44:39 +0000 |
1128 | +++ identityprovider/tests/test_models_openidmodels.py 2011-07-05 15:46:33 +0000 |
1129 | @@ -158,7 +158,7 @@ |
1130 | self.expires = datetime.now() + timedelta(1) |
1131 | |
1132 | def create_auth(self): |
1133 | - return OpenIDAuthorization.objects.create( account=self.account, |
1134 | + return OpenIDAuthorization.objects.create(account=self.account, |
1135 | client_id=None, date_expires=self.expires, |
1136 | trust_root=self.trust_root) |
1137 | |
1138 | @@ -219,13 +219,19 @@ |
1139 | |
1140 | |
1141 | class OpenIDRPSummaryTestCase(BasicAccountTestCase): |
1142 | + approved_data = { |
1143 | + 'test1': {'one': [1, 2], 'two': [2, 3]}, |
1144 | + 'test2': {'three': [3, 4], 'four': [4, 5]}} |
1145 | + |
1146 | def setUp(self): |
1147 | self.account = Account.objects.get_by_email('test@canonical.com') |
1148 | self.trust_root = 'http://openid.launchpad.dev' |
1149 | |
1150 | def create_summary(self): |
1151 | - return OpenIDRPSummary.objects.create(account=self.account, |
1152 | + summary = OpenIDRPSummary.objects.create(account=self.account, |
1153 | trust_root=self.trust_root, openid_identifier='oid') |
1154 | + summary.set_approved_data(self.approved_data) |
1155 | + return summary |
1156 | |
1157 | def test_record_when_readonly(self): |
1158 | rm = ReadOnlyManager() |
1159 | @@ -244,6 +250,7 @@ |
1160 | def test_record_with_existing_and_no_openid_identifier(self): |
1161 | summary1 = self.create_summary() |
1162 | summary2 = OpenIDRPSummary.objects.record(self.account, self.trust_root) |
1163 | + summary2.set_approved_data(self.approved_data) |
1164 | self.assertEqual(summary1.total_logins, 1) |
1165 | self.assertEqual(summary2.total_logins, 1) |
1166 | self.assertNotEqual(summary1, summary2) |
1167 | @@ -258,7 +265,8 @@ |
1168 | def test_record_existing_different_oid(self): |
1169 | summary1 = self.create_summary() |
1170 | summary2 = OpenIDRPSummary.objects.record(self.account, |
1171 | - self.trust_root, openid_identifier='oid1') |
1172 | + self.trust_root, openid_identifier='oid1', |
1173 | + approved_data=self.approved_data) |
1174 | self.assertEqual(summary1.total_logins, 1) |
1175 | self.assertEqual(summary2.total_logins, 1) |
1176 | self.assertNotEqual(summary1, summary2) |
1177 | @@ -267,7 +275,6 @@ |
1178 | summary1 = OpenIDRPSummary.objects.record(self.account, self.trust_root, |
1179 | openid_identifier='oid') |
1180 | self.assertEqual(summary1.total_logins, 1) |
1181 | - |
1182 | summary2 = OpenIDRPSummary.objects.get(account=self.account, |
1183 | trust_root=self.trust_root, openid_identifier='oid') |
1184 | self.assertEqual(summary1, summary2) |
1185 | @@ -278,3 +285,10 @@ |
1186 | |
1187 | summary.increment() |
1188 | self.assertEqual(summary.total_logins, 2) |
1189 | + |
1190 | + def test_approved_data(self): |
1191 | + summary = self.create_summary() |
1192 | + summary.set_approved_data(self.approved_data) |
1193 | + summary2 = OpenIDRPSummary.objects.get(account=self.account, |
1194 | + trust_root=self.trust_root, openid_identifier='oid') |
1195 | + self.assertEqual(summary2.get_approved_data(), self.approved_data) |
1196 | |
1197 | === modified file 'identityprovider/tests/test_models_team.py' |
1198 | --- identityprovider/tests/test_models_team.py 2010-05-20 21:27:05 +0000 |
1199 | +++ identityprovider/tests/test_models_team.py 2011-07-05 15:46:33 +0000 |
1200 | @@ -1,9 +1,20 @@ |
1201 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
1202 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1203 | |
1204 | -from identityprovider.models.person import Person |
1205 | -from identityprovider.models.team import TeamParticipation |
1206 | -from identityprovider.tests.utils import SQLCachedTestCase |
1207 | +from identityprovider.const import ( |
1208 | + PERSON_VISIBILITY_PUBLIC, |
1209 | + PERSON_VISIBILITY_PRIVATE_MEMBERSHIP, |
1210 | +) |
1211 | +from identityprovider.models import ( |
1212 | + Account, |
1213 | + Person, |
1214 | + TeamMembership, |
1215 | + TeamParticipation, |
1216 | +) |
1217 | +from identityprovider.tests.utils import ( |
1218 | + BasicAccountTestCase, |
1219 | + SQLCachedTestCase, |
1220 | +) |
1221 | |
1222 | |
1223 | class TeamParticipationTestCase(SQLCachedTestCase): |
1224 | @@ -12,3 +23,46 @@ |
1225 | person = Person.objects.create(displayname='Person', name='person') |
1226 | tp = TeamParticipation.objects.create(team=team, person=person) |
1227 | self.assertEqual(unicode(tp), u'Person in Team') |
1228 | + |
1229 | + |
1230 | +class TeamMembershipTest(BasicAccountTestCase): |
1231 | + |
1232 | + fixtures = ["test"] |
1233 | + |
1234 | + def _set_team_to_private_membership(self, team_name): |
1235 | + self._set_team_privacy(team_name, PERSON_VISIBILITY_PRIVATE_MEMBERSHIP) |
1236 | + |
1237 | + def _set_team_to_public_membership(self, team_name): |
1238 | + self._set_team_privacy(team_name, PERSON_VISIBILITY_PUBLIC) |
1239 | + |
1240 | + def _set_team_privacy(self, team_name, privacy): |
1241 | + team = Person.objects.get(name=team_name) |
1242 | + team.visibility = privacy |
1243 | + team.save() |
1244 | + |
1245 | + def setUp(self): |
1246 | + self.account = Account.objects.get(pk=1) |
1247 | + |
1248 | + def test_get_team_memberships_on_personless_account(self): |
1249 | + account = Account.objects.create_account('test', 'x@example.com', |
1250 | + 'password') |
1251 | + memberships = TeamMembership.get_team_memberships_for_user( |
1252 | + ['ubuntu-team'], account, False) |
1253 | + self.assertEquals(memberships, []) |
1254 | + |
1255 | + def test_get_team_memberships_when_team_is_visible(self): |
1256 | + memberships = TeamMembership.get_team_memberships_for_user( |
1257 | + ['ubuntu-team'], self.account, False) |
1258 | + self.assertEquals(memberships, ['ubuntu-team']) |
1259 | + |
1260 | + def test_get_team_membership_when_team_is_not_visible(self): |
1261 | + self._set_team_to_private_membership('ubuntu-team') |
1262 | + memberships = TeamMembership.get_team_memberships_for_user( |
1263 | + ['ubuntu-team', 'myteam'], self.account, False) |
1264 | + self.assertEquals(memberships, []) |
1265 | + |
1266 | + def test_team_is_private_but_you_can_see_them(self): |
1267 | + self._set_team_to_private_membership('ubuntu-team') |
1268 | + memberships = TeamMembership.get_team_memberships_for_user( |
1269 | + ['ubuntu-team', 'myteam'], self.account, True) |
1270 | + self.assertEquals(memberships, ['ubuntu-team']) |
1271 | |
1272 | === modified file 'identityprovider/tests/test_views_server.py' |
1273 | --- identityprovider/tests/test_views_server.py 2011-01-17 15:54:37 +0000 |
1274 | +++ identityprovider/tests/test_views_server.py 2011-07-05 15:46:33 +0000 |
1275 | @@ -3,14 +3,17 @@ |
1276 | |
1277 | import datetime |
1278 | import urlparse |
1279 | - |
1280 | from random import randint |
1281 | + |
1282 | from django.conf import settings |
1283 | from django.contrib.auth.models import AnonymousUser |
1284 | +from django.http import HttpRequest |
1285 | from django.test import TestCase |
1286 | from openid.extensions import pape |
1287 | from openid.extensions.sreg import SRegRequest |
1288 | |
1289 | +from mock import patch |
1290 | + |
1291 | from openid.message import (Message, IDENTIFIER_SELECT, OPENID1_URL_LIMIT, |
1292 | OPENID2_NS) |
1293 | from openid.yadis.constants import YADIS_HEADER_NAME |
1294 | @@ -20,16 +23,16 @@ |
1295 | from identityprovider.models import (Account, OpenIDAuthorization, |
1296 | OpenIDRPConfig, Person) |
1297 | from identityprovider.models.account import LPOpenIdIdentifier |
1298 | -from identityprovider.models.const import (AccountStatus, EmailStatus, |
1299 | - AccountCreationRationale) |
1300 | +from identityprovider.models.const import ( |
1301 | + AccountStatus, |
1302 | + AccountCreationRationale, |
1303 | +) |
1304 | from identityprovider.models.person import PersonLocation |
1305 | from identityprovider.models.authtoken import create_token |
1306 | |
1307 | from identityprovider.views import server |
1308 | from identityprovider.tests.utils import (SQLCachedTestCase, |
1309 | BasicAccountTestCase, AuthenticatedTestCase, OpenIDProviderTestCase) |
1310 | -from identityprovider.const import PERSON_VISIBILITY_PRIVATE_MEMBERSHIP |
1311 | - |
1312 | |
1313 | |
1314 | class DummyORequest(object): |
1315 | @@ -41,13 +44,15 @@ |
1316 | def idSelect(self): |
1317 | return False |
1318 | |
1319 | + |
1320 | class DummySession(dict): |
1321 | |
1322 | @property |
1323 | def session_key(self): |
1324 | return 'abc' |
1325 | |
1326 | - def flush(self): pass |
1327 | + def flush(self): |
1328 | + pass |
1329 | |
1330 | |
1331 | class DummyRequest(object): |
1332 | @@ -55,7 +60,7 @@ |
1333 | def __init__(self): |
1334 | self.session = DummySession() |
1335 | self.COOKIES = {} |
1336 | - self.META ={} |
1337 | + self.META = {} |
1338 | |
1339 | |
1340 | class HandleOpenIDErrorTestCase(OpenIDProviderTestCase): |
1341 | @@ -175,6 +180,7 @@ |
1342 | self.assertTrue(r['Location'].endswith('+decide')) |
1343 | self.assertEqual(openid_referer.value, META['HTTP_REFERER']) |
1344 | |
1345 | + |
1346 | class MultiLangOpenIDTestCase(TestCase): |
1347 | def setUp(self): |
1348 | self.orig_language_code = settings.LANGUAGE_CODE |
1349 | @@ -218,6 +224,7 @@ |
1350 | response = self.client.get('/+openid') |
1351 | self.assertEqual('de', self.client.session['django_language']) |
1352 | |
1353 | + |
1354 | class ValidOpenIDTestCase(OpenIDProviderTestCase): |
1355 | |
1356 | def test_is_valid_openid_idselect(self): |
1357 | @@ -280,6 +287,7 @@ |
1358 | |
1359 | class DecideTestCase(AuthenticatedTestCase, OpenIDProviderTestCase): |
1360 | fixtures = ['test'] |
1361 | + |
1362 | def _prepare_openid_token(self, param_overrides=None): |
1363 | request = {'openid.mode': 'checkid_setup', |
1364 | 'openid.trust_root': 'http://localhost/', |
1365 | @@ -363,14 +371,12 @@ |
1366 | self.assertEqual(r.status_code, 200) |
1367 | self.assertTemplateUsed(r, 'registration/login.html') |
1368 | |
1369 | - |
1370 | def test_list_of_details_is_complete(self): |
1371 | self.client.login(username='mark@example.com', |
1372 | password='test') |
1373 | |
1374 | # create a trusted rpconfig |
1375 | rpconfig = OpenIDRPConfig(trust_root='http://localhost/', |
1376 | - allowed_sreg="nickname,email,fullname,timezone,language", |
1377 | can_query_any_team=True, |
1378 | description="Some description", |
1379 | ) |
1380 | @@ -386,6 +392,95 @@ |
1381 | self.assertContains(response, "Email address") |
1382 | self.assertContains(response, "Preferred language") |
1383 | |
1384 | + def test_state_of_checkboxes_and_data_formats_trusted(self): |
1385 | + self.client.login(username='mark@example.com', |
1386 | + password='test') |
1387 | + |
1388 | + # create a trusted rpconfig |
1389 | + rpconfig = OpenIDRPConfig(trust_root='http://localhost/', |
1390 | + can_query_any_team=True, |
1391 | + description="Some description", |
1392 | + ) |
1393 | + rpconfig.save() |
1394 | + param_overrides = { |
1395 | + 'openid.sreg.required': 'nickname,email,fullname', |
1396 | + 'openid.sreg.optional': 'language', |
1397 | + 'openid.lp.query_membership': 'ubuntu-team,launchpad-team,isd-team', |
1398 | + } |
1399 | + self._prepare_openid_token(param_overrides=param_overrides) |
1400 | + response = self.client.get('/%s/+decide' % self.token) |
1401 | + # checkbox checked and disabled for required fields and label is bold |
1402 | + username_html = ('<li><input checked="checked" name="nickname" ' |
1403 | + 'value="mark" class="required" disabled="disabled" ' |
1404 | + 'type="checkbox" id="id_nickname" /> <label ' |
1405 | + 'for="id_nickname">Username: mark</label></li>') |
1406 | + email_html = ('<li><input checked="checked" name="email" ' |
1407 | + 'value="mark@example.com" class="required" ' |
1408 | + 'disabled="disabled" type="checkbox" id="id_email" /> ' |
1409 | + '<label for="id_email">Email address: ' |
1410 | + 'mark@example.com</label></li>') |
1411 | + # checkbox checked and enabled for optional fields and label is plain |
1412 | + language_html = ('<li><input checked="checked" type="checkbox" ' |
1413 | + 'name="language" value="en" id="id_language" /> ' |
1414 | + '<label for="id_language">Preferred language: en' |
1415 | + '</label></li>') |
1416 | + # team data is enabled and checked, the label is plain |
1417 | + team_html_1 = ('<li><input checked="checked" type="checkbox" ' |
1418 | + 'name="ubuntu-team" value="ubuntu-team" id="id_' |
1419 | + 'ubuntu-team" /> <label for="id_ubuntu-team">' |
1420 | + 'ubuntu-team</label></li>') |
1421 | + team_html_2 = ('<li><input checked="checked" type="checkbox" ' |
1422 | + 'name="isd-team" value="isd-team" id="id_isd-team" /> ' |
1423 | + '<label for="id_isd-team">isd-team</label></li>') |
1424 | + |
1425 | + self.assertContains(response, username_html) |
1426 | + self.assertContains(response, email_html) |
1427 | + self.assertContains(response, language_html) |
1428 | + self.assertContains(response, team_html_1) |
1429 | + self.assertContains(response, team_html_2) |
1430 | + |
1431 | + def test_state_of_checkboxes_and_data_formats_untrusted(self): |
1432 | + self.client.login(username='mark@example.com', |
1433 | + password='test') |
1434 | + |
1435 | + # create a trusted rpconfig |
1436 | + rpconfig = OpenIDRPConfig(trust_root='http://untrusted/', |
1437 | + can_query_any_team=True, |
1438 | + description="Some description", |
1439 | + ) |
1440 | + rpconfig.save() |
1441 | + param_overrides = { |
1442 | + 'openid.sreg.required': 'nickname,email,fullname', |
1443 | + 'openid.sreg.optional': 'language', |
1444 | + 'openid.lp.query_membership': 'ubuntu-team', |
1445 | + } |
1446 | + self._prepare_openid_token(param_overrides=param_overrides) |
1447 | + response = self.client.get('/%s/+decide' % self.token) |
1448 | + # checkbox checked and *enabled* for required fields and label is bold |
1449 | + username_html = ('<li><input checked="checked" name="nickname" ' |
1450 | + 'value="mark" class="required" type="checkbox" ' |
1451 | + 'id="id_nickname" /> <label for="id_nickname">' |
1452 | + 'Username: mark</label></li>') |
1453 | + email_html = ('<li><input checked="checked" name="email" ' |
1454 | + 'value="mark@example.com" class="required" type=' |
1455 | + '"checkbox" id="id_email" /> <label for="id_email">' |
1456 | + 'Email address: mark@example.com</label></li>') |
1457 | + # checkbox *not checked* and enabled for optional fields & label's plain |
1458 | + language_html = ('<li><input type="checkbox" name="language" value="' |
1459 | + 'en" id="id_language" /> <label for="id_language">' |
1460 | + 'Preferred language: en</label></li>') |
1461 | + # team data is enabled and *not checked*, the label is plain |
1462 | + team_html = ('<li><input type="checkbox" name="ubuntu-team" ' |
1463 | + 'value="ubuntu-team" id="id_ubuntu-team" /> Team ' |
1464 | + 'membership: <label for="id_ubuntu-team">ubuntu-team' |
1465 | + '</label></li>') |
1466 | + |
1467 | + self.assertContains(response, username_html) |
1468 | + self.assertContains(response, email_html) |
1469 | + self.assertContains(response, language_html) |
1470 | + self.assertContains(response, team_html) |
1471 | + |
1472 | + |
1473 | class PreAuthorizeTestCase(AuthenticatedTestCase): |
1474 | def setUp(self): |
1475 | super(PreAuthorizeTestCase, self).setUp(disableCSRF=True) |
1476 | @@ -666,7 +761,7 @@ |
1477 | old_fromOpenIDRequest = pape.Request.fromOpenIDRequest |
1478 | pape.Request.fromOpenIDRequest = mock_fromOpenIDRequest |
1479 | |
1480 | - r = server._openid_is_authorized(self.request, self.orequest) |
1481 | + r = server._openid_is_authorized(self.request, self.orequest) |
1482 | self.assertFalse(r) |
1483 | |
1484 | pape.Request.fromOpenIDRequest = old_fromOpenIDRequest |
1485 | @@ -832,66 +927,6 @@ |
1486 | self.request = DummyRequest() |
1487 | self.request.user = self.account |
1488 | |
1489 | - def test_sreg_fields_no_preferredemail(self): |
1490 | - for email in self.account.emailaddress_set.all(): |
1491 | - email.status = EmailStatus.NEW |
1492 | - email.save() |
1493 | - |
1494 | - expected = [] |
1495 | - result = server._sreg_fields(self.request, self.sreg_request, |
1496 | - self.rpconfig) |
1497 | - self.assertEqual(expected, result) |
1498 | - |
1499 | - def test_sreg_fields_timezone(self): |
1500 | - self.sreg_request.optional = ['timezone'] |
1501 | - expected = [('timezone', 'UTC')] |
1502 | - result = server._sreg_fields(self.request, self.sreg_request, |
1503 | - self.rpconfig) |
1504 | - self.assertEqual(result, expected) |
1505 | - |
1506 | - def test_sreg_fields_language(self): |
1507 | - self.sreg_request.optional = ['language'] |
1508 | - expected = [('language', 'en')] |
1509 | - result = server._sreg_fields(self.request, self.sreg_request, |
1510 | - self.rpconfig) |
1511 | - self.assertEqual(result, expected) |
1512 | - |
1513 | -class TestGetTeamMemberships(BasicAccountTestCase): |
1514 | - |
1515 | - fixtures = ["test"] |
1516 | - |
1517 | - def setUp(self): |
1518 | - self.account = Account.objects.get(pk=1) |
1519 | - |
1520 | - def test_get_team_memberships_on_personless_account(self): |
1521 | - account = Account.objects.create_account('test', 'x@example.com', 'password') |
1522 | - memberships = server.get_team_memberships( |
1523 | - ["ubuntu-team"], account, True) |
1524 | - self.assertEquals(memberships, []) |
1525 | - |
1526 | - def test_get_team_memberships_when_team_is_visible(self): |
1527 | - memberships = server.get_team_memberships( |
1528 | - ["ubuntu-team"], self.account, True) |
1529 | - self.assertEquals(memberships, ["ubuntu-team"]) |
1530 | - |
1531 | - def test_get_team_membership_when_team_is_not_visible(self): |
1532 | - self.set_team_to_private_membership('ubuntu-team') |
1533 | - |
1534 | - memberships = server.get_team_memberships( |
1535 | - ['ubuntu-team', "myteam"], self.account, True) |
1536 | - self.assertEquals(memberships, []) |
1537 | - |
1538 | - def test_team_is_private_but_you_can_see_them(self): |
1539 | - self.set_team_to_private_membership('ubuntu-team') |
1540 | - memberships = server.get_team_memberships( |
1541 | - ['ubuntu-team', "myteam"], self.account, False) |
1542 | - self.assertEquals(memberships, ['ubuntu-team']) |
1543 | - |
1544 | - def set_team_to_private_membership(self, team_name): |
1545 | - team = Person.objects.get(name=team_name) |
1546 | - team.visibility = PERSON_VISIBILITY_PRIVATE_MEMBERSHIP |
1547 | - team.save() |
1548 | - |
1549 | |
1550 | class MarkupTestCase(SQLCachedTestCase): |
1551 | |
1552 | @@ -908,3 +943,69 @@ |
1553 | |
1554 | r = self.client.get('/%s/+decide' % token) |
1555 | self.assertContains(r, '<em>not</em>') |
1556 | + |
1557 | + |
1558 | +class ApprovedDataTest(OpenIDProviderTestCase): |
1559 | + fixtures = ['test'] |
1560 | + |
1561 | + def _get_openid_request(self, with_sreg=True, with_teams=True): |
1562 | + request = { |
1563 | + 'openid.mode': 'checkid_setup', |
1564 | + 'openid.trust_root': 'http://localhost/', |
1565 | + 'openid.return_to': 'http://localhost/', |
1566 | + 'openid.identity': IDENTIFIER_SELECT} |
1567 | + if with_sreg: |
1568 | + request['openid.sreg.required'] = 'email,fullname' |
1569 | + if with_teams: |
1570 | + request['openid.lp.query_membership'] = 'ubuntu-team' |
1571 | + openid_server = server._get_openid_server() |
1572 | + return openid_server.decodeRequest(request) |
1573 | + |
1574 | + def _get_request_with_post_args(self, args={}): |
1575 | + request = HttpRequest() |
1576 | + request.user = Account.objects.get(pk=1) |
1577 | + request.POST = args |
1578 | + request.META = {'REQUEST_METHOD': 'POST'} |
1579 | + return request |
1580 | + |
1581 | + def setUp(self): |
1582 | + # Ensure that we're restricting RPs for these tests |
1583 | + self.old_restrict = getattr(settings, 'SSO_RESTRICT_RP', True) |
1584 | + settings.SSO_RESTRICT_RP = False |
1585 | + |
1586 | + def tearDown(self): |
1587 | + settings.SSO_RESTRICT_RP = self.old_restrict |
1588 | + |
1589 | + def test_approved_data_returns_none_for_no_request(self): |
1590 | + result = server._get_approved_data(HttpRequest(), None) |
1591 | + self.assertEqual(result, None) |
1592 | + |
1593 | + def test_approved_data_for_sreg_only(self): |
1594 | + post_args = {'email': 'email', 'ubuntu-team': 'ubuntu-team'} |
1595 | + result = server._get_approved_data( |
1596 | + self._get_request_with_post_args(post_args), |
1597 | + self._get_openid_request(True, False)) |
1598 | + self.assertEqual(sorted(result['sreg']['requested']), |
1599 | + ['email', 'fullname']) |
1600 | + self.assertEqual(result['sreg']['approved'], ['email']) |
1601 | + self.assertFalse(result.has_key('teams')) |
1602 | + |
1603 | + def test_approved_data_for_teams_only(self): |
1604 | + post_args = {'ubuntu-team': 'ubuntu-team'} |
1605 | + result = server._get_approved_data( |
1606 | + self._get_request_with_post_args(post_args), |
1607 | + self._get_openid_request(False, True)) |
1608 | + self.assertEqual(result['teams']['requested'], ['ubuntu-team']) |
1609 | + self.assertEqual(result['teams']['approved'], ['ubuntu-team']) |
1610 | + self.assertFalse(result.has_key('sreg')) |
1611 | + |
1612 | + def test_approved_data_for_sreg_and_teams(self): |
1613 | + post_args = {'email': 'email', 'ubuntu-team': 'ubuntu-team'} |
1614 | + result = server._get_approved_data( |
1615 | + self._get_request_with_post_args(post_args), |
1616 | + self._get_openid_request()) |
1617 | + self.assertEqual(sorted(result['sreg']['requested']), |
1618 | + ['email', 'fullname']) |
1619 | + self.assertEqual(result['sreg']['approved'], ['email']) |
1620 | + self.assertEqual(result['teams']['requested'], ['ubuntu-team']) |
1621 | + self.assertEqual(result['teams']['approved'], ['ubuntu-team']) |
1622 | |
1623 | === modified file 'identityprovider/tests/utils.py' |
1624 | --- identityprovider/tests/utils.py 2011-06-17 14:22:43 +0000 |
1625 | +++ identityprovider/tests/utils.py 2011-07-05 15:46:33 +0000 |
1626 | @@ -302,3 +302,54 @@ |
1627 | resp.code = 200 |
1628 | resp.msg = 'OK' |
1629 | return resp |
1630 | + |
1631 | + |
1632 | +def add_user_to_team(account, teamname): |
1633 | + """ Note: will not work on the staging or production databases as those are |
1634 | + replicated from LP and read-only. |
1635 | + """ |
1636 | + from datetime import date |
1637 | + from identityprovider.models.person import Person |
1638 | + from identityprovider.models.account import (Account, LPOpenIdIdentifier) |
1639 | + from identityprovider.models.team import (TeamMembership, TeamParticipation) |
1640 | + |
1641 | + memberships = TeamMembership.get_team_memberships_for_user([teamname], |
1642 | + account, True) |
1643 | + if len(memberships) > 0: |
1644 | + return |
1645 | + |
1646 | + p = account.person |
1647 | + if p is None: |
1648 | + p = Person.objects.create(lp_account=account.id, |
1649 | + displayname=account.displayname, |
1650 | + name=get_unique_username()) |
1651 | + |
1652 | + try: |
1653 | + oi = LPOpenIdIdentifier.objects.get(lp_account=account.id) |
1654 | + except LPOpenIdIdentifier.DoesNotExist: |
1655 | + oi = LPOpenIdIdentifier.objects.create( |
1656 | + identifier=account.openid_identifier, lp_account=account.id) |
1657 | + |
1658 | + try: |
1659 | + t = Person.objects.get(name=teamname) |
1660 | + except Person.DoesNotExist: |
1661 | + t = Person.objects.create(displayname="Team %s" % teamname, teamowner=p, |
1662 | + name=teamname) |
1663 | + |
1664 | + now = date.today() |
1665 | + exp = date(day=now.day, month=now.month, year=now.year+1) |
1666 | + TeamMembership.objects.create(person=p, date_expires=exp, team=t, status=1) |
1667 | + TeamParticipation.objects.create(team=t, person=p) |
1668 | + |
1669 | +def get_unique_username(): |
1670 | + from identityprovider.models.person import Person |
1671 | + i = 0 |
1672 | + while True: |
1673 | + try: |
1674 | + username = "user%d" % i |
1675 | + i += 1 |
1676 | + Person.objects.get(name=username) |
1677 | + except Person.DoesNotExist: |
1678 | + break |
1679 | + return username |
1680 | + |
1681 | |
1682 | === modified file 'identityprovider/views/server.py' |
1683 | --- identityprovider/views/server.py 2011-05-31 20:29:08 +0000 |
1684 | +++ identityprovider/views/server.py 2011-07-05 15:46:33 +0000 |
1685 | @@ -6,13 +6,26 @@ |
1686 | import urllib |
1687 | import urlparse |
1688 | |
1689 | -from datetime import datetime, timedelta |
1690 | +from datetime import ( |
1691 | + datetime, |
1692 | + timedelta, |
1693 | +) |
1694 | |
1695 | from openid.extensions import pape |
1696 | -from openid.extensions.sreg import SRegRequest, SRegResponse |
1697 | -from openid.message import IDENTIFIER_SELECT, registerNamespaceAlias |
1698 | -from openid.server.server import (CheckIDRequest, ENCODE_URL, ProtocolError, |
1699 | - Server) |
1700 | +from openid.extensions.sreg import ( |
1701 | + SRegRequest, |
1702 | + SRegResponse, |
1703 | +) |
1704 | +from openid.message import ( |
1705 | + IDENTIFIER_SELECT, |
1706 | + registerNamespaceAlias, |
1707 | +) |
1708 | +from openid.server.server import ( |
1709 | + CheckIDRequest, |
1710 | + ENCODE_URL, |
1711 | + ProtocolError, |
1712 | + Server, |
1713 | +) |
1714 | from openid.server.trustroot import TrustRoot |
1715 | from openid.urinorm import urinorm |
1716 | from openid.yadis.constants import YADIS_HEADER_NAME |
1717 | @@ -21,30 +34,47 @@ |
1718 | |
1719 | from django.conf import settings |
1720 | from django.contrib.auth.decorators import login_required |
1721 | -from django.http import (Http404, HttpResponse, HttpResponseBadRequest, |
1722 | - HttpResponseRedirect) |
1723 | -from django.template import RequestContext, loader |
1724 | -from django.shortcuts import get_object_or_404, render_to_response |
1725 | +from django.http import ( |
1726 | + Http404, |
1727 | + HttpResponse, |
1728 | + HttpResponseBadRequest, |
1729 | + HttpResponseRedirect, |
1730 | +) |
1731 | +from django.template import ( |
1732 | + RequestContext, |
1733 | + loader, |
1734 | +) |
1735 | +from django.shortcuts import ( |
1736 | + get_object_or_404, |
1737 | + render_to_response, |
1738 | +) |
1739 | from django.utils.decorators import decorator_from_middleware |
1740 | from django.utils import translation |
1741 | |
1742 | import identityprovider.signed as signed |
1743 | |
1744 | -from identityprovider.const import ( |
1745 | - LAUNCHPAD_TEAMS_NS, |
1746 | - PERSON_VISIBILITY_PUBLIC, |
1747 | - SREG_DATA_FIELDS_ORDER, |
1748 | - SREG_LABELS, |
1749 | +from identityprovider.const import LAUNCHPAD_TEAMS_NS |
1750 | +from identityprovider.forms import ( |
1751 | + PreAuthorizeForm, |
1752 | + SRegRequestForm, |
1753 | + TeamsRequestForm, |
1754 | ) |
1755 | -from identityprovider.forms import PreAuthorizeForm |
1756 | + |
1757 | from identityprovider.middleware.xrds import XRDSMiddleware |
1758 | -from identityprovider.models import (Account, DjangoOpenIDStore, |
1759 | - OpenIDAuthorization, OpenIDRPSummary, |
1760 | - Person) |
1761 | +from identityprovider.models import ( |
1762 | + Account, |
1763 | + DjangoOpenIDStore, |
1764 | + OpenIDAuthorization, |
1765 | + OpenIDRPSummary, |
1766 | + TeamMembership, |
1767 | +) |
1768 | from identityprovider.models.authtoken import create_token |
1769 | from identityprovider.teams import TeamsRequest |
1770 | from identityprovider.utils import is_django_13 |
1771 | -from identityprovider.views import ui, utils |
1772 | +from identityprovider.views import ( |
1773 | + ui, |
1774 | + utils, |
1775 | +) |
1776 | from identityprovider.views.i18n import set_language_info |
1777 | |
1778 | if not is_django_13(): |
1779 | @@ -56,13 +86,14 @@ |
1780 | registerNamespaceAlias(LAUNCHPAD_TEAMS_NS, 'lp') |
1781 | logger = logging.getLogger('sso') |
1782 | |
1783 | + |
1784 | @csrf_exempt |
1785 | @accept_xrds |
1786 | def openid_provider(request, lang='en'): |
1787 | if lang not in settings.SUPPORTED_LANGUAGES: |
1788 | # Next we check for a primary language. |
1789 | if lang is not None: |
1790 | - lang = lang.split('_')[0] # e.g. de_CH becomes de. |
1791 | + lang = lang.split('_')[0] # e.g. de_CH becomes de. |
1792 | if lang not in settings.SUPPORTED_LANGUAGES: |
1793 | lang = translation.get_language_from_request(request) |
1794 | translation.activate(lang) |
1795 | @@ -77,6 +108,7 @@ |
1796 | set_language_info(request, response, lang) |
1797 | return response |
1798 | |
1799 | + |
1800 | def _process_openid_request(request, orequest, openid_server): |
1801 | if not orequest: |
1802 | context = RequestContext(request) |
1803 | @@ -111,14 +143,15 @@ |
1804 | if orequest.immediate: |
1805 | rp_config = utils.get_rpconfig(orequest.trust_root) |
1806 | auto_authorized = _is_auto_authorized_rp(rp_config) |
1807 | - if auto_authorized and request.user.is_authenticated() and _is_identity_owner(request.user, orequest): |
1808 | + if (auto_authorized and request.user.is_authenticated() and |
1809 | + _is_identity_owner(request.user, orequest)): |
1810 | if orequest.idSelect(): |
1811 | oresponse = orequest.answer(True, |
1812 | identity=request.user.openid_identity_url) |
1813 | else: |
1814 | oresponse = orequest.answer(True) |
1815 | _add_sreg(request, orequest, oresponse) |
1816 | - _check_team_membership(request.user, orequest, oresponse) |
1817 | + _check_team_membership(request, orequest, oresponse) |
1818 | response = _django_response(request, oresponse, True) |
1819 | else: |
1820 | oresponse = orequest.answer(False) |
1821 | @@ -138,7 +171,7 @@ |
1822 | else: |
1823 | oresponse = orequest.answer(True) |
1824 | _add_sreg(request, orequest, oresponse) |
1825 | - _check_team_membership(request.user, orequest, oresponse) |
1826 | + _check_team_membership(request, orequest, oresponse) |
1827 | response = _django_response(request, oresponse, True) |
1828 | elif (request.user.is_authenticated() and not |
1829 | _is_identity_owner(request.user, orequest)): |
1830 | @@ -196,15 +229,21 @@ |
1831 | return _process_decide(request, orequest, True) |
1832 | else: |
1833 | sreg_request = SRegRequest.fromOpenIDRequest(orequest) |
1834 | - sreg_data = _sreg_fields(request, sreg_request, rpconfig) |
1835 | - sreg_data = _humanize_sreg_labels(sreg_data) |
1836 | - teams_data = _teams_data(request.user, orequest, rpconfig) |
1837 | + teams_request = TeamsRequest.fromOpenIDRequest(orequest) |
1838 | + try: |
1839 | + summary = OpenIDRPSummary.objects.get( |
1840 | + account=request.user, trust_root=orequest.trust_root) |
1841 | + approved_data = summary.get_approved_data() |
1842 | + except OpenIDRPSummary.DoesNotExist: |
1843 | + approved_data = {} |
1844 | context = RequestContext(request, { |
1845 | 'account': request.user, |
1846 | 'trust_root': orequest.trust_root, |
1847 | 'rpconfig': rpconfig, |
1848 | - 'sreg_data': sreg_data, |
1849 | - 'teams_data': teams_data, |
1850 | + 'sreg_form': SRegRequestForm(request, sreg_request, rpconfig, |
1851 | + approved_data=approved_data.get('sreg')), |
1852 | + 'teams_form': TeamsRequestForm(request, teams_request, rpconfig, |
1853 | + approved_data=approved_data.get('teams')), |
1854 | 'token': token, |
1855 | 'message': request.session.get('message', None), |
1856 | 'message_style': 'informational', |
1857 | @@ -422,7 +461,7 @@ |
1858 | return user.last_login <= cutoff |
1859 | |
1860 | |
1861 | -def _django_response(request, oresponse, auth_success=False): |
1862 | +def _django_response(request, oresponse, auth_success=False, orequest=None): |
1863 | """ Convert an OpenID response into a Django HttpResponse """ |
1864 | webresponse = _get_openid_server().encodeResponse(oresponse) |
1865 | response = HttpResponse(webresponse.body, mimetype="text/plain") |
1866 | @@ -433,54 +472,44 @@ |
1867 | logger.debug("response_body = " + webresponse.body) |
1868 | if auth_success and isinstance(oresponse.request, CheckIDRequest): |
1869 | logger.debug("oresponse.fields = " + str(oresponse.fields)) |
1870 | - OpenIDRPSummary.objects.record(request.user, |
1871 | - oresponse.request.trust_root) |
1872 | + approved_data = _get_approved_data(request, orequest) |
1873 | + OpenIDRPSummary.objects.record( |
1874 | + request.user, |
1875 | + oresponse.request.trust_root, |
1876 | + None, |
1877 | + approved_data) |
1878 | return response |
1879 | |
1880 | |
1881 | -def _sreg_fields(request, sreg_request, rpconfig): |
1882 | - account = request.user |
1883 | - field_names = set(sreg_request.required + sreg_request.optional) |
1884 | - if rpconfig is None: |
1885 | - # The nickname field is permitted by default. |
1886 | - field_names.intersection_update(['nickname']) |
1887 | - elif rpconfig.allowed_sreg is not None: |
1888 | - field_names.intersection_update(rpconfig.allowed_sreg.split(',')) |
1889 | - sreg_field_names = [name for name in SREG_DATA_FIELDS_ORDER |
1890 | - if name in field_names] |
1891 | - # Collect registration values |
1892 | - values = {} |
1893 | - values['fullname'] = account.displayname |
1894 | - if account.preferredemail is not None: |
1895 | - values['email'] = account.preferredemail.email |
1896 | - if account.person is not None: |
1897 | - values['nickname'] = account.person.name |
1898 | - if account.person.time_zone is not None: |
1899 | - values['timezone'] = account.person.time_zone |
1900 | - if account.preferredlanguage is not None: |
1901 | - values['language'] = account.preferredlanguage |
1902 | - else: |
1903 | - values['language'] = translation.get_language_from_request(request) |
1904 | - logger.debug("values (sreg_fields) = " + str(values)) |
1905 | - return [(field, values[field]) |
1906 | - for field in sreg_field_names if field in values] |
1907 | - |
1908 | - |
1909 | -def _humanize_sreg_labels(sreg_data): |
1910 | - new_data = [] |
1911 | - for k, v in sreg_data: |
1912 | - k = SREG_LABELS.get(k, k) |
1913 | - new_data.append((k, v)) |
1914 | - return new_data |
1915 | - |
1916 | - |
1917 | -def _teams_data(account, orequest, rpconfig): |
1918 | - teams_request = TeamsRequest.fromOpenIDRequest(orequest) |
1919 | - restrict_teams = True |
1920 | - if rpconfig is not None: |
1921 | - restrict_teams = not rpconfig.can_query_any_team |
1922 | - return get_team_memberships(teams_request.allRequestedTeams(), |
1923 | - account, restrict_teams) |
1924 | +def _get_approved_data(request, orequest): |
1925 | + """Given an HTTP request and an OpenID request, return a nested dict of |
1926 | + values requested in the request and approved by the user. |
1927 | + """ |
1928 | + if not orequest: |
1929 | + return None |
1930 | + |
1931 | + approved_data = {} |
1932 | + |
1933 | + sreg_request = SRegRequest.fromOpenIDRequest(orequest) |
1934 | + rpconfig = utils.get_rpconfig(orequest.trust_root) |
1935 | + sreg_form = SRegRequestForm(request, sreg_request, rpconfig) |
1936 | + if len(sreg_form.data.keys()) > 0: |
1937 | + approved_data['sreg'] = { |
1938 | + 'requested': sreg_form.data.keys(), |
1939 | + 'approved': sreg_form.data_approved_by_user.keys()} |
1940 | + |
1941 | + args = orequest.message.getArgs(LAUNCHPAD_TEAMS_NS) |
1942 | + team_names = args.get('query_membership') |
1943 | + if team_names: |
1944 | + team_names = team_names.split(',') |
1945 | + teams_form = TeamsRequestForm(request, |
1946 | + TeamsRequest.fromOpenIDRequest(orequest), |
1947 | + utils.get_rpconfig(orequest.trust_root)) |
1948 | + approved_data['teams'] = { |
1949 | + 'requested': team_names, |
1950 | + 'approved': teams_form.teams_approved_by_user} |
1951 | + |
1952 | + return approved_data |
1953 | |
1954 | |
1955 | def _is_identity_owner(user, openid_request): |
1956 | @@ -495,11 +524,11 @@ |
1957 | def _add_sreg(request, openid_request, openid_response): |
1958 | # Add sreg result data |
1959 | sreg_request = SRegRequest.fromOpenIDRequest(openid_request) |
1960 | - sreg_fields = _sreg_fields(request, sreg_request, |
1961 | - utils.get_rpconfig(openid_request.trust_root)) |
1962 | - if sreg_fields: |
1963 | + rpconfig = utils.get_rpconfig(openid_request.trust_root) |
1964 | + form = SRegRequestForm(request, sreg_request, rpconfig) |
1965 | + if form.data_approved_by_user: |
1966 | sreg_response = SRegResponse.extractResponse( |
1967 | - sreg_request, dict(sreg_fields)) |
1968 | + sreg_request, dict(form.data_approved_by_user)) |
1969 | openid_response.addExtension(sreg_response) |
1970 | |
1971 | |
1972 | @@ -522,8 +551,8 @@ |
1973 | datetime.now(), |
1974 | request.session.session_key) |
1975 | _add_sreg(request, orequest, oresponse) |
1976 | - _check_team_membership(request.user, orequest, oresponse) |
1977 | - r = _django_response(request, oresponse, decision) |
1978 | + _check_team_membership(request, orequest, oresponse, False) |
1979 | + r = _django_response(request, oresponse, decision, orequest) |
1980 | if r.content: |
1981 | # Only user-visible content is generated from this view. Wrap |
1982 | # it up and set the Content-Type. |
1983 | @@ -543,47 +572,43 @@ |
1984 | return openid_server |
1985 | |
1986 | |
1987 | -def _check_team_membership(account, openid_request, openid_response): |
1988 | +def _check_team_membership(request, orequest, oresponse, immediate=True): |
1989 | """Perform team membership checks. |
1990 | |
1991 | If any team membership checks have been requested as part of |
1992 | the OpenID request, annotate the response with the list of |
1993 | teams the user is actually a member of. |
1994 | """ |
1995 | - assert account is not None, ( |
1996 | + assert request.user is not None, ( |
1997 | 'Must be logged in to calculate team membership') |
1998 | - if account.person is None: |
1999 | + if request.user.person is None: |
2000 | return |
2001 | - args = openid_request.message.getArgs(LAUNCHPAD_TEAMS_NS) |
2002 | + args = orequest.message.getArgs(LAUNCHPAD_TEAMS_NS) |
2003 | team_names = args.get('query_membership') |
2004 | if not team_names: |
2005 | return |
2006 | team_names = team_names.split(',') |
2007 | - only_public_teams = ( |
2008 | - utils.get_rpconfig(openid_request.trust_root) is None or not |
2009 | - utils.get_rpconfig(openid_request.trust_root).can_query_any_team) |
2010 | - memberships = get_team_memberships( |
2011 | - team_names, account, only_public_teams) |
2012 | - openid_response.fields.namespaces.addAlias(LAUNCHPAD_TEAMS_NS, 'lp') |
2013 | - openid_response.fields.setArg( |
2014 | - LAUNCHPAD_TEAMS_NS, 'is_member', ','.join(memberships)) |
2015 | - |
2016 | - |
2017 | -def get_team_memberships(team_names, account, only_public_teams): |
2018 | - memberships = [] |
2019 | - for team_name in team_names: |
2020 | + if immediate: |
2021 | try: |
2022 | - team = Person.objects.get(name=team_name) |
2023 | - except Person.DoesNotExist: |
2024 | - team = None |
2025 | - if team is None or not team.is_team(): |
2026 | - continue |
2027 | - # Control access to private teams |
2028 | - if (team.visibility != PERSON_VISIBILITY_PUBLIC and only_public_teams): |
2029 | - continue |
2030 | - if account.person_in_team(team): |
2031 | - memberships.append(team_name) |
2032 | - return memberships |
2033 | + summary = OpenIDRPSummary.objects.get( |
2034 | + account=request.user, trust_root=orequest.trust_root) |
2035 | + approved_data = summary.get_approved_data() |
2036 | + except OpenIDRPSummary.DoesNotExist: |
2037 | + approved_data = {} |
2038 | + if 'teams' in approved_data: |
2039 | + teams = ','.join(approved_data['teams'].get('approved', [])) |
2040 | + else: |
2041 | + rpconfig = utils.get_rpconfig(orequest.trust_root) |
2042 | + teams = ','.join(TeamMembership.get_team_memberships_for_user( |
2043 | + team_names, request.user, |
2044 | + rpconfig and rpconfig.can_query_any_team)) |
2045 | + else: |
2046 | + form = TeamsRequestForm(request, |
2047 | + TeamsRequest.fromOpenIDRequest(orequest), |
2048 | + utils.get_rpconfig(orequest.trust_root)) |
2049 | + teams = ','.join(form.teams_approved_by_user) |
2050 | + oresponse.fields.namespaces.addAlias(LAUNCHPAD_TEAMS_NS, 'lp') |
2051 | + oresponse.fields.setArg(LAUNCHPAD_TEAMS_NS, 'is_member', teams) |
2052 | |
2053 | |
2054 | def untrusted(request, token): |
2055 | |
2056 | === modified file 'identityprovider/views/ui.py' |
2057 | --- identityprovider/views/ui.py 2011-05-31 15:44:44 +0000 |
2058 | +++ identityprovider/views/ui.py 2011-07-05 15:46:33 +0000 |
2059 | @@ -206,6 +206,7 @@ |
2060 | }) |
2061 | return render_to_response('enter_token.html', context) |
2062 | |
2063 | + |
2064 | def _redirect_to_enter_token(confirmation_code=None, email=None): |
2065 | params = {} |
2066 | if confirmation_code is not None: |
2067 | @@ -214,6 +215,7 @@ |
2068 | params['email'] = email |
2069 | return HttpResponseRedirect('/+enter_token?%s' % urlencode(params)) |
2070 | |
2071 | + |
2072 | def claim_token(request, authtoken): |
2073 | email = request.GET.get('email') or request.session.get('token_email') |
2074 | if email is None: |
2075 | @@ -221,6 +223,7 @@ |
2076 | token_type = get_type_of_token(authtoken) |
2077 | return _handle_confirmation(token_type, authtoken, email, request) |
2078 | |
2079 | + |
2080 | # As soon as _finish_account_creation doesn't need `request`, neither |
2081 | # does this. |
2082 | def _handle_confirmation(confirmation_type, confirmation_code, email, request, |
2083 | @@ -258,7 +261,8 @@ |
2084 | return HttpResponseRedirect(redirection_url) |
2085 | |
2086 | view = old_confirm_account |
2087 | - if 'token' in args: del args['token'] |
2088 | + if 'token' in args: |
2089 | + del args['token'] |
2090 | |
2091 | elif confirmation_type == LoginTokenType.VALIDATEEMAIL: |
2092 | view = confirm_email |
2093 | @@ -572,9 +576,11 @@ |
2094 | }) |
2095 | return render_to_response('registration/bad_token.html', context) |
2096 | |
2097 | + |
2098 | def logout_to_confirm(request): |
2099 | return render_to_response('registration/logout_to_confirm.html') |
2100 | |
2101 | + |
2102 | def deactivated(request): |
2103 | context = RequestContext(request, { |
2104 | 'message': request.session.pop('message', None), |
2105 | |
2106 | === modified file 'payload/__init__.py' |
2107 | --- payload/__init__.py 2011-06-23 19:51:28 +0000 |
2108 | +++ payload/__init__.py 2011-07-05 15:46:33 +0000 |
2109 | @@ -210,4 +210,7 @@ |
2110 | print 'Nothing to do; translations are up to date.' |
2111 | |
2112 | |
2113 | - |
2114 | +def manage(command, *args): |
2115 | + """Run manage.py command""" |
2116 | + virtualenv("python django_project/manage.py {0} {1}".format( |
2117 | + command, " ".join(args)), capture=False,) |
A few changes:
1. No need to add those extra files to the global .bzrignore file as they are not relevant to the project.
2. l. 363: don't like embedding html just for style issues: we should mark the field as required or some other class using css. you can do this by specific the css class in the widget in l. 367
3. l. 376 you're hiding an attribute: instead of changing the meaning of the 'value' attribute, use a different name. a way to do this would be
for key, v in self.data.items():
if value == v:
break
else:
key = None
4. l. 578: why using JSONDecoder, JSONEncoder, instead of just simplejson.loads, simplejson.dumps ? That way you can be sure the returned data is a dictionary, for example. 'python. ..') instead of local with the explicit path
5. l. 600: this breaks installing the function during the normal server setup
6. l. 680: since you're testing for number of items, if it's one, why do you loop over them? you could just use team_form.fields.0
7. l. 681,684: punctuation should be included in the string to be translated
8. l. 885: not sure that's a valid request. If the method is GET, shouldn't the args be somewhere else? The test is testing a GET request, not an invalid request.
9. l. 1836: why not do 'approved_data = {}'?
10. l. 1837: this can be replaced by "if 'teams' in approved_data" if the previous change was applied
11: l. 1914,1918: use virtualenv(
12: l. 1917: maybe you can integrate this into the 'test' target directly, instead of having an extra target?
Small issues:
l. 90: fix proper indentation 1591,1595: use multiple lines for different imports
l. 102: I'd rather have all arguments go into the next line than having a dangling one
l. 277,283,284: break multiple imports into separate lines
l. 359: can just be 'for key in data', or since you're using the value later 'for key, value in data.items()'
l. 374: since you're getting the value anyway, you could just do 'for k, v in self.data.items()' and use v
l. 381,463,1607: use parenthesis for line continuations instead of \
l. 632: \ not needed as parenthesis are already used
l. 1191,1199,
l. 1477: this can be replaced by a much nicer idiom. something like:
self.patch = patch('settings', 'SSO_RESTRICT_RP', False)
self.patch.start()
and in tearDown
self.patch.stop()
l. 1764: I think you can just use 'if form_data. approved_ by_user'
Questions:
l. 318: is this condition intended only for the default case, or should it be reevaluated everytime it's False?
l. 447: does this *have* to be a dict? as you're returning the same value as for the keys.
l. 651: are we using yui somewhere else? if so we may want to just import yui from a template we then include where we need to, so as to make sure we always require the same version of it, and not try to load different versions on separate pages.
Comments:
l. 877: the tearDown for this should not be needed, but I'm fine with it