Restrict member user link to admins (forbid policy)
Add ForbidMemberUserLinkUnlessAdmin check; forbid_if on Member create/update. Fix member user-link tests: pass :user in params, assert via reload.
This commit is contained in:
parent
4d3a64c177
commit
26fbafdd9d
3 changed files with 186 additions and 7 deletions
|
|
@ -0,0 +1,65 @@
|
|||
defmodule Mv.Authorization.Checks.ForbidMemberUserLinkUnlessAdmin do
|
||||
@moduledoc """
|
||||
Policy check: forbids setting or changing the member–user link unless the actor is admin.
|
||||
|
||||
Used on Member create_member and update_member actions. When the `:user` argument
|
||||
is present (linking a member to a user account), only admins may perform the action.
|
||||
Non-admin users (e.g. normal_user / Kassenwart) can still create and update members
|
||||
as long as they do not pass the `:user` argument.
|
||||
|
||||
## Usage
|
||||
|
||||
In Member resource policies, add **before** the general HasPermission policy:
|
||||
|
||||
policy action_type([:create, :update]) do
|
||||
forbid_if Mv.Authorization.Checks.ForbidMemberUserLinkUnlessAdmin
|
||||
authorize_if Mv.Authorization.Checks.HasPermission
|
||||
end
|
||||
|
||||
## Behaviour
|
||||
|
||||
- If the action has no `:user` argument or it is nil/empty → does not forbid.
|
||||
- If `:user` is set (e.g. `%{id: user_id}`) and actor is not admin → forbids (returns true).
|
||||
- If actor is admin (or system actor) → does not forbid.
|
||||
"""
|
||||
use Ash.Policy.Check
|
||||
|
||||
alias Mv.Authorization.Actor
|
||||
|
||||
@impl true
|
||||
def describe(_opts), do: "forbid setting member–user link unless actor is admin"
|
||||
|
||||
@impl true
|
||||
def strict_check(actor, authorizer, _opts) do
|
||||
actor = Actor.ensure_loaded(actor)
|
||||
|
||||
if user_argument_set?(authorizer) and not Actor.admin?(actor) do
|
||||
{:ok, true}
|
||||
else
|
||||
{:ok, false}
|
||||
end
|
||||
end
|
||||
|
||||
defp user_argument_set?(authorizer) do
|
||||
user_arg = get_user_argument(authorizer)
|
||||
not is_nil(user_arg) and not empty_user_arg?(user_arg)
|
||||
end
|
||||
|
||||
defp get_user_argument(authorizer) do
|
||||
changeset = authorizer.changeset || authorizer.subject
|
||||
|
||||
cond do
|
||||
is_struct(changeset, Ash.Changeset) ->
|
||||
Ash.Changeset.get_argument(changeset, :user)
|
||||
|
||||
is_struct(changeset, Ash.ActionInput) ->
|
||||
Map.get(changeset.arguments || %{}, :user)
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp empty_user_arg?(%{} = m), do: map_size(m) == 0
|
||||
defp empty_user_arg?(_), do: false
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue