From 4ba8c32c7efe5d037c2a64d78b663702367c29df Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 19 Aug 2024 17:24:45 -0400 Subject: [PATCH 1/2] feat(auth): build authz middlware from FastAPI config --- bento_lib/auth/middleware/base.py | 11 ++++++++++- tests/test_platform_fastapi.py | 14 +++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/bento_lib/auth/middleware/base.py b/bento_lib/auth/middleware/base.py index 5e46123..cd598d4 100644 --- a/bento_lib/auth/middleware/base.py +++ b/bento_lib/auth/middleware/base.py @@ -6,7 +6,7 @@ from abc import ABC, abstractmethod from typing import Any, Callable, Iterable -from bento_lib.config.pydantic import BentoBaseConfig +from bento_lib.config.pydantic import BentoBaseConfig, BentoFastAPIBaseConfig from ..exceptions import BentoAuthException from ..permissions import Permission from ..types import EvaluationResultMatrix, EvaluationResultDict @@ -81,6 +81,15 @@ def build_from_pydantic_config(cls, config: BentoBaseConfig, logger: logging.Log **kwargs, ) + @classmethod + def build_from_fastapi_pydantic_config(cls, config: BentoFastAPIBaseConfig, logger: logging.Logger, **kwargs): + exempt_request_patterns = ( + (r"GET", re.escape(config.service_docs_path)), + (r"GET", re.escape(config.service_openapi_path)), + *kwargs.pop("exempt_request_patterns", ()), + ) + return cls.build_from_pydantic_config(config, logger, exempt_request_patterns=exempt_request_patterns, **kwargs) + @property def enabled(self) -> bool: return self._enabled diff --git a/tests/test_platform_fastapi.py b/tests/test_platform_fastapi.py index cd5d379..41afd54 100644 --- a/tests/test_platform_fastapi.py +++ b/tests/test_platform_fastapi.py @@ -105,7 +105,7 @@ def get_403(): bento_authz_service_url="https://bento-auth.local", cors_origins=("*",), ) -auth_middleware = FastApiAuthMiddleware.build_from_pydantic_config( +auth_middleware = FastApiAuthMiddleware.build_from_fastapi_pydantic_config( app_test_auth_config, logger, include_request_patterns=authz_test_include_patterns, @@ -327,6 +327,18 @@ def test_fastapi_auth_public(fastapi_client_auth: TestClient): rd2 = r.json() assert rd == rd2 + # can get the FastAPI docs + r = fastapi_client_auth.get("/docs") + assert r.status_code == 200 + + # can get the OpenAPI schema + r = fastapi_client_auth.get("/openapi.json") + assert r.status_code == 200 + + # can post to the exempted post endpoint + r = fastapi_client_auth.post("/post-exempted", json=TEST_AUTHZ_VALID_POST_BODY) + assert r.status_code == 200 + # can post to the public post endpoint r = fastapi_client_auth.post("/post-public", json=TEST_AUTHZ_VALID_POST_BODY) assert r.status_code == 200 From ba70ef143226175898787e9210e8d9b90f3b5a43 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 19 Aug 2024 17:30:03 -0400 Subject: [PATCH 2/2] chore(auth): require FastAPI authz middlware with FastAPI config --- bento_lib/auth/middleware/base.py | 11 +---------- bento_lib/auth/middleware/fastapi.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bento_lib/auth/middleware/base.py b/bento_lib/auth/middleware/base.py index cd598d4..5e46123 100644 --- a/bento_lib/auth/middleware/base.py +++ b/bento_lib/auth/middleware/base.py @@ -6,7 +6,7 @@ from abc import ABC, abstractmethod from typing import Any, Callable, Iterable -from bento_lib.config.pydantic import BentoBaseConfig, BentoFastAPIBaseConfig +from bento_lib.config.pydantic import BentoBaseConfig from ..exceptions import BentoAuthException from ..permissions import Permission from ..types import EvaluationResultMatrix, EvaluationResultDict @@ -81,15 +81,6 @@ def build_from_pydantic_config(cls, config: BentoBaseConfig, logger: logging.Log **kwargs, ) - @classmethod - def build_from_fastapi_pydantic_config(cls, config: BentoFastAPIBaseConfig, logger: logging.Logger, **kwargs): - exempt_request_patterns = ( - (r"GET", re.escape(config.service_docs_path)), - (r"GET", re.escape(config.service_openapi_path)), - *kwargs.pop("exempt_request_patterns", ()), - ) - return cls.build_from_pydantic_config(config, logger, exempt_request_patterns=exempt_request_patterns, **kwargs) - @property def enabled(self) -> bool: return self._enabled diff --git a/bento_lib/auth/middleware/fastapi.py b/bento_lib/auth/middleware/fastapi.py index f6314b3..5eeef4f 100644 --- a/bento_lib/auth/middleware/fastapi.py +++ b/bento_lib/auth/middleware/fastapi.py @@ -1,4 +1,5 @@ import logging +import re from fastapi import Depends, FastAPI, Request, Response, status from fastapi.responses import JSONResponse @@ -8,6 +9,7 @@ from bento_lib.auth.middleware.base import BaseAuthMiddleware from bento_lib.auth.permissions import Permission from bento_lib.auth.resources import RESOURCE_EVERYTHING +from bento_lib.config.pydantic import BentoFastAPIBaseConfig from bento_lib.responses.errors import http_error __all__ = [ @@ -16,6 +18,24 @@ class FastApiAuthMiddleware(BaseAuthMiddleware): + @classmethod + def build_from_fastapi_pydantic_config(cls, config: BentoFastAPIBaseConfig, logger: logging.Logger, **kwargs): + """ + Build a FastAPI authorization middlware instance from a Bento Pydantic FastAPI config instance and a logger + instance. This automatically exempts the FastAPI-generated docs OpenAPI schema paths from requiring + authorization. + :param config: instance of the FastAPI subclass of the Bento Pydantic config class. + :param logger: A Python logger to instantiate the FastAPI authorization middlware with. + :param kwargs: Keyword arguments to pass to the FastAPI authorization middleware constructor. + :return: An instance of the authorization middleware. + """ + exempt_request_patterns = ( + (r"GET", re.escape(config.service_docs_path)), + (r"GET", re.escape(config.service_openapi_path)), + *kwargs.pop("exempt_request_patterns", ()), + ) + return cls.build_from_pydantic_config(config, logger, exempt_request_patterns=exempt_request_patterns, **kwargs) + def attach(self, app: FastAPI): """ Attach the middleware to an application. Must be called in order for requests to be checked.