Exemple #1
0
        def agent_visit(url, request):
            if request.method != "POST":
                raise Exception('unexpected method')
            log.info('agent_visit url {}'.format(url))
            body = json.loads(request.body.decode('utf-8'))
            if body['username'] != 'test-user':
                raise Exception('unexpected username in body {!r}'.format(
                    request.body))
            public_key = bakery.PublicKey.deserialize(body['public_key'])
            ms = httpbakery.extract_macaroons(request.headers)
            if len(ms) == 0:
                b = bakery.Bakery(key=discharge_key)
                m = b.oven.macaroon(
                    version=bakery.LATEST_VERSION,
                    expiry=datetime.utcnow() + timedelta(days=1),
                    caveats=[
                        bakery.local_third_party_caveat(
                            public_key,
                            version=httpbakery.request_version(
                                request.headers))
                    ],
                    ops=[bakery.Op(entity='agent', action='login')])
                content, headers = httpbakery.discharge_required_response(
                    m, '/', 'test', 'message')
                resp = response(status_code=401,
                                content=content,
                                headers=headers)
                return request.hooks['response'][0](resp)

            return {'status_code': 200, 'content': {'agent_login': True}}
Exemple #2
0
 def _setup_bakery(self, auth_endpoint, request):
     return bakery.Bakery(
         key=_get_macaroon_oven_key(),
         root_key_store=KeyStore(MACAROON_LIFESPAN),
         location=request.build_absolute_uri('/'),
         locator=httpbakery.ThirdPartyLocator(
             allow_insecure=not auth_endpoint.startswith('https:')),
         identity_client=IDClient(auth_endpoint),
         authorizer=bakery.ACLAuthorizer(
             get_acl=lambda ctx, op: [bakery.EVERYONE]))
Exemple #3
0
 def login(url, request):
     b = bakery.Bakery(key=discharge_key)
     m = b.oven.macaroon(
         version=bakery.LATEST_VERSION,
         expiry=datetime.utcnow() + timedelta(days=1),
         caveats=[
             bakery.local_third_party_caveat(
                 auth_info.key.public_key,
                 version=httpbakery.request_version(request.headers))
         ],
         ops=[bakery.Op(entity='agent', action='login')])
     return {'status_code': 200, 'content': {'macaroon': m.to_dict()}}
Exemple #4
0
def _get_bakery(request):
    auth_endpoint = request.external_auth_info.url
    auth_domain = request.external_auth_info.domain
    return bakery.Bakery(
        key=_get_macaroon_oven_key(),
        root_key_store=KeyStore(MACAROON_LIFESPAN),
        location=request.build_absolute_uri("/"),
        locator=httpbakery.ThirdPartyLocator(
            allow_insecure=not auth_endpoint.startswith("https:")),
        identity_client=_IDClient(auth_endpoint, auth_domain=auth_domain),
        authorizer=bakery.ACLAuthorizer(
            get_acl=lambda ctx, op: [bakery.EVERYONE]),
    )
 def test_version1_macaroon_id(self):
     # In the version 1 bakery, macaroon ids were hex-encoded with a
     # hyphenated UUID suffix.
     root_key_store = bakery.MemoryKeyStore()
     b = bakery.Bakery(
         root_key_store=root_key_store,
         identity_client=common.OneIdentity(),
     )
     key, id = root_key_store.root_key()
     root_key_store.get(id)
     m = Macaroon(key=key, version=MACAROON_V1, location='',
                  identifier=id + b'-deadl00f')
     b.checker.auth([[m]]).allow(common.test_context,
                                 [bakery.LOGIN_OP])
Exemple #6
0
    def is_authorized(*args, **kwargs):
        macaroon_bakery = bakery.Bakery(
            location="ubuntu.com/security",
            locator=httpbakery.ThirdPartyLocator(),
            identity_client=IdentityClient(),
            key=bakery.generate_key(),
            root_key_store=bakery.MemoryKeyStore(
                flask.current_app.config["SECRET_KEY"]),
        )
        macaroons = httpbakery.extract_macaroons(flask.request.headers)
        auth_checker = macaroon_bakery.checker.auth(macaroons)
        launchpad = Launchpad.login_anonymously("ubuntu.com/security",
                                                "production",
                                                version="devel")

        try:
            auth_info = auth_checker.allow(checkers.AuthContext(),
                                           [bakery.LOGIN_OP])
        except bakery._error.DischargeRequiredError:
            macaroon = macaroon_bakery.oven.macaroon(
                version=bakery.VERSION_2,
                expiry=datetime.utcnow() + timedelta(days=1),
                caveats=IDENTITY_CAVEATS,
                ops=[bakery.LOGIN_OP],
            )

            content, headers = httpbakery.discharge_required_response(
                macaroon, "/", "cookie-suffix")
            return content, 401, headers

        username = auth_info.identity.username()
        lp_user = launchpad.people(username)
        authorized = False

        for team in AUTHORIZED_TEAMS:
            if lp_user in launchpad.people(team).members:
                authorized = True
                break

        if not authorized:
            return (
                f"{username} is not in any of the authorized teams: "
                f"{str(AUTHORIZED_TEAMS)}",
                401,
            )

        # Validate authentication token
        return func(*args, **kwargs)
