Skip to content

Accessing arguments in Pundit authorization #5478

@Amnesthesia

Description

@Amnesthesia

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions