feat(phase-39): role-based access control overhaul + forced password change
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- Add must_change_password field to User model with migration b023 - Add POST /auth/change-password endpoint with password policy validation - Add require_password_changed dependency to block requests until password is changed - Add ChangePasswordModal with live password policy checklist (forced on first login) - Show password policy in CreateUserModal and EditUserModal - Fix backend permissions: tests, campaigns, templates, reports, evidence, worklogs - red_tech/blue_tech: execute only, cannot create tests/campaigns/templates - red_lead/blue_lead: create/edit tests/campaigns/templates, generate reports, no system access - viewer: read-only everywhere, can generate reports - Fix frontend role checks across TestDetailPage, TestDetailHeader, TeamTabs, TestsPage, CampaignsPage, CampaignDetailPage, Sidebar
This commit is contained in:
@@ -143,7 +143,7 @@ def list_tests(
|
||||
def create_test(
|
||||
payload: TestCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("red_tech")),
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Create a new test linked to an existing technique.
|
||||
|
||||
@@ -190,7 +190,7 @@ def create_test(
|
||||
def create_test_from_template(
|
||||
payload: TestTemplateInstantiate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("red_tech")),
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Instantiate a real Test from an existing TestTemplate.
|
||||
|
||||
@@ -289,11 +289,11 @@ def update_test(
|
||||
test_id: uuid.UUID,
|
||||
payload: TestUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Update one or more fields of an existing test.
|
||||
|
||||
Only the original creator or an admin can update.
|
||||
Only leads or admins can update general test fields.
|
||||
The test must be in ``draft`` or ``rejected`` state.
|
||||
"""
|
||||
test = _get_test_or_404(db, test_id)
|
||||
@@ -343,7 +343,7 @@ def update_test_red(
|
||||
test_id: uuid.UUID,
|
||||
payload: TestRedUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("red_tech")),
|
||||
current_user: User = Depends(require_any_role("red_tech", "red_lead")),
|
||||
):
|
||||
"""Red Team updates their fields (allowed in ``draft`` and ``red_executing``)."""
|
||||
test = _get_test_or_404(db, test_id)
|
||||
@@ -387,7 +387,7 @@ def update_test_blue(
|
||||
test_id: uuid.UUID,
|
||||
payload: TestBlueUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("blue_tech")),
|
||||
current_user: User = Depends(require_any_role("blue_tech", "blue_lead")),
|
||||
):
|
||||
"""Blue Team updates their fields (allowed only in ``blue_evaluating``)."""
|
||||
test = _get_test_or_404(db, test_id)
|
||||
@@ -430,7 +430,7 @@ def update_test_blue(
|
||||
def start_execution(
|
||||
test_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("red_tech")),
|
||||
current_user: User = Depends(require_any_role("red_tech", "red_lead")),
|
||||
):
|
||||
"""Move a test from ``draft`` to ``red_executing``."""
|
||||
test = _get_test_or_404(db, test_id)
|
||||
@@ -448,7 +448,7 @@ def start_execution(
|
||||
def submit_red(
|
||||
test_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("red_tech")),
|
||||
current_user: User = Depends(require_any_role("red_tech", "red_lead")),
|
||||
):
|
||||
"""Red Team finalises — move from ``red_executing`` to ``blue_evaluating``."""
|
||||
test = _get_test_or_404(db, test_id)
|
||||
@@ -466,7 +466,7 @@ def submit_red(
|
||||
def submit_blue(
|
||||
test_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("blue_tech")),
|
||||
current_user: User = Depends(require_any_role("blue_tech", "blue_lead")),
|
||||
):
|
||||
"""Blue Team finalises — move from ``blue_evaluating`` to ``in_review``."""
|
||||
test = _get_test_or_404(db, test_id)
|
||||
@@ -484,7 +484,7 @@ def submit_blue(
|
||||
def pause_timer(
|
||||
test_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Depends(require_any_role("red_tech", "blue_tech", "red_lead", "blue_lead")),
|
||||
):
|
||||
"""Pause the running timer for the current phase (red_executing or blue_evaluating)."""
|
||||
test = _get_test_or_404(db, test_id)
|
||||
@@ -502,7 +502,7 @@ def pause_timer(
|
||||
def resume_timer(
|
||||
test_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Depends(require_any_role("red_tech", "blue_tech", "red_lead", "blue_lead")),
|
||||
):
|
||||
"""Resume the paused timer for the current phase."""
|
||||
test = _get_test_or_404(db, test_id)
|
||||
@@ -595,9 +595,9 @@ def update_remediation(
|
||||
test_id: uuid.UUID,
|
||||
payload: TestRemediationUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Update remediation fields on a test (any authenticated user).
|
||||
"""Update remediation fields on a test.
|
||||
|
||||
When ``remediation_status`` transitions to ``'completed'``, an automatic
|
||||
re-test is created (subject to ``MAX_RETEST_COUNT``).
|
||||
|
||||
Reference in New Issue
Block a user