Policies to check ownership

Hello,

I have read the beta version of the book Ash Framework. It’s something Ash needed! Congratulations @zachdaniel and @sevenseacat , I appreciate it. I’m looking forward to the new chapters!

After reading chapter 6 about Authorization, I have a question.

Imagine a typical domain where we have:

  • Business (The tenant, everything is related to a business)
    • has many User. They are managers. Each manager can modify data related to the business.
  • Posts: Belong to business. The tenant is the business, and any of the managers can be the actor in a field like created_by
  • Comments: Any user can create comments. The actor is the user that created the comment

What I want to check:

  • Only business managers can update/delete comments

I see that the check actor_attribute_equals is very handy, but I need to check the actor following relationships on the instance being updated/deleted:

actor in (Comment -> Post -> Business -> managers)

What is the recommended Ash way to create a policy like this one?

Thanks!

Thank you so much, I’m glad you’ve found it useful!

The built in policy check relates_to_actor_via might be what you’re after?

4 Likes

Yes, I think that’s what I need! I don’t know how I missed that.

A couple more questions:

  1. Does the check load the relationships, or is it something we have to do before using the check?
  2. If the Users are related to Business using an intermediate M2M table with the field :role. Is it possible to check the role of the user to make sure that only :admin users can pass the check?

Thanks!

The check happens in the database query, no need to load anything :slight_smile:

if you need intermediate checks, you can use exists

authorize_if expr(exists(business.role, role == :admin and user_id == ^actor(:id)))
2 Likes

I hope neither of you mind, but I have a question so close to this that I thought I’d reply here instead of creating a different thread. In fact, this thread did answer a question I already had that leads up to the following question.

Forgive me but I’m going to change the domain.

  • a User has many Contacts and
  • a Contact belongs to a User and has many EmailAddresses

I would like to ensure that when a User creates or modifies an EmailAddress, it belongs to a Contact that in turn belongs to a User (who is the actor). In other words, If I have Zach as a Contact but adrian does not, I don’t want adrian to be able to create an EmailAddress for Zach. Correct me if I’m wrong, but in this thread it appears I can do this in an update action with

authorize_if relates_to_actor_via([:contact, :user])

However, during a create action, all I have is the contact_id. How can I make sure that the Contact whose id is the provided contact_id actually belongs to the User/actor? Is that done in the action definition? Is that done in the policies?

1 Like

For this you will need to write a custom simple check.

defmodule MyApp.Checks.CustomCheck do
  use Ash.Policy.SimpleCheck

  def match?(actor, %{subject: subject}, context) do
     ...
  end
end
2 Likes

Perfect, thank you. I got it working.

I don’t know if this is crazy or not, but it works. Is this crazy or crazy like a fox?

defmodule MyApp.Checks.RelatedResourceBelongsToActorCheck do
  @moduledoc false
  use Ash.Policy.SimpleCheck

  def describe(_), do: "Check to make sure that the related resource's user is the actor"

  def match?(actor, %{subject: subject}, context) do
    get_fn = Keyword.get(context, :get_fn, fn _, _ -> nil end)

    case get_fn.(subject.attributes.person_id, actor: actor) do
      {:ok, person} ->
        person.user_id == actor.id

      _ ->
        false
    end
  end
end
policies do
  policy action_type(:create) do
    authorize_if {MyApp.Checks.RelatedResourceBelongsToActorCheck, get_fn: &MyApp.People.get_person/2}
  end
end

My only suggestion would be to use Ash.Changeset.get_attribute over attributes.foo but otherwise yes :slight_smile: