def get_discharge(ctx, cav, payload): checker = common.ThirdPartyCheckerWithCaveats([ checkers.declared_caveat('foo', 'a'), checkers.declared_caveat('arble', 'b') ]) return macaroonbakery.discharge(ctx, cav.caveat_id_bytes, payload, third_party.oven.key, checker, third_party.oven.locator)
def check_third_party_caveat(self, ctx, cav_info): if cav_info.condition == b'x': return [checkers.declared_caveat('foo', 'fooval1')] if cav_info.condition == b'y': return [ checkers.declared_caveat('foo', 'fooval2'), checkers.declared_caveat('baz', 'bazval') ] raise common.ThirdPartyCaveatCheckFailed('not matched')
def get_discharge(ctx, cav, payload): checker = common.ThirdPartyCheckerWithCaveats([ checkers.declared_caveat('foo', 'a'), checkers.declared_caveat('arble', 'b'), ]) # Sneaky client adds a first party caveat. m = macaroonbakery.discharge(ctx, cav.caveat_id_bytes, payload, third_party.oven.key, checker, third_party.oven.locator) m.add_caveat(checkers.declared_caveat('foo', 'c'), None, None) return m
def get_discharge(cav, payload): checker = common.ThirdPartyCheckerWithCaveats([ checkers.declared_caveat('foo', 'a'), checkers.declared_caveat('arble', 'b') ]) return bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, third_party.oven.key, checker, third_party.oven.locator, )
def get_discharge(cav, payload): checker = common.ThirdPartyCheckerWithCaveats([ checkers.declared_caveat('foo', 'a'), checkers.declared_caveat('arble', 'b'), ]) # Sneaky client adds a first party caveat. m = bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, third_party.oven.key, checker, third_party.oven.locator, ) m.add_caveat(checkers.declared_caveat('foo', 'c'), None, None) return m
def check_third_party_caveat(self, ctx, info): if info.condition != 'is-authenticated-user': raise bakery.CaveatNotRecognizedError( 'third party condition not recognized') username = ctx.get(_DISCHARGE_USER_KEY, '') if username == '': raise bakery.ThirdPartyCaveatCheckFailed('no current user') self._test._discharges.append( _DischargeRecord(location=self._location, user=username)) return [checkers.declared_caveat('username', username)]
def _check_need_declared(ctx, cav_info, checker): arg = cav_info.condition.decode('utf-8') i = arg.find(' ') if i <= 0: raise macaroonbakery.VerificationError( 'need-declared caveat requires an argument, got %q'.format(arg)) need_declared = arg[0:i].split(',') for d in need_declared: if d == '': raise macaroonbakery.VerificationError('need-declared caveat with ' 'empty required attribute') if len(need_declared) == 0: raise macaroonbakery.VerificationError('need-declared caveat with no ' 'required attributes') cav_info = cav_info._replace(condition=arg[i + 1:].encode('utf-8')) caveats = checker.check_third_party_caveat(ctx, cav_info) declared = {} for cav in caveats: if cav.location is not None and cav.location != '': continue # Note that we ignore the error. We allow the service to # generate caveats that we don't understand here. try: cond, arg = checkers.parse_caveat(cav.condition) except ValueError: continue if cond != checkers.COND_DECLARED: continue parts = arg.split() if len(parts) != 2: raise macaroonbakery.VerificationError('declared caveat has no ' 'value') declared[parts[0]] = True # Add empty declarations for everything mentioned in need-declared # that was not actually declared. for d in need_declared: if not declared.get(d, False): caveats.append(checkers.declared_caveat(d, '')) return caveats
def _check_need_declared(ctx, cav_info, checker): arg = cav_info.condition.decode('utf-8') i = arg.find(' ') if i <= 0: raise bakery.VerificationError( 'need-declared caveat requires an argument, got %q'.format(arg), ) need_declared = arg[0:i].split(',') for d in need_declared: if d == '': raise bakery.VerificationError('need-declared caveat with empty required attribute') if len(need_declared) == 0: raise bakery.VerificationError('need-declared caveat with no required attributes') cav_info = cav_info._replace(condition=arg[i + 1:].encode('utf-8')) caveats = checker.check_third_party_caveat(ctx, cav_info) declared = {} for cav in caveats: if cav.location is not None and cav.location != '': continue # Note that we ignore the error. We allow the service to # generate caveats that we don't understand here. try: cond, arg = checkers.parse_caveat(cav.condition) except ValueError: continue if cond != checkers.COND_DECLARED: continue parts = arg.split() if len(parts) != 2: raise bakery.VerificationError('declared caveat has no value') declared[parts[0]] = True # Add empty declarations for everything mentioned in need-declared # that was not actually declared. for d in need_declared: if not declared.get(d, False): caveats.append(checkers.declared_caveat(d, '')) return caveats
def test_first_party_caveat_squashing(self): locator = _DischargerLocator() ids = _IdService('ids', locator, self) auth = _OpAuthorizer({ bakery.Op(entity='e1', action='read'): {'alice'}, bakery.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, [bakery.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, [bakery.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, [bakery.Op(entity='e1', action='read')]) self.assertEqual( _macaroon_conditions(m3.macaroon.caveats, False), _resolve_caveats(m3.namespace, test[2]))
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]))
def test_checkers(self): tests = [ ('nothing in context, no extra checkers', [ ('something', 'caveat "something" not satisfied: caveat not recognized'), ('', 'cannot parse caveat "": empty caveat'), (' hello', 'cannot parse caveat " hello": caveat starts with' ' space character'), ], None), ('one failed caveat', [ ('t:a aval', None), ('t:b bval', None), ('t:a wrong', 'caveat "t:a wrong" not satisfied: wrong arg'), ], None), ('time from clock', [ (checkers.time_before_caveat( datetime.utcnow() + timedelta(0, 1)).condition, None), (checkers.time_before_caveat(NOW).condition, 'caveat "time-before 2006-01-02T15:04:05.000123Z" ' 'not satisfied: macaroon has expired'), (checkers.time_before_caveat(NOW - timedelta(0, 1)).condition, 'caveat "time-before 2006-01-02T15:04:04.000123Z" ' 'not satisfied: macaroon has expired'), ('time-before bad-date', 'caveat "time-before bad-date" not satisfied: ' 'cannot parse "bad-date" as RFC 3339'), (checkers.time_before_caveat(NOW).condition + " ", 'caveat "time-before 2006-01-02T15:04:05.000123Z " ' 'not satisfied: ' 'cannot parse "2006-01-02T15:04:05.000123Z " as RFC 3339'), ], lambda x: checkers.context_with_clock(ctx, TestClock())), ('real time', [ (checkers.time_before_caveat(datetime( year=2010, month=1, day=1)).condition, 'caveat "time-before 2010-01-01T00:00:00.000000Z" not ' 'satisfied: macaroon has expired'), (checkers.time_before_caveat(datetime( year=3000, month=1, day=1)).condition, None), ], None), ('declared, no entries', [ (checkers.declared_caveat('a', 'aval').condition, 'caveat "declared a aval" not satisfied: got a=null, ' 'expected "aval"'), (checkers.COND_DECLARED, 'caveat "declared" not satisfied: ' 'declared caveat has no value'), ], None), ('declared, some entries', [ (checkers.declared_caveat('a', 'aval').condition, None), (checkers.declared_caveat('b', 'bval').condition, None), (checkers.declared_caveat('spc', ' a b').condition, None), (checkers.declared_caveat('a', 'bval').condition, 'caveat "declared a bval" not satisfied: ' 'got a="aval", expected "bval"'), (checkers.declared_caveat('a', ' aval').condition, 'caveat "declared a aval" not satisfied: ' 'got a="aval", expected " aval"'), (checkers.declared_caveat('spc', 'a b').condition, 'caveat "declared spc a b" not satisfied: ' 'got spc=" a b", expected "a b"'), (checkers.declared_caveat('', 'a b').condition, 'caveat "error invalid caveat \'declared\' key """ ' 'not satisfied: bad caveat'), (checkers.declared_caveat('a b', 'a b').condition, 'caveat "error invalid caveat \'declared\' key "a b"" ' 'not satisfied: bad caveat'), ], lambda x: checkers.context_with_declared(x, { 'a': 'aval', 'b': 'bval', 'spc': ' a b'})), ] checker = checkers.Checker() checker.namespace().register('testns', 't') checker.register('a', 'testns', arg_checker(self, 't:a', 'aval')) checker.register('b', 'testns', arg_checker(self, 't:b', 'bval')) ctx = checkers.AuthContext() for test in tests: print(test[0]) if test[2] is not None: ctx1 = test[2](ctx) else: ctx1 = ctx for check in test[1]: err = checker.check_first_party_caveat(ctx1, check[0]) if check[1] is not None: self.assertEqual(err, check[1]) else: self.assertIsNone(err)
def test_infer_declared(self): tests = [ ('no macaroons', [], {}, None), ('single macaroon with one declaration', [ [checkers.Caveat(condition='declared foo bar')] ], {'foo': 'bar'}, None), ('only one argument to declared', [ [checkers.Caveat(condition='declared foo')] ], {}, None), ('spaces in value', [ [checkers.Caveat(condition='declared foo bar bloggs')] ], {'foo': 'bar bloggs'}, None), ('attribute with declared prefix', [ [checkers.Caveat(condition='declaredccf foo')] ], {}, None), ('several macaroons with different declares', [ [ checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('b', 'bval') ], [ checkers.declared_caveat('c', 'cval'), checkers.declared_caveat('d', 'dval') ] ], {'a': 'aval', 'b': 'bval', 'c': 'cval', 'd': 'dval'}, None), ('duplicate values', [ [ checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('b', 'bval') ], [ checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('b', 'bval'), checkers.declared_caveat('c', 'cval'), checkers.declared_caveat('d', 'dval') ] ], {'a': 'aval', 'b': 'bval', 'c': 'cval', 'd': 'dval'}, None), ('conflicting values', [ [ checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('a', 'conflict'), checkers.declared_caveat('b', 'bval') ], [ checkers.declared_caveat('a', 'conflict'), checkers.declared_caveat('b', 'another conflict'), checkers.declared_caveat('c', 'cval'), checkers.declared_caveat('d', 'dval') ] ], {'c': 'cval', 'd': 'dval'}, None), ('third party caveats ignored', [ [checkers.Caveat(condition='declared a no conflict', location='location')], [checkers.declared_caveat('a', 'aval')] ], {'a': 'aval'}, None), ('unparseable caveats ignored', [ [checkers.Caveat(condition=' bad')], [checkers.declared_caveat('a', 'aval')] ], {'a': 'aval'}, None), ('infer with namespace', [ [ checkers.declared_caveat('a', 'aval'), caveat_with_ns(checkers.declared_caveat('a', 'aval'), 'testns'), ] ], {'a': 'aval'}, None), ] for test in tests: uri_to_prefix = test[3] if uri_to_prefix is None: uri_to_prefix = {checkers.STD_NAMESPACE: ''} ns = checkers.Namespace(uri_to_prefix) print(test[0]) ms = [] for i, caveats in enumerate(test[1]): m = Macaroon(key=None, identifier=six.int2byte(i), location='', version=MACAROON_V2) for cav in caveats: cav = ns.resolve_caveat(cav) if cav.location == '': m.add_first_party_caveat(cav.condition) else: m.add_third_party_caveat(cav.location, None, cav.condition) ms.append(m) self.assertEqual(checkers.infer_declared(ms), test[2])
def test_checkers(self): tests = [ ('nothing in context, no extra checkers', [ ('something', 'caveat "something" not satisfied: caveat not recognized'), ('', 'cannot parse caveat "": empty caveat'), (' hello', 'cannot parse caveat " hello": caveat starts with' ' space character'), ], None), ('one failed caveat', [ ('t:a aval', None), ('t:b bval', None), ('t:a wrong', 'caveat "t:a wrong" not satisfied: wrong arg'), ], None), ('time from clock', [ (checkers.time_before_caveat(datetime.utcnow() + timedelta(0, 1)).condition, None), (checkers.time_before_caveat(NOW).condition, 'caveat "time-before 2006-01-02T15:04:05.000123Z" ' 'not satisfied: macaroon has expired'), (checkers.time_before_caveat(NOW - timedelta(0, 1)).condition, 'caveat "time-before 2006-01-02T15:04:04.000123Z" ' 'not satisfied: macaroon has expired'), ('time-before bad-date', 'caveat "time-before bad-date" not satisfied: ' 'cannot parse "bad-date" as RFC 3339'), (checkers.time_before_caveat(NOW).condition + " ", 'caveat "time-before 2006-01-02T15:04:05.000123Z " ' 'not satisfied: ' 'cannot parse "2006-01-02T15:04:05.000123Z " as RFC 3339'), ], lambda x: checkers.context_with_clock(ctx, TestClock())), ('real time', [ (checkers.time_before_caveat( datetime(year=2010, month=1, day=1)).condition, 'caveat "time-before 2010-01-01T00:00:00.000000Z" not ' 'satisfied: macaroon has expired'), (checkers.time_before_caveat( datetime(year=3000, month=1, day=1)).condition, None), ], None), ('declared, no entries', [ (checkers.declared_caveat('a', 'aval').condition, 'caveat "declared a aval" not satisfied: got a=null, ' 'expected "aval"'), (checkers.COND_DECLARED, 'caveat "declared" not satisfied: ' 'declared caveat has no value'), ], None), ('declared, some entries', [ (checkers.declared_caveat('a', 'aval').condition, None), (checkers.declared_caveat('b', 'bval').condition, None), (checkers.declared_caveat('spc', ' a b').condition, None), (checkers.declared_caveat('a', 'bval').condition, 'caveat "declared a bval" not satisfied: ' 'got a="aval", expected "bval"'), (checkers.declared_caveat('a', ' aval').condition, 'caveat "declared a aval" not satisfied: ' 'got a="aval", expected " aval"'), (checkers.declared_caveat('spc', 'a b').condition, 'caveat "declared spc a b" not satisfied: ' 'got spc=" a b", expected "a b"'), (checkers.declared_caveat('', 'a b').condition, 'caveat "error invalid caveat \'declared\' key """ ' 'not satisfied: bad caveat'), (checkers.declared_caveat('a b', 'a b').condition, 'caveat "error invalid caveat \'declared\' key "a b"" ' 'not satisfied: bad caveat'), ], lambda x: checkers.context_with_declared(x, { 'a': 'aval', 'b': 'bval', 'spc': ' a b' })), ] checker = checkers.Checker() checker.namespace().register('testns', 't') checker.register('a', 'testns', arg_checker(self, 't:a', 'aval')) checker.register('b', 'testns', arg_checker(self, 't:b', 'bval')) ctx = checkers.AuthContext() for test in tests: print(test[0]) if test[2] is not None: ctx1 = test[2](ctx) else: ctx1 = ctx for check in test[1]: err = checker.check_first_party_caveat(ctx1, check[0]) if check[1] is not None: self.assertEqual(err, check[1]) else: self.assertIsNone(err)
def test_infer_declared(self): tests = [ ('no macaroons', [], {}, None), ('single macaroon with one declaration', [[checkers.Caveat(condition='declared foo bar')]], { 'foo': 'bar' }, None), ('only one argument to declared', [[checkers.Caveat(condition='declared foo')]], {}, None), ('spaces in value', [[checkers.Caveat(condition='declared foo bar bloggs')]], { 'foo': 'bar bloggs' }, None), ('attribute with declared prefix', [[checkers.Caveat(condition='declaredccf foo')]], {}, None), ('several macaroons with different declares', [[ checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('b', 'bval') ], [ checkers.declared_caveat('c', 'cval'), checkers.declared_caveat('d', 'dval') ]], { 'a': 'aval', 'b': 'bval', 'c': 'cval', 'd': 'dval' }, None), ('duplicate values', [[ checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('b', 'bval') ], [ checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('b', 'bval'), checkers.declared_caveat('c', 'cval'), checkers.declared_caveat('d', 'dval') ]], { 'a': 'aval', 'b': 'bval', 'c': 'cval', 'd': 'dval' }, None), ('conflicting values', [[ checkers.declared_caveat('a', 'aval'), checkers.declared_caveat('a', 'conflict'), checkers.declared_caveat('b', 'bval') ], [ checkers.declared_caveat('a', 'conflict'), checkers.declared_caveat('b', 'another conflict'), checkers.declared_caveat('c', 'cval'), checkers.declared_caveat('d', 'dval') ]], { 'c': 'cval', 'd': 'dval' }, None), ('third party caveats ignored', [[ checkers.Caveat(condition='declared a no conflict', location='location') ], [checkers.declared_caveat('a', 'aval')]], { 'a': 'aval' }, None), ('unparseable caveats ignored', [[checkers.Caveat(condition=' bad')], [checkers.declared_caveat('a', 'aval')]], { 'a': 'aval' }, None), ('infer with namespace', [[ checkers.declared_caveat('a', 'aval'), caveat_with_ns(checkers.declared_caveat('a', 'aval'), 'testns'), ]], { 'a': 'aval' }, None), ] for test in tests: uri_to_prefix = test[3] if uri_to_prefix is None: uri_to_prefix = {checkers.STD_NAMESPACE: ''} ns = checkers.Namespace(uri_to_prefix) print(test[0]) ms = [] for i, caveats in enumerate(test[1]): m = Macaroon(key=None, identifier=six.int2byte(i), location='', version=MACAROON_V2) for cav in caveats: cav = ns.resolve_caveat(cav) if cav.location == '': m.add_first_party_caveat(cav.condition) else: m.add_third_party_caveat(cav.location, None, cav.condition) ms.append(m) self.assertEqual(checkers.infer_declared(ms), test[2])