Conversation
Replace the endpoint-keyed exemption set with an endpoint-to-UserRole registry and rewrite the unsafe-method gate to compare the resolved identity against the route's registered minimum, defaulting to ADMIN. Alert Templates' restore and push admit an EDITOR; the tasks batch read keeps its no-credential behaviour at NONE; every other unsafe route relies on the ADMIN default.
There was a problem hiding this comment.
Pull request overview
Introduces route-specific minimum-role authorization while retaining admin-only defaults.
Changes:
- Adds the minimum-role registry and unsafe-method gate.
- Grants Editors access to alert restore/push routes.
- Updates gate wiring, tests, fixtures, and changelog.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
app/api/deps.py |
Implements minimum-role authorization. |
app/inventory/main.py |
Wires the new gate into Inventory. |
app/sep/api/router.py |
Wires the new SEP API gate. |
app/sep/apps/alerts/api_routes.py |
Grants Editors restore/push access. |
app/tasks/main.py |
Wires the new gate into Tasks. |
app/tasks/routes.py |
Registers the history lookup at NONE. |
changelog.d/SEP-1850.changed.md |
Documents authorization changes. |
tests/app/api/test_deps.py |
Covers role resolution and enforcement. |
tests/app/api/test_role_gate.py |
Classifies gated routes by minimum role. |
tests/app/conftest.py |
Updates shared gate overrides. |
tests/app/inventory/conftest.py |
Updates Inventory fixtures. |
tests/app/inventory/test_role_gate.py |
Renames Inventory gate documentation. |
tests/app/inventory/test_settings_routes.py |
Updates Inventory settings fixtures. |
tests/app/sep/api/routes/test_app_state.py |
Updates app-state overrides. |
tests/app/sep/api/routes/test_connectivity_check.py |
Updates connectivity overrides. |
tests/app/sep/api/routes/test_settings.py |
Updates settings overrides. |
tests/app/sep/api/routes/test_settings_export.py |
Updates export test overrides. |
tests/app/sep/api/routes/test_settings_proxy.py |
Updates proxy test overrides. |
tests/app/sep/api/test_router.py |
Tests router-level role enforcement. |
tests/app/sep/apps/alerts/test_api_routes.py |
Adds live Editor/Viewer route tests. |
tests/app/sep/apps/atw/conftest.py |
Updates ATW fixtures. |
tests/app/sep/apps/atw/test_batch_api.py |
Updates ATW gate overrides. |
tests/app/sep/apps/mysql_backups/conftest.py |
Updates backup fixtures. |
tests/app/sep/apps/snippets/conftest.py |
Updates snippet fixtures. |
tests/app/sep/apps/snippets/test_api_routes.py |
Updates snippet route tests. |
tests/app/sep/routes/test_stream_logs.py |
Updates renamed test reference. |
tests/app/sep/test_proxy_routes_with_override.py |
Updates proxy gate override. |
tests/app/tasks/conftest.py |
Updates shared Tasks fixtures. |
tests/app/tasks/connectivity/test_routes.py |
Updates connectivity fixtures. |
tests/app/tasks/periodic/conftest.py |
Updates periodic-task fixtures. |
tests/app/tasks/settings/test_routes.py |
Updates Tasks settings fixtures. |
tests/app/tasks/test_role_gate.py |
Renames Tasks gate documentation. |
tests/app/tasks/test_routes.py |
Updates Tasks route overrides. |
Suppressed comments (2)
tests/app/sep/api/test_router.py:855
- This touched docstring still calls the renamed dependency the “admin gate.” Update it to match the role-gate behavior and the symbol asserted below.
mutation must still carry ``BEARER_REQUIRED_DETAIL``; declaring the admin
gate first would answer a bare 401 with a different detail.
tests/app/sep/api/test_router.py:866
- The enclosing test name still refers to the removed admin gate, while this changed assertion targets
require_minimum_role_for_unsafe_methods. Rename it so test reports identify the current behavior.
assert api_deps.index(require_minimum_role_for_unsafe_methods) > api_deps.index(
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…tests Fix Copilot review nits: the batch-history changelog note no longer implies anonymous access, docstrings stop pointing at file paths or ticket references, and the test class/method names for the router-level gate follow the admin-to-role rename.
…ing claims The registration decorator's docstring pinned placement below @router.post as load-bearing; FastAPI's route decorator returns the endpoint unchanged, so the registry lands either way. State the constraint that does hold — below any decorator returning a new function — and reword the test docstring that claimed to guard the non-existent misplacement. Also narrow "would fail any rank comparison" to the minimums above VIEWER, which is what the comparison actually yields for the service principal.
Both new gate suites carried the same patch of CasdoorSDK.get_user with a payload-supplied role, plus the same rationale prose. Lift it to resolve_casdoor_as_role in the shared conftest, beside the casdoor fixtures it builds on, so the next suite needing a rank below administrator reuses it.
Automated QA — PASSVerified all ten Tested items against fresh instances built from the PR head, using a Grafana-backed deployment with real Editor, Viewer and administrator accounts:
Separately, I enumerated the registry independently of the suite — walking every Two notes on the classification that might be worth a line in the description rather than any code change:
Observations — pre-existing, out of scope
|
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||














Summary
The unsafe-method gate in
app/api/deps.pydecided authorization on oneis_adminboolean, with an endpoint-keyed exemption set (
_NON_ADMIN_MUTATIONS/allow_non_admin_mutation) carved out for the single route that had to stay open tonon-admins. It now resolves a minimum
UserRoleper route and compares thecaller's rank against it.
The gate (
require_minimum_role_for_unsafe_methods, renamed fromrequire_admin_for_unsafe_methods):GET/HEAD/OPTIONS) return before anything is resolved;minimum_role_for(request.scope.get("route"))reads the matched route'sendpointout of the registry — no request path or prefix is compared anywherein the gate;
UserRole.NONEminimum returns before the Bearer check, so the gateimposes no authentication of its own on a route it waives;
SEP_INTERNAL_TOKENservice principal is admitted by identity, ahead of therank comparison — it holds
UserRole.VIEWERand would fail any comparison;user.role < minimumrefuses with 403.The registry.
require_minimum_role(role)is a decorator factory registering anendpoint object;
minimum_role_foris the single home of the default,DEFAULT_MINIMUM_ROLE = UserRole.ADMIN. An unregistered endpoint, a request thatmatched no route, and a matched object without an
.endpointattribute all resolveto
ADMIN, so a route added without an authorization decision ships admin-only.Authorization changes, explicitly. Of the 97 gated unsafe route-methods across
the three sub-apps, 94 keep an administrator minimum. Three do not:
POST /api/apps/alerts/restoreEDITORPOST /api/apps/alerts/pushEDITORPOST /api/tasks/history/latestNONEPOST /api/apps/alerts/pagerdutyand/pagerduty/deletesit on the same router anddeliberately take the
ADMINdefault: they persist third-party routing and anintegration key, not alert-template content. Reads are untouched — no read-side
gating is introduced. Behaviour at an
ADMINminimum is identical to before, becauseBaseUser.is_adminis itselfself.role >= UserRole.ADMIN.Removed, not deprecated.
allow_non_admin_mutationand_NON_ADMIN_MUTATIONSare deleted, and
require_admin_for_unsafe_methods/RequireAdminForUnsafeMethodsare renamed with no compatibility re-export —neither old name resolves anywhere in the repo after this change (the only surviving
mention is the changelog fragment, which documents the rename for anyone carrying an
out-of-tree app package). 27 of the 33 changed files reference the renamed gate symbol; in most of them the rename is the only change.
Tests. The three
test_admin_gate.pyfiles becometest_role_gate.py. Thecross-app oracle changes from "exactly one route is exempt" to an equality against a
named map of every non-
ADMINroute and its minimum, so opening or closing a surfacefails the test until the map names it. A new
TestRoleGateclass in the alerts suitedrives the real gate over HTTP at Editor, Viewer and admin ranks — its fixture pops
the gate and user dependency overrides so the live gate runs, and the Viewer-403 rows
are the canary that the pop landed.
Tested
All Editor and Viewer scenarios need an auth provider that reports those ranks, so
run them against a SEP deployment using the Grafana auth provider with PMM
configured. Casdoor deployments cannot produce an Editor at all and are expected to
behave exactly as before.
viewer).Known limitations
inventory,atwandmysql_backups, all of which remain administrator-only under this classification, so Alert Templates is absent there. The value of the tier in that deployment is that the vocabulary is correct in advance — an app joining the embedded set carries a considered minimum instead of inheriting a blanket admin gate.TaskExecutionApp's generated create/update/delete and/execute) are closures local to their builders, so there is no function object to register and they cannot express a minimum belowADMIN. Every one of them is classifiedADMIN, so nothing is blocked today; giving the framework a declarative knob is separate work with no consumer yet.ADMINeven though a rank-based reading would put them lower: both forward the caller's own bearer to the tasks service and dispatchPOST /execute/{name}, which staysADMIN, so an Editor tier on the outer route would clear this gate and then be refused downstream. The ticket records the decision under Out of Scope.add_api_routethat reuses a handler rather than a defect here.Checklist
Database migrations generated if models changed ((N/A for this change — no models touched)make makemigrations)User-facing changes documented (README, inline help, UI text)(N/A for this change — no UI or README surface changes; the behaviour change is recorded in the changelog fragment)Configuration changes documented with examples(N/A for this change — no new configuration)