-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Is your feature request related to a problem? Please describe.
When authorizing resolvers/mutations with pundit on the resolver level, e.g by declaring a Policy class on the resolver, the object passed to the policy is the mutation/resolver instance.
If we want to authorize based on specific inputs — such as, is the user allowed to create a Comment on the Post in their arguments, it seems we have to resort to hacky ways of accessing arguments.
If we call arguments, we will get:
RuntimeError: Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call
.argumentslater in the code.) (RuntimeError)
from /Users/amnesthesia/.asdf/installs/ruby/3.4.5/lib/ruby/gems/3.4.0/gems/graphql-2.5.14/lib/graphql/schema/resolver.rb:58:in 'GraphQL::Schema::Resolver#arguments'
However, we can access arguments like this: instance.context[:current_arguments][:input]. Here, the arguments are usually available, and will give us access even to the prepared values, but with one important caveat .... They haven't finished resolving yet, so dataloaded values (e.g if a dataloaded value is returned in prepare) aren't always accessible yet if they are inside an InputObject, more than 1 level down
Describe the solution you'd like
This seems to be an implementation detail where pundit on the resolver/mutation level is only intended to check a permission and not depend on any input variables, in which case we could authorize those specific arguments, but what we want to do is something like:
# app/policies/mutation_policy.rb
class MutationPolicy < ApplicationPolicy
def initialize(user, instance)
self.user = user
self.instance = instance
end
protected
def arguments
# What we would like:
# instance.arguments
# What we have to do instead:
instance.context[:current_arguments][:input]
end
end
# app/graphql/mutations/create_comment.rb
class Mutations::CreateComment < BaseMutation
class Policy < MutationPolicy
def create?
self.arguments[:post].organization == self.user.organization
end
end
argument :post_id, as: :post, loads: Types::Post
def resolve(...)
end
endIt would also be great if we could scope in the dataloader, rather than apply the scope after the dataloader returns results — we don't want to return a huge result set and then fire a second query to intersect with that result set, we want to apply the scope in the fetching. Since we can't access context[:current_user] in the dataloader, we have resorted to passing current_user as an argument when we initialize it, and then overridden the fetch method to apply policy scoping, but I feel like this is something that should naturally be supported by the integration
Describe alternatives you've considered
What we're currently doing is hacky and depends on specific internal implementation details where we're accessing the arguments through the context, but this only works on the top level and becomes flaky when we get into nested input objects
When authorizing with def authorized?(...), we have access to all input arguments. It doesn't seem fair that if we use pundit, we should not have access to the input arguments.
In our case, it seems like the pundit integration is actually making something that should be pretty easy much more complex. It would be simpler for us to just do:
def authorize?(post:)
Pundit.policy!(context[:current_user], post).create?
endBut then again, I feel like this should be supported out of the box with the current integration on the resolver?