How to add authorization to filter nested data

I'm running a graphql query with the following filter
{
teams: {
id: {
in: teamIds,
},
},
}
{
teams: {
id: {
in: teamIds,
},
},
}
but I want this query to be possible only for a specific role
11 Replies
ZachDaniel
ZachDaniel•3mo ago
🤔 could you tell me about why you want that query to be possible only for a specific role? That sounds like a strange requirement to me.
Marco Dell'Olio
Marco Dell'OlioOP•3mo ago
I have the following role [:owner, :team_lead, :member] :owner can filter by any team_id, :team_lead only by the assigned team_ids, :member cannot filter at all
ZachDaniel
ZachDaniel•3mo ago
My question is why can't they filter? If they can see the data, why would you stop them from filtering it?
Marco Dell'Olio
Marco Dell'OlioOP•3mo ago
this is the solution I came up with, it will make more sense reading it
prepare fn query, context ->
user_id = context.actor.id

OrganizationMembership.get_by_user_id(user_id,
tenant: context.tenant,
load: [:team_memberships]
)
|> case do
{:ok, %{role: :owner}} ->
query

{:ok, %{role: :account_executive}} ->
Ash.Query.set_result(query, [])

{:ok, %{team_memberships: team_memberships}} ->
team_ids = Enum.map(team_memberships, & &1.team_id)
Ash.Query.filter(query, teams: [id: [in: team_ids]])
end


end
prepare fn query, context ->
user_id = context.actor.id

OrganizationMembership.get_by_user_id(user_id,
tenant: context.tenant,
load: [:team_memberships]
)
|> case do
{:ok, %{role: :owner}} ->
query

{:ok, %{role: :account_executive}} ->
Ash.Query.set_result(query, [])

{:ok, %{team_memberships: team_memberships}} ->
team_ids = Enum.map(team_memberships, & &1.team_id)
Ash.Query.filter(query, teams: [id: [in: team_ids]])
end


end
ZachDaniel
ZachDaniel•3mo ago
Gotcha, so I would typically model this with a policy, but that works too
policy action_type(:read) do
forbid_if actor_attribute_equals(:role, :account_executive)
authorize_if actor_attribute_equals(:role, :owner)
authorize_if expr(exists(team_memberships, user_id == ^actor(:id)))
end
policy action_type(:read) do
forbid_if actor_attribute_equals(:role, :account_executive)
authorize_if actor_attribute_equals(:role, :owner)
authorize_if expr(exists(team_memberships, user_id == ^actor(:id)))
end
something like that
Marco Dell'Olio
Marco Dell'OlioOP•3mo ago
but this fully authorize or not an action, it does not control its filters which is fine, probably my solution make sense, because trying to encode this pattern in some macro would create too much complexity for its outcome
ZachDaniel
ZachDaniel•3mo ago
this is incorrect 😄 Those policies will be applied as a filter
Marco Dell'Olio
Marco Dell'OlioOP•3mo ago
what do you mean, I don't understand
ZachDaniel
ZachDaniel•3mo ago
policy action_type(:read) do
forbid_if actor_attribute_equals(:role, :account_executive)
authorize_if actor_attribute_equals(:role, :owner)
authorize_if expr(exists(team_memberships, user_id == ^actor(:id)))
end
policy action_type(:read) do
forbid_if actor_attribute_equals(:role, :account_executive)
authorize_if actor_attribute_equals(:role, :owner)
authorize_if expr(exists(team_memberships, user_id == ^actor(:id)))
end
If you have those policies on your resource it will not forbid the action it will filter the underlying data when you read Meaning it doesn't matter how someone filters data, they will just not be able to see things they aren't allowed to see https://hexdocs.pm/ash/policies.html#read-actions-and-filtering-behavior
Marco Dell'Olio
Marco Dell'OlioOP•3mo ago
in the block you provided I don't understand how it prevents to filter by a specific field. or, I don't see any reference to a specific field
ZachDaniel
ZachDaniel•3mo ago
Right, it doesn't prevent filtering but neither does this:
OrganizationMembership.get_by_user_id(user_id,
tenant: context.tenant,
load: [:team_memberships]
)
|> case do
{:ok, %{role: :owner}} ->
query

{:ok, %{role: :account_executive}} ->
Ash.Query.set_result(query, [])

{:ok, %{team_memberships: team_memberships}} ->
team_ids = Enum.map(team_memberships, & &1.team_id)
Ash.Query.filter(query, teams: [id: [in: team_ids]])
end
OrganizationMembership.get_by_user_id(user_id,
tenant: context.tenant,
load: [:team_memberships]
)
|> case do
{:ok, %{role: :owner}} ->
query

{:ok, %{role: :account_executive}} ->
Ash.Query.set_result(query, [])

{:ok, %{team_memberships: team_memberships}} ->
team_ids = Enum.map(team_memberships, & &1.team_id)
Ash.Query.filter(query, teams: [id: [in: team_ids]])
end
What I'm saying is that preventing filtering is not a safe way to do authorization You instead attach a filter that allows you to see only what you can see, and then it doesn't matter if you filter by any given field because the query only returns what it should return

Did you find this page helpful?