def _write_discharge_error(self, exc): version = httpbakery.request_version(self.headers) if version < bakery.LATEST_VERSION: self._server_version = version caveats = [] if self._auth_location != '': caveats = [ checkers.Caveat(location=self._auth_location, condition='is-ok') ] if self._caveats is not None: caveats.extend(self._caveats) m = self._bakery.oven.macaroon(version=bakery.LATEST_VERSION, expiry=self._expiry, caveats=caveats, ops=[TEST_OP]) content, headers = httpbakery.discharge_required_response( m, '/', 'test', exc.args[0]) self.send_response(401) for h in headers: self.send_header(h, headers[h]) self.send_header('Connection', 'close') self.end_headers() self.wfile.write(content)
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_discharge_with_version1_macaroon(self): locator = bakery.ThirdPartyStore() bs = common.new_bakery('bs-loc', locator) ts = common.new_bakery('ts-loc', locator) # ts creates a old-version macaroon. ts_macaroon = ts.oven.macaroon(bakery.VERSION_1, common.ages, None, [bakery.LOGIN_OP]) ts_macaroon.add_caveat( checkers.Caveat(condition='something', location='bs-loc'), ts.oven.key, ts.oven.locator) # client asks for a discharge macaroon for each third party caveat def get_discharge(cav, payload): # Make sure that the caveat id really is old-style. try: cav.caveat_id_bytes.decode('utf-8') except UnicodeDecodeError: self.fail('caveat id is not utf-8') return bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, bs.oven.key, common.ThirdPartyStrcmpChecker('something'), bs.oven.locator, ) d = bakery.discharge_all(ts_macaroon, get_discharge) ts.checker.auth([d]).allow(common.test_context, [bakery.LOGIN_OP]) for m in d: self.assertEqual(m.version, MACAROON_V1)
def test_operations_checker(self): tests = [ ('all allowed', checkers.allow_caveat(['op1', 'op2', 'op4', 'op3' ]), ['op1', 'op3', 'op2'], None), ('none denied', checkers.deny_caveat(['op1', 'op2']), ['op3', 'op4'], None), ('one not allowed', checkers.allow_caveat(['op1', 'op2']), ['op1', 'op3'], 'caveat "allow op1 op2" not satisfied: op3 not allowed'), ('one not denied', checkers.deny_caveat(['op1', 'op2']), ['op4', 'op5', 'op2'], 'caveat "deny op1 op2" not satisfied: op2 not allowed'), ('no operations, allow caveat', checkers.allow_caveat(['op1']), [], 'caveat "allow op1" not satisfied: op1 not allowed'), ('no operations, deny caveat', checkers.deny_caveat(['op1']), [], None), ('no operations, empty allow caveat', checkers.Caveat(condition=checkers.COND_ALLOW), [], 'caveat "allow" not satisfied: no operations allowed'), ] checker = checkers.Checker() for test in tests: print(test[0]) ctx = checkers.context_with_operations(checkers.AuthContext(), test[2]) err = checker.check_first_party_caveat(ctx, test[1].condition) if test[3] is None: self.assertIsNone(err) continue self.assertEqual(err, test[3])
def test_macaroon_paper_fig6_fails_without_discharges(self): ''' Runs a similar test as test_macaroon_paper_fig6 without the client discharging the third party caveats. ''' locator = bakery.ThirdPartyStore() ts = common.new_bakery('ts-loc', locator) fs = common.new_bakery('fs-loc', locator) common.new_bakery('as-loc', locator) # ts creates a macaroon. ts_macaroon = ts.oven.macaroon(bakery.LATEST_VERSION, common.ages, None, [bakery.LOGIN_OP]) # ts somehow sends the macaroon to fs which adds a third party # caveat to be discharged by as. ts_macaroon.add_caveat( checkers.Caveat(location='as-loc', condition='user==bob'), fs.oven.key, fs.oven.locator) # client makes request to ts try: ts.checker.auth([[ts_macaroon.macaroon] ]).allow(common.test_context, bakery.LOGIN_OP) self.fail('macaroon unmet should be raised') except bakery.PermissionDenied: pass
def test_marshal_json_latest_version(self): locator = bakery.ThirdPartyStore() bs = common.new_bakery('bs-loc', locator) ns = checkers.Namespace({ 'testns': 'x', 'otherns': 'y', }) m = bakery.Macaroon( root_key=b'root key', id=b'id', location='location', version=bakery.LATEST_VERSION, namespace=ns) m.add_caveat(checkers.Caveat(location='bs-loc', condition='something'), bs.oven.key, locator) data = m.serialize_json() m1 = bakery.Macaroon.deserialize_json(data) # Just check the signature and version - we're not interested in fully # checking the macaroon marshaling here. self.assertEqual(m1.macaroon.signature, m.macaroon.signature) self.assertEqual(m1.macaroon.version, m.macaroon.version) self.assertEqual(len(m1.macaroon.caveats), 1) self.assertEqual(m1.namespace, m.namespace) self.assertEqual(m1._caveat_data, m._caveat_data) # test with the encoder, decoder data = json.dumps(m, cls=bakery.MacaroonJSONEncoder) m1 = json.loads(data, cls=bakery.MacaroonJSONDecoder) self.assertEqual(m1.macaroon.signature, m.macaroon.signature) self.assertEqual(m1.macaroon.version, m.macaroon.version) self.assertEqual(len(m1.macaroon.caveats), 1) self.assertEqual(m1.namespace, m.namespace) self.assertEqual(m1._caveat_data, m._caveat_data)
def test_add_third_party_caveat(self): locator = bakery.ThirdPartyStore() bs = common.new_bakery('bs-loc', locator) lbv = six.int2byte(bakery.LATEST_VERSION) tests = [ ('no existing id', b'', [], lbv + six.int2byte(0)), ('several existing ids', b'', [ lbv + six.int2byte(0), lbv + six.int2byte(1), lbv + six.int2byte(2) ], lbv + six.int2byte(3)), ('with base id', lbv + six.int2byte(0), [lbv + six.int2byte(0)], lbv + six.int2byte(0) + six.int2byte(0)), ('with base id and existing id', lbv + six.int2byte(0), [ lbv + six.int2byte(0) + six.int2byte(0) ], lbv + six.int2byte(0) + six.int2byte(1)) ] for test in tests: print('test ', test[0]) m = bakery.Macaroon( root_key=b'root key', id=b'id', location='location', version=bakery.LATEST_VERSION) for id in test[2]: m.macaroon.add_third_party_caveat(key=None, key_id=id, location='') m._caveat_id_prefix = test[1] m.add_caveat(checkers.Caveat(location='bs-loc', condition='something'), bs.oven.key, locator) self.assertEqual(m.macaroon.caveats[len(test[2])].caveat_id, test[3])
def test_add_first_party_caveat(self): m = bakery.Macaroon('rootkey', 'some id', 'here', bakery.LATEST_VERSION) m.add_caveat(checkers.Caveat('test_condition')) caveats = m.first_party_caveats() self.assertEquals(len(caveats), 1) self.assertEquals(caveats[0].caveat_id, b'test_condition')
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)
def _test_json_with_version(self, version): locator = bakery.ThirdPartyStore() bs = common.new_bakery('bs-loc', locator) ns = checkers.Namespace({ 'testns': 'x', }) m = bakery.Macaroon( root_key=b'root key', id=b'id', location='location', version=version, namespace=ns) m.add_caveat(checkers.Caveat(location='bs-loc', condition='something'), bs.oven.key, locator) # Sanity check that no external caveat data has been added. self.assertEqual(len(m._caveat_data), 0) data = json.dumps(m, cls=bakery.MacaroonJSONEncoder) m1 = json.loads(data, cls=bakery.MacaroonJSONDecoder) # Just check the signature and version - we're not interested in fully # checking the macaroon marshaling here. self.assertEqual(m1.macaroon.signature, m.macaroon.signature) self.assertEqual(m1.macaroon.version, bakery.macaroon_version(version)) self.assertEqual(len(m1.macaroon.caveats), 1) # Namespace information has been thrown away. self.assertEqual(m1.namespace, bakery.legacy_namespace()) self.assertEqual(len(m1._caveat_data), 0)
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 check_third_party_caveat(self, ctx, info): if self._loc == 'as1-loc': return [checkers.Caveat(condition='something', location='as2-loc')] if self._loc == 'as2-loc': return [] raise common.ThirdPartyCaveatCheckFailed( 'unknown location {}'.format(self._loc))
def add_caveat(self, cav, key=None, loc=None): '''Add a caveat to the macaroon. It encrypts it using the given key pair and by looking up the location using the given locator. As a special case, if the caveat's Location field has the prefix "local " the caveat is added as a client self-discharge caveat using the public key base64-encoded in the rest of the location. In this case, the Condition field must be empty. The resulting third-party caveat will encode the condition "true" encrypted with that public key. @param cav the checkers.Caveat to be added. @param key the public key to encrypt third party caveat. @param loc locator to find information on third parties when adding third party caveats. It is expected to have a third_party_info method that will be called with a location string and should return a ThirdPartyInfo instance holding the requested information. ''' if cav.location is None: self._macaroon.add_first_party_caveat( self.namespace.resolve_caveat(cav).condition) return if key is None: raise ValueError('no private key to encrypt third party caveat') local_info = _parse_local_location(cav.location) if local_info is not None: if cav.condition: raise ValueError('cannot specify caveat condition in ' 'local third-party caveat') info = local_info cav = checkers.Caveat(location='local', condition='true') else: if loc is None: raise ValueError('no locator when adding third party caveat') info = loc.third_party_info(cav.location) root_key = os.urandom(24) # Use the least supported version to encode the caveat. if self._version < info.version: info = ThirdPartyInfo( version=self._version, public_key=info.public_key, ) caveat_info = encode_caveat(cav.condition, root_key, info, key, self._namespace) if info.version < VERSION_3: # We're encoding for an earlier client or third party which does # not understand bundled caveat info, so use the encoded # caveat information as the caveat id. id = caveat_info else: id = self._new_caveat_id(self._caveat_id_prefix) self._caveat_data[id] = caveat_info self._macaroon.add_third_party_caveat(cav.location, root_key, id)
def _identity_caveats(): """Caveats required for user authentication using Candid.""" return [ checkers.need_declared_caveat( checkers.Caveat(location='https://api.staging.jujucharms.com/identity', condition='is-authenticated-user'), ['username'] ) ]
def local_third_party_caveat(key, version): ''' Returns a third-party caveat that, when added to a macaroon with add_caveat, results in a caveat with the location "local", encrypted with the given PublicKey. This can be automatically discharged by discharge_all passing a local key. ''' if version >= VERSION_2: loc = 'local {} {}'.format(version, key) else: loc = 'local {}'.format(key) return checkers.Caveat(location=loc, condition='')
def local_third_party_caveat(key, version): ''' Returns a third-party caveat that, when added to a macaroon with add_caveat, results in a caveat with the location "local", encrypted with the given PublicKey. This can be automatically discharged by discharge_all passing a local key. ''' encoded_key = key.encode().decode('utf-8') loc = 'local {}'.format(encoded_key) if version >= macaroonbakery.BAKERY_V2: loc = 'local {} {}'.format(version, encoded_key) return checkers.Caveat(location=loc, condition='')
def checker(_, ci): caveats = [] if ci.condition != 'something': self.fail('unexpected condition') for i in range(0, 2): if M.still_required <= 0: break caveats.append(checkers.Caveat(location=add_bakery(), condition='something')) M.still_required -= 1 return caveats
def test_clone(self): locator = bakery.ThirdPartyStore() bs = common.new_bakery("bs-loc", locator) ns = checkers.Namespace({ "testns": "x", }) m = bakery.Macaroon(root_key=b'root key', id=b'id', location='location', version=bakery.LATEST_VERSION, namespace=ns) m.add_caveat(checkers.Caveat(location='bs-loc', condition='something'), bs.oven.key, locator) m1 = m.copy() self.assertEqual(len(m.macaroon.caveats), 1) self.assertEqual(len(m1.macaroon.caveats), 1) self.assertEqual(m._caveat_data, m1._caveat_data) m.add_caveat(checkers.Caveat(location='bs-loc', condition='something'), bs.oven.key, locator) self.assertEqual(len(m.macaroon.caveats), 2) self.assertEqual(len(m1.macaroon.caveats), 1) self.assertNotEqual(m._caveat_data, m1._caveat_data)
def test_third_party_discharge_macaroon_ids_are_small(self): locator = bakery.ThirdPartyStore() bakeries = { 'ts-loc': common.new_bakery('ts-loc', locator), 'as1-loc': common.new_bakery('as1-loc', locator), 'as2-loc': common.new_bakery('as2-loc', locator), } ts = bakeries['ts-loc'] ts_macaroon = ts.oven.macaroon(bakery.LATEST_VERSION, common.ages, None, [bakery.LOGIN_OP]) ts_macaroon.add_caveat(checkers.Caveat(condition='something', location='as1-loc'), ts.oven.key, ts.oven.locator) class ThirdPartyCaveatCheckerF(bakery.ThirdPartyCaveatChecker): def __init__(self, loc): self._loc = loc def check_third_party_caveat(self, ctx, info): if self._loc == 'as1-loc': return [checkers.Caveat(condition='something', location='as2-loc')] if self._loc == 'as2-loc': return [] raise common.ThirdPartyCaveatCheckFailed( 'unknown location {}'.format(self._loc)) def get_discharge(cav, payload): oven = bakeries[cav.location].oven return bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, oven.key, ThirdPartyCaveatCheckerF(cav.location), oven.locator, ) d = bakery.discharge_all(ts_macaroon, get_discharge) ts.checker.auth([d]).allow(common.test_context, [bakery.LOGIN_OP]) for i, m in enumerate(d): for j, cav in enumerate(m.caveats): if (cav.verification_key_id is not None and len(cav.caveat_id) > 3): self.fail('caveat id on caveat {} of macaroon {} ' 'is too big ({})'.format(j, i, cav.id))
def test_single_service_first_party(self): ''' Creates a single service with a macaroon with one first party caveat. It creates a request with this macaroon and checks that the service can verify this macaroon as valid. ''' oc = common.new_bakery('bakerytest') primary = oc.oven.macaroon(bakery.LATEST_VERSION, common.ages, None, [bakery.LOGIN_OP]) self.assertEqual(primary.macaroon.location, 'bakerytest') primary.add_caveat(checkers.Caveat(condition='str something', namespace='testns'), oc.oven.key, oc.oven.locator) oc.checker.auth([[primary.macaroon]]).allow( common.str_context('something'), [bakery.LOGIN_OP])
def test_macaroon_paper_fig6(self): ''' Implements an example flow as described in the macaroons paper: http://theory.stanford.edu/~ataly/Papers/macaroons.pdf There are three services, ts, fs, bs: ts is a store service which has deligated authority to a forum service fs. The forum service wants to require its users to be logged into to an authentication service bs. The client obtains a macaroon from fs (minted by ts, with a third party caveat addressed to bs). The client obtains a discharge macaroon from bs to satisfy this caveat. The target service verifies the original macaroon it delegated to fs No direct contact between bs and ts is required ''' locator = bakery.ThirdPartyStore() bs = common.new_bakery('bs-loc', locator) ts = common.new_bakery('ts-loc', locator) fs = common.new_bakery('fs-loc', locator) # ts creates a macaroon. ts_macaroon = ts.oven.macaroon(bakery.LATEST_VERSION, common.ages, None, [bakery.LOGIN_OP]) # ts somehow sends the macaroon to fs which adds a third party caveat # to be discharged by bs. ts_macaroon.add_caveat(checkers.Caveat(location='bs-loc', condition='user==bob'), fs.oven.key, fs.oven.locator) # client asks for a discharge macaroon for each third party caveat def get_discharge(cav, payload): self.assertEqual(cav.location, 'bs-loc') return bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, bs.oven.key, common.ThirdPartyStrcmpChecker('user==bob'), bs.oven.locator, ) d = bakery.discharge_all(ts_macaroon, get_discharge) ts.checker.auth([d]).allow(common.test_context, [bakery.LOGIN_OP])
def test_third_party_discharge_macaroon_wrong_root_key_and_third_party_caveat( self): root_keys = bakery.MemoryKeyStore() ts = bakery.Bakery( key=bakery.generate_key(), checker=common.test_checker(), root_key_store=root_keys, identity_client=common.OneIdentity(), ) locator = bakery.ThirdPartyStore() bs = common.new_bakery('bs-loc', locator) # ts creates a macaroon with a third party caveat addressed to bs. ts_macaroon = ts.oven.macaroon(bakery.LATEST_VERSION, common.ages, None, [bakery.LOGIN_OP]) ts_macaroon.add_caveat( checkers.Caveat(location='bs-loc', condition='true'), ts.oven.key, locator, ) def get_discharge(cav, payload): return bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, bs.oven.key, common.ThirdPartyStrcmpChecker('true'), bs.oven.locator, ) d = bakery.discharge_all(ts_macaroon, get_discharge) # The authorization should succeed at first. ts.checker.auth([d]).allow(common.test_context, [bakery.LOGIN_OP]) # Corrupt the root key and try again. # We should get a DischargeRequiredError because the verification has failed. root_keys._key = os.urandom(24) with self.assertRaises(bakery.PermissionDenied) as err: ts.checker.auth([d]).allow(common.test_context, [bakery.LOGIN_OP]) self.assertEqual( str(err.exception), 'verification failed: Decryption failed. Ciphertext failed verification' )
def test_json_deserialize_from_go(self): ns = checkers.Namespace() ns.register("someuri", "x") m = bakery.Macaroon( root_key=b'rootkey', id=b'some id', location='here', version=bakery.LATEST_VERSION, namespace=ns) m.add_caveat(checkers.Caveat(condition='something', namespace='someuri')) data = '{"m":{"c":[{"i":"x:something"}],"l":"here","i":"some id",' \ '"s64":"c8edRIupArSrY-WZfa62pgZFD8VjDgqho9U2PlADe-E"},"v":3,' \ '"ns":"someuri:x"}' m_go = bakery.Macaroon.deserialize_json(data) self.assertEqual(m.macaroon.signature_bytes, m_go.macaroon.signature_bytes) self.assertEqual(m.macaroon.version, m_go.macaroon.version) self.assertEqual(len(m_go.macaroon.caveats), 1) self.assertEqual(m.namespace, m_go.namespace)
def test_macaroon_paper_fig6_fails_with_binding_on_tampered_sig(self): ''' Runs a similar test as test_macaroon_paper_fig6 with the discharge macaroon binding being done on a tampered signature. ''' locator = bakery.ThirdPartyStore() bs = common.new_bakery('bs-loc', locator) ts = common.new_bakery('ts-loc', locator) # ts creates a macaroon. ts_macaroon = ts.oven.macaroon(bakery.LATEST_VERSION, common.ages, None, [bakery.LOGIN_OP]) # ts somehow sends the macaroon to fs which adds a third party caveat # to be discharged by as. ts_macaroon.add_caveat(checkers.Caveat(condition='user==bob', location='bs-loc'), ts.oven.key, ts.oven.locator) # client asks for a discharge macaroon for each third party caveat def get_discharge(cav, payload): self.assertEqual(cav.location, 'bs-loc') return bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, bs.oven.key, common.ThirdPartyStrcmpChecker('user==bob'), bs.oven.locator, ) d = bakery.discharge_all(ts_macaroon, get_discharge) # client has all the discharge macaroons. For each discharge macaroon # bind it to our ts_macaroon and add it to our request. tampered_macaroon = Macaroon() for i, dm in enumerate(d[1:]): d[i + 1] = tampered_macaroon.prepare_for_request(dm) # client makes request to ts. with self.assertRaises(bakery.VerificationError) as exc: ts.checker.auth([d]).allow(common.test_context, bakery.LOGIN_OP) self.assertEqual('verification failed: Signatures do not match', exc.exception.args[0])
def test_discharge_macaroon_cannot_be_used_as_normal_macaroon(self): locator = bakery.ThirdPartyStore() first_party = common.new_bakery('first', locator) third_party = common.new_bakery('third', locator) # First party mints a macaroon with a 3rd party caveat. m = first_party.oven.macaroon(bakery.LATEST_VERSION, common.ages, [ checkers.Caveat(location='third', condition='true')], [bakery.LOGIN_OP]) # Acquire the discharge macaroon, but don't bind it to the original. class M: unbound = None def get_discharge(cav, payload): m = bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, third_party.oven.key, common.ThirdPartyStrcmpChecker('true'), third_party.oven.locator, ) M.unbound = m.macaroon.copy() return m bakery.discharge_all(m, get_discharge) self.assertIsNotNone(M.unbound) # Make sure it cannot be used as a normal macaroon in the third party. with self.assertRaises(bakery.VerificationError) as exc: third_party.checker.auth([[M.unbound]]).allow( common.test_context, [bakery.LOGIN_OP]) self.assertEqual('no operations found in macaroon', exc.exception.args[0])
def test_need_declared(self): locator = bakery.ThirdPartyStore() first_party = common.new_bakery('first', locator) third_party = common.new_bakery('third', locator) # firstParty mints a macaroon with a third-party caveat addressed # to thirdParty with a need-declared caveat. m = first_party.oven.macaroon( bakery.LATEST_VERSION, common.ages, [ checkers.need_declared_caveat( checkers.Caveat(location='third', condition='something'), ['foo', 'bar'] ) ], [bakery.LOGIN_OP]) # The client asks for a discharge macaroon for each third party caveat. def get_discharge(cav, payload): return bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, third_party.oven.key, common.ThirdPartyStrcmpChecker('something'), third_party.oven.locator, ) d = bakery.discharge_all(m, get_discharge) # The required declared attributes should have been added # to the discharge macaroons. declared = checkers.infer_declared(d, first_party.checker.namespace()) self.assertEqual(declared, { 'foo': '', 'bar': '', }) # Make sure the macaroons actually check out correctly # when provided with the declared checker. ctx = checkers.context_with_declared(common.test_context, declared) first_party.checker.auth([d]).allow(ctx, [bakery.LOGIN_OP]) # Try again when the third party does add a required declaration. # The client asks for a discharge macaroon for each third party caveat. 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, ) d = bakery.discharge_all(m, get_discharge) # One attribute should have been added, the other was already there. declared = checkers.infer_declared(d, first_party.checker.namespace()) self.assertEqual(declared, { 'foo': 'a', 'bar': '', 'arble': 'b', }) ctx = checkers.context_with_declared(common.test_context, declared) first_party.checker.auth([d]).allow(ctx, [bakery.LOGIN_OP]) # Try again, but this time pretend a client is sneakily trying # to add another 'declared' attribute to alter the declarations. 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 d = bakery.discharge_all(m, get_discharge) declared = checkers.infer_declared(d, first_party.checker.namespace()) self.assertEqual(declared, { 'bar': '', 'arble': 'b', }) with self.assertRaises(bakery.AuthInitError) as exc: first_party.checker.auth([d]).allow(common.test_context, bakery.LOGIN_OP) self.assertEqual('cannot authorize login macaroon: caveat ' '"declared foo a" not satisfied: got foo=null, ' 'expected "a"', exc.exception.args[0])
def _get_authentication_caveat(location, domain=""): """Return a Caveat requiring the user to be authenticated.""" condition = "is-authenticated-user" if domain: condition += " @" + domain return checkers.Caveat(condition, location=location)
def test_discharge_two_need_declared(self): locator = bakery.ThirdPartyStore() first_party = common.new_bakery('first', locator) third_party = common.new_bakery('third', locator) # first_party mints a macaroon with two third party caveats # with overlapping attributes. m = first_party.oven.macaroon( bakery.LATEST_VERSION, common.ages, [ checkers.need_declared_caveat( checkers.Caveat(location='third', condition='x'), ['foo', 'bar']), checkers.need_declared_caveat( checkers.Caveat(location='third', condition='y'), ['bar', 'baz']), ], [bakery.LOGIN_OP]) # The client asks for a discharge macaroon for each third party caveat. # Since no declarations are added by the discharger, def get_discharge(cav, payload): return bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, third_party.oven.key, common.ThirdPartyCaveatCheckerEmpty(), third_party.oven.locator, ) d = bakery.discharge_all(m, get_discharge) declared = checkers.infer_declared(d, first_party.checker.namespace()) self.assertEqual(declared, { 'foo': '', 'bar': '', 'baz': '', }) ctx = checkers.context_with_declared(common.test_context, declared) first_party.checker.auth([d]).allow(ctx, [bakery.LOGIN_OP]) # If they return conflicting values, the discharge fails. # The client asks for a discharge macaroon for each third party caveat. # Since no declarations are added by the discharger, class ThirdPartyCaveatCheckerF(bakery.ThirdPartyCaveatChecker): 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(cav, payload): return bakery.discharge( common.test_context, cav.caveat_id_bytes, payload, third_party.oven.key, ThirdPartyCaveatCheckerF(), third_party.oven.locator, ) d = bakery.discharge_all(m, get_discharge) declared = checkers.infer_declared(d, first_party.checker.namespace()) self.assertEqual(declared, { 'bar': '', 'baz': 'bazval', }) with self.assertRaises(bakery.AuthInitError) as exc: first_party.checker.auth([d]).allow(common.test_context, bakery.LOGIN_OP) self.assertEqual('cannot authorize login macaroon: caveat "declared ' 'foo fooval1" not satisfied: got foo=null, expected ' '"fooval1"', exc.exception.args[0])
def identity_from_context(self, ctx): return None, [ checkers.Caveat(location=self._location, condition='is-authenticated-user') ]
def is_something_caveat(): return checkers.Caveat(condition='is something', namespace='testns')