Expand scoped demo analysis access
This commit is contained in:
+48
-2
@@ -210,6 +210,12 @@ def create_app() -> FastAPI:
|
||||
guest_safe_read_paths = {
|
||||
f"{settings.api_prefix}/projects",
|
||||
f"{settings.api_prefix}/external/providers",
|
||||
f"{settings.api_prefix}/assistant/status",
|
||||
f"{settings.api_prefix}/assistant/models",
|
||||
f"{settings.api_prefix}/detection/models",
|
||||
f"{settings.api_prefix}/detection/model-assets",
|
||||
f"{settings.api_prefix}/detection/yolo/preflight",
|
||||
f"{settings.api_prefix}/segmentation/models",
|
||||
}
|
||||
normalized_path = raw_path.rstrip("/") or "/"
|
||||
guest_project_read = (
|
||||
@@ -218,7 +224,21 @@ def create_app() -> FastAPI:
|
||||
)
|
||||
is_read_request = request.method in {"GET", "HEAD", "OPTIONS"}
|
||||
if is_read_request:
|
||||
if normalized_path not in guest_safe_read_paths and not guest_project_read:
|
||||
guest_scoped_analysis_read = (
|
||||
query_project_id == str(principal.project_id)
|
||||
and normalized_path.startswith(
|
||||
(
|
||||
f"{settings.api_prefix}/detection/",
|
||||
f"{settings.api_prefix}/segmentation/",
|
||||
f"{settings.api_prefix}/exports/",
|
||||
)
|
||||
)
|
||||
)
|
||||
if (
|
||||
normalized_path not in guest_safe_read_paths
|
||||
and not guest_project_read
|
||||
and not guest_scoped_analysis_read
|
||||
):
|
||||
response = JSONResponse(
|
||||
status_code=403,
|
||||
content=_to_error_payload(
|
||||
@@ -234,6 +254,15 @@ def create_app() -> FastAPI:
|
||||
f"{settings.api_prefix}/demo/workflow",
|
||||
f"{settings.api_prefix}/external/coverage/resolve",
|
||||
}
|
||||
guest_scoped_analysis_post_paths = {
|
||||
f"{settings.api_prefix}/detection/run",
|
||||
f"{settings.api_prefix}/segmentation/run",
|
||||
f"{settings.api_prefix}/qa/detections-vs-reference",
|
||||
f"{settings.api_prefix}/exports/geojson",
|
||||
f"{settings.api_prefix}/exports/metadata",
|
||||
f"{settings.api_prefix}/exports/report",
|
||||
f"{settings.api_prefix}/exports/map-result",
|
||||
}
|
||||
guest_safe_post_suffixes = (
|
||||
"/vector/select",
|
||||
"/raster/bathymetry/select",
|
||||
@@ -247,9 +276,26 @@ def create_app() -> FastAPI:
|
||||
)
|
||||
is_guest_safe_post = request.method == "POST" and (
|
||||
raw_path in guest_safe_post_paths
|
||||
or (
|
||||
raw_path in guest_scoped_analysis_post_paths
|
||||
and query_project_id == str(principal.project_id)
|
||||
)
|
||||
or (
|
||||
query_project_id == str(principal.project_id)
|
||||
and raw_path.startswith(
|
||||
(
|
||||
f"{settings.api_prefix}/detection/runs/",
|
||||
f"{settings.api_prefix}/segmentation/runs/",
|
||||
)
|
||||
)
|
||||
and raw_path.endswith("/qa/reference")
|
||||
)
|
||||
or (
|
||||
raw_path.startswith(project_path_prefix)
|
||||
and raw_path.endswith(guest_safe_post_suffixes)
|
||||
and (
|
||||
raw_path.endswith(guest_safe_post_suffixes)
|
||||
or raw_path.endswith("/assistant/query")
|
||||
)
|
||||
)
|
||||
)
|
||||
if not is_guest_safe_post:
|
||||
|
||||
@@ -126,7 +126,7 @@ def test_login_uses_http_only_session_cookie_and_logout_revokes_browser_access(m
|
||||
assert protected_after_logout.status_code == 401
|
||||
|
||||
|
||||
def test_guest_login_seeds_scoped_demo_and_rejects_mutating_or_cross_project_requests(monkeypatch) -> None:
|
||||
def test_guest_login_exposes_models_but_rejects_management_and_cross_project_requests(monkeypatch) -> None:
|
||||
project_id = UUID("00000000-0000-0000-0000-000000000123")
|
||||
demo = DemoWorkflowResponse(
|
||||
project_id=project_id,
|
||||
@@ -152,7 +152,11 @@ def test_guest_login_seeds_scoped_demo_and_rejects_mutating_or_cross_project_req
|
||||
guest_session = client.get("/api/v1/auth/session")
|
||||
mutation = client.post("/api/v1/projects", json={"name": "Not allowed"})
|
||||
other_project = client.get("/api/v1/projects/00000000-0000-0000-0000-000000000999")
|
||||
unscoped_read = client.get("/api/v1/detection/models")
|
||||
detection_models = client.get("/api/v1/detection/models")
|
||||
segmentation_models = client.get("/api/v1/segmentation/models")
|
||||
cross_project_runs = client.get(
|
||||
"/api/v1/detection/runs?project_id=00000000-0000-0000-0000-000000000999"
|
||||
)
|
||||
cross_project_coverage = client.post(
|
||||
"/api/v1/external/coverage/resolve",
|
||||
json={
|
||||
@@ -172,8 +176,11 @@ def test_guest_login_seeds_scoped_demo_and_rejects_mutating_or_cross_project_req
|
||||
assert mutation.json()["error"] == "GUEST_READ_ONLY"
|
||||
assert other_project.status_code == 403
|
||||
assert other_project.json()["error"] == "GUEST_PROJECT_SCOPE_REQUIRED"
|
||||
assert unscoped_read.status_code == 403
|
||||
assert unscoped_read.json()["error"] == "GUEST_ROUTE_NOT_AVAILABLE"
|
||||
assert detection_models.status_code == 200
|
||||
assert detection_models.json()["data"]["models"]
|
||||
assert segmentation_models.status_code == 200
|
||||
assert cross_project_runs.status_code == 403
|
||||
assert cross_project_runs.json()["error"] == "GUEST_PROJECT_SCOPE_REQUIRED"
|
||||
assert cross_project_coverage.status_code == 403
|
||||
assert cross_project_coverage.json()["error"] == "GUEST_PROJECT_SCOPE_REQUIRED"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user