Code Monkey home page Code Monkey logo

graphql-guard's Issues

Upgraded to 2.0 And Cannot `rescue_from GraphQL::Guard::NotAuthorizedError`

Was on 1.2.2, graphql 1.8.10 and just upgraded to 2.0 and graphql 1.11.5 and see that:

rescue_from GraphQL::Guard::NotAuthorizedError do |e|
  # some handling
end

Is not hit when said error is raised.

At first I thought it may be due to rmosolgo/graphql-ruby#2140 but looking at the stacktrace I think it may be due to the addition use GraphQL::Execution::Interpreter:

 # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-guard-2.0.0/lib/graphql/guard.rb:13:in `block in <class:Guard>'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-guard-2.0.0/lib/graphql/guard.rb:73:in `ensure_guarded'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-guard-2.0.0/lib/graphql/guard.rb:40:in `trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `block in call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/platform_tracing.rb:52:in `block in trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/new_relic_tracing.rb:34:in `block in platform_trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/newrelic_rpm-6.3.0.355/lib/new_relic/agent/method_tracer_helpers.rb:30:in `trace_execution_scoped'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/new_relic_tracing.rb:33:in `platform_trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/platform_tracing.rb:51:in `trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:67:in `trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter/runtime.rb:225:in `block (4 levels) in evaluate_selections'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/query.rb:353:in `block in with_error_handling'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/errors.rb:30:in `with_error_handling'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/query.rb:352:in `with_error_handling'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter/runtime.rb:224:in `block (3 levels) in evaluate_selections'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter/runtime.rb:385:in `resolve_with_directives'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter/runtime.rb:221:in `block (2 levels) in evaluate_selections'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter/runtime.rb:460:in `after_lazy'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter/runtime.rb:179:in `block in evaluate_selections'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter/runtime.rb:126:in `each'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter/runtime.rb:126:in `evaluate_selections'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter/runtime.rb:58:in `run_eager'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter.rb:73:in `block in evaluate'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:81:in `call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `block in call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-guard-2.0.0/lib/graphql/guard.rb:42:in `trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `block in call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/platform_tracing.rb:26:in `block in trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/new_relic_tracing.rb:34:in `block in platform_trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/newrelic_rpm-6.3.0.355/lib/new_relic/agent/method_tracer_helpers.rb:30:in `trace_execution_scoped'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/new_relic_tracing.rb:33:in `platform_trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/platform_tracing.rb:25:in `trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:67:in `trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter.rb:72:in `evaluate'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/interpreter.rb:45:in `begin_query'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:112:in `begin_query'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:83:in `block in run_as_multiplex'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:82:in `map'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:82:in `run_as_multiplex'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:61:in `block (2 levels) in run_queries'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:195:in `block in instrument_and_analyze'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/instrumentation.rb:29:in `block (2 levels) in apply_instrumenters'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/instrumentation.rb:46:in `block (2 levels) in each_query_call_hooks'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/instrumentation.rb:41:in `each_query_call_hooks'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/instrumentation.rb:45:in `block in each_query_call_hooks'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/instrumentation.rb:72:in `call_hooks'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/instrumentation.rb:44:in `each_query_call_hooks'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/instrumentation.rb:27:in `block in apply_instrumenters'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/instrumentation.rb:72:in `call_hooks'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/instrumentation.rb:26:in `apply_instrumenters'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:174:in `instrument_and_analyze'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:60:in `block in run_queries'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:81:in `call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `block in call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-guard-2.0.0/lib/graphql/guard.rb:42:in `trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `block in call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/platform_tracing.rb:26:in `block in trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/new_relic_tracing.rb:34:in `block in platform_trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/newrelic_rpm-6.3.0.355/lib/new_relic/agent/method_tracer_helpers.rb:30:in `trace_execution_scoped'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/new_relic_tracing.rb:33:in `platform_trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing/platform_tracing.rb:25:in `trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:83:in `call_tracers'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/tracing.rb:67:in `trace'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:58:in `run_queries'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/execution/multiplex.rb:48:in `run_all'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/schema.rb:1656:in `multiplex'
     # /Users/sshaw/.rvm/gems/ruby-2.5.7/gems/graphql-1.11.5/lib/graphql/schema.rb:1627:in `execute'
<snip app trace>

not_authorized calls with Interface type instead of the real owner

When handling the errors on field which is defined in an Interface, field.owner returns the interface module, instead of the real owner (the subclass of GraphQL::Schema::Object). Probably it should be changed to trace_data[:owner]?

    def ensure_guarded(trace_data)
      field = trace_data[:field]
      guard_proc = find_guard_proc(field.owner, field)
      return yield unless guard_proc

      if guard_proc.call(trace_data[:object], args(trace_data), trace_data[:query].context)
        yield
      else
        not_authorized.call(field.owner.graphql_definition, field.name.to_sym)
      end
    end

Way to `nil` out the whole field rather than each of it's sub-fields if unauthorized to view it.