Exemple #7
0
    def __init__(self, locator=None):
        locator = httpbakery.ThirdPartyLocator()

        # generate a new keypair for encrypting third party caveats
        # it's safe to use a new keypair every time the server starts
        # as it's used only for encrypting the third party caveats
        # for sending them to be discharged. The private key doesn't need
        # to survive across restarts.
        key = bakery.generate_key()

        location = 'localhost:8000'
        root_key = 'private-key'

        self._bakery = bakery.Bakery(
            location=location, locator=locator,
            identity_client=IdentityClient(), key=key,
            root_key_store=bakery.MemoryKeyStore(root_key))
    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'
        )
Exemple #9
0
 def login(url, request):
     qs = parse_qs(urlparse(request.url).query)
     self.assertEqual(request.method, 'GET')
     self.assertEqual(qs, {
         'username': ['test-user'],
         'public-key': [PUBLIC_KEY]
     })
     b = bakery.Bakery(key=discharge_key)
     m = b.oven.macaroon(
         version=bakery.LATEST_VERSION,
         expiry=datetime.utcnow() + timedelta(days=1),
         caveats=[
             bakery.local_third_party_caveat(
                 PUBLIC_KEY,
                 version=httpbakery.request_version(request.headers))
         ],
         ops=[bakery.Op(entity='agent', action='login')])
     return {'status_code': 200, 'content': {'macaroon': m.to_dict()}}
Exemple #10
0
def new_bakery(location, locator=None):
    # Returns a new Bakery instance using a new
    # key pair, and registers the key with the given locator if provided.
    #
    # It uses test_checker to check first party caveats.
    key = bakery.generate_key()
    if locator is not None:
        locator.add_info(
            location,
            bakery.ThirdPartyInfo(public_key=key.public_key,
                                  version=bakery.LATEST_VERSION))
    return bakery.Bakery(
        key=key,
        checker=test_checker(),
        location=location,
        identity_client=OneIdentity(),
        locator=locator,
    )
def new_bakery(location, locator, checker):
    '''Return a new bakery instance.
    @param location Location of the bakery {str}.
    @param locator Locator for third parties {ThirdPartyLocator or None}
    @param checker Caveat checker {FirstPartyCaveatChecker or None}
    @return {Bakery}
    '''
    if checker is None:
        c = checkers.Checker()
        c.namespace().register('testns', '')
        c.register('is', 'testns', check_is_something)
        checker = c
    key = bakery.generate_key()
    return bakery.Bakery(
        location=location,
        locator=locator,
        key=key,
        checker=checker,
    )
Exemple #12
0
    def test_agent_login(self):
        discharge_key = bakery.generate_key()

        class _DischargerLocator(bakery.ThirdPartyLocator):
            def third_party_info(self, loc):
                if loc == 'http://0.3.2.1':
                    return bakery.ThirdPartyInfo(
                        public_key=discharge_key.public_key,
                        version=bakery.LATEST_VERSION,
                    )

        d = _DischargerLocator()
        server_key = bakery.generate_key()
        server_bakery = bakery.Bakery(key=server_key, locator=d)

        @urlmatch(path='.*/here')
        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)

        @urlmatch(path='.*/discharge')
        def discharge(url, request):
            qs = parse_qs(request.body)
            if qs.get('token64') is None:
                return response(status_code=401,
                                content={
                                    'Code':
                                    httpbakery.ERR_INTERACTION_REQUIRED,
                                    'Message': 'interaction required',
                                    'Info': {
                                        'InteractionMethods': {
                                            'agent': {
                                                'login-url': '/login'
                                            },
                                        },
                                    },
                                },
                                headers={'Content-Type': 'application/json'})
            else:
                qs = parse_qs(request.body)
                content = {q: qs[q][0] for q in qs}
                m = httpbakery.discharge(checkers.AuthContext(), content,
                                         discharge_key, None, alwaysOK3rd)
                return {
                    'status_code': 200,
                    'content': {
                        'Macaroon': m.to_dict()
                    }
                }

        auth_info = agent.load_auth_info(self.agent_filename)

        @urlmatch(path='.*/login')
        def login(url, request):
            b = bakery.Bakery(key=discharge_key)
            m = b.oven.macaroon(
                version=bakery.LATEST_VERSION,
                expiry=datetime.utcnow() + timedelta(days=1),
                caveats=[
                    bakery.local_third_party_caveat(
                        auth_info.key.public_key,
                        version=httpbakery.request_version(request.headers))
                ],
                ops=[bakery.Op(entity='agent', action='login')])
            return {'status_code': 200, 'content': {'macaroon': m.to_dict()}}

        with HTTMock(server_get), \
                HTTMock(discharge), \
                HTTMock(login):
            client = httpbakery.Client(interaction_methods=[
                agent.AgentInteractor(auth_info),
            ])
            resp = requests.get('http://0.1.2.3/here',
                                cookies=client.cookies,
                                auth=client.auth())
        self.assertEquals(resp.content, b'done')
