예제 #1
0
 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])
예제 #2
0
    def __call__(self, request):
        if not request.external_auth_info:
            return HttpResponseNotFound('Not found')

        macaroon_bakery = _get_bakery(request)
        req_headers = request_headers(request)
        auth_checker = macaroon_bakery.checker.auth(
            httpbakery.extract_macaroons(req_headers))
        try:
            auth_info = auth_checker.allow(
                checkers.AuthContext(), [bakery.LOGIN_OP])
        except bakery.DischargeRequiredError as err:
            return _authorization_request(
                macaroon_bakery, derr=err, req_headers=req_headers)
        except bakery.VerificationError:
            return _authorization_request(
                macaroon_bakery, req_headers=req_headers,
                auth_endpoint=request.external_auth_info.url)
        except bakery.PermissionDenied:
            return HttpResponseForbidden()

        # a user is always returned since the authentication middleware creates
        # one if not found
        user = authenticate(request, identity=auth_info.identity)
        login(
            request, user,
            backend='maasserver.macaroon_auth.MacaroonAuthorizationBackend')
        return JsonResponse({'id': user.id, 'username': user.username})
예제 #3
0
    def is_authenticated(self, request):
        if not request.external_auth_info:
            return False

        req_headers = request_headers(request)
        macaroon_bakery = _get_bakery(request)
        auth_checker = macaroon_bakery.checker.auth(
            httpbakery.extract_macaroons(req_headers))
        try:
            auth_info = auth_checker.allow(checkers.AuthContext(),
                                           [bakery.LOGIN_OP])
        except (bakery.DischargeRequiredError, bakery.PermissionDenied):
            return False

        # set the user in the request so that it's considered authenticated. If
        # a user is not found with the username from the identity, it's
        # created.
        username = auth_info.identity.id()
        try:
            user = User.objects.get(username=username)
            if user.userprofile.is_local:
                return False
        except User.DoesNotExist:
            user = User(username=username)
            user.save()

        if not validate_user_external_auth(user, request.external_auth_info):
            return False

        request.user = user
        return True
예제 #4
0
 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.serialize_json()
             }
         }
예제 #5
0
 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)
예제 #6
0
    def __call__(self, request):
        auth_endpoint = Config.objects.get_config('external_auth_url')
        if not auth_endpoint:
            return HttpResponseNotFound('Not found')

        macaroon_bakery = self._setup_bakery(auth_endpoint, request)
        req_headers = request_headers(request)
        auth_checker = macaroon_bakery.checker.auth(
            httpbakery.extract_macaroons(req_headers))
        try:
            auth_info = auth_checker.allow(
                checkers.AuthContext(), [bakery.LOGIN_OP])
        except bakery.DischargeRequiredError as err:
            return self._authorization_request(
                request, req_headers, macaroon_bakery, err)
        except bakery.PermissionDenied:
            return HttpResponseForbidden()

        # a user is always returned since the authentication middleware creates
        # one if not found
        user = authenticate(request, identity=auth_info.identity)
        backend = (
            'maasserver.views.macaroon_auth.MacaroonAuthenticationBackend')
        login(request, user, backend=backend)
        return JsonResponse({'id': user.id, 'username': user.username})
예제 #7
0
    def __call__(self, request):
        if not request.external_auth_info:
            return HttpResponseNotFound('Not found')

        macaroon_bakery = _get_bakery(request)
        req_headers = request_headers(request)
        auth_checker = macaroon_bakery.checker.auth(
            httpbakery.extract_macaroons(req_headers))
        try:
            auth_info = auth_checker.allow(
                checkers.AuthContext(), [bakery.LOGIN_OP])
        except bakery.DischargeRequiredError as err:
            return _authorization_request(
                macaroon_bakery, derr=err, req_headers=req_headers)
        except bakery.VerificationError:
            return _authorization_request(
                macaroon_bakery, req_headers=req_headers,
                auth_endpoint=request.external_auth_info.url,
                auth_domain=request.external_auth_info.domain)
        except bakery.PermissionDenied:
            return HttpResponseForbidden()

        user = authenticate(request, identity=auth_info.identity)
        if user is None:
            # macaroon authentication can return None if the user exists but
            # doesn't have permission to log in
            return HttpResponseForbidden('User login not allowed')

        login(
            request, user,
            backend='maasserver.macaroon_auth.MacaroonAuthorizationBackend')
        return JsonResponse(
            {attr: getattr(user, attr)
             for attr in ('id', 'username', 'is_superuser')})
