Commit b9a10010 authored by Steve Abrams's avatar Steve Abrams Committed by Kamil Trzciński

Fix bugs and security concerns in docker group API

Add additional permission checks to container finder
Refactor ContainerRepositoriesFinder
Fix finder to include all group descendents as well
Only include projects with container registry enabled
parent b56567dd
# frozen_string_literal: true # frozen_string_literal: true
class ContainerRepositoriesFinder class ContainerRepositoriesFinder
# id: group or project id VALID_SUBJECTS = [Group, Project].freeze
# container_type: :group or :project
def initialize(id:, container_type:) def initialize(user:, subject:)
@id = id @user = user
@type = container_type.to_sym @subject = subject
end end
def execute def execute
if project_type? raise ArgumentError, "invalid subject_type" unless valid_subject_type?
project.container_repositories return unless authorized?
else
group.container_repositories return project_repositories if @subject.is_a?(Project)
end return group_repositories if @subject.is_a?(Group)
end end
private private
attr_reader :id, :type def valid_subject_type?
VALID_SUBJECTS.include?(@subject.class)
end
def project_repositories
return unless @subject.container_registry_enabled
def project_type? @subject.container_repositories
type == :project
end end
def project def group_repositories
Project.find(id) ContainerRepository.for_group_and_its_subgroups(@subject)
end end
def group def authorized?
Group.find(id) Ability.allowed?(@user, :read_container_image, @subject)
end end
end end
...@@ -12,6 +12,9 @@ class ContainerRepository < ApplicationRecord ...@@ -12,6 +12,9 @@ class ContainerRepository < ApplicationRecord
scope :ordered, -> { order(:name) } scope :ordered, -> { order(:name) }
scope :with_api_entity_associations, -> { preload(project: [:route, { namespace: :route }]) } scope :with_api_entity_associations, -> { preload(project: [:route, { namespace: :route }]) }
scope :for_group_and_its_subgroups, ->(group) do
where(project_id: Project.for_group_and_its_subgroups(group).with_container_registry.select(:id))
end
# rubocop: disable CodeReuse/ServiceClass # rubocop: disable CodeReuse/ServiceClass
def registry def registry
......
...@@ -395,6 +395,7 @@ class Project < ApplicationRecord ...@@ -395,6 +395,7 @@ class Project < ApplicationRecord
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) } scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) } scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
scope :with_container_registry, -> { where(container_registry_enabled: true) }
scope :inside_path, ->(path) do scope :inside_path, ->(path) do
# We need routes alias rs for JOIN so it does not conflict with # We need routes alias rs for JOIN so it does not conflict with
# includes(:route) which we use in ProjectsFinder. # includes(:route) which we use in ProjectsFinder.
......
...@@ -23,7 +23,7 @@ module API ...@@ -23,7 +23,7 @@ module API
end end
get ':id/registry/repositories' do get ':id/registry/repositories' do
repositories = ContainerRepositoriesFinder.new( repositories = ContainerRepositoriesFinder.new(
id: user_group.id, container_type: :group user: current_user, subject: user_group
).execute ).execute
present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags] present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags]
......
...@@ -24,7 +24,7 @@ module API ...@@ -24,7 +24,7 @@ module API
end end
get ':id/registry/repositories' do get ':id/registry/repositories' do
repositories = ContainerRepositoriesFinder.new( repositories = ContainerRepositoriesFinder.new(
id: user_project.id, container_type: :project user: current_user, subject: user_project
).execute ).execute
present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags] present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags]
......
...@@ -3,42 +3,50 @@ ...@@ -3,42 +3,50 @@
require 'spec_helper' require 'spec_helper'
describe ContainerRepositoriesFinder do describe ContainerRepositoriesFinder do
let_it_be(:reporter) { create(:user) }
let_it_be(:guest) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, group: group) } let(:project) { create(:project, group: group) }
let(:project_repository) { create(:container_repository, project: project) } let(:project_repository) { create(:container_repository, project: project) }
before do
group.add_reporter(reporter)
project.add_reporter(reporter)
end
describe '#execute' do describe '#execute' do
let(:id) { nil } context 'with authorized user' do
subject { described_class.new(user: reporter, subject: subject_object).execute }
subject { described_class.new(id: id, container_type: container_type).execute } context 'when subject_type is group' do
let(:subject_object) { group }
let(:other_project) { create(:project, group: group) }
context 'when container_type is group' do let(:other_repository) do
let(:other_project) { create(:project, group: group) } create(:container_repository, name: 'test_repository2', project: other_project)
end
let(:other_repository) do it { is_expected.to match_array([project_repository, other_repository]) }
create(:container_repository, name: 'test_repository2', project: other_project)
end end
let(:container_type) { :group } context 'when subject_type is project' do
let(:id) { group.id } let(:subject_object) { project }
it { is_expected.to match_array([project_repository, other_repository]) } it { is_expected.to match_array([project_repository]) }
end end
context 'when container_type is project' do context 'with invalid subject_type' do
let(:container_type) { :project } let(:subject_object) { "invalid type" }
let(:id) { project.id }
it { is_expected.to match_array([project_repository]) } it { expect { subject }.to raise_exception('invalid subject_type') }
end
end end
context 'with invalid id' do context 'with unauthorized user' do
let(:container_type) { :project } subject { described_class.new(user: guest, subject: group).execute }
let(:id) { 123456789 }
it 'raises an error' do it { is_expected.to be nil }
expect { subject.execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end end
end end
end end
...@@ -235,4 +235,36 @@ describe ContainerRepository do ...@@ -235,4 +235,36 @@ describe ContainerRepository do
expect(repository).not_to be_persisted expect(repository).not_to be_persisted
end end
end end
describe '.for_group_and_its_subgroups' do
subject { described_class.for_group_and_its_subgroups(test_group) }
context 'in a group' do
let(:test_group) { group }
it { is_expected.to contain_exactly(repository) }
end
context 'with a subgroup' do
let(:test_group) { create(:group) }
let(:another_project) { create(:project, path: 'test', group: test_group) }
let(:another_repository) do
create(:container_repository, name: 'my_image', project: another_project)
end
before do
group.parent = test_group
group.save
end
it { is_expected.to contain_exactly(repository, another_repository) }
end
context 'group without container_repositories' do
let(:test_group) { create(:group) }
it { is_expected.to eq([]) }
end
end
end end
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
require 'spec_helper' require 'spec_helper'
describe API::GroupContainerRepositories do describe API::GroupContainerRepositories do
set(:group) { create(:group, :private) } let_it_be(:group) { create(:group, :private) }
set(:project) { create(:project, :private, group: group) } let_it_be(:project) { create(:project, :private, group: group) }
let(:reporter) { create(:user) } let_it_be(:reporter) { create(:user) }
let(:guest) { create(:user) } let_it_be(:guest) { create(:user) }
let(:root_repository) { create(:container_repository, :root, project: project) } let(:root_repository) { create(:container_repository, :root, project: project) }
let(:test_repository) { create(:container_repository, project: project) } let(:test_repository) { create(:container_repository, project: project) }
......
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