diff --git a/app/graphql/types/base_edge.rb b/app/graphql/types/base_edge.rb new file mode 100644 index 0000000000000000000000000000000000000000..f4409c983f8819eba88777354ce4cee80d1d95d8 --- /dev/null +++ b/app/graphql/types/base_edge.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Types + class BaseEdge < GraphQL::Types::Relay::BaseEdge + field_class Types::BaseField + end +end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index 93e17ea6dfcaed478da517dd1d6779b83e5d911b..75909592c6cb5fc370daa6c71e060960b68f3543 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -78,6 +78,8 @@ def visible?(context) attr_reader :feature_flag def field_authorized?(object, ctx) + object = object.node if object.is_a?(GraphQL::Pagination::Connection::Edge) + authorization.ok?(object, ctx[:current_user]) end diff --git a/rubocop/cop/graphql/authorize_types.rb b/rubocop/cop/graphql/authorize_types.rb index 180a1a27a858c57c87a8df28477047a6341e288e..d5866aa0aaf57fe9c107810560dcec8a56745807 100644 --- a/rubocop/cop/graphql/authorize_types.rb +++ b/rubocop/cop/graphql/authorize_types.rb @@ -8,7 +8,7 @@ class AuthorizeTypes < RuboCop::Cop::Cop 'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#type-authorization' # We want to exclude our own basetypes and scalars - ALLOWED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType SubscriptionType + ALLOWED_TYPES = %w[BaseEnum BaseEdge BaseScalar BasePermissionType MutationType SubscriptionType QueryType GraphQL::Schema BaseUnion BaseInputObject].freeze def_node_search :authorize?, <<~PATTERN diff --git a/spec/graphql/types/base_edge_spec.rb b/spec/graphql/types/base_edge_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..237726a352c4075c6af46d427885fff44a778b8d --- /dev/null +++ b/spec/graphql/types/base_edge_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::BaseEdge do + include GraphqlHelpers + + let_it_be(:test_schema) do + project_edge_type = Class.new(described_class) do + field :proof_of_admin_rights, String, + null: true, authorize: :admin_project + + def proof_of_admin_rights + 'ok' + end + end + + project_type = Class.new(::Types::BaseObject) do + graphql_name 'Project' + authorize :read_project + edge_type_class project_edge_type + + field :name, String, null: false + end + + Class.new(GraphQL::Schema) do + lazy_resolve ::Gitlab::Graphql::Lazy, :force + use ::GraphQL::Pagination::Connections + use ::Gitlab::Graphql::Pagination::Connections + + query(Class.new(::Types::BaseObject) do + graphql_name 'Query' + field :projects, project_type.connection_type, null: false + + def projects + context[:projects] + end + end) + end + end + + def document + GraphQL.parse(<<~GQL) + query { + projects { + edges { + proofOfAdminRights + node { name } + } + } + } + GQL + end + + it 'supports field authorization on edge fields' do + user = create(:user) + private_project = create(:project, :private) + member_project = create(:project, :private) + maintainer_project = create(:project, :private) + public_project = create(:project, :public) + + member_project.add_developer(user) + maintainer_project.add_maintainer(user) + projects = [private_project, member_project, maintainer_project, public_project] + + data = { current_user: user, projects: projects } + query = GraphQL::Query.new(test_schema, document: document, context: data) + result = query.result.to_h + + expect(graphql_dig_at(result, 'data', 'projects', 'edges', 'node', 'name')) + .to contain_exactly(member_project.name, maintainer_project.name, public_project.name) + + expect(graphql_dig_at(result, 'data', 'projects', 'edges', 'proofOfAdminRights')) + .to contain_exactly('ok') + end +end