Update documentation for User credentials strategy
Clarify that User.update :own is handled by HasPermission. Fix file path references from lib/mv/accounts to lib/accounts.
This commit is contained in:
parent
d0c1996d6e
commit
370e5af568
3 changed files with 69 additions and 11 deletions
|
|
@ -81,6 +81,17 @@ end
|
|||
| **CREATE** | ✅ Yes (changeset) | `HasPermission` with `scope :own` | strict_check evaluates record → ✅ Authorized |
|
||||
| **DESTROY** | ✅ Yes | `HasPermission` with `scope :own` | strict_check evaluates record → ✅ Authorized |
|
||||
|
||||
**Important: UPDATE Strategy**
|
||||
|
||||
UPDATE is **NOT** a hardcoded bypass. It is controlled by **PermissionSets**:
|
||||
|
||||
- All permission sets (`:own_data`, `:read_only`, `:normal_user`, `:admin`) explicitly grant `User.update :own`
|
||||
- `HasPermission` evaluates `scope :own` when a changeset with record is present
|
||||
- If a permission set is changed to remove `User.update :own`, users with that set will lose the ability to update their credentials
|
||||
- This is intentional - UPDATE is controlled by PermissionSets, not hardcoded
|
||||
|
||||
**Example:** The `read_only` permission set grants `User.update :own` even though it's "read-only" for member data. This allows password changes while keeping member data read-only.
|
||||
|
||||
---
|
||||
|
||||
## Why `scope :own` Is NOT Redundant
|
||||
|
|
|
|||
|
|
@ -930,7 +930,7 @@ This ensures consistent behavior and predictable authorization logic throughout
|
|||
|
||||
### User Resource Policies
|
||||
|
||||
**Location:** `lib/mv/accounts/user.ex`
|
||||
**Location:** `lib/accounts/user.ex`
|
||||
|
||||
**Pattern:** Bypass for READ (list queries), HasPermission for UPDATE (with scope :own).
|
||||
|
||||
|
|
@ -1744,17 +1744,21 @@ end
|
|||
|
||||
**Implementation:**
|
||||
|
||||
Policy in `User` resource places this check BEFORE the general `HasPermission` check:
|
||||
Policy in `User` resource uses a two-tier approach:
|
||||
- **READ**: Bypass with `expr()` for list queries (auto_filter)
|
||||
- **UPDATE**: HasPermission with `scope :own` (evaluates PermissionSets)
|
||||
|
||||
```elixir
|
||||
policies do
|
||||
# SPECIAL CASE: Takes precedence over role permissions
|
||||
policy action_type([:read, :update]) do
|
||||
description "Users can always read and update their own account"
|
||||
# SPECIAL CASE: Users can always READ their own account
|
||||
# Bypass needed for list queries (expr() triggers auto_filter in Ash)
|
||||
bypass action_type(:read) do
|
||||
description "Users can always read their own account"
|
||||
authorize_if expr(id == ^actor(:id))
|
||||
end
|
||||
|
||||
# GENERAL: For other operations (e.g., admin reading other users)
|
||||
# GENERAL: Check permissions from user's role
|
||||
# UPDATE uses scope :own from PermissionSets (all sets grant User.update :own)
|
||||
policy action_type([:read, :create, :update, :destroy]) do
|
||||
authorize_if Mv.Authorization.Checks.HasPermission
|
||||
end
|
||||
|
|
@ -1762,10 +1766,53 @@ end
|
|||
```
|
||||
|
||||
**Why this works:**
|
||||
- Ash evaluates policies top-to-bottom
|
||||
- First matching policy wins
|
||||
- Special case catches own-account access before checking permissions
|
||||
- Even a user with `own_data` (no admin permissions) can update their credentials
|
||||
- READ bypass handles list queries correctly (auto_filter)
|
||||
- UPDATE is handled by HasPermission with `scope :own` from PermissionSets
|
||||
- All permission sets (`:own_data`, `:read_only`, `:normal_user`, `:admin`) grant `User.update :own`
|
||||
- Even a user with `read_only` (read-only for member data) can update their own credentials
|
||||
|
||||
**Important:** UPDATE is NOT an unverrückbarer Spezialfall (hardcoded bypass). It is controlled by PermissionSets. If a permission set is changed to remove `User.update :own`, users with that set will lose the ability to update their credentials. See "User Credentials: Why read_only Can Still Update" below for details.
|
||||
|
||||
### 1a. User Credentials: Why read_only Can Still Update
|
||||
|
||||
**Question:** If `read_only` means "read-only", why can users with this permission set still update their own credentials?
|
||||
|
||||
**Answer:** The `read_only` permission set refers to **member data**, NOT user credentials. All permission sets grant `User.update :own` to allow password changes and profile updates.
|
||||
|
||||
**Implementation Details:**
|
||||
|
||||
1. **UPDATE is controlled by PermissionSets**, not a hardcoded bypass
|
||||
2. **All 4 permission sets** (`:own_data`, `:read_only`, `:normal_user`, `:admin`) explicitly grant:
|
||||
```elixir
|
||||
%{resource: "User", action: :update, scope: :own, granted: true}
|
||||
```
|
||||
3. **HasPermission** evaluates `scope :own` for UPDATE operations (when a changeset with record is present)
|
||||
4. **No special bypass** is needed for UPDATE - it works correctly via HasPermission
|
||||
|
||||
**Why This Design?**
|
||||
|
||||
- **Flexibility:** Permission sets can be modified to change UPDATE behavior
|
||||
- **Consistency:** All permissions are centralized in PermissionSets
|
||||
- **Clarity:** The name "read_only" refers to member data, not user credentials
|
||||
- **Maintainability:** Easy to see what each role can do in PermissionSets module
|
||||
|
||||
**Warning:** If a permission set is changed to remove `User.update :own`, users with that set will **lose the ability to update their credentials**. This is intentional - UPDATE is controlled by PermissionSets, not hardcoded.
|
||||
|
||||
**Example:**
|
||||
```elixir
|
||||
# In PermissionSets.get_permissions(:read_only)
|
||||
resources: [
|
||||
# User: Can read/update own credentials only
|
||||
# IMPORTANT: "read_only" refers to member data, NOT user credentials.
|
||||
# All permission sets grant User.update :own to allow password changes.
|
||||
%{resource: "User", action: :read, scope: :own, granted: true},
|
||||
%{resource: "User", action: :update, scope: :own, granted: true},
|
||||
|
||||
# Member: Can read all members, no modifications
|
||||
%{resource: "Member", action: :read, scope: :all, granted: true},
|
||||
# Note: No Member.update permission - this is the "read_only" part
|
||||
]
|
||||
```
|
||||
|
||||
### 2. Linked Member Email Editing
|
||||
|
||||
|
|
|
|||
|
|
@ -539,7 +539,7 @@ Following the same pattern as Member resource:
|
|||
|
||||
**Tasks:**
|
||||
|
||||
1. ✅ Open `lib/mv/accounts/user.ex`
|
||||
1. ✅ Open `lib/accounts/user.ex`
|
||||
2. ✅ Add `policies` block
|
||||
3. ✅ Add AshAuthentication bypass (registration/login without actor)
|
||||
4. ✅ Add NoActor bypass (test environment only)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue