graphql-rubyで認可制御を行うready?メソッドとauthorized?メソッド

こんにちは!kossyです!




さて、今回はgraphql-rubyで認可制御を行うready?メソッドとauthorized?メソッドについて、

備忘録としてブログに残してみたいと思います。




環境

Ruby 2.6.9
Rails 6.0.4
MacOS BigSur
graphql-ruby 1.13.0



graphql-rubyでの認可制御

graphql-rubyのドキュメントを見ると、Mutationの認可制御メソッドとして、ready?メソッドとauthorized?メソッドが使えると記載があります。

graphql-ruby.org

それぞれどのような違いがあるのでしょうか。ソースコードを見てみましょう。


ready?メソッドのソースコードを見る

まずはソースコードを読んでみます。

github.com

def ready?(**args)
  true
end

有無を言わさず true を返すメソッドになっていました。ちょっと意図がよくわからないので、コメントアウト部分を読んでみましょう。

Called before arguments are prepared.
Implement this hook to make checks before doing any work.

argumentsが準備される前に呼び出されます。
このフックを実装して、resolverが実行される前にチェックを行います。

If it returns a lazy object (like a promise), it will be synced by GraphQL
(but the resulting value won't be used).

遅延実行されるオブジェクト(Promiseのようなオブジェクトです)を返す場合、GraphQLによって同期されます。
(ただし、結果の値は使用されません)。

出典: graphql-ruby/resolver.rb at master · rmosolgo/graphql-ruby · GitHub

呼び出しのタイミングは、引数が準備される前とのことでした。


authorized?

こちらもソースコードを見てみます。

def authorized?(**inputs)
  self.class.arguments(context).each_value do |argument|
    arg_keyword = argument.keyword
    if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
      arg_auth, err = argument.authorized?(self, arg_value, context)
      if !arg_auth
        return arg_auth, err
      else
        true
      end
    else
      true
    end
  end
end

コメントアウト部分を見てみます。

Called after arguments are loaded, but before resolving.

引数がロードされた後、resolverの前に呼び出されます。

出典: graphql-ruby/resolver.rb at master · rmosolgo/graphql-ruby · GitHub

とのことでした。よしなにオーバーライドしてもOKなようです。

ready?とauthorized?のユースケース

ready?は引数がロードされる前に実行されるとのことなので、引数の値抜きで権限チェックを行いたい時に使うのがいいと思います。

def ready?
  if context[:current_user].admin?
    true
  else
     raise GraphQL::ExecutionError, "Only admins can run this mutation"
  end
end

逆にauthorized?メソッドは引数がロードされた後に実行されるとのことなので、引数の値込みで権限チェックを行いたい時に使うのがいいと思います。

例えば、管理者権限でも更新できない値があった場合に例外を発生させたりするパターンが考えられます。(履歴データとか)

def authorized?(**inputs)
  if context[:current_user].admin? && context[:current_user].can_edit_all?(inputs)
  else
    raise GraphQL::ExecutionError, "Inculde Cannnot Edit Value"
  end
end


勉強になりました。