Weird policy behavior

Given the following policy:
policy action_type(:read) do
forbid_if expr(is_nil(^actor(:organization_id)))
authorize_if expr(id == ^actor(:organization_id))
end
policy action_type(:read) do
forbid_if expr(is_nil(^actor(:organization_id)))
authorize_if expr(id == ^actor(:organization_id))
end
I'd expect the call to MyDomain.can_get_by_id?(user, resource) to return false when user.organization_id == nil but I'm getting a truthy value back. What I'm doing wrong?
Solution:
No, the way you're doing it makes sense.
Jump to solution
14 Replies
ZachDaniel
ZachDaniel4mo ago
Its a filter check, so all reads are "allowed" It will just return nothing If you want to change that:
policy action_type(:read) do
access_type :strict
authorize_unless expr(is_nil(^actor(:organization_id))
end

policy action_type(:read) do
authorize_if expr(id == ^actor(:organization_id))
end
policy action_type(:read) do
access_type :strict
authorize_unless expr(is_nil(^actor(:organization_id))
end

policy action_type(:read) do
authorize_if expr(id == ^actor(:organization_id))
end
You can make a policy that applies that check as a "strict" check. Right now we don't have custom error messages for policies, but its on my short-list
ggarciajr
ggarciajrOP4mo ago
so, should I just make sure the read action has a filter that is always applied instead of trying to restrict access via policies?
Solution
ZachDaniel
ZachDaniel4mo ago
No, the way you're doing it makes sense.
ZachDaniel
ZachDaniel4mo ago
It just depends on the behavior you want Do you want a forbidden error if the user has no organization_id? If so, use the example I showed. If you want the user to be allowed to do it but get a not-found error, then use your original solution The latter is often better as it protects from enumeration attacks, revealing the least amount of info possible about your system You can do MyDomain.can_get_by_id?(user, resource, data: [%Resource{id: the_id}]) if you want to use the first strategy but you want to know if the user can read this particular piece of data.
ggarciajr
ggarciajrOP4mo ago
I just want to make sure users don't fetch info from other organizations. What I'm trying to model is, either the user is an admin or it can only read data from the org it belongs to probably a 404 would be fine in my case
ZachDaniel
ZachDaniel4mo ago
Perfect Are you using the can_.. stuff just for testing? If so, then MyDomain.can_get_by_id?(user, resource, data: [%Resource{id: the_id}]) is the way to test "can I read this particular thing"
ggarciajr
ggarciajrOP4mo ago
yeah, given I was modeling using a policy. I may have done something wrong assert Organizations.can_get_organization_by_id?(users.admin, Organization, data: [organization1]) the above gives me this error:
** (Ash.Error.Query.InvalidFilterValue) Invalid filter value `"Elixir.Organizations.Organization"` supplied in `#Ecto.Query<from o0 in Organizations.Organization, as: 0, where: type(as(0).id, {:parameterized, {Ash.Type.UUID.EctoType, []}}) ==
type(
type(
^"Elixir.Organizations.Organization",
{:parameterized, {Ash.Type.UUID.EctoType, []}}
),
{:parameterized, {Ash.Type.UUID.EctoType, []}}
), where: type(as(0).id, {:parameterized, {Ash.Type.UUID.EctoType, []}}) ==
type(
type(^"8cda8766-5e51-4f60-9a16-828d3b9ad7b5", {:parameterized, {Ash.Type.UUID.EctoType, []}}),
{:parameterized, {Ash.Type.UUID.EctoType, []}}
), select: struct(o0, [:id])>`
code: assert Organizations.can_get_organization_by_id?(users.admin, Organization, data: [organization1])
stacktrace:
(ash 3.5.15) lib/ash/can.ex:50: Ash.Can.can?/4
test/my_app/organizations/organization_policies_test.exs:24: (test)
** (Ash.Error.Query.InvalidFilterValue) Invalid filter value `"Elixir.Organizations.Organization"` supplied in `#Ecto.Query<from o0 in Organizations.Organization, as: 0, where: type(as(0).id, {:parameterized, {Ash.Type.UUID.EctoType, []}}) ==
type(
type(
^"Elixir.Organizations.Organization",
{:parameterized, {Ash.Type.UUID.EctoType, []}}
),
{:parameterized, {Ash.Type.UUID.EctoType, []}}
), where: type(as(0).id, {:parameterized, {Ash.Type.UUID.EctoType, []}}) ==
type(
type(^"8cda8766-5e51-4f60-9a16-828d3b9ad7b5", {:parameterized, {Ash.Type.UUID.EctoType, []}}),
{:parameterized, {Ash.Type.UUID.EctoType, []}}
), select: struct(o0, [:id])>`
code: assert Organizations.can_get_organization_by_id?(users.admin, Organization, data: [organization1])
stacktrace:
(ash 3.5.15) lib/ash/can.ex:50: Ash.Can.can?/4
test/my_app/organizations/organization_policies_test.exs:24: (test)
here is the whole policies section
policies do
bypass actor_attribute_equals(:role, :admin) do
authorize_if always()
end

policy action_type(:update) do
authorize_if actor_attribute_equals(:role, :owner)
end

policy action_type(:read) do
forbid_if expr(is_nil(^actor(:organization_id)))
authorize_if expr(id == ^actor(:organization_id))
end

policy always() do
forbid_if always()
end
end
policies do
bypass actor_attribute_equals(:role, :admin) do
authorize_if always()
end

policy action_type(:update) do
authorize_if actor_attribute_equals(:role, :owner)
end

policy action_type(:read) do
forbid_if expr(is_nil(^actor(:organization_id)))
authorize_if expr(id == ^actor(:organization_id))
end

policy always() do
forbid_if always()
end
end
ZachDaniel
ZachDaniel4mo ago
Ah
Organizations.can_get_organization_by_id?(users.admin, organization1.id, data: [organization1])
Organizations.can_get_organization_by_id?(users.admin, organization1.id, data: [organization1])
It expects the same args as actually calling it except with an actor first
ggarciajr
ggarciajrOP4mo ago
not sure if I understand what your are saying
ZachDaniel
ZachDaniel4mo ago
in iex do h Organizations.can_get_organization_by_id? That function comes from your define :get_organization_by_id which I assume has args: [:id]
ggarciajr
ggarciajrOP4mo ago
can_get_organization_by_id?(actor, id, params_or_opts \\ %{}, opts \\ []) btw, this read is defined as define :get_organization_by_id, action: :read, get_by: :id which is based on the default read acion
ZachDaniel
ZachDaniel4mo ago
Yep, makes sense So this call you shared:
assert Organizations.can_get_organization_by_id?(users.admin, Organization, data: [organization1])
assert Organizations.can_get_organization_by_id?(users.admin, Organization, data: [organization1])
Is passing Organization as the id Which is invalid
ggarciajr
ggarciajrOP4mo ago
ah.. that makes total sense. Thanks @Zach Daniel
ZachDaniel
ZachDaniel4mo ago
🙇‍♂️ my pleasure 😄

Did you find this page helpful?