Merge lp:~facundo/canonical-identity-provider/support-caveat-v1 into lp:canonical-identity-provider/release

Proposed by Facundo Batista
Status: Merged
Approved by: Facundo Batista
Approved revision: no longer in the source branch.
Merged at revision: 1447
Proposed branch: lp:~facundo/canonical-identity-provider/support-caveat-v1
Merge into: lp:canonical-identity-provider/release
Diff against target: 224 lines (+89/-39)
3 files modified
src/api/v20/tests/test_handlers.py (+11/-16)
src/identityprovider/auth.py (+30/-5)
src/identityprovider/tests/test_auth.py (+48/-18)
To merge this branch: bzr merge lp:~facundo/canonical-identity-provider/support-caveat-v1
Reviewer Review Type Date Requested Status
Ricardo Kirkner (community) Approve
Review via email: mp+293941@code.launchpad.net

Commit message

Support v0 (unversioned) and v1 caveat_ids.

Description of the change

Support v0 (unversioned) and v1 caveat_ids.

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

LGTM

review: Approve

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 15:39:31 +0000
3+++ src/api/v20/tests/test_handlers.py 2016-05-05 20:58:38 +0000
4@@ -2176,17 +2176,7 @@
5 self.account = self.factory.make_account(
6 email=self.data['email'], password=self.data['password'])
7
8- self._stats_increment = self.patch('api.v20.handlers.stats.increment')
9-
10- def check_stats_increment(self, *args, **kwargs):
11- """Check stats increment was properly called."""
12- calls = self._stats_increment.call_args_list
13- self.assertEqual(len(calls), 1)
14- call_args, call_kwargs = calls[0]
15- self.assertEqual(call_args, args,
16- "Bad args in stats increment call {}".format(calls))
17- self.assertEqual(call_kwargs, kwargs,
18- "Bad kwargs in stats increment call {}".format(calls))
19+ self.stats_increment = self.patch('api.v20.handlers.stats.increment')
20
21
22 class MacaroonDischargeHandlerTestCase(MacaroonHandlerBaseTestCase,
23@@ -2342,7 +2332,8 @@
24 def test_macaroon_created(self):
25 json_body = self.do_post()
26 self.assert_response_correct(json_body, self.account)
27- self.check_stats_increment('macaroon.discharge', key='single_macaroon')
28+ self.stats_increment.assert_any_call(
29+ 'macaroon.discharge', key='single_macaroon')
30
31 def test_root_macaroon_corrupt(self):
32 data = dict(
33@@ -2388,7 +2379,8 @@
34 v.satisfy_general(verifying_function)
35 v.verify(root_macaroon, key, [discharge_macaroon])
36
37- self.check_stats_increment('macaroon.discharge', key='multi_macaroons')
38+ self.stats_increment.assert_any_call(
39+ 'macaroon.discharge', key='multi_macaroons')
40
41 def test_multiple_mixed(self):
42 bad_macaroon, _, _ = self.build_macaroon(
43@@ -2410,7 +2402,8 @@
44
45 json_body = self.do_post(data=data)
46 self.assert_response_correct(json_body, self.account, bind=True)
47- self.check_stats_increment('macaroon.discharge', key='caveat_id')
48+ self.stats_increment.assert_any_call(
49+ 'macaroon.discharge', key='caveat_id')
50
51 def test_caveat_id_corrupt(self):
52 data = dict(email='foo@bar.com', password='foobar123',
53@@ -2545,7 +2538,8 @@
54 v.satisfy_general(lambda c: True)
55 v.verify(
56 self.root_macaroon, self.macaroon_random_key, [discharge_macaroon])
57- self.check_stats_increment('macaroon.refresh', key='root_macaroon')
58+ self.stats_increment.assert_any_call(
59+ 'macaroon.refresh', key='root_macaroon')
60
61 def test_just_discharge_refreshed(self):
62 json_body = self.do_post()
63@@ -2559,4 +2553,5 @@
64 v = Verifier()
65 v.satisfy_general(lambda c: True)
66 v.verify(self.root_macaroon, self.macaroon_random_key, [discharge])
67- self.check_stats_increment('macaroon.refresh', key='just_discharge')
68+ self.stats_increment.assert_any_call(
69+ 'macaroon.refresh', key='just_discharge')
70
71=== modified file 'src/identityprovider/auth.py'
72--- src/identityprovider/auth.py 2016-05-05 15:39:31 +0000
73+++ src/identityprovider/auth.py 2016-05-05 20:58:38 +0000
74@@ -35,6 +35,7 @@
75 )
76 from identityprovider.models.api import APIUser
77 from identityprovider.models.const import EmailStatus, TokenScope
78+from identityprovider.stats import stats
79 from identityprovider.timeline_helpers import (
80 get_request_timing_function,
81 maybe_contextmanager,
82@@ -511,16 +512,40 @@
83 return sso_caveat.caveat_id, caveat_info
84
85
86+def _open_caveat_v0(info):
87+ """Open the caveat_id, v0 (unversioned, really) format."""
88+ caveat_info_raw = settings.CRYPTO_SSO_PRIVKEY.decrypt(
89+ base64.b64decode(info))
90+ caveat_info = json.loads(caveat_info_raw)
91+ return caveat_info
92+
93+
94+def _open_caveat_v1(info):
95+ """Open the caveat_id, v1 format."""
96+ assert info['version'] == 1 # only supported version
97+ info['3rdparty'] = settings.CRYPTO_SSO_PRIVKEY.decrypt(
98+ base64.b64decode(info.pop('secret')))
99+ return info
100+
101+
102 def _decrypt_caveat(raw_caveat_id):
103 """Decrypt and decode the caveat id info."""
104 try:
105- caveat_info_raw = settings.CRYPTO_SSO_PRIVKEY.decrypt(
106- base64.b64decode(raw_caveat_id))
107- caveat_info = json.loads(caveat_info_raw)
108- except:
109- # not properly encrypted information inside
110+ caveat_info = json.loads(raw_caveat_id)
111+ function = _open_caveat_v1
112+ stats_key = '1'
113+ except:
114+ # bad data, or maybe the v0 (unversioned, really) format
115+ caveat_info = raw_caveat_id
116+ function = _open_caveat_v0
117+ stats_key = '0'
118+
119+ try:
120+ caveat_info = function(caveat_info)
121+ except:
122 raise ValidationError("Bad info in the caveat_id")
123
124+ stats.increment('macaroon.auth.caveat_version', key=stats_key)
125 return caveat_info
126
127
128
129=== modified file 'src/identityprovider/tests/test_auth.py'
130--- src/identityprovider/tests/test_auth.py 2016-05-05 15:39:31 +0000
131+++ src/identityprovider/tests/test_auth.py 2016-05-05 20:58:38 +0000
132@@ -23,7 +23,7 @@
133 from django.test.utils import override_settings
134 from django.utils.timezone import now
135 from mock import call, Mock, patch
136-from pymacaroons import Macaroon, Verifier
137+from pymacaroons import Verifier
138
139 from identityprovider.auth import (
140 LaunchpadBackend,
141@@ -1108,36 +1108,66 @@
142
143 class MacaroonHelpersTestCase(SSOBaseTestCase):
144
145- def test_get_caveat_ok(self):
146- test_rsa_priv_key, test_rsa_pub_key = self.setup_key_pair()
147+ def setUp(self):
148+ super(MacaroonHelpersTestCase, self).setUp()
149+ self.stats_increment = self.patch(
150+ 'identityprovider.auth.stats.increment')
151
152- # create a Macaron with the proper third party caveat
153- macaroon_random_key = binascii.hexlify(os.urandom(32))
154- root_macaroon = Macaroon(
155- location='The store ;)',
156- key=macaroon_random_key,
157- identifier='A test macaroon',
158- )
159- random_key = binascii.hexlify(os.urandom(32))
160+ def test_get_caveat_ok_v0(self):
161 info = {
162 'some stuff': 'foo',
163 'more stuff': 'bar',
164 }
165+ _, test_rsa_pub_key = self.setup_key_pair()
166 info_encrypted = base64.b64encode(
167 test_rsa_pub_key.encrypt(json.dumps(info), 32)[0])
168- root_macaroon.add_third_party_caveat(
169- settings.MACAROON_SERVICE_LOCATION, random_key, info_encrypted)
170
171 # check
172- (sso_caveat,) = [c for c in root_macaroon.third_party_caveats()
173- if c.location == settings.MACAROON_SERVICE_LOCATION]
174- caveat_info = _decrypt_caveat(sso_caveat.caveat_id)
175+ caveat_info = _decrypt_caveat(info_encrypted)
176 self.assertEqual(caveat_info, info)
177-
178- def test_get_caveat_badly_encrypted(self):
179+ self.stats_increment.assert_any_call(
180+ 'macaroon.auth.caveat_version', key='0')
181+
182+ def test_get_caveat_ok_v1(self):
183+ random_key = binascii.hexlify(os.urandom(32))
184+ _, test_rsa_pub_key = self.setup_key_pair()
185+ caveat_id = json.dumps({
186+ 'version': 1,
187+ 'secret': base64.b64encode(
188+ test_rsa_pub_key.encrypt(random_key, 32)[0]),
189+ })
190+
191+ # check
192+ caveat_info = _decrypt_caveat(caveat_id)
193+ self.assertEqual(caveat_info, {
194+ 'version': 1,
195+ '3rdparty': random_key,
196+ })
197+ self.stats_increment.assert_any_call(
198+ 'macaroon.auth.caveat_version', key='1')
199+
200+ def test_get_caveat_v0_badly_encrypted(self):
201 self.assertRaises(ValidationError,
202 _decrypt_caveat, b"not really well encrypted stuff")
203
204+ def test_get_caveat_v1_bad_version(self):
205+ random_key = binascii.hexlify(os.urandom(32))
206+ _, test_rsa_pub_key = self.setup_key_pair()
207+ caveat_id = json.dumps({
208+ 'version': 7,
209+ 'secret': base64.b64encode(
210+ test_rsa_pub_key.encrypt(random_key, 32)[0]),
211+ })
212+ self.assertRaises(ValidationError, _decrypt_caveat, caveat_id)
213+
214+ def test_get_caveat_v1_badly_encrypted(self):
215+ _, test_rsa_pub_key = self.setup_key_pair()
216+ caveat_id = json.dumps({
217+ 'version': 1,
218+ 'secret': "badly encrypted",
219+ })
220+ self.assertRaises(ValidationError, _decrypt_caveat, caveat_id)
221+
222
223 class MacaroonRefreshFromRootTestCase(SSOBaseTestCase):
224 """Test the deprecated refresh_macaroons.