I’m trying to authorize actions in Ash Policy Authorizer while ensuring that the owner of a record (the person who created it) can always access or modify it. However, my current policy setup still results in a :forbidden
error unless MyApp.Auth.Checks.Can
passes.
Scenario:
- Each User has an Employee (Person) profile.
- A Person has many Leave Requests.
- A Person should be able to cancel their own Leave Requests, even if they don’t have the broader permissions granted by
MyApp.Auth.Checks.Can
.
Current Issue:
The following policy throws :forbidden
unless MyApp.Auth.Checks.Can
passes. I want the policy to allow access if either MyApp.Auth.Checks.Can
or expr(person.user_id == ^actor(:id))
is true
policies do
policy action_type(:read) do
description "Users with permission can read leave request or the request's owner."
authorize_if MyApp.Auth.Checks.Can
authorize_if expr(person.user_id == ^actor(:id))
end
policy action_type([:create, :update, :destroy]) do
description "People with permission can ceate, update or destory"
authorize_if MyApp.Auth.Checks.Can
access_type :strict
end
policy action(:cancel) do
description "A person can cancel a request belonging to him"
authorize_if expr(person.user_id == ^actor(:id))
end
end
Expected Behavior:
- Users with the correct permissions (
MyApp.Auth.Checks.Can
) should be able to manage leave requests. - The owner of a leave request (
person.user_id == ^actor(:id)
) should also be able to access or cancel their own requests, even without additional permissions.
How can I properly configure this policy so that the record owner is always authorized without requiring MyApp.Auth.Checks.Can
to pass?
Additional details on the forbidden error
{:error, %Ash.Error.Forbidden{
bread_crumbs: ["Error returned from: MyApp.TimeOffs.TimeOffRequest.cancel"],
errors: [
%Ash.Error.Forbidden.Policy{
facts: %{
{Ash.Policy.Check.Action, [action: [:cancel], access_type: :filter]} => true,
{Ash.Policy.Check.ActionType, [type: [:read], access_type: :filter]} => false,
{Ash.Policy.Check.Expression, [expr: person.user_id == {:_actor, :id}, access_type: :filter]} => :unknown,
{MyApp.Auth.Checks.Can, [access_type: :filter]} => false
},
actor: %MyApp.Auth.User{
id: "0194dc51-62ac-70f7-9bc9-c7021121d463",
email: "tester@example.com",
current_tenant: "test_org"
},
subject: %Ash.Changeset{
domain: MyApp.TimeOffs,
action: :cancel,
tenant: "test_org",
attributes: %{status: :cancelled},
data: %MyApp.TimeOffs.TimeOffRequest{
id: "0194e540-be3a-7481-a88e-e4afe8c4124f",
starts_at: ~D[2025-02-08],
ends_at: ~D[2025-02-08],
description: "Annual Leave",
status: :pending
}
}
}
]
}}