GraphQL Rubyを使用しているプロジェクトで、Introspectionクエリをクライアント毎に有効化、無効化の制御を行いたいことがあったので、その方法を備忘として残しておきたいと思います。
前提
graphql-rubyでは、GraphQL::Schema
の継承クラスでdisable_introspection_entry_points
を定義することで、Intorospectionのエントリーポイント(__schema,__type
)を無効化することがでできます。
公式ドキュメントより
class MySchema < GraphQL::Schema disable_introspection_entry_points if Rails.env.production? end
ただ、例のように本番環境のみIntrospectionクエリを無効化するようなユースケースであれば問題ないですが、リクエスト元のクライアントを判定して、Introspectionクエリの有効、無効を細かく制御したい場合にはそのまま使うことができません。
調べてみたところ、Introspectionを拡張することでContext情報に基づいたエントリーポイントの制御ができるようでした。
また、動作環境は以下を前提とします。
Context情報に基づいたエントリーポイントの制御
GraphQL Rubyの作者のrmosolgoさんのgistでその方法が紹介されているので、参考にしつつ説明していきます。
CustomIntrospectionモジュールを定義しIntrospection名前空間内で、Introspectionクエリで出力される各オブジェクトの定義を上書きます。
__Schema
: built-in introspection types__schema
: introspection entry points__typename
: dynamic, globally-available fields
各オブジェクトのFieldやSchema定義のvisible?メソッドをoverrideし、Context情報が特定の条件に該当する場合にのみFieldやSchemaが出力されるようにします。
app/graphql/custom_introspection.rb
module CustomIntrospection module HideIntrospectionByContext def visible?(ctx) super && if introspection? ctx[:introspection_permitted] else true end end end # globally-available fields class IntrospectionField < GraphQL::Schema::Field include HideIntrospectionByContext end # dynamic fields class DynamicFields < GraphQL::Introspection::DynamicFields field_class(IntrospectionField) field :__typename, String, null: false end # introspection entry points class EntryPoints < GraphQL::Introspection::EntryPoints field_class(IntrospectionField) field :__type, GraphQL::Introspection::TypeType do argument :name, String end end # built-in introspection types class SchemaType < GraphQL::Introspection::SchemaType extend HideIntrospectionByContext end end
上記で定義した、作成したカスタムCustomIntrospectionモジュールをGraphQL::Schemaの継承クラスに定義します。
app/graphql/my_schema.rb
class MySchema < GraphQL::Schema introspection(CustomIntrospection) ... end
出力の例
通常のクエリは問題なく実行できます(visible?メソッド内のintrospection?でIntrospectionクエリのみを制御の対象にできている)
pp MySchema.execute(things_query_str, context: {}).to_h # {"data"=> # {"things"=> # [{"name"=>"Pogo Stick"}, # {"name"=>"Immersion Blender"}, # {"name"=>"Ceiling Fan"}]}}
introspection_permittedがtrueの場合はIntrospectionクエリの実行が可能です
pp MySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY, context: { introspection_permitted: true }).to_h # {"code"=>"useAndDefineFragment", "fragmentName"=>"FullType"}}]} # {"data"=> # {"__schema"=> # {"queryType"=>{"name"=>"Query"}, # "mutationType"=>nil, # "subscriptionType"=>nil, # "types"=> # ...
introspection_permittedがContext情報にない場合は、Introspectionクエリに失敗します
pp MySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY, context: {}).to_h # {"errors"=> # [{"message"=>"Field '__schema' doesn't exist on type 'Query'", # ...
まとめ
GraphQL::Schema
の継承クラスでdisable_introspection_entry_points
を定義することで、環境変数や定数をベースに全体のIntrospectionクエリの実行可否を制御することは可能- クエリを実行してくるクライアント毎にIntrospectionクエリの実行可否を制御するには、Introspection名前空間内で、visible?メソッドのオーバーライドしContextに基づいたスキーマ情報の可視、不可視の切り替えを行うことで実現することができる