Merge lp:~cjwatson/canonical-identity-provider/openid-macaroon-discharge-v1 into lp:canonical-identity-provider/release
- openid-macaroon-discharge-v1
- Merge into trunk
Proposed by
Colin Watson
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1452 |
Proposed branch: | lp:~cjwatson/canonical-identity-provider/openid-macaroon-discharge-v1 |
Merge into: | lp:canonical-identity-provider/release |
Diff against target: |
672 lines (+141/-124) 10 files modified
src/api/v20/tests/test_handlers.py (+12/-5) src/identityprovider/forms.py (+11/-6) src/identityprovider/macaroon.py (+12/-26) src/identityprovider/tests/test_auth.py (+14/-11) src/identityprovider/tests/test_forms.py (+19/-20) src/identityprovider/tests/test_macaroon.py (+12/-16) src/identityprovider/tests/test_views_server.py (+17/-9) src/identityprovider/tests/utils.py (+30/-8) src/identityprovider/views/server.py (+9/-7) src/webui/views/consumer.py (+5/-16) |
To merge this branch: | bzr merge lp:~cjwatson/canonical-identity-provider/openid-macaroon-discharge-v1 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ricardo Kirkner (community) | Approve | ||
Review via email: mp+294272@code.launchpad.net |
Commit message
Switch the OpenID discharge macaroon protocol over to the new caveat-only mechanism and v1 caveat IDs.
Description of the change
Switch the OpenID discharge macaroon protocol over to the new caveat-only mechanism and v1 caveat IDs.
v0 caveat IDs should in fact still be accepted for the time being, but we won't support further features built on top of this branch such as human-readable descriptions with them, and we only test the new format.
I didn't bother keeping the older OpenID macaroon protocol around, because AFAIK it has no users yet other than my WIP changes to Launchpad.
The Launchpad side of this is in https:/
To post a comment you must log in.
Revision history for this message
Ricardo Kirkner (ricardokirkner) : | # |
Revision history for this message
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/api/v20/tests/test_handlers.py' |
2 | --- src/api/v20/tests/test_handlers.py 2016-05-05 20:58:23 +0000 |
3 | +++ src/api/v20/tests/test_handlers.py 2016-05-11 17:42:55 +0000 |
4 | @@ -2169,7 +2169,11 @@ |
5 | self.login_failed_calls = [] |
6 | login_failed.connect(self.track_failed_logins, dispatch_uid=self.id()) |
7 | |
8 | - self.root_macaroon, self.macaroon_random_key, _ = self.build_macaroon() |
9 | + # XXX cjwatson 2016-05-11: |
10 | + # MacaroonRefreshHandlerTestCase.test_macaroon_refreshed needs to be |
11 | + # fixed before changing the version here. |
12 | + self.root_macaroon, self.macaroon_random_key, _ = self.build_macaroon( |
13 | + version=0) |
14 | self.data = dict( |
15 | email='foo@bar.com', password='foobar123', |
16 | macaroon=self.root_macaroon.serialize()) |
17 | @@ -2344,7 +2348,8 @@ |
18 | check_login_failed=False) |
19 | |
20 | def test_root_macaroon_not_for_sso(self): |
21 | - macaroon, _, _ = self.build_macaroon(service_location="other service") |
22 | + macaroon, _, _ = self.build_macaroon( |
23 | + service_location="other service", version=1) |
24 | data = dict(email='foo@bar.com', password='foobar123', |
25 | macaroon=macaroon.serialize()) |
26 | self.assert_failed_login('INVALID_CREDENTIALS', data, |
27 | @@ -2360,7 +2365,8 @@ |
28 | |
29 | def test_multiple_macaroons(self): |
30 | # build *several* macaroons and send them all at once |
31 | - roots, rkeys, _ = zip(*[self.build_macaroon() for _ in range(3)]) |
32 | + roots, rkeys, _ = zip(*[ |
33 | + self.build_macaroon(version=1) for _ in range(3)]) |
34 | ids = range(3) |
35 | roots = dict(zip(ids, roots)) |
36 | rkeys = dict(zip(ids, rkeys)) |
37 | @@ -2384,7 +2390,7 @@ |
38 | |
39 | def test_multiple_mixed(self): |
40 | bad_macaroon, _, _ = self.build_macaroon( |
41 | - service_location="other service") |
42 | + service_location="other service", version=1) |
43 | data = dict(email='foo@bar.com', password='foobar123', macaroons=[ |
44 | ('good', self.root_macaroon.serialize()), |
45 | ('bad', bad_macaroon.serialize()), |
46 | @@ -2518,7 +2524,8 @@ |
47 | check_login_failed=False) |
48 | |
49 | def test_macaroon_bad_authinfo(self): |
50 | - macaroon, _, _ = self.build_macaroon(service_location="other service") |
51 | + macaroon, _, _ = self.build_macaroon( |
52 | + service_location="other service", version=1) |
53 | data = dict(root_macaroon=macaroon.serialize(), |
54 | discharge_macaroon=self.discharge_macaroon.serialize()) |
55 | self.assert_failed_login('INVALID_CREDENTIALS', data, |
56 | |
57 | === modified file 'src/identityprovider/forms.py' |
58 | --- src/identityprovider/forms.py 2016-05-03 12:21:13 +0000 |
59 | +++ src/identityprovider/forms.py 2016-05-11 17:42:55 +0000 |
60 | @@ -574,12 +574,13 @@ |
61 | class MacaroonRequestForm(Form): |
62 | """A form object for user control over requesting discharge macaroons.""" |
63 | |
64 | - def __init__(self, request, macaroon_request, rpconfig, |
65 | + def __init__(self, request, macaroon_request, rpconfig, trust_root, |
66 | approved_data=None): |
67 | self.request = request |
68 | self.macaroon_request = macaroon_request |
69 | - self.root_macaroon = self.macaroon_request.root_macaroon |
70 | + self.caveat_id = self.macaroon_request.caveat_id |
71 | self.rpconfig = rpconfig |
72 | + self.trust_root = trust_root |
73 | self.approved_data = approved_data |
74 | self.data = self._get_data_for_user() |
75 | super(MacaroonRequestForm, self).__init__(self.data) |
76 | @@ -590,14 +591,18 @@ |
77 | def _get_data_for_user(self): |
78 | """Data to ask about in the form.""" |
79 | data = {} |
80 | - if self.root_macaroon: |
81 | - data['macaroon'] = self.root_macaroon |
82 | + if self.caveat_id: |
83 | + data['macaroon'] = self.caveat_id |
84 | return data |
85 | |
86 | def _init_fields(self, data): |
87 | """Initialise fields from the discharge macaroon request.""" |
88 | - for location, root_macaroon in data.iteritems(): |
89 | - label = 'Service authorization for %s' % root_macaroon.location |
90 | + for location, caveat_id in data.iteritems(): |
91 | + if self.rpconfig: |
92 | + rp_displayname = self.rpconfig.displayname |
93 | + else: |
94 | + rp_displayname = self.trust_root |
95 | + label = 'Service authorization for %s' % rp_displayname |
96 | self.fields[location] = fields.BooleanField( |
97 | label=label, widget=forms.CheckboxInput( |
98 | check_test=self.check_test(location))) |
99 | |
100 | === modified file 'src/identityprovider/macaroon.py' |
101 | --- src/identityprovider/macaroon.py 2016-04-05 22:12:47 +0000 |
102 | +++ src/identityprovider/macaroon.py 2016-05-11 17:42:55 +0000 |
103 | @@ -19,8 +19,9 @@ |
104 | |
105 | It must be set to: http://ns.login.ubuntu.com/2016/openid-macaroon |
106 | |
107 | - openid.macaroon.root |
108 | - The serialised root macaroon that the RP wants to discharge. |
109 | + openid.macaroon.caveat_id |
110 | + The SSO third-party caveat ID from the root macaroon that the RP wants |
111 | + to discharge. |
112 | |
113 | As part of the positive assertion OpenID response, the following fields |
114 | will be provided: |
115 | @@ -38,7 +39,6 @@ |
116 | NamespaceAliasRegistrationError, |
117 | registerNamespaceAlias, |
118 | ) |
119 | -from pymacaroons import Macaroon |
120 | |
121 | from identityprovider.const import MACAROON_NS |
122 | |
123 | @@ -101,26 +101,22 @@ |
124 | class MacaroonRequest(Extension): |
125 | """An object to hold the state of a discharge macaroon request. |
126 | |
127 | - @ivar root_macaroon_raw: The serialised root macaroon to discharge. |
128 | - @type root_macaroon_raw: str |
129 | + @ivar caveat_id: The SSO third-party caveat ID from the root macaroon |
130 | + that the RP wants to discharge. |
131 | + @type caveat_id: str |
132 | |
133 | - @group Consumer: requestField, requestMacaroon, getExtensionArgs, |
134 | - addToOpenIDRequest |
135 | + @group Consumer: requestField, getExtensionArgs, addToOpenIDRequest |
136 | @group Server: fromOpenIDRequest, parseExtensionArgs |
137 | """ |
138 | |
139 | ns_alias = 'macaroon' |
140 | |
141 | - def __init__(self, root_macaroon_raw=None, macaroon_ns_uri=MACAROON_NS): |
142 | + def __init__(self, caveat_id=None, macaroon_ns_uri=MACAROON_NS): |
143 | """Initialize an empty discharge macaroon request.""" |
144 | Extension.__init__(self) |
145 | - self.root_macaroon_raw = root_macaroon_raw |
146 | - self.root_macaroon = None |
147 | + self.caveat_id = caveat_id |
148 | self.ns_uri = macaroon_ns_uri |
149 | |
150 | - if root_macaroon_raw: |
151 | - self.requestMacaroon(root_macaroon_raw) |
152 | - |
153 | @classmethod |
154 | def fromOpenIDRequest(cls, request): |
155 | """Create a discharge macaroon request that contains the fields that |
156 | @@ -166,17 +162,7 @@ |
157 | |
158 | @returns: None; updates this object |
159 | """ |
160 | - self.root_macaroon_raw = args.get('root') |
161 | - if self.root_macaroon_raw: |
162 | - self.requestMacaroon(self.root_macaroon_raw) |
163 | - |
164 | - def requestMacaroon(self, root_macaroon_raw): |
165 | - """Request a discharge macaroon. |
166 | - |
167 | - @param root_macaroon_raw: The serialised root macaroon to discharge. |
168 | - @type root_macaroon_raw: str |
169 | - """ |
170 | - self.root_macaroon = Macaroon.deserialize(root_macaroon_raw) |
171 | + self.caveat_id = args.get('caveat_id') |
172 | |
173 | def getExtensionArgs(self): |
174 | """Get a dictionary of unqualified macaroon request parameters |
175 | @@ -189,8 +175,8 @@ |
176 | """ |
177 | args = {} |
178 | |
179 | - if self.root_macaroon_raw: |
180 | - args['root'] = self.root_macaroon_raw |
181 | + if self.caveat_id: |
182 | + args['caveat_id'] = self.caveat_id |
183 | |
184 | return args |
185 | |
186 | |
187 | === modified file 'src/identityprovider/tests/test_auth.py' |
188 | --- src/identityprovider/tests/test_auth.py 2016-05-05 20:58:23 +0000 |
189 | +++ src/identityprovider/tests/test_auth.py 2016-05-11 17:42:55 +0000 |
190 | @@ -968,14 +968,16 @@ |
191 | |
192 | def setUp(self): |
193 | super(BuildMacaroonFromRootDischargeTestCase, self).setUp() |
194 | - self.root_macaroon, self.macaroon_random_key, _ = self.build_macaroon() |
195 | + self.root_macaroon, self.macaroon_random_key, _ = self.build_macaroon( |
196 | + version=0) |
197 | |
198 | def test_root_macaroon_corrupt(self): |
199 | self.assertRaises(ValidationError, build_discharge_macaroon_from_root, |
200 | "fake account", "I'm a seriously corrupted macaroon") |
201 | |
202 | def test_root_macaroon_not_for_sso(self): |
203 | - macaroon, _, _ = self.build_macaroon(service_location="other service") |
204 | + macaroon, _, _ = self.build_macaroon( |
205 | + service_location="other service", version=0) |
206 | self.assertRaises(AuthenticationError, |
207 | build_discharge_macaroon_from_root, |
208 | "fake account", macaroon.serialize()) |
209 | @@ -1043,18 +1045,18 @@ |
210 | "fake account", "I'm a seriously corrupted caveatid") |
211 | |
212 | def test_caveat_id_not_for_sso(self): |
213 | - macaroon, _, _ = self.build_macaroon(service_location="other service") |
214 | - (sso_caveat,) = [c for c in macaroon.third_party_caveats() |
215 | - if c.location == "other service"] |
216 | + macaroon, _, _ = self.build_macaroon( |
217 | + service_location="other service", version=1) |
218 | + sso_caveat = self.get_sso_caveat( |
219 | + macaroon, service_location="other service") |
220 | |
221 | self.assertRaises(ValidationError, build_discharge_macaroon, |
222 | "fake account", sso_caveat) |
223 | |
224 | def test_proper_discharging(self): |
225 | # build the input and call |
226 | - root_macaroon, _, random_key = self.build_macaroon() |
227 | - (sso_caveat,) = [c for c in root_macaroon.third_party_caveats() |
228 | - if c.location == settings.MACAROON_SERVICE_LOCATION] |
229 | + root_macaroon, _, random_key = self.build_macaroon(version=1) |
230 | + sso_caveat = self.get_sso_caveat(root_macaroon) |
231 | |
232 | real_account = self.factory.make_account() |
233 | before = now() |
234 | @@ -1177,7 +1179,8 @@ |
235 | |
236 | def setUp(self): |
237 | super(MacaroonRefreshFromRootTestCase, self).setUp() |
238 | - self.root_macaroon, self.macaroon_random_key, _ = self.build_macaroon() |
239 | + self.root_macaroon, self.macaroon_random_key, _ = self.build_macaroon( |
240 | + version=0) |
241 | |
242 | # discharge the test macaroon |
243 | self.account = self.factory.make_account() |
244 | @@ -1196,7 +1199,7 @@ |
245 | |
246 | def test_macaroons_dont_verify_ok(self): |
247 | # just get *another* discharge so it's not for the same root macaroon |
248 | - other_root, _, _ = self.build_macaroon() |
249 | + other_root, _, _ = self.build_macaroon(version=0) |
250 | other_discharge = build_discharge_macaroon_from_root( |
251 | self.account, other_root.serialize()) |
252 | self.assertRaises(AuthenticationError, refresh_macaroons, |
253 | @@ -1297,7 +1300,7 @@ |
254 | |
255 | def setUp(self): |
256 | super(MacaroonRefreshTestCase, self).setUp() |
257 | - root_macaroon, _, self.random_key = self.build_macaroon() |
258 | + root_macaroon, _, self.random_key = self.build_macaroon(version=1) |
259 | (caveat,) = [c for c in root_macaroon.third_party_caveats() |
260 | if c.location == settings.MACAROON_SERVICE_LOCATION] |
261 | |
262 | |
263 | === modified file 'src/identityprovider/tests/test_forms.py' |
264 | --- src/identityprovider/tests/test_forms.py 2016-05-05 11:39:43 +0000 |
265 | +++ src/identityprovider/tests/test_forms.py 2016-05-11 17:42:55 +0000 |
266 | @@ -45,7 +45,6 @@ |
267 | UserAttribsRequestForm, |
268 | tos_error, |
269 | ) |
270 | -from identityprovider.macaroon import MacaroonRequest |
271 | from identityprovider.models import Account, AccountPassword, OpenIDRPConfig |
272 | from identityprovider.models.const import AccountStatus, EmailStatus |
273 | from identityprovider.tests import DEFAULT_USER_PASSWORD |
274 | @@ -1131,71 +1130,71 @@ |
275 | def setUp(self): |
276 | super(MacaroonRequestFormTestCase, self).setUp() |
277 | self.account = self.factory.make_account() |
278 | + self.trust_root = 'http://localhost/' |
279 | self.rpconfig = OpenIDRPConfig.objects.create( |
280 | - trust_root='http://localhost/', description="Some description") |
281 | + trust_root=self.trust_root, description="Some description") |
282 | |
283 | def test_field_for_trusted_site(self): |
284 | """The server always returns discharge macaroons to trusted sites, |
285 | regardless of the state of the checkbox in the UI. |
286 | """ |
287 | - root_macaroon, _, _ = self.build_macaroon() |
288 | - macaroon_request = MacaroonRequest(root_macaroon.serialize()) |
289 | + macaroon_request = self.build_macaroon_request() |
290 | form = MacaroonRequestForm( |
291 | request=self._get_request_with_post_args(), |
292 | - macaroon_request=macaroon_request, rpconfig=self.rpconfig) |
293 | + macaroon_request=macaroon_request, rpconfig=self.rpconfig, |
294 | + trust_root=self.trust_root) |
295 | self.assertIn('macaroon', form.data_approved_for_request) |
296 | |
297 | def test_checked_field_for_untrusted_site(self): |
298 | """The server returns discharge macaroons to untrusted sites when |
299 | the user checks the checkbox in the UI. |
300 | """ |
301 | - root_macaroon, _, _ = self.build_macaroon() |
302 | - macaroon_request = MacaroonRequest(root_macaroon.serialize()) |
303 | + macaroon_request = self.build_macaroon_request() |
304 | form = MacaroonRequestForm( |
305 | request=self._get_request_with_post_args(macaroon='macaroon'), |
306 | - macaroon_request=macaroon_request, rpconfig=None) |
307 | + macaroon_request=macaroon_request, rpconfig=None, |
308 | + trust_root=self.trust_root) |
309 | self.assertIn('macaroon', form.data_approved_for_request) |
310 | |
311 | def test_unchecked_field_for_untrusted_site(self): |
312 | """The server does not return discharge macaroons to untrusted sites |
313 | when the user does not check the checkbox in the UI. |
314 | """ |
315 | - root_macaroon, _, _ = self.build_macaroon() |
316 | - macaroon_request = MacaroonRequest(root_macaroon.serialize()) |
317 | + macaroon_request = self.build_macaroon_request() |
318 | form = MacaroonRequestForm( |
319 | request=self._get_request_with_post_args(), |
320 | - macaroon_request=macaroon_request, rpconfig=None) |
321 | + macaroon_request=macaroon_request, rpconfig=None, |
322 | + trust_root=self.trust_root) |
323 | self.assertNotIn('macaroon', form.data_approved_for_request) |
324 | |
325 | def test_checkbox_status_for_trusted_site(self): |
326 | """Checkboxes are always checked if the site is trusted.""" |
327 | - root_macaroon, _, _ = self.build_macaroon() |
328 | - macaroon_request = MacaroonRequest(root_macaroon.serialize()) |
329 | + macaroon_request = self.build_macaroon_request() |
330 | form = MacaroonRequestForm( |
331 | request=self._get_request_with_post_args(), |
332 | - macaroon_request=macaroon_request, rpconfig=self.rpconfig) |
333 | + macaroon_request=macaroon_request, rpconfig=self.rpconfig, |
334 | + trust_root=self.trust_root) |
335 | self.assertTrue(form.check_test('macaroon')(True)) |
336 | |
337 | def test_checkbox_status_for_untrusted_site(self): |
338 | """Checkboxes are checked by default if the site is untrusted.""" |
339 | - root_macaroon, _, _ = self.build_macaroon() |
340 | - macaroon_request = MacaroonRequest(root_macaroon.serialize()) |
341 | + macaroon_request = self.build_macaroon_request() |
342 | form = MacaroonRequestForm( |
343 | request=self._get_request_with_post_args(), |
344 | - macaroon_request=macaroon_request, rpconfig=None) |
345 | + macaroon_request=macaroon_request, rpconfig=None, |
346 | + trust_root=self.trust_root) |
347 | self.assertTrue(form.check_test('macaroon')(True)) |
348 | |
349 | def test_checkbox_status_for_untrusted_site_with_approved_data(self): |
350 | """Checkboxes respect user preferences on untrusted sites where |
351 | available. |
352 | """ |
353 | - root_macaroon, _, _ = self.build_macaroon() |
354 | - macaroon_request = MacaroonRequest(root_macaroon.serialize()) |
355 | + macaroon_request = self.build_macaroon_request() |
356 | approved_data = { |
357 | 'requested': ['macaroon'], |
358 | 'approved': []} |
359 | form = MacaroonRequestForm( |
360 | request=self._get_request_with_post_args(), |
361 | macaroon_request=macaroon_request, rpconfig=None, |
362 | - approved_data=approved_data) |
363 | + trust_root=self.trust_root, approved_data=approved_data) |
364 | self.assertFalse(form.check_test('macaroon')(True)) |
365 | |
366 | === modified file 'src/identityprovider/tests/test_macaroon.py' |
367 | --- src/identityprovider/tests/test_macaroon.py 2016-05-05 11:39:43 +0000 |
368 | +++ src/identityprovider/tests/test_macaroon.py 2016-05-11 17:42:55 +0000 |
369 | @@ -45,19 +45,17 @@ |
370 | def setUp(self): |
371 | super(MacaroonRequestTestCase, self).setUp() |
372 | |
373 | - self.root_macaroon, self.macaroon_random_key, _ = self.build_macaroon() |
374 | - self.req = MacaroonRequest(self.root_macaroon.serialize()) |
375 | - |
376 | - def assertMacaroonsEqual(self, expected, observed): |
377 | - self.assertEqual(expected.serialize(), observed.serialize()) |
378 | + root_macaroon, _, _ = self.build_macaroon(version=1) |
379 | + sso_caveat = self.get_sso_caveat(root_macaroon) |
380 | + self.caveat_id = sso_caveat.caveat_id |
381 | + self.req = MacaroonRequest(self.caveat_id) |
382 | |
383 | def test_init(self): |
384 | req = MacaroonRequest() |
385 | - self.assertIsNone(req.root_macaroon_raw) |
386 | - self.assertIsNone(req.root_macaroon) |
387 | + self.assertIsNone(req.caveat_id) |
388 | self.assertEqual(MACAROON_NS, req.ns_uri) |
389 | |
390 | - self.assertMacaroonsEqual(self.root_macaroon, self.req.root_macaroon) |
391 | + self.assertEqual(self.caveat_id, self.req.caveat_id) |
392 | |
393 | def test_fromOpenIDRequest(self): |
394 | params = {'openid.mode': 'checkid_setup', |
395 | @@ -68,8 +66,7 @@ |
396 | openid_server = server._get_openid_server(request) |
397 | orequest = openid_server.decodeRequest(params) |
398 | req = MacaroonRequest.fromOpenIDRequest(orequest) |
399 | - self.assertIsNone(req.root_macaroon_raw) |
400 | - self.assertIsNone(req.root_macaroon) |
401 | + self.assertIsNone(req.caveat_id) |
402 | self.assertEqual(MACAROON_NS, req.ns_uri) |
403 | |
404 | def test_fromOpenIDRequest_with_root(self): |
405 | @@ -77,22 +74,21 @@ |
406 | 'openid.trust_root': 'http://localhost/', |
407 | 'openid.return_to': 'http://localhost/', |
408 | 'openid.identity': IDENTIFIER_SELECT, |
409 | - 'openid.macaroon.root': self.root_macaroon.serialize()} |
410 | + 'openid.macaroon.caveat_id': self.caveat_id} |
411 | request = self.factory.make_request(**params) |
412 | openid_server = server._get_openid_server(request) |
413 | orequest = openid_server.decodeRequest(params) |
414 | req = MacaroonRequest.fromOpenIDRequest(orequest) |
415 | - self.assertMacaroonsEqual(self.root_macaroon, req.root_macaroon) |
416 | + self.assertEqual(self.caveat_id, req.caveat_id) |
417 | self.assertEqual(MACAROON_NS, req.ns_uri) |
418 | |
419 | def test_parseExtensionArgs(self): |
420 | req = MacaroonRequest() |
421 | - req.parseExtensionArgs( |
422 | - {'root': self.root_macaroon.serialize()}) |
423 | - self.assertMacaroonsEqual(self.root_macaroon, req.root_macaroon) |
424 | + req.parseExtensionArgs({'caveat_id': self.caveat_id}) |
425 | + self.assertEqual(self.caveat_id, req.caveat_id) |
426 | |
427 | def test_getExtensionArgs(self): |
428 | - expected = {'root': self.root_macaroon.serialize()} |
429 | + expected = {'caveat_id': self.caveat_id} |
430 | self.assertEqual(expected, self.req.getExtensionArgs()) |
431 | |
432 | def test_getExtensionArgs_no_root(self): |
433 | |
434 | === modified file 'src/identityprovider/tests/test_views_server.py' |
435 | --- src/identityprovider/tests/test_views_server.py 2016-05-10 00:44:27 +0000 |
436 | +++ src/identityprovider/tests/test_views_server.py 2016-05-11 17:42:55 +0000 |
437 | @@ -227,7 +227,7 @@ |
438 | self._test_auto_auth(sreg=['fullname']) |
439 | |
440 | def test_handle_user_response_auto_auth_discharge_macaroon(self): |
441 | - root_macaroon, macaroon_random_key, _ = self.build_macaroon() |
442 | + root_macaroon, macaroon_random_key, _ = self.build_macaroon(version=1) |
443 | # Add padding to force a POST after signing. We don't know exactly |
444 | # how long the serialized discharge macaroon will be yet, but it |
445 | # will probably be at least 1024 bytes. |
446 | @@ -290,9 +290,10 @@ |
447 | for k, v in expected_values.iteritems() |
448 | if k not in sreg or v is None] |
449 | if root_macaroon and macaroon_key: |
450 | + sso_caveat = self.get_sso_caveat(root_macaroon) |
451 | self.params.update({ |
452 | 'openid.ns.macaroon': MACAROON_NS, |
453 | - 'openid.macaroon.root': root_macaroon.serialize(), |
454 | + 'openid.macaroon.caveat_id': sso_caveat.caveat_id, |
455 | }) |
456 | unexpected_fields.remove('openid.macaroon.discharge') |
457 | response = self.client.post(self.url, self.params) |
458 | @@ -314,6 +315,8 @@ |
459 | verifier.satisfy_general(lambda caveat: True) |
460 | discharge_macaroon = Macaroon.deserialize( |
461 | forms[0].fields['openid.macaroon.discharge']) |
462 | + discharge_macaroon = root_macaroon.prepare_for_request( |
463 | + discharge_macaroon) |
464 | self.assertRaises( |
465 | MacaroonUnmetCaveatException, verifier.verify, |
466 | root_macaroon, macaroon_key, []) |
467 | @@ -1066,10 +1069,11 @@ |
468 | # make sure rpconfig is set to auto authorize |
469 | OpenIDRPConfig.objects.create( |
470 | trust_root='http://localhost/', auto_authorize=True) |
471 | - root_macaroon, macaroon_random_key, _ = self.build_macaroon() |
472 | + root_macaroon, macaroon_random_key, _ = self.build_macaroon(version=1) |
473 | + sso_caveat = self.get_sso_caveat(root_macaroon) |
474 | param_overrides = { |
475 | 'openid.ns.macaroon': MACAROON_NS, |
476 | - 'openid.macaroon.root': root_macaroon.serialize(), |
477 | + 'openid.macaroon.caveat_id': sso_caveat.caveat_id, |
478 | } |
479 | self._prepare_openid_token(param_overrides=param_overrides) |
480 | response = self.client.post(self.url) |
481 | @@ -1086,14 +1090,17 @@ |
482 | self.assertRaises( |
483 | MacaroonUnmetCaveatException, verifier.verify, |
484 | root_macaroon, macaroon_random_key, []) |
485 | + discharge_macaroon = root_macaroon.prepare_for_request( |
486 | + discharge_macaroon) |
487 | self.assertTrue(verifier.verify( |
488 | root_macaroon, macaroon_random_key, [discharge_macaroon])) |
489 | |
490 | def test_state_of_checkboxes_and_data_formats_macaroon(self): |
491 | - root_macaroon, _, _ = self.build_macaroon() |
492 | + root_macaroon, _, _ = self.build_macaroon(version=1) |
493 | + sso_caveat = self.get_sso_caveat(root_macaroon) |
494 | param_overrides = { |
495 | 'openid.ns.macaroon': MACAROON_NS, |
496 | - 'openid.macaroon.root': root_macaroon.serialize(), |
497 | + 'openid.macaroon.caveat_id': sso_caveat.caveat_id, |
498 | } |
499 | self._prepare_openid_token(param_overrides=param_overrides) |
500 | response = self.client.post(self.url) |
501 | @@ -1101,7 +1108,7 @@ |
502 | # This field is checked regardless of whether a site is trusted. |
503 | self._test_optional_trusted_field( |
504 | dom, field='macaroon', |
505 | - value='Service authorization for The store ;)') |
506 | + value='Service authorization for http://localhost/') |
507 | |
508 | |
509 | class DecideUserUnverifiedTestCase(DecideBaseTestCase): |
510 | @@ -1865,9 +1872,10 @@ |
511 | if with_teams: |
512 | params['openid.lp.query_membership'] = 'ubuntu-team' |
513 | if with_macaroon: |
514 | - root_macaroon, _, _ = self.build_macaroon() |
515 | + root_macaroon, _, _ = self.build_macaroon(version=1) |
516 | + sso_caveat = self.get_sso_caveat(root_macaroon) |
517 | params['openid.ns.macaroon'] = MACAROON_NS |
518 | - params['openid.macaroon.root'] = root_macaroon.serialize() |
519 | + params['openid.macaroon.caveat_id'] = sso_caveat.caveat_id |
520 | provider_url = get_provider_url(request) |
521 | openid_server = server._get_openid_server(provider_url) |
522 | return openid_server.decodeRequest(params) |
523 | |
524 | === modified file 'src/identityprovider/tests/utils.py' |
525 | --- src/identityprovider/tests/utils.py 2016-05-10 16:12:20 +0000 |
526 | +++ src/identityprovider/tests/utils.py 2016-05-11 17:42:55 +0000 |
527 | @@ -33,6 +33,7 @@ |
528 | # import signals to ensure all the handlers are connected and tests use the |
529 | # same logic as prod systems |
530 | from identityprovider import signed, signals # noqa |
531 | +from identityprovider.macaroon import MacaroonRequest |
532 | from identityprovider.tests import DEFAULT_USER_PASSWORD |
533 | from identityprovider.tests.factory import SSOObjectFactory |
534 | from identityprovider.utils import generate_random_string |
535 | @@ -181,7 +182,7 @@ |
536 | self.addCleanup(p.disable) |
537 | return test_rsa_priv_key, test_rsa_pub_key |
538 | |
539 | - def build_macaroon(self, service_location=None): |
540 | + def build_macaroon(self, service_location=None, version=1): |
541 | if service_location is None: |
542 | service_location = settings.MACAROON_SERVICE_LOCATION |
543 | test_rsa_priv_key, test_rsa_pub_key = self.setup_key_pair() |
544 | @@ -194,16 +195,37 @@ |
545 | identifier='A test macaroon', |
546 | ) |
547 | random_key = binascii.hexlify(os.urandom(32)) |
548 | - info = { |
549 | - 'roothash': macaroon_random_key, |
550 | - '3rdparty': random_key, |
551 | - } |
552 | - info_encrypted = base64.b64encode( |
553 | - test_rsa_pub_key.encrypt(json.dumps(info), 32)[0]) |
554 | + if version == 0: |
555 | + info = { |
556 | + 'roothash': macaroon_random_key, |
557 | + '3rdparty': random_key, |
558 | + } |
559 | + payload = base64.b64encode( |
560 | + test_rsa_pub_key.encrypt(json.dumps(info), 32)[0]) |
561 | + else: |
562 | + payload = json.dumps({ |
563 | + 'version': 1, |
564 | + 'secret': base64.b64encode( |
565 | + test_rsa_pub_key.encrypt(random_key, 32)[0]), |
566 | + }) |
567 | root_macaroon.add_third_party_caveat( |
568 | - service_location, random_key, info_encrypted) |
569 | + service_location, random_key, payload) |
570 | return root_macaroon, macaroon_random_key, random_key |
571 | |
572 | + def get_sso_caveat(self, macaroon, service_location=None): |
573 | + """Extract the SSO caveat from a macaroon.""" |
574 | + if service_location is None: |
575 | + service_location = settings.MACAROON_SERVICE_LOCATION |
576 | + (sso_caveat,) = [c for c in macaroon.third_party_caveats() |
577 | + if c.location == service_location] |
578 | + return sso_caveat |
579 | + |
580 | + def build_macaroon_request(self): |
581 | + """Build a macaroon and a MacaroonRequest to discharge it.""" |
582 | + root_macaroon, _, _ = self.build_macaroon(version=1) |
583 | + sso_caveat = self.get_sso_caveat(root_macaroon) |
584 | + return MacaroonRequest(sso_caveat.caveat_id) |
585 | + |
586 | def use_fixture(self, fixture): |
587 | """Set up 'fixture' to be used with the current test. |
588 | |
589 | |
590 | === modified file 'src/identityprovider/views/server.py' |
591 | --- src/identityprovider/views/server.py 2016-05-05 11:39:43 +0000 |
592 | +++ src/identityprovider/views/server.py 2016-05-11 17:42:55 +0000 |
593 | @@ -42,7 +42,7 @@ |
594 | from openid.yadis.constants import YADIS_HEADER_NAME |
595 | |
596 | from identityprovider import signed |
597 | -from identityprovider.auth import build_discharge_macaroon_from_root |
598 | +from identityprovider.auth import build_discharge_macaroon |
599 | from identityprovider.const import ( |
600 | AX_DATA_FIELDS, |
601 | MACAROON_NS, |
602 | @@ -321,7 +321,7 @@ |
603 | teams_form = TeamsRequestForm(request, teams_request, rpconfig, |
604 | approved_data=approved_data.get('teams')) |
605 | macaroon_form = MacaroonRequestForm( |
606 | - request, macaroon_request, rpconfig, |
607 | + request, macaroon_request, rpconfig, orequest.trust_root, |
608 | approved_data=approved_data.get('macaroon')) |
609 | context = { |
610 | 'account': request.user, |
611 | @@ -637,9 +637,10 @@ |
612 | 'approved': teams_form.teams_approved_by_user} |
613 | |
614 | macaroon_args = orequest.message.getArgs(MACAROON_NS) |
615 | - if macaroon_args.get('root'): |
616 | + if macaroon_args.get('caveat_id'): |
617 | macaroon_form = MacaroonRequestForm( |
618 | - request, MacaroonRequest.fromOpenIDRequest(orequest), rpconfig) |
619 | + request, MacaroonRequest.fromOpenIDRequest(orequest), rpconfig, |
620 | + orequest.trust_root) |
621 | approved_data['macaroon'] = { |
622 | 'requested': macaroon_form.data.keys(), |
623 | 'approved': macaroon_form.data_approved_for_request.keys()} |
624 | @@ -767,10 +768,11 @@ |
625 | """Add discharge macaroon if requested and approved.""" |
626 | macaroon_request = MacaroonRequest.fromOpenIDRequest(openid_request) |
627 | rpconfig = utils.get_rpconfig(openid_request.trust_root) |
628 | - form = MacaroonRequestForm(request, macaroon_request, rpconfig) |
629 | + form = MacaroonRequestForm( |
630 | + request, macaroon_request, rpconfig, openid_request.trust_root) |
631 | if form.data_approved_for_request: |
632 | - discharge_macaroon = build_discharge_macaroon_from_root( |
633 | - request.user, macaroon_request.root_macaroon_raw) |
634 | + discharge_macaroon = build_discharge_macaroon( |
635 | + request.user, macaroon_request.caveat_id) |
636 | macaroon_response = MacaroonResponse.extractResponse( |
637 | macaroon_request, discharge_macaroon.serialize()) |
638 | openid_response.addExtension(macaroon_response) |
639 | |
640 | === modified file 'src/webui/views/consumer.py' |
641 | --- src/webui/views/consumer.py 2016-04-11 08:57:57 +0000 |
642 | +++ src/webui/views/consumer.py 2016-05-11 17:42:55 +0000 |
643 | @@ -171,24 +171,13 @@ |
644 | auth_request.addExtension(teams_request_from_string(req_teams)) |
645 | |
646 | if request.POST.get('macaroon', False): |
647 | - macaroon_random_key = binascii.hexlify(os.urandom(32)) |
648 | - root_macaroon = Macaroon( |
649 | - location='Test consumer', |
650 | - key=macaroon_random_key, |
651 | - identifier='A test macaroon', |
652 | - ) |
653 | random_key = binascii.hexlify(os.urandom(32)) |
654 | - info = { |
655 | - 'roothash': macaroon_random_key, |
656 | - '3rdparty': random_key, |
657 | - } |
658 | pubkey = settings.CRYPTO_SSO_PRIVKEY.publickey() |
659 | - info_encrypted = base64.b64encode( |
660 | - pubkey.encrypt(json.dumps(info), 32)[0]) |
661 | - root_macaroon.add_third_party_caveat( |
662 | - settings.MACAROON_SERVICE_LOCATION, random_key, info_encrypted) |
663 | - macaroon_request = macaroon.MacaroonRequest( |
664 | - root_macaroon.serialize()) |
665 | + caveat_id = json.dumps({ |
666 | + 'version': 1, |
667 | + 'secret': base64.b64encode(pubkey.encrypt(random_key, 32)[0]), |
668 | + }) |
669 | + macaroon_request = macaroon.MacaroonRequest(caveat_id) |
670 | auth_request.addExtension(macaroon_request) |
671 | |
672 | # Compute the trust root and return URL values to build the |
Much nicer. Thanks!