Merge lp:~facundo/canonical-identity-provider/remove-refresh-with-both-macaroons into lp:canonical-identity-provider/release

Proposed by Facundo Batista
Status: Merged
Approved by: Facundo Batista
Approved revision: no longer in the source branch.
Merge reported by: Ubuntu One Auto Pilot
Merged at revision: not available
Proposed branch: lp:~facundo/canonical-identity-provider/remove-refresh-with-both-macaroons
Merge into: lp:canonical-identity-provider/release
Diff against target: 256 lines (+2/-192)
4 files modified
src/api/v20/handlers.py (+1/-12)
src/api/v20/tests/test_handlers.py (+0/-18)
src/identityprovider/auth.py (+0/-31)
src/identityprovider/tests/test_auth.py (+1/-131)
To merge this branch: bzr merge lp:~facundo/canonical-identity-provider/remove-refresh-with-both-macaroons
Reviewer Review Type Date Requested Status
Matias Bordese (community) Approve
Review via email: mp+302731@code.launchpad.net

Commit message

Removed the deprecated way of refreshing macaroons (sending both root and discharge).

Description of the change

Removed the deprecated way of refreshing macaroons (sending both root and discharge).

To post a comment you must log in.
Revision history for this message
Matias Bordese (matiasb) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/api/v20/handlers.py'
--- src/api/v20/handlers.py 2016-07-27 14:41:55 +0000
+++ src/api/v20/handlers.py 2016-08-11 22:17:29 +0000
@@ -556,19 +556,8 @@
556 missing = {'discharge_macaroon': [FIELD_REQUIRED]}556 missing = {'discharge_macaroon': [FIELD_REQUIRED]}
557 return errors.INVALID_DATA(**missing)557 return errors.INVALID_DATA(**missing)
558558
559 if 'root_macaroon' in data:
560 # (deprecated 2016-05-03) use the root macaroon to old-fashion
561 # refresh, that also returns a bound discharge
562 method = auth.refresh_macaroons
563 params = (data['root_macaroon'], discharge_macaroon_raw)
564 stats.increment('macaroon.refresh', key='root_macaroon')
565 else:
566 method = auth.refresh_discharge
567 params = (discharge_macaroon_raw,)
568 stats.increment('macaroon.refresh', key='just_discharge')
569
570 try:559 try:
571 new_discharge = method(*params)560 new_discharge = auth.refresh_discharge(discharge_macaroon_raw)
572 except ValidationError:561 except ValidationError:
573 return errors.INVALID_DATA()562 return errors.INVALID_DATA()
574 except AccountDeactivated:563 except AccountDeactivated:
575564
=== modified file 'src/api/v20/tests/test_handlers.py'
--- src/api/v20/tests/test_handlers.py 2016-08-08 16:57:28 +0000
+++ src/api/v20/tests/test_handlers.py 2016-08-11 22:17:29 +0000
@@ -2850,22 +2850,6 @@
2850 expected_status_code=401,2850 expected_status_code=401,
2851 check_login_failed=False)2851 check_login_failed=False)
28522852
2853 def test_macaroon_refreshed(self):
2854 data = {'root_macaroon': self.root_macaroon.serialize(),
2855 'discharge_macaroon': self.discharge_macaroon.serialize()}
2856 json_body = self.do_post(data=data)
2857
2858 # get new discharge macaroon and verify with old root (its internals
2859 # are verified by the tests of the 'refresh_macaroons' function itself)
2860 discharge_macaroon_raw = json_body['discharge_macaroon']
2861 discharge_macaroon = Macaroon.deserialize(discharge_macaroon_raw)
2862 v = Verifier()
2863 v.satisfy_general(lambda c: True)
2864 v.verify(
2865 self.root_macaroon, self.macaroon_random_key, [discharge_macaroon])
2866 self.stats_increment.assert_any_call(
2867 'macaroon.refresh', key='root_macaroon')
2868
2869 def test_just_discharge_refreshed(self):2853 def test_just_discharge_refreshed(self):
2870 json_body = self.do_post()2854 json_body = self.do_post()
28712855
@@ -2878,5 +2862,3 @@
2878 v = Verifier()2862 v = Verifier()
2879 v.satisfy_general(lambda c: True)2863 v.satisfy_general(lambda c: True)
2880 v.verify(self.root_macaroon, self.macaroon_random_key, [discharge])2864 v.verify(self.root_macaroon, self.macaroon_random_key, [discharge])
2881 self.stats_increment.assert_any_call(
2882 'macaroon.refresh', key='just_discharge')
28832865
=== modified file 'src/identityprovider/auth.py'
--- src/identityprovider/auth.py 2016-07-28 16:07:45 +0000
+++ src/identityprovider/auth.py 2016-08-11 22:17:29 +0000
@@ -667,37 +667,6 @@
667 return account, info_holder667 return account, info_holder
668668
669669
670def refresh_macaroons(root_macaroon_raw, discharge_macaroon_raw):
671 """Refresh a root/discharge pair with a new discharge macaroon.
672
673 This function is deprecated; will be removed when clients stop hitting
674 the handler passing the root macaroon.
675 """
676 try:
677 root_macaroon = Macaroon.deserialize(root_macaroon_raw)
678 discharge_macaroon = Macaroon.deserialize(discharge_macaroon_raw)
679 except:
680 raise ValidationError("The received Macaroons are corrupt")
681
682 # get the raw caveat id and the decrypted deserialized info
683 raw_caveat_id, caveat_info = _get_own_caveat(root_macaroon)
684
685 account, macaroon_info = _account_from_macaroon(
686 root_macaroon, caveat_info['roothash'], discharge_macaroon)
687
688 # create the new discharge macaroon with same location, key and
689 # identifier than it's original 3rd-party caveat (so they can
690 # be matched and verified), and keeping the last_auth and expires from
691 # the original discharge macaroon
692 d = _build_discharge(
693 caveat_info['3rdparty'], raw_caveat_id, account,
694 last_auth=macaroon_info['last_auth'], expires=macaroon_info['expires'])
695
696 # return the properly prepared discharge macaroon
697 discharge = root_macaroon.prepare_for_request(d)
698 return discharge
699
700
701def refresh_discharge(discharge_macaroon_raw):670def refresh_discharge(discharge_macaroon_raw):
702 """Refresh a discharge with a new discharge macaroon."""671 """Refresh a discharge with a new discharge macaroon."""
703 try:672 try:
704673
=== modified file 'src/identityprovider/tests/test_auth.py'
--- src/identityprovider/tests/test_auth.py 2016-08-01 14:17:32 +0000
+++ src/identityprovider/tests/test_auth.py 2016-08-11 22:17:29 +0000
@@ -34,7 +34,6 @@
34 build_discharge_macaroon,34 build_discharge_macaroon,
35 build_discharge_macaroon_from_root,35 build_discharge_macaroon_from_root,
36 refresh_discharge,36 refresh_discharge,
37 refresh_macaroons,
38 validate_oauth_signature,37 validate_oauth_signature,
39)38)
40from identityprovider.login import AuthenticationError, AccountDeactivated39from identityprovider.login import AuthenticationError, AccountDeactivated
@@ -1175,135 +1174,6 @@
1175 self.assertRaises(ValidationError, _decrypt_caveat, caveat_id)1174 self.assertRaises(ValidationError, _decrypt_caveat, caveat_id)
11761175
11771176
1178class MacaroonRefreshFromRootTestCase(SSOBaseTestCase):
1179 """Test the deprecated refresh_macaroons.
1180
1181 These tests are kept separated as that function won't evolve no more.
1182 """
1183
1184 def setUp(self):
1185 super(MacaroonRefreshFromRootTestCase, self).setUp()
1186 self.root_macaroon, self.macaroon_random_key, _ = self.build_macaroon(
1187 version=0)
1188
1189 # discharge the test macaroon
1190 self.account = self.factory.make_account()
1191 self.discharge_macaroon = build_discharge_macaroon_from_root(
1192 self.account, self.root_macaroon.serialize())
1193
1194 def test_root_macaroon_corrupt(self):
1195 self.assertRaises(
1196 ValidationError, refresh_macaroons,
1197 "Corrupted macaroon", self.discharge_macaroon.serialize())
1198
1199 def test_discharge_macaroon_corrupt(self):
1200 self.assertRaises(
1201 ValidationError, refresh_macaroons,
1202 self.root_macaroon.serialize(), "Seriously corrupted macaroon")
1203
1204 def test_macaroons_dont_verify_ok(self):
1205 # just get *another* discharge so it's not for the same root macaroon
1206 other_root, _, _ = self.build_macaroon(version=0)
1207 other_discharge = build_discharge_macaroon_from_root(
1208 self.account, other_root.serialize())
1209 self.assertRaises(AuthenticationError, refresh_macaroons,
1210 self.root_macaroon.serialize(),
1211 other_discharge.serialize())
1212
1213 def test_deactivated_account(self):
1214 self.account.deactivate()
1215 self.assertRaises(AccountDeactivated, refresh_macaroons,
1216 self.root_macaroon.serialize(),
1217 self.discharge_macaroon.serialize())
1218
1219 def test_password_changed(self):
1220 self.account.set_password("a new password")
1221 self.assertRaises(AuthenticationError, refresh_macaroons,
1222 self.root_macaroon.serialize(),
1223 self.discharge_macaroon.serialize())
1224
1225 def test_password_with_no_datetime(self):
1226 # simulate an "old" account password (before we started to keep the
1227 # changed date); note that I'm bypassing the AccountPassword save(),
1228 # which will update the attribute that I want in Null! (and we confirm
1229 # that in the assert)
1230 self.account.accountpassword.date_changed = None
1231 super(AccountPassword, self.account.accountpassword).save()
1232 assert self.account.accountpassword.date_changed is None
1233
1234 # check that auths ok
1235 refresh_macaroons(self.root_macaroon.serialize(),
1236 self.discharge_macaroon.serialize())
1237
1238 def test_proper_refreshing(self):
1239 old_discharge = self.discharge_macaroon # just rename for readability
1240 service_location = settings.MACAROON_SERVICE_LOCATION
1241
1242 def get_value(search_key):
1243 for caveat in old_discharge.first_party_caveats():
1244 source, key, value = caveat.caveat_id.split("|", 2)
1245 if source == service_location and key == search_key:
1246 return value
1247
1248 # get old values from the macaroon and also change the account to see
1249 # that reflected
1250 old_last_auth = get_value('last_auth')
1251 old_expires = get_value('expires')
1252 new_mail = self.factory.make_email_for_account(
1253 self.account, status=EmailStatus.PREFERRED)
1254 self.account.displayname = "New test display name"
1255 self.account.save()
1256
1257 # call!
1258 before = now()
1259 new_discharge = refresh_macaroons(self.root_macaroon.serialize(),
1260 old_discharge.serialize())
1261 after = now()
1262
1263 # test
1264 def checker(caveat):
1265 """Assure all caveats inside the discharged macaroon are ok."""
1266 source, key, value = caveat.split("|", 2)
1267 if source != service_location:
1268 # just checking the NEW discharge one, not the root
1269 return True
1270
1271 if key == 'valid_since':
1272 valid_since = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
1273 self.assertGreater(valid_since, before)
1274 self.assertGreater(after, valid_since)
1275 return True
1276
1277 if key == 'last_auth':
1278 self.assertEqual(value, old_last_auth)
1279 return True
1280
1281 if key == 'account':
1282 expected = {
1283 'openid': self.account.openid_identifier,
1284 'email': new_mail.email,
1285 'displayname': "New test display name",
1286 'is_verified': self.account.is_verified,
1287 'username': self.account.username
1288 }
1289 actual = json.loads(base64.b64decode(value).decode("utf8"))
1290 self.assertEqual(expected, actual)
1291 return True
1292
1293 if key == 'expires':
1294 self.assertEqual(value, old_expires)
1295 return True
1296
1297 # we're not validating an SSO from the discharged macaroon, fail!
1298 return False
1299
1300 # verify using the NEW discharge macaroon
1301 v = Verifier()
1302 v.satisfy_general(checker)
1303 v.verify(self.root_macaroon, self.macaroon_random_key,
1304 [new_discharge])
1305
1306
1307class MacaroonRefreshTestCase(SSOBaseTestCase):1177class MacaroonRefreshTestCase(SSOBaseTestCase):
13081178
1309 def setUp(self):1179 def setUp(self):
@@ -1386,7 +1256,7 @@
1386 expected = {1256 expected = {
1387 'openid': self.account.openid_identifier,1257 'openid': self.account.openid_identifier,
1388 'email': new_mail.email,1258 'email': new_mail.email,
1389 'displayname': "New test display name",1259 'displayname': "New test display name",
1390 'is_verified': self.account.is_verified,1260 'is_verified': self.account.is_verified,
1391 'username': self.account.username1261 'username': self.account.username
1392 }1262 }