Right now we configure this library this way:

  use GraphQL::Guard.new(
    not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }
  )

And we add gaurds on each of the types:

Types::LocationType = GraphQL::ObjectType.define do
  name 'Location'
  guard ->(location, _args, context) { context[:current_ability].can?(:read, location) }
end

(We use cancancan as our ability library)

However this query...

{
  location(id:12) {
    id
    users {name}
  }
}

Has a semi-odd behavior:

{
  "data": {
    "location": {
      "id": null,
      "users": null
    }
  },
  "errors": [
    {
      "message": "Not authorized to access Location.id",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "location",
        "id"
      ]
    },
    {
      "message": "Not authorized to access Location.users",
      "locations": [
        {
          "line": 4,
          "column": 5
        }
      ],
      "path": [
        "location",
        "users"
      ]
    }
  ]
}

Rather than:

{
  "data": {
    "location": null
  },
  "errors": [
    {
      "message": "Not authorized to access Location id=12",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "location"
      ]
    }
  ]
}

Is there an easy way to do that with this library?

we managed to resolve it by doing:

  field :location, Types::LocationType do
    argument :id, !types.ID
    description 'Find a Location by ID'

    resolve(lambda do |_obj, args, context|
      location = Location.find_by(id: args['id'])
      if location.present? &&
         context[:current_ability].cannot?(:read, location)
        return GraphQL::ExecutionError.new("Not authorized to access Location id=#{args['id']}")
      end
      location
    end)
  end

However we have to do that for every field we want to protect. ๐Ÿคทโ€โ™‚๏ธ

Skip authorization on return fields

I am having issues with mutations where I return a type that is guarded. Let's say for example I have a mutation where a user can reset the password with a token it got via a mail and I return a UserType.

mutation($reset_password_token: String, $password: String) {
    newPassword(
      reset_password_token: $reset_password_token,
      password: $password,
      password_confirmation: $password
  ) { id }
}

The problem is that the UserType is guarded but within the mutation I want to skip the UserType guard policy. Is this possible?

Pass field name to guard block

I have my abilities defined in CanCanCan on a field level basis and I'd like to authorize the fields and objects inside a general guard block. I have a BaseObject from which every GraphQL object inherits:

class BaseObject < GraphQL::Schema::Object
  guard ->(obj, _args, context) do
    context.fetch(:current_ability).can?(:read, obj.object)
  end
end

But I'm missing the field name here, I'd like to do something like this:

class BaseObject < GraphQL::Schema::Object
  guard ->(obj, _args, ctx, field) do
    ctx.fetch(:current_ability).can?(:read, obj.object, field)
  end
end

Am I missing something? Defining the guard block at every field would lead to a lot of duplication and very noisy code:

  field :name, String do
    guard ->(obj, args, ctx) {  ctx.fetch(:current_ability).can?(:read, obj.object, :name) }
  end

(of course one could define some helpers to reduce the noise, but it would still be "define the guard at every field" by hand).

