def test_authorize_func(self): def f(ctx, identity, op): self.assertEqual(identity.id(), 'bob') if op.entity == 'a': return False, None elif op.entity == 'b': return True, None elif op.entity == 'c': return True, [ checkers.Caveat(location='somewhere', condition='c') ] elif op.entity == 'd': return True, [ checkers.Caveat(location='somewhere', condition='d') ] else: self.fail('unexpected entity: ' + op.Entity) ops = [ macaroonbakery.Op('a', 'x'), macaroonbakery.Op('b', 'x'), macaroonbakery.Op('c', 'x'), macaroonbakery.Op('d', 'x') ] allowed, caveats = macaroonbakery.AuthorizerFunc(f).authorize( checkers.AuthContext(), macaroonbakery.SimpleIdentity('bob'), ops) self.assertEqual(allowed, [False, True, True, True]) self.assertEqual(caveats, [ checkers.Caveat(location='somewhere', condition='c'), checkers.Caveat(location='somewhere', condition='d') ])
def test_operation_deny_caveat(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ macaroonbakery.Op(entity='e1', action='read'): {'bob'}, macaroonbakery.Op(entity='e1', action='write'): {'bob'}, macaroonbakery.Op(entity='e2', action='read'): {'bob'}, }) ts = _Service('myservice', auth, ids, locator) client = _Client(locator) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') m = client.capability(ctx, ts, [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e1', action='write'), macaroonbakery.Op(entity='e2', action='read') ]) # Sanity check that we can do a write. ts.do(test_context, [[m.macaroon]], [macaroonbakery.Op(entity='e1', action='write')]) m.add_caveat(checkers.deny_caveat(['write']), None, None) # A read operation should work. ts.do(test_context, [[m.macaroon]], [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e2', action='read') ]) # A write operation should fail # even though the original macaroon allowed it. with self.assertRaises(_DischargeRequiredError): ts.do(test_context, [[m.macaroon]], [macaroonbakery.Op(entity='e1', action='write')])
def test_multiple_ops_in_id_with_version1(self): test_oven = macaroonbakery.Oven() ops = [macaroonbakery.Op('one', 'read'), macaroonbakery.Op('one', 'write'), macaroonbakery.Op('two', 'read')] m = test_oven.macaroon(macaroonbakery.BAKERY_V1, AGES, None, ops) got_ops, conds = test_oven.macaroon_ops([m.macaroon]) self.assertEquals(len(conds), 1) # time-before caveat. self.assertEquals(macaroonbakery.canonical_ops(got_ops), ops)
def test_multiple_ops(self): test_oven = macaroonbakery.Oven( ops_store=macaroonbakery.MemoryOpsStore()) ops = [macaroonbakery.Op('one', 'read'), macaroonbakery.Op('one', 'write'), macaroonbakery.Op('two', 'read')] m = test_oven.macaroon(macaroonbakery.LATEST_BAKERY_VERSION, AGES, None, ops) got_ops, conds = test_oven.macaroon_ops([m.macaroon]) self.assertEquals(len(conds), 1) # time-before caveat. self.assertEquals(macaroonbakery.canonical_ops(got_ops), ops)
def test_multiple_ops_in_id(self): test_oven = bakery.Oven() ops = [ bakery.Op('one', 'read'), bakery.Op('one', 'write'), bakery.Op('two', 'read') ] m = test_oven.macaroon(bakery.LATEST_VERSION, AGES, None, ops) got_ops, conds = test_oven.macaroon_ops([m.macaroon]) self.assertEquals(len(conds), 1) # time-before caveat. self.assertEquals(bakery.canonical_ops(got_ops), ops)
def test_authorize_with_open_access_and_no_macaroons(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer( {bakery.Op(entity='something', action='read'): {bakery.EVERYONE}}) ts = _Service('myservice', auth, ids, locator) client = _Client(locator) auth_info = client.do(test_context, ts, [ bakery.Op(entity='something', action='read'), ]) self.assertEqual(len(self._discharges), 0) self.assertIsNotNone(auth_info) self.assertIsNone(auth_info.identity) self.assertEqual(len(auth_info.macaroons), 0)
def test_authorize_with_authentication_required(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer( {macaroonbakery.Op(entity='something', action='read'): {'bob'}}) ts = _Service('myservice', auth, ids, locator) client = _Client(locator) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') auth_info = client.do( ctx, ts, [macaroonbakery.Op(entity='something', action='read')]) self.assertEqual(self._discharges, [_DischargeRecord(location='ids', user='******')]) self.assertIsNotNone(auth_info) self.assertEqual(auth_info.identity.id(), 'bob') self.assertEqual(len(auth_info.macaroons), 1)
def test_authorize_multiple_ops(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ macaroonbakery.Op(entity='something', action='read'): {'bob'}, macaroonbakery.Op(entity='otherthing', action='read'): {'bob'} }) ts = _Service('myservice', auth, ids, locator) client = _Client(locator) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') client.do(ctx, ts, [ macaroonbakery.Op(entity='something', action='read'), macaroonbakery.Op(entity='otherthing', action='read') ]) self.assertEqual(self._discharges, [_DischargeRecord(location='ids', user='******')])
def macaroon_ops(self, ms): if len(ms) == 0: raise ValueError('no macaroons provided') m_id = json.loads(ms[0].identifier_bytes.decode('utf-8')) root_key = self._root_key_store.get( base64.urlsafe_b64decode(m_id['id'].encode('utf-8'))) v = Verifier() class NoValidationOnFirstPartyCaveat(FirstPartyCaveatVerifierDelegate): def verify_first_party_caveat(self, verifier, caveat, signature): return True v.first_party_caveat_verifier_delegate = \ NoValidationOnFirstPartyCaveat() ok = v.verify(macaroon=ms[0], key=root_key, discharge_macaroons=ms[1:]) if not ok: raise macaroonbakery.VerificationError('invalid signature') conditions = [] for m in ms: cavs = m.first_party_caveats() for cav in cavs: conditions.append(cav.caveat_id_bytes.decode('utf-8')) ops = [] for op in m_id['ops']: ops.append(macaroonbakery.Op(entity=op[0], action=op[1])) return ops, conditions
def server_get(url, request): ctx = checkers.AuthContext() test_ops = [bakery.Op(entity='test-op', action='read')] auth_checker = server_bakery.checker.auth( httpbakery.extract_macaroons(request.headers)) try: auth_checker.allow(ctx, test_ops) resp = response(status_code=200, content='done') except bakery.PermissionDenied: caveats = [ checkers.Caveat(location='http://0.3.2.1', condition='is-ok') ] m = server_bakery.oven.macaroon(version=bakery.LATEST_VERSION, expiry=datetime.utcnow() + timedelta(days=1), caveats=caveats, ops=test_ops) content, headers = httpbakery.discharge_required_response( m, '/', 'test', 'message') resp = response( status_code=401, content=content, headers=headers, ) return request.hooks['response'][0](resp)
def test_auth_with_identity_from_context(self): locator = _DischargerLocator() ids = _BasicAuthIdService() auth = _OpAuthorizer({ macaroonbakery.Op(entity='e1', action='read'): {'sherlock'}, macaroonbakery.Op(entity='e2', action='read'): {'bob'}, }) ts = _Service('myservice', auth, ids, locator) # Check that we can perform the ops with basic auth in the # context. ctx = _context_with_basic_auth(test_context, 'sherlock', 'holmes') auth_info = _Client(locator).do( ctx, ts, [macaroonbakery.Op(entity='e1', action='read')]) self.assertEqual(auth_info.identity.id(), 'sherlock') self.assertEqual(len(auth_info.macaroons), 0)
def visit(url, request): if request.headers.get('Accept') == 'application/json': return {'status_code': 200, 'content': {'agent': request.url}} cs = SimpleCookie() cookies = request.headers.get('Cookie') if cookies is not None: cs.load(str(cookies)) public_key = None for c in cs: if c == 'agent-login': json_cookie = json.loads( base64.b64decode(cs[c].value).decode('utf-8')) public_key = bakery.PublicKey.deserialize( json_cookie.get('public_key')) ms = httpbakery.extract_macaroons(request.headers) if len(ms) == 0: b = bakery.Bakery(key=discharge_key) m = b.oven.macaroon( version=bakery.LATEST_VERSION, expiry=datetime.utcnow() + timedelta(days=1), caveats=[ bakery.local_third_party_caveat( public_key, version=httpbakery.request_version( request.headers)) ], ops=[bakery.Op(entity='agent', action='login')]) content, headers = httpbakery.discharge_required_response( m, '/', 'test', 'message') resp = response(status_code=401, content=content, headers=headers) return request.hooks['response'][0](resp) return {'status_code': 200, 'content': {'agent-login': True}}
def authorize_with_tp_discharge(ctx, id, op): if (id is not None and id.id() == 'bob' and op == bakery.Op(entity='something', action='read')): return True, [ checkers.Caveat(condition='question', location='other third party') ] return False, None
def test_authorization_denied(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = bakery.ClosedAuthorizer() ts = _Service('myservice', auth, ids, locator) client = _Client(locator) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') with self.assertRaises(bakery.PermissionDenied): client.do(ctx, ts, [bakery.Op(entity='something', action='read')])
def test_capability(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer( {macaroonbakery.Op(entity='something', action='read'): {'bob'}}) ts = _Service('myservice', auth, ids, locator) client = _Client(locator) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') m = client.discharged_capability( ctx, ts, [macaroonbakery.Op(entity='something', action='read')]) # Check that we can exercise the capability directly on the service # with no discharging required. auth_info = ts.do( test_context, [m], [macaroonbakery.Op(entity='something', action='read')]) self.assertIsNotNone(auth_info) self.assertIsNone(auth_info.identity) self.assertEqual(len(auth_info.macaroons), 1) self.assertEqual(auth_info.macaroons[0][0].identifier_bytes, m[0].identifier_bytes)
def login(url, request): b = bakery.Bakery(key=discharge_key) m = b.oven.macaroon( version=bakery.LATEST_VERSION, expiry=datetime.utcnow() + timedelta(days=1), caveats=[ bakery.local_third_party_caveat( key.public_key, version=httpbakery.request_version(request.headers)) ], ops=[bakery.Op(entity='agent', action='login')]) return {'status_code': 200, 'content': {'macaroon': m.to_dict()}}
def test_multiple_capabilities(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ macaroonbakery.Op(entity='e1', action='read'): {'alice'}, macaroonbakery.Op(entity='e2', action='read'): {'bob'}, }) ts = _Service('myservice', auth, ids, locator) # Acquire two capabilities as different users and check # that we can combine them together to do both operations # at once. ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'alice') m1 = _Client(locator).discharged_capability( ctx, ts, [macaroonbakery.Op(entity='e1', action='read')]) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') m2 = _Client(locator).discharged_capability( ctx, ts, [macaroonbakery.Op(entity='e2', action='read')]) self.assertEqual(self._discharges, [ _DischargeRecord(location='ids', user='******'), _DischargeRecord(location='ids', user='******'), ]) auth_info = ts.do(test_context, [m1, m2], [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e2', action='read') ]) self.assertIsNotNone(auth_info) self.assertIsNone(auth_info.identity) self.assertEqual(len(auth_info.macaroons), 2) self.assertEqual(auth_info.macaroons[0][0].identifier_bytes, m1[0].identifier_bytes) self.assertEqual(auth_info.macaroons[1][0].identifier_bytes, m2[0].identifier_bytes)
def _decode_macaroon_id(id): storage_id = b'' base64_decoded = False first = id[:1] if first == b'A': # The first byte is not a version number and it's 'A', which is the # base64 encoding of the top 6 bits (all zero) of the version number 2 # or 3, so we assume that it's the base64 encoding of a new-style # macaroon id, so we base64 decode it. # # Note that old-style ids always start with an ASCII character >= 4 # (> 32 in fact) so this logic won't be triggered for those. try: dec = utils.raw_b64decode(id.decode('utf-8')) # Set the id only on success. id = dec base64_decoded = True except: # if it's a bad encoding, we'll get an error which is fine pass # Trim any extraneous information from the id before retrieving # it from storage, including the UUID that's added when # creating macaroons to make all macaroons unique even if # they're using the same root key. first = six.byte2int(id[:1]) if first == macaroonbakery.BAKERY_V2: # Skip the UUID at the start of the id. storage_id = id[1 + 16:] if first == macaroonbakery.BAKERY_V3: try: id1 = id_pb2.MacaroonId.FromString(id[1:]) except google.protobuf.message.DecodeError: raise macaroonbakery.VerificationError( 'no operations found in macaroon') if len(id1.ops) == 0 or len(id1.ops[0].actions) == 0: raise macaroonbakery.VerificationError( 'no operations found in macaroon') ops = [] for op in id1.ops: for action in op.actions: ops.append(macaroonbakery.Op(op.entity, action)) return id1.storageId, ops if not base64_decoded and _is_lower_case_hex_char(first): # It's an old-style id, probably with a hyphenated UUID. # so trim that off. last = id.rfind(b'-') if last >= 0: storage_id = id[0:last] return storage_id, [macaroonbakery.LOGIN_OP]
def test_partially_authorized_request(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ macaroonbakery.Op(entity='e1', action='read'): {'alice'}, macaroonbakery.Op(entity='e2', action='read'): {'bob'}, }) ts = _Service('myservice', auth, ids, locator) # Acquire a capability for e1 but rely on authentication to # authorize e2. ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'alice') m = _Client(locator).discharged_capability( ctx, ts, [macaroonbakery.Op(entity='e1', action='read')]) client = _Client(locator) client.add_macaroon(ts, 'authz', m) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') client.discharged_capability(ctx, ts, [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e2', action='read') ])
def test_huge_number_of_ops_gives_small_macaroon(self): test_oven = bakery.Oven(ops_store=bakery.MemoryOpsStore()) ops = [] for i in range(30000): ops.append( bakery.Op(entity='entity' + str(i), action='action' + str(i))) m = test_oven.macaroon(bakery.LATEST_VERSION, AGES, None, ops) got_ops, conds = test_oven.macaroon_ops([m.macaroon]) self.assertEquals(len(conds), 1) # time-before caveat. self.assertEquals(bakery.canonical_ops(got_ops), bakery.canonical_ops(ops)) data = m.serialize_json() self.assertLess(len(data), 300)
def test_acl_authorizer(self): ctx = checkers.AuthContext() tests = [ ('no ops, no problem', bakery.ACLAuthorizer(allow_public=True, get_acl=lambda x, y: []), None, [], []), ('identity that does not implement ACLIdentity; ' 'user should be denied except for everyone group', bakery.ACLAuthorizer( allow_public=True, get_acl=lambda ctx, op: [bakery.EVERYONE] if op.entity == 'a' else ['alice'], ), SimplestIdentity('bob'), [bakery.Op(entity='a', action='a'), bakery.Op(entity='b', action='b')], [True, False]), ('identity that does not implement ACLIdentity with user == Id; ' 'user should be denied except for everyone group', bakery.ACLAuthorizer( allow_public=True, get_acl=lambda ctx, op: [bakery.EVERYONE] if op.entity == 'a' else ['bob'], ), SimplestIdentity('bob'), [bakery.Op(entity='a', action='a'), bakery.Op(entity='b', action='b')], [True, False]), ('permission denied for everyone without AllowPublic', bakery.ACLAuthorizer( allow_public=False, get_acl=lambda x, y: [bakery.EVERYONE], ), SimplestIdentity('bob'), [bakery.Op(entity='a', action='a')], [False]), ('permission granted to anyone with no identity with AllowPublic', bakery.ACLAuthorizer( allow_public=True, get_acl=lambda x, y: [bakery.EVERYONE], ), None, [bakery.Op(entity='a', action='a')], [True]) ] for test in tests: allowed, caveats = test[1].authorize(ctx, test[2], test[3]) self.assertEqual(len(caveats), 0) self.assertEqual(allowed, test[4])
def test_auth_with_third_party_caveats(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) # We make an authorizer that requires a third party discharge # when authorizing. def authorize_with_tp_discharge(ctx, id, op): if (id is not None and id.id() == 'bob' and op == macaroonbakery.Op(entity='something', action='read')): return True, [ checkers.Caveat(condition='question', location='other third party') ] return False, None auth = macaroonbakery.AuthorizerFunc(authorize_with_tp_discharge) ts = _Service('myservice', auth, ids, locator) class _LocalDischargeChecker(macaroonbakery.ThirdPartyCaveatChecker): def check_third_party_caveat(_, ctx, info): if info.condition != 'question': raise ValueError('third party condition not recognized') self._discharges.append( _DischargeRecord(location='other third party', user=ctx.get(_DISCHARGE_USER_KEY))) return [] locator['other third party'] = _Discharger( key=macaroonbakery.generate_key(), checker=_LocalDischargeChecker(), locator=locator, ) client = _Client(locator) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') client.do(ctx, ts, [macaroonbakery.Op(entity='something', action='read')]) self.assertEqual(self._discharges, [ _DischargeRecord(location='ids', user='******'), _DischargeRecord(location='other third party', user='******') ])
def test_allow_any(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ bakery.Op(entity='e1', action='read'): {'alice'}, bakery.Op(entity='e2', action='read'): {'bob'}, }) ts = _Service('myservice', auth, ids, locator) # Acquire a capability for e1 but rely on authentication to # authorize e2. ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'alice') m = _Client(locator).discharged_capability(ctx, ts, [ bakery.Op(entity='e1', action='read'), ]) client = _Client(locator) client.add_macaroon(ts, 'authz', m) self._discharges = [] ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') with self.assertRaises(_DischargeRequiredError): client.do_any(ctx, ts, [ bakery.LOGIN_OP, bakery.Op(entity='e1', action='read'), bakery.Op(entity='e1', action='read') ]) self.assertEqual(len(self._discharges), 0) # Log in as bob. _, err = client.do(ctx, ts, [bakery.LOGIN_OP]) # All the previous actions should now be allowed. auth_info, allowed = client.do_any(ctx, ts, [ bakery.LOGIN_OP, bakery.Op(entity='e1', action='read'), bakery.Op(entity='e1', action='read'), ]) self.assertEqual(auth_info.identity.id(), 'bob') self.assertEqual(len(auth_info.macaroons), 2) self.assertEqual(allowed, [True, True, True])
def test_ops_stored_only_once(self): st = macaroonbakery.MemoryOpsStore() test_oven = macaroonbakery.Oven(ops_store=st) ops = [macaroonbakery.Op('one', 'read'), macaroonbakery.Op('one', 'write'), macaroonbakery.Op('two', 'read')] m = test_oven.macaroon(macaroonbakery.LATEST_BAKERY_VERSION, AGES, None, ops) got_ops, conds = test_oven.macaroon_ops([m.macaroon]) self.assertEquals(macaroonbakery.canonical_ops(got_ops), macaroonbakery.canonical_ops(ops)) # Make another macaroon containing the same ops in a different order. ops = [macaroonbakery.Op('one', 'write'), macaroonbakery.Op('one', 'read'), macaroonbakery.Op('one', 'read'), macaroonbakery.Op('two', 'read')] test_oven.macaroon(macaroonbakery.LATEST_BAKERY_VERSION, AGES, None, ops) self.assertEquals(len(st._store), 1)
def test_capability_combines_first_party_caveats(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ macaroonbakery.Op(entity='e1', action='read'): {'alice'}, macaroonbakery.Op(entity='e2', action='read'): {'bob'} }) ts = _Service('myservice', auth, ids, locator) # Acquire two capabilities as different users, add some first party # caveats that we can combine them together into a single capability # capable of both operations. ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'alice') m1 = _Client(locator).capability( ctx, ts, [macaroonbakery.Op(entity='e1', action='read')]) m1.macaroon.add_first_party_caveat('true 1') m1.macaroon.add_first_party_caveat('true 2') ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') m2 = _Client(locator).capability( ctx, ts, [macaroonbakery.Op(entity='e2', action='read')]) m2.macaroon.add_first_party_caveat('true 3') m2.macaroon.add_first_party_caveat('true 4') client = _Client(locator) client.add_macaroon(ts, 'authz1', [m1.macaroon]) client.add_macaroon(ts, 'authz2', [m2.macaroon]) m = client.capability(test_context, ts, [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e2', action='read') ]) self.assertEqual(_macaroon_conditions(m.macaroon.caveats, False), [ 'true 1', 'true 2', 'true 3', 'true 4', ])
def test_canonical_ops(self): canonical_ops_tests = ( ('empty array', [], []), ('one element', [macaroonbakery.Op('a', 'a')], [macaroonbakery.Op('a', 'a')]), ('all in order', [macaroonbakery.Op('a', 'a'), macaroonbakery.Op('a', 'b'), macaroonbakery.Op('c', 'c')], [macaroonbakery.Op('a', 'a'), macaroonbakery.Op('a', 'b'), macaroonbakery.Op('c', 'c')]), ('out of order', [macaroonbakery.Op('c', 'c'), macaroonbakery.Op('a', 'b'), macaroonbakery.Op('a', 'a')], [macaroonbakery.Op('a', 'a'), macaroonbakery.Op('a', 'b'), macaroonbakery.Op('c', 'c')]), ('with duplicates', [macaroonbakery.Op('c', 'c'), macaroonbakery.Op('a', 'b'), macaroonbakery.Op('a', 'a'), macaroonbakery.Op('c', 'a'), macaroonbakery.Op('c', 'b'), macaroonbakery.Op('c', 'c'), macaroonbakery.Op('a', 'a')], [macaroonbakery.Op('a', 'a'), macaroonbakery.Op('a', 'b'), macaroonbakery.Op('c', 'a'), macaroonbakery.Op('c', 'b'), macaroonbakery.Op('c', 'c')]), ('make sure we\'ve got the fields right', [macaroonbakery.Op(entity='read', action='two'), macaroonbakery.Op(entity='read', action='one'), macaroonbakery.Op(entity='write', action='one')], [macaroonbakery.Op(entity='read', action='one'), macaroonbakery.Op(entity='read', action='two'), macaroonbakery.Op(entity='write', action='one')]) ) for about, ops, expected in canonical_ops_tests: new_ops = copy.copy(ops) canonical_ops = macaroonbakery.canonical_ops(new_ops) self.assertEquals(canonical_ops, expected) # Verify that the original array isn't changed. self.assertEquals(new_ops, ops)
def test_capability_multiple_entities(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ macaroonbakery.Op(entity='e1', action='read'): {'bob'}, macaroonbakery.Op(entity='e2', action='read'): {'bob'}, macaroonbakery.Op(entity='e3', action='read'): {'bob'}, }) ts = _Service('myservice', auth, ids, locator) client = _Client(locator) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') m = client.discharged_capability(ctx, ts, [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e2', action='read'), macaroonbakery.Op(entity='e3', action='read') ]) self.assertEqual(self._discharges, [_DischargeRecord(location='ids', user='******')]) # Check that we can exercise the capability directly on the service # with no discharging required. ts.do(test_context, [m], [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e2', action='read'), macaroonbakery.Op(entity='e3', action='read') ]) # Check that we can exercise the capability to act on a subset of # the operations. ts.do(test_context, [m], [ macaroonbakery.Op(entity='e2', action='read'), macaroonbakery.Op(entity='e3', action='read') ]) ts.do(test_context, [m], [macaroonbakery.Op(entity='e3', action='read')])
from http.server import HTTPServer, BaseHTTPRequestHandler import threading from httmock import ( HTTMock, urlmatch ) import requests from six.moves.urllib.parse import parse_qs import macaroonbakery as bakery import macaroonbakery.httpbakery as httpbakery import macaroonbakery.checkers as checkers AGES = datetime.datetime.utcnow() + datetime.timedelta(days=1) TEST_OP = bakery.Op(entity='test', action='test') class TestClient(TestCase): def test_single_service_first_party(self): b = new_bakery('loc', None, None) def handler(*args): GetHandler(b, None, None, None, None, *args) try: httpd = HTTPServer(('', 0), handler) thread = threading.Thread(target=httpd.serve_forever) thread.start() srv_macaroon = b.oven.macaroon( version=bakery.LATEST_VERSION, expiry=AGES, caveats=None, ops=[TEST_OP])
def test_combine_capabilities(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ macaroonbakery.Op(entity='e1', action='read'): {'alice'}, macaroonbakery.Op(entity='e2', action='read'): {'bob'}, macaroonbakery.Op(entity='e3', action='read'): {'bob', 'alice'}, }) ts = _Service('myservice', auth, ids, locator) # Acquire two capabilities as different users and check # that we can combine them together into a single capability # capable of both operations. ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'alice') m1 = _Client(locator).discharged_capability(ctx, ts, [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e3', action='read') ]) ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob') m2 = _Client(locator).discharged_capability( ctx, ts, [macaroonbakery.Op(entity='e2', action='read')]) m = ts.capability(test_context, [m1, m2], [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e2', action='read'), macaroonbakery.Op(entity='e3', action='read') ]) ts.do(test_context, [[m.macaroon]], [ macaroonbakery.Op(entity='e1', action='read'), macaroonbakery.Op(entity='e2', action='read'), macaroonbakery.Op(entity='e3', action='read') ])
def test_first_party_caveat_squashing(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ macaroonbakery.Op(entity='e1', action='read'): {'alice'}, macaroonbakery.Op(entity='e2', action='read'): {'alice'}, }) ts = _Service('myservice', auth, ids, locator) tests = [('duplicates removed', [ checkers.Caveat(condition='true 1', namespace='testns'), checkers.Caveat(condition='true 2', namespace='testns'), checkers.Caveat(condition='true 1', namespace='testns'), checkers.Caveat(condition='true 1', namespace='testns'), checkers.Caveat(condition='true 3', namespace='testns'), ], [ checkers.Caveat(condition='true 1', namespace='testns'), checkers.Caveat(condition='true 2', namespace='testns'), checkers.Caveat(condition='true 3', namespace='testns'), ]), ('earliest time before', [ checkers.time_before_caveat(epoch + timedelta(days=1)), checkers.Caveat(condition='true 1', namespace='testns'), checkers.time_before_caveat(epoch + timedelta(days=0, hours=1)), checkers.time_before_caveat( epoch + timedelta(days=0, hours=0, minutes=5)), ], [ checkers.time_before_caveat( epoch + timedelta(days=0, hours=0, minutes=5)), checkers.Caveat(condition='true 1', namespace='testns'), ]), ('operations and declared caveats removed', [ checkers.deny_caveat(['foo']), checkers.allow_caveat(['read', 'write']), checkers.declared_caveat('username', 'bob'), checkers.Caveat(condition='true 1', namespace='testns'), ], [ checkers.Caveat(condition='true 1', namespace='testns'), ])] for test in tests: print(test[0]) # Make a first macaroon with all the required first party caveats. ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'alice') m1 = _Client(locator).capability( ctx, ts, [macaroonbakery.Op(entity='e1', action='read')]) m1.add_caveats(test[1], None, None) # Make a second macaroon that's not used to check that it's # caveats are not added. m2 = _Client(locator).capability( ctx, ts, [macaroonbakery.Op(entity='e1', action='read')]) m2.add_caveat( checkers.Caveat(condition='true notused', namespace='testns'), None, None) client = _Client(locator) client.add_macaroon(ts, 'authz1', [m1.macaroon]) client.add_macaroon(ts, 'authz2', [m2.macaroon]) m3 = client.capability( test_context, ts, [macaroonbakery.Op(entity='e1', action='read')]) self.assertEqual(_macaroon_conditions(m3.macaroon.caveats, False), _resolve_caveats(m3.namespace, test[2]))