alove
How to use Ash Policies when users have a different role for each organization?
So we have an application where users fundamentally belong to many organizations, and as such, they have different roles for each organization. Organizations can have child organizations as well, however is needed to organize and control their flow of data.
I want to use an ash policy to restrict the actions of users based on the role they have for the organization they are trying to perform the action on.
So I have the following table which associates a user to an organization and has their role for that organization on it
defmodule UsersOrganizations do
use Ash.Resource,
otp_app: :my_app,
domain: User.Domain,
data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer]
attributes do
uuid_primary_key(:id)
attribute(:user_role, :atom,
default: :default,
allow_nil?: false,
always_select?: true,
)
end
relationships do
belongs_to(:user, User, allow_nil?: false)
belongs_to(:organization, Organization, allow_nil?: false)
end
end
But I don’t know understand how to grab this relationship in a policy, such as this simplified situation below
defmodule MyResource do
use Ash.Resource,
otp_app: :my_app,
domain: SomeResource.Domain,
data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer]
attributes do
attribute :message_body, :string
end
relationships do
belongs_to :sender, User
belongs_to :organization, Organization
end
actions do
read :get_sender_information do
#read sensitive user information, like a name and email.
end
end
policies do
policy action(:get_sender_information) do
authorize_if #The reading user's role in the organization the message was sent in is :admin
end
end
end
So my question is, what is the best approach to building out this sort of policy?
Loosely related question, is it possible to export Ash Policies as an Access Control List (ACL)?
Marked As Solved
zachdaniel
Nope, so for creating data it’s unfortunately basically the Wild West because the data doesn’t yet exist for us to query. You will need to write a custom check.
defmodule MyApp.Checks.HasRoleInOrganization do
use Ash.Policy.SimpleCheck
def match?(actor, %{subject: changeset}, context) do
# run queries here to determine the result
{:ok, false}
end
end
Also Liked
alove
For anyone who finds this page later, I used Zach’s approach above in combination with the following aggregates and calculations on my Organizations resource
aggregates do
list :admins, :users_join_assoc, :user_id, filter: [user_role: :admin]
list :editors, :users_join_assoc, :user_id, filter: [user_role: :editor]
list :guests, :users_join_assoc, :user_id, filter: [user_role: :guest]
end
calculations do
calculate :actor_is_admin?, :boolean, expr(^actor(:id) in admins)
calculate :actor_is_editor?, :boolean, expr(^actor(:id) in editors)
calculate :actor_is_guest?, :boolean, expr(^actor(:id) in guests)
end
chazwatkins
I used a Filter check with different filter/3 clauses for each resource. Of course, I have an OrgMembership join resource that has the actor’s org role on it. It seems to work fine.
You can probably add an option to pass in the role so it can be used for any org role.
defmodule PetalProAsh.Orgs.Checks.IsOrgAdmin do
@moduledoc false
use Ash.Policy.FilterCheck
alias MyApp.MyDomain.Foo
alias PetalProAsh.Orgs.Org
@impl true
def describe(_options) do
"actor is an org admin"
end
@impl true
def filter(actor, %{resource: Org}, _options) when not is_nil(actor) do
expr(exists(memberships, user_id == ^actor.id and role == :admin))
end
@impl true
def filter(actor, %{resource: Foo}, _options) when not is_nil(actor) do
expr(exists(org.memberships, user_id == ^actor.id and role == :admin))
end
@impl true
def filter(_actor, _context, _options) do
false
end
end
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









