AE
Ash Elixirโ€ข2y ago
brad

Multitenancy `global?` authorization question

https://hexdocs.pm/ash/multitenancy.html#attribute-multitenancy
If you want to enable running queries without a tenant as well as queries with a tenant, the global? option supports this. You will likely need to incorporate this ability into any authorization rules though, to ensure that users from one tenant can't access other tenant's data.
Could you please provide a brief example of what incorporating this into authorization rules would look like? My specific issue is that I have an Invite resource, used to invite users to the org, and the accept action for this resource needs only a code, but all the CRUD actions for admin to create invites obviously require a tenant. Adding global? true under multitenancy fixes accept, but I'm not sure how to adjust my policies to then require a tenant for the CRUD actions. Having articulated that, I think what I actually want is to just exempt the accept action from the tenant requirement. Is there perhaps a way to do that? Some personal context: I'm new to Ash, but I have a basic app working, and have written a couple Ash.Policy.SimpleChecks, so I have a rudimentary grasp of authorization, I'd say. Oh, and great work on Ash by the way! ๐Ÿ™‚
3 Replies
brad
bradOPโ€ข2y ago
defmodule ActorBelongsToTenant do
use Ash.Policy.SimpleCheck

def describe(_) do
"actor belongs to tenant (used with multitenancy's `global?`)"
end

def match?(user, %{query: %{tenant: org}}, _opts) do
pass?(user, org)
end
def match?(user, %{changeset: %{tenant: org}}, _opts) do
pass?(user, org)
end

defp pass?(_user, nil), do: false
defp pass?(nil, _org), do: false
defp pass?(user, org), do: user.org_id == org.id
end

policies do
policy action([:accept]) do
authorize_if expr(code == ^arg(:code))
end

policy action([:create, :read, :update, :destroy]) do
forbid_unless ActorBelongsToTenant
authorize_if UserIsAdmin
end
end
defmodule ActorBelongsToTenant do
use Ash.Policy.SimpleCheck

def describe(_) do
"actor belongs to tenant (used with multitenancy's `global?`)"
end

def match?(user, %{query: %{tenant: org}}, _opts) do
pass?(user, org)
end
def match?(user, %{changeset: %{tenant: org}}, _opts) do
pass?(user, org)
end

defp pass?(_user, nil), do: false
defp pass?(nil, _org), do: false
defp pass?(user, org), do: user.org_id == org.id
end

policies do
policy action([:accept]) do
authorize_if expr(code == ^arg(:code))
end

policy action([:create, :read, :update, :destroy]) do
forbid_unless ActorBelongsToTenant
authorize_if UserIsAdmin
end
end
Is this on the right track?
ZachDaniel
ZachDanielโ€ข2y ago
Hey there! Sorry I havenโ€™t had a chance to respond yet. At a conference ๐Ÿ™‚ It looks like youโ€™re taking a good approach though
brad
bradOPโ€ข2y ago
No worries! Thanks for confirming I'm pointed in the right direction ๐Ÿ˜„ Enjoy the conference!

Did you find this page helpful?