Merge lp:~canonical-isd-hackers/canonical-identity-provider/813619-fix-private-teams into lp:~canonical-isd-hackers/canonical-identity-provider/stable

Proposed by Anthony Lenton
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: no longer in the source branch.
Merged at revision: 118
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/813619-fix-private-teams
Merge into: lp:~canonical-isd-hackers/canonical-identity-provider/stable
Diff against target: 402 lines (+282/-18)
4 files modified
identityprovider/api10/handlers.py (+3/-3)
identityprovider/tests/factory.py (+105/-0)
identityprovider/tests/test_handlers.py (+172/-14)
identityprovider/tests/test_views_preflight.py (+2/-1)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/813619-fix-private-teams
Reviewer Review Type Date Requested Status
Ricardo Kirkner (community) Approve
Review via email: mp+68844@code.launchpad.net

Commit message

Two small api bugfixes

Description of the change

A branch with mostly tests, completing missing unit tests for our api, and two one-liner bug fixes.

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

lovely!

review: Approve
Revision history for this message
ISD Branch Mangler (isd-branches-mangler) wrote :

Attempt to merge into lp:~canonical-isd-hackers/canonical-identity-provider/2.x-stable failed due to conflicts:

text conflict in identityprovider/tests/test_handlers.py

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/api10/handlers.py'
2--- identityprovider/api10/handlers.py 2011-07-21 20:54:07 +0000
3+++ identityprovider/api10/handlers.py 2011-07-22 16:46:33 +0000
4@@ -371,7 +371,7 @@
5 account = accounts[0]
6 memberships = (
7 TeamMembership.get_team_memberships_for_user(
8- data['team_names'], account, False))
9+ data['team_names'], account, include_private=True))
10 return memberships
11 else:
12 return []
13@@ -409,8 +409,8 @@
14 team_names = request.data['team_names']
15
16 memberships = (
17- TeamMembership.get_team_memberships(
18- team_names, request.user, True))
19+ TeamMembership.get_team_memberships_for_user(
20+ team_names, request.user, include_private=True))
21 return memberships
22
23 @named_operation
24
25=== added file 'identityprovider/tests/factory.py'
26--- identityprovider/tests/factory.py 1970-01-01 00:00:00 +0000
27+++ identityprovider/tests/factory.py 2011-07-22 16:46:33 +0000
28@@ -0,0 +1,105 @@
29+from itertools import count
30+
31+from identityprovider.models import (
32+ Account,
33+ APIUser,
34+ EmailAddress,
35+ LPOpenIdIdentifier,
36+ Person,
37+ TeamParticipation,
38+ )
39+from identityprovider.const import (
40+ PERSON_VISIBILITY_PUBLIC,
41+ PERSON_VISIBILITY_PRIVATE_MEMBERSHIP,
42+)
43+
44+from identityprovider.models.const import EmailStatus
45+from identityprovider.tests.utils import SQLCachedTestCase
46+
47+__all__ = ['TestCaseWithFactory']
48+
49+
50+class SSOObjectFactory(object):
51+ """A factory for creating model objects for tests."""
52+
53+ def __init__(self):
54+ self.counter = count()
55+
56+ def get_unique_string(self, prefix=None):
57+ if prefix is None:
58+ prefix = "generic-string"
59+ return prefix + str(self.get_unique_integer())
60+
61+ def get_unique_integer(self):
62+ """Return a unique integer for the test run."""
63+ return self.counter.next()
64+
65+ def make_apiuser(self, username=None, password='test'):
66+ if username is None:
67+ username = self.get_unique_string(prefix='apiuser-')
68+ user, created = APIUser.objects.get_or_create(username=username)
69+ user.set_password(password)
70+ user.save()
71+ return user
72+
73+ def make_account(self, displayname=None, username=None, email=None):
74+ if displayname is None:
75+ displayname = self.get_unique_string(prefix='Account ')
76+ if username is None:
77+ username = self.get_unique_string(prefix='account-')
78+ account = Account.objects.create_account(displayname, username,
79+ password='test')
80+ self.make_email_for_account(account, email=email,
81+ status=EmailStatus.PREFERRED)
82+ return account
83+
84+ def make_person(self, name=None, displayname=None, account=None):
85+ if name is None:
86+ name = self.get_unique_string(prefix='person-')
87+ if displayname is None:
88+ displayname = self.get_unique_string(prefix='Person ')
89+ lp_account = None
90+ if account is not None:
91+ lp_account = account.id
92+
93+ person = Person.objects.create(name=name, displayname=displayname,
94+ lp_account=lp_account)
95+ if lp_account is not None:
96+ LPOpenIdIdentifier.objects.create(
97+ identifier=account.openid_identifier,
98+ lp_account=lp_account)
99+ return person
100+
101+ def make_team(self, name=None, private=False, owner=None):
102+ if private:
103+ visibility = PERSON_VISIBILITY_PRIVATE_MEMBERSHIP
104+ else:
105+ visibility = PERSON_VISIBILITY_PUBLIC
106+ if owner is None:
107+ owner = self.make_person()
108+ if name is None:
109+ name = self.get_unique_string(prefix='team-')
110+ displayname = self.get_unique_string(prefix='Team ')
111+ team = self.make_person(name, displayname=displayname)
112+ team.visibility = visibility
113+ team.teamowner = owner
114+ team.save()
115+ return team
116+
117+ def add_account_to_team(self, account, team):
118+ if account.person is None:
119+ self.make_person(account=account)
120+ elif account.person.in_team(team.name):
121+ return
122+ TeamParticipation.objects.create(person=account.person, team=team)
123+
124+ def make_email_for_account(self, account, email=None, status=None):
125+ if email is None:
126+ email = self.get_unique_string(prefix='email-') + '@example.com'
127+ if status is None:
128+ status = EmailStatus.VALIDATED
129+ return EmailAddress.objects.create(email=email, account=account,
130+ status=status)
131+
132+class TestCaseWithFactory(SQLCachedTestCase):
133+ factory = SSOObjectFactory()
134
135=== modified file 'identityprovider/tests/test_handlers.py'
136--- identityprovider/tests/test_handlers.py 2011-07-21 20:54:07 +0000
137+++ identityprovider/tests/test_handlers.py 2011-07-22 16:46:33 +0000
138@@ -1,9 +1,19 @@
139 import base64
140-
141+import json
142 from django.utils import simplejson
143 from mock import patch
144+from oauth.oauth import (
145+ OAuthRequest,
146+ OAuthConsumer,
147+ OAuthToken,
148+ OAuthSignatureMethod_PLAINTEXT,
149+ )
150
151-from identityprovider.tests.utils import SQLCachedTestCase
152+from identityprovider.api10.handlers import (
153+ AuthenticationHandler,
154+ CanNotResetPasswordError,
155+ RegistrationHandler,
156+)
157 from identityprovider.models import (
158 Account,
159 APIUser,
160@@ -16,11 +26,8 @@
161 EmailStatus,
162 LoginTokenType,
163 )
164-from identityprovider.api10.handlers import (
165- AuthenticationHandler,
166- CanNotResetPasswordError,
167- RegistrationHandler,
168-)
169+from identityprovider.tests.utils import SQLCachedTestCase
170+from identityprovider.tests.factory import TestCaseWithFactory
171
172
173 class MockRequest(object):
174@@ -31,6 +38,7 @@
175 self.data = data
176 self.environ = {'REMOTE_ADDR': '127.0.0.1'}
177
178+
179 class RegistrationHandlerTestCase(SQLCachedTestCase):
180
181 fixtures = ["test"]
182@@ -241,19 +249,21 @@
183 email.account, 'test@example.com', None)
184
185
186-class AuthenticationTestCase(SQLCachedTestCase):
187-
188- fixtures = ["test"]
189+class AuthenticationTestCase(TestCaseWithFactory):
190+ pgsql_functions = ["generate_openid_identifier"]
191
192 def setUp(self):
193 self.authentication = AuthenticationHandler()
194
195 def test_account_by_openid_with_valid_openid(self):
196- request = MockRequest(user=APIUser(), data={'openid': "mark_oid"})
197+ account = self.factory.make_account()
198+ openid = account.openid_identifier
199+
200+ request = MockRequest(user=APIUser(), data={'openid': openid})
201
202 account = self.authentication.account_by_openid(request)
203
204- self.assertEquals(account['openid_identifier'], "mark_oid")
205+ self.assertEquals(account['openid_identifier'], openid)
206
207 def test_account_by_openid_with_invalid_openid(self):
208 request = MockRequest(user=APIUser(), data={'openid': "bad-openid"})
209@@ -263,10 +273,12 @@
210 self.assertTrue(account is None)
211
212 def test_authenticate_unquoted_token(self):
213+ account = self.factory.make_account(email='mark@example.com')
214 expected_keys = ['consumer_key', 'consumer_secret', 'name', 'token',
215 'token_secret']
216
217- extra = {'HTTP_AUTHORIZATION': 'basic ' + base64.b64encode('mark@example.com:test')}
218+ extra = {'HTTP_AUTHORIZATION': 'basic ' +
219+ base64.b64encode('mark@example.com:test')}
220 response = self.client.get('/api/1.0/authentications', data={
221 'ws.op': 'authenticate', 'token_name': 'some-token'}, **extra)
222
223@@ -275,13 +287,159 @@
224 self.assertEqual(content['name'], 'some-token')
225
226 def test_authenticate_quoted_token(self):
227+ account = self.factory.make_account(email='mark@example.com')
228 expected_keys = ['consumer_key', 'consumer_secret', 'name', 'token',
229 'token_secret']
230
231- extra = {'HTTP_AUTHORIZATION': 'basic ' + base64.b64encode('mark@example.com:test')}
232+ extra = {'HTTP_AUTHORIZATION': 'basic ' +
233+ base64.b64encode('mark@example.com:test')}
234 response = self.client.get('/api/1.0/authentications', data={
235 'ws.op': 'authenticate', 'token_name': '"some-token"'}, **extra)
236
237+
238 content = simplejson.loads(response.content)
239 self.assertEqual(set(content.keys()), set(expected_keys))
240 self.assertEqual(content['name'], 'some-token')
241+
242+ def test_team_memberships_public_teams(self):
243+ team = self.factory.make_team()
244+ otherteam = self.factory.make_team()
245+ apiuser = self.factory.make_apiuser(username='foobar')
246+ account = self.factory.make_account()
247+ self.factory.add_account_to_team(account, team)
248+
249+ # Request membership via API
250+ extra = {'HTTP_AUTHORIZATION': 'basic ' +
251+ base64.b64encode('foobar:test')}
252+ response = self.client.get('/api/1.0/authentications', data={
253+ 'ws.op': 'team_memberships',
254+ 'team_names': '["%s", "%s"]' % (team.name, otherteam.name),
255+ 'openid_identifier': account.openid_identifier,
256+ }, **extra)
257+ self.assertContains(response, team.name)
258+ self.assertNotContains(response, otherteam.name)
259+
260+ def test_team_membership_private_teams(self):
261+ team = self.factory.make_team(private=True)
262+ apiuser = self.factory.make_apiuser(username='foobar')
263+ account = self.factory.make_account()
264+ self.factory.add_account_to_team(account, team)
265+
266+ # Request membership via API
267+ extra = {'HTTP_AUTHORIZATION': 'basic ' +
268+ base64.b64encode('foobar:test')}
269+ response = self.client.get('/api/1.0/authentications', data={
270+ 'ws.op': 'team_memberships',
271+ 'team_names': '["%s"]' % team.name,
272+ 'openid_identifier': account.openid_identifier,
273+ }, **extra)
274+ self.assertContains(response, team.name)
275+
276+ def test_team_membership_basic_auth_protected(self):
277+ response = self.client.get('/api/1.0/authentications', data={
278+ 'ws.op': 'team_memberships',
279+ 'team_names': '["something"]',
280+ 'openid_identifier': 'test',
281+ })
282+ self.assertEqual(401, response.status_code)
283+
284+ def test_team_membership_fails_for_regular_user(self):
285+ account = self.factory.make_account(email='mark@example.com')
286+ extra = {'HTTP_AUTHORIZATION': 'basic ' +
287+ base64.b64encode('mark@example.com:test')}
288+ response = self.client.get('/api/1.0/authentications', data={
289+ 'ws.op': 'team_memberships',
290+ 'team_names': '["something"]',
291+ 'openid_identifier': 'test',
292+ }, **extra)
293+ self.assertEqual(403, response.status_code)
294+
295+
296+class AccountsTestCase(TestCaseWithFactory):
297+ pgsql_functions = ["generate_openid_identifier"]
298+
299+ def auth_header(self, token, url, realm='OAuth'):
300+ oaconsumer = OAuthConsumer(token.consumer.key, token.consumer.secret)
301+ oatoken = OAuthToken(token.token, token.token_secret)
302+ oarequest = OAuthRequest.from_consumer_and_token(
303+ oaconsumer, oatoken, http_url=url)
304+ oarequest.sign_request(OAuthSignatureMethod_PLAINTEXT(),
305+ oaconsumer, oatoken)
306+ header = oarequest.to_header(realm)
307+ return {'HTTP_AUTHORIZATION': header['Authorization']}
308+
309+
310+ def test_me_is_oauth_protected(self):
311+ extra = {'HTTP_AUTHORIZATION': 'basic ' +
312+ base64.b64encode('mark@example.com:test')}
313+ response = self.client.get('/api/1.0/accounts',
314+ data={'ws.op': 'me'}, **extra)
315+ self.assertEqual(401, response.status_code)
316+
317+ def test_me_success(self):
318+ account = self.factory.make_account()
319+ token = account.create_oauth_token('token-name')
320+ url = '/api/1.0/accounts'
321+
322+ response = self.client.get(url,
323+ data={'ws.op': 'me'}, **self.auth_header(token, url))
324+
325+ data = json.loads(response.content)
326+ expected = set(['username', 'preferred_email', 'displayname',
327+ 'unverified_emails', 'verified_emails', 'openid_identifier'])
328+ self.assertEqual(expected, set(data.keys()))
329+
330+ def test_team_membership_public_teams(self):
331+ account = self.factory.make_account()
332+ team = self.factory.make_team()
333+ otherteam = self.factory.make_team()
334+ self.factory.add_account_to_team(account, team)
335+ token = account.create_oauth_token('token-name')
336+ url = '/api/1.0/accounts'
337+
338+ response = self.client.get(url, data={'ws.op': 'team_memberships',
339+ 'team_names': '["%s", "%s"]' % (team.name, otherteam.name)},
340+ **self.auth_header(token, url))
341+
342+ self.assertContains(response, team.name)
343+ self.assertNotContains(response, otherteam.name)
344+
345+ def test_team_membership_private_teams(self):
346+ account = self.factory.make_account()
347+ team = self.factory.make_team(private=True)
348+ self.factory.add_account_to_team(account, team)
349+ token = account.create_oauth_token('token-name')
350+ url = '/api/1.0/accounts'
351+
352+ response = self.client.get(url, data={'ws.op': 'team_memberships',
353+ 'team_names': '["%s"]' % team.name},
354+ **self.auth_header(token, url))
355+
356+ self.assertContains(response, team.name)
357+
358+ def test_validate_email_bad_token(self):
359+ account = self.factory.make_account()
360+ token = account.create_oauth_token('token-name')
361+ url = '/api/1.0/accounts'
362+
363+ response = self.client.get(url, data={'ws.op': 'validate_email',
364+ 'email_token': 'ABCD'}, **self.auth_header(token, url))
365+
366+ self.assertContains(response, 'Bad email token!')
367+
368+ def test_validate_email_success(self):
369+ account = self.factory.make_account()
370+ email = self.factory.make_email_for_account(account,
371+ status=EmailStatus.NEW)
372+ authtoken = AuthTokenFactory().new_api_email_validation_token(
373+ account, email.email)
374+
375+ token = account.create_oauth_token('token-name')
376+ url = '/api/1.0/accounts'
377+
378+ response = self.client.get(url, data={'ws.op': 'validate_email',
379+ 'email_token': authtoken.token}, **self.auth_header(token, url))
380+
381+ self.assertContains(response, email.email)
382+ updatedemail = EmailAddress.objects.get(email=email.email)
383+ self.assertEqual(EmailStatus.VALIDATED, updatedemail.status)
384
385=== modified file 'identityprovider/tests/test_views_preflight.py'
386--- identityprovider/tests/test_views_preflight.py 2011-03-04 17:09:45 +0000
387+++ identityprovider/tests/test_views_preflight.py 2011-07-22 16:46:33 +0000
388@@ -13,12 +13,13 @@
389 r = self.client.get(self.URL)
390 self.assertEquals(r.status_code, 404)
391
392- def test_account_withtout_person_receives_404(self):
393+ def test_account_without_person_receives_404(self):
394 account = Account.objects.create_account(
395 "test", "abcd@example.com", "test")
396 self.client.login(username="abcd@example.com", password="test")
397 r = self.client.get(self.URL)
398 self.assertEquals(r.status_code, 404)
399+ account.emailaddress_set.all().delete()
400 account.delete()
401
402 def test_person_not_in_the_required_team_will_get_404(self):

Subscribers

People subscribed via source and target branches