GraphQL 認証

作成者は以下の場所で適用できます:

  • タイプ
    • オブジェクト(::Types::BaseObject から始まるすべてのクラス)
    • 列挙型(::Types::BaseEnum から始まるすべてのクラス)
  • リゾルバ:
    • フィールド・リゾルバ (::Types::BaseResolver から始まるすべてのクラス)
    • 突然変異(::Types::BaseMutation から派生するすべてのクラス)
  • フィールド (field DSL メソッドを使用して宣言されたすべてのフィールド)

抽象型 (インターフェースと共用体) には作成者を指定できません。抽象型はメンバ型に委譲します。基本的なスカラー(整数など)には作成者はいません。

作成者の権限システムは、アプリケーションの他の部分と同じDeclarativePolicy システムを使用しています。

  • 単一の値 (Query.project など) については、現在認証されているユーザーが認証に失敗した場合、フィールドはnull に解決されます。
  • コレクション(Project.issues など)の場合、コレクションは、ユーザーの認証チェックが失敗したオブジェクトを除外するためにフィルタされます。このフィルタリング処理(_再編集とも_呼ばれる)はページ分割の後に行われるため、再編集されたオブジェクトが削除されることにより、ページによっては要求されたページサイズよりも小さくなることがあります。

mutation におけるリソースの作成者も参照してください。

note
ベストプラクティスは、レコードをフィルタリングするために権限に依存することなく、現在認証されているユーザーが既存のファインダで表示することが許可されているものだけを最初にロードすることです。これにより、データベースのクエリや、ロードされたレコードの不必要な作成者チェックを最小限に抑えることができます。また、短いページなど、機密リソースの存在を暴露する可能性がある状況を避けることができます。

ここで説明するすべての認可スキームの例については、authorization_spec.rb を参照してください。

タイプ認証

authorize メソッドに能力を渡すことで、型の作成者を認可します。現在認証されているユーザーが必要な能力を持っていることをチェックすることで、同じ型を持つすべてのフィールドがオーソライズされます。

たとえば、次のような権限付与を行うと、現在認証されているユーザーは、read_project の能力を持つプロジェクト (Types::ProjectType を使用するフィールドでプロジェクトが返される場合) のみを表示できるようになります:

module Types
  class ProjectType < BaseObject
    authorize :read_project
  end
end

複数の能力に対して権限を付与することもできます。この場合、すべての能力チェックに合格する必要があります。

たとえば、以下の作成者は、現在認証されているユーザーが、read_project およびanother_ability の能力を持っていなければプロジェクトを表示できないようにしています:

module Types
  class ProjectType < BaseObject
    authorize [:read_project, :another_ability]
  end
end

リゾルバ作成者の権限

リゾルバは独自の作成者を持つことができ、それは親オブジェクトにも解決された値にも適用することができます。

:read_list 親オブジェクトに対して権限を与えるリゾルバの例として、Resolvers::BoardListsResolver があります。

解決されたリソースに対して認可を行う例はResolvers::Ci::ConfigResolver であり、これは解決された値が:read_pipeline を満たすことを要求しています。

親に対して認可するためには、リゾルバはauthorizes_object! で宣言することで、 (これは初期状態ではデフォルト値ではなかったので)_オプトイン_しなければなりません:

module Resolvers
  class MyResolver < BaseResolver
    authorizes_object!

    authorize :some_permission
  end
end

解決された値に対して認可を行うには、リゾルバはどこかの時点で認可を適用しなければなりません。通常は#authorized_find!(**args) を使用します:

module Resolvers
  class MyResolver < BaseResolver
    authorize :some_permission

    def resolve(**args)
      authorized_find!(**args) # calls find_object
    end

    def find_object(id:)
      MyThing.find(id)
    end
  end
end

この2つのアプローチのうち、オブジェクトを認可する方が、 不要なクエリを避けることができるので効率的です。

フィールドの承認

フィールドはauthorize オプションで作成者を認可することができます。

フィールドの認可は現在のオブジェクトに対してチェックされ、認可はリゾルバの_前に_行われます。フィールドに認可チェックを適用する必要がある場合は、おそらくリゾルバに認可を追加するか、理想的には型に認可を追加することになるでしょう。

たとえば、次の作成者は、認証されたユーザーがsecretName フィールドを表示するには、プロジェクトの管理者レベルのアクセス権が必要であることを保証します:

module Types
  class ProjectType < BaseObject
    field :secret_name, ::GraphQL::Types::String, null: true, authorize: :owner_access
  end
end

この例では、より高価なクエリを回避するために、フィールド権限 (Ability.allowed?(current_user, :read_transactions, bank_account) など) を使用します:

module Types
  class BankAccountType < BaseObject
    field :transactions, ::Types::TransactionType.connection_type, null: true,
      authorize: :read_transactions
  end
end

フィールドの承認は、以下のような場合に推奨されます:

  • 他のフィールドとは異なるレベルのアクセス制御が必要なスカラー・フィールド(文字列、ブーリアン、数値)。
  • フィールドの解決を保存し、解決された各オブジェクトに対する個別のポリシー・チェックを回避するために、アクセス・チェックを親に適用できるオブジェクト・フィールドとコレクション・フィールド。

オブジェクトが親プロジェクトのアクセス・レベルと正確に一致しない限り、フィールドの作成者はオブジェクト・レベルのチェックに取って代わることはありません。例えば、イシューは親プロジェクトのアクセスレベルとは無関係に機密扱いにすることができます。したがって、Project.issue

複数の能力に対してフィールドを作成することもできます。能力を単一の値としてではなく、配列として渡してください:

module Types
  class MyType < BaseObject
    field :hidden_field, ::GraphQL::Types::Int,
      null: true,
      authorize: [:owner_access, :another_ability]
  end
end

MyType.hiddenField 、フィールドの作成者は以下のテストを行います:

Ability.allowed?(current_user, :owner_access, object_of_my_type) &&
    Ability.allowed?(current_user, :another_ability, object_of_my_type)

タイプ権限とフィールド権限の組み合わせ

作成者の権限は累積されます。言い換えると、現在認証されているユーザーは、フィールドとフィールド・タイプの両方の認証要件をパスする必要があります。

以下の単純化した例では、現在認証されているユーザがissueの作成者を確認するためには、ユーザに関するfirst_permission 、およびissueに関するsecond_permission の両方が必要です。

class UserType
  authorize :first_permission
end
class IssueType
  field :author, UserType, authorize: :second_permission
end

UserType 上のオブジェクト権限とIssueType.author 上のフィールド権限の組み合わせは、以下のテストを意味します:

Ability.allowed?(current_user, :second_permission, issue) &&
  Ability.allowed?(current_user, :first_permission, issue.author)