예제 #8
0
    def is_authenticated(self, request):
        if not request.external_auth_info:
            return False

        req_headers = request_headers(request)
        macaroon_bakery = _get_bakery(request)
        auth_checker = macaroon_bakery.checker.auth(
            httpbakery.extract_macaroons(req_headers))
        try:
            auth_info = auth_checker.allow(
                checkers.AuthContext(), [bakery.LOGIN_OP])
        except (bakery.DischargeRequiredError, bakery.PermissionDenied):
            return False

        # set the user in the request so that it's considered authenticated. If
        # a user is not found with the username from the identity, it's
        # created.
        user, created = User.objects.get_or_create(
            username=auth_info.identity.id(), defaults={'is_superuser': True})

        # Only check the user with IDM again if it wasn't just created
        if not created and not validate_user_external_auth(user):
            return False

        request.user = user
        return True
    def test_authorize_func(self):
        def f(ctx, identity, op):
            self.assertEqual(identity.id(), 'bob')
            if op.entity == 'a':
                return False, None
            elif op.entity == 'b':
                return True, None
            elif op.entity == 'c':
                return True, [
                    checkers.Caveat(location='somewhere', condition='c')
                ]
            elif op.entity == 'd':
                return True, [
                    checkers.Caveat(location='somewhere', condition='d')
                ]
            else:
                self.fail('unexpected entity: ' + op.Entity)

        ops = [
            macaroonbakery.Op('a', 'x'),
            macaroonbakery.Op('b', 'x'),
            macaroonbakery.Op('c', 'x'),
            macaroonbakery.Op('d', 'x')
        ]
        allowed, caveats = macaroonbakery.AuthorizerFunc(f).authorize(
            checkers.AuthContext(), macaroonbakery.SimpleIdentity('bob'), ops)
        self.assertEqual(allowed, [False, True, True, True])
        self.assertEqual(caveats, [
            checkers.Caveat(location='somewhere', condition='c'),
            checkers.Caveat(location='somewhere', condition='d')
        ])
    def test_context_wired_properly(self):
        ctx = checkers.AuthContext({'a': 'aval'})

        class Visited:
            in_f = False
            in_allow = False
            in_get_acl = False

        def f(ctx, identity, op):
            self.assertEqual(ctx.get('a'), 'aval')
            Visited.in_f = True
            return False, None

        macaroonbakery.AuthorizerFunc(f).authorize(
            ctx, macaroonbakery.SimpleIdentity('bob'), ['op1'])
        self.assertTrue(Visited.in_f)

        class TestIdentity(SimplestIdentity, macaroonbakery.ACLIdentity):
            def allow(other, ctx, acls):
                self.assertEqual(ctx.get('a'), 'aval')
                Visited.in_allow = True
                return False

        def get_acl(ctx, acl):
            self.assertEqual(ctx.get('a'), 'aval')
            Visited.in_get_acl = True
            return []

        macaroonbakery.ACLAuthorizer(allow_public=False,
                                     get_acl=get_acl).authorize(
                                         ctx, TestIdentity('bob'), ['op1'])
        self.assertTrue(Visited.in_get_acl)
        self.assertTrue(Visited.in_allow)
예제 #11
0
 def discharge(url, request):
     self.assertEqual(url.path, '/discharge')
     qs = parse_qs(request.body)
     content = {q: qs[q][0] for q in qs}
     m = httpbakery.discharge(checkers.AuthContext(), content, d.key, d,
                              alwaysOK3rd)
     return {'status_code': 200, 'content': {'Macaroon': m.to_dict()}}
예제 #12
0
    def verify(self, macaroons):
        """Verify macaroons and return authenticated user details."""
        auth_checker = self._bakery.checker.auth(macaroons)
        ctx = checkers.AuthContext()

        # raises DischargeRequiredError if invalid auth or expired macaroon
        auth_info = auth_checker.allow(ctx, [bakery.LOGIN_OP])

        user = auth_info.identity
        return {'username': user.username(), 'domain': user.domain()}
