From 025265ce6915bcc2114bb92aa6ca2f264bfff435 Mon Sep 17 00:00:00 2001 From: Radamanthus Batnag <rbatnag@gitlab.com> Date: Mon, 17 Feb 2025 13:19:42 +0000 Subject: [PATCH] Package registry: Optimize query for selecting namespace projects --- .../packages/npm/packages_for_user_finder.rb | 14 ++++ .../concerns/packages/finder_helper_spec.rb | 69 +++++++++++++------ .../npm/packages_for_user_finder_spec.rb | 50 +++++++++++++- 3 files changed, 108 insertions(+), 25 deletions(-) diff --git a/app/finders/packages/npm/packages_for_user_finder.rb b/app/finders/packages/npm/packages_for_user_finder.rb index e9b069340b2e..7979e9e1dd42 100644 --- a/app/finders/packages/npm/packages_for_user_finder.rb +++ b/app/finders/packages/npm/packages_for_user_finder.rb @@ -20,6 +20,20 @@ def group_packages packages_visible_to_user(@current_user, within_group: @project_or_group, with_package_registry_enabled: true) end + # This overrides projects_visible_to_reporters in app/finders/concerns/packages/finder_helper.rb + # to implement npm-specific optimizations + def projects_visible_to_reporters(user, within_group:, _within_public_package_registry: false) + return user.accessible_projects if user.is_a?(DeployToken) + + access = if Feature.enabled?(:allow_guest_plus_roles_to_pull_packages, within_group.root_ancestor) + ::Gitlab::Access::GUEST + else + ::Gitlab::Access::REPORTER + end + + ::Project.public_or_visible_to_user(user, access).by_any_overlap_with_traversal_ids(within_group.id) + end + override :packages_class def packages_class ::Packages::Npm::Package diff --git a/spec/finders/concerns/packages/finder_helper_spec.rb b/spec/finders/concerns/packages/finder_helper_spec.rb index e0729cff5ac8..b8696cbe500e 100644 --- a/spec/finders/concerns/packages/finder_helper_spec.rb +++ b/spec/finders/concerns/packages/finder_helper_spec.rb @@ -323,6 +323,20 @@ def packages_class it { is_expected.to be_empty } end + shared_examples 'handles a group deploy token' do + let_it_be(:user) { create(:deploy_token, :group, read_package_registry: true) } + let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) } + + before do + project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get('PRIVATE', false)) + subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get('PRIVATE', false)) + project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get('PRIVATE', false)) + group.update!(visibility_level: Gitlab::VisibilityLevel.const_get('PRIVATE', false)) + end + + it_behaves_like 'returning both projects' + end + describe '#projects_visible_to_user' do subject { finder.projects_visible_to_user(user, within_group: group) } @@ -365,28 +379,7 @@ def packages_class end end - context 'with a group deploy token' do - let_it_be(:user) { create(:deploy_token, :group, read_package_registry: true) } - let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) } - - where(:group_visibility, :subgroup_visibility, :project2_visibility, :shared_example_name) do - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | 'returning both projects' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | 'returning both projects' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | 'returning both projects' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | 'returning both projects' - end - - with_them do - before do - project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) - subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) - project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) - group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) - end - - it_behaves_like params[:shared_example_name] - end - end + it_behaves_like 'handles a group deploy token' end describe '#projects_visible_to_user_including_public_registries' do @@ -415,5 +408,37 @@ def packages_class it_behaves_like params[:shared_example_name] end end + + describe '#projects_visible_to_reporters' do + before_all do + project1.add_reporter(user) + end + + let(:within_public_package_registry) { false } + + subject do + finder.projects_visible_to_reporters(user, + within_group: group, + within_public_package_registry: within_public_package_registry) + end + + it_behaves_like 'handles a group deploy token' + + it_behaves_like 'returning project1' + + context 'when within_public_package_registry set to true' do + let_it_be(:project_with_public_package_registry) { create(:project, group: group) } + + let(:within_public_package_registry) { true } + + before do + project_with_public_package_registry + .project_feature + .update_attribute(:package_registry_access_level, ::ProjectFeature::PUBLIC) + end + + it { is_expected.to match_array [project1, project_with_public_package_registry] } + end + end end end diff --git a/spec/finders/packages/npm/packages_for_user_finder_spec.rb b/spec/finders/packages/npm/packages_for_user_finder_spec.rb index 473c3463e7dd..81c9b1fb1d05 100644 --- a/spec/finders/packages/npm/packages_for_user_finder_spec.rb +++ b/spec/finders/packages/npm/packages_for_user_finder_spec.rb @@ -33,15 +33,32 @@ let(:project_or_group) { group } before_all do - project.add_reporter(user) + project.add_guest(user) end it_behaves_like 'searches for packages' it_behaves_like 'avoids N+1 database queries in the package registry' - context 'when an user is a reporter of both projects' do + context 'with a project inaccessible to user, but with a public registry' do + let_it_be(:project_with_public_package_registry) { create(:project, group: group) } + let_it_be(:other_package) do + create(:npm_package, project: project_with_public_package_registry, name: package.name) + end + + before do + project_with_public_package_registry + .project_feature + .update_attribute(:package_registry_access_level, ::ProjectFeature::PUBLIC) + end + + it 'excludes the packages from the public registry' do + is_expected.to contain_exactly(package) + end + end + + context 'when an user is a guest of both projects' do before_all do - project2.add_reporter(user) + project2.add_guest(user) end it { is_expected.to contain_exactly(package, package_with_diff_project) } @@ -56,6 +73,33 @@ it_behaves_like 'searches for packages' end end + + context 'when allow_guest_plus_roles_to_pull_packages is disabled' do + before_all do + stub_feature_flags(allow_guest_plus_roles_to_pull_packages: false) + project.add_reporter(user) + end + + it_behaves_like 'searches for packages' + + context 'when an user is a reporter of both projects' do + before_all do + project2.add_reporter(user) + end + + it { is_expected.to contain_exactly(package, package_with_diff_project) } + + context 'when the second project has the package registry disabled' do + before_all do + project.reload.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project2.reload.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC, + package_registry_access_level: 'disabled', packages_enabled: false) + end + + it_behaves_like 'searches for packages' + end + end + end end end end -- GitLab