Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
 def interact(self, client, location, interaction_required_err):
     '''Implement Interactor.interact by obtaining obtaining
     a macaroon from the discharger, discharging it with the
     local private key using the discharged macaroon as
     a discharge token'''
     p = interaction_required_err.interaction_method(
         'agent', InteractionInfo)
     if p.login_url is None or p.login_url == '':
         raise httpbakery.InteractionError(
             'no login-url field found in agent interaction method')
     agent = self._find_agent(location)
     if not location.endswith('/'):
         location += '/'
     login_url = urljoin(location, p.login_url)
     # TODO use client to make the request.
     resp = requests.get(login_url,
                         json={
                             'Username': agent.username,
                             'PublicKey': str(self._auth_info.key),
                         })
     if resp.status_code != 200:
         raise httpbakery.InteractionError(
             'cannot acquire agent macaroon: {}'.format(resp.status_code))
     m = resp.json().get('macaroon')
     if m is None:
         raise httpbakery.InteractionError('no macaroon in response')
     m = bakery.Macaroon.from_dict(m)
     ms = bakery.discharge_all(m, None, self._auth_info.key)
     b = bytearray()
     for m in ms:
         b.extend(utils.b64decode(m.serialize()))
     return httpbakery.DischargeToken(kind='agent', value=bytes(b))
    def handle_error(self, error, url):
        '''Try to resolve the given error, which should be a response
        to the given URL, by discharging any macaroon contained in
        it. That is, if error.code is ERR_DISCHARGE_REQUIRED
        then it will try to discharge err.info.macaroon. If the discharge
        succeeds, the discharged macaroon will be saved to the client's cookie
        jar, otherwise an exception will be raised.
        '''
        if error.info is None or error.info.macaroon is None:
            raise BakeryException('unable to read info in discharge error '
                                  'response')

        discharges = bakery.discharge_all(
            error.info.macaroon,
            self.acquire_discharge,
            self.key,
        )
        macaroons = '[' + ','.join(map(utils.macaroon_to_json_string,
                                       discharges)) + ']'
        all_macaroons = base64.urlsafe_b64encode(utils.to_bytes(macaroons))

        full_path = urljoin(url, error.info.macaroon_path)
        if error.info.cookie_name_suffix is not None:
            name = 'macaroon-' + error.info.cookie_name_suffix
        else:
            name = 'macaroon-auth'
        expires = checkers.macaroons_expiry_time(checkers.Namespace(), discharges)
        self.cookies.set_cookie(utils.cookie(
            name=name,
            value=all_macaroons.decode('ascii'),
            url=full_path,
            expires=expires,
        ))
Exemplo n.º 4
0
    def handle_error(self, error, url):
        '''Try to resolve the given error, which should be a response
        to the given URL, by discharging any macaroon contained in
        it. That is, if error.code is ERR_DISCHARGE_REQUIRED
        then it will try to discharge err.info.macaroon. If the discharge
        succeeds, the discharged macaroon will be saved to the client's cookie
        jar, otherwise an exception will be raised.
        '''
        if error.info is None or error.info.macaroon is None:
            raise BakeryException('unable to read info in discharge error '
                                  'response')

        discharges = bakery.discharge_all(
            error.info.macaroon,
            self.acquire_discharge,
            self.key,
        )
        macaroons = '[' + ','.join(
            map(utils.macaroon_to_json_string, discharges)) + ']'
        all_macaroons = base64.urlsafe_b64encode(utils.to_bytes(macaroons))

        full_path = relative_url(url, error.info.macaroon_path)
        if error.info.cookie_name_suffix is not None:
            name = 'macaroon-' + error.info.cookie_name_suffix
        else:
            name = 'macaroon-auth'
        expires = checkers.macaroons_expiry_time(checkers.Namespace(),
                                                 discharges)
        self.cookies.set_cookie(
            utils.cookie(
                name=name,
                value=all_macaroons.decode('ascii'),
                url=full_path,
                expires=expires,
            ))
 def interact(self, client, location, interaction_required_err):
     '''Implement Interactor.interact by obtaining obtaining
     a macaroon from the discharger, discharging it with the
     local private key using the discharged macaroon as
     a discharge token'''
     p = interaction_required_err.interaction_method('agent',
                                                     InteractionInfo)
     if p.login_url is None or p.login_url == '':
         raise httpbakery.InteractionError(
             'no login-url field found in agent interaction method')
     agent = self._find_agent(location)
     if not location.endswith('/'):
         location += '/'
     login_url = urljoin(location, p.login_url)
     resp = requests.get(
         login_url, params={
             'username': agent.username,
             'public-key': str(self._auth_info.key.public_key)},
         auth=client.auth())
     if resp.status_code != 200:
         raise httpbakery.InteractionError(
             'cannot acquire agent macaroon: {} {}'.format(
                 resp.status_code, resp.text)
         )
     m = resp.json().get('macaroon')
     if m is None:
         raise httpbakery.InteractionError('no macaroon in response')
     m = bakery.Macaroon.from_dict(m)
     ms = bakery.discharge_all(m, None, self._auth_info.key)
     b = bytearray()
     for m in ms:
         b.extend(utils.b64decode(m.serialize()))
     return httpbakery.DischargeToken(kind='agent', value=bytes(b))
Exemplo n.º 6
0
    def _discharge_all(self, ctx, m):
        def get_discharge(cav, payload):
            d = self._dischargers.get(cav.location)
            if d is None:
                raise ValueError('third party discharger '
                                 '{} not found'.format(cav.location))
            return d.discharge(ctx, cav, payload)

        return bakery.discharge_all(m, get_discharge)
    def _discharge_all(self, ctx, m):
        def get_discharge(cav, payload):
            d = self._dischargers.get(cav.location)
            if d is None:
                raise ValueError('third party discharger '
                                 '{} not found'.format(cav.location))
            return d.discharge(ctx, cav, payload)

        return bakery.discharge_all(m, get_discharge)
Exemplo n.º 8
0
 def test_discharge_all_local_discharge_version1(self):
     oc = common.new_bakery('ts', None)
     client_key = bakery.generate_key()
     m = oc.oven.macaroon(bakery.VERSION_1, common.ages, [
         bakery.local_third_party_caveat(
             client_key.public_key, bakery.VERSION_1)
     ], [bakery.LOGIN_OP])
     ms = bakery.discharge_all(m, no_discharge(self), client_key)
     oc.checker.auth([ms]).allow(common.test_context,
                                 [bakery.LOGIN_OP])
Exemplo n.º 9
0
 def test_discharge_all_no_discharges(self):
     root_key = b'root key'
     m = bakery.Macaroon(
         root_key=root_key, id=b'id0', location='loc0',
         version=bakery.LATEST_VERSION,
         namespace=common.test_checker().namespace())
     ms = bakery.discharge_all(m, no_discharge(self))
     self.assertEqual(len(ms), 1)
     self.assertEqual(ms[0], m.macaroon)
     v = Verifier()
     v.satisfy_general(always_ok)
     v.verify(m.macaroon, root_key, None)
Exemplo n.º 10
0
    def _login(self, macaroon: str) -> None:
        bakery_macaroon = bakery.Macaroon.from_dict(json.loads(macaroon))
        discharges = bakery.discharge_all(bakery_macaroon,
                                          self.bakery_client.acquire_discharge)

        # serialize macaroons the bakery-way
        discharged_macaroons = (
            "[" + ",".join(map(utils.macaroon_to_json_string, discharges)) +
            "]")

        self._auth = base64.urlsafe_b64encode(
            utils.to_bytes(discharged_macaroons)).decode("ascii")
        self._macaroon = macaroon
    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 bakery.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))
Exemplo n.º 12
0
    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 bakery.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))
Exemplo n.º 13
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.PermissionDenied) 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_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.PermissionDenied) 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_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])
Exemplo n.º 16
0
    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])
Exemplo n.º 17
0
    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_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.PermissionDenied) 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])
Exemplo n.º 19
0
    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])
Exemplo n.º 20
0
    def test_discharge_all_many_discharges(self):
        root_key = b'root key'
        m0 = bakery.Macaroon(
            root_key=root_key, id=b'id0', location='loc0',
            version=bakery.LATEST_VERSION)

        class State(object):
            total_required = 40
            id = 1

        def add_caveats(m):
            for i in range(0, 1):
                if State.total_required == 0:
                    break
                cid = 'id{}'.format(State.id)
                m.macaroon.add_third_party_caveat(
                    location='somewhere',
                    key='root key {}'.format(cid).encode('utf-8'),
                    key_id=cid.encode('utf-8'))
                State.id += 1
                State.total_required -= 1

        add_caveats(m0)

        def get_discharge(cav, payload):
            self.assertEqual(payload, None)
            m = bakery.Macaroon(
                root_key='root key {}'.format(
                    cav.caveat_id.decode('utf-8')).encode('utf-8'),
                id=cav.caveat_id, location='',
                version=bakery.LATEST_VERSION)

            add_caveats(m)
            return m

        ms = bakery.discharge_all(m0, get_discharge)

        self.assertEqual(len(ms), 41)

        v = Verifier()
        v.satisfy_general(always_ok)
        v.verify(ms[0], root_key, ms[1:])
    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_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)
Exemplo n.º 23
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.PermissionDenied) 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])
Exemplo n.º 24
0
    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 == 'x':
                    return [checkers.declared_caveat('foo', 'fooval1')]
                if cav_info.condition == 'y':
                    return [
                        checkers.declared_caveat('foo', 'fooval2'),
                        checkers.declared_caveat('baz', 'bazval')
                    ]
                raise bakery.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.PermissionDenied) 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])
