feat: add OIDC browser sessions with live access enforcement
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
parent
fdea2f5192
commit
a15fe032b0
14 changed files with 807 additions and 31 deletions
|
|
@ -31,6 +31,7 @@ from hub_core.runtime.workload_projection import (
|
|||
from hub_core.runtime.workload_projection_routes import create_workload_projection_router
|
||||
from hub_core.security.boundary import AccessBoundary, AccessController, FactSource
|
||||
from hub_core.security.config import SecuritySettings
|
||||
from hub_core.security.browser import BrowserSettings, BrowserSessions, create_browser_router
|
||||
|
||||
|
||||
def create_app(
|
||||
|
|
@ -42,8 +43,11 @@ def create_app(
|
|||
access_controller: AccessController | None = None,
|
||||
access_facts: FactSource | None = None,
|
||||
security_settings: SecuritySettings | None = None,
|
||||
browser_settings: BrowserSettings | None = None,
|
||||
) -> FastAPI:
|
||||
resolved_settings = settings or RuntimeSettings.from_env()
|
||||
if access_controller is not None and not resolved_settings.enforce_access:
|
||||
raise ValueError("an access controller requires enforcement mode")
|
||||
# Importing this module also constructs the standalone app. Environment
|
||||
# configuration is activated only by an explicit owner-facts composition;
|
||||
# without that adapter the default app stays closed, not import-broken.
|
||||
|
|
@ -59,6 +63,9 @@ def create_app(
|
|||
raise ValueError("security composition requires configuration and authoritative owner facts")
|
||||
security_client = httpx.AsyncClient(trust_env=False)
|
||||
access_controller = security_settings.compose(facts=access_facts, client=security_client)
|
||||
if browser_settings is not None and (not resolved_settings.enforce_access or access_controller is None):
|
||||
raise ValueError("browser sessions require an enforced access controller")
|
||||
browser = BrowserSessions(settings=browser_settings, controller=access_controller) if browser_settings else None
|
||||
resolved_store = port_store or _create_store(resolved_settings)
|
||||
owns_store = port_store is None
|
||||
resolved_repo_projection_client = repo_projection_client
|
||||
|
|
@ -107,6 +114,8 @@ def create_app(
|
|||
try:
|
||||
yield
|
||||
finally:
|
||||
if browser is not None:
|
||||
browser.clear()
|
||||
if refresh_task is not None:
|
||||
refresh_task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
|
|
@ -135,8 +144,11 @@ def create_app(
|
|||
app.state.repository_navigation = repository_navigation
|
||||
app.state.workload_projection = workload_projection
|
||||
app.state.access_controller = access_controller
|
||||
app.state.browser_sessions = browser
|
||||
if browser is not None:
|
||||
app.include_router(create_browser_router())
|
||||
if resolved_settings.enforce_access:
|
||||
app.add_middleware(AccessBoundary, host=app, controller=access_controller)
|
||||
app.add_middleware(AccessBoundary, host=app, controller=access_controller, browser=browser)
|
||||
|
||||
@app.get("/healthz", response_model=HealthResponse, tags=["system"])
|
||||
async def healthz() -> HealthResponse:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue