I am trying to wrap my head around field_policies, but they don’t quite make sense to me.
From the docs: “If any field policies exist then all fields must be authorized by a field policy.”
So then let’s start with an example:
attributes do
uuid_primary_key :id
attribute :subject, :string, public?: true
attribute :admin_note, :string, public?: true
attribute :message, :string
timestamps()
end
relationships do
belongs_to :user, User
end
policies do
policy action_type(:read) do
authorize_if actor_attribute_equals(:is_admin, true)
authorize_if relates_to_actor_via(:user)
end
end
field_policies do
field_policy :admin_note do
authorize_if actor_attribute_equals(:is_admin, true)
end
field_policy :subject do
authorize_if actor_attribute_equals(:is_admin, true)
authorize_if relates_to_actor_via(:user)
end
end
If I now query the ticket:
iex> Ash.read!(Helpdesk.Support.Ticket, actor: user)
[
#Helpdesk.Support.Ticket<
user: #Ash.NotLoaded<:relationship, field: :user>,
__meta__: #Ecto.Schema.Metadata<:loaded, "tickets">,
id: "4ce1002e-2c45-4dba-badc-d37216398475",
subject: "Something is broke",
admin_note: #Ash.ForbiddenField<field: :admin_note, type: :attribute, ...>,
message: "We are investigating",
inserted_at: ~U[2024-07-03 05:16:08.245708Z],
updated_at: ~U[2024-07-03 05:16:08.245708Z],
user_id: "98e8e6b1-486a-412f-9421-ee2b447ad75d",
aggregates: %{},
calculations: %{},
...
>
]
As expected, the admin_note
is hidden, since the user is not an admin. But why can I now see all the other fields, like message
? message
is private, so why can I still see that field? It seems like the only way to make message
hidden would be to make it public, which does not quite make sense to me.
I could of course make every field public, but that feels a bit counterintuitive since all public fields are accessible through the :*
shortcut. If I then only have default actions, I would be able to access and change the admin_note
, although I should not be able to change that field.
actions do
defaults [:read, :destroy, create: :*, update: :*]
end
iex> Ash.update!(ticket, %{admin_note: "I should not be allowed to do this"}, actor: user)
#Helpdesk.Support.Ticket<
user: #Ash.NotLoaded<:relationship, field: :user>,
__meta__: #Ecto.Schema.Metadata<:loaded, "tickets">,
id: "4ce1002e-2c45-4dba-badc-d37216398475",
subject: "new message",
admin_note: #Ash.ForbiddenField<field: :admin_note, type: :attribute, ...>,
message: "We are investigating",
inserted_at: ~U[2024-07-03 05:16:08.245708Z],
updated_at: ~U[2024-07-03 06:24:09.825098Z],
user_id: "98e8e6b1-486a-412f-9421-ee2b447ad75d",
aggregates: %{},
calculations: %{},
...
>
iex> [ticket] = Ash.read!(Helpdesk.Support.Ticket, actor: admin)
[
#Helpdesk.Support.Ticket<
user: #Ash.NotLoaded<:relationship, field: :user>,
__meta__: #Ecto.Schema.Metadata<:loaded, "tickets">,
id: "4ce1002e-2c45-4dba-badc-d37216398475",
subject: "new message",
admin_note: "I should not be allowed to do this",
message: "We are investigating",
inserted_at: ~U[2024-07-03 05:16:08.245708Z],
updated_at: ~U[2024-07-03 06:26:50.409949Z],
user_id: "98e8e6b1-486a-412f-9421-ee2b447ad75d",
aggregates: %{},
calculations: %{},
...
>
]
So then I have a few questions:
- Why do I need to make a field public to be able to hide it? Wouldn’t it have made sense to hide all fields if you add a field policy to one field? Just like the docs says.
- Why do I need to make a field public for it to have an effect on the field policy? Sounds a bit countrer intutive to make all fields public, to hide them.
- Why can only public fields be part of the field policies?
field_policies:
Invalid field reference(s) in field policy: [:message]
Only non primary-key, public attributes, calculations and aggregates are supported.
- Lastly, it is hopefully a bug that I am allowed to write to a field that I should not be allowed to see?