Update documentation: Remove NoActor bypass references

This commit is contained in:
Moritz 2026-01-23 20:18:28 +01:00
parent 0f48a9b15a
commit d9eb131d96
Signed by: moritz
GPG key ID: 1020A035E5DD0824
5 changed files with 73 additions and 137 deletions

View file

@ -946,12 +946,7 @@ defmodule Mv.Accounts.User do
authorize_if always()
end
# 2. NoActor Bypass (test environment only, for test fixtures)
bypass action_type([:create, :read, :update, :destroy]) do
authorize_if Mv.Authorization.Checks.NoActor
end
# 3. SPECIAL CASE: Users can always READ their own account
# 2. SPECIAL CASE: Users can always READ their own account
# Bypass needed for list queries (expr() triggers auto_filter in Ash)
# UPDATE is handled by HasPermission below (scope :own works with changesets)
bypass action_type(:read) do
@ -959,7 +954,7 @@ defmodule Mv.Accounts.User do
authorize_if expr(id == ^actor(:id))
end
# 4. GENERAL: Check permissions from user's role
# 3. GENERAL: Check permissions from user's role
# - :own_data → can UPDATE own user (scope :own via HasPermission)
# - :read_only → can UPDATE own user (scope :own via HasPermission)
# - :normal_user → can UPDATE own user (scope :own via HasPermission)
@ -969,7 +964,7 @@ defmodule Mv.Accounts.User do
authorize_if Mv.Authorization.Checks.HasPermission
end
# 5. DEFAULT: Ash implicitly forbids if no policy authorizes (fail-closed)
# 4. DEFAULT: Ash implicitly forbids if no policy authorizes (fail-closed)
end
# ...
@ -1007,12 +1002,7 @@ defmodule Mv.Membership.Member do
use Ash.Resource, ...
policies do
# 1. NoActor Bypass (test environment only, for test fixtures)
bypass action_type([:create, :read, :update, :destroy]) do
authorize_if Mv.Authorization.Checks.NoActor
end
# 2. SPECIAL CASE: Users can always READ their linked member
# 1. SPECIAL CASE: Users can always READ their linked member
# Bypass needed for list queries (expr() triggers auto_filter in Ash)
# UPDATE is handled by HasPermission below (scope :linked works with changesets)
bypass action_type(:read) do
@ -1020,7 +1010,7 @@ defmodule Mv.Membership.Member do
authorize_if expr(id == ^actor(:member_id))
end
# 3. GENERAL: Check permissions from role
# 2. GENERAL: Check permissions from role
# - :own_data → can UPDATE linked member (scope :linked via HasPermission)
# - :read_only → can READ all members (scope :all), no update permission
# - :normal_user → can CRUD all members (scope :all)
@ -2629,45 +2619,16 @@ This section clarifies three different mechanisms for bypassing standard authori
### Overview
The codebase uses three authorization bypass mechanisms:
The codebase uses two authorization bypass mechanisms:
1. **NoActor** - Test-only bypass (compile-time secured)
2. **system_actor** - Admin user for systemic operations
3. **authorize?: false** - Bootstrap bypass for circular dependencies
1. **system_actor** - Admin user for systemic operations
2. **authorize?: false** - Bootstrap bypass for circular dependencies
**All three are necessary and serve different purposes.**
**Both are necessary and serve different purposes.**
### 1. NoActor Check
**Note:** The NoActor bypass has been removed to prevent masking authorization bugs in tests. All tests now explicitly use `system_actor` for authorization.
**Purpose:** Allows CRUD operations without actor in test environment only.
**Implementation:**
```elixir
# lib/mv/authorization/checks/no_actor.ex
@allow_no_actor_bypass Application.compile_env(:mv, :allow_no_actor_bypass, false)
def match?(nil, _context, _opts) do
@allow_no_actor_bypass # true in test.exs, false elsewhere
end
```
**Security:**
- Compile-time flag (not runtime `Mix.env()` check)
- Default: false (fail-closed)
- Only enabled in `config/test.exs`
**Use Case:** Test fixtures without verbose actor setup:
```elixir
# With NoActor (test environment only)
member = create_member(%{name: "Test"})
# Production behavior (NoActor returns false)
member = create_member(%{name: "Test"}, actor: user)
```
**Trade-off:** May mask tests that should fail without actor. Mitigated by explicit policy tests (e.g., `test/mv/accounts/user_policies_test.exs`).
### 2. System Actor
### 1. System Actor
**Purpose:** Admin user for systemic operations that must always succeed regardless of user permissions.
@ -2708,7 +2669,7 @@ end
- Consistent authorization flow
- Testable
### 3. authorize?: false
### 2. authorize?: false
**Purpose:** Skip policies for bootstrap scenarios with circular dependencies.
@ -2759,21 +2720,17 @@ Mv.Authorization.Role
### Comparison
| Aspect | NoActor | system_actor | authorize?: false |
|--------|---------|--------------|-------------------|
| **Environment** | Test only | All | All |
| **Actor** | nil | Admin user | nil |
| **Policies** | Bypassed | Evaluated | Skipped |
| **Audit Trail** | No | Yes (system@mila.local) | No |
| **Use Case** | Test fixtures | Systemic operations | Bootstrap |
| **Explicit?** | Policy bypass | Function call | Query option |
| Aspect | system_actor | authorize?: false |
|--------|--------------|-------------------|
| **Environment** | All | All |
| **Actor** | Admin user | nil |
| **Policies** | Evaluated | Skipped |
| **Audit Trail** | Yes (system@mila.local) | No |
| **Use Case** | Systemic operations, test fixtures | Bootstrap |
| **Explicit?** | Function call | Query option |
### Decision Guide
**Use NoActor when:**
- ✅ Writing test fixtures
- ✅ Compile-time guard ensures test-only
**Use system_actor when:**
- ✅ Systemic operation must always succeed
- ✅ Email synchronization
@ -2789,7 +2746,7 @@ Mv.Authorization.Role
**DON'T:**
- ❌ Use `authorize?: false` for user-initiated actions
- ❌ Use `authorize?: false` when `system_actor` would work
- ❌ Enable NoActor outside test environment
- ❌ Skip actor in tests (always use system_actor)
### The Circular Dependency Problem
@ -2873,7 +2830,8 @@ end
- Enhanced edge case documentation
**Changes from V2.0:**
- Added "Authorization Bootstrap Patterns" section explaining NoActor, system_actor, and authorize?: false
- Added "Authorization Bootstrap Patterns" section explaining system_actor and authorize?: false
- Removed NoActor bypass (all tests now use system_actor for explicit authorization)
---