Skip to content

fix: enforce scenario RBAC by reading role from request.state.user - #5

Open
MoonWindow wants to merge 1 commit into
aws-samples:mainfrom
MoonWindow:fix/scenarios-rbac-role-lookup
Open

fix: enforce scenario RBAC by reading role from request.state.user#5
MoonWindow wants to merge 1 commit into
aws-samples:mainfrom
MoonWindow:fix/scenarios-rbac-role-lookup

Conversation

@MoonWindow

Copy link
Copy Markdown

What is wrong

api/main.py, in the /api/scenarios handler (get_scenarios, around line 1508), reads the caller's role like this:

user_role = getattr(request.state, "role", "admin") if hasattr(request, "state") else "admin"

But AuthMiddleware.dispatch (in auth.py) stores the authenticated user as a dict on request.state.user:

request.state.user = {"user_id": ..., "username": ..., "email": ..., "role": ...}

It never sets request.state.role. So getattr(request.state, "role", ...) always misses and falls back to the default "admin".

Concrete failure

Every caller is treated as admin for scenario listing, which defeats the per-scene RBAC filter a few lines below:

allowed_roles = v2.get("access", {}).get("roles", [])
if allowed_roles and user_role not in allowed_roles:
    continue

Because user_role is always "admin", this condition never excludes anything. A viewer or analyst user is served scenes whose access.roles do not include their role (for example an admin-only scene). The RBAC restriction on this endpoint is silently a no-op for non-admin users.

The fix

Use the same helper every other handler in this file already uses to read the current user:

user_role = get_current_user(request).get("role", "viewer")

get_current_user (already imported at the top of main.py) reads request.state.user, so the real role is returned and the access.roles filter is actually enforced. The default is "viewer" (least privilege) rather than "admin".

How it was verified

  • Reproduced the attribute-lookup defect with a faithful clone of Starlette's State object (which is exactly what request.state is) plus the verbatim get_current_user body from auth.py: a middleware-populated viewer yields user_role == "admin" with the old code and "viewer" with the fix; the admin-only scene filter admits the viewer before the change and rejects them after.
  • python -m py_compile api/main.py auth.py passes.

Not verified

I did not boot the full FastAPI app end-to-end against live DynamoDB (strands and AWS resources are required to import/run the app), so this was verified at the unit level of the role-resolution logic rather than through a running server request.

The /api/scenarios handler read the role via getattr(request.state, "role", "admin"), but AuthMiddleware stores the authenticated user as request.state.user (a dict), never as request.state.role. The attribute is therefore always missing and the lookup falls back to "admin", so the per-scene access.roles filter never excludes anyone: viewer/analyst users see scenes restricted to roles they do not hold.

Use get_current_user(request).get("role", "viewer"), consistent with every other handler in this file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant