rmosolgo / rmosolgo/graphql-ruby

Accessing arguments in Pundit authorization

Open
#5,478 4 comments 0 reactions 0 assignees View on GitHub

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 .arguments later 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.