Wire case-level access control into cases router #64

Closed
opened 2026-05-23 01:34:14 +00:00 by Mark · 3 comments
Owner

Parent Issue: #36 (Case-level access control) / #53

Scope

Wire require_case_access dependency into cases router (backend/app/routers/cases.py).

Background

  • CaseMembership model, CaseAccessService, and require_case_access(min_role) dependency already exist but are unused
  • Case POST handler never sets created_by or auto-assigns owner membership
  • All endpoints use only require_role() — no case-level checks

Acceptance Criteria

  • POST /cases/ — sets case.created_by = current_user.id and auto-creates owner membership via CaseAccessService.add_member()
  • GET /cases/ — filters list to only cases where user has membership (or admin bypass)
  • GET /cases/{case_id} — requires viewer case access
  • PUT /cases/{case_id} — requires editor case access
  • DELETE /cases/{case_id} — requires owner case access
  • New endpoint: GET /cases/{case_id}/members — list memberships (requires viewer)
  • New endpoint: POST /cases/{case_id}/members — add member (requires owner)
  • New endpoint: DELETE /cases/{case_id}/members/{user_id} — remove member (requires owner, cannot remove last owner)
  • New endpoint: PUT /cases/{case_id}/members/{user_id} — change role (requires owner, cannot demote self if last owner)
  • Admin role bypasses all case-level checks (already in CaseAccessService)
  • Existing tests updated to create memberships for test cases
  • New tests for all membership management endpoints

Dependencies

  • None (this is the foundation issue — other routers depend on this being done first)

Implementation Notes

  • require_case_access in deps.py extracts case_id from path param and calls CaseAccessService.can_access()
  • The dependency may need adjustment for LIST endpoints where case_id is a query param rather than path param
  • Consider adding a require_case_membership dependency for LIST endpoints that filters by membership
  • Migration needed: backfill CaseMembership for existing cases using created_by field (or assign all existing cases to admin users)
## Parent Issue: #36 (Case-level access control) / #53 ## Scope Wire `require_case_access` dependency into **cases router** (`backend/app/routers/cases.py`). ## Background - `CaseMembership` model, `CaseAccessService`, and `require_case_access(min_role)` dependency already exist but are unused - Case POST handler never sets `created_by` or auto-assigns owner membership - All endpoints use only `require_role()` — no case-level checks ## Acceptance Criteria - [ ] `POST /cases/` — sets `case.created_by = current_user.id` and auto-creates owner membership via `CaseAccessService.add_member()` - [ ] `GET /cases/` — filters list to only cases where user has membership (or admin bypass) - [ ] `GET /cases/{case_id}` — requires `viewer` case access - [ ] `PUT /cases/{case_id}` — requires `editor` case access - [ ] `DELETE /cases/{case_id}` — requires `owner` case access - [ ] New endpoint: `GET /cases/{case_id}/members` — list memberships (requires `viewer`) - [ ] New endpoint: `POST /cases/{case_id}/members` — add member (requires `owner`) - [ ] New endpoint: `DELETE /cases/{case_id}/members/{user_id}` — remove member (requires `owner`, cannot remove last owner) - [ ] New endpoint: `PUT /cases/{case_id}/members/{user_id}` — change role (requires `owner`, cannot demote self if last owner) - [ ] Admin role bypasses all case-level checks (already in `CaseAccessService`) - [ ] Existing tests updated to create memberships for test cases - [ ] New tests for all membership management endpoints ## Dependencies - None (this is the foundation issue — other routers depend on this being done first) ## Implementation Notes - `require_case_access` in `deps.py` extracts `case_id` from path param and calls `CaseAccessService.can_access()` - The dependency may need adjustment for LIST endpoints where `case_id` is a query param rather than path param - Consider adding a `require_case_membership` dependency for LIST endpoints that filters by membership - Migration needed: backfill `CaseMembership` for existing cases using `created_by` field (or assign all existing cases to admin users)
Author
Owner

⏸ Needs Decision

Blocking factors:

  • Didn't meet auto-approve criteria — scope=medium, risk=medium, spec=partial

