Skip to content
Snippets Groups Projects
Verified Commit 8fd743ea authored by Moaz Khalifa's avatar Moaz Khalifa Committed by GitLab
Browse files

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

parent 9dce944b
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
Showing
with 195 additions and 102 deletions
......@@ -4282,7 +4282,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'
......
......@@ -1285,7 +1285,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'
......
......@@ -2541,7 +2541,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)
......
......@@ -7980,7 +7980,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