GraphQL 권한 부여

인가는 다음 위치에 적용할 수 있습니다:

  • 유형:
    • 객체 ( ::Types::BaseObject에서 하위 클래스 모두)
    • Enum ( ::Types::BaseEnum에서 하위 클래스 모두)
  • 리졸버:
    • 필드 리졸버 ( ::Types::BaseResolver에서 하위 클래스 모두)
    • 변형 ( ::Types::BaseMutation에서 하위 클래스 모두)
  • 필드 (field DSL 메소드를 사용하여 선언된 모든 필드)

추상 유형 (인터페이스 및 연합)에 대해 특정 인가를 지정할 수 없습니다. 추상 유형은 멤버 유형으로 위임됩니다. 기본 내장 스칼라(예: 정수)에는 인가가 없습니다.

저희의 인가 시스템은 응용 프로그램의 나머지 부분과 마찬가지로 동일한 DeclarativePolicy 시스템을 사용합니다.

  • 단일 값 (예: Query.project와 같은)의 경우 현재 인증된 사용자가 인가에 실패하면 필드가 null로 해결됩니다.
  • 컬렉션(예: Project.issues와 같은)의 경우 컬렉션이 사용자의 인가 확인에 실패한 객체를 제외하고 필터링됩니다. 이 필터링 과정(또는 _레드액션_이라고도 함)은 페이지네이션 이후에 발생하므로 리다렉트된 객체가 제거되어 요청된 페이지 크기보다 작은 페이지가 될 수 있습니다.

또한 변형에서 리소스에 대해 인가하는 것을 참조하세요.

참고: 최선의 방법은 현재 인증된 사용자가 볼 수 있는 것만 기존 파인더(finders)를 통해 먼저 로드하고 인가에 의존하지 않습니다. 이렇게 하면 데이터베이스 쿼리와 불필요한 인가 확인을 최소화할 수 있습니다. 또한, 이는 단축된 페이지와 같은 기밀 리소스의 존재를 노출시킬 수 있는 상황을 피할 수 있습니다.

여기서 언급한 모든 인가 체계의 예시는 authorization_spec.rb를 참조하세요.

유형 인가

authorize 메소드에 권한을 전달하여 유형을 인가합니다. 동일한 유형의 모든 필드는 현재 인증된 사용자가 필요한 권한을 가지고 있는지 확인하여 인가됩니다.

예를 들어, 다음 인가는 현재 인증된 사용자가 read_project 능력을 가진 프로젝트만 볼 수 있도록 보장합니다(Types::ProjectType을 사용하는 필드에서 프로젝트를 반환하는 경우에 해당):

module Types
  class ProjectType < BaseObject
    authorize :read_project
  end
end

여러 능력에 대한 인가도 가능합니다. 이 경우 모든 능력 확인이 통과해야 합니다.

예를 들어, 다음 인가는 현재 인증된 사용자가 프로젝트를 볼 수 있으려면 read_projectanother_ability 능력을 가져야 합니다:

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

리졹버 인가

리졸버에는 자체 인가가 있을 수 있으며, 이는 상위 객체 또는 해결된 값에 적용할 수 있습니다.

상위 개체에 대한 인가를 하는 리졸버의 예로는 Resolvers::BoardListsResolver가 있으며, 이는 실행 전에 상위가 :read_list를 충족해야 합니다.

해결된 리소스에 대한 인가를 하는 예로는 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) # find_object를 호출합니다
    end

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

두 가지 접근 방법 중 객체를 인가하는 것이 더 효율적입니다. 왜냐하면 불필요한 쿼리를 피할 수 있기 때문입니다.

필드 인가

authorize 옵션으로 필드를 인가할 수 있습니다.

필드 인가는 현재 객체에 대해 확인되며, 인가는 결정(해결) 이전에 이뤄지므로 필드에는 해결된 리소스에 액세스할 수 없습니다. 필드에 인가 확인을 적용해야 하는 경우에는 resolver에 인가를 추가하거나 이상적으로는 유형에 인가를 추가해야 합니다.

예를 들어, 관리자 수준 액세스 권한이 있는 사용자만 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)

유형 및 필드 권한 함께

권한은 누적됩니다. 다시 말해, 현재 인증된 사용자는 필드 및 필드 유형 모두의 권한 요구 사항을 통과해야 할 수 있습니다.

다음의 간소화된 예제에서 현재 인증된 사용자는 이슈의 작성자를 보기 위해 사용자에게 first_permission과 이슈에게 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)