Commit 731397ff authored by Mark Chao's avatar Mark Chao

Merge branch...

Merge branch '329695-add-read_commit_status-field-authorization-to-pipeline-fields-that-can-return-jobs' into 'master'

Resolve "Add `:read_commit_status` field authorization to Pipeline fields that can return jobs" [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!60754
parents dabb6d6d f2844d03
...@@ -60,12 +60,17 @@ module Types ...@@ -60,12 +60,17 @@ module Types
field :committed_at, Types::TimeType, null: true, field :committed_at, Types::TimeType, null: true,
description: "Timestamp of the pipeline's commit." description: "Timestamp of the pipeline's commit."
field :stages, Types::Ci::StageType.connection_type, null: true, field :stages,
type: Types::Ci::StageType.connection_type,
null: true,
authorize: :read_commit_status,
description: 'Stages of the pipeline.', description: 'Stages of the pipeline.',
extras: [:lookahead], extras: [:lookahead],
resolver: Resolvers::Ci::PipelineStagesResolver resolver: Resolvers::Ci::PipelineStagesResolver
field :user, Types::UserType, null: true, field :user,
type: Types::UserType,
null: true,
description: 'Pipeline user.' description: 'Pipeline user.'
field :retryable, GraphQL::BOOLEAN_TYPE, field :retryable, GraphQL::BOOLEAN_TYPE,
...@@ -81,12 +86,14 @@ module Types ...@@ -81,12 +86,14 @@ module Types
field :jobs, field :jobs,
::Types::Ci::JobType.connection_type, ::Types::Ci::JobType.connection_type,
null: true, null: true,
authorize: :read_commit_status,
description: 'Jobs belonging to the pipeline.', description: 'Jobs belonging to the pipeline.',
resolver: ::Resolvers::Ci::JobsResolver resolver: ::Resolvers::Ci::JobsResolver
field :job, field :job,
type: ::Types::Ci::JobType, type: ::Types::Ci::JobType,
null: true, null: true,
authorize: :read_commit_status,
description: 'A specific job in this pipeline, either by name or ID.' do description: 'A specific job in this pipeline, either by name or ID.' do
argument :id, argument :id,
type: ::Types::GlobalIDType[::CommitStatus], type: ::Types::GlobalIDType[::CommitStatus],
...@@ -98,7 +105,10 @@ module Types ...@@ -98,7 +105,10 @@ module Types
description: 'Name of the job.' description: 'Name of the job.'
end end
field :source_job, Types::Ci::JobType, null: true, field :source_job,
type: Types::Ci::JobType,
null: true,
authorize: :read_commit_status,
description: 'Job where pipeline was triggered from.' description: 'Job where pipeline was triggered from.'
field :downstream, Types::Ci::PipelineType.connection_type, null: true, field :downstream, Types::Ci::PipelineType.connection_type, null: true,
......
...@@ -2,18 +2,24 @@ ...@@ -2,18 +2,24 @@
module Types module Types
module Ci module Ci
# rubocop: disable Graphql/AuthorizeTypes
class StageType < BaseObject class StageType < BaseObject
graphql_name 'CiStage' graphql_name 'CiStage'
authorize :read_commit_status
field :name, GraphQL::STRING_TYPE, null: true, field :name,
type: GraphQL::STRING_TYPE,
null: true,
description: 'Name of the stage.' description: 'Name of the stage.'
field :groups, Ci::GroupType.connection_type, null: true, field :groups,
type: Ci::GroupType.connection_type,
null: true,
extras: [:lookahead], extras: [:lookahead],
description: 'Group of jobs for the stage.' description: 'Group of jobs for the stage.'
field :detailed_status, Types::Ci::DetailedStatusType, null: true, field :detailed_status, Types::Ci::DetailedStatusType,
null: true,
description: 'Detailed status of the stage.' description: 'Detailed status of the stage.'
field :jobs, Ci::JobType.connection_type, null: true, field :jobs, Ci::JobType.connection_type,
null: true,
description: 'Jobs for the stage.', description: 'Jobs for the stage.',
method: 'latest_statuses' method: 'latest_statuses'
...@@ -37,33 +43,6 @@ module Types ...@@ -37,33 +43,6 @@ module Types
key = indexed[stage_id] key = indexed[stage_id]
groups = ::Ci::Group.fabricate(project, key.stage, statuses) groups = ::Ci::Group.fabricate(project, key.stage, statuses)
if Feature.enabled?(:ci_no_empty_groups, project)
groups.each do |group|
rejected = group.jobs.reject { |job| Ability.allowed?(current_user, :read_commit_status, job) }
group.jobs.select! { |job| Ability.allowed?(current_user, :read_commit_status, job) }
next unless group.jobs.empty?
exc = StandardError.new('Empty Ci::Group')
traces = rejected.map do |job|
trace = []
policy = Ability.policy_for(current_user, job)
policy.debug(:read_commit_status, trace)
trace
end
extra = {
current_user_id: current_user&.id,
project_id: project.id,
pipeline_id: pl.id,
stage_id: stage_id,
group_name: group.name,
rejected_job_ids: rejected.map(&:id),
rejected_traces: traces
}
Gitlab::ErrorTracking.track_exception(exc, extra)
end
groups.reject! { |group| group.jobs.empty? }
end
loader.call(key, groups) loader.call(key, groups)
end end
end end
......
...@@ -184,8 +184,9 @@ module Types ...@@ -184,8 +184,9 @@ module Types
resolver: Resolvers::ProjectPackagesResolver resolver: Resolvers::ProjectPackagesResolver
field :jobs, field :jobs,
Types::Ci::JobType.connection_type, type: Types::Ci::JobType.connection_type,
null: true, null: true,
authorize: :read_commit_status,
description: 'Jobs of a project. This field can only be resolved for one project in any single request.', description: 'Jobs of a project. This field can only be resolved for one project in any single request.',
resolver: Resolvers::ProjectJobsResolver resolver: Resolvers::ProjectJobsResolver
......
# frozen_string_literal: true
module Ci
class StagePolicy < BasePolicy
delegate :pipeline
end
end
---
title: Adds field authorization to pipeline fields
merge_request: 60754
author:
type: changed
---
name: ci_no_empty_groups
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58789
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327139
milestone: '13.11'
type: development
group: group::verify
default_enabled: false
...@@ -51,6 +51,87 @@ RSpec.describe 'Query.project(fullPath).pipelines' do ...@@ -51,6 +51,87 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
end end
end end
describe '.stages' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_empty_pipeline, project: project) }
let_it_be(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: project) }
let_it_be(:other_stage) { create(:ci_stage_entity, pipeline: pipeline, project: project, name: 'other') }
let(:first_n) { var('Int') }
let(:query_path) do
[
[:project, { full_path: project.full_path }],
[:pipelines],
[:nodes],
[:stages, { first: first_n }],
[:nodes]
]
end
let(:query) do
with_signature([first_n], wrap_fields(query_graphql_path(query_path, :name)))
end
before_all do
# see app/services/ci/ensure_stage_service.rb to explain why we use stage_id
create(:ci_build, pipeline: pipeline, stage_id: stage.id, name: 'linux: [foo]')
create(:ci_build, pipeline: pipeline, stage_id: stage.id, name: 'linux: [bar]')
create(:ci_build, pipeline: pipeline, stage_id: other_stage.id, name: 'linux: [baz]')
end
it 'is null if the user is a guest' do
project.add_guest(user)
post_graphql(query, current_user: user, variables: first_n.with(1))
expect(graphql_data_at(:project, :pipelines, :nodes)).to contain_exactly a_hash_including('stages' => be_nil)
end
it 'is present if the user has reporter access' do
project.add_reporter(user)
post_graphql(query, current_user: user)
expect(graphql_data_at(:project, :pipelines, :nodes, :stages, :nodes, :name))
.to contain_exactly(eq(stage.name), eq(other_stage.name))
end
describe '.groups' do
let(:query_path) do
[
[:project, { full_path: project.full_path }],
[:pipelines],
[:nodes],
[:stages],
[:nodes],
[:groups],
[:nodes]
]
end
let(:query) do
wrap_fields(query_graphql_path(query_path, :name))
end
it 'is empty if the user is a guest' do
project.add_guest(user)
post_graphql(query, current_user: user)
expect(graphql_data_at(:project, :pipelines, :nodes, :stages, :nodes, :groups)).to be_empty
end
it 'is present if the user has reporter access' do
project.add_reporter(user)
post_graphql(query, current_user: user)
expect(graphql_data_at(:project, :pipelines, :nodes, :stages, :nodes, :groups, :nodes, :name))
.to contain_exactly('linux', 'linux')
end
end
end
describe '.jobs' do describe '.jobs' do
let(:first_n) { var('Int') } let(:first_n) { var('Int') }
let(:query_path) do let(:query_path) do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment