mitgliederverwaltung/docs/page-permission-route-coverage.md

6.5 KiB
Raw Blame History

Page Permission Route and Test Coverage

This document lists all protected routes, which permission set may access them, and how they are covered by tests.

Protected Routes (Router scope with CheckPagePermission in :browser)

Route own_data read_only normal_user admin
/
/members
/members/new
/members/:id ✓ (linked only)
/members/:id/edit ✓ (linked only)
/members/:id/show/edit ✓ (linked only)
/users
/users/new
/users/:id ✓ (own only) ✓ (own only) ✓ (own only)
/users/:id/edit ✓ (own only) ✓ (own only) ✓ (own only)
/users/:id/show/edit ✓ (own only) ✓ (own only) ✓ (own only)
/settings
/membership_fee_settings
/membership_fee_types
/membership_fee_types/new
/membership_fee_types/:id/edit
/groups
/groups/new
/groups/:slug
/groups/:slug/edit
/statistics
/admin/roles
/admin/roles/new
/admin/roles/:id
/admin/roles/:id/edit
/join_requests (Step 2)
/join_requests/:id (Step 2)

Note: Permission sets define /custom_field_values and related paths, but there are no such routes in the router; those entries are for future use. Step 2 (Approval UI) adds /join_requests and /join_requests/:id for normal_user and admin; routes and permission set entries are not yet implemented; tests exist in check_page_permission_test.exs (describe "join_requests routes" and integration blocks).

Public Paths (no permission check)

  • /auth*, /register, /reset, /sign-in, /sign-out, /confirm*, /password-reset*, /set_locale, /join

The public join page GET /join is explicitly public (Subtask 4); unauthenticated access returns 200 when join form is enabled, 404 when disabled. Unit test: test/mv_web/plugs/check_page_permission_test.exs (plug allows /join); integration: test/mv_web/live/join_live_test.exs.

The join confirmation route GET /confirm_join/:token is public (matched by /confirm*). Unit tests: test/mv_web/controllers/join_confirm_controller_test.exs (stubbed callback, no integration).

Test Coverage

File: test/mv_web/plugs/check_page_permission_test.exs

Unit tests (plug called directly with mock conn)

  • Static: own_data denied /members; read_only allowed /members; flash on denial.
  • Dynamic: read_only allowed /members/123; normal_user allowed /members/456/edit; read_only denied /members/123/edit.
  • read_only / normal_user: denied /admin/roles; read_only denied /members/new.
  • Wildcard: admin allowed /admin/roles, /members/999/edit.
  • Unauthenticated: nil user denied, redirect /sign-in.
  • Public: unauthenticated allowed /auth/sign-in, /register.
  • Error: no role, invalid permission_set_name → denied.
  • Join requests (Step 2): normal_user and admin allowed /join_requests, /join_requests/:id; read_only and own_data denied. Tests fail (red) until routes and permission set are added.

Integration tests (full router, Mitglied = own_data)

Denied (Mitglied gets 302 → /users/:id):

  • /members, /members/new, /users, /users/new, /settings, /membership_fee_settings, /membership_fee_types, /membership_fee_types/new, /groups, /groups/new, /admin/roles, /admin/roles/new
  • /members/:id/edit, /members/:id/show/edit, /users/:id (other user), /users/:id/edit (other), /users/:id/show/edit (other), /membership_fee_types/:id/edit, /groups/:slug, /admin/roles/:id, /admin/roles/:id/edit

Allowed (Mitglied gets 200):

  • /users/:id (own profile), /users/:id/edit, /users/:id/show/edit
  • /members/:id, /members/:id/edit, /members/:id/show/edit for linked member (plug unit tests; full-router tests for linked member skipped: session/LiveView constraints)

Root: GET / redirects Mitglied to profile (root not allowed for own_data).

All protected routes above are either covered by integration “denied” tests for Mitglied or by unit tests for the relevant permission set.

Integration tests (full router, read_only = Vorstand/Buchhaltung)

Allowed (200): /, /members, /members/:id, /users/:id (own profile), /users/:id/edit, /users/:id/show/edit, /groups, /groups/:slug.

Denied (302 → /users/:id): /members/new, /members/:id/edit, /members/:id/show/edit, /users, /users/new, /users/:id (other user), /settings, /membership_fee_settings, /membership_fee_types, /groups/new, /groups/:slug/edit, /admin/roles, /admin/roles/:id.

Integration tests (full router, normal_user = Kassenwart)

Allowed (200): /, /members, /members/new, /members/:id, /members/:id/edit, /members/:id/show/edit, /users/:id (own profile), /users/:id/edit, /users/:id/show/edit, /groups, /groups/:slug.

Denied (302 → /users/:id): /users, /users/new, /users/:id (other user), /settings, /membership_fee_settings, /membership_fee_types, /groups/new, /groups/:slug/edit, /admin/roles, /admin/roles/:id.

Integration tests (full router, admin)

Allowed (200): All protected routes (sample covered: /, /members, /users, /settings, /membership_fee_settings, /admin/roles, /members/:id, /admin/roles/:id, /groups/:slug).

Plug behaviour: reserved segments

The plug treats "new" as a reserved path segment so that patterns like /members/:id and /groups/:slug do not match /members/new or /groups/new. Thus /groups/new is only allowed when the permission set explicitly lists /groups/new (currently only admin).

Role and member_id loading

The plug may reload the user's role (and optionally member_id) before checking page permission. Session/load_from_session can leave the role unloaded; the plug uses Mv.Authorization.Actor.ensure_loaded/1 (and, when needed, loads member_id) so that permission checks always have the required data. No change to session loading is required; this is documented for clarity.