diff options
Diffstat (limited to 'lib/gitlab/graphql')
-rw-r--r-- | lib/gitlab/graphql/authorize/authorize_field_service.rb | 48 | ||||
-rw-r--r-- | lib/gitlab/graphql/docs/helper.rb | 6 | ||||
-rw-r--r-- | lib/gitlab/graphql/docs/templates/default.md.haml | 2 | ||||
-rw-r--r-- | lib/gitlab/graphql/lazy.rb | 26 | ||||
-rw-r--r-- | lib/gitlab/graphql/loaders/batch_model_loader.rb | 11 | ||||
-rw-r--r-- | lib/gitlab/graphql/present/instrumentation.rb | 11 |
6 files changed, 78 insertions, 26 deletions
diff --git a/lib/gitlab/graphql/authorize/authorize_field_service.rb b/lib/gitlab/graphql/authorize/authorize_field_service.rb index cbf3e7b8429..e8db619f88a 100644 --- a/lib/gitlab/graphql/authorize/authorize_field_service.rb +++ b/lib/gitlab/graphql/authorize/authorize_field_service.rb @@ -46,6 +46,8 @@ module Gitlab # Returns any authorize metadata from @field def field_authorizations + return [] if @field.metadata[:authorize] == true + Array.wrap(@field.metadata[:authorize]) end @@ -54,7 +56,7 @@ module Gitlab # The field is a built-in/scalar type, or a list of scalars # authorize using the parent's object parent_typed_object.object - elsif @field.connection? || resolved_type.is_a?(Array) + elsif @field.connection? || @field.type.list? || resolved_type.is_a?(Array) # The field is a connection or a list of non-built-in types, we'll # authorize each element when rendering nil @@ -75,16 +77,25 @@ module Gitlab # no need to do anything elsif authorizing_object # Authorizing fields representing scalars, or a simple field with an object - resolved_type if allowed_access?(current_user, authorizing_object) + ::Gitlab::Graphql::Lazy.with_value(authorizing_object) do |object| + resolved_type if allowed_access?(current_user, object) + end elsif @field.connection? - # A connection with pagination, modify the visible nodes on the - # connection type in place - resolved_type.object.edge_nodes.to_a.keep_if { |node| allowed_access?(current_user, node) } - resolved_type - elsif resolved_type.is_a? Array + ::Gitlab::Graphql::Lazy.with_value(resolved_type) do |type| + # A connection with pagination, modify the visible nodes on the + # connection type in place + nodes = to_nodes(type) + nodes.keep_if { |node| allowed_access?(current_user, node) } if nodes + type + end + elsif @field.type.list? || resolved_type.is_a?(Array) # A simple list of rendered types each object being an object to authorize - resolved_type.select do |single_object_type| - allowed_access?(current_user, realized(single_object_type).object) + ::Gitlab::Graphql::Lazy.with_value(resolved_type) do |items| + items.select do |single_object_type| + object_type = realized(single_object_type) + object = object_type.try(:object) || object_type + allowed_access?(current_user, object) + end end else raise "Can't authorize #{@field}" @@ -93,18 +104,23 @@ module Gitlab # Ensure that we are dealing with realized objects, not delayed promises def realized(thing) - case thing - when BatchLoader::GraphQL - thing.sync - when GraphQL::Execution::Lazy - thing.value # part of the private api, but we need to unwrap it here. + ::Gitlab::Graphql::Lazy.force(thing) + end + + # Try to get the connection + # can be at type.object or at type + def to_nodes(type) + if type.respond_to?(:nodes) + type.nodes + elsif type.respond_to?(:object) + to_nodes(type.object) else - thing + nil end end def allowed_access?(current_user, object) - object = object.sync if object.respond_to?(:sync) + object = realized(object) authorizations.all? do |ability| Ability.allowed?(current_user, ability, object) diff --git a/lib/gitlab/graphql/docs/helper.rb b/lib/gitlab/graphql/docs/helper.rb index dcd0e12cbfc..503b1064b11 100644 --- a/lib/gitlab/graphql/docs/helper.rb +++ b/lib/gitlab/graphql/docs/helper.rb @@ -81,11 +81,15 @@ module Gitlab # We are ignoring connections and built in types for now, # they should be added when queries are generated. def objects - graphql_object_types.select do |object_type| + object_types = graphql_object_types.select do |object_type| !object_type[:name]["Connection"] && !object_type[:name]["Edge"] && !object_type[:name]["__"] end + + object_types.each do |type| + type[:fields] += type[:connections] + end end # We ignore the built-in enum types. diff --git a/lib/gitlab/graphql/docs/templates/default.md.haml b/lib/gitlab/graphql/docs/templates/default.md.haml index ec052943589..97df4233905 100644 --- a/lib/gitlab/graphql/docs/templates/default.md.haml +++ b/lib/gitlab/graphql/docs/templates/default.md.haml @@ -14,6 +14,8 @@ CAUTION: **Caution:** Fields that are deprecated are marked with **{warning-solid}**. + Items (fields, enums, etc) that have been removed according to our [deprecation process](../index.md#deprecation-process) can be found + in [Removed Items](../removed_items.md). \ :plain diff --git a/lib/gitlab/graphql/lazy.rb b/lib/gitlab/graphql/lazy.rb index a7f7610a041..3cc11047387 100644 --- a/lib/gitlab/graphql/lazy.rb +++ b/lib/gitlab/graphql/lazy.rb @@ -3,17 +3,41 @@ module Gitlab module Graphql class Lazy + include Gitlab::Utils::StrongMemoize + + def initialize(&block) + @proc = block + end + + def force + strong_memoize(:force) { self.class.force(@proc.call) } + end + + def then(&block) + self.class.new { yield force } + end + # Force evaluation of a (possibly) lazy value def self.force(value) case value + when ::Gitlab::Graphql::Lazy + value.force when ::BatchLoader::GraphQL value.sync + when ::GraphQL::Execution::Lazy + value.value # part of the private api, but we can force this as well when ::Concurrent::Promise - value.execute.value + value.execute if value.state == :unscheduled + + value.value # value.value(10.seconds) else value end end + + def self.with_value(unforced, &block) + self.new { unforced }.then(&block) + end end end end diff --git a/lib/gitlab/graphql/loaders/batch_model_loader.rb b/lib/gitlab/graphql/loaders/batch_model_loader.rb index 164fe74148c..9b85ba164d4 100644 --- a/lib/gitlab/graphql/loaders/batch_model_loader.rb +++ b/lib/gitlab/graphql/loaders/batch_model_loader.rb @@ -12,14 +12,11 @@ module Gitlab # rubocop: disable CodeReuse/ActiveRecord def find - BatchLoader::GraphQL.for({ model: model_class, id: model_id.to_i }).batch do |loader_info, loader| - per_model = loader_info.group_by { |info| info[:model] } - per_model.each do |model, info| - ids = info.map { |i| i[:id] } - results = model.where(id: ids) + BatchLoader::GraphQL.for(model_id.to_i).batch(key: model_class) do |ids, loader, args| + model = args[:key] + results = model.where(id: ids) - results.each { |record| loader.call({ model: model, id: record.id }, record) } - end + results.each { |record| loader.call(record.id, record) } end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/gitlab/graphql/present/instrumentation.rb b/lib/gitlab/graphql/present/instrumentation.rb index 941a4f434a1..b8535575da5 100644 --- a/lib/gitlab/graphql/present/instrumentation.rb +++ b/lib/gitlab/graphql/present/instrumentation.rb @@ -4,6 +4,8 @@ module Gitlab module Graphql module Present class Instrumentation + SAFE_CONTEXT_KEYS = %i[current_user].freeze + def instrument(type, field) return field unless field.metadata[:type_class] @@ -22,7 +24,8 @@ module Gitlab next old_resolver.call(presented_type, args, context) end - presenter = presented_in.presenter_class.new(object, **context.to_h) + attrs = safe_context_values(context) + presenter = presented_in.presenter_class.new(object, **attrs) # we have to use the new `authorized_new` method, as `new` is protected wrapped = presented_type.class.authorized_new(presenter, context) @@ -34,6 +37,12 @@ module Gitlab resolve(resolve_with_presenter) end end + + private + + def safe_context_values(context) + context.to_h.slice(*SAFE_CONTEXT_KEYS) + end end end end |