I also had a look at your Policy Objects (see https://github.com/exAspArk/graphql-guard#policy-object) but I didn't have any luck with them (it seems to me that the .guard method of the policy object is only called once and not on every call, but I'm not sure).

So, is there a way to get the field inside the guard block to lazy evaluate if the field is authorized or not?

Type-level guard taking higher priority than field-level

Hi! I am trying to setup field-level and type-level permissioning, and I'm running to an issue where the type guard seems to have a higher priority than the field. Here is my code:

module Types
  ExampleType = GraphQL::ObjectType.define do
    name "Example"
    description "Example Description"

    implements GraphQL::Relay::Node.interface

    guard ->(obj, _args, context) { false }

    global_id_field :id
    field :weight, types.String
    field :pulse, types.String
    field :height do
      type types.String
      guard ->(obj, args, context) { true }

      resolve lambda {|obj, _args, _context|
        obj.height
      }
    end
  end
end

I'm not able to see any of the fields, including height, even though the guard returns true for height.

Arguments using loads don't get loaded in mutations unless in a subtype

Apparent bug

It seems like arguments of mutations that use loads to automatically load the types from the database don't get loaded unless they are nested in another type. With queries I have not experienced any issues.

Environment

rails: 6.0.3
graphql_ruby: 1.11.6
graphql_guard: 2.0

Description

This query works fine. The facility entity is present in args[facility] in the GuardPolicy. args is a hash.

field :facility, Types::Facility::FacilityType, 'Returns a certain Facility',
                        null: false, authenticate: false do
        argument :facility_id, GraphQL::Types::ID, required: true, loads: Types::Facility::FacilityType
        argument :locale, String, required: true
      end

def facility(facility:, locale:)
  facility
end

This mutation does not work. There is a args[exhibitor] key ('_id' is stripped) but it contains the bare exhibitor_id. In comparison to the query, args is of type class.

class UpdateExhibitor < Mutations::BaseMutation
  null false
  
  argument :locale, String, required: true
  argument :exhibitor_id, ID, required: true, loads: Types::Exhibitor::ExhibitorType
  argument :data, Types::Exhibitor::ExhibitorUpdateInput, required: true
  
  type Types::Exhibitor::ExhibitorType
  
  def resolve(locale:, exhibitor:, data:)
    EntityInteraction.update_entity(exhibitor, data)
  end
end

This mutation does work. It contains the loaded argument in a 'nested' type. The exhibition entity is available in args[:data][:exhibition]:

class CreateEvent < Mutations::BaseMutation
  null false

  argument :locale, String, required: true
  argument :data, Types::Shared::AssociatedWithExhibitionCreateInput, required: true

  type Types::Event::EventType

  def resolve(locale:, data:)
    EntityInteraction.create_entity(::Event, data)
  end
end
class AssociatedWithExhibitionCreateInput < BaseCreateInput
  description 'Attributes for create action of entities associated with exhibition'
  argument :exhibition_id, ID, required: true, loads: Exhibition::ExhibitionType
end

Testing get an error with Rspec

I have created a spec file as below:

require 'graphql/guard/testing'

RSpec.describe "GraphQL Guard" do
  it 'Authorization for rentals API' do
    rentals = Types::QueryType.field_with_guard('rentals', GraphqlPolicy)
    result = rentals.guard(obj, args, ctx)
    expect(result).to eq(true)
  end
end

When I try to run rspec, I got an error as followings:

     
NameError:
undefined local variable or method `obj' for #<RSpec::ExampleGroups::GraphQLGuard:0x0000559aa47de350>

Please let me know what I'm wrong?

Compatibility with graphql-ruby's new interpreter

At first, thank you very much for the great gem, it saved us a lot of headaches! I just came across that the new (experimental) interpreter of graphql-ruby 1.9 does not work with graphql-guard (probably because it doesn't call lambdas/procs anymore?). Is there a plan to support the new interpreter in the future?

[Bug] Guarding or masking a field on an Object Type hides the entire object and not just the individual field

Versions
graphql 1.11.2
graphql-guard 2.0.0

Example Code

module Types
  class PostType < Types::BaseObject
    field :id, ID, null: false
    field :name, String, null: true
    field :masked_or_guarded_field, String, null: true, mask: -> (ctx) { !ctx[:current_user].is_admin?} # or, guard: -> (obj, args, ctx) { !ctx[:current_user].is_admin?}
  end
end

Expected Response

{
  "id": "1",
  "name": "This is a name",
  "masked_or_guarded_field": null, // or just not there at all
}

Actual Response

null

I've also tried it using this syntax with the same results:

field :masked_or_guarded_field, String, null: true do
  mask: -> (ctx) { !ctx[:current_user].is_admin?} # or, guard: -> (obj, args, ctx) { !ctx[:current_user].is_admin?}
end

Let me know if I can provide any more information!

Deprecation warnings with graphql-1.13.1

Hello,

Deprecation warnings starting graphql-1.13.1:

Legacy `.to_graphql` objects are deprecated and will be removed in GraphQL-Ruby 2.0. Remove `.to_graphql` to use a class-based definition instead.

Called on #<GraphQL::Schema::Field Query.posts(...): [Post!]!> from:

graphql-guard/lib/graphql/guard/testing.rb:29:in `field_with_guard'
.....

From changelog

.to_graphql and .graphql_definition are deprecated and will be removed in GraphQL-Ruby 2.0. 
All features using those legacy definitions are already removed
and all behaviors should have been ported to class-based definitions. 
So, you should be able to remove those calls entirely.
Please open an issue if you have trouble with it! #3750 #3765

Also I noticed, that inline guards return Array wrapped guard Proc and not just proc right away. Is this intended outcome in graphql or some undesired behaviour - I don't know, should investigate more.

type.introspection? || RULES.dig(type, field) doesn't working.

If I don't use this:

def self.guard(type, field)
  type.introspection? || RULES.dig(type, field)
end

I can get introspection types without problems which is not good:

Started POST "/graphql" for 172.18.0.1 at 2017-11-26 12:07:58 +0000
Processing by GraphqlController#execute as */*
  Parameters: {"query"=>"\n  query IntrospectionQuery {\n    __schema {\n      queryType { name }\n      mutationType { name }\n      subscriptionType { name }\n      types {\n        ...FullType\n      }\n      directives {\n        name\n        description\n        locations\n        args {\n          ...InputValue\n        }\n      }\n    }\n  }\n\n  fragment FullType on __Type {\n    kind\n    name\n    description\n    fields(includeDeprecated: true) {\n      name\n      description\n      args {\n        ...InputValue\n      }\n      type {\n        ...TypeRef\n      }\n      isDeprecated\n      deprecationReason\n    }\n    inputFields {\n      ...InputValue\n    }\n    interfaces {\n      ...TypeRef\n    }\n    enumValues(includeDeprecated: true) {\n      name\n      description\n      isDeprecated\n      deprecationReason\n    }\n    possibleTypes {\n      ...TypeRef\n    }\n  }\n\n  fragment InputValue on __InputValue {\n    name\n    description\n    type { ...TypeRef }\n    defaultValue\n  }\n\n  fragment TypeRef on __Type {\n    kind\n    name\n    ofType {\n      kind\n      name\n      ofType {\n
        kind\n        name\n        ofType {\n          kind\n          name\n          ofType {\n            kind\n            name\n            ofType {\n              kind\n              name\n              ofType {\n
        kind\n                name\n                ofType {\n                  kind\n                  name\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n", "graphql"=>{"query"=>"\n  query IntrospectionQuery {\n    __schema {\n      queryType { name }\n      mutationType { name }\n      subscriptionType { name }\n      types {\n        ...FullType\n      }\n      directives {\n        name\n        description\n        locations\n        args {\n          ...InputValue\n        }\n      }\n    }\n  }\n\n  fragment FullType on __Type {\n    kind\n    name\n    description\n    fields(includeDeprecated: true) {\n      name\n
    description\n      args {\n        ...InputValue\n      }\n      type {\n        ...TypeRef\n      }\n      isDeprecated\n      deprecationReason\n    }\n    inputFields {\n      ...InputValue\n    }\n    interfaces {\n
 ...TypeRef\n    }\n    enumValues(includeDeprecated: true) {\n      name\n      description\n      isDeprecated\n      deprecationReason\n    }\n    possibleTypes {\n      ...TypeRef\n    }\n  }\n\n  fragment InputValue on __InputValue {\n    name\n    description\n    type { ...TypeRef }\n    defaultValue\n  }\n\n  fragment TypeRef on __Type {\n    kind\n    name\n    ofType {\n      kind\n      name\n      ofType {\n        kind\n        name\n
   ofType {\n          kind\n          name\n          ofType {\n            kind\n            name\n            ofType {\n              kind\n              name\n              ofType {\n                kind\n                name\n                ofType {\n                  kind\n                  name\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n"}}
[active_model_serializers] Rendered ActiveModel::Serializer::Null with GraphQL::Query::Result (12.33ms)
Completed 200 OK in 61ms (Views: 13.7ms | ActiveRecord: 0.0ms)

My GraphQLPolicy:

class GraphqlPolicy
  
  RULES = {
  
    Types::QueryType => {
      '*': ->(obj, args, ctx) { ctx[:current_user] } #nil
    }
  
  }

  def self.guard(type, field)
    RULES.dig(type, field)
  end

end

Why is introspection skipping authorization?

Does not seem to work with GraphQL::Execution::Interpreter

graphql gem v1.10 adds GraphQL::Execution::Interpreter by default in newly generated schemas, and that somehow sidesteps graphql-guard. For example, with following configuration:

class MySchema < GraphQL::Schema
  query(Types::QueryType)

  # Opt in to the new runtime (default in future graphql-ruby versions)
  use(GraphQL::Execution::Interpreter)
  use(GraphQL::Analysis::AST)

  use(GraphQL::Guard.new)
end

module Types
  class QueryType < Types::BaseObject
    guard ->(obj, args, ctx) { false }
    field :current_user, UserType, null: false
  end
end

the query

query {
  currentUser {
    id
  }
}

is successful. After commenting out

  use(GraphQL::Execution::Interpreter)
  use(GraphQL::Analysis::AST)

block, the same query returns an error, as expected.

Hiding unauthorized fields from introspection?

Looking at the docs and other issues I don't think I've seen an exact question like mine, so here goes:

Is there a way to hide a field from introspection if the user is unauthorized to see it? I'd like to have a schema that mixes public and internal fields, and hides the internal fields completely from external users of the application. Is this possible?

Outdated Documentation

class ShopType < Types::BaseObject
  field :title, String, null: false
  field :user, Types::Models::UserType, null: false
end

How would you include a guard on this field for the ShopType, we would like to limit the field user to only signed in users, but allow the other fields on a shop to be public without the need for authentication.

Changing NotAuthorizedError to Be A Subclass of GraphQL::ExecutionError

Yes, breaking change but I was thinking with NotAuthorizedError as a superclass one gets proper error response formatting for free, as GraphQL Ruby will insert the exception's message into the "errors" property of the response.

And related, how do you feel about changing the default message of NotAuthorizedError from Query.queryName to Not authorized to access: Query.queryName (or similar)?

I just started using this and saw 500 errors with a log message of "Query.queryName". Not a helpful message.

Cancancan documentation

Hi!

Thanks for your awesome's work on this gem!

I've followed your CanCanCan documentation but it didn't work until i've added this configuration:

# app/graph/graphql_schema.rb
GraphqlSchema = GraphQL::Schema.define do
  query QueryType
  mutation MutationType

  use GraphQL::Guard.new

  resolve_type -> (obj, args, ctx) {
    type_name = obj.class.name
    Schema.types[type_name]
  }
end

I use graphql-guard (~> 1.0.0) on rails 5.1

Did i missed something?

Policy lambda receiving inconsistent value in test

I am using a graphql policy like so:

      Types::UserType => {
          email: {
              guard: ->(obj, args, ctx) { puts obj.inspect; UserPolicy.new(ctx[:current_user], obj).show_protected_fields? }
          },
...

This works great. If I use the graphql endpoint and request a user:

{
  user(id: 1){
  	name
        email
  }
}

It works fine, and the output in the console is :

 #<Types::UserType:0x00007f03c20e5750 @object=#<User member_id: 1, email: "[email protected]" ...

However, when I run the same query in rspec, the guard lambda is seemingly receiving the same object (the structure is the same, not the contents).

#<Types::UserType:0x000055a2afb7d6b0 @object=#<User member_id: 10288

However, all my tests fail with:

       undefined method `member_id' for #<Types::UserType:0x0000562c884e4f20>
     # ./app/policies/user_policy.rb:10:in `show_protected_fields?'
     # ./app/graphql/graphql_policy.rb:10:in `block in <class:GraphqlPolicy>'
     # /usr/local/bundle/gems/graphql-guard-2.0.0/lib/graphql/guard.rb:70:in `ensure_guarded'
     # /usr/local/bundle/gems/graphql-guard-2.0.0/lib/graphql/guard.rb:40:in `trace'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:84:in `call_tracers'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:68:in `trace'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:225:in `block (4 levels) in evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/query.rb:354:in `block in with_error_handling'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/errors.rb:30:in `with_error_handling'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/query.rb:353:in `with_error_handling'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:224:in `block (3 levels) in evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:382:in `resolve_with_directives'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:221:in `block (2 levels) in evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:457:in `after_lazy'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:177:in `block in evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:124:in `each'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:124:in `evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:334:in `block in continue_field'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:457:in `after_lazy'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:329:in `continue_field'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:375:in `continue_field'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:238:in `block (4 levels) in evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:457:in `after_lazy'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:232:in `block (3 levels) in evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:382:in `resolve_with_directives'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:221:in `block (2 levels) in evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:457:in `after_lazy'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:177:in `block in evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:124:in `each'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:124:in `evaluate_selections'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter/runtime.rb:60:in `run_eager'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter.rb:73:in `block in evaluate'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:82:in `call_tracers'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:84:in `block in call_tracers'
     # /usr/local/bundle/gems/graphql-guard-2.0.0/lib/graphql/guard.rb:42:in `trace'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:84:in `call_tracers'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:68:in `trace'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter.rb:72:in `evaluate'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/interpreter.rb:45:in `begin_query'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:113:in `begin_query'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:84:in `block in run_as_multiplex'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:83:in `map'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:83:in `run_as_multiplex'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:62:in `block (2 levels) in run_queries'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:196:in `block in instrument_and_analyze'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/instrumentation.rb:29:in `block (2 levels) in apply_instrumenters'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/instrumentation.rb:46:in `block (2 levels) in each_query_call_hooks'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/instrumentation.rb:41:in `each_query_call_hooks'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/instrumentation.rb:45:in `block in each_query_call_hooks'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/instrumentation.rb:72:in `call_hooks'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/instrumentation.rb:44:in `each_query_call_hooks'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/instrumentation.rb:27:in `block in apply_instrumenters'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/instrumentation.rb:72:in `call_hooks'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/instrumentation.rb:26:in `apply_instrumenters'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:175:in `instrument_and_analyze'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:61:in `block in run_queries'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:82:in `call_tracers'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:84:in `block in call_tracers'
     # /usr/local/bundle/gems/graphql-guard-2.0.0/lib/graphql/guard.rb:42:in `trace'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:84:in `call_tracers'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/tracing.rb:68:in `trace'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:59:in `run_queries'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/execution/multiplex.rb:49:in `run_all'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/schema.rb:1633:in `multiplex'
     # /usr/local/bundle/gems/graphql-1.10.8/lib/graphql/schema.rb:1604:in `execute'

I am using the latest version of graphql, graphql-guard and pundit. My UserPolicy for reference:


  def show?
    true
  end

  def show_protected_fields?
    puts record.to_json
    !user.nil? && (user.admin? || user.member_id == record.member_id)
  end
end```

Add support for arguments

I'd like to mask certain arguments so they are hidden in the schema. I'm currently playing around with this

# in an initializer
GraphQL::Argument.accepts_definitions(mask: GraphQL::Define.assign_metadata_key(:mask))
GraphQL::Schema::Argument.accepts_definition(:mask)

and then use it like this

argument :foo, String, required: false, mask: ->(ctx) { false }

Is this the right way to do it? And can someone explain why I need to define the mask on GraphQL::Argument AND GraphQL::Schema::Argument?

guard lambdas are receiving the schema type object, rather than the found instance

Hi,

I just found this gem, and am looking to integrate it. I've run into an issue where my guard lambdas are receiving the graphql type object as the object argument, rather than the found instance that I expect (and the examples in the README also imply that this is what I should be receiving.) This behavior is showing up in all cases.

I'm currently using version 2.0.0, with the latest versions of all the related GraphQL gems.

My setup for testing this is currently

PROJECT_MEMBER_GUARD = lambda { |object, _args, context|
  user = context.fetch(:current_user, nil)
  ProjectMemberPolicy.new(user, object).show?
}

as the guard, which is attached via

field :project,
      Types::ProjectType,
      null: false,
      description: 'Get a single project',
      guard: PROJECT_MEMBER_GUARD do
  argument :id, ID, required: true, description: 'The project ID'
end

def project(id:)
  # Project.find id also fails
  RecordLoader.for(Project).load(id)
end

Am I doing something wrong? Without guard / auth checks, the queries run as expected. I'm completely baffled right now.

The specific error i'm receiving is "undefined method `to_global_id' for #<Types::QueryType:0x000056063dace2e0>", but the issue is that the object shouldn't be a Types::QueryType object, as far as my understanding goes.

Authorization on Enum Values

I'd like to be able to restrict certain enum input values to only super_admin users. Is there a way to do something like this? (This doesn't work obviously)

Types::OrganizationTypeEnumType = GraphQL::EnumType.define do
  name 'OrganizationTypeEnumType'
  description 'Enum for the possible organization types'

  value('PROVIDER', 'provider', value: 'provider'), guard: ->(obj, _args, ctx) { ctx[:current_user].super_admin? }
  value('CUSTOMER', 'customer', value: 'customer')
  value('THIRD_PARTY', 'third_party', value: 'third_party')
end

Usage with graphql-ruby 1.8 class syntax

graphql-ruby 1.8 is introducing a new class-based syntax, however it seems to break compatibility with graphql-guard.

class Types::Query < GraphQL::Schema::Object
  field :posts, [Types::Post], null: false do
    guard ->(obj, args, ctx) { true }
  end
end

results in undefined method 'guard' for GraphQL::Schema::Field

Is there a potential workaround (aside from just not using the new syntax)?

GraphQL::Guard::NotAuthorizedError: 500 instead of 401. How to rescue?

This is NOT a bug and I immediately apologize for this issue but I'm newbie in Ruby, Rails and this amazing gem.

When I get this: GraphQL::Guard::NotAuthorizedError:

{
	"status": 500,
	"error": "Internal Server Error",
	"exception": "#<GraphQL::Guard::NotAuthorizedError: Query.users>",
	"traces": {
		"Application Trace": [
			{
				"id": 35,
				"trace": "app/controllers/graphql_controller.rb:11:in `execute'"
			}
		],
		"Framework Trace": [
			{
				"id": 0,
				"trace": "graphql-guard (1.0.0) lib/graphql/guard.rb:12:in `block in <class:Guard>'"
			},
			{
				"id": 1,
				"trace": "graphql-guard (1.0.0) lib/graphql/guard.rb:38:in `block in instrument'"
			},
			{
				"id": 2,
				"trace": "graphql (1.7.6) lib/graphql/field.rb:230:in `resolve'"
			},
			{
				"id": 3,
				"trace": "graphql (1.7.6) lib/graphql/execution/execute.rb:254:in `call'"
			},
			{
				"id": 4,
				"trace": "graphql (1.7.6) lib/graphql/schema/middleware_chain.rb:45:in `invoke_core'"
			},
			{
				"id": 5,
				"trace": "graphql (1.7.6) lib/graphql/schema/middleware_chain.rb:38:in `invoke'"
			}, ....................

I would prefer something like REST API Unauthorized error 401.

How to do that?

I have to rescue that exception?

Something like:

render json: ["Error 401. Unauthorized."], status: :unauthorized

Undefined method guard for Resolvers Class

I am trying to use guard inside the Mutation, using Resolvers class:

class Resolvers::UpdateExample < GraphQL::Function
  argument :id, !types.ID
  argument :example, !Inputs::ExampleInput

  type Types::ExampleType

  guard ->(obj, args, ctx) { 
      true
    }

  def call(obj,args,ctx)
    puts "hello world"
  end
end

When I try to call this mutation I get error:

Failure/Error:
guard ->(obj, args, ctx) {
..

 NoMethodError:
   undefined method `guard' for Resolvers::UpdateExample:Class

Use on resolver

Hi,

Can you add guard to Resolver

Thanks for your open source contributions!

Guarding for the entire root of an object

QueryType = GraphQL::ObjectType.define do
  name 'Root'

  field :widget do
    type WidgetType
    argument :id, !types.ID

    # Can't guard here because we haven't queried for the object yet

    resolve ->(_obj, args, _ctx) do
      Widget.find(args['id'])
    end
  end
end

WidgetType = GraphQL::ObjectType.define do
  name 'Widget'

  guard ->(widget, _args, ctx) do
    ctx[:current_user].can?(:read, widget)
  end

  field :id, type: types.String # => Not authorized to access Widget.id
  field :title, TruncatedStringField.new # Returns the title because we haven't explicitly guarded on this path
end

How would one go about guarding the entire WidgetType and every field under it? It seems to me that TruncatedStringField should not be returning, but that is in fact what happens. The errors return for every field that's queried, which also seems non-ideal.

Can't test guard proc on Mutation root

Related to the documentation on the testing of the guard proc: https://github.com/exAspArk/graphql-guard#testing

The documentation suggests that I should be able to do QueryType.field_with_guard('posts'), but when I try to do it with my own Mutations::Root object (which is a GraphQL::ObjectType), I get a no method error for field_with_guard:

Mutations::Root.field_with_guard('some_existing_mutation_endpoint')
NoMethodError: undefined method `field_with_guard' for Mutation:GraphQL::ObjectType

It seems to be the same for all other object types I defined in my GraphQL schema using graphql-ruby v1.8.4 and graphql-guard 1.0.0 & 1.2.0 (tested with both).

Add support for Mutation

Is there any technical limitation for not supporting the DSL on mutations ?

Something like this:

class CreateDevice < GraphQL::Schema::Mutation
  guard -> (_, _, _) { raise StandardError.new }
   
  argument :arg1, String, null: false

  def resolve(arg1:)
    # My logic
  end
end

allow introspection field types to skip authentication

Hello,

Would you be open to making it configurable for users of graphql-guard to indicate that introspection fields should not be checked for authentication?

We've worked around this by using a policy_object, and checking for type.introspection? in the guard method of the policy_object itself, but it does feel like this should be offered out of the box.

If yes, I would even suggest it should be the default option, so that introspection fields would by default be "guard free", and if someone wants to hide their schema they would explicitly have to flip that option (but perhaps that's just me).

Happy to submit a PR if you think this is a sensible idea.

Cheers,
Vlad

PORO Policy receiving incorrect (unexpected) type

Hey, first of all, thanks for making this! Its easy integration with Pundit and its good documentation are pretty great.

Running into an issue here - I'm looking to provide authorization and not_authorized handling on the field level. So, I copy-pasted your example, swapped out constants ... and found that the procs in the GraphqlPolicy weren't being executed.

This may be a misunderstanding of graphql-ruby itself.

Here are snippets:

the schema (verbatim from documentation, except for Types:: prefix):

  use GraphQL::Guard.new(
    policy_object: Types::GraphqlPolicy,
    not_authorized: ->(type, field) {
      handler = Types::GraphqlPolicy.not_authorized_handler(type, field)
      handler.call(type, field)
    }
  )

graphql/types/graphql_policy.rb
(named and placed that way to put it in the same load path as UserType etc, definitely open to suggestions) (pretty much copy-pasted)

module Types
  class GraphqlPolicy
    RULES = {
      UserType => {
        '*': {
          guard: ->(obj, args, ctx) { byebug; nil; UserPolicy.new(ctx[:current_user], obj.object).show? },
        },
        summary: {
          guard: ->(obj, args, ctx) { byebug; nil; UserPolicy.new(ctx[:current_user], obj.object).show_progress? },
          not_authorized: ->(type, field) { nil } # simply return nil if not authorized, no errors
        }
      }
    }

    def self.guard(type, field)
      byebug # This is the only byebug that is evaluated, and produces the output in next snippet
      RULES.dig(type, field, :guard)
    end
  
    def self.not_authorized_handler(type, field)
      Rails.logger.error("Calling not_authorized_handler for #{type.class}: #{type}, #{field}")
      RULES.dig(type, field, :not_authorized) || RULES.dig(type, :'*', :not_authorized)
    end
  end

The output (one of many) from that byebug statement, showing it's not matching the rule:

(byebug)
1: type = User
2: field = :*
3: RULES.dig(type, field, :guard) = nil

The output from the logger:

Calling not_authorized_handler for GraphQL::ObjectType: User, summary

Attempted workarounds:

  1. UserType to User -> No change

  2. UserType to GraphQL::ObjectType::User -> .../rails-root/app/graphql/types/graphql_policy.rb:4: warning: toplevel constant User referenced by GraphQL::ObjectType::User

  3. RULES.dig("#{type.to_s}Type", field, :not_authorized) || RULES.dig("#{type.to_s}Type", :'*', :not_authorized), and using a string as the key in RULES -> it works, but is hacky and adds unnecessary overhead in string conversions

So, the problem seems to be that the parameters sent to guard don't line up with that object's shape.

What's weird, to me, is that the type is User, which isn't the graphql type name at all:

module Types
  class UserType < Types::BaseObject

User is the model's class name.

The entire point of using the PORO was to be able to deny access only to that field, returning nil (but still returning an error, ideally, and definitely errors for other fields in other queries). Unfortunately, the best I can do with this is block the whole query:

{
  "data": {
    "user": null
  },
  "errors": [
    {
      "message": "Not authorized to access summary on this User.",
      "locations": [
        {
          "line": 8,
          "column": 5
        }
      ],
      "path": [
        "user",
        "summary"
      ]
    }
  ]
}

IMO it would intuitive if we could just define not_authorized right there in the type definition:

field :summary, UserSummaryType, null: false do
not_authorized ->(type, field) { nil }
guard ->(obj, args, ctx) { UserPolicy.new(ctx[:current_user], obj.object).show_progress? }
end

but unfortunately

undefined method `not_authorized' for #<GraphQL::Schema::Field:>

PS, in the example for not_authorized_handler, there's a good chance of that returning nil, which throws a NoMethodError on call(). Shouldn't there also be a fallback to a default handler in the schema?

not_authorized: ->(type, field) {
      handler = Types::GraphqlPolicy.not_authorized_handler(type, field)
      handler ? handler.call(type, field) : GraphQL::ExecutionError.new("Not authorized to access #{field} on this #{type}.")
    }

Can't Using Mutation Type and Query Type Together

I'd like to use mutation type and query type to be used in GraphQL Policy, but what i got, only "QueryType" that always got called even call mutation. Is there an idea why this happen?

Rails 5.2.3
Ruby 2.6.3

class PhoneBookSchema < GraphQL::Schema
  mutation(Types::MutationType)
  query(Types::QueryType)

  use GraphQL::Guard.new(
    policy_object: GraphqlPolicy,
    not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{field}") }
  )
end

class GraphqlPolicy
    RULES = {
        Types::QueryType => {
          :user => ->(obj,args,ctx) { ctx[:current_user].is_admin || ctx[:current_ability].can?(:read,User.find(args[:id])) },
          :users => ->(obj,args,ctx) { ctx[:current_user].is_admin },
        },
        Types::MutationType => {
          :create_user => ->(obj,args,ctx) { ctx[:current_user].is_admin },
          :update_user => ->(obj,args,ctx) { ctx[:current_ability].can?(:update, User.find(args[:user_id])) || ctx[:current_user].is_admin }
        },
        
    }
  
    def self.guard(type, field)
      RULES.dig(type.metadata[:type_class], field)
    end
end

Using guard in base/parent class always takes priority

If using guard ->(obj, args, ctx) { some_method } inside a base type class, and then inheriting from that class you cannot specify overrides per sub class or even per field. It will always use the guard from the parent class.

class ParentType < BaseType
  guard ->(obj, args, ctx) { false }
end

class ChildType < ParentType
  guard ->(obj, args, ctx) { true } # will not work

  field :id, ID, required: true, guard ->(obj, args, ctx) { true } # also will not work
end

Add support for context to not_authorized callback when using Policy

This is a question as well as a suggestion.

I'm combining graphql-ruby and graphql-guard with Doorkeeper+Sorcery to handle my authentication. In my graphql_controller.rb I have:

def current_user
    token = Doorkeeper.authenticate(request)
    unless token&.accessible?
      raise UnauthenticatedError
    end

    current_resource_owner
  rescue UnauthenticatedError => e
    GraphQL::ExecutionError.new('Unauthenticated', extensions: { code: 'AUTHENTICATION_ERROR' })
end

And my policy is:

...
Types::WorkoutType => {
    '*': ->(obj, args, ctx) { obj.try(:author) == ctx[:current_user] || ctx[:current_user].try(:admin) }
},
...

So when a user is not authenticated, I expect the Unauthenticated error to be returned, but instead I get the Not authorized to access #{type}.#{field} defined in graphl_controller.rb.

Question:

Would it make sense to add the context to the callback so one could do something like this?

use GraphQL::Guard.new(
    policy_object: GraphqlPolicy,
    not_authorized: ->(type, field, ctx) do
      ctx.add_error(GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}"))
    end
)

That way we wouldn't remove any other errors that are in there and we can see that we are in fact unauthenticated as well as unauthorized.

Pundit field whitelisting

First of all, thanks for this great gem! I love the simplicity.

By default this gem follows a blacklisting strategy where you disable access to queries, fields, etc. I'd like it the other way around and whitelist everything that is allowed to be used.

So far I have an implementation that works (based on other discussions on this gem).

The problem with my implementation is that for each field I will need to have a proc which tests the access.

See: https://gist.github.com/yourivdlans/7d1093e5500820804a7ca8d263c98ecf

This might grow to something unmanageable which I'd like to avoid.

What could work is if the field name would be passed into the proc, but I'm not sure if this is the right approach.

Would love to hear your opinion and thoughts :)

Thanks!

How to tell which record is causing `GraphQL::Guard::NotAuthorizedError` to be raised?

I'm getting the Not authorized to access: Xxxxx.id (GraphQL::Guard::NotAuthorizedError) error in my logs, but are often not sure which record it's talking about, and it's making it a real pain to debug issues.

It usually happens when a GraphQL query with multiple levels of nested fields is executed, and the error is something deeper in the structure, so it can't easily be worked out, by just looking at the arguments.

What I would like to do, is override the default error message so that it prints something like:

raise NotAuthorizedError.new("Not authorized to access: #{type}.#{field} for #{type}.id = #{trace_data[:object].try(:id)}")

and then I'll better understand which record it's referring to when I see this error.

Looking at the source code, I think that it's not currently possible since the 'object' is not passed to the proc. But just putting this here as I think it would be really useful to be able to do.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.