rmosolgo / rmosolgo/graphql-ruby
Accessing arguments in Pundit authorization
Nobody has claimed this yet.
- Dominant language
- Ruby
- Stars
- 5.4k
- Forks
- 1.4k
- Avg merge
- 23h 19m
- Merged PRs (30d)
- 28
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
end
It 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?
end
But then again, I feel like this should be supported out of the box with the current integration on the resolver?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading GraphQL::Schema::Resolver#arguments in graphql/schema/resolver.rb and the resolver-level Pundit integration described in the issue. Reproduce the nested InputObject and dataloader cases using the shown mutation and policy examples. Done should be defined as a clear integration design that exposes resolved arguments to authorization and addresses policy scoping during data loading.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, ruby
- Domain
- authorization, backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100