Merge lp:~canonical-isd-hackers/canonical-identity-provider/813619-fix-private-teams into lp:~canonical-isd-hackers/canonical-identity-provider/stable
- 813619-fix-private-teams
- Merge into 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 |
Related bugs: |
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
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 identityprovide
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): |
lovely!