예제 #13
0
 def test_acl_authorizer(self):
     ctx = checkers.AuthContext()
     tests = [
         ('no ops, no problem',
          bakery.ACLAuthorizer(allow_public=True, get_acl=lambda x, y: []),
          None,
          [],
          []),
         ('identity that does not implement ACLIdentity; '
          'user should be denied except for everyone group',
          bakery.ACLAuthorizer(
              allow_public=True,
              get_acl=lambda ctx, op: [bakery.EVERYONE] if op.entity == 'a' else ['alice'],
          ),
          SimplestIdentity('bob'),
          [bakery.Op(entity='a', action='a'),
           bakery.Op(entity='b', action='b')],
          [True, False]),
         ('identity that does not implement ACLIdentity with user == Id; '
          'user should be denied except for everyone group',
          bakery.ACLAuthorizer(
              allow_public=True,
              get_acl=lambda ctx, op: [bakery.EVERYONE] if op.entity == 'a' else ['bob'],
          ),
          SimplestIdentity('bob'),
          [bakery.Op(entity='a', action='a'),
           bakery.Op(entity='b', action='b')],
          [True, False]),
         ('permission denied for everyone without AllowPublic',
          bakery.ACLAuthorizer(
              allow_public=False,
              get_acl=lambda x, y: [bakery.EVERYONE],
          ),
          SimplestIdentity('bob'),
          [bakery.Op(entity='a', action='a')],
          [False]),
         ('permission granted to anyone with no identity with AllowPublic',
          bakery.ACLAuthorizer(
              allow_public=True,
              get_acl=lambda x, y: [bakery.EVERYONE],
          ),
          None,
          [bakery.Op(entity='a', action='a')],
          [True])
     ]
     for test in tests:
         allowed, caveats = test[1].authorize(ctx, test[2], test[3])
         self.assertEqual(len(caveats), 0)
         self.assertEqual(allowed, test[4])
예제 #14
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)
예제 #15
0
        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()}}
예제 #16
0
 def do_GET(self):
     '''do_GET implements a handler for the HTTP GET method'''
     ctx = checkers.AuthContext()
     auth_checker = self._bakery.checker.auth(
         httpbakery.extract_macaroons(self.headers))
     try:
         auth_checker.allow(ctx, [TEST_OP])
     except (bakery.PermissionDenied, bakery.VerificationError) as exc:
         return self._write_discharge_error(exc)
     self.send_response(200)
     self.end_headers()
     content_len = int(self.headers.get('content-length', 0))
     content = 'done'
     if self.path != '/no-body' and content_len > 0:
         body = self.rfile.read(content_len)
         content = content + ' ' + body
     self.wfile.write(content.encode('utf-8'))
     return
예제 #17
0
        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'})
예제 #18
0
 def discharge(url, request):
     qs = parse_qs(request.body)
     content = {q: qs[q][0] for q in qs}
     httpbakery.discharge(checkers.AuthContext(), content, d.key, d,
                          ThirdPartyCaveatCheckerF(check))
예제 #19
0
import macaroonbakery.checkers as checkers


class _StoppedClock(object):
    def __init__(self, t):
        self.t = t

    def utcnow(self):
        return self.t


epoch = pytz.utc.localize(
    datetime(year=1900, month=11, day=17, hour=19, minute=00, second=13))
ages = epoch + timedelta(days=1)

test_context = checkers.context_with_clock(checkers.AuthContext(),
                                           _StoppedClock(epoch))


def test_checker():
    c = checkers.Checker()
    c.namespace().register('testns', '')
    c.register('str', 'testns', str_check)
    c.register('true', 'testns', true_check)
    return c


_str_key = checkers.ContextKey('str_check')


def str_context(s):
예제 #20
0
from ._error import (
    ThirdPartyCaveatCheckFailed,
    CaveatNotRecognizedError,
    VerificationError,
)
from ._codec import decode_caveat
from ._macaroon import (
    Macaroon,
    ThirdPartyLocator,
)
from ._versions import VERSION_2
from ._third_party import ThirdPartyCaveatInfo

import macaroonbakery.checkers as checkers

emptyContext = checkers.AuthContext()


