Files
mimic-big/backend/src/mimic/rbac/matrix.py
knacky 48a1c756bf feat(backend): add USER_MANAGE permission + delta migration (D-015)
Adds `Permission.USER_MANAGE = "user.manage"` to the F11 matrix. rt_lead
already holds ALL_PERMISSIONS so GROUP_PERMISSIONS is unchanged — rt_lead
gets the new permission automatically, rt_operator and soc_analyst get 403.

Alembic migration `202605230001_add_user_manage_permission`:
- inserts the `user.manage` row into `permission`,
- inserts the `(rt_lead, user.manage)` link into `group_permission`,
- exposes `_DELTA_PERMISSIONS` / `_DELTA_GROUP_PERMISSIONS` for parity tests.

The previous `test_frozen_*_matches_runtime` invariant (MA3) is generalised
to "runtime = initial frozen ∪ deltas of every migration in `_DELTAS`". New
migrations register themselves there without editing the historical one.

Verbatim wording from spec-analyst is recorded as D-015 in
`tasks/spec-decisions.md` (separate commit).
2026-05-23 15:52:47 +02:00

99 lines
2.5 KiB
Python

"""F11 permission matrix as code.
This mirrors the spec table 1:1 — keeping it as a single source in code lets
us hash-check it against the spec in CI (spec-analyst task S0.2). Three default
groups are seeded by Alembic and align with the three user types.
"""
from __future__ import annotations
import enum
class Permission(enum.StrEnum):
"""Application permissions. Codes are kebab-case + dotted scope."""
# Engagement
ENGAGEMENT_CREATE = "engagement.create"
ENGAGEMENT_READ = "engagement.read"
ENGAGEMENT_READ_OWN = "engagement.read_own" # scoped to soc_session
ENGAGEMENT_UPDATE = "engagement.update"
ENGAGEMENT_DELETE = "engagement.delete"
ENGAGEMENT_MEMBER_MANAGE = "engagement.member.manage"
ENGAGEMENT_SOC_TOKEN_ISSUE = "engagement.soc_token.issue" # noqa: S105
# Hosts
HOST_CRUD = "host.crud"
# TTPs
TTP_READ = "ttp.read"
TTP_DRAFT = "ttp.draft"
TTP_PROMOTE = "ttp.promote"
# Imports
IMPORT_JOURNAL = "import.journal"
# Scenarios
SCENARIO_CRUD = "scenario.crud"
# Runs
RUN_START = "run.start"
RUN_CONTROL = "run.control"
# Detection / evidence
EVIDENCE_ADD = "evidence.add"
DETECTION_ADD = "detection.add"
# Cleanup
CLEANUP_TRIGGER = "cleanup.trigger"
# Reports
REPORT_GENERATE = "report.generate"
REPORT_READ = "report.read"
# Audit
AUDIT_READ = "audit.read"
# User management (D-015): gates all /api/v1/users CRUD. rt_lead only.
USER_MANAGE = "user.manage"
ALL_PERMISSIONS: tuple[Permission, ...] = tuple(Permission)
class GroupName(enum.StrEnum):
RT_OPERATOR = "rt_operator"
RT_LEAD = "rt_lead"
SOC_ANALYST = "soc_analyst"
# Source-of-truth mapping derived from spec §F11. Verified by tests against
# the spec table.
GROUP_PERMISSIONS: dict[GroupName, frozenset[Permission]] = {
GroupName.RT_OPERATOR: frozenset(
{
Permission.ENGAGEMENT_CREATE,
Permission.ENGAGEMENT_READ,
Permission.HOST_CRUD,
Permission.TTP_READ,
Permission.TTP_DRAFT,
Permission.IMPORT_JOURNAL,
Permission.SCENARIO_CRUD,
Permission.EVIDENCE_ADD,
Permission.CLEANUP_TRIGGER,
Permission.REPORT_READ,
}
),
GroupName.RT_LEAD: frozenset(ALL_PERMISSIONS),
GroupName.SOC_ANALYST: frozenset(
{
Permission.ENGAGEMENT_READ_OWN,
Permission.DETECTION_ADD,
Permission.REPORT_READ,
}
),
}
DEFAULT_GROUPS: tuple[GroupName, ...] = tuple(GroupName)