Blame
|
1 | --- |
||||||
| 2 | category: reference |
|||||||
| 3 | tags: |
|||||||
| 4 | - phase-2 |
|||||||
| 5 | - acl |
|||||||
| 6 | - middleware |
|||||||
| 7 | last_updated: 2026-03-13 |
|||||||
| 8 | --- |
|||||||
| 9 | ||||||||
| 10 | # P2-3: ACL Enforcement Middleware |
|||||||
| 11 | ||||||||
| 12 | **Branch:** `feat/P2-3-acl-enforcement` (from `phase-2`) |
|||||||
| 13 | **Status:** Complete — 21/21 tests passing |
|||||||
| 14 | ||||||||
| 15 | ## Deliverables |
|||||||
| 16 | ||||||||
| 17 | ### `app/auth/permissions.py` |
|||||||
| 18 | - String constants: `READ`, `WRITE`, `UPLOAD`, `ADMIN` |
|||||||
| 19 | - `ROLE_PERMISSIONS` dict mapping owner/editor/viewer to permission tuples |
|||||||
| 20 | - `permissions_for_role(role)` — returns tuple or raises `ValueError` |
|||||||
| 21 | - `format_permission_header(permissions)` — comma-separated string |
|||||||
| 22 | ||||||||
| 23 | ### `app/auth/acl.py` |
|||||||
| 24 | - `AclEnforcer` class with constructor injection (`acl_model`, `wiki_model`) |
|||||||
| 25 | - `check_access(user_id, wiki_id)` — looks up ACL entry, returns `{role, permissions}` or raises `AuthError(403)` |
|||||||
| 26 | - `check_public_access(wiki_id)` — parses `owner_id:wiki_slug`, checks `is_public` flag, returns READ or raises |
|||||||
| 27 | - `check_bearer_token(token)` — delegates to `WikiModel.scan_by_token()`, returns editor-level permissions or raises `AuthError(401)` |
|||||||
| 28 | ||||||||
| 29 | ### `app/auth/headers.py` |
|||||||
| 30 | - `build_proxy_headers(email, name, permissions)` — returns dict with `x-otterwiki-name`, `x-otterwiki-email`, `x-otterwiki-permissions` |
|||||||
| 31 | ||||||||
| 32 | ### `app/models/wiki.py` (modified) |
|||||||
| 33 | - Added `scan_by_token(plaintext_token)` — scans all wikis with `mcp_token_hash`, checks each with `bcrypt.checkpw()`, handles pagination |
|||||||
| 34 | - Added `bcrypt` and `Attr` imports |
|||||||
| 35 | ||||||||
| 36 | ### `tests/test_acl.py` |
|||||||
| 37 | - 21 tests using moto for DynamoDB mocking |
|||||||
| 38 | - Covers: role mapping (4), format_permission_header (4), check_access (4), check_public_access (3), check_bearer_token (3), build_proxy_headers (3) |
|||||||
| 39 | ||||||||
| 40 | ## Design Decisions |
|||||||
| 41 | - Bearer token validation scans all wikis then bcrypt-checks each (bcrypt salts are non-deterministic) |
|||||||
| 42 | - `wiki_id` format: `{owner_id}:{wiki_slug}` — parsed by `_parse_wiki_id()` helper |
|||||||
| 43 | - Token-authenticated requests get editor-level permissions (READ, WRITE, UPLOAD) |
|||||||
| 44 | - No new dependencies beyond `bcrypt` (already needed for token hashing) |
|||||||