Can't get policies to work with AshGraphql

I have an action, update_customer_registration, which requires a Customer actor. My Customer policy looks like this:
policies do
policy action(:update_customer_registration) do
authorize_if actor_attribute_equals(:__struct__, __MODULE__)
end
end
policies do
policy action(:update_customer_registration) do
authorize_if actor_attribute_equals(:__struct__, __MODULE__)
end
end
This works as expected when using the application's Ash API directly:
customer
|> Ash.Changeset.new()
|> Ash.Changeset.for_update(:update_customer_registration, %{contact_name: "Test Name"},
actor: customer
)
|> Corp.Ash.Api.update!()
customer
|> Ash.Changeset.new()
|> Ash.Changeset.for_update(:update_customer_registration, %{contact_name: "Test Name"},
actor: customer
)
|> Corp.Ash.Api.update!()
Setting actor to nil, raises an policy error as expected. However, it doesn't seem to work when going through AshGraphql, when the actor is present:
%{
"data" => %{"updateCustomerRegistration" => nil},
"errors" => [%{"code" => "Forbidden", "fields" => [], "locations" => [%{"column" => 5, "line" => 2}], "message" => "forbidden", "path" => ["updateCustomerRegistration"], "short_message" => "forbidden", "vars" => %{}}]
}
%{
"data" => %{"updateCustomerRegistration" => nil},
"errors" => [%{"code" => "Forbidden", "fields" => [], "locations" => [%{"column" => 5, "line" => 2}], "message" => "forbidden", "path" => ["updateCustomerRegistration"], "short_message" => "forbidden", "vars" => %{}}]
}
I know the actor is being set correctly with Ash.PlugHelpers.set_actor() However, when I change the policy to match on any action (policy always() do), it works. There seems to be something specific with specifying the action in the policy that doesn't work with AshGraphql.
13 Replies
Alan Heywood
Alan Heywood2y ago
Do you have AshGraphql.Plug in your :api pipeline?
moxley
moxleyOP2y ago
Yes, I have it in the pipeline And I'm calling Ash.PlugHelpers.set_actor(conn, session_resource), And I inspected session_resource, and it's the Customer struct.
Alan Heywood
Alan Heywood2y ago
My pipeline looks like this:
pipeline :api do
plug(:accepts, ["json"])
plug(:load_from_bearer)
plug(HtWeb.AuthPlug) # Here I am calling Ash.PlugHelpers.set_actor
plug(AshGraphql.Plug) # This is needed as well
end
pipeline :api do
plug(:accepts, ["json"])
plug(:load_from_bearer)
plug(HtWeb.AuthPlug) # Here I am calling Ash.PlugHelpers.set_actor
plug(AshGraphql.Plug) # This is needed as well
end
Assuming you have something similar set up, my next steps would be to set the following to see if it offers any more clues.
config :ash, :policies, log_policy_breakdowns: :error
config :ash, :policies, log_successful_policy_breakdowns: :error
config :ash, :policies, log_policy_breakdowns: :error
config :ash, :policies, log_successful_policy_breakdowns: :error
moxley
moxleyOP2y ago
Yes, my pipeline looks similar to that. Yes, I've set up logging. The policy logging doesn't seem to work when going through AshGraphql through When I enable policy logging, it works correctly when I go direct through my Ash API, but when going through GraphQL, this is the only thing that is added to the log output:
[warning] Corp.Customers.Customer.read
[warning] Corp.Customers.Customer.read
The basic policy integration seems to be working. It just doesn't work when the policy is specific about which action it applies to.
Alan Heywood
Alan Heywood2y ago
From memory, ash_graphql does need to have permission to also :read the thing it's updating, could that be it?
moxley
moxleyOP2y ago
Maybe... That was it! I needed to add the :read action to the policy
Alan Heywood
Alan Heywood2y ago
Great! Glad you got it working. Also I just ran one of my unit tests that uses GQL, and when I turn on the policy logging it does log all the policy logs with breakdown, so i'm not sure why that doesn't work for you.
moxley
moxleyOP2y ago
Yeah, I don't know. That's strange. The other problem I need to solve with this GraphQL query is that it needs to get the Customer from the current actor. I was temporarily allowing the request to pass in the Customer ID, but the requirement is that the session token is the only identifying information that will be passed in. I'm not sure how to do that.
Alan Heywood
Alan Heywood2y ago
Does your actor have a relationship to the customer already?
moxley
moxleyOP2y ago
The actor is the customer Do I need to create a new read action to pass the actor along to the update_customer_registration action?
Alan Heywood
Alan Heywood2y ago
You can create a new read action that gets the current actor from session. Here is what I have on my actor resource:
read :current_actor do
get? true
manual Ht.Actor.Actions.CurrentActorRead
end
read :current_actor do
get? true
manual Ht.Actor.Actions.CurrentActorRead
end
defmodule Ht.Actor.Actions.CurrentActorRead do
use Ash.Resource.ManualRead

@impl true
def read(_, _, _, %{actor: actor}) when not is_nil(actor) do
{:ok, [actor]}
end

def read(_, _, _, _), do: {:ok, []}
end
defmodule Ht.Actor.Actions.CurrentActorRead do
use Ash.Resource.ManualRead

@impl true
def read(_, _, _, %{actor: actor}) when not is_nil(actor) do
{:ok, [actor]}
end

def read(_, _, _, _), do: {:ok, []}
end
Then in your graphql definition:
mutations do
update :update do
identity false
read_action :current_actor
end

...
mutations do
update :update do
identity false
read_action :current_actor
end

...
moxley
moxleyOP2y ago
Nice! I'll try that. Wow, it worked! Thanks @Alan Heywood !
Alan Heywood
Alan Heywood2y ago
You're welcome!

Did you find this page helpful?