예제 #1
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')})
예제 #2
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
예제 #3
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})
 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)
예제 #5
0
        def visit(url, request):
            if request.headers.get('Accept') == 'application/json':
                return {'status_code': 200, 'content': {'agent': request.url}}
            cs = SimpleCookie()
            cookies = request.headers.get('Cookie')
            if cookies is not None:
                cs.load(str(cookies))
            public_key = None
            for c in cs:
                if c == 'agent-login':
                    json_cookie = json.loads(
                        base64.b64decode(cs[c].value).decode('utf-8'))
                    public_key = bakery.PublicKey.deserialize(
                        json_cookie.get('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}}
        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
                }
            }
예제 #7
0
    def test_extract_macaroons_from_request(self):
        def encode_macaroon(m):
            macaroons = '[' + utils.macaroon_to_json_string(m) + ']'
            return base64.urlsafe_b64encode(
                utils.to_bytes(macaroons)).decode('ascii')

        req = Request('http://example.com')
        m1 = pymacaroons.Macaroon(version=pymacaroons.MACAROON_V2,
                                  identifier='one')
        req.add_header('Macaroons', encode_macaroon(m1))
        m2 = pymacaroons.Macaroon(version=pymacaroons.MACAROON_V2,
                                  identifier='two')
        jar = requests.cookies.RequestsCookieJar()
        jar.set_cookie(
            utils.cookie(
                name='macaroon-auth',
                value=encode_macaroon(m2),
                url='http://example.com',
            ))
        jar.add_cookie_header(req)

        macaroons = httpbakery.extract_macaroons(req)
        self.assertEquals(len(macaroons), 2)
        macaroons.sort(key=lambda ms: ms[0].identifier)
        self.assertEquals(macaroons[0][0].identifier, m1.identifier)
        self.assertEquals(macaroons[1][0].identifier, m2.identifier)
    def test_extract_macaroons_from_request(self):
        def encode_macaroon(m):
            macaroons = '[' + utils.macaroon_to_json_string(m) + ']'
            return base64.urlsafe_b64encode(utils.to_bytes(macaroons)).decode('ascii')

        req = Request('http://example.com')
        m1 = pymacaroons.Macaroon(version=pymacaroons.MACAROON_V2, identifier='one')
        req.add_header('Macaroons', encode_macaroon(m1))
        m2 = pymacaroons.Macaroon(version=pymacaroons.MACAROON_V2, identifier='two')
        jar = requests.cookies.RequestsCookieJar()
        jar.set_cookie(utils.cookie(
            name='macaroon-auth',
            value=encode_macaroon(m2),
            url='http://example.com',
        ))
        jar.set_cookie(utils.cookie(
            name='macaroon-empty',
            value='',
            url='http://example.com',
        ))
        jar.add_cookie_header(req)

        macaroons = httpbakery.extract_macaroons(req)
        self.assertEquals(len(macaroons), 2)
        macaroons.sort(key=lambda ms: ms[0].identifier)
        self.assertEquals(macaroons[0][0].identifier, m1.identifier)
        self.assertEquals(macaroons[1][0].identifier, m2.identifier)
예제 #9
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)
예제 #10
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})
예제 #11
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
예제 #12
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}}
예제 #13
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)
예제 #14
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
 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
예제 #16
0
def _macaroons_for_domain(cookies, domain):
    '''Return any macaroons from the given cookie jar that
    apply to the given domain name.'''
    req = urllib.request.Request('https://' + domain + '/')
    cookies.add_cookie_header(req)
    return httpbakery.extract_macaroons(req)
예제 #17
0
def _authenticate(request):
    headers = request.headers
    macaroons = httpbakery.extract_macaroons(headers)
    u = bkry.verify(macaroons)
    return u
예제 #18
0
def _macaroons_for_domain(cookies, domain):
    '''Return any macaroons from the given cookie jar that
    apply to the given domain name.'''
    req = urllib.request.Request('https://' + domain + '/')
    cookies.add_cookie_header(req)
    return httpbakery.extract_macaroons(req)