Merge lp:~canonical-isd-hackers/canonical-identity-provider/split-oauth into lp:canonical-identity-provider/release

Proposed by Łukasz Czyżykowski
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: no longer in the source branch.
Merged at revision: 124
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/split-oauth
Merge into: lp:canonical-identity-provider/release
Diff against target: 561 lines (+143/-110)
12 files modified
identityprovider/auth.py (+7/-5)
identityprovider/bin/accounts_merge.py (+2/-4)
identityprovider/models/__init__.py (+0/-1)
identityprovider/models/account.py (+20/-0)
identityprovider/tests/test_auth.py (+15/-10)
identityprovider/tests/test_models_account.py (+28/-0)
identityprovider/tests/test_views_account.py (+5/-5)
identityprovider/views/account.py (+6/-9)
identityprovider/webservice/models.py (+3/-4)
identityprovider/wsgi.py (+1/-1)
oauth_backend/models.py (+20/-24)
oauth_backend/tests.py (+36/-47)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/split-oauth
Reviewer Review Type Date Requested Status
Ricardo Kirkner (community) Approve
Review via email: mp+45819@code.launchpad.net

Commit message

Extracting oauth_backend as separate Django app.

Description of the change

Extracting oauth_backend as separate Django app.

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

The changes seem to be alright. No new tests are failing (I get 3 test failures, but those are also failing in trunk, so it's not something purely related to this branch). Good work!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/auth.py'
2--- identityprovider/auth.py 2010-09-17 04:23:40 +0000
3+++ identityprovider/auth.py 2011-01-11 15:53:23 +0000
4@@ -3,8 +3,9 @@
5
6 from datetime import datetime
7
8-from identityprovider.models import (Account, AccountPassword, EmailAddress,
9- Consumer, Token)
10+from oauth_backend.models import Consumer, Token
11+
12+from identityprovider.models import Account, AccountPassword, EmailAddress
13 from identityprovider.models.api import APIUser
14 from identityprovider.models.const import EmailStatus
15 from identityprovider.utils import validate_launchpad_password
16@@ -68,17 +69,18 @@
17 """Currently only checks that given consumer and token are in database"""
18 try:
19 consumer = Consumer.objects.get(
20- account__openid_identifier=oauth_consumer.key)
21+ user__username=oauth_consumer.key)
22 token = Token.objects.get(token=oauth_token.key)
23
24 except (Token.DoesNotExist, Consumer.DoesNotExist):
25 return None
26 else:
27 # only allow authentication if account is active
28- if not consumer.account.is_active:
29+ account = Account.objects.get(openid_identifier=consumer.key)
30+ if not account.is_active:
31 return None
32
33 if token.consumer.key == oauth_consumer.key:
34- return consumer.account
35+ return account
36 else:
37 return None
38
39=== modified file 'identityprovider/bin/accounts_merge.py'
40--- identityprovider/bin/accounts_merge.py 2010-11-09 14:07:02 +0000
41+++ identityprovider/bin/accounts_merge.py 2011-01-11 15:53:23 +0000
42@@ -67,7 +67,7 @@
43
44 def merge_accounts(self, destination, source):
45 from identityprovider.models.const import AccountStatus
46- from identityprovider.models.oauthtoken import Consumer
47+ from oauth_backend.models import Consumer
48 source.status = AccountStatus.DEACTIVATED
49 source.authtoken_set.all().delete()
50 source.status_comment = (
51@@ -163,9 +163,7 @@
52
53 def test_merge_accounts_with_oauth_tokens(self):
54 s, d = self.create_account_pair()
55- from identityprovider.models.oauthtoken import (
56- create_oauth_token_for_account)
57- create_oauth_token_for_account(s, "test")
58+ s.create_oauth_token("test")
59 self.merger.merge_accounts(d, s)
60 self.assertEqual(s.oauth_consumer.token_set.count(), 0)
61
62
63=== modified file 'identityprovider/models/__init__.py'
64--- identityprovider/models/__init__.py 2010-04-21 15:29:24 +0000
65+++ identityprovider/models/__init__.py 2011-01-11 15:53:23 +0000
66@@ -8,5 +8,4 @@
67 from authtoken import *
68 from openidmodels import *
69 from team import *
70-from oauthtoken import *
71 from api import *
72
73=== modified file 'identityprovider/models/account.py'
74--- identityprovider/models/account.py 2010-12-20 16:17:59 +0000
75+++ identityprovider/models/account.py 2011-01-11 15:53:23 +0000
76@@ -10,6 +10,8 @@
77 from django.utils.translation import ugettext_lazy as _
78 from zope.interface import classImplements
79
80+from oauth_backend.models import Consumer
81+
82 from identityprovider.models.const import (AccountCreationRationale,
83 AccountStatus, EmailStatus)
84 from identityprovider.utils import (encrypt_launchpad_password,
85@@ -252,6 +254,24 @@
86 return True
87 return False
88
89+ def create_oauth_token(self, token_name):
90+ user, _ = User.objects.get_or_create(username=self.openid_identifier)
91+ try:
92+ consumer = user.oauth_consumer
93+ except Consumer.DoesNotExist:
94+ consumer = Consumer.objects.create(user=user)
95+ token = consumer.token_set.create(name=token_name)
96+ return token
97+
98+ def oauth_tokens(self):
99+ user, _ = User.objects.get_or_create(username=self.openid_identifier)
100+ try:
101+ consumer = user.oauth_consumer
102+ return consumer.token_set.all()
103+ except Consumer.DoesNotExist:
104+ return []
105+
106+
107 def save(self, force_insert=False, force_update=False):
108 if settings.READ_ONLY_MODE:
109 return
110
111=== modified file 'identityprovider/tests/test_auth.py'
112--- identityprovider/tests/test_auth.py 2010-05-28 22:03:16 +0000
113+++ identityprovider/tests/test_auth.py 2011-01-11 15:53:23 +0000
114@@ -1,11 +1,12 @@
115 # Copyright 2010 Canonical Ltd. This software is licensed under the
116 # GNU Affero General Public License version 3 (see the file LICENSE).
117+from django.contrib.auth.models import User
118+
119+from oauth_backend.models import Consumer
120
121 from identityprovider.models.account import Account
122 from identityprovider.models.const import AccountStatus, EmailStatus
123 from identityprovider.models.emailaddress import EmailAddress
124-from identityprovider.models.oauthtoken import (Consumer,
125- create_oauth_token_for_account)
126 from identityprovider.tests.utils import BasicAccountTestCase
127 from identityprovider.auth import LaunchpadBackend, oauth_authenticate
128
129@@ -74,17 +75,18 @@
130 def test_authenticate_account_no_password(self):
131 account = Account.objects.get_by_email('mark@example.com')
132 account.accountpassword.delete()
133-
134+
135 response = self.backend.authenticate('mark@example.com', 'test')
136-
137+
138 self.assertTrue(response is None)
139 account = Account.objects.get_by_email('mark@example.com')
140 self.assertTrue(account is not None)
141
142 def test_oauth_authenticate_account_active(self):
143 account = Account.objects.get_by_email('mark@example.com')
144- consumer, created = Consumer.objects.get_or_create(account=account)
145- token = create_oauth_token_for_account(account, 'new-token')
146+ user, _ = User.objects.get_or_create(username=account.openid_identifier)
147+ consumer, created = Consumer.objects.get_or_create(user=user)
148+ token = account.create_oauth_token('new-token')
149 oauth_token = token.oauth_token()
150
151 # make sure the account is active
152@@ -97,9 +99,10 @@
153 def test_oauth_authenticate_account_inactive(self):
154 account = Account.objects.get_by_email('mark@example.com')
155 _status = account.status
156+ user, _ = User.objects.get_or_create(username=account.openid_identifier)
157
158- consumer, created = Consumer.objects.get_or_create(account=account)
159- token = create_oauth_token_for_account(account, 'new-token')
160+ consumer, created = Consumer.objects.get_or_create(user=user)
161+ token = account.create_oauth_token('new-token')
162 oauth_token = token.oauth_token()
163
164 for status, _ in AccountStatus._get_choices():
165@@ -123,11 +126,13 @@
166
167 def test_oauth_authenticate_stolen_token(self):
168 victim_account = Account.objects.get_by_email('mark@example.com')
169- token = create_oauth_token_for_account(victim_account, 'new-token')
170+ token = victim_account.create_oauth_token('new-token')
171 oauth_token = token.oauth_token()
172
173 malicious_account = Account.objects.get_by_email('test@canonical.com')
174- consumer, created = Consumer.objects.get_or_create(account=malicious_account)
175+ malicious_user, _ = User.objects.get_or_create(
176+ username=malicious_account.openid_identifier)
177+ consumer, created = Consumer.objects.get_or_create(user=malicious_user)
178
179 # make sure the accounts are active
180 self.assertTrue(victim_account.status, AccountStatus.ACTIVE)
181
182=== modified file 'identityprovider/tests/test_models_account.py'
183--- identityprovider/tests/test_models_account.py 2010-12-20 18:22:53 +0000
184+++ identityprovider/tests/test_models_account.py 2011-01-11 15:53:23 +0000
185@@ -4,6 +4,9 @@
186 from datetime import datetime, timedelta
187 from django.conf import settings
188 from django.contrib.auth.models import User
189+
190+from oauth_backend.models import Consumer
191+
192 from identityprovider.tests.utils import BasicAccountTestCase
193 from identityprovider.models.account import (Account, AccountPassword,
194 AccountStatus)
195@@ -322,3 +325,28 @@
196 password = AccountPassword(account=account, password='password')
197 self.assertEqual(unicode(password), u'Password for displayname')
198
199+
200+class CreateOAuthTokenForAccountTestCase(BasicAccountTestCase):
201+
202+ def setUp(self):
203+ self.account = Account.objects.create_account(
204+ 'test', 'test-oauth@example.com', 'password'
205+ )
206+ self.user = User.objects.create_user(
207+ self.account.openid_identifier,
208+ 'test@example.com', 'password'
209+ )
210+
211+ def test_when_account_has_associated_consumer(self):
212+ consumer, _ = Consumer.objects.get_or_create(user=self.user)
213+
214+ token = self.account.create_oauth_token('new-token')
215+
216+ self.assertEquals(token.consumer.id, consumer.id)
217+
218+ def test_when_account_has_no_associated_consumer(self):
219+ Consumer.objects.filter(user=self.user).delete()
220+
221+ token = self.account.create_oauth_token('new-token')
222+
223+ self.assertEquals(token.consumer.user.id, self.user.id)
224
225=== modified file 'identityprovider/tests/test_views_account.py'
226--- identityprovider/tests/test_views_account.py 2010-07-12 22:49:50 +0000
227+++ identityprovider/tests/test_views_account.py 2011-01-11 15:53:23 +0000
228@@ -5,9 +5,9 @@
229
230 from django.core import mail
231
232+from oauth_backend.models import Token
233+
234 from identityprovider.models import Account, EmailAddress
235-from identityprovider.models.oauthtoken import (create_oauth_token_for_account,
236- Token)
237 from identityprovider.models.const import AccountStatus, EmailStatus
238 from identityprovider.utils import validate_launchpad_password
239
240@@ -199,8 +199,8 @@
241
242 def test_revoking_token_removes_it_from_being_displayed(self):
243 account = Account.objects.get_by_email("mark@example.com")
244- token_1 = create_oauth_token_for_account(account, "Token-1")
245- token_2 = create_oauth_token_for_account(account, "Token-2")
246+ token_1 = account.create_oauth_token("Token-1")
247+ token_2 = account.create_oauth_token("Token-2")
248
249 r = self.client.get('/+applications')
250
251@@ -217,7 +217,7 @@
252
253 def test_revoking_token_which_does_not_belogs_to_an_account(self):
254 account = Account.objects.get_by_email("test@canonical.com")
255- token = create_oauth_token_for_account(account, "Token")
256+ token = account.create_oauth_token("Token")
257
258 r = self.client.post('/+applications', {'token_id': token.token})
259
260
261=== modified file 'identityprovider/views/account.py'
262--- identityprovider/views/account.py 2010-09-18 05:38:59 +0000
263+++ identityprovider/views/account.py 2011-01-11 15:53:23 +0000
264@@ -2,11 +2,11 @@
265 # GNU Affero General Public License version 3 (see the file LICENSE).
266
267 from openid.yadis.constants import YADIS_HEADER_NAME
268-import urllib
269
270 from django.conf import settings
271 from django.contrib.auth.decorators import login_required
272-from django.http import HttpResponse, HttpResponseRedirect
273+from django.contrib.auth.models import User
274+from django.http import HttpResponseRedirect
275 from django.shortcuts import render_to_response, get_object_or_404
276 from django.template import RequestContext
277 from django.utils.translation import ugettext as _
278@@ -15,7 +15,7 @@
279 from identityprovider.forms import EditAccountForm, LoginForm, NewEmailForm
280 from identityprovider.models import (send_validation_email_request,
281 EmailAddress)
282-from identityprovider.models.oauthtoken import Consumer, Token
283+from oauth_backend.models import Consumer, Token
284 from identityprovider.models.const import EmailStatus
285 from identityprovider.views.utils import (redirection_url_for_token,
286 set_session_token_info)
287@@ -201,18 +201,15 @@
288 token_id = request.POST.get('token_id')
289 if token_id:
290 try:
291- token = request.user.oauth_consumer.token_set.get(pk=token_id)
292+ token = request.user.oauth_tokens().get(pk=token_id)
293 message = _("'%s' token was revoked successfully") % token.name
294 token.delete()
295 request.session['message'] = message
296- except (Consumer.DoesNotExist, Token.DoesNotExist):
297+ except (AttributeError, Token.DoesNotExist):
298 pass
299 return HttpResponseRedirect('/+applications')
300 else:
301- try:
302- tokens = request.user.oauth_consumer.token_set.all()
303- except Consumer.DoesNotExist:
304- tokens = []
305+ tokens = request.user.oauth_tokens()
306 context = RequestContext(request, {
307 'current_section': 'applications',
308 'tokens': tokens,
309
310=== modified file 'identityprovider/webservice/models.py'
311--- identityprovider/webservice/models.py 2010-12-20 18:20:12 +0000
312+++ identityprovider/webservice/models.py 2011-01-11 15:53:23 +0000
313@@ -12,12 +12,11 @@
314 from lazr.restful.utils import get_current_browser_request
315 from lazr.restful.declarations import webservice_error
316
317+from oauth_backend.models import Token
318 from identityprovider.models import (Account, EmailAddress,
319- AccountPassword, Token, APIUser, Person)
320+ AccountPassword, APIUser, Person)
321 from identityprovider.models.const import (
322 AccountCreationRationale, AccountStatus, EmailStatus, LoginTokenType)
323-from identityprovider.models.oauthtoken import (
324- create_oauth_token_for_account)
325 from identityprovider.models.authtoken import AuthToken, AuthTokenFactory
326 from identityprovider.utils import encrypt_launchpad_password
327 from identityprovider.utils import password_policy_compliant
328@@ -159,7 +158,7 @@
329 def authenticate(self, token_name):
330 request = get_current_browser_request()
331 account = request.environment['authenticated_user']
332- token = create_oauth_token_for_account(account, token_name)
333+ token = account.create_oauth_token(token_name)
334 application_token_created.send(
335 sender=self, openid_identifier=account.openid_identifier)
336 return token.serialize()
337
338=== modified file 'identityprovider/wsgi.py'
339--- identityprovider/wsgi.py 2010-11-15 16:24:53 +0000
340+++ identityprovider/wsgi.py 2011-01-11 15:53:23 +0000
341@@ -9,7 +9,7 @@
342 from lazr.restful.wsgi import WSGIApplication
343 from lazr.authentication.wsgi import BasicAuthMiddleware, OAuthMiddleware
344 from identityprovider.auth import basic_authenticate, oauth_authenticate
345-from identityprovider.models.oauthtoken import DataStore
346+from oauth_backend.models import DataStore
347
348
349
350
351=== added directory 'oauth_backend'
352=== added file 'oauth_backend/__init__.py'
353=== renamed file 'identityprovider/models/oauthtoken.py' => 'oauth_backend/models.py'
354--- identityprovider/models/oauthtoken.py 2010-04-21 15:29:24 +0000
355+++ oauth_backend/models.py 2011-01-11 15:53:23 +0000
356@@ -10,13 +10,21 @@
357
358 from functools import partial
359 from django.db import models
360-
361-from identityprovider.models import Account
362-
363-
364-TOKEN_LENGTH = 50
365-TOKEN_SECRET_LENGTH = 50
366-CONSUMER_SECRET_LENGTH = 30
367+from django.conf import settings
368+from django.contrib.auth.models import User
369+
370+
371+__all__ = [
372+ 'Token',
373+ 'Consumer',
374+ 'Nonce',
375+ 'DataStore',
376+]
377+
378+
379+TOKEN_LENGTH = getattr(settings, 'OAUTH_TOKEN_LENGTH', 50)
380+TOKEN_SECRET_LENGTH = getattr(settings, 'OAUTH_TOKEN_SECRET_LENGTH', 50)
381+CONSUMER_SECRET_LENGTH = getattr(settings, 'OAUTH_CONSUMER_SECRET_LENGTH', 30)
382
383
384 def _set_seed():
385@@ -68,16 +76,15 @@
386 return self.token
387
388 class Meta:
389- app_label = 'identityprovider'
390 db_table = 'oauth_token'
391
392
393 class Consumer(models.Model):
394- account = models.OneToOneField(Account, related_name='oauth_consumer')
395+ user = models.OneToOneField(User, related_name='oauth_consumer')
396
397 @property
398 def key(self):
399- return self.account.openid_identifier
400+ return self.user.username
401
402 secret = models.CharField(
403 max_length=255,
404@@ -97,7 +104,6 @@
405 return OAuthConsumer(self.key, self.secret)
406
407 class Meta:
408- app_label = 'identityprovider'
409 db_table = 'oauth_consumer'
410
411
412@@ -115,12 +121,11 @@
413 Create new nonce object linked to given consumer and token
414 """
415 consumer = Consumer.objects.get(
416- account__openid_identifier=consumer_key)
417+ user__username=consumer_key)
418 token = consumer.token_set.get(token=token_value)
419 return consumer.nonce_set.create(token=token, nonce=nonce)
420
421 class Meta:
422- app_label = 'identityprovider'
423 db_table = 'oauth_nonce'
424
425
426@@ -152,7 +157,7 @@
427 """
428 try:
429 consumer = Consumer.objects.get(
430- account__openid_identifier=consumer_key)
431+ user__username=consumer_key)
432 return consumer.oauth_consumer()
433 except Consumer.DoesNotExist:
434 return None
435@@ -165,7 +170,7 @@
436
437 """
438 count = Nonce.objects.filter(
439- consumer__account__openid_identifier=consumer.key,
440+ consumer__user__username=consumer.key,
441 token__token=token.key,
442 nonce=nonce).count()
443 if count > 0:
444@@ -174,12 +179,3 @@
445 Nonce.create(consumer.key, token.key, nonce)
446 return False
447
448-
449-def create_oauth_token_for_account(account, token_name):
450- try:
451- consumer = account.oauth_consumer
452- except Consumer.DoesNotExist:
453- consumer = Consumer.objects.create(account=account)
454-
455- token = consumer.token_set.create(name=token_name)
456- return token
457
458=== renamed file 'identityprovider/tests/test_models_oauthtoken.py' => 'oauth_backend/tests.py'
459--- identityprovider/tests/test_models_oauthtoken.py 2010-05-20 17:01:43 +0000
460+++ oauth_backend/tests.py 2011-01-11 15:53:23 +0000
461@@ -1,45 +1,27 @@
462 # Copyright 2010 Canonical Ltd. This software is licensed under the
463 # GNU Affero General Public License version 3 (see the file LICENSE).
464
465-from identityprovider.tests.utils import BasicAccountTestCase
466-from identityprovider.models.account import Account
467-from identityprovider.models.oauthtoken import (Consumer, DataStore, Nonce,
468- Token, OAuthToken, create_oauth_token_for_account)
469-
470-
471-class ConsumerTestCase(BasicAccountTestCase):
472-
473- def test_one_to_one_relationship_with_account(self):
474- account = Account.objects.get_by_email('test@canonical.com')
475- consumer, _ = Consumer.objects.get_or_create(account=account)
476-
477- self.assertEquals(account.id, consumer.account.id)
478- self.assertEquals(consumer.id, account.oauth_consumer.id)
479-
480-
481-class CreateOAuthTokenForAccountTestCase(BasicAccountTestCase):
482-
483- def test_when_account_has_associated_consumer(self):
484- account = Account.objects.get_by_email('test@canonical.com')
485- consumer, _ = Consumer.objects.get_or_create(account=account)
486-
487- token = create_oauth_token_for_account(account, 'new-token')
488-
489- self.assertEquals(token.consumer.id, consumer.id)
490-
491- def test_when_account_has_no_associated_consumer(self):
492- account = Account.objects.get_by_email('test@canonical.com')
493- Consumer.objects.filter(account=account).delete()
494-
495- token = create_oauth_token_for_account(account, 'new-token')
496-
497- self.assertEquals(token.consumer.account.id, account.id)
498-
499-
500-class TokenTestCase(BasicAccountTestCase):
501+from django.test import TestCase
502+from django.contrib.auth.models import User
503+
504+from .models import Consumer, DataStore, Nonce, Token, OAuthToken
505+
506+
507+class BaseTestCase(TestCase):
508+
509+ @property
510+ def user(self):
511+ if not hasattr(self, '_user'):
512+ self._user = User.objects.create_user(
513+ 'test_username', 'test@canonical.com', 'password'
514+ )
515+ return self._user
516+
517+
518+class TokenTestCase(BaseTestCase):
519+
520 def setUp(self):
521- self.account = Account.objects.get_by_email('test@canonical.com')
522- self.consumer, _ = Consumer.objects.get_or_create(account=self.account)
523+ self.consumer, _ = Consumer.objects.get_or_create(user=self.user)
524 self.token = Token(consumer=self.consumer, token='token',
525 token_secret='secret', name='name')
526 def test_unicode(self):
527@@ -54,18 +36,25 @@
528 self.assertEqual(self.token.serialize(), expected)
529
530
531-class ConsumerTestCase(BasicAccountTestCase):
532+class ConsumerTestCase(BaseTestCase):
533+
534 def test_unicode(self):
535- account = Account.objects.get_by_email('test@canonical.com')
536- consumer, _ = Consumer.objects.get_or_create(account=account)
537- self.assertEqual(unicode(consumer), unicode(account.openid_identifier))
538-
539-
540-class DataStoreTestCase(BasicAccountTestCase):
541+ consumer, _ = Consumer.objects.get_or_create(user=self.user)
542+ self.assertEqual(unicode(consumer),
543+ unicode(self.user.username))
544+
545+ def test_one_to_one_relationship_with_user(self):
546+ consumer, _ = Consumer.objects.get_or_create(user=self.user)
547+
548+ self.assertEquals(self.user.id, consumer.user.id)
549+ self.assertEquals(consumer.id, self.user.oauth_consumer.id)
550+
551+
552+class DataStoreTestCase(BaseTestCase):
553+
554 def setUp(self):
555 self.ds = DataStore()
556- account = Account.objects.get_by_email('test@canonical.com')
557- self.consumer, _ = Consumer.objects.get_or_create(account=account)
558+ self.consumer, _ = Consumer.objects.get_or_create(user=self.user)
559 self.token, _ = Token.objects.get_or_create(consumer=self.consumer,
560 token='token', token_secret='secret', name='name')
561 self.nonce, _ = Nonce.objects.get_or_create(consumer=self.consumer,