From e414a62abc3000b129c210b792377577efe66638 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Fri, 24 May 2024 09:46:52 -0400 Subject: [PATCH] refact: use bento_lib boilerplate for some authz + app setup --- bento_authorization_service/authz.py | 98 +++++++++++++++++ bento_authorization_service/config.py | 5 +- bento_authorization_service/main.py | 86 ++------------- .../routers/all_permissions.py | 4 +- bento_authorization_service/routers/grants.py | 19 ++-- bento_authorization_service/routers/groups.py | 17 ++- .../routers/policy/common.py | 8 +- .../routers/schemas.py | 4 +- bento_authorization_service/routers/utils.py | 104 ------------------ bento_authorization_service/utils.py | 10 +- poetry.lock | 46 ++++---- pyproject.toml | 2 +- tests/conftest.py | 1 + 13 files changed, 177 insertions(+), 227 deletions(-) create mode 100644 bento_authorization_service/authz.py delete mode 100644 bento_authorization_service/routers/utils.py diff --git a/bento_authorization_service/authz.py b/bento_authorization_service/authz.py new file mode 100644 index 0000000..e81c0a8 --- /dev/null +++ b/bento_authorization_service/authz.py @@ -0,0 +1,98 @@ +import jwt + +from bento_lib.auth.middleware.fastapi import FastApiAuthMiddleware +from bento_lib.auth.permissions import Permission +from fastapi import Depends, HTTPException, Request, status + +from .config import get_config +from .db import Database, DatabaseDependency +from .dependencies import OptionalBearerToken +from .idp_manager import IdPManager, IdPManagerDependency +from .logger import logger +from .models import ResourceModel +from .policy_engine.evaluation import evaluate +from .utils import extract_token + + +# TODO: Find a way to DI this +config_for_setup = get_config() + + +class LocalFastApiAuthMiddleware(FastApiAuthMiddleware): + def forbidden(self, request: Request) -> HTTPException: + self.mark_authz_done(request) + return HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") + + # Set up our own methods for doing authorization instead of using the middleware default ones, since they make HTTP + # calls to this service, which we should skip and replace with evaluate() calls. + + async def raise_if_no_resource_access( + self, + request: Request, + token: str, + resource: ResourceModel, + required_permission: Permission, + db: Database, + idp_manager: IdPManager, + ) -> None: + try: + eval_res = (await evaluate(idp_manager, db, token, (resource,), (required_permission,)))[0][0] + if not eval_res: + # Forbidden from accessing or deleting this grant + raise self.forbidden(request) + except HTTPException as e: + raise e # Pass it on + except jwt.ExpiredSignatureError: # Straightforward, expired token - don't bother logging + raise self.forbidden(request) + except Exception as e: # Could not properly run evaluate(); return forbidden! + logger.error( + f"Encountered error while checking permissions for request {request.method} {request.url.path}: " + f"{repr(e)}" + ) + raise self.forbidden(request) + + async def require_permission_and_flag( + self, + resource: ResourceModel, + permission: Permission, + request: Request, + authorization: OptionalBearerToken, + db: DatabaseDependency, + idp_manager: IdPManagerDependency, + ): + await self.raise_if_no_resource_access( + request, + extract_token(authorization), + resource, + permission, + db, + idp_manager, + ) + # Flag that we have thought about auth + authz_middleware.mark_authz_done(request) + + def require_permission_dependency(self, resource: ResourceModel, permission: Permission): + async def _inner( + request: Request, + authorization: OptionalBearerToken, + db: DatabaseDependency, + idp_manager: IdPManagerDependency, + ): + return await self.require_permission_and_flag( + resource, + permission, + request, + authorization, + db, + idp_manager, + ) + + return Depends(_inner) + + +authz_middleware = LocalFastApiAuthMiddleware( + config_for_setup.bento_authz_service_url, + debug_mode=config_for_setup.bento_debug, + logger=logger, + enabled=config_for_setup.bento_authz_enabled, +) diff --git a/bento_authorization_service/config.py b/bento_authorization_service/config.py index 6f076f3..6b97b1d 100644 --- a/bento_authorization_service/config.py +++ b/bento_authorization_service/config.py @@ -1,4 +1,4 @@ -from bento_lib.config.pydantic import BentoBaseConfig +from bento_lib.config.pydantic import BentoFastAPIBaseConfig from fastapi import Depends from functools import lru_cache from typing import Annotated @@ -12,14 +12,13 @@ ] -class Config(BentoBaseConfig): +class Config(BentoFastAPIBaseConfig): # the superclass has this as a required field - we ignore it since this IS the authz service # TODO: should this maybe be a [str | None] in bento_lib? bento_authz_service_url: str = "" service_id: str = f"{SERVICE_GROUP}:{SERVICE_ARTIFACT}" service_name: str = "Bento Authorization Service" - service_url_base_path: str = "http://127.0.0.1:5000" # Base path to construct URIs from database_uri: str = "postgres://localhost:5432" diff --git a/bento_authorization_service/main.py b/bento_authorization_service/main.py index 1de57d8..e2c6b24 100644 --- a/bento_authorization_service/main.py +++ b/bento_authorization_service/main.py @@ -1,15 +1,9 @@ -from bento_lib.responses.fastapi_errors import http_exception_handler_factory, validation_exception_handler_factory -from bento_lib.service_info.helpers import build_service_info_from_pydantic_config +from bento_lib.apps.fastapi import BentoFastAPI from bento_lib.service_info.types import BentoExtraServiceInfo -from fastapi import FastAPI, Request, Response, status -from fastapi.exceptions import RequestValidationError -from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse -from starlette.exceptions import HTTPException as StarletteHTTPException -from urllib.parse import urlparse from . import __version__ -from .config import ConfigDependency, get_config +from .authz import authz_middleware +from .config import get_config from .constants import BENTO_SERVICE_KIND, SERVICE_TYPE from .logger import logger from .routers.all_permissions import all_permissions_router @@ -17,80 +11,22 @@ from .routers.groups import groups_router from .routers.policy import policy_router from .routers.schemas import schema_router -from .routers.utils import MarkAuthzDone, public_endpoint_dependency -# TODO: Find a way to DI this -config_for_setup = get_config() +BENTO_SERVICE_INFO: BentoExtraServiceInfo = { + "serviceKind": BENTO_SERVICE_KIND, + "dataService": False, + "gitRepository": "https://github.com/bento-platform/bento_authorization_service", +} -DOCS_URL = "/docs" -OPENAPI_URL = "/openapi.json" -app = FastAPI( - title=config_for_setup.service_name, - root_path=urlparse(config_for_setup.service_url_base_path).path, - docs_url=DOCS_URL, - openapi_url=OPENAPI_URL, - version=__version__, -) -app.add_middleware( - CORSMiddleware, - allow_origins=config_for_setup.cors_origins, - allow_headers=["Authorization"], - allow_credentials=True, - allow_methods=["*"], -) +# TODO: Find a way to DI this +config_for_setup = get_config() -app.exception_handler(StarletteHTTPException)(http_exception_handler_factory(logger, MarkAuthzDone)) -app.exception_handler(RequestValidationError)(validation_exception_handler_factory(MarkAuthzDone)) +app = BentoFastAPI(authz_middleware, config_for_setup, logger, BENTO_SERVICE_INFO, SERVICE_TYPE, __version__) app.include_router(all_permissions_router) app.include_router(grants_router) app.include_router(groups_router) app.include_router(policy_router) app.include_router(schema_router) - - -@app.middleware("http") -async def permissions_enforcement(request: Request, call_next) -> Response: - """ - Permissions enforcement middleware. We require all endpoints to explicitly set a flag to say they have 'thought' - about permissions and decided the request should go through (or be rejected). - """ - - # Allow pre-flight responses through - # Allow docs responses through in development mode - req_path = request.url.path - if request.method == "OPTIONS" or ( - config_for_setup.bento_debug - and (req_path == DOCS_URL or req_path.startswith(f"{DOCS_URL}/") or req_path == OPENAPI_URL) - ): - return await call_next(request) - - # Set flag saying the request hasn't had its permissions determined yet. - request.state.determined_authz = False - - # Run the next steps in the response chain - response: Response = await call_next(request) - - if not request.state.determined_authz: - # Next in response chain didn't properly think about auth; return 403 - logger.warning( - f"Masking true response with 403 since determined_authz was not set: {request.url} {response.status_code}" - ) - return JSONResponse(status_code=status.HTTP_403_FORBIDDEN, content={"detail": "Forbidden"}) - - # Otherwise, return the response as normal - return response - - -BENTO_SERVICE_INFO: BentoExtraServiceInfo = { - "serviceKind": BENTO_SERVICE_KIND, - "dataService": False, - "gitRepository": "https://github.com/bento-platform/bento_authorization_service", -} - - -@app.get("/service-info", dependencies=[public_endpoint_dependency]) -async def service_info(config: ConfigDependency): - return await build_service_info_from_pydantic_config(config, logger, BENTO_SERVICE_INFO, SERVICE_TYPE, __version__) diff --git a/bento_authorization_service/routers/all_permissions.py b/bento_authorization_service/routers/all_permissions.py index 1b4c137..3d2d9a8 100644 --- a/bento_authorization_service/routers/all_permissions.py +++ b/bento_authorization_service/routers/all_permissions.py @@ -2,7 +2,7 @@ from fastapi import APIRouter from pydantic import BaseModel -from .utils import public_endpoint_dependency +from ..authz import authz_middleware __all__ = [ "all_permissions_router", @@ -31,6 +31,6 @@ def response_item_from_permission(p: Permission) -> PermissionResponseItem: ) -@all_permissions_router.get("/", dependencies=[public_endpoint_dependency]) +@all_permissions_router.get("/", dependencies=[authz_middleware.dep_public_endpoint()]) def list_all_permissions() -> list[PermissionResponseItem]: return list(map(response_item_from_permission, PERMISSIONS)) diff --git a/bento_authorization_service/routers/grants.py b/bento_authorization_service/routers/grants.py index af552f3..e5a7de3 100644 --- a/bento_authorization_service/routers/grants.py +++ b/bento_authorization_service/routers/grants.py @@ -3,12 +3,13 @@ from datetime import datetime, timezone from fastapi import APIRouter, HTTPException, Request, status +from ..authz import authz_middleware from ..db import Database, DatabaseDependency from ..dependencies import OptionalBearerToken from ..idp_manager import IdPManager, IdPManagerDependency from ..models import GrantModel, StoredGrantModel from ..policy_engine.evaluation import evaluate -from .utils import raise_if_no_resource_access, extract_token, public_endpoint_dependency, MarkAuthzDone +from ..utils import extract_token __all__ = [ "grants_router", @@ -34,16 +35,18 @@ async def get_grant_and_check_access( idp_manager: IdPManager, ) -> StoredGrantModel: if (grant := await db.get_grant(grant_id)) is not None: - await raise_if_no_resource_access(request, token, grant.resource, required_permission, db, idp_manager) + await authz_middleware.raise_if_no_resource_access( + request, token, grant.resource, required_permission, db, idp_manager + ) return grant # Flag that we have thought about auth - since we are about to raise a NotFound error; consider this OK since # any user could theoretically see some grants. - MarkAuthzDone.mark_authz_done(request) + authz_middleware.mark_authz_done(request) raise grant_not_found(grant_id) -@grants_router.get("/", dependencies=[public_endpoint_dependency]) +@grants_router.get("/", dependencies=[authz_middleware.dep_public_endpoint()]) async def list_grants( db: DatabaseDependency, idp_manager: IdPManagerDependency, @@ -69,12 +72,12 @@ async def create_grant( ) -> StoredGrantModel: # Make sure the token is allowed to edit permissions (in this case, 'editing permissions' # extends to creating grants) on the resource in question. - await raise_if_no_resource_access( + await authz_middleware.raise_if_no_resource_access( request, extract_token(authorization), grant.resource, P_EDIT_PERMISSIONS, db, idp_manager ) # Flag that we have thought about auth - MarkAuthzDone.mark_authz_done(request) + authz_middleware.mark_authz_done(request) # Forbid creating a grant which is expired from the get-go. if grant.expiry is not None and grant.expiry < datetime.now(timezone.utc): @@ -116,7 +119,7 @@ async def get_grant( ) # Flag that we have thought about auth - MarkAuthzDone.mark_authz_done(request) + authz_middleware.mark_authz_done(request) return grant @@ -135,7 +138,7 @@ async def delete_grant( ) # Flag that we have thought about auth - MarkAuthzDone.mark_authz_done(request) + authz_middleware.mark_authz_done(request) # If the above didn't raise anything, delete the grant. await db.delete_grant(grant_id) diff --git a/bento_authorization_service/routers/groups.py b/bento_authorization_service/routers/groups.py index 6a41a25..b0c90d2 100644 --- a/bento_authorization_service/routers/groups.py +++ b/bento_authorization_service/routers/groups.py @@ -3,9 +3,9 @@ from fastapi import APIRouter, HTTPException, status +from ..authz import authz_middleware from ..db import DatabaseDependency from ..models import RESOURCE_EVERYTHING, GroupModel, StoredGroupModel -from .utils import require_permission_dependency __all__ = [ "groups_router", @@ -22,7 +22,9 @@ def group_not_created() -> HTTPException: return HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Group could not be created") -@groups_router.get("/", dependencies=[require_permission_dependency(RESOURCE_EVERYTHING, P_VIEW_PERMISSIONS)]) +@groups_router.get( + "/", dependencies=[authz_middleware.require_permission_dependency(RESOURCE_EVERYTHING, P_VIEW_PERMISSIONS)] +) async def list_groups(db: DatabaseDependency) -> list[StoredGroupModel]: return await db.get_groups() @@ -30,7 +32,7 @@ async def list_groups(db: DatabaseDependency) -> list[StoredGroupModel]: @groups_router.post( "/", status_code=status.HTTP_201_CREATED, - dependencies=[require_permission_dependency(RESOURCE_EVERYTHING, P_EDIT_PERMISSIONS)], + dependencies=[authz_middleware.require_permission_dependency(RESOURCE_EVERYTHING, P_EDIT_PERMISSIONS)], ) async def create_group( group: GroupModel, @@ -47,7 +49,10 @@ async def create_group( raise group_not_created() -@groups_router.get("/{group_id}", dependencies=[require_permission_dependency(RESOURCE_EVERYTHING, P_VIEW_PERMISSIONS)]) +@groups_router.get( + "/{group_id}", + dependencies=[authz_middleware.require_permission_dependency(RESOURCE_EVERYTHING, P_VIEW_PERMISSIONS)], +) async def get_group(group_id: int, db: DatabaseDependency) -> StoredGroupModel: # TODO: sub-groups owned by another group # TODO: test permissions for this endpoint @@ -60,7 +65,7 @@ async def get_group(group_id: int, db: DatabaseDependency) -> StoredGroupModel: @groups_router.delete( "/{group_id}", status_code=status.HTTP_204_NO_CONTENT, - dependencies=[require_permission_dependency(RESOURCE_EVERYTHING, P_EDIT_PERMISSIONS)], + dependencies=[authz_middleware.require_permission_dependency(RESOURCE_EVERYTHING, P_EDIT_PERMISSIONS)], ) async def delete_group(group_id: int, db: DatabaseDependency) -> None: # TODO: sub-groups owned by another group @@ -74,7 +79,7 @@ async def delete_group(group_id: int, db: DatabaseDependency) -> None: @groups_router.put( "/{group_id}", status_code=status.HTTP_204_NO_CONTENT, - dependencies=[require_permission_dependency(RESOURCE_EVERYTHING, P_EDIT_PERMISSIONS)], + dependencies=[authz_middleware.require_permission_dependency(RESOURCE_EVERYTHING, P_EDIT_PERMISSIONS)], ) async def update_group(group_id: int, group: GroupModel, db: DatabaseDependency) -> None: # TODO: sub-groups owned by another group diff --git a/bento_authorization_service/routers/policy/common.py b/bento_authorization_service/routers/policy/common.py index 1da3c3d..3102ee0 100644 --- a/bento_authorization_service/routers/policy/common.py +++ b/bento_authorization_service/routers/policy/common.py @@ -6,13 +6,13 @@ from pydantic import BaseModel from typing import Awaitable, Callable, TypeVar +from bento_authorization_service.authz import authz_middleware from bento_authorization_service.db import Database from bento_authorization_service.dependencies import OptionalBearerToken from bento_authorization_service.idp_manager import IdPManager from bento_authorization_service.logger import logger from bento_authorization_service.models import ResourceModel from bento_authorization_service.policy_engine.evaluation import TokenData -from ..utils import MarkAuthzDone, require_permission_and_flag __all__ = [ "ResponseType", @@ -33,11 +33,13 @@ async def check_non_bearer_token_data_use( ) -> None: if token_data is None: # Using our own token, so this becomes a public endpoint. - MarkAuthzDone.mark_authz_done(request) + authz_middleware.mark_authz_done(request) return async def req_inner(r: ResourceModel): - await require_permission_and_flag(r, P_VIEW_PERMISSIONS, request, authorization, db, idp_manager) + await authz_middleware.require_permission_and_flag( + r, P_VIEW_PERMISSIONS, request, authorization, db, idp_manager + ) await asyncio.gather(*map(req_inner, resources)) diff --git a/bento_authorization_service/routers/schemas.py b/bento_authorization_service/routers/schemas.py index 053e89b..3a08489 100644 --- a/bento_authorization_service/routers/schemas.py +++ b/bento_authorization_service/routers/schemas.py @@ -1,14 +1,14 @@ from fastapi import APIRouter from .. import json_schemas as js -from .utils import public_endpoint_dependency +from ..authz import authz_middleware __all__ = ["schema_router"] schema_router = APIRouter(prefix="/schemas") -@schema_router.get("/token_data.json", dependencies=[public_endpoint_dependency]) +@schema_router.get("/token_data.json", dependencies=[authz_middleware.dep_public_endpoint()]) def token_data_json_schema(): # Public endpoint, no permissions checks required return js.TOKEN_DATA diff --git a/bento_authorization_service/routers/utils.py b/bento_authorization_service/routers/utils.py deleted file mode 100644 index 37c478f..0000000 --- a/bento_authorization_service/routers/utils.py +++ /dev/null @@ -1,104 +0,0 @@ -import jwt -from bento_lib.auth.middleware.mark_authz_done_mixin import MarkAuthzDoneMixin -from bento_lib.auth.permissions import Permission -from fastapi import Depends, HTTPException, Request, status -from fastapi.security import HTTPAuthorizationCredentials - -from ..db import Database, DatabaseDependency -from ..dependencies import OptionalBearerToken -from ..idp_manager import IdPManager, IdPManagerDependency -from ..logger import logger -from ..models import ResourceModel -from ..policy_engine.evaluation import evaluate - -__all__ = [ - "forbidden", - "raise_if_no_resource_access", - "extract_token", - "MarkAuthzDone", - "require_permission_and_flag", - "require_permission_dependency", - "public_endpoint_dependency", -] - - -def forbidden() -> HTTPException: - return HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") - - -async def raise_if_no_resource_access( - request: Request, - token: str, - resource: ResourceModel, - required_permission: Permission, - db: Database, - idp_manager: IdPManager, -) -> None: - try: - eval_res = (await evaluate(idp_manager, db, token, (resource,), (required_permission,)))[0][0] - if not eval_res: - # Forbidden from accessing or deleting this grant - raise forbidden() - except HTTPException as e: - raise e # Pass it on - except jwt.ExpiredSignatureError: # Straightforward, expired token - don't bother logging - raise forbidden() - except Exception as e: # Could not properly run evaluate(); return forbidden! - logger.error( - f"Encountered error while checking permissions for request {request.method} {request.url.path}: " - f"{repr(e)}" - ) - raise forbidden() - - -def extract_token(authorization: HTTPAuthorizationCredentials | None) -> str | None: - return authorization.credentials if authorization is not None else None - - -class MarkAuthzDone(MarkAuthzDoneMixin): - @staticmethod - def mark_authz_done(request: Request): - # Flag that we have thought about auth - request.state.determined_authz = True - - -async def require_permission_and_flag( - resource: ResourceModel, - permission: Permission, - request: Request, - authorization: OptionalBearerToken, - db: DatabaseDependency, - idp_manager: IdPManagerDependency, -): - await raise_if_no_resource_access( - request, - extract_token(authorization), - resource, - permission, - db, - idp_manager, - ) - # Flag that we have thought about auth - MarkAuthzDone.mark_authz_done(request) - - -def require_permission_dependency(resource: ResourceModel, permission: Permission): - async def _inner( - request: Request, - authorization: OptionalBearerToken, - db: DatabaseDependency, - idp_manager: IdPManagerDependency, - ): - return await require_permission_and_flag( - resource, - permission, - request, - authorization, - db, - idp_manager, - ) - - return Depends(_inner) - - -public_endpoint_dependency = Depends(MarkAuthzDone.mark_authz_done) diff --git a/bento_authorization_service/utils.py b/bento_authorization_service/utils.py index 8470636..868c50c 100644 --- a/bento_authorization_service/utils.py +++ b/bento_authorization_service/utils.py @@ -1,7 +1,15 @@ import json +from fastapi.security import HTTPAuthorizationCredentials from pydantic import BaseModel -__all__ = ["json_model_dump_kwargs"] +__all__ = [ + "extract_token", + "json_model_dump_kwargs", +] + + +def extract_token(authorization: HTTPAuthorizationCredentials | None) -> str | None: + return authorization.credentials if authorization is not None else None def json_model_dump_kwargs(x: BaseModel, **kwargs) -> str: diff --git a/poetry.lock b/poetry.lock index 2f36425..9828126 100644 --- a/poetry.lock +++ b/poetry.lock @@ -137,13 +137,13 @@ frozenlist = ">=1.1.0" [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [[package]] @@ -257,18 +257,19 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "bento-lib" -version = "11.8.0" +version = "11.10.0" description = "A set of common utilities and helpers for Bento platform services." optional = false python-versions = "<4.0,>=3.10" files = [ - {file = "bento_lib-11.8.0-py3-none-any.whl", hash = "sha256:9e72e47028da9a31fd3ec39bae70b256cd54785f05d6ae88770a92f509ec7b71"}, - {file = "bento_lib-11.8.0.tar.gz", hash = "sha256:3f7a34ed9144e7749a715e3235da109c295f2cdcf7ebea7e10be7a02c44166ae"}, + {file = "bento_lib-11.10.0-py3-none-any.whl", hash = "sha256:c4be79c3a0a67ee48be4cf84aebab84532b8ac07d5ed77fdfb302513ab076aad"}, + {file = "bento_lib-11.10.0.tar.gz", hash = "sha256:7ea6e0c4477d6789e3bf3b129a3a5324953e2e21a18e575b6248de15d1b562ee"}, ] [package.dependencies] aiofiles = ">=23.2.1,<24.0.0" aiohttp = ">=3.9.4,<4" +fastapi = {version = ">=0.104,<0.112", optional = true, markers = "extra == \"fastapi\""} jsonschema = ">=4.20.0,<5" psycopg2-binary = ">=2.9.9,<3.0" pydantic = ">=2.5.2,<3" @@ -758,19 +759,20 @@ all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" [[package]] name = "fastapi-cli" -version = "0.0.3" +version = "0.0.4" description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi_cli-0.0.3-py3-none-any.whl", hash = "sha256:ae233115f729945479044917d949095e829d2d84f56f55ce1ca17627872825a5"}, - {file = "fastapi_cli-0.0.3.tar.gz", hash = "sha256:3b6e4d2c4daee940fb8db59ebbfd60a72c4b962bcf593e263e4cc69da4ea3d7f"}, + {file = "fastapi_cli-0.0.4-py3-none-any.whl", hash = "sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809"}, + {file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"}, ] [package.dependencies] -fastapi = "*" typer = ">=0.12.3" -uvicorn = {version = ">=0.15.0", extras = ["standard"]} + +[package.extras] +standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"] [[package]] name = "flake8" @@ -1752,13 +1754,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-asyncio" -version = "0.23.6" +version = "0.23.7" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, - {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, ] [package.dependencies] @@ -1909,13 +1911,13 @@ rpds-py = ">=0.7.0" [[package]] name = "requests" -version = "2.31.0" +version = "2.32.2" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"}, + {file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"}, ] [package.dependencies] @@ -2123,13 +2125,13 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, + {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] [[package]] @@ -2597,4 +2599,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "d2a851b602ae9632f7b352a097139fcde38b5d88c3a0b8c39bc5b798d5ca66e3" +content-hash = "e35afe05b88598b7fb430f8e0cfa0012b3a4db9909b6547c21b90dad03aae0cd" diff --git a/pyproject.toml b/pyproject.toml index 23de02b..2aaf00d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ aiodns = "^3.2.0" aiofiles = "^23.2.1" aiohttp = "^3.9.5" asyncpg = "^0.29.0" -bento-lib = "^11.8.0" +bento-lib = {extras = ["fastapi"], version = "^11.10.0"} fastapi = "^0.111.0" jsonschema = "^4.21.1" pydantic = "^2.7.1" diff --git a/tests/conftest.py b/tests/conftest.py index 8fdc341..1d5bd32 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,6 +9,7 @@ import os +os.environ["BENTO_AUTHZ_ENABLED"] = "false" os.environ["BENTO_DEBUG"] = "true" os.environ["CORS_ORIGINS"] = "*"