Exemple #13
0
    def test_agent_legacy(self):
        discharge_key = bakery.generate_key()

        class _DischargerLocator(bakery.ThirdPartyLocator):
            def third_party_info(self, loc):
                if loc == 'http://0.3.2.1':
                    return bakery.ThirdPartyInfo(
                        public_key=discharge_key.public_key,
                        version=bakery.LATEST_VERSION,
                    )

        d = _DischargerLocator()
        server_key = bakery.generate_key()
        server_bakery = bakery.Bakery(key=server_key, locator=d)

        @urlmatch(path='.*/here')
        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)

        class InfoStorage:
            info = None

        @urlmatch(path='.*/discharge')
        def discharge(url, request):
            qs = parse_qs(request.body)
            if qs.get('caveat64') is not None:
                content = {q: qs[q][0] for q in qs}

                class InteractionRequiredError(Exception):
                    def __init__(self, error):
                        self.error = error

                class CheckerInError(bakery.ThirdPartyCaveatChecker):
                    def check_third_party_caveat(self, ctx, info):
                        InfoStorage.info = info
                        raise InteractionRequiredError(
                            httpbakery.Error(
                                code=httpbakery.ERR_INTERACTION_REQUIRED,
                                version=httpbakery.request_version(
                                    request.headers),
                                message='interaction required',
                                info=httpbakery.ErrorInfo(
                                    wait_url='http://0.3.2.1/wait?'
                                    'dischargeid=1',
                                    visit_url='http://0.3.2.1/visit?'
                                    'dischargeid=1'),
                            ), )

                try:
                    httpbakery.discharge(checkers.AuthContext(), content,
                                         discharge_key, None, CheckerInError())
                except InteractionRequiredError as exc:
                    return response(
                        status_code=401,
                        content={
                            'Code': exc.error.code,
                            'Message': exc.error.message,
                            'Info': {
                                'WaitURL': exc.error.info.wait_url,
                                'VisitURL': exc.error.info.visit_url,
                            },
                        },
                        headers={'Content-Type': 'application/json'})

        key = bakery.generate_key()

        @urlmatch(path='.*/visit')
        def visit(url, request):
            if request.headers.get('Accept') == 'application/json':
                return {
                    'status_code': 200,
                    'content': {
                        'agent': '/agent-visit',
                    }
                }
            raise Exception('unexpected call to visit without Accept header')

        @urlmatch(path='.*/agent-visit')
        def agent_visit(url, request):
            if request.method != "POST":
                raise Exception('unexpected method')
            log.info('agent_visit url {}'.format(url))
            body = json.loads(request.body.decode('utf-8'))
            if body['username'] != 'test-user':
                raise Exception('unexpected username in body {!r}'.format(
                    request.body))
            public_key = bakery.PublicKey.deserialize(body['public_key'])
            ms = httpbakery.extract_macaroons(request.headers)
            if len(ms) == 0:
                b = bakery.Bakery(key=discharge_key)
                m = b.oven.macaroon(
                    version=bakery.LATEST_VERSION,
                    expiry=datetime.utcnow() + timedelta(days=1),
                    caveats=[
                        bakery.local_third_party_caveat(
                            public_key,
                            version=httpbakery.request_version(
                                request.headers))
                    ],
                    ops=[bakery.Op(entity='agent', action='login')])
                content, headers = httpbakery.discharge_required_response(
                    m, '/', 'test', 'message')
                resp = response(status_code=401,
                                content=content,
                                headers=headers)
                return request.hooks['response'][0](resp)

            return {'status_code': 200, 'content': {'agent_login': True}}

        @urlmatch(path='.*/wait$')
        def wait(url, request):
            class EmptyChecker(bakery.ThirdPartyCaveatChecker):
                def check_third_party_caveat(self, ctx, info):
                    return []

            if InfoStorage.info is None:
                self.fail('visit url has not been visited')
            m = bakery.discharge(
                checkers.AuthContext(),
                InfoStorage.info.id,
                InfoStorage.info.caveat,
                discharge_key,
                EmptyChecker(),
                _DischargerLocator(),
            )
            return {'status_code': 200, 'content': {'Macaroon': m.to_dict()}}

        with HTTMock(server_get), \
                HTTMock(discharge), \
                HTTMock(visit), \
                HTTMock(wait), \
                HTTMock(agent_visit):
            client = httpbakery.Client(interaction_methods=[
                agent.AgentInteractor(
                    agent.AuthInfo(
                        key=key,
                        agents=[
                            agent.Agent(username='******',
                                        url=u'http://0.3.2.1')
                        ],
                    ), ),
            ])
            resp = requests.get(
                'http://0.1.2.3/here',
                cookies=client.cookies,
                auth=client.auth(),
            )
        self.assertEquals(resp.content, b'done')