Scope: medium Risk: medium Spec: partial Type: feature
Summary: Wire case-level access control into cases router
Suggestion: needs investigation
Rationale:

Action: Review each blocking factor above. Remove blocked:needs-decision label to route back to triage.

## ⏸ Needs Decision **Blocking factors:** - **Didn't meet auto-approve criteria** — scope=medium, risk=medium, spec=partial --- **Scope:** medium **Risk:** medium **Spec:** partial **Type:** feature **Summary:** Wire case-level access control into cases router **Suggestion:** needs investigation **Rationale:** **Action:** Review each blocking factor above. Remove `blocked:needs-decision` label to route back to triage.
Author
Owner

GSD VERIFY phase failed after 3 attempts.
═══ Gate: verify-passed (osint-hub#64) ═══
Mode: Tier 1 (functional only)

GATE OK: All gates up to 'implement' pass
── Tier 1: Functional ────────────────────────────────
Running tests...
❌ Tests failed (exit 4)
configfile: pyproject.toml
plugins: anyio-4.13.0, httpx-0.36.2, timeout-2.4.0, xdist-3.8.0, cov-7.1.0, split-0.11.0, asyncio-1.4.0
timeout: 60.0s
timeout method: signal
timeout func_only: False
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 0 items

============================ no tests ran in 0.00s =============================
ERROR: file or directory not found: tests/

============================= test session starts ==============================
platform linux -- Python 3.11.14, pytest-9.0.3, pluggy-1.6.0
rootdir: /home/b/Projects/osint-hub
configfile: pyproject.toml
plugins: anyio-4.13.0, httpx-0.36.2, timeout-2.4.0, xdist-3.8.0, cov-7.1.0, split-0.11.0, asyncio-1.4.0
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 0 items

============================ no tests ran in 0.00s =============================
Running linter...
✅ Lint clean
Running type checker...
✅ Types check clean
Previous test count: unknown

❌ GATE FAIL: verify-passed
- Tests failed with exit code 4
GATE FAIL: verify — blocked at 2026-05-27T00:50:37Z
Class: test-failure
Evidence: Verification failed: 1 failures
Git tag: osint-hub#64-gate-verify-fail-b79631d3de94 (use gate-retry after fixing)

GSD VERIFY phase failed after 3 attempts. ═══ Gate: verify-passed (osint-hub#64) ═══ Mode: Tier 1 (functional only) GATE OK: All gates up to 'implement' pass ── Tier 1: Functional ──────────────────────────────── Running tests... ❌ Tests failed (exit 4) configfile: pyproject.toml plugins: anyio-4.13.0, httpx-0.36.2, timeout-2.4.0, xdist-3.8.0, cov-7.1.0, split-0.11.0, asyncio-1.4.0 timeout: 60.0s timeout method: signal timeout func_only: False asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function collected 0 items ============================ no tests ran in 0.00s ============================= ERROR: file or directory not found: tests/ ============================= test session starts ============================== platform linux -- Python 3.11.14, pytest-9.0.3, pluggy-1.6.0 rootdir: /home/b/Projects/osint-hub configfile: pyproject.toml plugins: anyio-4.13.0, httpx-0.36.2, timeout-2.4.0, xdist-3.8.0, cov-7.1.0, split-0.11.0, asyncio-1.4.0 asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function collected 0 items ============================ no tests ran in 0.00s ============================= Running linter... ✅ Lint clean Running type checker... ✅ Types check clean Previous test count: unknown ❌ GATE FAIL: verify-passed - Tests failed with exit code 4 GATE FAIL: verify — blocked at 2026-05-27T00:50:37Z Class: test-failure Evidence: Verification failed: 1 failures Git tag: osint-hub#64-gate-verify-fail-b79631d3de94 (use gate-retry after fixing)
Author
Owner

GSD PLAN: response missing GATE: line.

GSD PLAN: response missing GATE: line.
Mark closed this issue 2026-06-08 18:20:21 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Mark/osint-hub#64
No description provided.