Exemplo n.º 25
0
    def hook(response, *args, **kwargs):
        ''' Requests hooks system, this is the hook for the response.
        '''
        status_401 = (response.status_code == 401 and
                      response.headers.get('WWW-Authenticate') == 'Macaroon')
        if not status_401 and response.status_code != 407:
            return response
        if response.headers.get('Content-Type') != 'application/json':
            return response

        try:
            error = response.json()
        except:
            raise BakeryException('unable to read discharge error response')
        if error.get('Code') != ERR_DISCHARGE_REQUIRED:
            return response
        Retry.count += 1
        if Retry.count > MAX_DISCHARGE_RETRIES:
            raise BakeryException('too many discharges')
        info = error.get('Info')
        if not isinstance(info, dict):
            raise BakeryException(
                'unable to read info in discharge error response')
        serialized_macaroon = info.get('Macaroon')
        if not isinstance(serialized_macaroon, dict):
            raise BakeryException(
                'unable to read macaroon in discharge error response')

        macaroon = utils.deserialize(serialized_macaroon)
        discharges = discharge_all(macaroon, visit_page, jar, key)
        encoded_discharges = map(utils.serialize_macaroon_string, discharges)

        macaroons = '[' + ','.join(encoded_discharges) + ']'
        all_macaroons = base64.urlsafe_b64encode(
            macaroons.encode('utf-8')).decode('ascii')

        full_path = urljoin(response.url, info['MacaroonPath'])
        parsed_url = urlparse(full_path)
        if info and info.get('CookieNameSuffix'):
            name = 'macaroon-' + info['CookieNameSuffix']
        else:
            name = 'macaroon-' + discharges[0].signature
        domain = parsed_url.hostname or parsed_url.netloc
        port = str(parsed_url.port) if parsed_url.port is not None else None
        secure = parsed_url.scheme == 'https'
        cookie = Cookie(version=0,
                        name=name,
                        value=all_macaroons,
                        port=port,
                        port_specified=port is not None,
                        domain=domain,
                        domain_specified=True,
                        domain_initial_dot=False,
                        path=parsed_url.path,
                        path_specified=True,
                        secure=secure,
                        expires=None,
                        discard=False,
                        comment=None,
                        comment_url=None,
                        rest=None,
                        rfc2109=False)
        jar.set_cookie(cookie)
        # Replace the private _cookies from req as it is a copy of
        # the original cookie jar passed into the requests method and we need
        # to set the cookie for this request.
        req._cookies = jar
        req.headers.pop('Cookie', None)
        req.prepare_cookies(req._cookies)
        req.headers['Bakery-Protocol-Version'] = '1'
        with requests.Session() as s:
            return s.send(req)
    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 == 'x':
                    return [checkers.declared_caveat('foo', 'fooval1')]
                if cav_info.condition == 'y':
                    return [
                        checkers.declared_caveat('foo', 'fooval2'),
                        checkers.declared_caveat('baz', 'bazval')
                    ]
                raise bakery.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.PermissionDenied) 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])
Exemplo n.º 27
0
    def test_discharge_all_many_discharges_with_real_third_party_caveats(self):
        # This is the same flow as TestDischargeAllManyDischarges except that
        # we're using actual third party caveats as added by
        # Macaroon.add_caveat and we use a larger number of caveats
        # so that caveat ids will need to get larger.
        locator = bakery.ThirdPartyStore()
        bakeries = {}
        total_discharges_required = 40

        class M:
            bakery_id = 0
            still_required = total_discharges_required

        def add_bakery():
            M.bakery_id += 1
            loc = 'loc{}'.format(M.bakery_id)
            bakeries[loc] = common.new_bakery(loc, locator)
            return loc

        ts = common.new_bakery('ts-loc', locator)

        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

        root_key = b'root key'
        m0 = bakery.Macaroon(
            root_key=root_key, id=b'id0', location='ts-loc',
            version=bakery.LATEST_VERSION)

        m0.add_caveat(checkers. Caveat(location=add_bakery(),
                                       condition='something'),
                      ts.oven.key, locator)

        # We've added a caveat (the first) so one less caveat is required.
        M.still_required -= 1

        class ThirdPartyCaveatCheckerF(bakery.ThirdPartyCaveatChecker):
            def check_third_party_caveat(self, ctx, info):
                return checker(ctx, info)

        def get_discharge(cav, payload):
            return bakery.discharge(
                common.test_context, cav.caveat_id, payload,
                bakeries[cav.location].oven.key,
                ThirdPartyCaveatCheckerF(), locator)

        ms = bakery.discharge_all(m0, get_discharge)

        self.assertEqual(len(ms), total_discharges_required + 1)

        v = Verifier()
        v.satisfy_general(always_ok)
        v.verify(ms[0], root_key, ms[1:])
    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.PermissionDenied) 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])