Skip to content
Snippets Groups Projects
Verified Commit 64098ecf authored by charlie ablett's avatar charlie ablett :tools: Committed by GitLab
Browse files

Merge branch '467396-allow-anyone-to-search-for-nuget-packages-in-public-registries' into 'master'

Allow any GitLab user to search for nuget packages in public registries

See merge request !162177



Merged-by: charlie ablett's avatarcharlie ablett <cablett@gitlab.com>
Approved-by: default avatarSincheol (David) Kim <dkim@gitlab.com>
Approved-by: Hercules Merscher's avatarHercules Merscher <hmerscher@gitlab.com>
Approved-by: default avatarDouglas Barbosa Alexandre <dbalexandre@gmail.com>
Approved-by: charlie ablett's avatarcharlie ablett <cablett@gitlab.com>
Reviewed-by: default avatarSincheol (David) Kim <dkim@gitlab.com>
Reviewed-by: Hercules Merscher's avatarHercules Merscher <hmerscher@gitlab.com>
Reviewed-by: default avatarMoaz Khalifa <mkhalifa@gitlab.com>
Co-authored-by: default avatarmoaz-khalifa <mkhalifa@gitlab.com>
parents 47fb89ab 8fd743ea
No related branches found
No related tags found
2 merge requests!164749Enable parallel in test-on-omnibus,!162177Allow any GitLab user to search for nuget packages in public registries
Pipeline #1422837678 passed with warnings
Showing
with 195 additions and 102 deletions
......@@ -4280,7 +4280,6 @@ Layout/LineLength:
- 'spec/services/packages/maven/metadata/sync_service_spec.rb'
- 'spec/services/packages/npm/create_tag_service_spec.rb'
- 'spec/services/packages/nuget/create_dependency_service_spec.rb'
- 'spec/services/packages/nuget/search_service_spec.rb'
- 'spec/services/packages/nuget/update_package_from_metadata_service_spec.rb'
- 'spec/services/packages/rubygems/process_gem_service_spec.rb'
- 'spec/services/personal_access_tokens/create_service_spec.rb'
......
......@@ -1284,7 +1284,6 @@ RSpec/BeforeAllRoleAssignment:
- 'spec/services/notification_service_spec.rb'
- 'spec/services/packages/mark_packages_for_destruction_service_spec.rb'
- 'spec/services/packages/maven/metadata/sync_service_spec.rb'
- 'spec/services/packages/nuget/search_service_spec.rb'
- 'spec/services/packages/rubygems/dependency_resolver_service_spec.rb'
- 'spec/services/pages_domains/create_service_spec.rb'
- 'spec/services/post_receive_service_spec.rb'
......
......@@ -2540,7 +2540,6 @@ RSpec/ContextWording:
- 'spec/services/packages/helm/process_file_service_spec.rb'
- 'spec/services/packages/maven/metadata/create_versions_xml_service_spec.rb'
- 'spec/services/packages/maven/metadata/sync_service_spec.rb'
- 'spec/services/packages/nuget/search_service_spec.rb'
- 'spec/services/packages/nuget/update_package_from_metadata_service_spec.rb'
- 'spec/services/packages/rubygems/dependency_resolver_service_spec.rb'
- 'spec/services/packages/rubygems/process_gem_service_spec.rb'
......
......@@ -39,13 +39,7 @@ def packages_visible_to_user(user, within_group:, with_package_registry_enabled:
end
def packages_visible_to_user_including_public_registries(user, within_group:)
return ::Packages::Package.none unless within_group
return ::Packages::Package.none unless Ability.allowed?(user, :read_package_within_public_registries,
within_group.packages_policy_subject)
projects = projects_visible_to_reporters(user, within_group: within_group,
within_public_package_registry: !Ability.allowed?(user, :read_group, within_group))
projects = projects_visible_to_user_including_public_registries(user, within_group: within_group)
::Packages::Package.for_projects(projects.select(:id)).installable
end
......@@ -57,6 +51,16 @@ def projects_visible_to_user(user, within_group:)
projects_visible_to_reporters(user, within_group: within_group)
end
def projects_visible_to_user_including_public_registries(user, within_group:)
return ::Project.none unless within_group
return ::Project.none unless Ability.allowed?(user, :read_package_within_public_registries,
within_group.packages_policy_subject)
projects_visible_to_reporters(user, within_group: within_group,
within_public_package_registry: !Ability.allowed?(user, :read_group, within_group))
end
def projects_visible_to_reporters(user, within_group:, within_public_package_registry: false)
return user.accessible_projects if user.is_a?(DeployToken)
......
......@@ -102,15 +102,19 @@ def base_matching_package_names
def nuget_packages
Packages::Package.nuget
.displayable
.installable
.has_version
.without_nuget_temporary_name
end
def project_ids_cte
return unless use_project_ids_cte?
query = projects_visible_to_user(@current_user, within_group: @project_or_group)
query = if Feature.enabled?(:allow_anyone_to_pull_public_nuget_packages_on_group_level, @project_or_group)
projects_visible_to_user_including_public_registries(@current_user, within_group: @project_or_group)
else
projects_visible_to_user(@current_user, within_group: @project_or_group)
end
Gitlab::SQL::CTE.new(:project_ids, query.select(:id))
end
strong_memoize_attr :project_ids_cte
......
......@@ -90,21 +90,13 @@ module PrivateEndpoints
tags %w[nuget_packages]
end
get format: :json, urgency: :low do
search_options = {
include_prerelease_versions: params[:prerelease],
per_page: params[:take],
padding: params[:skip]
}
results = search_packages(params[:q], search_options)
track_package_event(
'search_package',
:nuget,
**snowplow_gitlab_standard_context.merge(category: 'API::NugetPackages')
)
present ::Packages::Nuget::SearchResultsPresenter.new(results),
present ::Packages::Nuget::SearchResultsPresenter.new(search_packages),
with: ::API::Entities::Nuget::SearchResults
end
end
......
......@@ -30,7 +30,13 @@ def package_finder(package_name, package_version = nil)
)
end
def search_packages(_search_term, search_options)
def search_packages
search_options = {
include_prerelease_versions: params[:prerelease],
per_page: params[:take],
padding: params[:skip]
}
::Packages::Nuget::SearchService
.new(current_user, project_or_group, params[:q], search_options)
.execute
......
......@@ -42,12 +42,8 @@ def symbol_server_enabled?
project_or_group_without_auth.package_settings.nuget_symbol_server_enabled
end
def require_authenticated!
unauthorized! unless current_user
end
def snowplow_gitlab_standard_context
{ namespace: find_authorized_group! }
{ namespace: project_or_group }
end
def snowplow_gitlab_standard_context_without_auth
......@@ -63,8 +59,7 @@ def required_permission
end
def allow_anyone_to_pull_public_packages?
options[:path].first.in?(%w[index *package_version]) &&
::Feature.enabled?(:allow_anyone_to_pull_public_nuget_packages_on_group_level, project_or_group_without_auth)
::Feature.enabled?(:allow_anyone_to_pull_public_nuget_packages_on_group_level, project_or_group_without_auth)
end
end
......@@ -86,7 +81,7 @@ def allow_anyone_to_pull_public_packages?
namespace '/nuget' do
after_validation do
# This API can't be accessed anonymously
require_authenticated!
authenticate!
end
include ::API::Concerns::Packages::Nuget::PrivateEndpoints
......
......@@ -267,7 +267,7 @@ def respond_to_missing?
end
end
describe '#projects_visible_to_user' do
context 'for projecs visibile to user' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
......@@ -276,8 +276,6 @@ def respond_to_missing?
let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
let_it_be_with_reload(:project2) { create(:project, namespace: subgroup) }
subject { finder.projects_visible_to_user(user, within_group: group) }
shared_examples 'returning both projects' do
it { is_expected.to contain_exactly(project1, project2) }
end
......@@ -286,70 +284,103 @@ def respond_to_missing?
it { is_expected.to eq [project1] }
end
shared_examples 'returning project2' do
it { is_expected.to eq [project2] }
end
shared_examples 'returning no project' do
it { is_expected.to be_empty }
end
context 'with a user' do
let_it_be(:user) { create(:user) }
describe '#projects_visible_to_user' do
subject { finder.projects_visible_to_user(user, within_group: group) }
where(:group_visibility, :subgroup_visibility, :project2_visibility, :user_role, :shared_example_name) do
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :maintainer | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :developer | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :guest | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :anonymous | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :maintainer | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :developer | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :guest | 'returning project1'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :anonymous | 'returning project1'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both projects'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both projects'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning project1'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning project1'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both projects'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both projects'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning no project'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning no project'
end
context 'with a user' do
where(:group_visibility, :subgroup_visibility, :project2_visibility, :user_role, :shared_example_name) do
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :maintainer | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :developer | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :guest | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :anonymous | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :maintainer | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :developer | 'returning both projects'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :guest | 'returning project1'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :anonymous | 'returning project1'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both projects'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both projects'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning project1'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning project1'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both projects'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both projects'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning no project'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning no project'
end
with_them do
before do
unless user_role == :anonymous
group.send("add_#{user_role}", user)
subgroup.send("add_#{user_role}", user)
project1.send("add_#{user_role}", user)
project2.send("add_#{user_role}", user)
with_them do
before do
unless user_role == :anonymous
group.send("add_#{user_role}", user)
subgroup.send("add_#{user_role}", user)
project1.send("add_#{user_role}", user)
project2.send("add_#{user_role}", user)
end
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
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))
it_behaves_like params[:shared_example_name]
end
end
it_behaves_like params[:shared_example_name]
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
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) }
describe '#projects_visible_to_user_including_public_registries' do
subject { finder.projects_visible_to_user_including_public_registries(user, within_group: group) }
before do
[subgroup, group, project1, project2].each do |entity|
entity.update!(visibility_level: Gitlab::VisibilityLevel.const_get(:PRIVATE, false))
end
project1.project_feature.update!(package_registry_access_level: project1_package_registry_access_level)
project2.project_feature.update!(package_registry_access_level: project2_package_registry_access_level)
end
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'
where(:project1_package_registry_access_level, :project2_package_registry_access_level, :shared_example_name) do
::ProjectFeature::PUBLIC | ::ProjectFeature::PUBLIC | 'returning both projects'
::ProjectFeature::PUBLIC | ::ProjectFeature::PRIVATE | 'returning project1'
::ProjectFeature::PUBLIC | ::ProjectFeature::DISABLED | 'returning project1'
::ProjectFeature::PUBLIC | ::ProjectFeature::ENABLED | 'returning project1'
::ProjectFeature::PRIVATE | ::ProjectFeature::PUBLIC | 'returning project2'
::ProjectFeature::DISABLED | ::ProjectFeature::PUBLIC | 'returning project2'
::ProjectFeature::ENABLED | ::ProjectFeature::PUBLIC | 'returning project2'
::ProjectFeature::PRIVATE | ::ProjectFeature::PRIVATE | 'returning no project'
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
......
......@@ -49,7 +49,9 @@ def snowplow_context(user_role: :developer)
guest_requests_status: :not_found
}
it_behaves_like 'allows anyone to pull public nuget packages on group level'
it_behaves_like 'allows anyone to pull public nuget packages on group level' do
let(:json_schema) { 'public_api/v4/packages/nuget/packages_metadata' }
end
end
describe 'GET /api/v4/groups/:id/-/packages/nuget/metadata/*package_name/*package_version' do
......@@ -64,18 +66,26 @@ def snowplow_context(user_role: :developer)
guest_requests_status: :not_found
}
it_behaves_like 'allows anyone to pull public nuget packages on group level'
it_behaves_like 'allows anyone to pull public nuget packages on group level' do
let(:json_schema) { 'public_api/v4/packages/nuget/package_metadata' }
end
end
describe 'GET /api/v4/groups/:id/-/packages/nuget/query' do
let(:url) { "/groups/#{target.id}/-/packages/nuget/query?#{query_parameters.to_query}" }
it_behaves_like 'handling nuget search requests',
example_names_with_status: {
anonymous_requests_example_name: 'rejects nuget packages access',
anonymous_requests_status: :unauthorized,
guest_requests_example_name: 'process empty nuget search request',
guest_requests_status: :success
} do
let(:url) { "/groups/#{target.id}/-/packages/nuget/query?#{query_parameters.to_query}" }
}
it_behaves_like 'allows anyone to pull public nuget packages on group level' do
let(:query_parameters) { { q: 'uMmy', take: 26, skip: 0, prerelease: true } }
let(:json_schema) { 'public_api/v4/packages/nuget/search' }
let(:not_found_response) { :ok }
end
end
end
......
......@@ -12,7 +12,7 @@
let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
let_it_be(:package_d) { create(:nuget_package, project: project, name: 'FooBarD') }
let_it_be(:other_package_a) { create(:nuget_package, name: 'DummyPackageA') }
let_it_be(:other_package_a) { create(:nuget_package, name: 'DummyPackageB') }
let_it_be(:other_package_b) { create(:nuget_package, name: 'DummyPackageB') }
let(:search_term) { 'ummy' }
let(:per_page) { 5 }
......@@ -80,11 +80,11 @@
it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
end
context 'with non-displayable packages' do
context 'with non-installable packages' do
let(:search_term) { '' }
before do
package_a.update_column(:status, 1)
package_a.update_column(:status, 2)
end
it { expect_search_results 3, packages_b, packages_c, package_d }
......@@ -103,19 +103,23 @@
end
context 'with pre release packages' do
let_it_be(:package_e) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1-alpha') }
let_it_be(:package_e) do
create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1-alpha')
end
context 'including them' do
context 'when including them' do
it { expect_search_results 4, package_a, packages_b, packages_c, package_e }
end
context 'excluding them' do
context 'when excluding them' do
let(:include_prerelease_versions) { false }
it { expect_search_results 3, package_a, packages_b, packages_c }
context 'when mixed with release versions' do
let_it_be(:package_e_release) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1') }
let_it_be(:package_e_release) do
create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1')
end
it { expect_search_results 4, package_a, packages_b, packages_c, package_e_release }
end
......@@ -126,7 +130,7 @@
context 'with project' do
let(:target) { project }
before do
before_all do
project.add_developer(user)
end
......@@ -136,7 +140,7 @@
context 'with subgroup' do
let(:target) { subgroup }
before do
before_all do
subgroup.add_developer(user)
end
......@@ -146,11 +150,38 @@
context 'with group' do
let(:target) { group }
before do
group.add_developer(user)
context 'when user is a group member' do
before_all do
group.add_developer(user)
end
it_behaves_like 'handling all the conditions'
end
it_behaves_like 'handling all the conditions'
context 'when user is not a group member' do
context 'with public registry in private group' do
before_all do
[subgroup, group, project].each do |entity|
entity.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(:PRIVATE, false))
end
project.project_feature.update!(package_registry_access_level: ::ProjectFeature::PUBLIC)
end
before do
stub_application_setting(package_registry_allow_anyone_to_pull_option: true)
end
it_behaves_like 'handling all the conditions'
context 'when feaure flag is disabled' do
before do
stub_feature_flags(allow_anyone_to_pull_public_nuget_packages_on_group_level: false)
end
it { expect_search_results 0, [] }
end
end
end
end
def expect_search_results(total_count, *results)
......
......@@ -7978,7 +7978,6 @@
- './spec/services/packages/npm/create_tag_service_spec.rb'
- './spec/services/packages/nuget/create_dependency_service_spec.rb'
- './spec/services/packages/nuget/metadata_extraction_service_spec.rb'
- './spec/services/packages/nuget/search_service_spec.rb'
- './spec/services/packages/nuget/sync_metadatum_service_spec.rb'
- './spec/services/packages/nuget/update_package_from_metadata_service_spec.rb'
- './spec/services/packages/pypi/create_package_service_spec.rb'
......
......@@ -490,17 +490,31 @@
let_it_be(:package_name) { 'dummy.package' }
let_it_be(:package) { create(:nuget_package, project: project, name: package_name) }
let(:not_found_response) { :not_found }
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
before do
shared_examples 'successfull reponse' do
it 'returns a successfull response' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to match_schema(json_schema)
end
end
before_all do
[subgroup, group, project].each do |entity|
entity.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(:PRIVATE, false))
end
project.project_feature.update!(package_registry_access_level: ::ProjectFeature::PUBLIC)
end
before do
stub_application_setting(package_registry_allow_anyone_to_pull_option: true)
end
it_behaves_like 'returning response status', :ok
it_behaves_like 'successfull reponse'
context 'when target package is in a private registry and group has another public registry' do
let(:other_project) { create(:project, group: target, visibility_level: target.visibility_level) }
......@@ -510,14 +524,24 @@
other_project.project_feature.update!(package_registry_access_level: ::ProjectFeature::PUBLIC)
end
it_behaves_like 'returning response status', :not_found
it 'returns no packages' do
subject
expect(response).to have_gitlab_http_status(not_found_response)
if not_found_response == :ok
expect(json_response).to match_schema(json_schema)
expect(json_response['totalHits']).to eq(0)
expect(json_response['data']).to be_empty
end
end
context 'when package is in the project with public registry' do
before do
package.update!(project: other_project)
end
it_behaves_like 'returning response status', :ok
it_behaves_like 'successfull reponse'
end
end
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment