Closed kalzoo closed 5 years ago
As an aside, found that using type.graphql_name
in .dig
avoids the string conversion, but that's still fairly hacky:
module Types
class GraphqlPolicy
RULES = {
'User' => {
'*': {
guard: ->(obj, args, ctx) { UserPolicy.new(ctx[:current_user], obj.object).show? },
},
summary: {
not_authorized: ->(type, field) { nil } # simply return nil if not authorized, no errors
}
}
}
def self.guard(type, field)
RULES.dig(type, field, :guard)
end
def self.not_authorized_handler(type, field)
RULES.dig(type.graphql_name, field, :not_authorized) || RULES.dig(type.graphql_name, :'*', :not_authorized)
end
end
end
Hey @kalzoo,
Is the issue that you have User
instead of UserType
, so it can't find the correct RULES.dig(type, field, :not_authorized)
?
From your code sample, it looks like you use class-based definitions:
module Types
class UserType < Types::BaseObject
I'll try to convert these types in the project's tests, which historically use DSL, and see if it fails as well
I was able to reproduce it with graphql-ruby gem version 1.8 and class-based definitions:
module PolicyObject
class PostType < GraphQL::Schema::Object
field :id, ID, null: false
field :title, String, null: true
end
class QueryType < GraphQL::Schema::Object
field :posts, [PostType], null: false do
argument :user_id, ID, required: true
end
def posts(user_id:)
Post.where(user_id: user_id)
end
end
class GraphqlPolicy
RULES = {
QueryType => {
posts: ->(_obj, args, ctx) { args[:userId] == ctx[:current_user].id }
},
PostType => {
'*': ->(_post, args, ctx) { ctx[:current_user].admin? }
}
}
def self.guard(type, field)
RULES.dig(type, field)
end
end
class Schema < GraphQL::Schema
query QueryType
use GraphQL::Guard.new(policy_object: GraphqlPolicy)
end
end
[14] pry(PolicyObject::GraphqlPolicy)> @
graphql-guard/spec/fixtures/policy_object_schema.rb @ line 30 PolicyObject::GraphqlPolicy.guard:
29: def self.guard(type, field)
=> 30: require 'pry'; binding.pry
31: RULES.dig(type, field)
32: end
[15] pry(PolicyObject::GraphqlPolicy)> type
=> Post
[16] pry(PolicyObject::GraphqlPolicy)> type.class
=> GraphQL::ObjectType
[17] pry(PolicyObject::GraphqlPolicy)> type.class.ancestors
=> [GraphQL::ObjectType,
GraphQL::BaseType,
GraphQL::Relay::TypeExtensions,
GraphQL::Define::InstanceDefinable,
GraphQL::Define::NonNullWithBang,
Object,
PP::ObjectMixin,
JSON::Ext::Generator::GeneratorMethods::Object,
Kernel,
BasicObject]
It is related to this issue https://github.com/rmosolgo/graphql-ruby/issues/1429. A workaround for now is to use type.metadata[:type_class]
, for example:
def self.guard(type, field)
RULES.dig(type.metadata[:type_class], field)
end
I'm going to add a note to the readme and add tests to cover this case.
Added tests and some notes to the README in https://github.com/exAspArk/graphql-guard/pull/24
Wow, good eye for the underlying graphql-ruby
issue! That's definitely it. That workaround is super easy and will make for a quick fix once he works out the kinks in the class-based schema. Thanks!
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 theGraphqlPolicy
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):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)
The output (one of many) from that byebug statement, showing it's not matching the rule:
The output from the logger:
Attempted workarounds:
UserType
toUser
-> No changeUserType
toGraphQL::ObjectType::User
->.../rails-root/app/graphql/types/graphql_policy.rb:4: warning: toplevel constant User referenced by GraphQL::ObjectType::User
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 conversionsSo, 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: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:
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
PS, in the example for
not_authorized_handler
, there's a good chance of that returning nil, which throws aNoMethodError
oncall()
. Shouldn't there also be a fallback to a default handler in the schema?