Commit ed6f3663 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-list-runner-jobs-14-10' into '14-10-stable-ee'

Adds a filter based on user access to Runner jobs endpoint

See merge request gitlab-org/security/gitlab!2497
parents 316736b8 dafaf3e5
...@@ -6,19 +6,29 @@ module Ci ...@@ -6,19 +6,29 @@ module Ci
ALLOWED_INDEXED_COLUMNS = %w[id].freeze ALLOWED_INDEXED_COLUMNS = %w[id].freeze
def initialize(runner, params = {}) def initialize(runner, current_user, params = {})
@runner = runner @runner = runner
@user = current_user
@params = params @params = params
end end
def execute def execute
items = @runner.builds items = @runner.builds
items = by_permission(items)
items = by_status(items) items = by_status(items)
sort_items(items) sort_items(items)
end end
private private
# rubocop: disable CodeReuse/ActiveRecord
def by_permission(items)
return items if @user.can_read_all_resources?
items.for_project(@user.authorized_project_mirrors(Gitlab::Access::REPORTER).select(:project_id))
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def by_status(items) def by_status(items)
return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status]) return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])
......
...@@ -4,6 +4,8 @@ module Ci ...@@ -4,6 +4,8 @@ module Ci
# This model represents a shadow table of the main database's projects table. # This model represents a shadow table of the main database's projects table.
# It allows us to navigate the project and namespace hierarchy on the ci database. # It allows us to navigate the project and namespace hierarchy on the ci database.
class ProjectMirror < ApplicationRecord class ProjectMirror < ApplicationRecord
include FromUnion
belongs_to :project belongs_to :project
scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) } scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) }
......
...@@ -1646,6 +1646,14 @@ class User < ApplicationRecord ...@@ -1646,6 +1646,14 @@ class User < ApplicationRecord
true true
end end
def authorized_project_mirrors(level)
projects = Ci::ProjectMirror.by_project_id(ci_project_mirrors_for_project_members(level))
namespace_projects = Ci::ProjectMirror.by_namespace_id(ci_namespace_mirrors_for_group_members(level).select(:namespace_id))
Ci::ProjectMirror.from_union([projects, namespace_projects])
end
def ci_owned_runners def ci_owned_runners
@ci_owned_runners ||= begin @ci_owned_runners ||= begin
if ci_owned_runners_cross_joins_fix_enabled? if ci_owned_runners_cross_joins_fix_enabled?
...@@ -2116,6 +2124,10 @@ class User < ApplicationRecord ...@@ -2116,6 +2124,10 @@ class User < ApplicationRecord
end end
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
def ci_project_mirrors_for_project_members(level)
project_members.where('access_level >= ?', level).pluck(:source_id)
end
def notification_email_verified def notification_email_verified
return if notification_email.blank? || temp_oauth_email? return if notification_email.blank? || temp_oauth_email?
...@@ -2267,7 +2279,7 @@ class User < ApplicationRecord ...@@ -2267,7 +2279,7 @@ class User < ApplicationRecord
end end
def ci_owned_project_runners_from_project_members def ci_owned_project_runners_from_project_members
project_ids = project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id) project_ids = ci_project_mirrors_for_project_members(Gitlab::Access::MAINTAINER)
Ci::Runner Ci::Runner
.joins(:runner_projects) .joins(:runner_projects)
......
...@@ -359,7 +359,8 @@ and will be removed in [GitLab 16.0](https://gitlab.com/gitlab-org/gitlab/-/issu ...@@ -359,7 +359,8 @@ and will be removed in [GitLab 16.0](https://gitlab.com/gitlab-org/gitlab/-/issu
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3.
List jobs that are being processed or were processed by specified runner. List jobs that are being processed or were processed by the specified runner. The list of jobs is limited
to projects where the user has at least the Reporter role.
```plaintext ```plaintext
GET /runners/:id/jobs GET /runners/:id/jobs
......
...@@ -127,7 +127,7 @@ module API ...@@ -127,7 +127,7 @@ module API
runner = get_runner(params[:id]) runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner) authenticate_list_runners_jobs!(runner)
jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute
present paginate(jobs), with: Entities::Ci::JobBasicWithProject present paginate(jobs), with: Entities::Ci::JobBasicWithProject
end end
......
...@@ -5,12 +5,17 @@ require 'spec_helper' ...@@ -5,12 +5,17 @@ require 'spec_helper'
RSpec.describe Ci::RunnerJobsFinder do RSpec.describe Ci::RunnerJobsFinder do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :instance) } let(:runner) { create(:ci_runner, :instance) }
let(:user) { create(:user) }
let(:params) { {} }
subject { described_class.new(runner, params).execute } subject { described_class.new(runner, user, params).execute }
before do
project.add_developer(user)
end
describe '#execute' do describe '#execute' do
context 'when params is empty' do context 'when params is empty' do
let(:params) { {} }
let!(:job) { create(:ci_build, runner: runner, project: project) } let!(:job) { create(:ci_build, runner: runner, project: project) }
let!(:job1) { create(:ci_build, project: project) } let!(:job1) { create(:ci_build, project: project) }
...@@ -20,6 +25,50 @@ RSpec.describe Ci::RunnerJobsFinder do ...@@ -20,6 +25,50 @@ RSpec.describe Ci::RunnerJobsFinder do
end end
end end
context 'when the user has guest access' do
it 'does not returns jobs the user does not have permission to see' do
another_project = create(:project)
job = create(:ci_build, runner: runner, project: another_project)
another_project.add_guest(user)
is_expected.not_to match_array(job)
end
end
context 'when the user has permission to read all resources' do
let(:user) { create(:user, :admin) }
it 'returns all the jobs assigned to a runner' do
jobs = create_list(:ci_build, 5, runner: runner, project: project)
is_expected.to match_array(jobs)
end
end
context 'when the user has different access levels in different projects' do
it 'returns only the jobs the user has permission to see' do
guest_project = create(:project)
reporter_project = create(:project)
_guest_jobs = create_list(:ci_build, 2, runner: runner, project: guest_project)
reporter_jobs = create_list(:ci_build, 3, runner: runner, project: reporter_project)
guest_project.add_guest(user)
reporter_project.add_reporter(user)
is_expected.to match_array(reporter_jobs)
end
end
context 'when the user has reporter access level or greater' do
it 'returns jobs assigned to the Runner that the user has accesss to' do
jobs = create_list(:ci_build, 3, runner: runner, project: project)
is_expected.to match_array(jobs)
end
end
context 'when params contains status' do context 'when params contains status' do
Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status| Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
context "when status is #{target_status}" do context "when status is #{target_status}" do
......
...@@ -4045,6 +4045,41 @@ RSpec.describe User do ...@@ -4045,6 +4045,41 @@ RSpec.describe User do
end end
end end
describe '#authorized_project_mirrors' do
it 'returns project mirrors where the user has access equal to or above the given level' do
guest_project = create(:project)
reporter_project = create(:project)
maintainer_project = create(:project)
guest_group = create(:group)
reporter_group = create(:group)
maintainer_group = create(:group)
_guest_group_project = create(:project, group: guest_group)
reporter_group_project = create(:project, group: reporter_group)
maintainer_group_project = create(:project, group: maintainer_group)
user = create(:user)
guest_project.add_guest(user)
reporter_project.add_reporter(user)
maintainer_project.add_maintainer(user)
guest_group.add_guest(user)
reporter_group.add_reporter(user)
maintainer_group.add_maintainer(user)
project_mirrors = user.authorized_project_mirrors(Gitlab::Access::REPORTER)
expect(project_mirrors.pluck(:project_id)).to contain_exactly(
reporter_group_project.id,
maintainer_group_project.id,
reporter_project.id,
maintainer_project.id
)
end
end
shared_context '#ci_owned_runners' do shared_context '#ci_owned_runners' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -804,6 +804,23 @@ RSpec.describe API::Ci::Runners do ...@@ -804,6 +804,23 @@ RSpec.describe API::Ci::Runners do
expect(json_response).to be_an(Array) expect(json_response).to be_an(Array)
expect(json_response.length).to eq(2) expect(json_response.length).to eq(2)
end end
context 'when user does not have authorization to see all jobs' do
it 'shows only jobs it has permission to see' do
create(:ci_build, :running, runner: two_projects_runner, project: project)
create(:ci_build, :running, runner: two_projects_runner, project: project2)
project.add_guest(user2)
project2.add_maintainer(user2)
get api("/runners/#{two_projects_runner.id}/jobs", user2)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(1)
end
end
end end
context 'when valid status is provided' do context 'when valid status is provided' 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