AE
Ash Elixir•2y ago
moxley

Actor not passed to `read` action after `create` action

I have a resource with a default :create action and :read action, and an AshGraphql mutation that calls :create. The resource has a module-based policy that requires the actor have a certain "role". When I test the mutation, the policy allows the actor to perform the :create, but when it checks the :read action that happens at the end of the mutation, the actor passed to the policy is nil. What makes this especially confusing is that I have other resources that are set up the same way, and they don't have this nil actor problem. I'll post my code in the comments below.
4 Replies
moxley
moxleyOP•2y ago
defmodule GF.Announcements.Announcement2 do
@moduledoc """
An announcement record, as an Ash resource.
"""

use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshGraphql.Resource],
authorizers: [Ash.Policy.Authorizer]

alias GF.Members.ActiveMemberPolicy
alias GF.Repo

multitenancy do
strategy :attribute
attribute :org_id
global? true
end

postgres do
table "announcements"
repo Repo
end

actions do
defaults [:create, :read]
end

attributes do
integer_primary_key :id

attribute :body, :string
attribute :subject, :string
attribute :org_id, :integer
create_timestamp :inserted_at
update_timestamp :updated_at
end

policies do
policy always() do
authorize_if {ActiveMemberPolicy, role: :group_announcements}
end
end

graphql do
type :announcement

mutations do
create :create_announcement, :create
end
end
end
defmodule GF.Announcements.Announcement2 do
@moduledoc """
An announcement record, as an Ash resource.
"""

use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshGraphql.Resource],
authorizers: [Ash.Policy.Authorizer]

alias GF.Members.ActiveMemberPolicy
alias GF.Repo

multitenancy do
strategy :attribute
attribute :org_id
global? true
end

postgres do
table "announcements"
repo Repo
end

actions do
defaults [:create, :read]
end

attributes do
integer_primary_key :id

attribute :body, :string
attribute :subject, :string
attribute :org_id, :integer
create_timestamp :inserted_at
update_timestamp :updated_at
end

policies do
policy always() do
authorize_if {ActiveMemberPolicy, role: :group_announcements}
end
end

graphql do
type :announcement

mutations do
create :create_announcement, :create
end
end
end
defmodule GF.Members.ActiveMemberPolicy do
use Ash.Policy.SimpleCheck

require Logger

alias GF.Members.Member

# This is used when logging a breakdown of how a policy is applied - see Logging below.
def describe(_) do
"Member is active and has given role"
end

# The context here may have a changeset, query, resource, and api module, depending
# on the action being run.
# `match?` should return true or false, and answer the statement being posed in the description,
# i.e "is the actor old enough?"
def match?(%GF.Members.Member{} = member, %{resource: _resource} = _context, opts) do
dbg({:match1, member.roles, member.active?, opts})
# ... skipping the logic here for brevity
end

def match?(%GF.Members.Member2{} = member, context, opts) do
dbg({:match2, member.roles, member.active, opts})
member = GF.Members.Member2.to_ecto(member)
match?(member, context, opts)
end

def match?(member, _context, opts) do
# Here, the member is nil
dbg({:match3, member, opts})
false
end
end
defmodule GF.Members.ActiveMemberPolicy do
use Ash.Policy.SimpleCheck

require Logger

alias GF.Members.Member

# This is used when logging a breakdown of how a policy is applied - see Logging below.
def describe(_) do
"Member is active and has given role"
end

# The context here may have a changeset, query, resource, and api module, depending
# on the action being run.
# `match?` should return true or false, and answer the statement being posed in the description,
# i.e "is the actor old enough?"
def match?(%GF.Members.Member{} = member, %{resource: _resource} = _context, opts) do
dbg({:match1, member.roles, member.active?, opts})
# ... skipping the logic here for brevity
end

def match?(%GF.Members.Member2{} = member, context, opts) do
dbg({:match2, member.roles, member.active, opts})
member = GF.Members.Member2.to_ecto(member)
match?(member, context, opts)
end

def match?(member, _context, opts) do
# Here, the member is nil
dbg({:match3, member, opts})
false
end
end
ZachDaniel
ZachDaniel•2y ago
AFAIK we don't do a read action after the create action. It could potentially be a request to load data, but that shouldn't rerun the policies on the resource, only on related resources that it has to read. And it should of course be setting the actor
moxley
moxleyOP•2y ago
When I debug that third match? clause, the context contains a Ash.Resource.Actions.Read action for the GF.Announcements.Announcement2 resource. Oh, wait I found the problem. It's my test! It's calling the read action without an actor! Wow, I spent a couple hours on this. Thanks for being the rubber duck on this!
ZachDaniel
ZachDaniel•2y ago
Happy to help 🙂 This is a good ending for me, means no spelunking 😆 glad you got it worked out

Did you find this page helpful?