Merge branch 'main' into feature/371-groups-resource
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
simon 2026-01-27 17:16:34 +01:00
commit 5df1da1573
9 changed files with 632 additions and 27 deletions

View file

@ -501,9 +501,11 @@ defmodule Mv.Authorization.PermissionSets do
%{resource: "Member", action: :read, scope: :linked, granted: true},
%{resource: "Member", action: :update, scope: :linked, granted: true},
# CustomFieldValue: Can read/update custom field values of linked member
# CustomFieldValue: Can read/update/create/destroy custom field values of linked member
%{resource: "CustomFieldValue", action: :read, scope: :linked, granted: true},
%{resource: "CustomFieldValue", action: :update, scope: :linked, granted: true},
%{resource: "CustomFieldValue", action: :create, scope: :linked, granted: true},
%{resource: "CustomFieldValue", action: :destroy, scope: :linked, granted: true},
# CustomField: Can read all (needed for forms)
%{resource: "CustomField", action: :read, scope: :all, granted: true}
@ -678,7 +680,7 @@ Quick reference table showing what each permission set allows:
| **User** (all) | - | - | - | R, C, U, D |
| **Member** (linked) | R, U | - | - | - |
| **Member** (all) | - | R | R, C, U | R, C, U, D |
| **CustomFieldValue** (linked) | R, U | - | - | - |
| **CustomFieldValue** (linked) | R, U, C, D | - | - | - |
| **CustomFieldValue** (all) | - | R | R, C, U, D | R, C, U, D |
| **CustomField** (all) | R | R | R | R, C, U, D |
| **Role** (all) | - | - | - | R, C, U, D |
@ -1053,35 +1055,35 @@ end
### CustomFieldValue Resource Policies
**Location:** `lib/mv/membership/custom_field_value.ex`
**Location:** `lib/membership/custom_field_value.ex`
**Special Case:** Users can access custom field values of their linked member.
**Pattern:** Bypass for READ (list queries), CustomFieldValueCreateScope for create (no filter), HasPermission for read/update/destroy. Create uses a dedicated check because Ash cannot apply filters to create actions.
The bypass `action_type(:read)` is a production-side rule: reading own CFVs (where `member_id == actor.member_id`) is always allowed and overrides Permission-Sets; no further policies are needed for that. It applies to all read actions (get, list, load).
```elixir
defmodule Mv.Membership.CustomFieldValue do
use Ash.Resource, ...
policies do
# SPECIAL CASE: Users can access custom field values of their linked member
# Note: This uses member_id relationship (CustomFieldValue.member_id → Member.id → User.member_id)
policy action_type([:read, :update]) do
description "Users can access custom field values of their linked member"
# Bypass for READ (list queries; expr triggers auto_filter)
bypass action_type(:read) do
description "Users can read custom field values of their linked member"
authorize_if expr(member_id == ^actor(:member_id))
end
# GENERAL: Check permissions from role
policy action_type([:read, :create, :update, :destroy]) do
description "Check permissions from user's role"
authorize_if Mv.Authorization.Checks.HasPermission
# CREATE: CustomFieldValueCreateScope (no filter; Ash rejects filters on create)
# own_data -> create when member_id == actor.member_id; normal_user/admin -> create (scope :all)
policy action_type(:create) do
authorize_if Mv.Authorization.Checks.CustomFieldValueCreateScope
end
# DEFAULT: Forbid
policy action_type([:read, :create, :update, :destroy]) do
forbid_if always()
# READ/UPDATE/DESTROY: HasPermission (scope :linked / :all)
policy action_type([:read, :update, :destroy]) do
authorize_if Mv.Authorization.Checks.HasPermission
end
# DEFAULT: Ash implicitly forbids if no policy authorized (fail-closed)
end
# ...
end
```
@ -1089,11 +1091,13 @@ end
| Action | Mitglied | Vorstand | Kassenwart | Buchhaltung | Admin |
|--------|----------|----------|------------|-------------|-------|
| Read linked | ✅ (special) | ✅ (if linked) | ✅ | ✅ (if linked) | ✅ |
| Update linked | ✅ (special) | ❌ | ✅ | ❌ | ✅ |
| Read linked | ✅ (bypass) | ✅ (if linked) | ✅ | ✅ (if linked) | ✅ |
| Update linked | ✅ (scope :linked) | ❌ | ✅ | ❌ | ✅ |
| Create linked | ✅ (CustomFieldValueCreateScope) | ❌ | ✅ | ❌ | ✅ |
| Destroy linked | ✅ (scope :linked) | ❌ | ✅ | ❌ | ✅ |
| Read all | ❌ | ✅ | ✅ | ✅ | ✅ |
| Create | ❌ | ❌ | ✅ | ❌ | ✅ |
| Destroy | ❌ | ❌ | ✅ | ❌ | ✅ |
| Create all | ❌ | ❌ | ✅ | ❌ | ✅ |
| Destroy all | ❌ | ❌ | ✅ | ❌ | ✅ |
### CustomField Resource Policies