-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #40 from bento-platform/refact/use-bento-lib-boile…
…rplate refact: use bento_lib boilerplate for some authz + app setup
- Loading branch information
Showing
13 changed files
with
177 additions
and
227 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,96 +1,32 @@ | ||
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 | ||
from .routers.grants import grants_router | ||
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__) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.