Merge lp:~canonical-isd-hackers/canonical-identity-provider/improve-coverage into lp:canonical-identity-provider/release
- improve-coverage
- Merge into trunk
Proposed by
Łukasz Czyżykowski
Status: | Merged |
---|---|
Merged at revision: | 45 |
Proposed branch: | lp:~canonical-isd-hackers/canonical-identity-provider/improve-coverage |
Merge into: | lp:canonical-identity-provider/release |
Diff against target: |
4295 lines (+3282/-157) 44 files modified
identityprovider/admin.py (+8/-41) identityprovider/backend/base.py (+3/-6) identityprovider/models/account.py (+2/-2) identityprovider/models/api.py (+2/-2) identityprovider/models/authtoken.py (+4/-2) identityprovider/models/fixtures/admin.json (+20/-0) identityprovider/models/openidmodels.py (+1/-1) identityprovider/models/person.py (+0/-2) identityprovider/readonly.py (+0/-4) identityprovider/tests/mockdb.py (+15/-0) identityprovider/tests/test_admin.py (+93/-4) identityprovider/tests/test_auth.py (+18/-2) identityprovider/tests/test_backend_base.py (+212/-0) identityprovider/tests/test_backend_old_base.py (+53/-0) identityprovider/tests/test_command_sqlcachedflush.py (+2/-1) identityprovider/tests/test_command_sqlcachedloaddata.py (+132/-0) identityprovider/tests/test_middleware.py (+43/-0) identityprovider/tests/test_models_account.py (+128/-1) identityprovider/tests/test_models_api.py (+41/-0) identityprovider/tests/test_models_authtoken.py (+100/-15) identityprovider/tests/test_models_captcha.py (+157/-0) identityprovider/tests/test_models_oauthtoken.py (+75/-2) identityprovider/tests/test_models_openidmodels.py (+198/-11) identityprovider/tests/test_models_person.py (+57/-0) identityprovider/tests/test_models_team.py (+14/-0) identityprovider/tests/test_readonly.py (+284/-12) identityprovider/tests/test_teams.py (+200/-0) identityprovider/tests/test_views_account.py (+38/-0) identityprovider/tests/test_views_consumer.py (+177/-3) identityprovider/tests/test_views_i18n.py (+22/-0) identityprovider/tests/test_views_server.py (+712/-14) identityprovider/tests/test_views_testing.py (+19/-0) identityprovider/tests/test_views_ui.py (+175/-1) identityprovider/tests/test_widgets.py (+141/-1) identityprovider/tests/test_wsgi.py (+43/-0) identityprovider/tests/utils.py (+26/-0) identityprovider/utils.py (+1/-3) identityprovider/views/account.py (+0/-3) identityprovider/views/consumer.py (+1/-1) identityprovider/views/server.py (+4/-4) identityprovider/views/ui.py (+4/-9) identityprovider/views/utils.py (+2/-5) identityprovider/widgets.py (+36/-1) scripts/test (+19/-4) |
To merge this branch: | bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/improve-coverage |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Szilveszter Farkas (community) | Approve | ||
Review via email: mp+26427@code.launchpad.net |
Commit message
Description of the change
Improve test coverage to 98%
To post a comment you must log in.
Revision history for this message
Szilveszter Farkas (phanatic) wrote : | # |
Thanks for the update!
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'identityprovider/admin.py' |
2 | --- identityprovider/admin.py 2010-04-29 10:17:02 +0000 |
3 | +++ identityprovider/admin.py 2010-06-01 12:03:28 +0000 |
4 | @@ -1,17 +1,19 @@ |
5 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
6 | # GNU Affero General Public License version 3 (see the file LICENSE). |
7 | |
8 | +from django import forms |
9 | from django.contrib import admin |
10 | -from django import forms |
11 | from django.utils.safestring import mark_safe |
12 | from django.utils.translation import ugettext_lazy as _ |
13 | |
14 | +from identityprovider.const import SREG_LABELS |
15 | +from identityprovider.fields import CommaSeparatedField |
16 | from identityprovider.models import (Account, AccountPassword, EmailAddress, |
17 | OpenIDRPConfig, APIUser) |
18 | +from identityprovider.models.const import AccountStatus, EmailStatus |
19 | from identityprovider.utils import encrypt_launchpad_password |
20 | -from identityprovider.const import SREG_LABELS |
21 | -from identityprovider.models.const import AccountStatus, EmailStatus |
22 | -from identityprovider.fields import CommaSeparatedField |
23 | +from identityprovider.widgets import (StatusWidget, ReadOnlyDateTimeWidget, |
24 | + LPUsernameWidget, account_to_lp_link) |
25 | |
26 | |
27 | class OpenIDRPConfigForm(forms.ModelForm): |
28 | @@ -100,35 +102,6 @@ |
29 | verbose_name_plural = _("Email addresses") |
30 | |
31 | |
32 | -class StatusWidget(forms.Select): |
33 | - |
34 | - date_status_set = None |
35 | - |
36 | - def render(self, name, value, attrs=None, choices=()): |
37 | - select = super(StatusWidget, self).render(name, value, attrs, choices) |
38 | - if self.date_status_set: |
39 | - status_date = _("Set on %s") % self.date_status_set |
40 | - else: |
41 | - status_date = "" |
42 | - return mark_safe("%s %s" % (select, status_date)) |
43 | - |
44 | - |
45 | -class ReadOnlyDateTime(forms.widgets.Widget): |
46 | - |
47 | - value = None |
48 | - |
49 | - def render(self, name, value, attrs=None): |
50 | - return str(self.value) |
51 | - |
52 | - |
53 | -class LPUsernameWidget(forms.widgets.Widget): |
54 | - |
55 | - account = None |
56 | - |
57 | - def render(self, name, value, attrs=None): |
58 | - return mark_safe(account_to_lp_link(self.account)) |
59 | - |
60 | - |
61 | class LPUsernameField(forms.Field): |
62 | widget = LPUsernameWidget |
63 | |
64 | @@ -151,7 +124,8 @@ |
65 | widget.account = instance |
66 | super(AccountAdminForm, self).__init__(*args, **kwargs) |
67 | |
68 | - date_created = forms.DateTimeField(widget=ReadOnlyDateTime, required=False) |
69 | + date_created = forms.DateTimeField(widget=ReadOnlyDateTimeWidget, |
70 | + required=False) |
71 | lp_username = LPUsernameField(label=_("LP username"), required=False) |
72 | status = forms.TypedChoiceField(widget=StatusWidget, |
73 | choices=AccountStatus._get_choices()) |
74 | @@ -162,13 +136,6 @@ |
75 | widget=forms.TextInput) |
76 | |
77 | |
78 | -def account_to_lp_link(account): |
79 | - if account and account.person: |
80 | - return ('<a href="https://launchpad.net/~%s">%s</a>' % |
81 | - (account.person.name, account.person.name)) |
82 | - return "" |
83 | - |
84 | - |
85 | class AccountAdmin(admin.ModelAdmin): |
86 | inlines = [AccountPasswordInline, EmailAddressInline] |
87 | |
88 | |
89 | === modified file 'identityprovider/backend/base.py' |
90 | --- identityprovider/backend/base.py 2010-04-21 15:29:24 +0000 |
91 | +++ identityprovider/backend/base.py 2010-06-01 12:03:28 +0000 |
92 | @@ -86,8 +86,8 @@ |
93 | """django_session is always updated in the same way, so we can map |
94 | that in to a row in the database. |
95 | """ |
96 | - pkey_cond = '"%s"."%s" = %%s ' % (self.table, self.primary_key) |
97 | - assert pkey_cond == match.group('cond') |
98 | + pkey_cond = '"%s"."%s" = %%s' % (self.table, self.primary_key) |
99 | + assert pkey_cond == match.group('cond').strip() |
100 | update_format = '"session_data" = %s, "expire_date" = %s' |
101 | assert update_format == match.group('cols') |
102 | key = self.cache_key(params[2]) |
103 | @@ -170,10 +170,7 @@ |
104 | return self.cursor.fetchmany(chunk) |
105 | |
106 | def __getattr__(self, attr): |
107 | - if attr in self.__dict__: |
108 | - return self.__dict__[attr] |
109 | - else: |
110 | - return getattr(self.cursor, attr) |
111 | + return getattr(self.cursor, attr) |
112 | |
113 | |
114 | class DatabaseWrapper(PostgresDatabaseWrapper): |
115 | |
116 | === modified file 'identityprovider/models/account.py' |
117 | --- identityprovider/models/account.py 2010-05-13 10:45:42 +0000 |
118 | +++ identityprovider/models/account.py 2010-06-01 12:03:28 +0000 |
119 | @@ -29,7 +29,7 @@ |
120 | an Account, and the necessary AccountPassword, EmailAddress objects. """ |
121 | |
122 | def create_account(self, displayname, username, password, |
123 | - creation_rationale=None): |
124 | + creation_rationale=None, salt=None): |
125 | from identityprovider.models import EmailAddress |
126 | if creation_rationale is None: |
127 | creation_rationale = \ |
128 | @@ -44,7 +44,7 @@ |
129 | status=EmailStatus.PREFERRED) |
130 | password = AccountPassword.objects.create( |
131 | account=account, |
132 | - password=encrypt_launchpad_password(password)) |
133 | + password=encrypt_launchpad_password(password, salt=salt)) |
134 | return account |
135 | |
136 | def get_by_email(self, email): |
137 | |
138 | === modified file 'identityprovider/models/api.py' |
139 | --- identityprovider/models/api.py 2010-04-22 15:29:52 +0000 |
140 | +++ identityprovider/models/api.py 2010-06-01 12:03:28 +0000 |
141 | @@ -15,8 +15,8 @@ |
142 | created_at = models.DateTimeField(auto_now_add=True) |
143 | updated_at = models.DateTimeField(auto_now=True) |
144 | |
145 | - def set_password(self, password): |
146 | - self.password = encrypt_launchpad_password(password) |
147 | + def set_password(self, password, salt=None): |
148 | + self.password = encrypt_launchpad_password(password, salt=salt) |
149 | |
150 | def verify_password(self, password): |
151 | return validate_launchpad_password(password, self.password) |
152 | |
153 | === modified file 'identityprovider/models/authtoken.py' |
154 | --- identityprovider/models/authtoken.py 2010-05-13 14:53:17 +0000 |
155 | +++ identityprovider/models/authtoken.py 2010-06-01 12:03:28 +0000 |
156 | @@ -173,9 +173,11 @@ |
157 | """ |
158 | token = create_token(token_length) |
159 | try: |
160 | - while obj.objects.get(column=token) is not None: |
161 | + params = {column: token} |
162 | + while obj.objects.get(**params) is not None: |
163 | token = create_token(token_length) |
164 | - except: |
165 | + params = {column: token} |
166 | + except Exception, e: |
167 | pass |
168 | return token |
169 | |
170 | |
171 | === added file 'identityprovider/models/fixtures/admin.json' |
172 | --- identityprovider/models/fixtures/admin.json 1970-01-01 00:00:00 +0000 |
173 | +++ identityprovider/models/fixtures/admin.json 2010-06-01 12:03:28 +0000 |
174 | @@ -0,0 +1,20 @@ |
175 | +[ |
176 | + { |
177 | + "pk": 1, |
178 | + "model": "auth.user", |
179 | + "fields": { |
180 | + "username": "admin", |
181 | + "first_name": "", |
182 | + "last_name": "", |
183 | + "is_active": true, |
184 | + "is_superuser": true, |
185 | + "is_staff": true, |
186 | + "last_login": "2010-05-27 12:45:14", |
187 | + "groups": [], |
188 | + "user_permissions": [], |
189 | + "password": "sha1$1639f$b6ab1ef76b7cf760e8431be7ab61a25dc4a433ac", |
190 | + "email": "nobody@example.org", |
191 | + "date_joined": "2010-05-27 12:45:14" |
192 | + } |
193 | + } |
194 | +] |
195 | |
196 | === modified file 'identityprovider/models/openidmodels.py' |
197 | --- identityprovider/models/openidmodels.py 2010-04-21 15:29:24 +0000 |
198 | +++ identityprovider/models/openidmodels.py 2010-06-01 12:03:28 +0000 |
199 | @@ -270,4 +270,4 @@ |
200 | def cleanupAssociations(self): |
201 | OpenIDAssociation.objects.extra( |
202 | where=[ |
203 | - 'issued + lifetimeint < (%s)' % time.time()]).delete() |
204 | + '(issued + lifetime) < (%s)' % time.time()]).delete() |
205 | |
206 | === modified file 'identityprovider/models/person.py' |
207 | --- identityprovider/models/person.py 2010-04-21 15:29:24 +0000 |
208 | +++ identityprovider/models/person.py 2010-06-01 12:03:28 +0000 |
209 | @@ -88,8 +88,6 @@ |
210 | except TeamParticipation.DoesNotExist: |
211 | if self.id == team.teamowner.id: |
212 | return True |
213 | - elif team.is_team() and not team.teamowner.in_team(team): |
214 | - return self.in_team(team.teamowner) |
215 | return False |
216 | |
217 | @property |
218 | |
219 | === modified file 'identityprovider/readonly.py' |
220 | --- identityprovider/readonly.py 2010-05-05 17:47:40 +0000 |
221 | +++ identityprovider/readonly.py 2010-06-01 12:03:28 +0000 |
222 | @@ -80,10 +80,6 @@ |
223 | filename = self.marker_file_pattern % dbname |
224 | return os.path.exists(filename) |
225 | |
226 | - def is_current_failed(self): |
227 | - """Returns true if the current DB connection is failed.""" |
228 | - return self.is_failed(self.current_dbid()) |
229 | - |
230 | def get_connections(self): |
231 | return settings.DB_CONNECTIONS |
232 | connections = property(get_connections) |
233 | |
234 | === modified file 'identityprovider/tests/mockdb.py' |
235 | --- identityprovider/tests/mockdb.py 2010-04-21 15:29:24 +0000 |
236 | +++ identityprovider/tests/mockdb.py 2010-06-01 12:03:28 +0000 |
237 | @@ -5,6 +5,21 @@ |
238 | from psycopg2 import DatabaseError |
239 | |
240 | |
241 | +class MockCursor(object): |
242 | + def __init__(self, data=None, connection=None): |
243 | + self.data = data |
244 | + self.connection = connection |
245 | + |
246 | + def execute(self, query, params): |
247 | + return True |
248 | + |
249 | + def fetchmany(self, size=None): |
250 | + if size is not None: |
251 | + return self.data[:size] |
252 | + else: |
253 | + return self.data |
254 | + |
255 | + |
256 | class MockConnection(object): |
257 | def __init__(self, connection, psycopg): |
258 | self.connection = connection |
259 | |
260 | === modified file 'identityprovider/tests/test_admin.py' |
261 | --- identityprovider/tests/test_admin.py 2010-04-21 15:29:24 +0000 |
262 | +++ identityprovider/tests/test_admin.py 2010-06-01 12:03:28 +0000 |
263 | @@ -3,15 +3,28 @@ |
264 | |
265 | from django.contrib import admin |
266 | from django.contrib.admin.sites import AlreadyRegistered, NotRegistered |
267 | -from django.test import TestCase |
268 | |
269 | from identityprovider.admin import (AccountPasswordInline, |
270 | EmailAddressInline) |
271 | from identityprovider.models import (Account, AccountPassword, EmailAddress, |
272 | OpenIDRPConfig, APIUser) |
273 | - |
274 | - |
275 | -class AdminTestCase(TestCase): |
276 | +from identityprovider.models.const import EmailStatus |
277 | +from identityprovider.utils import validate_launchpad_password |
278 | + |
279 | +from utils import BasicAccountTestCase |
280 | + |
281 | + |
282 | +class AdminTestCase(BasicAccountTestCase): |
283 | + |
284 | + fixtures = ['admin.json', 'test.json'] |
285 | + |
286 | + def setUp(self): |
287 | + self.disable_csrf() |
288 | + self.client.login(username="admin", password="admin007") |
289 | + |
290 | + def tearDown(self): |
291 | + self.reset_csrf() |
292 | + |
293 | def test_registered_models(self): |
294 | for model in (Account, OpenIDRPConfig, APIUser): |
295 | self.assertRaises(AlreadyRegistered, admin.site.register, model) |
296 | @@ -23,3 +36,79 @@ |
297 | expected_inlines = [AccountPasswordInline, EmailAddressInline] |
298 | registered_inlines = admin.site._registry[Account].inlines |
299 | self.assertEqual(registered_inlines, expected_inlines) |
300 | + |
301 | + def test_account_overview(self): |
302 | + r = self.client.get('/admin/identityprovider/account/') |
303 | + self.assertContains(r, 'mark@example.com') |
304 | + |
305 | + def test_account_change(self): |
306 | + account_id = 1 |
307 | + url = '/admin/identityprovider/account/%s/' % account_id |
308 | + account = Account.objects.get(pk=account_id) |
309 | + email = account.preferredemail |
310 | + new_email = 'mark2@example.com' |
311 | + new_password = 'blah' |
312 | + r = self.client.post(url, { |
313 | + 'creation_rationale': str(account.creation_rationale), |
314 | + 'status': str(account.status), |
315 | + 'displayname': account.displayname, |
316 | + 'openid_identifier': account.openid_identifier, |
317 | + 'accountpassword-TOTAL_FORMS': '1', |
318 | + 'accountpassword-INITIAL_FORMS': '1', |
319 | + 'accountpassword-0-id': str(account_id), |
320 | + 'accountpassword-0-account': str(account_id), |
321 | + 'accountpassword-0-password': new_password, |
322 | + 'emailaddress_set-TOTAL_FORMS': '1', |
323 | + 'emailaddress_set-INITIAL_FORMS': '1', |
324 | + 'emailaddress_set-0-id': str(email.id), |
325 | + 'emailaddress_set-0-account': str(account.id), |
326 | + 'emailaddress_set-0-email': new_email, |
327 | + 'emailaddress_set-0-status': str(email.status)}) |
328 | + # Any non-error status code would be fine here, but right now |
329 | + # it redirects with a 302. |
330 | + self.assertEqual(r.status_code, 302) |
331 | + account = Account.objects.get(pk=1) |
332 | + self.assertEqual(account.preferredemail.email, new_email) |
333 | + self.assertTrue(validate_launchpad_password( |
334 | + new_password, account.accountpassword.password)) |
335 | + |
336 | + def test_multiple_preferred_emails(self): |
337 | + account_id = 1 |
338 | + url = '/admin/identityprovider/account/%s/' % account_id |
339 | + account = Account.objects.get(pk=account_id) |
340 | + email = account.preferredemail |
341 | + r = self.client.post(url, { |
342 | + 'creation_rationale': str(account.creation_rationale), |
343 | + 'status': str(account.status), |
344 | + 'displayname': account.displayname, |
345 | + 'openid_identifier': account.openid_identifier, |
346 | + 'accountpassword-TOTAL_FORMS': '1', |
347 | + 'accountpassword-INITIAL_FORMS': '1', |
348 | + 'accountpassword-0-id': str(account_id), |
349 | + 'accountpassword-0-account': str(account_id), |
350 | + 'emailaddress_set-TOTAL_FORMS': '2', |
351 | + 'emailaddress_set-INITIAL_FORMS': '1', |
352 | + 'emailaddress_set-0-id': str(email.id), |
353 | + 'emailaddress_set-0-account': str(account.id), |
354 | + 'emailaddress_set-0-email': email.email, |
355 | + 'emailaddress_set-0-status': str(email.status), |
356 | + 'emailaddress_set-1-account': str(account.id), |
357 | + 'emailaddress_set-1-email': 'failure@example.com', |
358 | + 'emailaddress_set-1-status': str(email.status)}) |
359 | + account = Account.objects.get(pk=1) |
360 | + self.assertContains(r, 'Only one email address can be preferred.') |
361 | + self.assertEqual(len(account.emailaddress_set.filter( |
362 | + status=EmailStatus.PREFERRED)), 1) |
363 | + self.assertEqual(account.preferredemail, email) |
364 | + |
365 | + def test_inline_forms(self): |
366 | + r = self.client.get('/admin/identityprovider/account/1/') |
367 | + self.assertEqual(r.status_code, 200) |
368 | + self.assertContains(r, '<input type="password" ' |
369 | + 'name="accountpassword-0-password"') |
370 | + |
371 | + def test_add_api_user(self): |
372 | + r = self.client.post('/admin/identityprovider/apiuser/add/', { |
373 | + 'username': 'api_user', |
374 | + 'password': 'backend'}) |
375 | + self.assertEqual(r.status_code, 302) |
376 | |
377 | === modified file 'identityprovider/tests/test_auth.py' |
378 | --- identityprovider/tests/test_auth.py 2010-05-10 14:07:45 +0000 |
379 | +++ identityprovider/tests/test_auth.py 2010-06-01 12:03:28 +0000 |
380 | @@ -20,10 +20,10 @@ |
381 | def test_authenticate_with_email_status_not_in_expected_one(self): |
382 | email_address = EmailAddress.objects.get( |
383 | email__iexact="mark@example.com") |
384 | - email_address.status = EmailStatus.NEW |
385 | + email_address.status = EmailStatus.OLD |
386 | email_address.save() |
387 | |
388 | - result = self.backend.authenticate('mark@example.com', '') |
389 | + result = self.backend.authenticate('mark@example.com', 'test') |
390 | |
391 | self.assertTrue(result is None) |
392 | |
393 | @@ -120,3 +120,19 @@ |
394 | |
395 | if created: |
396 | consumer.delete() |
397 | + |
398 | + def test_oauth_authenticate_stolen_token(self): |
399 | + victim_account = Account.objects.get_by_email('mark@example.com') |
400 | + token = create_oauth_token_for_account(victim_account, 'new-token') |
401 | + oauth_token = token.oauth_token() |
402 | + |
403 | + malicious_account = Account.objects.get_by_email('test@canonical.com') |
404 | + consumer, created = Consumer.objects.get_or_create(account=malicious_account) |
405 | + |
406 | + # make sure the accounts are active |
407 | + self.assertTrue(victim_account.status, AccountStatus.ACTIVE) |
408 | + self.assertTrue(malicious_account.status, AccountStatus.ACTIVE) |
409 | + |
410 | + # make sure authentication succeeds |
411 | + response = oauth_authenticate(consumer, oauth_token, None) |
412 | + self.assertFalse(response) |
413 | |
414 | === added file 'identityprovider/tests/test_backend_base.py' |
415 | --- identityprovider/tests/test_backend_base.py 1970-01-01 00:00:00 +0000 |
416 | +++ identityprovider/tests/test_backend_base.py 2010-06-01 12:03:28 +0000 |
417 | @@ -0,0 +1,212 @@ |
418 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
419 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
420 | + |
421 | +import re |
422 | +import random |
423 | + |
424 | +from django.conf import settings |
425 | +from django.core.cache import cache |
426 | +from django.db.backends.postgresql_psycopg2.base import ( |
427 | + DatabaseWrapper as PostgresDatabaseWrapper) |
428 | +from django.test import TestCase |
429 | + |
430 | +from identityprovider.backend.base import (AuthUserCache, CursorReadOnlyWrapper, |
431 | + DatabaseError, DatabaseWrapper, BaseCache, DjangoSessionCache) |
432 | +from identityprovider.readonly import readonly_manager |
433 | +from identityprovider.tests.mockdb import MockCursor |
434 | +from identityprovider.tests.test_backend_old_base import ( |
435 | + DatabaseWrapperTestCase as OldDatabaseWrapperTestCase) |
436 | + |
437 | + |
438 | +class BaseCacheTestCase(TestCase): |
439 | + |
440 | + def setUp(self): |
441 | + # initialize random number generator |
442 | + random.seed(42) |
443 | + # start with empty cache |
444 | + cache._cache.flush_all() |
445 | + |
446 | + self.cursor = MockCursor("'value'") |
447 | + |
448 | + self.base_cache = BaseCache() |
449 | + self.base_cache.table = "t" |
450 | + self.base_cache.primary_key = "w" |
451 | + self.match = re.match(CursorReadOnlyWrapper.select_pattern, |
452 | + 'SELECT "a", "b" FROM "t" WHERE "t"."w" = %s') |
453 | + |
454 | + def test_select_when_no_cached_value(self): |
455 | + value = self.base_cache.select(self.match, ["1"], self.cursor) |
456 | + |
457 | + self.assertEquals(value, "'value'") |
458 | + |
459 | + def test_select_when_there_is_cached_value(self): |
460 | + cache._cache.add(self.base_cache.cache_key("1"), "'VALUE'") |
461 | + value = self.base_cache.select(self.match, ["1"], self.cursor) |
462 | + |
463 | + self.assertEquals(value, "VALUE") |
464 | + |
465 | + |
466 | +class DjangoSessionCacheTestCase(TestCase): |
467 | + |
468 | + def test_update(self): |
469 | + mock = MockCursor("value") |
470 | + match = re.match( |
471 | + CursorReadOnlyWrapper.update_pattern, |
472 | + 'UPDATE "t" SET "session_data" = %s, "expire_date" = %s ' |
473 | + 'WHERE "django_session"."session_key" = %s' |
474 | + ) |
475 | + django_session_cache = DjangoSessionCache() |
476 | + django_session_cache.update(match, ["a", "b", "c"], mock) |
477 | + |
478 | + value = cache._cache.get(django_session_cache.cache_key("c")) |
479 | + self.assertEquals(value, "[['c', 'a', 'b']]") |
480 | + |
481 | + |
482 | +class CursorReadOnlyWrapperTestCase(TestCase): |
483 | + def setUp(self): |
484 | + # initialize random number generator |
485 | + random.seed(42) |
486 | + # start with empty cache |
487 | + cache._cache.flush_all() |
488 | + self.cursor = MockCursor() |
489 | + self.wrapper = CursorReadOnlyWrapper(self.cursor) |
490 | + |
491 | + def test_init(self): |
492 | + self.assertEqual(self.wrapper.cursor, self.cursor) |
493 | + self.assertEqual(self.wrapper.cache, None) |
494 | + |
495 | + def test_execute_cached_bad_command(self): |
496 | + sql = '' |
497 | + params = [] |
498 | + cached = self.wrapper.execute_cached('', sql, params) |
499 | + self.assertFalse(cached) |
500 | + |
501 | + def test_execute_cached_bad_sql(self): |
502 | + sql = '' |
503 | + params = [] |
504 | + cached = self.wrapper.execute_cached('select', sql, params) |
505 | + self.assertFalse(cached) |
506 | + |
507 | + def test_execute_cached_non_cached_table(self): |
508 | + sql = 'SELECT * FROM "account" WHERE account_pkey = %s' |
509 | + params = ['1'] |
510 | + cached = self.wrapper.execute_cached('select', sql, params) |
511 | + self.assertFalse(cached) |
512 | + |
513 | + def test_execute_cached_select(self): |
514 | + sql = 'SELECT * FROM "auth_user" WHERE auth_user_pkey = %s' |
515 | + params = ['1'] |
516 | + self.cursor.data = expected_values = [('auth_user_pkey', '1'), |
517 | + ('username', 'user')] |
518 | + cached = self.wrapper.execute_cached('select', sql, params) |
519 | + self.assertTrue(cached) |
520 | + self.assertEqual(self.wrapper._values, expected_values) |
521 | + |
522 | + def test_execute_cached_insert(self): |
523 | + sql = 'INSERT INTO "auth_user" (auth_user_pkey, username) VALUES (%s, %s)' |
524 | + params = ['1', 'user'] |
525 | + cached = self.wrapper.execute_cached('insert', sql, params) |
526 | + self.assertTrue(cached) |
527 | + key = self.wrapper.cache.cache_key('1') |
528 | + self.assertEqual(cache.get(key), str([params])) |
529 | + |
530 | + def test_execute_cached_delete(self): |
531 | + sql = 'DELETE FROM "auth_user" WHERE "auth_user_pkey" IN (%s)' |
532 | + params = ['1'] |
533 | + cached = self.wrapper.execute_cached('delete', sql, params) |
534 | + self.assertTrue(cached) |
535 | + key = self.wrapper.cache.cache_key('1') |
536 | + self.assertEqual(cache.get(key), '[]') |
537 | + |
538 | + def test_execute_cached_update(self): |
539 | + sql = 'UPDATE "auth_user" SET username=%s WHERE auth_user_pkey=%s' |
540 | + params = ['user', '1'] |
541 | + cached = self.wrapper.execute_cached('update', sql, params) |
542 | + self.assertTrue(cached) |
543 | + key = self.wrapper.cache.cache_key('1') |
544 | + self.assertEqual(cache.get(key), None) |
545 | + |
546 | + def test_execute_when_cached(self): |
547 | + sql = 'SELECT * FROM "auth_user" WHERE auth_user_pkey=%s' |
548 | + params = ['1'] |
549 | + self.cursor.data = [('auth_user_pkey', '1'), |
550 | + ('username', 'user')] |
551 | + result = self.wrapper.execute(sql, params) |
552 | + # query was cached |
553 | + self.assertEqual(result, None) |
554 | + |
555 | + def test_execute_select(self): |
556 | + sql = 'SELECT * FROM "account"' |
557 | + self.cursor.data = [('account_pkey', '1'), |
558 | + ('displayname', 'Sample Person')] |
559 | + result = self.wrapper.execute(sql) |
560 | + # query was not cached |
561 | + self.assertTrue(result) |
562 | + |
563 | + def test_execute_modify(self): |
564 | + sql = 'INSERT INTO "account" (account_pkey, displayname) VALUES (%s, %s)' |
565 | + params = ['1', 'Sample Person'] |
566 | + self.assertRaises(DatabaseError, self.wrapper.execute, sql, params) |
567 | + |
568 | + def test_fetchmany_not_cached(self): |
569 | + self.cursor.data = expected_values = [('auth_user_pkey', '1'), |
570 | + ('username', 'user')] |
571 | + result = self.wrapper.fetchmany(100) |
572 | + self.assertEqual(self.wrapper.cache, None) |
573 | + self.assertEqual(result, expected_values) |
574 | + |
575 | + def test_fetchmany_cached(self): |
576 | + self.cursor.data = expected_values = [('auth_user_pkey', '1'), |
577 | + ('username', 'user')] |
578 | + sql = 'SELECT * FROM "auth_user" WHERE auth_user_pkey=%s' |
579 | + params = ['1'] |
580 | + self.wrapper.execute_cached('select', sql, params) |
581 | + result = self.wrapper.fetchmany(100) |
582 | + self.assertTrue(isinstance(self.wrapper.cache, AuthUserCache)) |
583 | + self.assertEqual(result, expected_values) |
584 | + |
585 | + def test_fetchmany_cache_empty(self): |
586 | + self.cursor.data = [] |
587 | + sql = 'SELECT * FROM "auth_user" WHERE auth_user_pkey=%s' |
588 | + params = ['1'] |
589 | + self.wrapper.execute_cached('select', sql, params) |
590 | + self.assertRaises(StopIteration, self.wrapper.fetchmany, 100) |
591 | + |
592 | + def test_getattr(self): |
593 | + # test wrapper attribute |
594 | + self.assertEqual(self.wrapper.cache, None) |
595 | + # test cursor attribute |
596 | + self.assertEqual(self.wrapper.connection, None) |
597 | + |
598 | + |
599 | +class DatabaseWrapperTestCase(OldDatabaseWrapperTestCase): |
600 | + def setUp(self): |
601 | + super(DatabaseWrapperTestCase, self).setUp() |
602 | + settings.DATABASE_ENGINE = 'identityprovider.backend' |
603 | + |
604 | + def test__cursor_readonly(self): |
605 | + readonly_manager.set_readonly() |
606 | + |
607 | + expected_cursor = PostgresDatabaseWrapper()._cursor(settings) |
608 | + wrapper = DatabaseWrapper() |
609 | + cursor = wrapper._cursor(settings) |
610 | + self.assertTrue(isinstance(cursor, CursorReadOnlyWrapper)) |
611 | + self.assertEqual(type(cursor.cursor), type(expected_cursor)) |
612 | + self.assertEqual(cursor.cache, None) |
613 | + |
614 | + expected_cursor.connection.close() |
615 | + cursor.connection.close() |
616 | + |
617 | + readonly_manager.clear_readonly() |
618 | + |
619 | + def test__cursor_not_readonly(self): |
620 | + readonly_manager.clear_readonly() |
621 | + |
622 | + expected_cursor = PostgresDatabaseWrapper()._cursor(settings) |
623 | + wrapper = DatabaseWrapper() |
624 | + cursor = wrapper._cursor(settings) |
625 | + self.assertEqual(type(cursor), type(expected_cursor)) |
626 | + |
627 | + expected_cursor.connection.close() |
628 | + cursor.connection.close() |
629 | + |
630 | |
631 | === added file 'identityprovider/tests/test_backend_old_base.py' |
632 | --- identityprovider/tests/test_backend_old_base.py 1970-01-01 00:00:00 +0000 |
633 | +++ identityprovider/tests/test_backend_old_base.py 2010-06-01 12:03:28 +0000 |
634 | @@ -0,0 +1,53 @@ |
635 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
636 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
637 | + |
638 | +import psycopg2.extensions |
639 | +from django.conf import settings |
640 | +from django.db.backends.postgresql_psycopg2 import base as base_pg |
641 | +from django.db.backends.util import CursorDebugWrapper |
642 | +from django.test import TestCase |
643 | + |
644 | +from identityprovider.backend.old import base |
645 | + |
646 | + |
647 | +class DatabaseWrapperTestCase(TestCase): |
648 | + def setUp(self): |
649 | + self.old_DATABASE_ENGINE = settings.DATABASE_ENGINE |
650 | + settings.DATABASE_ENGINE = 'identityprovider.backend.old' |
651 | + self.old_DEBUG = settings.DEBUG |
652 | + |
653 | + def tearDown(self): |
654 | + settings.DATABASE_ENGINE = self.old_DATABASE_ENGINE |
655 | + settings.DEBUG = self.old_DEBUG |
656 | + |
657 | + def test_cursor_when_debug(self): |
658 | + settings.DEBUG = True |
659 | + |
660 | + expected_cursor = base_pg.DatabaseWrapper().cursor() |
661 | + wrapper = base.DatabaseWrapper() |
662 | + cursor = wrapper.cursor() |
663 | + self.assertTrue(isinstance(cursor, CursorDebugWrapper)) |
664 | + self.assertTrue(isinstance(expected_cursor, CursorDebugWrapper)) |
665 | + self.assertEqual(cursor.cursor.connection.dsn, |
666 | + expected_cursor.connection.dsn) |
667 | + self.assertEqual(cursor.db, wrapper) |
668 | + |
669 | + expected_cursor.connection.close() |
670 | + cursor.connection.close() |
671 | + |
672 | + def test_cursor_when_not_debug(self): |
673 | + settings.DEBUG = False |
674 | + |
675 | + expected_cursor = base_pg.DatabaseWrapper().cursor() |
676 | + wrapper = base.DatabaseWrapper() |
677 | + cursor = wrapper.cursor() |
678 | + self.assertTrue(isinstance(cursor, CursorDebugWrapper)) |
679 | + self.assertTrue(isinstance(expected_cursor, |
680 | + psycopg2.extensions.cursor)) |
681 | + self.assertEqual(cursor.cursor.connection.dsn, |
682 | + expected_cursor.connection.dsn) |
683 | + self.assertEqual(cursor.db, wrapper) |
684 | + |
685 | + expected_cursor.connection.close() |
686 | + cursor.connection.close() |
687 | + |
688 | |
689 | === modified file 'identityprovider/tests/test_command_sqlcachedflush.py' |
690 | --- identityprovider/tests/test_command_sqlcachedflush.py 2010-05-11 16:59:11 +0000 |
691 | +++ identityprovider/tests/test_command_sqlcachedflush.py 2010-06-01 12:03:28 +0000 |
692 | @@ -1,6 +1,7 @@ |
693 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
694 | # GNU Affero General Public License version 3 (see the file LICENSE). |
695 | |
696 | +import os |
697 | import signal |
698 | from subprocess import Popen, PIPE |
699 | |
700 | @@ -32,7 +33,7 @@ |
701 | if settings.DATABASE_USER: |
702 | args.extend(['-U', settings.DATABASE_USER]) |
703 | if settings.DATABASE_PASSWORD: |
704 | - _PGPASS = os.environ['PGPASSWORD'] |
705 | + _PGPASS = os.environ.get('PGPASSWORD') |
706 | os.environ['PGPASSWORD'] = settings.DATABASE_PASSWORD |
707 | if settings.DATABASE_HOST: |
708 | args.extend(['-h', settings.DATABASE_HOST]) |
709 | |
710 | === added file 'identityprovider/tests/test_command_sqlcachedloaddata.py' |
711 | --- identityprovider/tests/test_command_sqlcachedloaddata.py 1970-01-01 00:00:00 +0000 |
712 | +++ identityprovider/tests/test_command_sqlcachedloaddata.py 2010-06-01 12:03:28 +0000 |
713 | @@ -0,0 +1,132 @@ |
714 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
715 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
716 | + |
717 | +import os |
718 | +import signal |
719 | +import tempfile |
720 | +from subprocess import Popen, PIPE |
721 | + |
722 | +from django.conf import settings |
723 | +from django.core import serializers |
724 | +from django.core.management import call_command |
725 | +from django.test import TestCase |
726 | +from django.utils import simplejson |
727 | + |
728 | +from identityprovider.tests.utils import create_pgsql_functions |
729 | + |
730 | + |
731 | +class Alarm(Exception): |
732 | + pass |
733 | + |
734 | + |
735 | +def alarm_handler(signum, frame): |
736 | + raise Alarm |
737 | + |
738 | + |
739 | +class SQLCachedLoadDataCommandTestCase(TestCase): |
740 | + pgsql_functions = ['generate_openid_identifier'] |
741 | + |
742 | + def _pre_setup(self): |
743 | + super(SQLCachedLoadDataCommandTestCase, self)._pre_setup() |
744 | + create_pgsql_functions(self.pgsql_functions) |
745 | + |
746 | + def setUp(self): |
747 | + # set alarm handler |
748 | + self.old_signal = signal.signal(signal.SIGALRM, alarm_handler) |
749 | + |
750 | + def tearDown(self): |
751 | + # reset alarm handler |
752 | + signal.signal(signal.SIGALRM, self.old_signal) |
753 | + |
754 | + def compare_sqlcachedloaddata_command(self, fixtures): |
755 | + args = ['pg_dump'] |
756 | + if settings.DATABASE_USER: |
757 | + args.extend(['-U', settings.DATABASE_USER]) |
758 | + if settings.DATABASE_PASSWORD: |
759 | + _PGPASS = os.environ.get('PGPASSWORD') |
760 | + os.environ['PGPASSWORD'] = settings.DATABASE_PASSWORD |
761 | + if settings.DATABASE_HOST: |
762 | + args.extend(['-h', settings.DATABASE_HOST]) |
763 | + if settings.DATABASE_PORT: |
764 | + args.extend(['-p', settings.DATABASE_PORT]) |
765 | + args.append(settings.DATABASE_NAME) |
766 | + |
767 | + # make sure we start off a clean db |
768 | + call_command('flush', interactive=False, verbosity=0) |
769 | + |
770 | + options = {'interactive': False, 'verbosity': 0, 'commit': True} |
771 | + call_command('loaddata', *fixtures, **options) |
772 | + loaddata_sql = Popen(args, stdout=PIPE).communicate()[0] |
773 | + |
774 | + # clear db so we can load the data without triggering integrity errors |
775 | + call_command('flush', interactive=False, verbosity=0) |
776 | + |
777 | + call_command('sqlcachedloaddata', *fixtures, **options) |
778 | + # timeout after 5 seconds |
779 | + signal.alarm(5) |
780 | + try: |
781 | + sqlcachedloaddata_sql = Popen(args, stdout=PIPE).communicate()[0] |
782 | + # reset the alarm |
783 | + signal.alarm(0) |
784 | + except Alarm: |
785 | + self.fail() |
786 | + |
787 | + self.assertEqual(sqlcachedloaddata_sql, loaddata_sql) |
788 | + |
789 | + # play nice and leave the db clean |
790 | + call_command('flush', interactive=False, verbosity=0) |
791 | + |
792 | + def test_sqlcachedloaddata_command_not_cached(self): |
793 | + self.compare_sqlcachedloaddata_command(['test']) |
794 | + |
795 | + def test_sqlcachedloaddata_command_with_format(self): |
796 | + self.compare_sqlcachedloaddata_command(['test.json']) |
797 | + |
798 | + def test_sqlcachedloaddata_command_with_invalid_format(self): |
799 | + self.compare_sqlcachedloaddata_command(['test.txt']) |
800 | + |
801 | + def test_sqlcachedloaddata_command_with_fixture_dir(self): |
802 | + self.compare_sqlcachedloaddata_command(['/tmp/test.json']) |
803 | + |
804 | + def test_sqlcachedloaddata_command_conflicting_fixtures(self): |
805 | + # create two fixtures with the same name |
806 | + prefix = tempfile.gettempprefix() |
807 | + fixtures = ["/tmp/%s.json" % prefix, "/tmp/%s.xml" % prefix] |
808 | + simplejson.dump([], open(fixtures[0], 'a')) |
809 | + f = open(fixtures[1], 'a') |
810 | + f.write("""<?xml version="1.0" encoding="utf-8"?>""") |
811 | + f.close() |
812 | + |
813 | + self.compare_sqlcachedloaddata_command(["/tmp/%s" % prefix]) |
814 | + |
815 | + # remove fixture files |
816 | + for fixture in fixtures: |
817 | + os.unlink(fixture) |
818 | + |
819 | + def test_sqlcachedloaddata_command_interrupt(self): |
820 | + def mock_deserialize(format, fixture): |
821 | + self.exception_raised = True |
822 | + raise KeyboardInterrupt() |
823 | + |
824 | + old_deserialize = serializers.deserialize |
825 | + serializers.deserialize = mock_deserialize |
826 | + |
827 | + self.exception_raised = False |
828 | + self.compare_sqlcachedloaddata_command(['test']) |
829 | + self.assertTrue(self.exception_raised) |
830 | + |
831 | + serializers.deserialize = old_deserialize |
832 | + |
833 | + def test_sqlcachedloaddata_command_show_traceback(self): |
834 | + def mock_deserialize(format, fixture): |
835 | + self.exception_raised = True |
836 | + raise Exception() |
837 | + |
838 | + old_deserialize = serializers.deserialize |
839 | + serializers.deserialize = mock_deserialize |
840 | + |
841 | + self.exception_raised = False |
842 | + self.compare_sqlcachedloaddata_command(['test']) |
843 | + self.assertTrue(self.exception_raised) |
844 | + |
845 | + serializers.deserialize = old_deserialize |
846 | |
847 | === modified file 'identityprovider/tests/test_middleware.py' |
848 | --- identityprovider/tests/test_middleware.py 2010-05-07 23:31:41 +0000 |
849 | +++ identityprovider/tests/test_middleware.py 2010-06-01 12:03:28 +0000 |
850 | @@ -1,7 +1,9 @@ |
851 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
852 | # GNU Affero General Public License version 3 (see the file LICENSE). |
853 | |
854 | +import functools |
855 | import logging |
856 | +import re |
857 | import sys |
858 | |
859 | from django.conf import settings |
860 | @@ -136,3 +138,44 @@ |
861 | # Make sure public data is unaffected |
862 | self.assertEquals(req.POST['post_public'], self.POST_PUBLIC) |
863 | self.assertEquals(req.GET['get_public'], self.GET_PUBLIC) |
864 | + |
865 | + |
866 | +# The CSRF middleware requires a session cookie in order to activate. |
867 | +# The tests perform a login in order to acquire this session cookie. |
868 | +class CSRFMiddlewareTestCase(BasicAccountTestCase): |
869 | + |
870 | + def login(self): |
871 | + r = self.client.post('/+login', { |
872 | + 'email': 'mark@example.com', |
873 | + 'password': 'test'}) |
874 | + self.assertTrue('sessionid' in self.client.cookies) |
875 | + |
876 | + def test_allow_authorized(self): |
877 | + self.login() |
878 | + r = self.client.get('/') |
879 | + csrf_field = re.search('<input [^>]*name=[\'"]csrfmiddlewaretoken[\'"][^>]*/>', r.content) |
880 | + self.assertTrue(csrf_field) |
881 | + csrf_token = re.search(' value=[\'"]([^\'"]+)[\'"]', csrf_field.group()).group(1) |
882 | + r = self.client.post('/+edit', { |
883 | + 'displayname': 'Mark Shuttleworthy', |
884 | + 'csrfmiddlewaretoken': csrf_token |
885 | + }) |
886 | + self.assertNotEquals(r.status_code, 403) |
887 | + |
888 | + def test_forbid_unauthorized(self): |
889 | + self.login() |
890 | + r = self.client.post('/+edit', {'displayname': 'Mark Shuttleworthy'}) |
891 | + self.assertEquals(r.status_code, 403) |
892 | + |
893 | + def test_forbid_forged(self): |
894 | + self.login() |
895 | + r = self.client.post('/+edit', { |
896 | + 'displayname': 'Mark Shuttleworthy', |
897 | + 'csrfmiddlewaretoken': '0'}) |
898 | + self.assertEquals(r.status_code, 403) |
899 | + |
900 | + def test_ajax(self): |
901 | + self.login() |
902 | + r = self.client.post('/+edit', {'displayname': 'Mark Shuttleworthy'}, |
903 | + HTTP_X_REQUESTED_WITH='XMLHttpRequest') |
904 | + self.assertNotEquals(r.status_code, 403) |
905 | |
906 | === modified file 'identityprovider/tests/test_models_account.py' |
907 | --- identityprovider/tests/test_models_account.py 2010-04-21 15:29:24 +0000 |
908 | +++ identityprovider/tests/test_models_account.py 2010-06-01 12:03:28 +0000 |
909 | @@ -5,10 +5,13 @@ |
910 | from django.contrib.auth.models import User |
911 | from identityprovider.tests.utils import BasicAccountTestCase |
912 | from identityprovider.models.account import (Account, AccountPassword, |
913 | - AccountStatus) |
914 | + AccountStatus, LPAccount) |
915 | from identityprovider.models import account as a |
916 | from identityprovider.models.emailaddress import EmailAddress |
917 | from identityprovider.models.const import AccountCreationRationale, EmailStatus |
918 | +from identityprovider.readonly import readonly_manager |
919 | +from identityprovider.utils import encrypt_launchpad_password, generate_salt |
920 | +from identityprovider.webservice.models import AccountSet |
921 | |
922 | |
923 | class MockBackend(object): |
924 | @@ -50,6 +53,31 @@ |
925 | |
926 | self.assertTrue(account.last_login is not None) |
927 | |
928 | + def test_set_last_login_when_readonly(self): |
929 | + account = self.get_existing_account() |
930 | + account.last_login = last_login = datetime(2010, 01, 01) |
931 | + self.assertEqual(account.last_login, last_login) |
932 | + |
933 | + self.assertFalse(readonly_manager.is_readonly()) |
934 | + readonly_manager.set_readonly() |
935 | + |
936 | + try: |
937 | + account.last_login = now = datetime.now() |
938 | + self.assertEqual(account.last_login, last_login) |
939 | + self.assertNotEqual(account.last_login, now) |
940 | + finally: |
941 | + readonly_manager.clear_readonly() |
942 | + self.assertFalse(readonly_manager.is_readonly()) |
943 | + |
944 | + def test_set_last_login_when_no_preferredemail(self): |
945 | + account = self.get_new_account() |
946 | + for email in account.emailaddress_set.all(): |
947 | + email.status = EmailStatus.NEW |
948 | + email.save() |
949 | + account.last_login = datetime.now() |
950 | + self.assertRaises(User.DoesNotExist, User.objects.get, |
951 | + username=account.openid_identifier) |
952 | + |
953 | def test_is_active(self): |
954 | account = self.get_new_account() |
955 | account.status = AccountStatus.ACTIVE |
956 | @@ -168,3 +196,102 @@ |
957 | |
958 | account_password = AccountPassword.objects.get(account=account) |
959 | self.assertEqual(account_password.password, 'invalid') |
960 | + |
961 | + def test_save_when_readonly(self): |
962 | + account = self.get_existing_account() |
963 | + status = account.status |
964 | + self.assertNotEqual(status, AccountStatus.SUSPENDED) |
965 | + |
966 | + self.assertFalse(readonly_manager.is_readonly()) |
967 | + readonly_manager.set_readonly() |
968 | + |
969 | + try: |
970 | + account.status = AccountStatus.SUSPENDED |
971 | + account.save() |
972 | + |
973 | + # refresh account from db |
974 | + account = self.get_existing_account() |
975 | + self.assertEqual(account.status, status) |
976 | + finally: |
977 | + readonly_manager.clear_readonly() |
978 | + self.assertFalse(readonly_manager.is_readonly()) |
979 | + |
980 | + def test_save_when_no_password(self): |
981 | + account = self.get_existing_account() |
982 | + # remove all passwords |
983 | + AccountPassword.objects.filter(account=account).delete() |
984 | + |
985 | + # trigger test |
986 | + account.status = AccountStatus.SUSPENDED |
987 | + account.save() |
988 | + |
989 | + password_count = AccountPassword.objects.filter(account=account).count() |
990 | + self.assertEqual(password_count, 0) |
991 | + |
992 | + def test_parent(self): |
993 | + account = self.get_existing_account() |
994 | + parent = getattr(account, '__parent__') |
995 | + self.assertTrue(isinstance(parent, AccountSet)) |
996 | + |
997 | + def test_url_path(self): |
998 | + account = self.get_existing_account() |
999 | + url_path = getattr(account, '__url_path__') |
1000 | + self.assertEqual(url_path, str(account.id)) |
1001 | + |
1002 | + def test_create_account_with_rationale(self): |
1003 | + salt = generate_salt() |
1004 | + account = Account.objects.create_account('displayname', 'username', |
1005 | + 'password', AccountCreationRationale.USER_CREATED, salt=salt) |
1006 | + |
1007 | + self.assertEqual(account.displayname, 'displayname') |
1008 | + self.assertEqual(account.creation_rationale, |
1009 | + AccountCreationRationale.USER_CREATED) |
1010 | + self.assertEqual(account.status, AccountStatus.ACTIVE) |
1011 | + |
1012 | + emails = EmailAddress.objects.filter(account=account) |
1013 | + self.assertEqual(emails.count(), 1) |
1014 | + email = emails[0] |
1015 | + self.assertEqual(email.email, 'username') |
1016 | + self.assertEqual(email.status, EmailStatus.PREFERRED) |
1017 | + |
1018 | + passwords = AccountPassword.objects.filter(account=account) |
1019 | + self.assertEqual(passwords.count(), 1) |
1020 | + password = passwords[0] |
1021 | + self.assertEqual(password.password, |
1022 | + encrypt_launchpad_password('password', salt=salt)) |
1023 | + |
1024 | + def test_create_account_with_no_rationale(self): |
1025 | + salt = generate_salt() |
1026 | + account = Account.objects.create_account('displayname', 'username', |
1027 | + 'password', salt=salt) |
1028 | + |
1029 | + self.assertEqual(account.displayname, 'displayname') |
1030 | + self.assertEqual(account.creation_rationale, |
1031 | + AccountCreationRationale.OWNER_CREATED_LAUNCHPAD) |
1032 | + self.assertEqual(account.status, AccountStatus.ACTIVE) |
1033 | + |
1034 | + emails = EmailAddress.objects.filter(account=account) |
1035 | + self.assertEqual(emails.count(), 1) |
1036 | + email = emails[0] |
1037 | + self.assertEqual(email.email, 'username') |
1038 | + self.assertEqual(email.status, EmailStatus.PREFERRED) |
1039 | + |
1040 | + passwords = AccountPassword.objects.filter(account=account) |
1041 | + self.assertEqual(passwords.count(), 1) |
1042 | + password = passwords[0] |
1043 | + self.assertEqual(password.password, |
1044 | + encrypt_launchpad_password('password', salt=salt)) |
1045 | + |
1046 | + |
1047 | +class AccountPasswordTestCase(BasicAccountTestCase): |
1048 | + def test_unicode(self): |
1049 | + account = Account(openid_identifier='oid', displayname='displayname') |
1050 | + password = AccountPassword(account=account, password='password') |
1051 | + self.assertEqual(unicode(password), u'Password for displayname') |
1052 | + |
1053 | + |
1054 | +class LPAccountTestCase(BasicAccountTestCase): |
1055 | + def test_unicode(self): |
1056 | + account = LPAccount(openid_identifier='oid') |
1057 | + self.assertEqual(unicode(account), u'LP account for oid') |
1058 | + |
1059 | |
1060 | === added file 'identityprovider/tests/test_models_api.py' |
1061 | --- identityprovider/tests/test_models_api.py 1970-01-01 00:00:00 +0000 |
1062 | +++ identityprovider/tests/test_models_api.py 2010-06-01 12:03:28 +0000 |
1063 | @@ -0,0 +1,41 @@ |
1064 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
1065 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1066 | + |
1067 | +from identityprovider.models.api import APIUser |
1068 | +from identityprovider.tests.utils import SQLCachedTestCase |
1069 | +from identityprovider.utils import encrypt_launchpad_password, generate_salt |
1070 | + |
1071 | + |
1072 | +class APIUserTestCase(SQLCachedTestCase): |
1073 | + def setUp(self): |
1074 | + super(APIUserTestCase, self).setUp() |
1075 | + |
1076 | + self.salt = generate_salt() |
1077 | + self.user = APIUser(username='username') |
1078 | + self.user.set_password('password', salt=self.salt) |
1079 | + self.user.save() |
1080 | + |
1081 | + def test_set_password(self): |
1082 | + self.user.set_password('otherpassword', salt=self.salt) |
1083 | + self.assertEqual(self.user.password, |
1084 | + encrypt_launchpad_password('otherpassword', salt=self.salt)) |
1085 | + |
1086 | + def test_verify_password(self): |
1087 | + self.user.set_password('password', salt=self.salt) |
1088 | + self.assertFalse(self.user.verify_password('otherpassword')) |
1089 | + self.assertTrue(self.user.verify_password('password')) |
1090 | + |
1091 | + def test_authenticate_user_exists(self): |
1092 | + user = APIUser.authenticate('username', 'password') |
1093 | + self.assertEqual(user, self.user) |
1094 | + |
1095 | + def test_authenticate_wrong_password(self): |
1096 | + user = APIUser.authenticate('username', 'otherpassword') |
1097 | + self.assertEqual(user, None) |
1098 | + |
1099 | + def test_authenticate_user_does_not_exist(self): |
1100 | + user = APIUser.authenticate('bad_user', 'password') |
1101 | + self.assertEqual(user, None) |
1102 | + |
1103 | + def test_unicode(self): |
1104 | + self.assertEqual(unicode(self.user), u'username') |
1105 | |
1106 | === modified file 'identityprovider/tests/test_models_authtoken.py' |
1107 | --- identityprovider/tests/test_models_authtoken.py 2010-04-21 15:29:24 +0000 |
1108 | +++ identityprovider/tests/test_models_authtoken.py 2010-06-01 12:03:28 +0000 |
1109 | @@ -1,38 +1,60 @@ |
1110 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
1111 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1112 | |
1113 | +import random |
1114 | from django.conf import settings |
1115 | |
1116 | from identityprovider.tests.utils import BasicAccountTestCase |
1117 | from identityprovider.models.account import Account |
1118 | from identityprovider.models.authtoken import (AuthToken, AuthTokenFactory, |
1119 | - send_validation_email_request) |
1120 | + send_validation_email_request, create_unique_token_for_table, valid_email) |
1121 | +from identityprovider.models.const import EmailStatus, LoginTokenType |
1122 | |
1123 | |
1124 | class AuthTokenTestCase(BasicAccountTestCase): |
1125 | - |
1126 | - def test_sendEmailValidationRequest(self): |
1127 | + def setUp(self): |
1128 | def mock_send_email(self, from_name, subject, message): |
1129 | self.message = message |
1130 | |
1131 | + super(AuthTokenTestCase, self).setUp() |
1132 | + |
1133 | # override method to capture message |
1134 | - _send_email = AuthToken._send_email |
1135 | + self.old_send_email = AuthToken._send_email |
1136 | AuthToken._send_email = mock_send_email |
1137 | |
1138 | - # make sure we start from a clean environment |
1139 | - self.assertFalse(hasattr(self, 'message')) |
1140 | - |
1141 | + def tearDown(self): |
1142 | + AuthToken._send_email = self.old_send_email |
1143 | + |
1144 | + super(AuthTokenTestCase, self).tearDown() |
1145 | + |
1146 | + def test_sendEmailValidationRequest(self): |
1147 | account = Account.objects.get_by_email('test@canonical.com') |
1148 | token = AuthTokenFactory().new_api_email_validation_token( |
1149 | account, 'email@domain.com') |
1150 | + |
1151 | + self.assertFalse(hasattr(token, 'message')) |
1152 | token.sendEmailValidationRequest() |
1153 | - |
1154 | self.assertTrue(hasattr(token, 'message')) |
1155 | self.assertTrue(settings.SUPPORT_FORM_URL in token.message) |
1156 | |
1157 | - # cleanup |
1158 | - del token.message |
1159 | - AuthToken._send_email = _send_email |
1160 | + def test_sendEmailValidationToken(self): |
1161 | + account = Account.objects.get_by_email('test@canonical.com') |
1162 | + token = AuthTokenFactory().new_api_email_validation_token( |
1163 | + account, 'email@domain.com') |
1164 | + self.assertFalse(hasattr(token, 'message')) |
1165 | + token.sendEmailValidationToken() |
1166 | + self.assertTrue(hasattr(token, 'message')) |
1167 | + self.assertTrue(token.token in token.message) |
1168 | + |
1169 | + def test_sendNewUserEmail(self): |
1170 | + account = Account.objects.get_by_email('test@canonical.com') |
1171 | + token = AuthTokenFactory().new_api_email_validation_token( |
1172 | + account, 'email@domain.com') |
1173 | + |
1174 | + self.assertFalse(hasattr(token, 'message')) |
1175 | + token.sendNewUserEmail() |
1176 | + self.assertTrue(hasattr(token, 'message')) |
1177 | + self.assertTrue(token.get_absolute_url() in token.message) |
1178 | |
1179 | def test_send_validation_email_request_no_preferredemail(self): |
1180 | def mock_sendEmailValidationRequest(self): |
1181 | @@ -40,16 +62,79 @@ |
1182 | _sendEmailValidationRequest = AuthToken.sendEmailValidationRequest |
1183 | AuthToken.sendEmailValidationRequest = mock_sendEmailValidationRequest |
1184 | AuthToken.sendEmailValidationRequest_called = False |
1185 | - # replace preferredemail property |
1186 | - _preferredemail = Account.preferredemail |
1187 | - Account.preferredemail = None |
1188 | |
1189 | account = Account.objects.get_by_email('test@canonical.com') |
1190 | + # remove preferredemail address |
1191 | + for email in account.emailaddress_set.all(): |
1192 | + email.status = EmailStatus.NEW |
1193 | + email.save() |
1194 | + |
1195 | email = 'some@email.com' |
1196 | redirection_url = 'http://localhost/' |
1197 | send_validation_email_request(account, email, redirection_url) |
1198 | self.assertTrue(AuthToken.sendEmailValidationRequest_called) |
1199 | |
1200 | - Account.preferredemail = _preferredemail |
1201 | del AuthToken.sendEmailValidationRequest_called |
1202 | AuthToken.sendEmailValidationRequest = _sendEmailValidationRequest |
1203 | + |
1204 | + def test_consume(self): |
1205 | + email = 'mark@example.com' |
1206 | + token_type = LoginTokenType.NEWACCOUNT |
1207 | + # need more than one token to fully test the method |
1208 | + token = AuthToken(email=email, token_type=token_type, token='a') |
1209 | + token.save() |
1210 | + token2 = AuthToken(email=email, token_type=token_type, token='b') |
1211 | + token2.save() |
1212 | + self.assertEqual(token.date_consumed, None) |
1213 | + self.assertEqual(token2.date_consumed, None) |
1214 | + |
1215 | + token.consume() |
1216 | + |
1217 | + tokens = AuthToken.objects.filter(email=token.email, |
1218 | + token_type=token.token_type, requester=token.requester) |
1219 | + self.assertEqual(tokens.count(), 2) |
1220 | + self.assertNotEqual(tokens[0].date_consumed, None) |
1221 | + self.assertNotEqual(tokens[1].date_consumed, None) |
1222 | + |
1223 | + def test_new_token_invalid_type(self): |
1224 | + factory = AuthTokenFactory() |
1225 | + good_types = [LoginTokenType.PASSWORDRECOVERY, |
1226 | + LoginTokenType.NEWACCOUNT, |
1227 | + LoginTokenType.VALIDATEEMAIL, |
1228 | + LoginTokenType.NEWPERSONLESSACCOUNT] |
1229 | + bad_types = [t[0] for t in LoginTokenType._get_choices() \ |
1230 | + if t[0] not in good_types] |
1231 | + for token_type in bad_types: |
1232 | + self.assertRaises(ValueError, factory.new, None, '', |
1233 | + 'mark@example.com', token_type, '') |
1234 | + |
1235 | + def test_create_unique_token_for_table(self): |
1236 | + # initialize randomizer |
1237 | + random.seed(42) |
1238 | + |
1239 | + # create a token |
1240 | + token1 = create_unique_token_for_table(1, AuthToken, 'token') |
1241 | + # use that token within an AuthToken |
1242 | + AuthToken(email='mark@example.com', |
1243 | + token_type=LoginTokenType.NEWACCOUNT, token=token1).save() |
1244 | + |
1245 | + # reinitialize randomizer to force same token |
1246 | + random.seed(42) |
1247 | + token2 = create_unique_token_for_table(1, AuthToken, 'token') |
1248 | + self.assertNotEqual(token1, token2) |
1249 | + |
1250 | + def test_valid_email(self): |
1251 | + emails = {'valid': ['kiko.async@hotmail.com', 'kiko+async@hotmail.com', |
1252 | + 'kiko-async@hotmail.com', 'kiko_async@hotmail.com', |
1253 | + 'kiko@async.com.br', 'kiko@canonical.com', |
1254 | + 'kiko@UBUNTU.COM', 'i@tv', 'kiko@gnu.info', |
1255 | + 'user@z.de', 'bob=dobbs@example.com', |
1256 | + 'keith@risby-family.co.uk'], |
1257 | + 'invalid': ['user@z..de', 'user@.z.de', |
1258 | + 'keith@risby-family-.co.uk', |
1259 | + 'keith@-risby-family.co.uk']} |
1260 | + for email in emails['valid']: |
1261 | + self.assertTrue(valid_email(email)) |
1262 | + for email in emails['invalid']: |
1263 | + self.assertFalse(valid_email(email)) |
1264 | + |
1265 | |
1266 | === added file 'identityprovider/tests/test_models_captcha.py' |
1267 | --- identityprovider/tests/test_models_captcha.py 1970-01-01 00:00:00 +0000 |
1268 | +++ identityprovider/tests/test_models_captcha.py 2010-06-01 12:03:28 +0000 |
1269 | @@ -0,0 +1,157 @@ |
1270 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
1271 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1272 | + |
1273 | +import unittest |
1274 | +import urllib2 |
1275 | +from cStringIO import StringIO |
1276 | +from django.conf import settings |
1277 | + |
1278 | +from identityprovider.models.captcha import Captcha, CaptchaResponse |
1279 | + |
1280 | + |
1281 | +class CaptchaResponseTestCase(unittest.TestCase): |
1282 | + def test_no_data(self): |
1283 | + r = CaptchaResponse(200, None) |
1284 | + self.assertEqual(r.data(), None) |
1285 | + |
1286 | + def test_data_from_response(self): |
1287 | + resp = StringIO('this is the response') |
1288 | + r = CaptchaResponse(200, resp) |
1289 | + self.assertEqual(r._data, None) |
1290 | + self.assertEqual(r.data(), 'this is the response') |
1291 | + |
1292 | + def test_data_exists(self): |
1293 | + r = CaptchaResponse(200, None) |
1294 | + r._data = 'this is the data' |
1295 | + self.assertEqual(r.data(), 'this is the data') |
1296 | + |
1297 | + |
1298 | +class CaptchaTestCase(unittest.TestCase): |
1299 | + def test_new_captcha_oops(self): |
1300 | + @classmethod |
1301 | + def mock_open(cls, request, env): |
1302 | + r = CaptchaResponse(500, None, 'a traceback') |
1303 | + return r |
1304 | + old_open = Captcha._open |
1305 | + Captcha._open = mock_open |
1306 | + |
1307 | + c = Captcha.new({}) |
1308 | + self.assertTrue(c.env['oops-dump']) |
1309 | + |
1310 | + Captcha._open = old_open |
1311 | + |
1312 | + def test_new_captcha_no_challenge(self): |
1313 | + @classmethod |
1314 | + def mock_open(cls, request, env): |
1315 | + r = CaptchaResponse(200, StringIO('')) |
1316 | + return r |
1317 | + old_open = Captcha._open |
1318 | + Captcha._open = mock_open |
1319 | + |
1320 | + c = Captcha.new({}) |
1321 | + self.assertEqual(c.captcha_id, None) |
1322 | + self.assertEqual(c.image_url, None) |
1323 | + |
1324 | + Captcha._open = old_open |
1325 | + |
1326 | + def test_captcha_open(self): |
1327 | + class MockOpener(object): |
1328 | + def open(self, request): |
1329 | + raise urllib2.URLError((-1, 'error')) |
1330 | + old_opener = Captcha.opener |
1331 | + Captcha.opener = MockOpener() |
1332 | + |
1333 | + self.assertRaises(urllib2.URLError, Captcha.new, {}) |
1334 | + |
1335 | + Captcha.opener = old_opener |
1336 | + |
1337 | +class CaptchaVerifyTestCase(unittest.TestCase): |
1338 | + def setUp(self): |
1339 | + self.old_DISABLE_CAPTCHA_VERIFICATION = settings.DISABLE_CAPTCHA_VERIFICATION |
1340 | + settings.DISABLE_CAPTCHA_VERIFICATION = False |
1341 | + |
1342 | + def tearDown(self): |
1343 | + settings.DISABLE_CAPTCHA_VERIFICATION = self.old_DISABLE_CAPTCHA_VERIFICATION |
1344 | + |
1345 | + def test_verify_already_verified(self): |
1346 | + c = Captcha.new({}) |
1347 | + c._verified = True |
1348 | + r = c.verify(None) |
1349 | + self.assertTrue(r) |
1350 | + |
1351 | + def test_verify_no_verification(self): |
1352 | + settings.DISABLE_CAPTCHA_VERIFICATION = True |
1353 | + |
1354 | + c = Captcha.new({}) |
1355 | + r = c.verify(None) |
1356 | + self.assertTrue(r) |
1357 | + |
1358 | + def test_verify_response_ok(self): |
1359 | + @classmethod |
1360 | + def mock_open(cls, request, env): |
1361 | + r = CaptchaResponse(200, StringIO('true\nok')) |
1362 | + return r |
1363 | + old_open = Captcha._open |
1364 | + Captcha._open = mock_open |
1365 | + |
1366 | + c = Captcha.new({'REMOTE_ADDR': 'localhost'}) |
1367 | + r = c.verify(None) |
1368 | + self.assertTrue(r) |
1369 | + self.assertEqual(c.message, 'ok') |
1370 | + |
1371 | + Captcha._open = old_open |
1372 | + |
1373 | + def test_verify_no_captcha_id(self): |
1374 | + @classmethod |
1375 | + def mock_open(cls, request, env): |
1376 | + r = CaptchaResponse(200, StringIO(''), 'a traceback') |
1377 | + return r |
1378 | + old_open = Captcha._open |
1379 | + Captcha._open = mock_open |
1380 | + |
1381 | + c = Captcha.new({'REMOTE_ADDR': 'localhost'}) |
1382 | + r = c.verify(None) |
1383 | + self.assertFalse(r) |
1384 | + self.assertEqual(c.message, 'no-challenge') |
1385 | + |
1386 | + Captcha._open = old_open |
1387 | + |
1388 | + def test_verify_error(self): |
1389 | + @classmethod |
1390 | + def mock_open(cls, request, env): |
1391 | + r = CaptchaResponse(500, None, 'a traceback') |
1392 | + return r |
1393 | + old_open = Captcha._open |
1394 | + Captcha._open = mock_open |
1395 | + |
1396 | + class MockEnv(dict): |
1397 | + def __getattribute__(self, attr): |
1398 | + if attr == '__setitem__': |
1399 | + raise AttributeError() |
1400 | + else: |
1401 | + return super(MockEnv, self).__getattribute__(attr) |
1402 | + env = MockEnv({'REMOTE_ADDR': 'localhost'}) |
1403 | + |
1404 | + c = Captcha(env, 'challenge-id') |
1405 | + r = c.verify(None) |
1406 | + self.assertFalse(r) |
1407 | + self.assertFalse('oops-dump' in c.env) |
1408 | + |
1409 | + Captcha._open = old_open |
1410 | + |
1411 | + def test_verify_error_oops(self): |
1412 | + @classmethod |
1413 | + def mock_open(cls, request, env): |
1414 | + r = CaptchaResponse(500, None, 'a traceback') |
1415 | + return r |
1416 | + old_open = Captcha._open |
1417 | + Captcha._open = mock_open |
1418 | + |
1419 | + c = Captcha.new({'REMOTE_ADDR': 'localhost'}) |
1420 | + c.captcha_id = 'challenge-id' |
1421 | + r = c.verify(None) |
1422 | + self.assertFalse(r) |
1423 | + self.assertTrue(c.env['oops-dump']) |
1424 | + |
1425 | + Captcha._open = old_open |
1426 | + |
1427 | |
1428 | === modified file 'identityprovider/tests/test_models_oauthtoken.py' |
1429 | --- identityprovider/tests/test_models_oauthtoken.py 2010-04-21 15:29:24 +0000 |
1430 | +++ identityprovider/tests/test_models_oauthtoken.py 2010-06-01 12:03:28 +0000 |
1431 | @@ -3,8 +3,8 @@ |
1432 | |
1433 | from identityprovider.tests.utils import BasicAccountTestCase |
1434 | from identityprovider.models.account import Account |
1435 | -from identityprovider.models.oauthtoken import (Consumer, |
1436 | - create_oauth_token_for_account) |
1437 | +from identityprovider.models.oauthtoken import (Consumer, DataStore, Nonce, |
1438 | + Token, OAuthToken, create_oauth_token_for_account) |
1439 | |
1440 | |
1441 | class ConsumerTestCase(BasicAccountTestCase): |
1442 | @@ -34,3 +34,76 @@ |
1443 | token = create_oauth_token_for_account(account, 'new-token') |
1444 | |
1445 | self.assertEquals(token.consumer.account.id, account.id) |
1446 | + |
1447 | + |
1448 | +class TokenTestCase(BasicAccountTestCase): |
1449 | + def setUp(self): |
1450 | + self.account = Account.objects.get_by_email('test@canonical.com') |
1451 | + self.consumer, _ = Consumer.objects.get_or_create(account=self.account) |
1452 | + self.token = Token(consumer=self.consumer, token='token', |
1453 | + token_secret='secret', name='name') |
1454 | + def test_unicode(self): |
1455 | + self.assertEqual(unicode(self.token), u'token') |
1456 | + |
1457 | + def test_serialize(self): |
1458 | + expected = {'consumer_key': self.consumer.key, |
1459 | + 'consumer_secret': self.consumer.secret, |
1460 | + 'token': 'token', |
1461 | + 'token_secret': 'secret', |
1462 | + 'name': 'name'} |
1463 | + self.assertEqual(self.token.serialize(), expected) |
1464 | + |
1465 | + |
1466 | +class ConsumerTestCase(BasicAccountTestCase): |
1467 | + def test_unicode(self): |
1468 | + account = Account.objects.get_by_email('test@canonical.com') |
1469 | + consumer, _ = Consumer.objects.get_or_create(account=account) |
1470 | + self.assertEqual(unicode(consumer), unicode(account.openid_identifier)) |
1471 | + |
1472 | + |
1473 | +class DataStoreTestCase(BasicAccountTestCase): |
1474 | + def setUp(self): |
1475 | + self.ds = DataStore() |
1476 | + account = Account.objects.get_by_email('test@canonical.com') |
1477 | + self.consumer, _ = Consumer.objects.get_or_create(account=account) |
1478 | + self.token, _ = Token.objects.get_or_create(consumer=self.consumer, |
1479 | + token='token', token_secret='secret', name='name') |
1480 | + self.nonce, _ = Nonce.objects.get_or_create(consumer=self.consumer, |
1481 | + token=self.token, nonce='nonce') |
1482 | + |
1483 | + def test_lookup_token_wrong_type(self): |
1484 | + self.assertRaises(AssertionError, self.ds.lookup_token, 'value', |
1485 | + 'token') |
1486 | + |
1487 | + def test_lookup_token_exists(self): |
1488 | + # create a token |
1489 | + token = self.ds.lookup_token('access', 'token') |
1490 | + self.assertEqual(token.key, 'token') |
1491 | + self.assertEqual(token.secret, 'secret') |
1492 | + |
1493 | + def test_lookup_token_not_exists(self): |
1494 | + self.token.delete() |
1495 | + token = self.ds.lookup_token('access', 'token') |
1496 | + self.assertEqual(token, None) |
1497 | + |
1498 | + def test_lookup_consumer_exists(self): |
1499 | + consumer_key = self.consumer.key |
1500 | + result = self.ds.lookup_consumer(consumer_key) |
1501 | + self.assertEqual(result.key, self.consumer.key) |
1502 | + self.assertEqual(result.secret, self.consumer.secret) |
1503 | + |
1504 | + def test_lookup_consumer_not_exists(self): |
1505 | + consumer_key = 'foo' |
1506 | + consumer = self.ds.lookup_consumer(consumer_key) |
1507 | + self.assertEqual(consumer, None) |
1508 | + |
1509 | + def test_lookup_nonce_exists(self): |
1510 | + otoken = OAuthToken(self.token.token, self.token.token_secret) |
1511 | + r = self.ds.lookup_nonce(self.consumer, otoken, 'nonce') |
1512 | + self.assertTrue(r) |
1513 | + |
1514 | + def test_lookup_nonce_not_exists(self): |
1515 | + self.nonce.delete() |
1516 | + otoken = OAuthToken(self.token.token, self.token.token_secret) |
1517 | + r = self.ds.lookup_nonce(self.consumer, otoken, 'nonce') |
1518 | + self.assertFalse(r) |
1519 | |
1520 | === modified file 'identityprovider/tests/test_models_openidmodels.py' |
1521 | --- identityprovider/tests/test_models_openidmodels.py 2010-04-21 15:29:24 +0000 |
1522 | +++ identityprovider/tests/test_models_openidmodels.py 2010-06-01 12:03:28 +0000 |
1523 | @@ -1,46 +1,52 @@ |
1524 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
1525 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1526 | |
1527 | +import base64 |
1528 | +from datetime import datetime, timedelta |
1529 | from time import time |
1530 | from openid.association import Association |
1531 | from openid.store.nonce import SKEW |
1532 | + |
1533 | +from identityprovider.const import NEVER_EXPIRES |
1534 | +from identityprovider.models.account import Account |
1535 | +from identityprovider.models.openidmodels import (DjangoOpenIDStore, |
1536 | + OpenIDAssociation, OpenIDAuthorization, OpenIDNonce, OpenIDRPConfig, |
1537 | + OpenIDRPSummary) |
1538 | +from identityprovider.readonly import readonly_manager |
1539 | from identityprovider.tests.utils import BasicAccountTestCase |
1540 | -from identityprovider.models.openidmodels import (DjangoOpenIDStore, |
1541 | - OpenIDAssociation, |
1542 | - OpenIDRPConfig, |
1543 | - OpenIDNonce) |
1544 | |
1545 | |
1546 | class DjangoOpenIDStoreTestCase(BasicAccountTestCase): |
1547 | |
1548 | def setUp(self): |
1549 | + self.server_url = 'http://server.example.com' |
1550 | self.store = DjangoOpenIDStore() |
1551 | - OpenIDAssociation.objects.get_or_create( |
1552 | - server_url="http://server.example.com", |
1553 | + self.assoc, _ = OpenIDAssociation.objects.get_or_create( |
1554 | + server_url=self.server_url, |
1555 | handle="handle", secret="secret".encode("base64"), |
1556 | issued=time(), lifetime=1000, assoc_type='HMAC-SHA1') |
1557 | |
1558 | def test_store_association_when_association_already_exists(self): |
1559 | association = Association("handle", "secret", 2, 2, 'HMAC-SHA1') |
1560 | |
1561 | - self.store.storeAssociation("http://server.example.com", association) |
1562 | + self.store.storeAssociation(self.server_url, association) |
1563 | openid_association = OpenIDAssociation.objects.get( |
1564 | - server_url="http://server.example.com", handle="handle") |
1565 | + server_url=self.server_url, handle="handle") |
1566 | |
1567 | self.assertEqual(openid_association.issued, 2) |
1568 | |
1569 | def test_get_association_with_handle_none(self): |
1570 | - association = self.store.getAssociation("http://server.example.com") |
1571 | + association = self.store.getAssociation(self.server_url) |
1572 | |
1573 | self.assertEqual(association.handle, "handle") |
1574 | |
1575 | def test_get_association_when_lifetime_is_zero(self): |
1576 | openid_association = OpenIDAssociation.objects.get( |
1577 | - server_url="http://server.example.com", handle="handle") |
1578 | + server_url=self.server_url, handle="handle") |
1579 | openid_association.lifetime = 0 |
1580 | openid_association.save() |
1581 | |
1582 | - association = self.store.getAssociation("http://server.example.com") |
1583 | + association = self.store.getAssociation(self.server_url) |
1584 | |
1585 | self.assertTrue(association is None) |
1586 | |
1587 | @@ -61,6 +67,55 @@ |
1588 | OpenIDNonce.objects.get( |
1589 | server_url="http://example.com", salt="salt").delete() |
1590 | |
1591 | + def test_getAssociation_not_existing(self): |
1592 | + OpenIDAssociation.objects.filter(server_url=self.server_url).delete() |
1593 | + assoc = self.store.getAssociation(self.server_url) |
1594 | + self.assertEqual(assoc, None) |
1595 | + |
1596 | + def test_getAssociation_existing_no_handle(self): |
1597 | + expected = Association(self.assoc.handle, |
1598 | + base64.b64decode(str(self.assoc.secret)), int(self.assoc.issued), |
1599 | + self.assoc.lifetime, self.assoc.assoc_type) |
1600 | + assoc = self.store.getAssociation(self.server_url) |
1601 | + self.assertEqual(assoc, expected) |
1602 | + |
1603 | + def test_getAssociation_expired(self): |
1604 | + # make sure the association has expired |
1605 | + self.assoc.lifetime = 0 |
1606 | + self.assoc.save() |
1607 | + |
1608 | + assoc = self.store.getAssociation(self.server_url) |
1609 | + self.assertEqual(assoc, None) |
1610 | + assocs = OpenIDAssociation.objects.filter(server_url=self.server_url) |
1611 | + self.assertEqual(assocs.count(), 0) |
1612 | + |
1613 | + def test_getAssociation_existing_same_handle(self): |
1614 | + expected = Association(self.assoc.handle, |
1615 | + base64.b64decode(str(self.assoc.secret)), int(self.assoc.issued), |
1616 | + self.assoc.lifetime, self.assoc.assoc_type) |
1617 | + assoc = self.store.getAssociation(self.server_url, 'handle') |
1618 | + self.assertEqual(assoc, expected) |
1619 | + |
1620 | + def test_getAssociation_existing_different_handle(self): |
1621 | + assoc = self.store.getAssociation(self.server_url, 'otherhandle') |
1622 | + self.assertEqual(assoc, None) |
1623 | + |
1624 | + def test_cleanupNonce(self): |
1625 | + OpenIDNonce.objects.create(server_url=self.server_url, timestamp=0, |
1626 | + salt='') |
1627 | + |
1628 | + self.assertEqual(OpenIDNonce.objects.all().count(), 1) |
1629 | + self.store.cleanupNonce() |
1630 | + self.assertEqual(OpenIDNonce.objects.all().count(), 0) |
1631 | + |
1632 | + def test_cleanupAssociations(self): |
1633 | + self.assoc.lifetime = 0 |
1634 | + self.assoc.save() |
1635 | + |
1636 | + self.assertEqual(OpenIDAssociation.objects.all().count(), 1) |
1637 | + self.store.cleanupAssociations() |
1638 | + self.assertEqual(OpenIDAssociation.objects.all().count(), 0) |
1639 | + |
1640 | |
1641 | class OpenIDRPConfigTestCase(BasicAccountTestCase): |
1642 | |
1643 | @@ -77,6 +132,11 @@ |
1644 | |
1645 | self.assertTrue(self.rp_config.logo_url.endswith('test.png')) |
1646 | |
1647 | + def test_unicode(self): |
1648 | + self.rp_config.displayname = 'MyOpenIDRPConfig' |
1649 | + self.rp_config.save() |
1650 | + self.assertEqual(unicode(self.rp_config), u'MyOpenIDRPConfig') |
1651 | + |
1652 | |
1653 | class OpenIDAssociationTestCase(BasicAccountTestCase): |
1654 | fixtures = ['multiple_associations'] |
1655 | @@ -89,3 +149,130 @@ |
1656 | server_url="http://test.com", |
1657 | handle="handle", secret="secret".encode("base64"), |
1658 | issued=time(), lifetime=1000, assoc_type='HMAC-SHA1') |
1659 | + |
1660 | + |
1661 | +class OpenIDAuthorizationTestCase(BasicAccountTestCase): |
1662 | + def setUp(self): |
1663 | + self.account = Account.objects.get_by_email('test@canonical.com') |
1664 | + self.trust_root = 'http://openid.launchpad.dev' |
1665 | + self.expires = datetime.now() + timedelta(1) |
1666 | + |
1667 | + def create_auth(self): |
1668 | + return OpenIDAuthorization.objects.create( account=self.account, |
1669 | + client_id=None, date_expires=self.expires, |
1670 | + trust_root=self.trust_root) |
1671 | + |
1672 | + def test_authorize_when_readonly(self): |
1673 | + readonly_manager.set_readonly() |
1674 | + |
1675 | + try: |
1676 | + expires = datetime.now() |
1677 | + OpenIDAuthorization.objects.authorize(self.account, self.trust_root, |
1678 | + expires) |
1679 | + self.assertRaises(OpenIDAuthorization.DoesNotExist, |
1680 | + OpenIDAuthorization.objects.get, account=self.account, |
1681 | + client_id=None, trust_root=self.trust_root) |
1682 | + finally: |
1683 | + readonly_manager.clear_readonly() |
1684 | + |
1685 | + def test_authorize_expires(self): |
1686 | + OpenIDAuthorization.objects.authorize(self.account, self.trust_root, |
1687 | + None) |
1688 | + auth = OpenIDAuthorization.objects.get(account=self.account, |
1689 | + client_id=None, trust_root=self.trust_root) |
1690 | + self.assertEqual(auth.date_expires, |
1691 | + datetime.fromordinal(NEVER_EXPIRES.toordinal())) |
1692 | + |
1693 | + def test_authorize_existing(self): |
1694 | + auth1 = self.create_auth() |
1695 | + OpenIDAuthorization.objects.authorize(self.account, self.trust_root, |
1696 | + None) |
1697 | + auth2 = OpenIDAuthorization.objects.get(pk=auth1.id) |
1698 | + self.assertEqual(auth2.date_expires, |
1699 | + datetime.fromordinal(NEVER_EXPIRES.toordinal())) |
1700 | + |
1701 | + def test_authorize_not_existing(self): |
1702 | + OpenIDAuthorization.objects.authorize(self.account, self.trust_root, |
1703 | + None) |
1704 | + auth = OpenIDAuthorization.objects.get(account=self.account, |
1705 | + client_id=None, trust_root=self.trust_root) |
1706 | + self.assertEqual(auth.date_expires, |
1707 | + datetime.fromordinal(NEVER_EXPIRES.toordinal())) |
1708 | + |
1709 | + def test_is_authorized_generic(self): |
1710 | + self.create_auth() |
1711 | + self.assertTrue(OpenIDAuthorization.objects.is_authorized(self.account, |
1712 | + self.trust_root, 'client')) |
1713 | + |
1714 | + def test_is_authorized_client_id(self): |
1715 | + self.assertFalse(OpenIDAuthorization.objects.is_authorized(self.account, |
1716 | + self.trust_root, 'client')) |
1717 | + OpenIDAuthorization.objects.authorize(self.account, self.trust_root, |
1718 | + None, client_id='client') |
1719 | + self.assertTrue(OpenIDAuthorization.objects.is_authorized(self.account, |
1720 | + self.trust_root, 'client')) |
1721 | + |
1722 | + def test_is_not_authorized(self): |
1723 | + self.assertFalse(OpenIDAuthorization.objects.is_authorized(self.account, |
1724 | + self.trust_root, 'client')) |
1725 | + |
1726 | + |
1727 | +class OpenIDRPSummaryTestCase(BasicAccountTestCase): |
1728 | + def setUp(self): |
1729 | + self.account = Account.objects.get_by_email('test@canonical.com') |
1730 | + self.trust_root = 'http://openid.launchpad.dev' |
1731 | + |
1732 | + def create_summary(self): |
1733 | + return OpenIDRPSummary.objects.create(account=self.account, |
1734 | + trust_root=self.trust_root, openid_identifier='oid') |
1735 | + |
1736 | + def test_record_when_readonly(self): |
1737 | + readonly_manager.set_readonly() |
1738 | + |
1739 | + try: |
1740 | + summary = OpenIDRPSummary.objects.record(self.account, |
1741 | + self.trust_root) |
1742 | + self.assertEqual(summary, None) |
1743 | + self.assertRaises(OpenIDRPSummary.DoesNotExist, |
1744 | + OpenIDRPSummary.objects.get, account=self.account, |
1745 | + trust_root=self.trust_root, openid_identifier=None) |
1746 | + finally: |
1747 | + readonly_manager.clear_readonly() |
1748 | + |
1749 | + def test_record_with_existing_and_no_openid_identifier(self): |
1750 | + summary1 = self.create_summary() |
1751 | + summary2 = OpenIDRPSummary.objects.record(self.account, self.trust_root) |
1752 | + self.assertEqual(summary1.total_logins, 1) |
1753 | + self.assertEqual(summary2.total_logins, 1) |
1754 | + self.assertNotEqual(summary1, summary2) |
1755 | + |
1756 | + def test_record_existing_same_oid(self): |
1757 | + summary1 = self.create_summary() |
1758 | + summary2 = OpenIDRPSummary.objects.record(self.account, |
1759 | + self.trust_root, openid_identifier='oid') |
1760 | + self.assertEqual(summary2.total_logins, 2) |
1761 | + self.assertEqual(summary1, summary2) |
1762 | + |
1763 | + def test_record_existing_different_oid(self): |
1764 | + summary1 = self.create_summary() |
1765 | + summary2 = OpenIDRPSummary.objects.record(self.account, |
1766 | + self.trust_root, openid_identifier='oid1') |
1767 | + self.assertEqual(summary1.total_logins, 1) |
1768 | + self.assertEqual(summary2.total_logins, 1) |
1769 | + self.assertNotEqual(summary1, summary2) |
1770 | + |
1771 | + def test_record_not_existing(self): |
1772 | + summary1 = OpenIDRPSummary.objects.record(self.account, self.trust_root, |
1773 | + openid_identifier='oid') |
1774 | + self.assertEqual(summary1.total_logins, 1) |
1775 | + |
1776 | + summary2 = OpenIDRPSummary.objects.get(account=self.account, |
1777 | + trust_root=self.trust_root, openid_identifier='oid') |
1778 | + self.assertEqual(summary1, summary2) |
1779 | + |
1780 | + def test_increment(self): |
1781 | + summary = self.create_summary() |
1782 | + self.assertEqual(summary.total_logins, 1) |
1783 | + |
1784 | + summary.increment() |
1785 | + self.assertEqual(summary.total_logins, 2) |
1786 | |
1787 | === added file 'identityprovider/tests/test_models_person.py' |
1788 | --- identityprovider/tests/test_models_person.py 1970-01-01 00:00:00 +0000 |
1789 | +++ identityprovider/tests/test_models_person.py 2010-06-01 12:03:28 +0000 |
1790 | @@ -0,0 +1,57 @@ |
1791 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
1792 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1793 | + |
1794 | + |
1795 | +from identityprovider.models.account import Account, LPAccount |
1796 | +from identityprovider.models.const import (AccountCreationRationale, |
1797 | + AccountStatus) |
1798 | +from identityprovider.models.person import Person |
1799 | +from identityprovider.models.team import TeamParticipation |
1800 | +from identityprovider.tests.utils import SQLCachedTestCase |
1801 | + |
1802 | + |
1803 | +class PersonTestCase(SQLCachedTestCase): |
1804 | + pgsql_functions = ['generate_openid_identifier'] |
1805 | + |
1806 | + def setUp(self): |
1807 | + lp_account = LPAccount.objects.create(openid_identifier='oid') |
1808 | + self.person1 = Person.objects.create(displayname='Person', |
1809 | + name='person', lp_account=lp_account) |
1810 | + self.person2 = Person.objects.create(displayname='Other', name='other') |
1811 | + self.team1 = Person.objects.create(name='team', teamowner=self.person2) |
1812 | + self.team2 = Person.objects.create(name='other-team', |
1813 | + teamowner=self.person1) |
1814 | + |
1815 | + def test_unicode(self): |
1816 | + self.assertEqual(unicode(self.person1), u'Person') |
1817 | + |
1818 | + def test_in_team_no_team(self): |
1819 | + self.assertFalse(self.person1.in_team('no-team')) |
1820 | + |
1821 | + def test_in_team(self): |
1822 | + tp = TeamParticipation.objects.create(team=self.team1, |
1823 | + person=self.person1) |
1824 | + self.assertTrue(self.person1.in_team('team')) |
1825 | + |
1826 | + def test_in_team_object(self): |
1827 | + tp = TeamParticipation.objects.create(team=self.team1, |
1828 | + person=self.person1) |
1829 | + self.assertTrue(self.person1.in_team(self.team1)) |
1830 | + |
1831 | + def test_not_in_team(self): |
1832 | + self.assertFalse(self.person1.in_team('team')) |
1833 | + |
1834 | + def test_in_team_no_teamparticipation_same_owner(self): |
1835 | + team = Person.objects.create(name='otherteam', teamowner=self.person1) |
1836 | + self.assertTrue(self.person1.in_team('otherteam')) |
1837 | + |
1838 | + def test_account_when_no_account(self): |
1839 | + self.assertEqual(self.person1.account, None) |
1840 | + |
1841 | + def test_account_when_account(self): |
1842 | + account = Account.objects.create( |
1843 | + creation_rationale=AccountCreationRationale.USER_CREATED, |
1844 | + status=AccountStatus.ACTIVE, displayname='Person', |
1845 | + openid_identifier='oid') |
1846 | + self.assertEqual(self.person1.account, account) |
1847 | + |
1848 | |
1849 | === added file 'identityprovider/tests/test_models_team.py' |
1850 | --- identityprovider/tests/test_models_team.py 1970-01-01 00:00:00 +0000 |
1851 | +++ identityprovider/tests/test_models_team.py 2010-06-01 12:03:28 +0000 |
1852 | @@ -0,0 +1,14 @@ |
1853 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
1854 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1855 | + |
1856 | +from identityprovider.models.person import Person |
1857 | +from identityprovider.models.team import TeamParticipation |
1858 | +from identityprovider.tests.utils import SQLCachedTestCase |
1859 | + |
1860 | + |
1861 | +class TeamParticipationTestCase(SQLCachedTestCase): |
1862 | + def test_unicode(self): |
1863 | + team = Person.objects.create(displayname='Team', name='team') |
1864 | + person = Person.objects.create(displayname='Person', name='person') |
1865 | + tp = TeamParticipation.objects.create(team=team, person=person) |
1866 | + self.assertEqual(unicode(tp), u'Person in Team') |
1867 | |
1868 | === modified file 'identityprovider/tests/test_readonly.py' |
1869 | --- identityprovider/tests/test_readonly.py 2010-05-05 17:47:40 +0000 |
1870 | +++ identityprovider/tests/test_readonly.py 2010-06-01 12:03:28 +0000 |
1871 | @@ -7,29 +7,35 @@ |
1872 | import tempfile |
1873 | import socket |
1874 | import urllib2 |
1875 | +from datetime import datetime, timedelta |
1876 | from StringIO import StringIO |
1877 | |
1878 | from django.conf import settings |
1879 | +from django.contrib.auth.models import User |
1880 | +from django.contrib.sessions.models import Session |
1881 | from django.utils import simplejson |
1882 | |
1883 | from identityprovider.readonly import readonly_manager |
1884 | -from identityprovider.tests.utils import SQLCachedTestCase |
1885 | +from identityprovider.tests.utils import SQLCachedTestCase, BasicAccountTestCase |
1886 | from identityprovider import models |
1887 | from identityprovider.backend.base import DatabaseError |
1888 | -from identityprovider.views.readonly import _remote_req |
1889 | - |
1890 | - |
1891 | -class ReadOnlyTest(SQLCachedTestCase): |
1892 | +from identityprovider.views.readonly import (_remote_req, get_server_atts, |
1893 | + update_server) |
1894 | + |
1895 | + |
1896 | +class InReadOnlyTestCase(SQLCachedTestCase): |
1897 | + |
1898 | + def setUp(self): |
1899 | + readonly_manager.set_readonly() |
1900 | + |
1901 | + def tearDown(self): |
1902 | + readonly_manager.clear_readonly() |
1903 | + |
1904 | + |
1905 | +class ReadOnlyTest(InReadOnlyTestCase): |
1906 | fixtures = ["test"] |
1907 | pgsql_functions = ['generate_openid_identifier'] |
1908 | |
1909 | - def setUp(self): |
1910 | - self.readonly = getattr(settings, 'READ_ONLY_MODE', False) |
1911 | - settings.READ_ONLY_MODE = True |
1912 | - |
1913 | - def tearDown(self): |
1914 | - settings.READ_ONLY_MODE = self.readonly |
1915 | - |
1916 | def test_invalid_insert(self): |
1917 | tests = [(models.OpenIDRPConfig, {'trust_root': 'foo', |
1918 | 'displayname': 'foo', 'description': 'foo'}), |
1919 | @@ -47,6 +53,26 @@ |
1920 | p.displayname = 'Something Different' |
1921 | self.assertRaises(DatabaseError, p.save) |
1922 | |
1923 | + def test_current_dbid_with_settings(self): |
1924 | + old_DATABASE_ID = settings.DATABASE_ID |
1925 | + settings.DATABASE_ID = 'mydb' |
1926 | + |
1927 | + self.assertEqual(readonly_manager.current_dbid(), 'mydb') |
1928 | + |
1929 | + settings.DATABASE_ID = old_DATABASE_ID |
1930 | + |
1931 | + def test_current_dbid_without_settings(self): |
1932 | + old_DATABASE_ID = settings.DATABASE_ID |
1933 | + del settings._target.DATABASE_ID |
1934 | + |
1935 | + self.assertTrue(len(readonly_manager.connections) > 0) |
1936 | + |
1937 | + self.assertEqual(readonly_manager.current_dbid(), |
1938 | + readonly_manager.connections[0]['DATABASE_ID']) |
1939 | + |
1940 | + settings.DATABASE_ID = old_DATABASE_ID |
1941 | + |
1942 | + |
1943 | |
1944 | class RemoteRequestTest(SQLCachedTestCase): |
1945 | msg = 'hello' |
1946 | @@ -96,8 +122,20 @@ |
1947 | self.assertEquals(self.host, self.req.get_host()) |
1948 | self.restore_orig_urlopen() |
1949 | |
1950 | + def test_remote_req_urllib2_error(self): |
1951 | + def mock_urlopen(req, data=None): |
1952 | + raise urllib2.URLError((-1, 'error')) |
1953 | + |
1954 | + self.setup_mock_urlopen(mock_urlopen) |
1955 | + server = {'host': self.host} |
1956 | + self.assertEquals(None, _remote_req(**server)) |
1957 | + self.restore_orig_urlopen() |
1958 | + |
1959 | + |
1960 | class ReadOnlyBackUp(SQLCachedTestCase): |
1961 | """A base TestCase for backing up and restoring our readonly settings""" |
1962 | + pgsql_functions = ['generate_openid_identifier'] |
1963 | + |
1964 | def setUp(self): |
1965 | self.orig_readonly_secret = settings.READONLY_SECRET |
1966 | settings.READONLY_SECRET = 'test secret' |
1967 | @@ -123,6 +161,7 @@ |
1968 | readonly_manager.clear_failed('master') |
1969 | readonly_manager.set_db(settings.DB_CONNECTIONS[0]) |
1970 | |
1971 | + |
1972 | class ReadOnlyFlagFilesTest(ReadOnlyBackUp): |
1973 | def test_flag_files_in_right_directory(self): |
1974 | readonly_manager.set_readonly() |
1975 | @@ -142,7 +181,10 @@ |
1976 | self.assertTrue(mode & stat.S_IRGRP) |
1977 | self.assertTrue(mode & stat.S_IWGRP) |
1978 | |
1979 | + |
1980 | class ReadOnlyDataTest(ReadOnlyBackUp): |
1981 | + pgsql_functions = ['generate_openid_identifier'] |
1982 | + |
1983 | def test_readonly_returns_404_for_get(self): |
1984 | response = self.client.get('/readonlydata') |
1985 | self.assertEquals(404, response.status_code) |
1986 | @@ -199,3 +241,233 @@ |
1987 | data = simplejson.loads(response.content) |
1988 | self.assertFalse(data['connections'][0]['failed']) |
1989 | self.assertTrue(data['readonly']) |
1990 | + |
1991 | + |
1992 | +class ReadOnlyViewsTestCase(BasicAccountTestCase): |
1993 | + pgsql_functions = ['generate_openid_identifier'] |
1994 | + |
1995 | + def login_with_staff(self): |
1996 | + user = User.objects.create(username='admin') |
1997 | + user.is_staff = True |
1998 | + user.set_password('password') |
1999 | + user.save() |
2000 | + r = self.client.login(username='admin', password='password') |
2001 | + self.assertTrue(r) |
2002 | + |
2003 | + def test_readonly_admin(self): |
2004 | + self.login_with_staff() |
2005 | + |
2006 | + old_APP_SERVERS = settings.APP_SERVERS |
2007 | + settings.APP_SERVERS = [{'SERVER_ID': 'localhost', 'SCHEME': 'http', |
2008 | + 'HOST': 'localhost', 'VIRTUAL_HOST': '', |
2009 | + 'PORT': '8000'}] |
2010 | + expected = [{'appservers': [{'name': 'localhost', 'reachable': False}], |
2011 | + 'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX, |
2012 | + 'clear_all_readonly': False, |
2013 | + 'set_all_readonly': False}] |
2014 | + r = self.client.get('/readonly') |
2015 | + self.assertTemplateUsed(r, 'admin/readonly.html') |
2016 | + for item in r.context: |
2017 | + self.assertEqual(item.dicts, expected) |
2018 | + |
2019 | + settings.APP_SERVERS = old_APP_SERVERS |
2020 | + |
2021 | + def test_get_server_atts_server_unreachable(self): |
2022 | + servers = [{'SERVER_ID': 'localhost', 'SCHEME': 'http', |
2023 | + 'HOST': 'localhost', 'VIRTUAL_HOST': '', 'PORT': '8000'}] |
2024 | + expected = {'appservers': [{'name': 'localhost', 'reachable': False}], |
2025 | + 'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX, |
2026 | + 'clear_all_readonly': False, |
2027 | + 'set_all_readonly': False} |
2028 | + atts = get_server_atts(servers) |
2029 | + self.assertEqual(atts, expected) |
2030 | + |
2031 | + def test_get_server_atts_data_error(self): |
2032 | + def mock_loads(data): |
2033 | + raise ValueError() |
2034 | + old_loads = simplejson.loads |
2035 | + simplejson.loads = mock_loads |
2036 | + def mock_urlopen(req, data=None): |
2037 | + data = {} |
2038 | + return StringIO(simplejson.dumps(data)) |
2039 | + old_urlopen = urllib2.urlopen |
2040 | + urllib2.urlopen = mock_urlopen |
2041 | + |
2042 | + servers = [{'SERVER_ID': 'localhost', 'SCHEME': 'http', |
2043 | + 'HOST': 'localhost', 'VIRTUAL_HOST': '', 'PORT': '8000'}] |
2044 | + expected = {'appservers': [{'name': 'localhost', 'reachable': False}], |
2045 | + 'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX, |
2046 | + 'clear_all_readonly': False, |
2047 | + 'set_all_readonly': True} |
2048 | + atts = get_server_atts(servers) |
2049 | + self.assertEqual(atts, expected) |
2050 | + |
2051 | + simplejson.loads = old_loads |
2052 | + urllib2.urlopen = old_urlopen |
2053 | + |
2054 | + def test_get_server_atts_readonly(self): |
2055 | + def mock_urlopen(req, data=None): |
2056 | + data = {'readonly': True} |
2057 | + return StringIO(simplejson.dumps(data)) |
2058 | + old_urlopen = urllib2.urlopen |
2059 | + urllib2.urlopen = mock_urlopen |
2060 | + |
2061 | + servers = [{'SERVER_ID': 'localhost', 'SCHEME': 'http', |
2062 | + 'HOST': 'localhost', 'VIRTUAL_HOST': '', 'PORT': '8000'}] |
2063 | + expected = {'appservers': [{'name': 'localhost', 'reachable': True, |
2064 | + 'readonly': True}], |
2065 | + 'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX, |
2066 | + 'clear_all_readonly': True, |
2067 | + 'set_all_readonly': False} |
2068 | + atts = get_server_atts(servers) |
2069 | + self.assertEqual(atts, expected) |
2070 | + |
2071 | + urllib2.urlopen = old_urlopen |
2072 | + |
2073 | + def test_readonly_confirm_get(self): |
2074 | + self.login_with_staff() |
2075 | + |
2076 | + r = self.client.get('/readonly/localhost/set') |
2077 | + self.assertTemplateUsed(r, 'admin/readonly_confirm.html') |
2078 | + for item in r.context: |
2079 | + self.assertEqual(item.dicts, |
2080 | + [{'appserver': 'localhost', 'action': 'set', 'conn': None}]) |
2081 | + |
2082 | + def test_readonly_confirm_post(self): |
2083 | + def mock_urlopen(req, data=None): |
2084 | + self.reqs.append(req) |
2085 | + return StringIO(simplejson.dumps({})) |
2086 | + old_urlopen = urllib2.urlopen |
2087 | + urllib2.urlopen = mock_urlopen |
2088 | + |
2089 | + self.disable_csrf() |
2090 | + self.login_with_staff() |
2091 | + |
2092 | + self.reqs = [] |
2093 | + r = self.client.post('/readonly/localhost/set') |
2094 | + data = map(lambda x: x.data, self.reqs) |
2095 | + self.assertEqual(data, [ |
2096 | + "action=set&conn=None&secret=%s" % settings.READONLY_SECRET |
2097 | + ]) |
2098 | + |
2099 | + self.assertRedirects(r, '/readonly') |
2100 | + |
2101 | + self.reset_csrf() |
2102 | + |
2103 | + urllib2.urlopen = mock_urlopen |
2104 | + |
2105 | + def test_update_server_all_appservers(self): |
2106 | + def mock_urlopen(req, data=None): |
2107 | + self.reqs.append(req) |
2108 | + return StringIO(simplejson.dumps({})) |
2109 | + old_urlopen = urllib2.urlopen |
2110 | + urllib2.urlopen = mock_urlopen |
2111 | + |
2112 | + old_APP_SERVERS = settings.APP_SERVERS |
2113 | + settings.APP_SERVERS = servers = [ |
2114 | + {'SERVER_ID': 'localhost', 'SCHEME': 'http', |
2115 | + 'HOST': 'localhost', 'VIRTUAL_HOST': '', 'PORT': '8000'}, |
2116 | + {'SERVER_ID': 'otherhost', 'SCHEME': 'http', |
2117 | + 'HOST': 'otherhost', 'VIRTUAL_HOST': '', 'PORT': '8000'}, |
2118 | + ] |
2119 | + |
2120 | + self.reqs = [] |
2121 | + update_server('set') |
2122 | + data = map(lambda x: x.data, self.reqs) |
2123 | + self.assertEqual(data, [ |
2124 | + "action=set&conn=None&secret=%s" % settings.READONLY_SECRET, |
2125 | + "action=set&conn=None&secret=%s" % settings.READONLY_SECRET |
2126 | + ]) |
2127 | + |
2128 | + settings.APP_SERVERS = old_APP_SERVERS |
2129 | + urllib2.urlopen = mock_urlopen |
2130 | + |
2131 | + def test_update_server_one_appserver(self): |
2132 | + def mock_urlopen(req, data=None): |
2133 | + self.reqs.append(req) |
2134 | + return StringIO(simplejson.dumps({})) |
2135 | + old_urlopen = urllib2.urlopen |
2136 | + urllib2.urlopen = mock_urlopen |
2137 | + |
2138 | + old_APP_SERVERS = settings.APP_SERVERS |
2139 | + settings.APP_SERVERS = servers = [ |
2140 | + {'SERVER_ID': 'localhost', 'SCHEME': 'http', |
2141 | + 'HOST': 'localhost', 'VIRTUAL_HOST': '', 'PORT': '8000'}, |
2142 | + {'SERVER_ID': 'otherhost', 'SCHEME': 'http', |
2143 | + 'HOST': 'otherhost', 'VIRTUAL_HOST': '', 'PORT': '8000'}, |
2144 | + ] |
2145 | + |
2146 | + self.reqs = [] |
2147 | + update_server('set', 'localhost') |
2148 | + data = map(lambda x: x.data, self.reqs) |
2149 | + self.assertEqual(data, [ |
2150 | + "action=set&conn=None&secret=%s" % settings.READONLY_SECRET |
2151 | + ]) |
2152 | + |
2153 | + settings.APP_SERVERS = old_APP_SERVERS |
2154 | + urllib2.urlopen = mock_urlopen |
2155 | + |
2156 | + def test_update_server_one_connection(self): |
2157 | + def mock_urlopen(req, data=None): |
2158 | + self.reqs.append(req) |
2159 | + return StringIO(simplejson.dumps({})) |
2160 | + old_urlopen = urllib2.urlopen |
2161 | + urllib2.urlopen = mock_urlopen |
2162 | + |
2163 | + old_APP_SERVERS = settings.APP_SERVERS |
2164 | + settings.APP_SERVERS = servers = [ |
2165 | + {'SERVER_ID': 'localhost', 'SCHEME': 'http', |
2166 | + 'HOST': 'localhost', 'VIRTUAL_HOST': '', 'PORT': '8000'}, |
2167 | + ] |
2168 | + |
2169 | + self.reqs = [] |
2170 | + update_server('set', 'localhost', 'master') |
2171 | + data = map(lambda x: x.data, self.reqs) |
2172 | + self.assertEqual(data, [ |
2173 | + "action=set&conn=master&secret=%s" % settings.READONLY_SECRET, |
2174 | + ]) |
2175 | + |
2176 | + settings.APP_SERVERS = old_APP_SERVERS |
2177 | + urllib2.urlopen = mock_urlopen |
2178 | + |
2179 | + def test_update_server_clear_all(self): |
2180 | + def mock_urlopen(req, data=None): |
2181 | + self.reqs.append(req) |
2182 | + return StringIO(simplejson.dumps({})) |
2183 | + old_urlopen = urllib2.urlopen |
2184 | + urllib2.urlopen = mock_urlopen |
2185 | + |
2186 | + old_APP_SERVERS = settings.APP_SERVERS |
2187 | + settings.APP_SERVERS = servers = [ |
2188 | + {'SERVER_ID': 'localhost', 'SCHEME': 'http', |
2189 | + 'HOST': 'localhost', 'VIRTUAL_HOST': '', 'PORT': '8000'}, |
2190 | + ] |
2191 | + |
2192 | + # create a dummy session for testing purposes |
2193 | + Session.objects.create( |
2194 | + session_key='session-key', session_data='session-data', |
2195 | + expire_date=datetime.now() + timedelta(1)) |
2196 | + |
2197 | + |
2198 | + self.reqs = [] |
2199 | + update_server('clear') |
2200 | + data = map(lambda x: x.data, self.reqs) |
2201 | + self.assertEqual(data, [ |
2202 | + "action=clear&conn=None&secret=%s" % settings.READONLY_SECRET, |
2203 | + ]) |
2204 | + |
2205 | + # assert sessions were cleared |
2206 | + self.assertEqual(Session.objects.all().count(), 0) |
2207 | + |
2208 | + settings.APP_SERVERS = old_APP_SERVERS |
2209 | + urllib2.urlopen = mock_urlopen |
2210 | + |
2211 | + |
2212 | +class ReadOnlyViews(InReadOnlyTestCase): |
2213 | + |
2214 | + pgsql_functions = ['generate_openid_identifier'] |
2215 | + |
2216 | + def test_new_account_is_rendered_using_read_only_template(self): |
2217 | + settings.READ_ONLY_MODE = True |
2218 | + r = self.client.get('/+new_account') |
2219 | + self.assertTemplateUsed(r, 'readonly.html') |
2220 | |
2221 | === added file 'identityprovider/tests/test_teams.py' |
2222 | --- identityprovider/tests/test_teams.py 1970-01-01 00:00:00 +0000 |
2223 | +++ identityprovider/tests/test_teams.py 2010-06-01 12:03:28 +0000 |
2224 | @@ -0,0 +1,200 @@ |
2225 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
2226 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
2227 | + |
2228 | +from openid.consumer.consumer import SuccessResponse |
2229 | +from openid.consumer.discover import OpenIDServiceEndpoint |
2230 | +from openid.message import IDENTIFIER_SELECT |
2231 | + |
2232 | +from identityprovider.teams import (supportsTeams, getTeamsNS, ns_uri, |
2233 | + TeamsNamespaceError, TeamsRequest, TeamsResponse) |
2234 | +from identityprovider.tests.utils import SQLCachedTestCase |
2235 | +from identityprovider.views import server |
2236 | + |
2237 | + |
2238 | +class TeamsTestCase(SQLCachedTestCase): |
2239 | + def test_supportsTeams(self): |
2240 | + endpoint = OpenIDServiceEndpoint() |
2241 | + endpoint.type_uris.append(ns_uri) |
2242 | + r = supportsTeams(endpoint) |
2243 | + self.assertTrue(r) |
2244 | + |
2245 | + def test_not_supportsTeams(self): |
2246 | + endpoint = OpenIDServiceEndpoint() |
2247 | + r = supportsTeams(endpoint) |
2248 | + self.assertFalse(r) |
2249 | + |
2250 | + |
2251 | +class GetTeamsNSTestCase(SQLCachedTestCase): |
2252 | + def setUp(self): |
2253 | + super(GetTeamsNSTestCase, self).setUp() |
2254 | + |
2255 | + request = {'openid.mode': 'checkid_setup', |
2256 | + 'openid.trust_root': 'http://localhost/', |
2257 | + 'openid.return_to': 'http://localhost/', |
2258 | + 'openid.identity': IDENTIFIER_SELECT} |
2259 | + openid_server = server._get_openid_server() |
2260 | + self.orequest = openid_server.decodeRequest(request) |
2261 | + |
2262 | + def test_getTeamsNS(self): |
2263 | + message = self.orequest.message |
2264 | + self.assertEqual(message.namespaces.getAlias(ns_uri), None) |
2265 | + uri = getTeamsNS(message) |
2266 | + self.assertEqual(uri, ns_uri) |
2267 | + self.assertEqual(message.namespaces.getAlias(ns_uri), 'lp') |
2268 | + |
2269 | + def test_getTeamsNS_alias_already_exists(self): |
2270 | + message = self.orequest.message |
2271 | + message.namespaces.addAlias('http://localhost/', 'lp') |
2272 | + self.assertEqual(message.namespaces.getAlias(ns_uri), None) |
2273 | + self.assertRaises(TeamsNamespaceError, getTeamsNS, message) |
2274 | + |
2275 | + |
2276 | +class TeamsRequestTestCase(SQLCachedTestCase): |
2277 | + def setUp(self): |
2278 | + super(TeamsRequestTestCase, self).setUp() |
2279 | + |
2280 | + self.req = TeamsRequest( |
2281 | + query_membership=['canonical-identity-provider']) |
2282 | + |
2283 | + def test_init(self): |
2284 | + req = TeamsRequest() |
2285 | + self.assertEqual(req.query_membership, []) |
2286 | + self.assertEqual(req.ns_uri, ns_uri) |
2287 | + |
2288 | + self.assertEqual(self.req.query_membership, ['canonical-identity-provider']) |
2289 | + |
2290 | + self.assertRaises(TypeError, TeamsRequest, |
2291 | + query_membership='canonical-identity-provider') |
2292 | + |
2293 | + def test_fromOpenIDRequest(self): |
2294 | + request = {'openid.mode': 'checkid_setup', |
2295 | + 'openid.trust_root': 'http://localhost/', |
2296 | + 'openid.return_to': 'http://localhost/', |
2297 | + 'openid.identity': IDENTIFIER_SELECT} |
2298 | + openid_server = server._get_openid_server() |
2299 | + orequest = openid_server.decodeRequest(request) |
2300 | + req = TeamsRequest.fromOpenIDRequest(orequest) |
2301 | + self.assertEqual(req.query_membership, []) |
2302 | + self.assertEqual(req.ns_uri, ns_uri) |
2303 | + |
2304 | + def test_fromOpenIDRequest_with_query_membership(self): |
2305 | + request = {'openid.mode': 'checkid_setup', |
2306 | + 'openid.trust_root': 'http://localhost/', |
2307 | + 'openid.return_to': 'http://localhost/', |
2308 | + 'openid.identity': IDENTIFIER_SELECT, |
2309 | + 'openid.lp.query_membership': 'canonical-identity-provider'} |
2310 | + openid_server = server._get_openid_server() |
2311 | + orequest = openid_server.decodeRequest(request) |
2312 | + req = TeamsRequest.fromOpenIDRequest(orequest) |
2313 | + self.assertEqual(req.query_membership, ['canonical-identity-provider']) |
2314 | + self.assertEqual(req.ns_uri, ns_uri) |
2315 | + |
2316 | + def test_parseExtensionArgs_no_strict(self): |
2317 | + req = TeamsRequest() |
2318 | + req.parseExtensionArgs( |
2319 | + {'query_membership': |
2320 | + 'canonical-identity-provider,canonical-identity-provider'}, |
2321 | + strict=False) |
2322 | + self.assertEqual(req.query_membership, ['canonical-identity-provider']) |
2323 | + |
2324 | + def test_parseExtensionArgs_strict(self): |
2325 | + req = TeamsRequest() |
2326 | + self.assertRaises(ValueError, req.parseExtensionArgs, |
2327 | + {'query_membership': |
2328 | + 'canonical-identity-provider,canonical-identity-provider'}, |
2329 | + strict=True) |
2330 | + |
2331 | + def test_allRequestedTeams(self): |
2332 | + self.assertEqual(self.req.allRequestedTeams(), |
2333 | + ['canonical-identity-provider']) |
2334 | + |
2335 | + def test_not_wereTeamsRequested(self): |
2336 | + req = TeamsRequest() |
2337 | + self.assertFalse(req.wereTeamsRequested()) |
2338 | + |
2339 | + def test_wereTeamsRequested(self): |
2340 | + self.assertTrue(self.req.wereTeamsRequested()) |
2341 | + |
2342 | + def test_contains(self): |
2343 | + self.assertTrue('canonical-identity-provider' in self.req) |
2344 | + self.assertFalse('other-team' in self.req) |
2345 | + |
2346 | + def test_getExtensionArgs(self): |
2347 | + expected = {'query_membership': 'canonical-identity-provider'} |
2348 | + self.assertEqual(self.req.getExtensionArgs(), expected) |
2349 | + |
2350 | + teams = ['canonical-identity-provider', 'other-team'] |
2351 | + expected = {'query_membership': ','.join(teams)} |
2352 | + req = TeamsRequest(query_membership=teams) |
2353 | + self.assertEqual(req.getExtensionArgs(), expected) |
2354 | + |
2355 | + |
2356 | +class TeamsResponseTestCase(SQLCachedTestCase): |
2357 | + def test_init(self): |
2358 | + resp = TeamsResponse() |
2359 | + self.assertEqual(resp.is_member, []) |
2360 | + self.assertEqual(resp.ns_uri, ns_uri) |
2361 | + |
2362 | + resp = TeamsResponse(is_member=['canonical-identity-provider']) |
2363 | + self.assertEqual(resp.is_member, ['canonical-identity-provider']) |
2364 | + |
2365 | + def test_addTeam(self): |
2366 | + resp = TeamsResponse() |
2367 | + self.assertEqual(resp.is_member, []) |
2368 | + resp.addTeam('canonical-identity-provider') |
2369 | + self.assertEqual(resp.is_member, ['canonical-identity-provider']) |
2370 | + |
2371 | + def test_extractResponse(self): |
2372 | + req = TeamsRequest() |
2373 | + is_member_str = 'canonical-identity-provider' |
2374 | + resp = TeamsResponse.extractResponse(req, is_member_str) |
2375 | + self.assertEqual(resp.ns_uri, req.ns_uri) |
2376 | + self.assertEqual(resp.is_member, ['canonical-identity-provider']) |
2377 | + |
2378 | + def test_fromSuccessResponse_signed_present(self): |
2379 | + request = {'openid.mode': 'checkid_setup', |
2380 | + 'openid.trust_root': 'http://localhost/', |
2381 | + 'openid.return_to': 'http://localhost/', |
2382 | + 'openid.identity': IDENTIFIER_SELECT, |
2383 | + 'openid.lp.is_member': 'canonical-identity-provider'} |
2384 | + openid_server = server._get_openid_server() |
2385 | + orequest = openid_server.decodeRequest(request) |
2386 | + signed_fields = ['openid.lp.is_member'] |
2387 | + success_resp = SuccessResponse(orequest, orequest.message, |
2388 | + signed_fields=signed_fields) |
2389 | + resp = TeamsResponse.fromSuccessResponse(success_resp) |
2390 | + self.assertEqual(resp.is_member, ['canonical-identity-provider']) |
2391 | + |
2392 | + def test_fromSuccessResponse_no_signed(self): |
2393 | + request = {'openid.mode': 'checkid_setup', |
2394 | + 'openid.trust_root': 'http://localhost/', |
2395 | + 'openid.return_to': 'http://localhost/', |
2396 | + 'openid.identity': IDENTIFIER_SELECT} |
2397 | + openid_server = server._get_openid_server() |
2398 | + orequest = openid_server.decodeRequest(request) |
2399 | + success_resp = SuccessResponse(orequest, orequest.message) |
2400 | + resp = TeamsResponse.fromSuccessResponse(success_resp) |
2401 | + self.assertEqual(resp.is_member, []) |
2402 | + |
2403 | + def test_fromSuccessResponse_all(self): |
2404 | + request = {'openid.mode': 'checkid_setup', |
2405 | + 'openid.trust_root': 'http://localhost/', |
2406 | + 'openid.return_to': 'http://localhost/', |
2407 | + 'openid.identity': IDENTIFIER_SELECT, |
2408 | + 'openid.lp.is_member': 'canonical-identity-provider'} |
2409 | + openid_server = server._get_openid_server() |
2410 | + orequest = openid_server.decodeRequest(request) |
2411 | + success_resp = SuccessResponse(orequest, orequest.message) |
2412 | + resp = TeamsResponse.fromSuccessResponse(success_resp, False) |
2413 | + self.assertEqual(resp.is_member, ['canonical-identity-provider']) |
2414 | + |
2415 | + def test_getExtensionArgs(self): |
2416 | + expected = {'is_member': 'canonical-identity-provider'} |
2417 | + req = TeamsResponse(is_member=['canonical-identity-provider']) |
2418 | + self.assertEqual(req.getExtensionArgs(), expected) |
2419 | + |
2420 | + teams = ['canonical-identity-provider', 'other-team'] |
2421 | + expected = {'is_member': ','.join(teams)} |
2422 | + req = TeamsResponse(is_member=teams) |
2423 | + self.assertEqual(req.getExtensionArgs(), expected) |
2424 | + |
2425 | |
2426 | === modified file 'identityprovider/tests/test_views_account.py' |
2427 | --- identityprovider/tests/test_views_account.py 2010-04-21 15:29:24 +0000 |
2428 | +++ identityprovider/tests/test_views_account.py 2010-06-01 12:03:28 +0000 |
2429 | @@ -126,6 +126,10 @@ |
2430 | self.account = email.account |
2431 | |
2432 | def test_deactivate_account(self): |
2433 | + # make sure we have a preferredemail |
2434 | + email = self.account.emailaddress_set.all()[0] |
2435 | + self.account.preferredemail = email |
2436 | + |
2437 | # deactivate account |
2438 | r = self.client.post('/+deactivate') |
2439 | self.assertRedirects(r, '/+deactivated') |
2440 | @@ -138,3 +142,37 @@ |
2441 | self.assertEqual(None, self.account.preferredemail) |
2442 | for email in self.account.emailaddress_set.all(): |
2443 | self.assertEqual(EmailStatus.NEW, email.status) |
2444 | + |
2445 | + def test_deactivate_account_no_preferredemail(self): |
2446 | + # unset preferredemail |
2447 | + for email in self.account.emailaddress_set.all(): |
2448 | + email.status = EmailStatus.NEW |
2449 | + email.save() |
2450 | + self.assertEqual(self.account.preferredemail, None) |
2451 | + |
2452 | + # deactivate account |
2453 | + r = self.client.post('/+deactivate') |
2454 | + self.assertRedirects(r, '/+deactivated') |
2455 | + |
2456 | + # re-request account object from db |
2457 | + self._refresh_account() |
2458 | + |
2459 | + # test preferredemail status |
2460 | + self.assertEqual(self.account.preferredemail, None) |
2461 | + |
2462 | + def test_deactivate_with_token(self): |
2463 | + token = 'a' * 16 |
2464 | + # This strange dance with session is necessary to overcome way in which |
2465 | + # test.Client returns session (recreating it on every Client.session |
2466 | + # access) |
2467 | + session = self.client.session |
2468 | + session[token] = 'raw_orequest content' |
2469 | + session.save() |
2470 | + |
2471 | + # deactivate account |
2472 | + r = self.client.post('/%s/+deactivate' % token) |
2473 | + self.assertRedirects(r, '/+deactivated') |
2474 | + |
2475 | + # test token is preserved |
2476 | + self.assertEquals(self.client.session.get(token), |
2477 | + 'raw_orequest content') |
2478 | |
2479 | === modified file 'identityprovider/tests/test_views_consumer.py' |
2480 | --- identityprovider/tests/test_views_consumer.py 2010-05-10 18:26:52 +0000 |
2481 | +++ identityprovider/tests/test_views_consumer.py 2010-06-01 12:03:28 +0000 |
2482 | @@ -5,6 +5,12 @@ |
2483 | import unittest |
2484 | |
2485 | from openid import fetchers |
2486 | +from openid.consumer import consumer |
2487 | +from openid.consumer.discover import OpenIDServiceEndpoint, OPENID_2_0_TYPE |
2488 | +from openid.message import IDENTIFIER_SELECT |
2489 | +from openid.store.filestore import FileOpenIDStore |
2490 | +from openid.yadis.constants import YADIS_CONTENT_TYPE |
2491 | +from urllib import quote_plus |
2492 | |
2493 | # need to override fetcher before consumer module gets imported |
2494 | class MockFetcher(fetchers.Urllib2Fetcher): |
2495 | @@ -14,8 +20,12 @@ |
2496 | return super(MockFetcher, self).fetch(url, body, headers) |
2497 | |
2498 | fetchers.Urllib2Fetcher = MockFetcher |
2499 | -from identityprovider.views.consumer import (getBaseURL, startOpenID, |
2500 | - renderIndexPage) |
2501 | +from identityprovider.tests.utils import (SQLCachedTestCase, |
2502 | + OpenIDProviderTestCase) |
2503 | +from identityprovider.views import server |
2504 | +from identityprovider.views.consumer import (getBaseURL, normalDict, |
2505 | + getOpenIDStore, getConsumer, getViewURL, renderIndexPage, startOpenID, |
2506 | + finishOpenID, rpXRDS) |
2507 | |
2508 | |
2509 | class DummyRequest(object): |
2510 | @@ -26,7 +36,7 @@ |
2511 | class DummyDjangoRequest(object): |
2512 | META = { |
2513 | 'HTTP_HOST': "localhost", |
2514 | - 'SCRIPT_NAME': "http://localhost/consumer/", |
2515 | + 'SCRIPT_NAME': "http://localhost", |
2516 | 'SERVER_PROTOCOL': "http", |
2517 | } |
2518 | POST = { |
2519 | @@ -64,6 +74,14 @@ |
2520 | url = getBaseURL(self.https_req) |
2521 | self.assertEqual(url, self.https_consumer_url) |
2522 | |
2523 | + def test_consumer_on_port(self): |
2524 | + req = DummyRequest({'HTTP_HOST': 'localhost', |
2525 | + 'SERVER_PORT': '81', |
2526 | + 'SERVER_PROTOCOL': 'HTTP/1.1'}) |
2527 | + base_url = 'http://localhost:81/' |
2528 | + url = getBaseURL(req) |
2529 | + self.assertEqual(url, base_url) |
2530 | + |
2531 | |
2532 | class RenderIndexPageTestCase(unittest.TestCase): |
2533 | def test_renderIndexPage_utf8(self): |
2534 | @@ -78,3 +96,159 @@ |
2535 | headers = dict(response.items()) |
2536 | self.assertEqual(headers['Content-Type'], 'text/html; charset=utf-8') |
2537 | self.assertTrue(fullname in response.content) |
2538 | + |
2539 | + |
2540 | +class ConsumerTestCase(OpenIDProviderTestCase): |
2541 | + def setUp(self): |
2542 | + super(ConsumerTestCase, self).setUp() |
2543 | + |
2544 | + self.req = DummyDjangoRequest() |
2545 | + self.endpoint = OpenIDServiceEndpoint() |
2546 | + self.endpoint.claimed_id = 'oid' |
2547 | + self.endpoint.server_url = 'http://localhost/' |
2548 | + |
2549 | + class MockConsumer(consumer.Consumer): |
2550 | + def begin(this, url): |
2551 | + auth_request = consumer.AuthRequest(self.endpoint, None) |
2552 | + return auth_request |
2553 | + self.old_consumer = consumer.Consumer |
2554 | + consumer.Consumer = MockConsumer |
2555 | + |
2556 | + def tearDown(self): |
2557 | + consumer.Consumer = self.old_consumer |
2558 | + |
2559 | + super(ConsumerTestCase, self).tearDown() |
2560 | + |
2561 | + def test_normalDict(self): |
2562 | + req = DummyDjangoRequest() |
2563 | + self.assertEqual(normalDict(req.POST), |
2564 | + {'openid_identifier': "http://localhost/+id/abcd123"}) |
2565 | + |
2566 | + def test_startOpenID_get(self): |
2567 | + r = self.client.get('/consumer/', **self.req.META) |
2568 | + self.assertEqual(r.status_code, 200) |
2569 | + self.assertTemplateUsed(r, 'consumer/index.html') |
2570 | + |
2571 | + def test_startOpenID_discovery_error(self): |
2572 | + # we don't want the mock consumer for this test |
2573 | + consumer.Consumer = self.old_consumer |
2574 | + |
2575 | + r = self.client.post('/consumer/', self.req.POST, **self.req.META) |
2576 | + self.assertEqual(r.status_code, 200) |
2577 | + self.assertTemplateUsed(r, 'consumer/index.html') |
2578 | + self.assertTrue(r.context['error'].startswith( |
2579 | + 'OpenID discovery error')) |
2580 | + |
2581 | + def test_startOpenID_sreg(self): |
2582 | + self.req.POST.update({'sreg': 'yes', 'sreg_nickname': 'yes'}) |
2583 | + r = self.client.post('/consumer/', self.req.POST, **self.req.META) |
2584 | + query = self.get_query(r) |
2585 | + self.assertEqual(r.status_code, 302) |
2586 | + self.assertEqual(query['openid.sreg.required'], |
2587 | + quote_plus('fullname,email')) |
2588 | + self.assertEqual(query['openid.sreg.optional'], 'nickname') |
2589 | + |
2590 | + def test_startOpenID_teams(self): |
2591 | + self.req.POST.update({'teams': 'yes'}) |
2592 | + r = self.client.post('/consumer/', self.req.POST, **self.req.META) |
2593 | + query = self.get_query(r) |
2594 | + self.assertEqual(r.status_code, 302) |
2595 | + self.assertEqual(query['openid.lp.query_membership'], |
2596 | + quote_plus('myteam,hwdb-team,otherteam')) |
2597 | + |
2598 | + def test_startOpenID_should_redirect(self): |
2599 | + r = self.client.post('/consumer/', self.req.POST, **self.req.META) |
2600 | + query = self.get_query(r) |
2601 | + self.assertEqual(r.status_code, 302) |
2602 | + self.assertEqual(query['openid.trust_root'], |
2603 | + quote_plus('http://localhost/consumer/')) |
2604 | + self.assertEqual(query['openid.return_to'], |
2605 | + quote_plus('http://localhost/consumer/finish/')) |
2606 | + |
2607 | + def test_startOpenID_render_template(self): |
2608 | + # replace consumer with OpenID 2.0 compliant one |
2609 | + class MockConsumer(consumer.Consumer): |
2610 | + def begin(self, url): |
2611 | + endpoint = OpenIDServiceEndpoint() |
2612 | + endpoint.claimed_id = 'oid' |
2613 | + endpoint.server_url = 'http://localhost/' |
2614 | + endpoint.type_uris = [OPENID_2_0_TYPE] |
2615 | + auth_request = consumer.AuthRequest(endpoint, None) |
2616 | + return auth_request |
2617 | + consumer.Consumer = MockConsumer |
2618 | + |
2619 | + r = self.client.post('/consumer/', self.req.POST, **self.req.META) |
2620 | + self.assertEqual(r.status_code, 200) |
2621 | + self.assertTemplateUsed(r, 'consumer/request_form.html') |
2622 | + self.assertTrue('openid_message'in r.context['html']) |
2623 | + |
2624 | + def test_finishOpenID_get(self): |
2625 | + r = self.client.get('/consumer/finish/', **self.req.META) |
2626 | + self.assertEqual(r.status_code, 200) |
2627 | + self.assertTemplateUsed(r, 'consumer/index.html') |
2628 | + |
2629 | + def test_finishOpenID_post(self): |
2630 | + r = self.client.post('/consumer/finish/', **self.req.META) |
2631 | + self.assertEqual(r.status_code, 200) |
2632 | + self.assertTemplateUsed(r, 'consumer/index.html') |
2633 | + |
2634 | + def test_finishOpenID_consumer_success(self): |
2635 | + def mock_complete(this, request_args, return_to): |
2636 | + request = {'openid.mode': 'checkid_setup', |
2637 | + 'openid.trust_root': 'http://localhost/', |
2638 | + 'openid.return_to': 'http://localhost/', |
2639 | + 'openid.identity': IDENTIFIER_SELECT, |
2640 | + 'openid.sreg.nickname': 'mynickname', |
2641 | + 'openid.lp.is_member': 'myteam'} |
2642 | + openid_server = server._get_openid_server() |
2643 | + orequest = openid_server.decodeRequest(request) |
2644 | + response = consumer.SuccessResponse( |
2645 | + self.endpoint, orequest.message, |
2646 | + signed_fields=['openid.sreg.nickname', |
2647 | + 'openid.lp.is_member']) |
2648 | + return response |
2649 | + old_complete = consumer.Consumer.complete |
2650 | + consumer.Consumer.complete = mock_complete |
2651 | + |
2652 | + r = self.client.post('/consumer/finish/', self.req.POST, |
2653 | + **self.req.META) |
2654 | + self.assertEqual(r.context['url'], 'oid') |
2655 | + self.assertEqual(r.context['sreg'], [('nickname', 'mynickname')]) |
2656 | + self.assertEqual(r.context['teams'], ['myteam']) |
2657 | + |
2658 | + consumer.Consumer.complete = old_complete |
2659 | + |
2660 | + def test_finishOpenID_consumer_cancel(self): |
2661 | + def mock_complete(this, request_args, return_to): |
2662 | + response = consumer.CancelResponse(self.endpoint) |
2663 | + return response |
2664 | + old_complete = consumer.Consumer.complete |
2665 | + consumer.Consumer.complete = mock_complete |
2666 | + |
2667 | + r = self.client.post('/consumer/finish/', self.req.POST, |
2668 | + **self.req.META) |
2669 | + self.assertEqual(r.context['message'], |
2670 | + 'OpenID authentication cancelled.') |
2671 | + |
2672 | + consumer.Consumer.complete = old_complete |
2673 | + |
2674 | + def test_finishOpenID_consumer_failure(self): |
2675 | + def mock_complete(this, request_args, return_to): |
2676 | + response = consumer.FailureResponse(self.endpoint, |
2677 | + message='some error') |
2678 | + return response |
2679 | + old_complete = consumer.Consumer.complete |
2680 | + consumer.Consumer.complete = mock_complete |
2681 | + |
2682 | + r = self.client.post('/consumer/finish/', self.req.POST, |
2683 | + **self.req.META) |
2684 | + self.assertEqual(r.context['error'], |
2685 | + 'OpenID authentication failed.') |
2686 | + self.assertEqual(r.context['failure_reason'], 'some error') |
2687 | + |
2688 | + consumer.Consumer.complete = old_complete |
2689 | + |
2690 | + def test_rpXRDS(self): |
2691 | + r = self.client.get('/consumer/xrds/', **self.req.META) |
2692 | + self.assertTemplateUsed(r, 'openidapplication-xrds.xml') |
2693 | + self.assertEqual(r['Content-Type'], YADIS_CONTENT_TYPE) |
2694 | |
2695 | === modified file 'identityprovider/tests/test_views_i18n.py' |
2696 | --- identityprovider/tests/test_views_i18n.py 2010-05-13 15:01:03 +0000 |
2697 | +++ identityprovider/tests/test_views_i18n.py 2010-06-01 12:03:28 +0000 |
2698 | @@ -1,4 +1,5 @@ |
2699 | from identityprovider.tests.utils import BasicAccountTestCase |
2700 | +from identityprovider.models.account import Account |
2701 | |
2702 | |
2703 | class SetLanguageTestCase(BasicAccountTestCase): |
2704 | @@ -23,3 +24,24 @@ |
2705 | r = self.client.post('/set_language', |
2706 | {'language': 'es', 'next': '/+login'}) |
2707 | self.assertRedirects(r, '/+login') |
2708 | + |
2709 | + def test_not_known_http_method_cases_404(self): |
2710 | + # Faking client.put method |
2711 | + r = self.client.get('/set_language', REQUEST_METHOD='PUT') |
2712 | + self.assertEquals(r.status_code, 404) |
2713 | + |
2714 | + def test_setting_unsupported_language_raises_404(self): |
2715 | + r = self.client.post('/set_language', {'language': 'xx'}) |
2716 | + self.assertEquals(r.status_code, 404) |
2717 | + |
2718 | + def test_getting_renders_choose_page(self): |
2719 | + r = self.client.get('/set_language') |
2720 | + self.assertTemplateUsed(r, 'select_language.html') |
2721 | + |
2722 | + def test_setting_language_for_authenticated_users_updates_db(self): |
2723 | + self.client.login(username='mark@example.com', password='test') |
2724 | + self.client.post('/set_language', {'language': 'es', 'next': '/'}) |
2725 | + |
2726 | + account = Account.objects.get_by_email('mark@example.com') |
2727 | + |
2728 | + self.assertEquals(account.preferredlanguage, 'es') |
2729 | |
2730 | === modified file 'identityprovider/tests/test_views_server.py' |
2731 | --- identityprovider/tests/test_views_server.py 2010-04-21 15:29:24 +0000 |
2732 | +++ identityprovider/tests/test_views_server.py 2010-06-01 12:03:28 +0000 |
2733 | @@ -1,13 +1,28 @@ |
2734 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
2735 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2736 | |
2737 | +import datetime |
2738 | +import urlparse |
2739 | +from copy import deepcopy |
2740 | from django.conf import settings |
2741 | +from django.contrib.auth.models import AnonymousUser |
2742 | +from openid.extensions import pape |
2743 | from openid.extensions.sreg import SRegRequest |
2744 | +from openid.message import Message, OPENID2_NS, IDENTIFIER_SELECT |
2745 | +from openid.server.server import Server, ProtocolError |
2746 | +from openid.yadis.constants import YADIS_HEADER_NAME |
2747 | +from urllib import quote, quote_plus |
2748 | |
2749 | -from identityprovider.models import Account, OpenIDRPConfig, Person |
2750 | +import identityprovider.signed as signed |
2751 | +from identityprovider.models import (Account, LPAccount, OpenIDAuthorization, |
2752 | + OpenIDRPConfig, Person) |
2753 | +from identityprovider.models.const import (AccountStatus, EmailStatus, |
2754 | + AccountCreationRationale) |
2755 | +from identityprovider.models.person import PersonLocation |
2756 | +from identityprovider.models.authtoken import create_token |
2757 | from identityprovider.views import server |
2758 | from identityprovider.tests.utils import (SQLCachedTestCase, |
2759 | - BasicAccountTestCase) |
2760 | + BasicAccountTestCase, AuthenticatedTestCase, OpenIDProviderTestCase) |
2761 | from identityprovider.const import PERSON_VISIBILITY_PRIVATE_MEMBERSHIP |
2762 | |
2763 | __all__ = ['UntrustedRPTest', ] |
2764 | @@ -16,14 +31,649 @@ |
2765 | class DummyORequest(object): |
2766 | mode = 'checkid_setup' |
2767 | trust_root = 'http://localhost/' |
2768 | + identity = 'http://localhost/+id/mark_oid' |
2769 | + |
2770 | + def idSelect(self): |
2771 | + return False |
2772 | |
2773 | |
2774 | class DummyRequest(object): |
2775 | - def __init__(self): |
2776 | - self.session = {} |
2777 | - |
2778 | - |
2779 | -class UntrustedRPTest(SQLCachedTestCase): |
2780 | + def __init__(self, META=None, REQUEST=None): |
2781 | + class MockSession(dict): |
2782 | + def __init__(self): |
2783 | + self.session_key = 'session-key' |
2784 | + def flush(self): |
2785 | + pass |
2786 | + |
2787 | + self.META = META if META is not None else {} |
2788 | + self.REQUEST = REQUEST if REQUEST is not None else {} |
2789 | + self.session = MockSession() |
2790 | + |
2791 | + |
2792 | +class HandleOpenIDErrorTestCase(OpenIDProviderTestCase): |
2793 | + |
2794 | + # tests for the _handle_openid_error method |
2795 | + |
2796 | + def test_handle_openid_error_with_encode_url(self): |
2797 | + params = {'openid.return_to': 'http://localhost/'} |
2798 | + r = self.client.get('/+openid', params) |
2799 | + query = self.get_query(r) |
2800 | + error_msg = 'No+mode+value+in+message+' |
2801 | + self.assertEqual(r.status_code, 302) |
2802 | + self.assertEqual(query['openid.mode'], 'error') |
2803 | + self.assertTrue(query['openid.error'].startswith(error_msg)) |
2804 | + |
2805 | + def test_handle_openid_error_other(self): |
2806 | + params = {'openid.mode': 'checkid_setup'} |
2807 | + r = self.client.get('/+openid', params) |
2808 | + error_mode = "mode:error" |
2809 | + error_msg = "error:Missing required field 'return_to'" |
2810 | + self.assertEqual(r.status_code, 200) |
2811 | + self.assertTrue(error_mode in r.content) |
2812 | + self.assertTrue(error_msg in r.content) |
2813 | + |
2814 | + |
2815 | +class ProcessOpenIDRequestTestCase(OpenIDProviderTestCase): |
2816 | + |
2817 | + # tests for the _process_openid_request method |
2818 | + |
2819 | + def test_process_openid_request_no_orequest(self): |
2820 | + r = self.client.get('/+openid') |
2821 | + self.assertEqual(r.status_code, 200) |
2822 | + self.assertTemplateUsed(r, 'server_info.html') |
2823 | + |
2824 | + |
2825 | +class HandleUserResponseTestCase(OpenIDProviderTestCase): |
2826 | + fixtures = ['test'] |
2827 | + |
2828 | + def setUp(self): |
2829 | + super(HandleUserResponseTestCase, self).setUp() |
2830 | + |
2831 | + # create a trusted rpconfig |
2832 | + self.rpconfig = OpenIDRPConfig(trust_root='http://localhost/') |
2833 | + self.rpconfig.save() |
2834 | + |
2835 | + self.params = {'openid.trust_root': 'http://localhost/', |
2836 | + 'openid.return_to': 'http://localhost/', |
2837 | + 'openid.identity': IDENTIFIER_SELECT, |
2838 | + 'openid.claimed_id': 'http://localhost/~userid', |
2839 | + 'openid.ns': OPENID2_NS, |
2840 | + 'openid.mode': 'checkid_setup'} |
2841 | + |
2842 | + # tests for the _handle_user_response method |
2843 | + |
2844 | + def test_handle_user_response_checkid_immediate(self): |
2845 | + self.params['openid.mode'] = 'checkid_immediate' |
2846 | + |
2847 | + r = self.client.get('/+openid', self.params) |
2848 | + query = self.get_query(r) |
2849 | + self.assertEqual(r.status_code, 302) |
2850 | + self.assertEqual(query['openid.mode'], 'setup_needed') |
2851 | + |
2852 | + def test_handle_user_response_no_valid_openid(self): |
2853 | + self.params.update({'openid.identity': 'bogus', |
2854 | + 'openid.claimed_id': 'bogus'}) |
2855 | + r = self.client.get('/+openid', self.params) |
2856 | + self.assertEqual(r.status_code, 200) |
2857 | + self.assertTemplateUsed(r, 'invalid_identifier.html') |
2858 | + |
2859 | + def test_handle_user_response_openid_is_authorized_idselect(self): |
2860 | + # update rp to auto authorize |
2861 | + self.rpconfig.auto_authorize = True |
2862 | + self.rpconfig.save() |
2863 | + |
2864 | + account = Account.objects.get_by_email('mark@example.com') |
2865 | + self.client.login(username='mark@example.com', password='test') |
2866 | + r = self.client.get('/+openid', self.params) |
2867 | + query = self.get_query(r) |
2868 | + self.assertEqual(r.status_code, 302) |
2869 | + self.assertEqual(query['openid.mode'], 'id_res') |
2870 | + self.assertEqual(query['openid.identity'], |
2871 | + quote_plus(account.openid_identity_url)) |
2872 | + |
2873 | + def test_handle_user_response_openid_is_authorized_other_id(self): |
2874 | + self.rpconfig.auto_authorize = True |
2875 | + self.rpconfig.save() |
2876 | + |
2877 | + self.params['openid.identity'] = 'http://openid.launchpad.dev/+id/mark_oid' |
2878 | + self.client.login(username='mark@example.com', password='test') |
2879 | + r = self.client.get('/+openid', self.params) |
2880 | + query = self.get_query(r) |
2881 | + self.assertEqual(r.status_code, 302) |
2882 | + self.assertEqual(query['openid.mode'], 'id_res') |
2883 | + self.assertEqual(query['openid.identity'], |
2884 | + quote_plus(self.params['openid.identity'])) |
2885 | + |
2886 | + def test_handle_user_response_user_is_authenticated(self): |
2887 | + self.params['openid.identity'] = 'http://openid.launchpad.dev/+id/other_oid' |
2888 | + self.client.login(username='mark@example.com', password='test') |
2889 | + r = self.client.get('/+openid', self.params) |
2890 | + query = self.get_query(r) |
2891 | + self.assertEqual(r.status_code, 302) |
2892 | + self.assertEqual(query['openid.mode'], 'cancel') |
2893 | + |
2894 | + def test_handle_user_response_decide(self): |
2895 | + r = self.client.get('/+openid', self.params) |
2896 | + self.assertEqual(r.status_code, 302) |
2897 | + self.assertTrue(r['Location'].endswith('+decide')) |
2898 | + |
2899 | + def test_handle_user_response_with_referer(self): |
2900 | + META = {'HTTP_REFERER': 'http://localhost/'} |
2901 | + r = self.client.get('/+openid', self.params, **META) |
2902 | + openid_referer = self.client.cookies.get('openid_referer') |
2903 | + self.assertEqual(r.status_code, 302) |
2904 | + self.assertTrue(r['Location'].endswith('+decide')) |
2905 | + self.assertEqual(openid_referer.value, META['HTTP_REFERER']) |
2906 | + |
2907 | + |
2908 | +class ValidOpenIDTestCase(OpenIDProviderTestCase): |
2909 | + |
2910 | + def test_is_valid_openid_idselect(self): |
2911 | + valid = server._is_valid_openid_for_this_site(IDENTIFIER_SELECT) |
2912 | + self.assertTrue(valid) |
2913 | + |
2914 | + def test_is_valid_openid_different_scheme(self): |
2915 | + srvparts = urlparse.urlparse(settings.SSO_ROOT_URL) |
2916 | + if srvparts.scheme == 'http': |
2917 | + scheme = 'https' |
2918 | + else: |
2919 | + scheme = 'http' |
2920 | + identity = settings.SSO_ROOT_URL.replace(srvparts.scheme, scheme) |
2921 | + valid = server._is_valid_openid_for_this_site(identity) |
2922 | + self.assertFalse(valid) |
2923 | + |
2924 | + def test_is_valid_openid_different_port(self): |
2925 | + srvparts = urlparse.urlparse(settings.SSO_ROOT_URL) |
2926 | + if srvparts.port is not None: |
2927 | + port = int(srvparts.port) + 1 |
2928 | + else: |
2929 | + port = 81 |
2930 | + identity = "%s:%s//%s%s" % (srvparts.scheme, port, |
2931 | + srvparts.hostname, srvparts.path) |
2932 | + valid = server._is_valid_openid_for_this_site(identity) |
2933 | + self.assertFalse(valid) |
2934 | + |
2935 | + def test_is_valid_openid_different_hostname(self): |
2936 | + identity = 'http://testserver/' |
2937 | + valid = server._is_valid_openid_for_this_site(identity) |
2938 | + self.assertFalse(valid) |
2939 | + |
2940 | + def test_is_valid_openid_path_patterns(self): |
2941 | + identity = settings.SSO_ROOT_URL |
2942 | + valid = server._is_valid_openid_for_this_site(identity) |
2943 | + self.assertTrue(valid) |
2944 | + |
2945 | + identity += '+id/foo' |
2946 | + valid = server._is_valid_openid_for_this_site(identity) |
2947 | + self.assertTrue(valid) |
2948 | + |
2949 | + identity = settings.SSO_ROOT_URL + '~foo' |
2950 | + valid = server._is_valid_openid_for_this_site(identity) |
2951 | + self.assertTrue(valid) |
2952 | + |
2953 | + # try a non-normalized uri |
2954 | + identity = settings.SSO_ROOT_URL.strip('/') |
2955 | + valid = server._is_valid_openid_for_this_site(identity) |
2956 | + self.assertTrue(valid) |
2957 | + |
2958 | + def test_is_valid_openid_other(self): |
2959 | + identity = settings.SSO_ROOT_URL + '+foo' |
2960 | + valid = server._is_valid_openid_for_this_site(identity) |
2961 | + self.assertFalse(valid) |
2962 | + |
2963 | + def test_is_valid_openid_error(self): |
2964 | + valid = server._is_valid_openid_for_this_site(None) |
2965 | + self.assertFalse(valid) |
2966 | + |
2967 | + |
2968 | +class DecideTestCase(AuthenticatedTestCase, OpenIDProviderTestCase): |
2969 | + def setUp(self): |
2970 | + super(DecideTestCase, self).setUp(disableCSRF=True) |
2971 | + |
2972 | + request = {'openid.mode': 'checkid_setup', |
2973 | + 'openid.trust_root': 'http://localhost/', |
2974 | + 'openid.return_to': 'http://localhost/', |
2975 | + 'openid.identity': IDENTIFIER_SELECT} |
2976 | + openid_server = server._get_openid_server() |
2977 | + self.orequest = openid_server.decodeRequest(request) |
2978 | + self.token = create_token(16) |
2979 | + session = self.client.session |
2980 | + session[self.token] = signed.dumps(self.orequest, |
2981 | + settings.SECRET_KEY) |
2982 | + session.save() |
2983 | + |
2984 | + def test_decide_invalid(self): |
2985 | + token = 'a' * 16 |
2986 | + r = self.client.get("/%s/+decide" % token) |
2987 | + self.assertEqual(r.status_code, 200) |
2988 | + self.assertEqual(r.content, 'Invalid OpenID transaction') |
2989 | + |
2990 | + def test_decide_authenticated(self): |
2991 | + r = self.client.post("/%s/+decide" % self.token, {'yes': 'yes'}) |
2992 | + query = self.get_query(r) |
2993 | + self.assertEqual(r.status_code, 302) |
2994 | + self.assertEqual(query['openid.mode'], 'id_res') |
2995 | + |
2996 | + def test_decide_auto_authorize(self): |
2997 | + # make sure rpconfig is set to auto authorize |
2998 | + rpconfig = OpenIDRPConfig(trust_root='http://localhost/', |
2999 | + auto_authorize=True) |
3000 | + rpconfig.save() |
3001 | + |
3002 | + r = self.client.post("/%s/+decide" % self.token) |
3003 | + query = self.get_query(r) |
3004 | + self.assertEqual(r.status_code, 302) |
3005 | + self.assertEqual(query['openid.mode'], 'id_res') |
3006 | + |
3007 | + def test_decide_process(self): |
3008 | + r = self.client.post("/%s/+decide" % self.token) |
3009 | + self.assertEqual(r.status_code, 200) |
3010 | + self.assertTemplateUsed(r, 'decide.html') |
3011 | + |
3012 | + def test_decide_not_authenticated(self): |
3013 | + self.client.logout() |
3014 | + |
3015 | + # create a trusted rpconfig |
3016 | + rpconfig = OpenIDRPConfig(trust_root='http://localhost/') |
3017 | + rpconfig.save() |
3018 | + |
3019 | + # start openid request |
3020 | + params = {'openid.trust_root': 'http://localhost/', |
3021 | + 'openid.return_to': 'http://localhost/', |
3022 | + 'openid.identity': IDENTIFIER_SELECT, |
3023 | + 'openid.claimed_id': 'http://localhost/~userid', |
3024 | + 'openid.ns': OPENID2_NS, |
3025 | + 'openid.mode': 'checkid_setup'} |
3026 | + r = self.client.get('/+openid', params) |
3027 | + # follow redirect |
3028 | + path = r['Location'].split('http://testserver')[1] |
3029 | + self.assertTrue(path.endswith('+decide')) |
3030 | + r = self.client.get(path) |
3031 | + self.assertEqual(r.status_code, 200) |
3032 | + self.assertTemplateUsed(r, 'registration/login.html') |
3033 | + |
3034 | + |
3035 | +class PreAuthorizeTestCase(AuthenticatedTestCase): |
3036 | + def setUp(self): |
3037 | + super(PreAuthorizeTestCase, self).setUp(disableCSRF=True) |
3038 | + |
3039 | + def test_pre_authorize_get(self): |
3040 | + r = self.client.get('/+pre-authorize-rp') |
3041 | + self.assertEqual(r.status_code, 400) |
3042 | + |
3043 | + def test_pre_authorize_unauthorized(self): |
3044 | + r = self.client.post('/+pre-authorize-rp', |
3045 | + {'trust_root': 'http://localhost/', |
3046 | + 'callback': 'http://localhost/'}) |
3047 | + self.assertEqual(r.status_code, 400) |
3048 | + |
3049 | + def test_pre_authorize_no_pre_authorized(self): |
3050 | + old_OPENID_PREAUTHORIZATION_ACL = settings.OPENID_PREAUTHORIZATION_ACL |
3051 | + settings.OPENID_PREAUTHORIZATION_ACL = [] |
3052 | + |
3053 | + rpconfig = OpenIDRPConfig(trust_root='http://localhost/') |
3054 | + rpconfig.save() |
3055 | + |
3056 | + extra = {'HTTP_REFERER': 'http://localhost/'} |
3057 | + r = self.client.post('/+pre-authorize-rp', |
3058 | + {'trust_root': 'http://localhost/', |
3059 | + 'callback': 'http://localhost/'}, |
3060 | + **extra) |
3061 | + self.assertEqual(r.status_code, 400) |
3062 | + |
3063 | + settings.OPENID_PREAUTHORIZATION_ACL = old_OPENID_PREAUTHORIZATION_ACL |
3064 | + |
3065 | + def test_pre_authorize_authenticated(self): |
3066 | + old_OPENID_PREAUTHORIZATION_ACL = settings.OPENID_PREAUTHORIZATION_ACL |
3067 | + settings.OPENID_PREAUTHORIZATION_ACL = [ |
3068 | + ('http://localhost/', 'http://localhost/') |
3069 | + ] |
3070 | + rpconfig = OpenIDRPConfig(trust_root='http://localhost/') |
3071 | + rpconfig.save() |
3072 | + |
3073 | + extra = {'HTTP_REFERER': 'http://localhost/'} |
3074 | + r = self.client.post('/+pre-authorize-rp', |
3075 | + {'trust_root': 'http://localhost/', |
3076 | + 'callback': 'http://localhost/'}, |
3077 | + **extra) |
3078 | + self.assertRedirects(r, 'http://localhost/') |
3079 | + |
3080 | + settings.OPENID_PREAUTHORIZATION_ACL = old_OPENID_PREAUTHORIZATION_ACL |
3081 | + |
3082 | + def test_pre_authorize_not_authenticated(self): |
3083 | + old_OPENID_PREAUTHORIZATION_ACL = settings.OPENID_PREAUTHORIZATION_ACL |
3084 | + settings.OPENID_PREAUTHORIZATION_ACL = [ |
3085 | + ('http://localhost/', 'http://localhost/') |
3086 | + ] |
3087 | + rpconfig = OpenIDRPConfig(trust_root='http://localhost/') |
3088 | + rpconfig.save() |
3089 | + |
3090 | + self.client.logout() |
3091 | + extra = {'HTTP_REFERER': 'http://localhost/'} |
3092 | + r = self.client.post('/+pre-authorize-rp', |
3093 | + {'trust_root': 'http://localhost/', |
3094 | + 'callback': 'http://localhost/'}, |
3095 | + **extra) |
3096 | + next_url = '/+login?next=' + quote('/+pre-authorize-rp?') |
3097 | + self.assertRedirects(r, next_url) |
3098 | + |
3099 | + settings.OPENID_PREAUTHORIZATION_ACL = old_OPENID_PREAUTHORIZATION_ACL |
3100 | + |
3101 | + def test_pre_authorize_after_login(self): |
3102 | + old_OPENID_PREAUTHORIZATION_ACL = settings.OPENID_PREAUTHORIZATION_ACL |
3103 | + settings.OPENID_PREAUTHORIZATION_ACL = [ |
3104 | + ('http://localhost/', 'http://localhost/') |
3105 | + ] |
3106 | + rpconfig = OpenIDRPConfig(trust_root='http://localhost/') |
3107 | + rpconfig.save() |
3108 | + |
3109 | + # make sure we are logged out |
3110 | + self.client.logout() |
3111 | + |
3112 | + # attempt to pre-authorize |
3113 | + extra = {'HTTP_REFERER': 'http://localhost/'} |
3114 | + data = {'trust_root': 'http://localhost/', |
3115 | + 'callback': 'http://localhost/'} |
3116 | + r = self.client.post('/+pre-authorize-rp', data, **extra) |
3117 | + # we get redirected to login |
3118 | + next_url = '/+login?next=' + quote('/+pre-authorize-rp?') |
3119 | + self.assertRedirects(r, next_url) |
3120 | + # and the referer info is stored in the session |
3121 | + self.assertEqual(self.client.session['pre_auth_referer'], |
3122 | + 'http://localhost/') |
3123 | + self.assertTrue(self.client.session['pre_auth_referer_for'], |
3124 | + 'http://localhost/') |
3125 | + |
3126 | + # login and redirect to pre-authorize again |
3127 | + data.update({'email': 'mark@example.com', 'password': 'test', |
3128 | + 'next': '/+pre-authorize-rp'}) |
3129 | + r = self.client.post('/+login', data, **extra) |
3130 | + r = self.client.post('/+pre-authorize-rp', data, **extra) |
3131 | + # we get effectively pre-authorized |
3132 | + self.assertRedirects(r, 'http://localhost/') |
3133 | + # and the pre-auth session data gets removed |
3134 | + self.assertFalse('pre_auth_referer' in self.client.session) |
3135 | + self.assertFalse('pre_auth_referer_for' in self.client.session) |
3136 | + |
3137 | + settings.OPENID_PREAUTHORIZATION_ACL = old_OPENID_PREAUTHORIZATION_ACL |
3138 | + |
3139 | + def test_pre_authorize_sess_referer_not_trust_root(self): |
3140 | + old_OPENID_PREAUTHORIZATION_ACL = settings.OPENID_PREAUTHORIZATION_ACL |
3141 | + settings.OPENID_PREAUTHORIZATION_ACL = [ |
3142 | + ('http://otherhost/', 'http://localhost/') |
3143 | + ] |
3144 | + rpconfig = OpenIDRPConfig(trust_root='http://localhost/') |
3145 | + rpconfig.save() |
3146 | + |
3147 | + # make sure we are logged out |
3148 | + self.client.logout() |
3149 | + |
3150 | + # attempt to pre-authorize |
3151 | + extra = {'HTTP_REFERER': 'http://otherhost/'} |
3152 | + data = {'trust_root': 'http://localhost/', |
3153 | + 'callback': 'http://localhost/'} |
3154 | + r = self.client.post('/+pre-authorize-rp', data, **extra) |
3155 | + # we get redirected to login |
3156 | + next_url = '/+login?next=' + quote('/+pre-authorize-rp?') |
3157 | + self.assertRedirects(r, next_url) |
3158 | + # and the referer info is stored in the session |
3159 | + self.assertEqual(self.client.session['pre_auth_referer'], |
3160 | + 'http://otherhost/') |
3161 | + self.assertTrue(self.client.session['pre_auth_referer_for'], |
3162 | + 'http://localhost/') |
3163 | + |
3164 | + # attempt to pre-authorize for a different trust_root |
3165 | + data['trust_root'] = 'http://otherhost/' |
3166 | + r = self.client.post('/+pre-authorize-rp', data, **extra) |
3167 | + # pre-authorization is denied |
3168 | + self.assertEqual(r.status_code, 400) |
3169 | + |
3170 | + settings.OPENID_PREAUTHORIZATION_ACL = old_OPENID_PREAUTHORIZATION_ACL |
3171 | + |
3172 | + |
3173 | +class CancelTestCase(AuthenticatedTestCase, OpenIDProviderTestCase): |
3174 | + def setUp(self): |
3175 | + super(CancelTestCase, self).setUp(disableCSRF=True) |
3176 | + |
3177 | + self.params = request = {'openid.mode': 'checkid_setup', |
3178 | + 'openid.trust_root': 'http://localhost/', |
3179 | + 'openid.return_to': 'http://localhost/', |
3180 | + 'openid.identity': IDENTIFIER_SELECT} |
3181 | + openid_server = server._get_openid_server() |
3182 | + self.orequest = openid_server.decodeRequest(request) |
3183 | + self.token = create_token(16) |
3184 | + session = self.client.session |
3185 | + session[self.token] = signed.dumps(self.orequest, |
3186 | + settings.SECRET_KEY) |
3187 | + session.save() |
3188 | + |
3189 | + rpconfig = OpenIDRPConfig(trust_root='http://localhost') |
3190 | + rpconfig.save() |
3191 | + |
3192 | + def test_cancel_invalid_openid_transaction(self): |
3193 | + token = 'a' * 16 |
3194 | + r = self.client.get("/%s/+cancel" % token) |
3195 | + self.assertEqual(r.status_code, 200) |
3196 | + self.assertEqual(r.content, 'Invalid OpenID transaction') |
3197 | + |
3198 | + def test_cancel_user_is_authenticated(self): |
3199 | + # cancel request |
3200 | + r = self.client.get("/%s/+cancel" % self.token) |
3201 | + query = self.get_query(r) |
3202 | + self.assertEqual(r.status_code, 302) |
3203 | + self.assertEqual(query['openid.mode'], 'cancel') |
3204 | + |
3205 | + def test_cancel_user_is_not_authenticated(self): |
3206 | + # save session data |
3207 | + session_data = self.client.session[self.token] |
3208 | + |
3209 | + # logout |
3210 | + self.client.logout() |
3211 | + # request something so we get a real session in the client |
3212 | + self.client.get('/+openid', self.params) |
3213 | + |
3214 | + # manipulate session |
3215 | + session = self.client.session |
3216 | + session[self.token] = session_data |
3217 | + session.save() |
3218 | + |
3219 | + # cancel request |
3220 | + r = self.client.get("/%s/+cancel" % self.token) |
3221 | + query = self.get_query(r) |
3222 | + self.assertEqual(r.status_code, 302) |
3223 | + self.assertEqual(query['openid.mode'], 'cancel') |
3224 | + |
3225 | + |
3226 | +class XRDSTestCase(OpenIDProviderTestCase): |
3227 | + fixtures = ['test'] |
3228 | + |
3229 | + def test_xrds(self): |
3230 | + r = self.client.get('/+xrds') |
3231 | + self.assertEqual(r.status_code, 200) |
3232 | + self.assertEqual(r['Content-Type'], 'application/xrds+xml') |
3233 | + self.assertTemplateUsed(r, 'openidapplication-xrds.xml') |
3234 | + |
3235 | + def test_identity_page_nonexisting_account(self): |
3236 | + r = self.client.get('/+id/aaaaaaaaaaaaaaaa') |
3237 | + self.assertEqual(r.status_code, 404) |
3238 | + |
3239 | + def test_identity_page_inactive_account(self): |
3240 | + account = Account.objects.get_by_email('mark@example.com') |
3241 | + account.status = AccountStatus.DEACTIVATED |
3242 | + account.save() |
3243 | + r = self.client.get("/+id/%s" % account.openid_identifier) |
3244 | + self.assertEqual(r.status_code, 404) |
3245 | + |
3246 | + def test_identity_page_active_account(self): |
3247 | + account = Account.objects.get_by_email('mark@example.com') |
3248 | + r = self.client.get("/+id/%s" % account.openid_identifier) |
3249 | + self.assertEqual(r.status_code, 200) |
3250 | + self.assertTemplateUsed(r, 'person.html') |
3251 | + self.assertEqual(r[YADIS_HEADER_NAME], |
3252 | + "%s/+xrds" % account.openid_identity_url) |
3253 | + |
3254 | + def test_xrds_identity_page_nonexisting_account(self): |
3255 | + r = self.client.get('/+id/aaaaaaaaaaaaaaaa') |
3256 | + self.assertEqual(r.status_code, 404) |
3257 | + |
3258 | + def test_xrds_identity_page_inactive_account(self): |
3259 | + account = Account.objects.get_by_email('mark@example.com') |
3260 | + account.status = AccountStatus.DEACTIVATED |
3261 | + account.save() |
3262 | + r = self.client.get("/+id/%s/+xrds" % account.openid_identifier) |
3263 | + self.assertEqual(r.status_code, 404) |
3264 | + |
3265 | + def test_xrds_identity_page_active_account(self): |
3266 | + account = Account.objects.get_by_email('mark@example.com') |
3267 | + r = self.client.get("/+id/%s/+xrds" % account.openid_identifier) |
3268 | + self.assertEqual(r.status_code, 200) |
3269 | + self.assertTemplateUsed(r, 'person-xrds.xml') |
3270 | + self.assertEqual(r['Content-Type'], 'application/xrds+xml') |
3271 | + |
3272 | + |
3273 | +class OpenIDAuthorizedTestCase(AuthenticatedTestCase): |
3274 | + fixtures = ['test'] |
3275 | + |
3276 | + def setUp(self): |
3277 | + super(OpenIDAuthorizedTestCase, self).setUp() |
3278 | + |
3279 | + self.request = DummyRequest() |
3280 | + self.request.user = Account.objects.get_by_email('mark@example.com') |
3281 | + self.request.user.last_login = datetime.datetime.utcnow() |
3282 | + self.orequest = DummyORequest() |
3283 | + self.orequest.identity = 'http://openid.launchpad.dev/+id/mark_oid' |
3284 | + |
3285 | + @classmethod |
3286 | + def mock_fromOpenIDRequest(cls, request): |
3287 | + return None |
3288 | + self.old_fromOpenIDRequest = pape.Request.fromOpenIDRequest |
3289 | + pape.Request.fromOpenIDRequest = mock_fromOpenIDRequest |
3290 | + |
3291 | + def tearDown(self): |
3292 | + pape.Request.fromOpenIDRequest = self.old_fromOpenIDRequest |
3293 | + |
3294 | + super(OpenIDAuthorizedTestCase, self).tearDown() |
3295 | + |
3296 | + def test_openid_is_authorized_not_authenticated(self): |
3297 | + self.request.user = AnonymousUser() |
3298 | + r = server._openid_is_authorized(self.request, self.orequest) |
3299 | + self.assertFalse(r) |
3300 | + |
3301 | + def test_openid_is_authorized_id_owner(self): |
3302 | + self.orequest.identity = 'http://localhost/+id/mark_oid' |
3303 | + r = server._openid_is_authorized(self.request, self.orequest) |
3304 | + self.assertFalse(r) |
3305 | + |
3306 | + def test_openid_is_authorized_should_reauthenticate(self): |
3307 | + @classmethod |
3308 | + def mock_fromOpenIDRequest(cls, request): |
3309 | + class MockPapeRequest(object): |
3310 | + max_auth_age = '0' |
3311 | + return MockPapeRequest() |
3312 | + old_fromOpenIDRequest = pape.Request.fromOpenIDRequest |
3313 | + pape.Request.fromOpenIDRequest = mock_fromOpenIDRequest |
3314 | + |
3315 | + r = server._openid_is_authorized(self.request, self.orequest) |
3316 | + self.assertFalse(r) |
3317 | + |
3318 | + pape.Request.fromOpenIDRequest = old_fromOpenIDRequest |
3319 | + |
3320 | + def test_openid_is_authorized_rpconfig(self): |
3321 | + rpconfig = OpenIDRPConfig(trust_root=self.orequest.trust_root) |
3322 | + rpconfig.auto_authorize = True |
3323 | + rpconfig.save() |
3324 | + |
3325 | + r = server._openid_is_authorized(self.request, self.orequest) |
3326 | + self.assertTrue(r) |
3327 | + |
3328 | + def test_openid_is_authorized_other(self): |
3329 | + r = server._openid_is_authorized(self.request, self.orequest) |
3330 | + self.assertFalse(r) |
3331 | + |
3332 | + expires = datetime.datetime.utcnow() + datetime.timedelta(1) |
3333 | + OpenIDAuthorization.objects.authorize(self.request.user, |
3334 | + self.orequest.trust_root, expires, |
3335 | + self.request.session.session_key) |
3336 | + |
3337 | + r = server._openid_is_authorized(self.request, self.orequest) |
3338 | + self.assertTrue(r) |
3339 | + |
3340 | + |
3341 | +class ShouldReauthenticateTestCase(SQLCachedTestCase): |
3342 | + fixtures = ['test'] |
3343 | + |
3344 | + def setUp(self): |
3345 | + self.user = Account.objects.get_by_email('mark@example.com') |
3346 | + self.orequest = DummyORequest() |
3347 | + self.old_fromOpenIDRequest = pape.Request.fromOpenIDRequest |
3348 | + |
3349 | + def tearDown(self): |
3350 | + pape.Request.fromOpenIDRequest = self.old_fromOpenIDRequest |
3351 | + |
3352 | + def test_should_reauthenticate_no_pape(self): |
3353 | + # pape_request is None |
3354 | + @classmethod |
3355 | + def mock_fromOpenIDRequest(cls, orequest): |
3356 | + return None |
3357 | + pape.Request.fromOpenIDRequest = mock_fromOpenIDRequest |
3358 | + |
3359 | + r = server._should_reauthenticate(self.orequest, self.user) |
3360 | + self.assertFalse(r) |
3361 | + |
3362 | + def test_should_reauthenticate_no_pape_max_auth_age(self): |
3363 | + # pape_request.max_auth_age is None |
3364 | + @classmethod |
3365 | + def mock_fromOpenIDRequest(cls, request): |
3366 | + class MockPapeRequest(object): |
3367 | + max_auth_age = None |
3368 | + return MockPapeRequest() |
3369 | + pape.Request.fromOpenIDRequest = mock_fromOpenIDRequest |
3370 | + |
3371 | + r = server._should_reauthenticate(self.orequest, self.user) |
3372 | + self.assertFalse(r) |
3373 | + |
3374 | + def test_should_reauthenticate_invalid_max_auth_age(self): |
3375 | + # max_auth_age raise ValueError |
3376 | + @classmethod |
3377 | + def mock_fromOpenIDRequest(cls, request): |
3378 | + class MockPapeRequest(object): |
3379 | + max_auth_age = 'bad-value' |
3380 | + return MockPapeRequest() |
3381 | + pape.Request.fromOpenIDRequest = mock_fromOpenIDRequest |
3382 | + |
3383 | + r = server._should_reauthenticate(self.orequest, self.user) |
3384 | + self.assertFalse(r) |
3385 | + |
3386 | + def test_should_reauthenticate_true(self): |
3387 | + # last_login <= cutoff |
3388 | + @classmethod |
3389 | + def mock_fromOpenIDRequest(cls, request): |
3390 | + class MockPapeRequest(object): |
3391 | + max_auth_age = '0' |
3392 | + return MockPapeRequest() |
3393 | + pape.Request.fromOpenIDRequest = mock_fromOpenIDRequest |
3394 | + self.user.last_login = datetime.datetime.utcnow() |
3395 | + |
3396 | + r = server._should_reauthenticate(self.orequest, self.user) |
3397 | + self.assertTrue(r) |
3398 | + |
3399 | + def test_should_reauthenticate_false(self): |
3400 | + # last_login > cutoff |
3401 | + @classmethod |
3402 | + def mock_fromOpenIDRequest(cls, request): |
3403 | + class MockPapeRequest(object): |
3404 | + max_auth_age = '100' |
3405 | + return MockPapeRequest() |
3406 | + pape.Request.fromOpenIDRequest = mock_fromOpenIDRequest |
3407 | + self.user.last_login = datetime.datetime.utcnow() |
3408 | + |
3409 | + r = server._should_reauthenticate(self.orequest, self.user) |
3410 | + self.assertFalse(r) |
3411 | + |
3412 | + |
3413 | +class UntrustedRPTest(OpenIDProviderTestCase): |
3414 | + fixtures = ['test'] |
3415 | + |
3416 | def setUp(self): |
3417 | # Ensure that we're restricting RPs for these tests |
3418 | self.old_restrict = getattr(settings, 'SSO_RESTRICT_RP', True) |
3419 | @@ -39,20 +689,68 @@ |
3420 | self.assertEquals(302, response.status_code) |
3421 | self.assert_(response['Location'].endswith('+untrusted')) |
3422 | |
3423 | + def test_untrusted(self): |
3424 | + request = {'openid.mode': 'checkid_setup', |
3425 | + 'openid.trust_root': 'http://localhost/', |
3426 | + 'openid.return_to': 'http://localhost/', |
3427 | + 'openid.identity': IDENTIFIER_SELECT} |
3428 | + openid_server = server._get_openid_server() |
3429 | + orequest = openid_server.decodeRequest(request) |
3430 | + token = create_token(16) |
3431 | + |
3432 | + # call up a session-modifying view to get a real session object |
3433 | + r = self.client.login(username='mark@example.com', password='test') |
3434 | + self.assertTrue(r) |
3435 | + |
3436 | + session = self.client.session |
3437 | + session[token] = signed.dumps(orequest, |
3438 | + settings.SECRET_KEY) |
3439 | + session.save() |
3440 | + |
3441 | + r = self.client.get("/%s/+untrusted" % token) |
3442 | + self.assertEqual(r.status_code, 200) |
3443 | + self.assertTemplateUsed(r, 'untrusted.html') |
3444 | + |
3445 | |
3446 | class TestSregFields(BasicAccountTestCase): |
3447 | + def setUp(self): |
3448 | + super(TestSregFields, self).setUp() |
3449 | + |
3450 | + self.account = Account( |
3451 | + creation_rationale=AccountCreationRationale.USER_CREATED, |
3452 | + status=AccountStatus.ACTIVE, |
3453 | + displayname='User') |
3454 | + self.account.save() |
3455 | + lp_account = LPAccount( |
3456 | + openid_identifier=self.account.openid_identifier) |
3457 | + lp_account.save() |
3458 | + person = Person(lp_account=lp_account) |
3459 | + person.save() |
3460 | + now = datetime.datetime.now() |
3461 | + personlocation = PersonLocation(date_created=now, |
3462 | + person=person, time_zone='UTC', |
3463 | + last_modified_by=person, |
3464 | + date_last_modified=now) |
3465 | + personlocation.save() |
3466 | + self.sreg_request = SRegRequest() |
3467 | + self.rpconfig = OpenIDRPConfig() |
3468 | + |
3469 | def test_sreg_fields_no_preferredemail(self): |
3470 | - _preferredemail = Account.preferredemail |
3471 | - Account.preferredemail = None |
3472 | + for email in self.account.emailaddress_set.all(): |
3473 | + email.status = EmailStatus.NEW |
3474 | + email.save() |
3475 | |
3476 | - account = Account() |
3477 | - sreg_request = SRegRequest() |
3478 | - rpconfig = OpenIDRPConfig() |
3479 | expected = [] |
3480 | - result = server._sreg_fields(account, sreg_request, rpconfig) |
3481 | + result = server._sreg_fields(self.account, self.sreg_request, |
3482 | + self.rpconfig) |
3483 | self.assertEqual(expected, result) |
3484 | |
3485 | - Account.preferredemail = _preferredemail |
3486 | + def test_sreg_fields_timezone(self): |
3487 | + self.sreg_request.optional = ['timezone'] |
3488 | + expected = [('timezone', 'UTC')] |
3489 | + result = server._sreg_fields(self.account, self.sreg_request, |
3490 | + self.rpconfig) |
3491 | + self.assertEqual(result, expected) |
3492 | |
3493 | |
3494 | class TestGetTeamMemberships(BasicAccountTestCase): |
3495 | |
3496 | === added file 'identityprovider/tests/test_views_testing.py' |
3497 | --- identityprovider/tests/test_views_testing.py 1970-01-01 00:00:00 +0000 |
3498 | +++ identityprovider/tests/test_views_testing.py 2010-06-01 12:03:28 +0000 |
3499 | @@ -0,0 +1,19 @@ |
3500 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
3501 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
3502 | + |
3503 | +from django.conf import settings |
3504 | +from django.test import TestCase |
3505 | + |
3506 | +import identityprovider.urls |
3507 | + |
3508 | + |
3509 | +class TestingViewsTestCase(TestCase): |
3510 | + def test_error(self): |
3511 | + old_DEBUG = settings.DEBUG |
3512 | + settings.DEBUG = True |
3513 | + reload(identityprovider.urls) |
3514 | + |
3515 | + self.assertRaises(FloatingPointError, self.client.get, '/error') |
3516 | + |
3517 | + settings.DEBUG = old_DEBUG |
3518 | + |
3519 | |
3520 | === modified file 'identityprovider/tests/test_views_ui.py' |
3521 | --- identityprovider/tests/test_views_ui.py 2010-05-13 16:36:02 +0000 |
3522 | +++ identityprovider/tests/test_views_ui.py 2010-06-01 12:03:28 +0000 |
3523 | @@ -8,11 +8,17 @@ |
3524 | |
3525 | from django.conf import settings |
3526 | from django.core import mail |
3527 | +from django.core import urlresolvers |
3528 | from django.test.client import Client |
3529 | +from openid.message import IDENTIFIER_SELECT |
3530 | |
3531 | +from identityprovider import signed |
3532 | from identityprovider.models import authtoken as at |
3533 | -from identityprovider.views import ui |
3534 | +from identityprovider.models import EmailAddress, Person, OpenIDRPConfig |
3535 | +from identityprovider.models.const import EmailStatus |
3536 | +from identityprovider.views import ui, server |
3537 | from identityprovider.models import Account, AuthToken, EmailAddress |
3538 | +from identityprovider.models.authtoken import create_token |
3539 | from identityprovider.models.captcha import Captcha |
3540 | from identityprovider.models.const import (AccountStatus, EmailStatus, |
3541 | LoginTokenType) |
3542 | @@ -26,6 +32,19 @@ |
3543 | def test_is_safe_redirect_url_return_false(self): |
3544 | self.assertFalse(ui._is_safe_redirect_url('non-existing')) |
3545 | |
3546 | + def test_is_safe_redirect_url_with_params(self): |
3547 | + self.assertFalse(ui._is_safe_redirect_url('non-existing?q=some_value')) |
3548 | + |
3549 | + def test_is_safe_redirect_url_404(self): |
3550 | + def mock_resolve(urlconf): |
3551 | + raise urlresolvers.Resolver404() |
3552 | + old_resolve = urlresolvers.resolve |
3553 | + urlresolvers.resolve = mock_resolve |
3554 | + |
3555 | + self.assertFalse(ui._is_safe_redirect_url('non-existing')) |
3556 | + |
3557 | + urlresolvers.resolve = old_resolve |
3558 | + |
3559 | |
3560 | class UIViewsTestCase(BasicAccountTestCase): |
3561 | |
3562 | @@ -88,6 +107,18 @@ |
3563 | self.assertFormError(r, 'form', None, |
3564 | 'Your account has been deactivated') |
3565 | |
3566 | + def test_login_without_next_with_token(self): |
3567 | + token = 'a'*16 |
3568 | + r = self.client.post("/%s/+login" % token, |
3569 | + {'email': 'mark@example.com', 'password': 'test'}) |
3570 | + self.assertEqual(r.status_code, 302) |
3571 | + self.assertEqual(r['Location'], "http://testserver/%s/" % token) |
3572 | + |
3573 | + def test_login_without_next_nor_token(self): |
3574 | + r = self.client.post('/+login', {'email': 'mark@example.com', |
3575 | + 'password': 'test'}) |
3576 | + self.assertRedirects(r, '/') |
3577 | + |
3578 | def test_logout(self): |
3579 | self.authenticate() |
3580 | r = self.client.get('/+logout') |
3581 | @@ -107,6 +138,10 @@ |
3582 | self.assertEquals(self.client.session.get(token), |
3583 | 'raw_orequest content') |
3584 | |
3585 | + def test_logout_no_preferredlanguage_attribute(self): |
3586 | + r = self.client.get('/+logout') |
3587 | + self.assertRedirects(r, '/+login') |
3588 | + |
3589 | def create_token(self, token_type, email=None, redirection_url=None): |
3590 | token = at.AuthToken.objects.create( |
3591 | token_type=token_type, |
3592 | @@ -219,6 +254,80 @@ |
3593 | r = self.client.get(location) |
3594 | self.assertRedirects(r, '/+logout-to-confirm') |
3595 | |
3596 | + def test_confirm_account_redirect_to_decide_token_error(self): |
3597 | + # get a valid session |
3598 | + token1 = 'a'*16 |
3599 | + r = self.client.post("/%s/+new_account" % token1, |
3600 | + {'email': 'person@example.com'}) |
3601 | + self.assertRedirects(r, '/+email-sent') |
3602 | + |
3603 | + # claim token |
3604 | + token2 = self.client.session['token_newaccount'] |
3605 | + |
3606 | + r = self.client.post("/token/%s/+newaccount" % token2, |
3607 | + {'displayname': 'Person', 'password': 'P4ssw0rd', |
3608 | + 'passwordconfirm': 'P4ssw0rd'}) |
3609 | + self.assertRedirects(r, "/%s/+decide" % token1) |
3610 | + |
3611 | + def test_confirm_account_redirect_to_decide_token(self): |
3612 | + # get a valid session |
3613 | + token1 = create_token(16) |
3614 | + r = self.client.post("/%s/+new_account" % token1, |
3615 | + {'email': 'person@example.com'}) |
3616 | + self.assertRedirects(r, '/+email-sent') |
3617 | + |
3618 | + # claim token |
3619 | + token2 = self.client.session['token_newaccount'] |
3620 | + |
3621 | + r = self.client.post("/token/%s/+newaccount" % token2, |
3622 | + {'displayname': 'Person', 'password': 'P4ssw0rd', |
3623 | + 'passwordconfirm': 'P4ssw0rd'}) |
3624 | + self.assertRedirects(r, "/%s/+decide" % token1) |
3625 | + |
3626 | + def test_confirm_account_redirect_to_decide_with_rpconfig(self): |
3627 | + token1 = create_token(16) |
3628 | + r = self.client.post("/%s/+new_account" % token1, |
3629 | + {'email': 'person@example.com'}) |
3630 | + self.assertRedirects(r, '/+email-sent') |
3631 | + |
3632 | + request = {'openid.mode': 'checkid_setup', |
3633 | + 'openid.trust_root': 'http://localhost/', |
3634 | + 'openid.return_to': 'http://localhost/', |
3635 | + 'openid.identity': IDENTIFIER_SELECT} |
3636 | + openid_server = server._get_openid_server() |
3637 | + orequest = openid_server.decodeRequest(request) |
3638 | + session = self.client.session |
3639 | + session[token1] = signed.dumps(orequest, settings.SECRET_KEY) |
3640 | + session.save() |
3641 | + |
3642 | + # create rpconfig |
3643 | + rpconfig = OpenIDRPConfig.objects.create(trust_root='http://localhost/') |
3644 | + |
3645 | + # claim token |
3646 | + token2 = self.client.session['token_newaccount'] |
3647 | + |
3648 | + r = self.client.post("/token/%s/+newaccount" % token2, |
3649 | + {'displayname': 'Person', 'password': 'P4ssw0rd', |
3650 | + 'passwordconfirm': 'P4ssw0rd'}) |
3651 | + self.assertRedirects(r, "/%s/+decide" % token1) |
3652 | + |
3653 | + # verify account created |
3654 | + account = Account.objects.get(pk=self.client.session['_auth_user_id']) |
3655 | + self.assertEqual(account.creation_rationale, |
3656 | + rpconfig.creation_rationale) |
3657 | + |
3658 | + def test_confirm_account_invalid_form(self): |
3659 | + # setup session |
3660 | + token1 = create_token(16) |
3661 | + r = self.client.post("/%s/+new_account" % token1, |
3662 | + {'email': 'person@example.com'}) |
3663 | + |
3664 | + # test view |
3665 | + token2 = self.client.session['token_newaccount'] |
3666 | + r = self.client.post("/token/%s/+newaccount" % token2) |
3667 | + self.assertTemplateUsed(r, 'registration/confirm_new_account.html') |
3668 | + self.assertFormError(r, 'form', 'displayname', 'Required field.') |
3669 | + |
3670 | def test_forgot_password_when_captcha_verification_fails(self): |
3671 | r = self.request_when_captcha_fails('/+forgot_password', |
3672 | {'email': 'mark@example.com'}) |
3673 | @@ -276,6 +385,21 @@ |
3674 | self.assertEqual(len(mail.outbox), 0) |
3675 | mail.outbox = [] |
3676 | |
3677 | + def test_forgot_password_invalid_form(self): |
3678 | + r = self.client.post('/+forgot_password') |
3679 | + self.assertEqual(r.status_code, 200) |
3680 | + self.assertTemplateUsed(r, 'registration/forgot_password.html') |
3681 | + self.assertFormError(r, 'form', 'email', 'Required field.') |
3682 | + |
3683 | + def test_forgot_password_with_token(self): |
3684 | + token1 = create_token(16) |
3685 | + r = self.client.post("/%s/+forgot_password" % token1, |
3686 | + {'email': 'test@canonical.com'}) |
3687 | + |
3688 | + token2 = self.client.session['token_forgotpassword'] |
3689 | + auth_token = AuthToken.objects.get(token=token2) |
3690 | + self.assertEqual(auth_token.redirection_url, "/%s/+decide" % token1) |
3691 | + |
3692 | def test_reset_password_when_account_active(self): |
3693 | r = self.client.post('/+forgot_password', |
3694 | {'email': 'test@canonical.com'}) |
3695 | @@ -391,6 +515,31 @@ |
3696 | r = self.client.post('/token/%s/+resetpassword' % token, data) |
3697 | self.assertRedirects(r, '/+bad-token') |
3698 | |
3699 | + def test_reset_password_invalid_form(self): |
3700 | + # get valid session |
3701 | + r = self.client.post('/+forgot_password', |
3702 | + {'email': 'test@canonical.com'}) |
3703 | + # claim token |
3704 | + token = self.client.session['token_forgotpassword'] |
3705 | + |
3706 | + # test view |
3707 | + r = self.client.post("/token/%s/+resetpassword" % token) |
3708 | + self.assertTemplateUsed(r, 'registration/reset_password.html') |
3709 | + self.assertFormError(r, 'form', 'password', 'Required field.') |
3710 | + |
3711 | + def test_reset_password_get(self): |
3712 | + # get valid session |
3713 | + r = self.client.post('/+forgot_password', |
3714 | + {'email': 'test@canonical.com'}) |
3715 | + # claim token |
3716 | + token = self.client.session['token_forgotpassword'] |
3717 | + |
3718 | + # test view |
3719 | + r = self.client.get("/token/%s/+resetpassword" % token) |
3720 | + self.assertTemplateUsed(r, 'registration/reset_password.html') |
3721 | + for context in r.context: |
3722 | + self.assertEqual(context['form'].errors, {}) |
3723 | + |
3724 | def request_when_captcha_fails(self, url, data): |
3725 | class MockCaptcha(object): |
3726 | def __init__(self, *args): |
3727 | @@ -440,6 +589,27 @@ |
3728 | self.assertEqual(len(mail.outbox), mails_sent) |
3729 | mail.outbox = [] |
3730 | |
3731 | + def test_new_account_when_person_exists_and_account_not(self): |
3732 | + def mock_debug(logger, msg): |
3733 | + self.msg = msg |
3734 | + old_debug = logging.Logger.debug |
3735 | + logging.Logger.debug = mock_debug |
3736 | + |
3737 | + person = Person.objects.create(displayname='Person') |
3738 | + emailaddress = EmailAddress.objects.create(email='person@example.com', |
3739 | + lp_person=person, status=EmailStatus.VALIDATED) |
3740 | + |
3741 | + r = self.client.post('/+new_account', {'email': 'person@example.com'}) |
3742 | + self.assertTrue('account for person' in self.msg) |
3743 | + |
3744 | + logging.Logger.debug = old_debug |
3745 | + |
3746 | + def test_new_account_when_account_not_exists_no_token(self): |
3747 | + r = self.client.post('/+new_account', {'email': 'person@example.com'}) |
3748 | + token = self.client.session['token_newaccount'] |
3749 | + token_obj = AuthToken.objects.get(token=token) |
3750 | + self.assertEqual(token_obj.redirection_url, '/') |
3751 | + |
3752 | def test_edit_account_template(self): |
3753 | r = self.client.post('/+login', {'email': 'mark@example.com', |
3754 | 'password': 'test', |
3755 | @@ -546,6 +716,10 @@ |
3756 | r = self.client.get("/token/%s/+newemail" % token.token) |
3757 | return (r, token) |
3758 | |
3759 | + def test_suspended(self): |
3760 | + r = self.client.get('/+suspended') |
3761 | + self.assertTemplateUsed(r, 'account/suspended.html') |
3762 | + |
3763 | |
3764 | class UIViewsLPModelTestCase(LPAccountTestCase): |
3765 | def test_new_account_when_person_does_not_exist(self): |
3766 | |
3767 | === modified file 'identityprovider/tests/test_widgets.py' |
3768 | --- identityprovider/tests/test_widgets.py 2010-04-21 15:29:24 +0000 |
3769 | +++ identityprovider/tests/test_widgets.py 2010-06-01 12:03:28 +0000 |
3770 | @@ -1,8 +1,74 @@ |
3771 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
3772 | # GNU Affero General Public License version 3 (see the file LICENSE). |
3773 | |
3774 | +from datetime import datetime |
3775 | +from django.conf import settings |
3776 | from unittest import TestCase |
3777 | -from identityprovider.widgets import CommaSeparatedWidget |
3778 | + |
3779 | +from identityprovider.models import Account, LPAccount |
3780 | +from identityprovider.tests.utils import SQLCachedTestCase |
3781 | +from identityprovider.widgets import (CommaSeparatedWidget, ROAwareSelect, |
3782 | + ROAwareTextInput, StatusWidget, ReadOnlyDateTimeWidget, LPUsernameWidget) |
3783 | + |
3784 | + |
3785 | +class ROAwareTextInputTestCase(TestCase): |
3786 | + def setUp(self): |
3787 | + self.widget = ROAwareTextInput() |
3788 | + self.old_READ_ONLY_MODE = getattr(settings, 'READ_ONLY_MODE', False) |
3789 | + |
3790 | + def tearDown(self): |
3791 | + settings.READ_ONLY_MODE = self.old_READ_ONLY_MODE |
3792 | + |
3793 | + def test_render_when_readonly(self): |
3794 | + settings.READ_ONLY_MODE = True |
3795 | + |
3796 | + r = self.widget.render('test', None) |
3797 | + self.assertEqual(r, '<span class="rofield"></span>') |
3798 | + |
3799 | + r = self.widget.render('test', 'value') |
3800 | + self.assertEqual(r, '<span class="rofield">value</span>') |
3801 | + |
3802 | + def test_render_when_not_readonly(self): |
3803 | + settings.READ_ONLY_MODE = False |
3804 | + |
3805 | + r = self.widget.render('test', None) |
3806 | + self.assertEqual(r, '<input type="text" name="test" />') |
3807 | + |
3808 | + r = self.widget.render('test', 'value') |
3809 | + self.assertEqual(r, '<input type="text" name="test" value="value" />') |
3810 | + |
3811 | + |
3812 | +class ROAwareSelectTestCase(TestCase): |
3813 | + def setUp(self): |
3814 | + self.choices = (('1', 'One'), ('2', 'Two')) |
3815 | + self.widget = ROAwareSelect() |
3816 | + self.old_READ_ONLY_MODE = getattr(settings, 'READ_ONLY_MODE', False) |
3817 | + |
3818 | + def tearDown(self): |
3819 | + settings.READ_ONLY_MODE = self.old_READ_ONLY_MODE |
3820 | + |
3821 | + def test_render_when_readonly(self): |
3822 | + settings.READ_ONLY_MODE = True |
3823 | + |
3824 | + r = self.widget.render('test', 'value', choices=self.choices) |
3825 | + self.assertEqual(r, '<span class="rofield"></span>') |
3826 | + |
3827 | + def test_render_when_readonly_selected(self): |
3828 | + settings.READ_ONLY_MODE = True |
3829 | + |
3830 | + choices = self.choices + (('value', 'The Value'),) |
3831 | + r = self.widget.render('test', 'value', choices=choices) |
3832 | + self.assertEqual(r, '<span class="rofield">The Value</span>') |
3833 | + |
3834 | + def test_render_when_not_readonly(self): |
3835 | + settings.READ_ONLY_MODE = False |
3836 | + |
3837 | + r = self.widget.render('test', 'value', choices=self.choices) |
3838 | + expected = """<select name="test"> |
3839 | +<option value="1">One</option> |
3840 | +<option value="2">Two</option> |
3841 | +</select>""" |
3842 | + self.assertEqual(r, expected) |
3843 | |
3844 | |
3845 | class CommaSeparatedWidgetTestCase(TestCase): |
3846 | @@ -22,3 +88,77 @@ |
3847 | def test_render_when_value_is_comma_separated_list(self): |
3848 | r = self.widget.render('test', "1,2", choices=self.choices) |
3849 | self.assertEquals(r.count('selected='), 2) |
3850 | + |
3851 | + |
3852 | +class StatusWidgetTestCase(TestCase): |
3853 | + def setUp(self): |
3854 | + self.choices = (('1', 'One'), ('2', 'Two')) |
3855 | + self.widget = StatusWidget() |
3856 | + |
3857 | + def test_render_not_date_status_set(self): |
3858 | + r = self.widget.render('test', 'value', choices=self.choices) |
3859 | + expected = """<select name="test"> |
3860 | +<option value="1">One</option> |
3861 | +<option value="2">Two</option> |
3862 | +</select> """ |
3863 | + self.assertEqual(r, expected) |
3864 | + |
3865 | + def test_render_date_status_set(self): |
3866 | + self.widget.date_status_set = datetime(2010, 01, 01) |
3867 | + r = self.widget.render('test', 'value', choices=self.choices) |
3868 | + expected = """<select name="test"> |
3869 | +<option value="1">One</option> |
3870 | +<option value="2">Two</option> |
3871 | +</select> Set on 2010-01-01 00:00:00""" |
3872 | + self.assertEqual(r, expected) |
3873 | + |
3874 | + |
3875 | +class ReadOnlyDateTimeWidgetTestCase(TestCase): |
3876 | + def setUp(self): |
3877 | + self.widget = ReadOnlyDateTimeWidget() |
3878 | + |
3879 | + def test_render_string(self): |
3880 | + self.widget.value = 'value' |
3881 | + r = self.widget.render('test', None) |
3882 | + self.assertEqual(r, 'value') |
3883 | + |
3884 | + def test_render_int(self): |
3885 | + self.widget.value = 4 |
3886 | + r = self.widget.render('test', None) |
3887 | + self.assertEqual(r, '4') |
3888 | + |
3889 | + def test_render_bool(self): |
3890 | + self.widget.value = False |
3891 | + r = self.widget.render('test', None) |
3892 | + self.assertEqual(r, 'False') |
3893 | + |
3894 | + def test_render_datetime(self): |
3895 | + self.widget.value = datetime(2010, 01, 01) |
3896 | + r = self.widget.render('test', None) |
3897 | + self.assertEqual(r, '2010-01-01 00:00:00') |
3898 | + |
3899 | + |
3900 | +class LPUsernameWidgetTestCase(SQLCachedTestCase): |
3901 | + pgsql_functions = ['generate_openid_identifier'] |
3902 | + fixtures = ['test'] |
3903 | + |
3904 | + def setUp(self): |
3905 | + self.widget = LPUsernameWidget() |
3906 | + self.widget.account = Account.objects.get_by_email('mark@example.com') |
3907 | + |
3908 | + def test_render_account(self): |
3909 | + r = self.widget.render('test', None) |
3910 | + self.assertEqual(r, '<a href="https://launchpad.net/~mark">mark</a>') |
3911 | + |
3912 | + def test_render_no_account(self): |
3913 | + # unlink account |
3914 | + self.widget.account = None |
3915 | + r = self.widget.render('test', None) |
3916 | + self.assertEqual(r, '') |
3917 | + |
3918 | + def test_render_account_no_person(self): |
3919 | + # unlink person |
3920 | + LPAccount.objects.filter( |
3921 | + openid_identifier=self.widget.account.openid_identifier).delete() |
3922 | + r = self.widget.render('test', None) |
3923 | + self.assertEqual(r, '') |
3924 | |
3925 | === added file 'identityprovider/tests/test_wsgi.py' |
3926 | --- identityprovider/tests/test_wsgi.py 1970-01-01 00:00:00 +0000 |
3927 | +++ identityprovider/tests/test_wsgi.py 2010-06-01 12:03:28 +0000 |
3928 | @@ -0,0 +1,43 @@ |
3929 | +from unittest import TestCase |
3930 | +from identityprovider.wsgi import WSGIDispatch, make_app |
3931 | + |
3932 | + |
3933 | +class StubWSGIApp(object): |
3934 | + |
3935 | + def __init__(self): |
3936 | + self.called = False |
3937 | + |
3938 | + def __call__(self, environ, start_response): |
3939 | + self.called = True |
3940 | + return [] |
3941 | + |
3942 | + |
3943 | +class WSGIDispatchTestCase(TestCase): |
3944 | + |
3945 | + def setUp(self): |
3946 | + self.app_1 = StubWSGIApp() |
3947 | + self.app_2 = StubWSGIApp() |
3948 | + self.default_app = StubWSGIApp() |
3949 | + self.mapping = (('/a1', self.app_1), ('/a2', self.app_2)) |
3950 | + self.dispatch = WSGIDispatch(self.mapping, self.default_app) |
3951 | + |
3952 | + def call_dispatch_with_path(self, path): |
3953 | + self.dispatch({'PATH_INFO': path}, lambda x: x) |
3954 | + |
3955 | + def test_call_is_routed_to_the_right_application(self): |
3956 | + self.call_dispatch_with_path('/a1') |
3957 | + |
3958 | + self.assertTrue(self.app_1.called) |
3959 | + |
3960 | + def test_call_is_routed_to_default_if_path_is_not_matching(self): |
3961 | + self.call_dispatch_with_path('/other') |
3962 | + |
3963 | + self.assertTrue(self.default_app.called) |
3964 | + |
3965 | + |
3966 | +class MakeAppTestCase(TestCase): |
3967 | + |
3968 | + def test_make_sure_that_callable_is_returned(self): |
3969 | + app = make_app() |
3970 | + |
3971 | + self.assertTrue(callable(app)) |
3972 | |
3973 | === modified file 'identityprovider/tests/utils.py' |
3974 | --- identityprovider/tests/utils.py 2010-05-13 13:30:24 +0000 |
3975 | +++ identityprovider/tests/utils.py 2010-06-01 12:03:28 +0000 |
3976 | @@ -4,6 +4,7 @@ |
3977 | import logging |
3978 | import urllib |
3979 | import urllib2 |
3980 | +import sys |
3981 | from cStringIO import StringIO |
3982 | from types import MethodType |
3983 | from unittest import TestSuite, TextTestRunner, TestLoader |
3984 | @@ -107,6 +108,18 @@ |
3985 | settings.MIDDLEWARE_CLASSES = self.old_middlewares |
3986 | |
3987 | |
3988 | +class OpenIDProviderTestCase(SQLCachedTestCase): |
3989 | + pgsql_functions = ['generate_openid_identifier'] |
3990 | + |
3991 | + def get_query(self, response): |
3992 | + query_start = response['Location'].find('?') |
3993 | + query_str = response['Location'][query_start+1:] |
3994 | + query_items = map(tuple, |
3995 | + [item.split('=') for item in query_str.split('&')]) |
3996 | + query = dict(query_items) |
3997 | + return query |
3998 | + |
3999 | + |
4000 | def form_error_tester(cls, url, test_data): |
4001 | for test in test_data: |
4002 | response = cls.client.post(url, test['query']) |
4003 | @@ -192,9 +205,19 @@ |
4004 | return TestLoader().loadTestsFromModule(test) |
4005 | |
4006 | |
4007 | +class NullDevice(object): |
4008 | + "A class that simulates writing to /dev/null." |
4009 | + def write(self, s): |
4010 | + pass |
4011 | + |
4012 | + |
4013 | # This function was adapted from the django project. |
4014 | # Please see the license file in the thirdparty/django directory. |
4015 | def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]): |
4016 | + # disable stderr while running the tests |
4017 | + old_stderr = sys.stderr |
4018 | + sys.stderr = NullDevice() |
4019 | + |
4020 | setup_test_environment() |
4021 | settings.DEBUG = False |
4022 | suite = TestSuite() |
4023 | @@ -237,6 +260,9 @@ |
4024 | settings.DATABASE_NAME = 'test_launchpad_dev' |
4025 | connection.creation.destroy_test_db(old_name, verbosity) |
4026 | teardown_test_environment() |
4027 | + |
4028 | + # re-enable stderr |
4029 | + sys.stderr = old_stderr |
4030 | return len(result.failures) + len(result.errors) |
4031 | |
4032 | |
4033 | |
4034 | === modified file 'identityprovider/utils.py' |
4035 | --- identityprovider/utils.py 2010-04-21 15:29:24 +0000 |
4036 | +++ identityprovider/utils.py 2010-06-01 12:03:28 +0000 |
4037 | @@ -100,7 +100,7 @@ |
4038 | |
4039 | |
4040 | def get_person_and_account_by_email(email): |
4041 | - from identityprovider.models import EmailAddress, Person |
4042 | + from identityprovider.models import EmailAddress |
4043 | try: |
4044 | email = EmailAddress.objects.get(email__iexact=email) |
4045 | if email.account is None and email.person is None: |
4046 | @@ -111,8 +111,6 @@ |
4047 | "to a team.") % email) |
4048 | else: |
4049 | return email.person, email.account |
4050 | - except Person.DoesNotExist: |
4051 | - return None, email.account |
4052 | except EmailAddress.DoesNotExist: |
4053 | raise PersonAndAccountNotFoundException() |
4054 | |
4055 | |
4056 | === modified file 'identityprovider/views/account.py' |
4057 | --- identityprovider/views/account.py 2010-05-07 20:29:41 +0000 |
4058 | +++ identityprovider/views/account.py 2010-06-01 12:03:28 +0000 |
4059 | @@ -67,9 +67,6 @@ |
4060 | email.status = EmailStatus.NEW |
4061 | email.save() |
4062 | account.status = AccountStatus.DEACTIVATED |
4063 | - if account.preferredemail is not None: |
4064 | - account.preferredemail.status = EmailStatus.NEW |
4065 | - account.preferredemail.save() |
4066 | account.save() |
4067 | |
4068 | # We don't want to lose session[token] when we log the user out |
4069 | |
4070 | === modified file 'identityprovider/views/consumer.py' |
4071 | --- identityprovider/views/consumer.py 2010-05-10 18:26:52 +0000 |
4072 | +++ identityprovider/views/consumer.py 2010-06-01 12:03:28 +0000 |
4073 | @@ -251,6 +251,6 @@ |
4074 | args = {'type_uris': [RP_RETURN_TO_URL_TYPE], |
4075 | 'endpoint_uris': [getViewURL(request, finishOpenID)], |
4076 | } |
4077 | - response = direct_to_template(request, 'xrds.xml', args) |
4078 | + response = direct_to_template(request, 'openidapplication-xrds.xml', args) |
4079 | response['Content-Type'] = YADIS_CONTENT_TYPE |
4080 | return response |
4081 | |
4082 | === modified file 'identityprovider/views/server.py' |
4083 | --- identityprovider/views/server.py 2010-04-21 15:29:24 +0000 |
4084 | +++ identityprovider/views/server.py 2010-06-01 12:03:28 +0000 |
4085 | @@ -129,7 +129,7 @@ |
4086 | |
4087 | def _is_valid_openid_for_this_site(identity): |
4088 | try: |
4089 | - urinorm(identity) |
4090 | + identity = urinorm(identity) |
4091 | idparts = urlparse.urlparse(identity) |
4092 | srvparts = urlparse.urlparse(settings.SSO_ROOT_URL) |
4093 | if identity == IDENTIFIER_SELECT: |
4094 | @@ -141,7 +141,7 @@ |
4095 | srvparts.hostname.endswith(".%s" % idparts.hostname)): |
4096 | return False |
4097 | accept_path_patterns = [ |
4098 | - '/', |
4099 | + '^/$', |
4100 | '^/\+id/[a-zA-Z0-9\-_\.]+$', |
4101 | '^/~[a-zA-Z0-9\-_\.]+$', |
4102 | ] |
4103 | @@ -383,8 +383,8 @@ |
4104 | max_auth_age = int(pape_request.max_auth_age) |
4105 | except ValueError: |
4106 | logger.debug("pape:max_auth_age parameter should be an integer: %s" % |
4107 | - max_auth_age) |
4108 | - raise HttpResponseBadRequest() |
4109 | + pape_request.max_auth_age) |
4110 | + return False |
4111 | |
4112 | cutoff = datetime.utcnow() - timedelta(seconds=max_auth_age) |
4113 | logger.debug("%s" % user.last_login) |
4114 | |
4115 | === modified file 'identityprovider/views/ui.py' |
4116 | --- identityprovider/views/ui.py 2010-05-13 16:01:26 +0000 |
4117 | +++ identityprovider/views/ui.py 2010-06-01 12:03:28 +0000 |
4118 | @@ -9,6 +9,7 @@ |
4119 | |
4120 | from django.conf import settings |
4121 | from django.contrib.auth.decorators import login_required |
4122 | +from django.core import urlresolvers |
4123 | from django.core.mail import send_mail |
4124 | from django.core.urlresolvers import Resolver404, resolve, reverse |
4125 | from django.http import Http404, HttpResponseRedirect, HttpResponseNotFound |
4126 | @@ -81,7 +82,7 @@ |
4127 | try: |
4128 | if url.find('?') >= 0: |
4129 | url = url[:url.find('?')] |
4130 | - response = resolve(url) |
4131 | + response = urlresolvers.resolve(url) |
4132 | except Resolver404: |
4133 | return False |
4134 | if response is None: |
4135 | @@ -242,7 +243,7 @@ |
4136 | rpconfig = get_rpconfig(openid_request.trust_root) |
4137 | if rpconfig is not None: |
4138 | creation_rationale = rpconfig.creation_rationale |
4139 | - except: |
4140 | + except Exception, e: |
4141 | pass |
4142 | Account.objects.create_account( |
4143 | displayname, username, password, creation_rationale) |
4144 | @@ -404,13 +405,7 @@ |
4145 | form = ResetPasswordForm(request.POST) |
4146 | if form.is_valid(): |
4147 | password = form.cleaned_data['password'] |
4148 | - if not account.can_reset_password: |
4149 | - # the only possible status that can get us here is SUSPENDED, |
4150 | - # because we don't use NOACCOUNT |
4151 | - request.session['message'] = _( |
4152 | - 'You cannot reset the password of a suspended account.') |
4153 | - return HttpResponseRedirect('/+suspended') |
4154 | - elif not account.is_active and account.can_reactivate: |
4155 | + if not account.is_active and account.can_reactivate: |
4156 | # if account can be reactivated, set the preferred email |
4157 | # address |
4158 | email_obj, created = EmailAddress.objects.get_or_create( |
4159 | |
4160 | === modified file 'identityprovider/views/utils.py' |
4161 | --- identityprovider/views/utils.py 2010-04-21 15:29:24 +0000 |
4162 | +++ identityprovider/views/utils.py 2010-06-01 12:03:28 +0000 |
4163 | @@ -19,8 +19,5 @@ |
4164 | else: |
4165 | alternatives.append(trust_root + '/') |
4166 | |
4167 | - try: |
4168 | - rpconfig = OpenIDRPConfig.objects.filter(trust_root__in=alternatives) |
4169 | - return rpconfig and rpconfig[0] or None |
4170 | - except OpenIDRPConfig.DoesNotExist: |
4171 | - return None |
4172 | + rpconfig = OpenIDRPConfig.objects.filter(trust_root__in=alternatives) |
4173 | + return rpconfig and rpconfig[0] or None |
4174 | |
4175 | === modified file 'identityprovider/widgets.py' |
4176 | --- identityprovider/widgets.py 2010-04-21 15:29:24 +0000 |
4177 | +++ identityprovider/widgets.py 2010-06-01 12:03:28 +0000 |
4178 | @@ -2,9 +2,10 @@ |
4179 | # GNU Affero General Public License version 3 (see the file LICENSE). |
4180 | |
4181 | from itertools import chain |
4182 | -from django.forms.widgets import TextInput, Select, SelectMultiple |
4183 | +from django.forms.widgets import TextInput, Select, SelectMultiple, Widget |
4184 | from django.conf import settings |
4185 | from django.utils.safestring import mark_safe |
4186 | +from django.utils.translation import ugettext_lazy as _ |
4187 | |
4188 | |
4189 | def read_only_markup(value): |
4190 | @@ -13,6 +14,12 @@ |
4191 | markup = '<span class="rofield">%s</span>' % value |
4192 | return mark_safe(markup) |
4193 | |
4194 | +def account_to_lp_link(account): |
4195 | + if account and account.person: |
4196 | + return ('<a href="https://launchpad.net/~%s">%s</a>' % |
4197 | + (account.person.name, account.person.name)) |
4198 | + return '' |
4199 | + |
4200 | |
4201 | class ROAwareTextInput(TextInput): |
4202 | def render(self, name, value, attrs=None): |
4203 | @@ -47,3 +54,31 @@ |
4204 | vals = value.split(',') |
4205 | return super(CommaSeparatedWidget, self).render( |
4206 | name, vals, attrs, choices) |
4207 | + |
4208 | + |
4209 | +class StatusWidget(Select): |
4210 | + date_status_set = None |
4211 | + |
4212 | + def render(self, name, value, attrs=None, choices=()): |
4213 | + select = super(StatusWidget, self).render(name, value, attrs, choices) |
4214 | + if self.date_status_set: |
4215 | + status_date = _("Set on %s") % self.date_status_set |
4216 | + else: |
4217 | + status_date = "" |
4218 | + return mark_safe("%s %s" % (select, status_date)) |
4219 | + |
4220 | + |
4221 | +class ReadOnlyDateTimeWidget(Widget): |
4222 | + value = None |
4223 | + |
4224 | + def render(self, name, value, attrs=None): |
4225 | + return str(self.value) |
4226 | + |
4227 | + |
4228 | +class LPUsernameWidget(Widget): |
4229 | + account = None |
4230 | + |
4231 | + def render(self, name, value, attrs=None): |
4232 | + return mark_safe(account_to_lp_link(self.account)) |
4233 | + |
4234 | + |
4235 | |
4236 | === modified file 'scripts/test' |
4237 | --- scripts/test 2010-05-10 15:46:09 +0000 |
4238 | +++ scripts/test 2010-06-01 12:03:28 +0000 |
4239 | @@ -1,4 +1,4 @@ |
4240 | -#!/bin/sh |
4241 | +#!/bin/bash |
4242 | |
4243 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
4244 | # GNU Affero General Public License version 3 (see the file LICENSE). |
4245 | @@ -21,9 +21,11 @@ |
4246 | echo "Probably you haven't activated right virtual environment." |
4247 | exit 1 |
4248 | fi |
4249 | - |
4250 | +# Cleanup coverage only when running whole test suite |
4251 | +cleanup="false" |
4252 | if [ ${#@} -eq 0 ] |
4253 | then |
4254 | + cleanup="true" |
4255 | unittest="true" |
4256 | doctest="true" |
4257 | fi |
4258 | @@ -66,7 +68,18 @@ |
4259 | fi |
4260 | |
4261 | # get rid of old coverage files to get a clean report |
4262 | -find . -name '.coverage*' -exec rm -f {} \; |
4263 | +if [ "$cleanup" = true ] |
4264 | +then |
4265 | + find . -name '.coverage*' -exec rm -f {} \; |
4266 | +fi |
4267 | + |
4268 | +function is_up() { |
4269 | + wget -q -O /dev/null "http://launchpad.dev/" |
4270 | +} |
4271 | + |
4272 | +function wait_until_up() { |
4273 | + until is_up; do sleep 0.2; done |
4274 | +} |
4275 | |
4276 | if [ "$doctest" = true ] |
4277 | then |
4278 | @@ -77,6 +90,8 @@ |
4279 | sudo $ENV `which python` wsgi_test_server.py & |
4280 | sso_pid=$! |
4281 | |
4282 | + wait_until_up |
4283 | + |
4284 | (cd doctests && python runner.py) |
4285 | |
4286 | sudo kill $sso_pid |
4287 | @@ -94,7 +109,7 @@ |
4288 | |
4289 | if [ "$unittest" = true ] |
4290 | then |
4291 | - coverage run -p manage.py test $test_specification |
4292 | + coverage run -p manage.py test --noinput $test_specification |
4293 | fi |
4294 | |
4295 | # Creating nice (HTML) coverage report |
identityprovide r/tests/ test_admin. py: the 'as_administrator' decorator is very elegant, but for the sake of having a consistent coding style, I'd prefer using setUp/tearDown for that (as it's used in other test modules).
There are also a few points that make pylint/pyflakes unhappy, but I don't have a full report available, so I don't want to block this based on that.