def discharge_all(m, get_discharge, local_key=None):
    '''Gathers discharge macaroons for all the third party caveats in m
    (and any subsequent caveats required by those) using get_discharge to
    acquire each discharge macaroon.
    The local_key parameter may optionally hold the key of the client, in
    which case it will be used to discharge any third party caveats with the
    special location "local". In this case, the caveat itself must be "true".
    This can be used be a server to ask a client to prove ownership of the
    private key.
    It returns a list of macaroon with m as the first element, followed by all
    the discharge macaroons.
    All the discharge macaroons will be bound to the primary macaroon.
예제 #21
0
    def test_checkers(self):

        tests = [
            ('nothing in context, no extra checkers', [
                ('something',
                 'caveat "something" not satisfied: caveat not recognized'),
                ('', 'cannot parse caveat "": empty caveat'),
                (' hello', 'cannot parse caveat " hello": caveat starts with'
                 ' space character'),
            ], None),
            ('one failed caveat', [
                ('t:a aval', None),
                ('t:b bval', None),
                ('t:a wrong', 'caveat "t:a wrong" not satisfied: wrong arg'),
            ], None),
            ('time from clock', [
                (checkers.time_before_caveat(datetime.utcnow() +
                                             timedelta(0, 1)).condition, None),
                (checkers.time_before_caveat(NOW).condition,
                 'caveat "time-before 2006-01-02T15:04:05.000123Z" '
                 'not satisfied: macaroon has expired'),
                (checkers.time_before_caveat(NOW - timedelta(0, 1)).condition,
                 'caveat "time-before 2006-01-02T15:04:04.000123Z" '
                 'not satisfied: macaroon has expired'),
                ('time-before bad-date',
                 'caveat "time-before bad-date" not satisfied: '
                 'cannot parse "bad-date" as RFC 3339'),
                (checkers.time_before_caveat(NOW).condition + " ",
                 'caveat "time-before 2006-01-02T15:04:05.000123Z " '
                 'not satisfied: '
                 'cannot parse "2006-01-02T15:04:05.000123Z " as RFC 3339'),
            ], lambda x: checkers.context_with_clock(ctx, TestClock())),
            ('real time', [
                (checkers.time_before_caveat(
                    datetime(year=2010, month=1, day=1)).condition,
                 'caveat "time-before 2010-01-01T00:00:00.000000Z" not '
                 'satisfied: macaroon has expired'),
                (checkers.time_before_caveat(
                    datetime(year=3000, month=1, day=1)).condition, None),
            ], None),
            ('declared, no entries', [
                (checkers.declared_caveat('a', 'aval').condition,
                 'caveat "declared a aval" not satisfied: got a=null, '
                 'expected "aval"'),
                (checkers.COND_DECLARED, 'caveat "declared" not satisfied: '
                 'declared caveat has no value'),
            ], None),
            ('declared, some entries', [
                (checkers.declared_caveat('a', 'aval').condition, None),
                (checkers.declared_caveat('b', 'bval').condition, None),
                (checkers.declared_caveat('spc', ' a b').condition, None),
                (checkers.declared_caveat('a', 'bval').condition,
                 'caveat "declared a bval" not satisfied: '
                 'got a="aval", expected "bval"'),
                (checkers.declared_caveat('a', ' aval').condition,
                 'caveat "declared a  aval" not satisfied: '
                 'got a="aval", expected " aval"'),
                (checkers.declared_caveat('spc', 'a b').condition,
                 'caveat "declared spc a b" not satisfied: '
                 'got spc=" a b", expected "a b"'),
                (checkers.declared_caveat('', 'a b').condition,
                 'caveat "error invalid caveat \'declared\' key """ '
                 'not satisfied: bad caveat'),
                (checkers.declared_caveat('a b', 'a b').condition,
                 'caveat "error invalid caveat \'declared\' key "a b"" '
                 'not satisfied: bad caveat'),
            ],
             lambda x: checkers.context_with_declared(x, {
                 'a': 'aval',
                 'b': 'bval',
                 'spc': ' a b'
             })),
        ]
        checker = checkers.Checker()
        checker.namespace().register('testns', 't')
        checker.register('a', 'testns', arg_checker(self, 't:a', 'aval'))
        checker.register('b', 'testns', arg_checker(self, 't:b', 'bval'))
        ctx = checkers.AuthContext()
        for test in tests:
            print(test[0])
            if test[2] is not None:
                ctx1 = test[2](ctx)
            else:
                ctx1 = ctx
            for check in test[1]:
                err = checker.check_first_party_caveat(ctx1, check[0])
                if check[1] is not None:
                    self.assertEqual(err, check[1])
                else:
                    self.assertIsNone(err)