Can't get policies to work with AshGraphql

moxley7725
2023-06-06

moxley7725:

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

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!()

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" => %{}}]
}

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.

_ahey:

Do you have AshGraphql.Plug in your :api pipeline?

moxley7725:

Yes, I have it in the pipeline

moxley7725:

And I’m calling Ash.PlugHelpers.set_actor(conn, session_resource) ,

And I inspected session_resource , and it’s the Customer struct.

_ahey:

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

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

moxley7725:

Yes, my pipeline looks similar to that.

moxley7725:

Yes, I’ve set up logging.

moxley7725:

The policy logging doesn’t seem to work when going through AshGraphql through

moxley7725:

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

moxley7725:

The basic policy integration seems to be working. It just doesn’t work when the policy is specific about which action it applies to.

_ahey:

From memory, ash_graphql does need to have permission to also :read the thing it’s updating, could that be it?

moxley7725:

Maybe…

moxley7725:

That was it!

moxley7725:

I needed to add the :read action to the policy

_ahey:

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.

moxley7725:

Yeah, I don’t know. That’s strange.

moxley7725:

The other problem I need to solve with this GraphQL query is that it needs to get the Customer from the current actor.

moxley7725:

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.

moxley7725:

I’m not sure how to do that.

_ahey:

Does your actor have a relationship to the customer already?

moxley7725:

The actor is the customer

moxley7725:

Do I need to create a new read action to pass the actor along to the update_customer_registration action?

_ahey:

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
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

  ...

moxley7725:

Nice! I’ll try that.

moxley7725:

Wow, it worked!

moxley7725:

Thanks <@717986162282725387> !

_ahey:

You’re welcome!