Membership FilterCheck

Hello lovely people šŸ‘‹ I’ve been working on a custom SpaceMemberFilter policy check (using Ash.Policy.FilterCheck) with a goal to authorize access if the actor is a member of the space associated with a given resource (optionally filtered by actor’s role in given space). I took inspiration from Ash.Policy.Check.RelatesToActorVia, but instead of comparing to a single actor ID, I need to check whether the resource’s space_id is in the list of the actor’s valid space memberships. I attached the whole code, It works, but I’m worried about: - Performance: I have to precompute the actor’s valid space IDs in a separate query (get_actor_space_ids/2) and then use an in expression against them. It feels clunky and potentially expensive for actors with many memberships. - Elegance: I tried several approaches with expr/exists to avoid the extra query and push everything into one correlated subquery, but always hit Data layer does not support unrelated exists expressions. So my question is: Is there a cleaner or more idiomatic way in Ash to implement this check (ideally using exists/2 or similar) without doing the extra query to get all space IDs first? Thanks in advance šŸ™
10 Replies
ZachDaniel
ZachDaniel•2w ago
Can you upgrade ash, ash_postgres and ash_sql to the latest and try the unrelated aggregates again?
Geril
GerilOP•2w ago
I updated to most recent versions, but still no luck
ZachDaniel
ZachDaniel•2w ago
šŸ¤” okay try updating again šŸ™‚ I hadn't released the fix yet
Geril
GerilOP•2w ago
Still no luck with converting it into an expression-like syntax (the shared code is working), I am just trying to find a cleaner way to write it. I was thinking about something like this:
expr(
exists(
Hatatitla.Spaces.SpaceMembership,
space_id == ^ref(path, pkey) and
user_id == ^actor(:id)
)
)
expr(
exists(
Hatatitla.Spaces.SpaceMembership,
space_id == ^ref(path, pkey) and
user_id == ^actor(:id)
)
)
Instead of approaching by fetching the space IDs of the current actor.
ZachDaniel
ZachDaniel•2w ago
That looks roughly correct to me, but I think you may need something like this:
expr(
exists(
Hatatitla.Spaces.SpaceMembership,
space_id == parent(^ref(path, pkey)) and
user_id == ^actor(:id)
)
)
expr(
exists(
Hatatitla.Spaces.SpaceMembership,
space_id == parent(^ref(path, pkey)) and
user_id == ^actor(:id)
)
)
If you have relationships that point to the space_membership, then you can do exists([:path, :to, :space_membership], user_id == ^actor(:id))
Geril
GerilOP•2w ago
with this approach it returns: no function clause matching in :lists.droplast/1 šŸ¤”
ZachDaniel
ZachDaniel•2w ago
šŸ¤” weird maybe a bug whats the stacktrace?
Geril
GerilOP•2w ago
Sorry for the delay - my weekend ran longer than expected. I replaced the expression in my filter with the one you suggested (for simplicity, I didn’t include roles at this stage):
expr(
exists(
Hatatitla.Spaces.SpaceMembership,
space_id == parent(^ref(path, pkey)) and
user_id == ^actor(:id)
)
)
expr(
exists(
Hatatitla.Spaces.SpaceMembership,
space_id == parent(^ref(path, pkey)) and
user_id == ^actor(:id)
)
)
I then tried calling my actions from different resources. When I call the read action (via the GraphQL API) on the space_membership resource which has a space_id field directly and the policy set to:
authorize_if {Hatatitla.Shared.Policy.SpaceMemberFilter, roles: [:admin]}
authorize_if {Hatatitla.Shared.Policy.SpaceMemberFilter, roles: [:admin]}
I get back the attached error (could not fit it into a message). Similarly, when I call the read action for my user resource (which has a relationship to space via space_membership) and set the policy as:
authorize_if {Hatatitla.Shared.Policy.SpaceMemberFilter, path: [:space_memberships, :space], roles: [:admin]}
authorize_if {Hatatitla.Shared.Policy.SpaceMemberFilter, path: [:space_memberships, :space], roles: [:admin]}
I get a similar error (attached), so basically, the same issue occurs in both cases.
ZachDaniel
ZachDaniel•2w ago
šŸ¤” something is very strange there 😢 I think I'll need a repro. Please reproduce it in a project and then open an issue šŸ™‡ā€ā™‚ļø
Geril
GerilOP•2w ago
Here’s the repository with the testing app. For the sake of simplicity, I’ve also added seed files for users and spaces. In theory, that should be enough to call the read actions on both the user and space_membership resources. The code we're dealing with lives at space_member_filter.ex. The app is configured to use the DATABASE_URL environment variable, so no additional configuration should be required for database. It also expects some values at SECRET_KEY_BASE, and TOKEN_SIGNING_SECRET to be set.

Did you find this page helpful?