Merge lp:~canonical-isd-hackers/canonical-identity-provider/improve-coverage into lp:canonical-identity-provider/release

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
Reviewer Review Type Date Requested Status
Szilveszter Farkas (community) Approve
Review via email: mp+26427@code.launchpad.net

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 :

identityprovider/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.

review: Needs Fixing
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