From 5e02ef43bc655456ce3004a1f47a6dcf5200c1d2 Mon Sep 17 00:00:00 2001 From: Alex Kalderimis <akalderimis@gitlab.com> Date: Wed, 29 Sep 2021 14:03:49 +0100 Subject: [PATCH] Add BaseEdge base class See https://gitlab.com/gitlab-org/gitlab/-/issues/341911 This adds a new base class for edges that uses our custom `BaseField` as the standard field class. We do not need to customize authorization for edges, since nodes are redacted by our connections before edges are constructed from them. All our edges should inherit from this base class in order to use field customizations. Changelog: changed --- app/graphql/types/base_edge.rb | 7 +++ app/graphql/types/base_field.rb | 2 + rubocop/cop/graphql/authorize_types.rb | 2 +- spec/graphql/types/base_edge_spec.rb | 76 ++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 app/graphql/types/base_edge.rb create mode 100644 spec/graphql/types/base_edge_spec.rb diff --git a/app/graphql/types/base_edge.rb b/app/graphql/types/base_edge.rb new file mode 100644 index 0000000000000..f4409c983f881 --- /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 93e17ea6dfcae..75909592c6cb5 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 180a1a27a858c..d5866aa0aaf57 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 0000000000000..237726a352c40 --- /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 -- GitLab