これの続きです。
問題
graphql-guardはインラインでポリシーを書く方法の他に、ポリシーオブジェクトを定義して GraphQL::Guard.new
に渡すという方法があります。使いかたはREADMEに書いてあるとおりです。
class GraphqlPolicy RULES = { Types::QueryType => { viewer: ->(obj, args, ctx) { !ctx[:current_user].nil? } } } def self.guard(type, field) type.introspection? ? ->(_, _, _) { true } : RULES.dig(type, field) end end MySchema = GraphQL::Schema.define do query Types::QueryType use GraphQL::Guard.new(policy_object: GraphqlPolicy) end Types::QueryType = GraphQL::ObjectType.define do name "Query" field :viewer, !Types::UserType do resolver ->(obj, args, ctx) { ctx[:current_user] } end end # 認証に失敗すると GraphQL::Guard::NotAuthorizedError がraiseされる
で、graphql-ruby 1.8でclass-based APIを使ってスキーマを書き直すと、残念ながらこのポリシーオブジェクトは動かなくなります。
class GraphqlPolicy RULES = { Types::Query => { viewer: ->(obj, args, ctx) { !ctx[:current_user].nil? } } } def self.guard(type, field) type.introspection? ? ->(_, _, _) { true } : RULES.dig(type.name, field) end end class MySchema < GraphQL::Schema query Types::Query use GraphQL::Guard.new(policy_object: GraphqlPolicy) end class Types::Query < Types::BaseObject field :viewer, Types::User, null: false def viewer context[:current_user] end end # 認証に失敗すると GraphQL::Guard::NotAuthorizedError がraiseされず、 # viewerのresolverの結果がnilになるので、次のエラーになってしまう # { # "data": null, # "errors": [ # { # "message": "Cannot return null for non-nullable field Query.viewer" # } # ] # }
解決策
上の問題の原因はHash GraphqlPolicy::RULE
のキーの等価比較にあります。
graphql-guardではフィールドのinstrumentationを使っており、GraphqlPolicy.guard
に渡る type
は、graphql-guardの差し込むinstrumentationで取得した、class-based API以前のオブジェクト型を表す GraphQL::ObjectType
のインスタンスです。一方、RULE
のキーはclass-based APIで導入された GraphQL::Schema::Object
のインスタンスなので、クラスがそもそも一致していません。なので、これを GraphQL::ObjectType
のような古いほうのクラスのインスタンスに変換するためのメソッド #to_graphql
を呼ぶ必要があります。
また、GraphQL::ObjectType
では eql?
をオーバーライドしていないので、Hashのキーの比較にはオブジェクトIDが使われます。ここで、#to_graphql
は内部で GraphQL::ObjectType.new
しているので、これで作ったインスタンスと RULE
のキーになっているインスタンスはオブジェクトIDが異なってしまいます。よって、キーが一致せず、RULE
からguard Procを見つけられないので、望むような動作をしなくなっています。
GraphqlPolicy
を次のように定義し直せば動きます。GraphQLの型をキーとするのは諦めて、その型の名前 name
をキーとすることで、Hashのキー探索がうまくハマるようにしています。
class GraphqlPolicy RULES = { Types::Query.to_graphql.name => { viewer: ->(obj, args, ctx) { !ctx[:current_user].nil? } } } def self.guard(type, field) type.introspection? ? ->(_, _, _) { true } : RULES.dig(type.name, field) end end # 認証に失敗すると GraphQL::Guard::NotAuthorizedError がraiseされる
雑感
この件はgemのREADMEを改善したほうがよさそう。また、なにかもうちょっといい方法があれば教えてください。