Ash FilterCheck policy for read and create/update/destroy actions

I have this policy that I use to identify if the user is himself or not:

defmodule Core.Ash.Policies.Self do
  @moduledoc false

  use Ash.Policy.FilterCheck

  @impl Ash.Policy.Check
  def describe(opts) do
    field = Keyword.get(opts, :field, :id)

    "record #{field} matches actor id"
  end

  @impl Ash.Policy.FilterCheck
  def filter(_actor, _context, opts) do
    field = Keyword.get(opts, :field, :id)

    expr(^ref(field) == ^actor(:id))
  end
end

This works fine for read actions, but it will fail for create/update/destroy actions. To fix that I made these changes:

defmodule Core.Ash.Policies.Self do
  @moduledoc false

  use Ash.Policy.FilterCheck

  defoverridable strict_check: 3

  @impl Ash.Policy.Check
  def describe(opts) do
    field = Keyword.get(opts, :field, :id)

    "record #{field} matches actor id"
  end

  @impl Ash.Policy.FilterCheck
  def filter(_actor, _context, opts) do
    field = Keyword.get(opts, :field, :id)

    expr(^ref(field) == ^actor(:id))
  end

  @impl Ash.Policy.Check
  def strict_check(actor, %{changeset: %Ash.Changeset{action_type: :create} = changeset}, opts) do
    field = Keyword.get(opts, :field, :id)

    {:ok, Ash.Changeset.get_attribute(changeset, field) == actor.id}
  end

  def strict_check(actor, %{changeset: %Ash.Changeset{} = changeset}, opts) do
    field = Keyword.get(opts, :field, :id)

    {:ok, Ash.Changeset.get_data(changeset, field) == actor.id}
  end

  def strict_check(actor, authorizer, opts) do
    super(actor, authorizer, opts)
  end
end

Now it seems to work fine, but I’m not sure if this is the best way to do that. Any thoughts?

they should work for update/destroy actions, but not for create actions. I would not suggest using FilterCheck and overriding strict_check, because there are callbacks you need for your version to work consistently around atomic updates. You would want to rework this as a full Ash.Policy.Check module.

I don’t really see how you can have an actor that is creating themselves, but assuming they were creating something else and you wanted to check ownership, you could do:

policy_group some_shared_condition() do
  policy action_type(:create) do
    authorize_if relating_to_actor(:owner)
  end

  policy action_type([:read, :update, :destroy]) do
    authorize_if relates_to_actor_via(:owner)
  end
end