11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GraphQL Rubyのvisible?, authorized?について

Last updated at Posted at 2022-07-23

GraphQLでは、MutationやQueryのそれぞれのobject, field単位などで、認可を行います。
これにより、どのようなクエリが発行されても、ユーザーが参照する権限を持っているかを各field, objectなどの単位で確認して、結果を返すことができます。

contextでユーザーの情報を持ち、認可を行います。

ビジネスロジックレイヤーでの認可設定

この記事では、GraphQL RubyのVisibility、Authorizationについて説明しますが、そのようなGraphQLレイヤーでの認可の前に、ビジネスロジックレイヤーでの認可を検討すべきと言われています。

例えば、Modelにロジックを書くことで、すべてのfieldに同じ処理を書く必要がなくなります。

class Post < ActiveRecord::Base
  def self.posts_for(user)
    if user.admin?
      self.all
    else
      self.published
    end
  end
end
field :posts, [Types::Post], null: false

def posts
  Post.posts_for(context[:current_user])
end

一方で、GraphQLのレイヤーで認可を行うのにもメリットがあります。
APIレイヤーが安全であることをさらに保証できたり、実行する前に認可ができることです。

GraphQL Rubyには、VisibilityとAuthorizationの機能があるので1こちらについて説明していきます。

Visibility と Authorization

どちらも、あるfieldやobject(型クラス)などに対してユーザーの権限に応じて参照できる情報の制限を設定できます。
それぞれ以下のような特徴があります。

  • Visibility: visible?
    • スキーマの一部を隠すことができる
    • 権限のないユーザーが参照しようとするとバリデーションエラーを返す
  • Authorization: authorized?
    • 権限のないユーザーも、fieldやobjectをIntrospectionで見ることができる
    • 権限のないユーザーが参照しようとするとnilを返す

visible?はGraphQL仕様ではなく、GraphQL Rubyの機能としてあるものです。
一般ユーザーには存在を知られたくない開発中の機能を、特定のメンバーにのみ公開するときなどに使うことが想定されています。

どちらもobject、field、引数などにvisible?, authorized?メソッドを定義し、それぞれ設定することができます。

field

field単位で、隠す・認可の設定を検証してみます。
BaseFieldでvisible?authorized?を定義します。
それぞれのメソッドが、falseを返すと、ユーザーは参照できなくなります。

module Types
  class BaseField < GraphQL::Schema::Field
    argument_class Types::BaseArgument

    def initialize(*args, admin_only_visible: false, admin_only_authorized: false, **kwargs, &block)
      @admin_only_visible = admin_only_visible
      @admin_only_authorized = admin_only_authorized
      super(*args, **kwargs, &block)
    end

    def visible?(ctx)
      super && (@admin_only_visible ? ctx[:current_user]&.admin? : true)
    end

    def authorized?(obj, args, ctx)
      super && (@admin_only_authorized ? ctx[:current_user]&.admin? : true)
    end
  end
end

そして制限したいfieldでそれぞれadmin_only_visible, admin_only_authorizedを設定すると、adminユーザーのみが参照できるようになります。

module Types
  class UserType < Types::BaseObject
    field :id, ID, null: false
-    field :name, String, null: false
-    field :email, String, null: true
+    field :name, String, null: false, admin_only_visible: true
+    field :email, String, null: true, admin_only_authorized: true
  end
end

これによって、adminユーザーの場合は以下のように参照することができますが、

image.png

adminではない場合、このようにnamevisible?の設定によってfieldが参照できず、エラーが返されます。

image.png

fieldを見ても、nameが隠されていることが分かります。

image.png

authorized?が設定されているemail fieldはエラーにはなりませんが、nullが返ります。

image.png

Object

次はObjectに設定してみます。

module Types
  class UserType < Types::BaseObject
    field :id, ID, null: false
    field :name, String, null: false
    field :email, String, null: true
+    field :articles, ArticleType.connection_type, null: false
  end
end

module Types
  class ArticleType < Types::BaseObject
    field :id, ID, null: false
    field :title, String, null: false
    field :body, String, null: false
    field :author, Types::UserType, null: false

+    def self.visible?(context)
+      super && context[:current_user]&.admin?
+    end
+
+    # def self.authorized?(object, context)
+    #   super && context[:current_user]&.admin?
+    # end
  end
end

visible? の場合は、Userの型としてarticlesが存在しないように見えます。
また、objectだけでなく、その型を返すfieldも隠されていることが分かります。

image.png

authorized?の場合は、エラーにはならずに、nullが返ります。

image.png

authorized? のエラーのカスタマイズ

authorized?を設定したobject, fieldを参照したときには、特にエラーが発生せずにnullが返されますが、これをカスタマイズすることもできます。

このようにSchemaに設定することで、

class MySchema < GraphQL::Schema
  lazy_resolve(Promise, :sync)
  mutation(Types::MutationType)
  query(Types::QueryType)
  default_max_page_size 20
  use GraphQL::Dataloader

  def self.unauthorized_object(error)
    raise GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions"
  end

  def self.unauthorized_field(error)
    raise GraphQL::ExecutionError, "The field #{error.field.graphql_name} on an object of type #{error.type.graphql_name} was hidden due to permissions"
  end
end

このようにエラーの情報を追加することもできるようです。

image.png

参考

  1. GraphQL::ProにはCanCanとPunditのインテグレーションがあるようです。

11
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?