Commit 836ff0
2026-03-13 19:41:59 Claude (MCP): [mcp] Add P2-3 ACL enforcement summary| /dev/null .. Dev/P2-3_Summary.md | |
| @@ 0,0 1,44 @@ | |
| + | --- |
| + | category: reference |
| + | tags: |
| + | - phase-2 |
| + | - acl |
| + | - middleware |
| + | last_updated: 2026-03-13 |
| + | --- |
| + | |
| + | # P2-3: ACL Enforcement Middleware |
| + | |
| + | **Branch:** `feat/P2-3-acl-enforcement` (from `phase-2`) |
| + | **Status:** Complete — 21/21 tests passing |
| + | |
| + | ## Deliverables |
| + | |
| + | ### `app/auth/permissions.py` |
| + | - String constants: `READ`, `WRITE`, `UPLOAD`, `ADMIN` |
| + | - `ROLE_PERMISSIONS` dict mapping owner/editor/viewer to permission tuples |
| + | - `permissions_for_role(role)` — returns tuple or raises `ValueError` |
| + | - `format_permission_header(permissions)` — comma-separated string |
| + | |
| + | ### `app/auth/acl.py` |
| + | - `AclEnforcer` class with constructor injection (`acl_model`, `wiki_model`) |
| + | - `check_access(user_id, wiki_id)` — looks up ACL entry, returns `{role, permissions}` or raises `AuthError(403)` |
| + | - `check_public_access(wiki_id)` — parses `owner_id:wiki_slug`, checks `is_public` flag, returns READ or raises |
| + | - `check_bearer_token(token)` — delegates to `WikiModel.scan_by_token()`, returns editor-level permissions or raises `AuthError(401)` |
| + | |
| + | ### `app/auth/headers.py` |
| + | - `build_proxy_headers(email, name, permissions)` — returns dict with `x-otterwiki-name`, `x-otterwiki-email`, `x-otterwiki-permissions` |
| + | |
| + | ### `app/models/wiki.py` (modified) |
| + | - Added `scan_by_token(plaintext_token)` — scans all wikis with `mcp_token_hash`, checks each with `bcrypt.checkpw()`, handles pagination |
| + | - Added `bcrypt` and `Attr` imports |
| + | |
| + | ### `tests/test_acl.py` |
| + | - 21 tests using moto for DynamoDB mocking |
| + | - Covers: role mapping (4), format_permission_header (4), check_access (4), check_public_access (3), check_bearer_token (3), build_proxy_headers (3) |
| + | |
| + | ## Design Decisions |
| + | - Bearer token validation scans all wikis then bcrypt-checks each (bcrypt salts are non-deterministic) |
| + | - `wiki_id` format: `{owner_id}:{wiki_slug}` — parsed by `_parse_wiki_id()` helper |
| + | - Token-authenticated requests get editor-level permissions (READ, WRITE, UPLOAD) |
| + | - No new dependencies beyond `bcrypt` (already needed for token hashing) |