Commit 3cb09d05 authored by Steve Abrams's avatar Steve Abrams Committed by David Fernandez

NuGet symbol package support

parent 09ad4f3b
......@@ -2,6 +2,7 @@
module Packages
module Nuget
TEMPORARY_PACKAGE_NAME = 'NuGet.Temporary.Package'
TEMPORARY_SYMBOL_PACKAGE_NAME = 'NuGet.Temporary.SymbolPackage'
def self.table_name_prefix
'packages_nuget_'
......
......@@ -8,6 +8,7 @@ module Packages
SERVICE_VERSIONS = {
download: %w[PackageBaseAddress/3.0.0],
search: %w[SearchQueryService SearchQueryService/3.0.0-beta SearchQueryService/3.0.0-rc],
symbol: %w[SymbolPackagePublish/4.9.0],
publish: %w[PackagePublish/2.0.0],
metadata: %w[RegistrationsBaseUrl RegistrationsBaseUrl/3.0.0-beta RegistrationsBaseUrl/3.0.0-rc]
}.freeze
......@@ -15,13 +16,14 @@ module Packages
SERVICE_COMMENTS = {
download: 'Get package content (.nupkg).',
search: 'Filter and search for packages by keyword.',
symbol: 'Push symbol packages.',
publish: 'Push and delete (or unlist) packages.',
metadata: 'Get package metadata.'
}.freeze
VERSION = '3.0.0'
PROJECT_LEVEL_SERVICES = %i[download publish].freeze
PROJECT_LEVEL_SERVICES = %i[download publish symbol].freeze
GROUP_LEVEL_SERVICES = %i[search metadata].freeze
def initialize(project_or_group)
......@@ -63,6 +65,8 @@ module Packages
download_service_url
when :search
search_service_url
when :symbol
symbol_service_url
when :metadata
metadata_service_url
when :publish
......@@ -124,6 +128,10 @@ module Packages
def publish_service_url
api_v4_projects_packages_nuget_path(id: @project_or_group.id)
end
def symbol_service_url
api_v4_projects_packages_nuget_symbolpackage_path(id: @project_or_group.id)
end
end
end
end
......@@ -18,6 +18,7 @@ module Packages
XPATH_DEPENDENCIES = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:dependency'
XPATH_DEPENDENCY_GROUPS = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:group'
XPATH_TAGS = '//xmlns:package/xmlns:metadata/xmlns:tags'
XPATH_PACKAGE_TYPES = '//xmlns:package/xmlns:metadata/xmlns:packageTypes/xmlns:packageType'
MAX_FILE_SIZE = 4.megabytes.freeze
......@@ -57,6 +58,7 @@ module Packages
.tap do |metadata|
metadata[:package_dependencies] = extract_dependencies(doc)
metadata[:package_tags] = extract_tags(doc)
metadata[:package_types] = extract_package_types(doc)
end
end
......@@ -85,6 +87,10 @@ module Packages
}.compact
end
def extract_package_types(doc)
doc.xpath(XPATH_PACKAGE_TYPES).map { |node| node.attr('name') }.uniq
end
def extract_tags(doc)
tags = doc.xpath(XPATH_TAGS).text
......
......@@ -8,6 +8,7 @@ module Packages
# used by ExclusiveLeaseGuard
DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze
SYMBOL_PACKAGE_IDENTIFIER = 'SymbolsPackage'
InvalidMetadataError = Class.new(StandardError)
......@@ -20,7 +21,13 @@ module Packages
try_obtain_lease do
@package_file.transaction do
package = existing_package ? link_to_existing_package : update_linked_package
if existing_package
package = link_to_existing_package
elsif symbol_package?
raise InvalidMetadataError, 'symbol package is invalid, matching package does not exist'
else
package = update_linked_package
end
update_package(package)
......@@ -39,6 +46,8 @@ module Packages
private
def update_package(package)
return if symbol_package?
::Packages::Nuget::SyncMetadatumService
.new(package, metadata.slice(:project_url, :license_url, :icon_url))
.execute
......@@ -103,6 +112,14 @@ module Packages
metadata.fetch(:package_tags, [])
end
def package_types
metadata.fetch(:package_types, [])
end
def symbol_package?
package_types.include?(SYMBOL_PACKAGE_IDENTIFIER)
end
def metadata
strong_memoize(:metadata) do
::Packages::Nuget::MetadataExtractionService.new(@package_file.id).execute
......@@ -110,7 +127,7 @@ module Packages
end
def package_filename
"#{package_name.downcase}.#{package_version.downcase}.nupkg"
"#{package_name.downcase}.#{package_version.downcase}.#{symbol_package? ? 'snupkg' : 'nupkg'}"
end
# used by ExclusiveLeaseGuard
......
......@@ -8,6 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20050) in GitLab Premium 12.8.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/221259) to GitLab Free in 13.3.
> - Symbol package support [added](https://gitlab.com/gitlab-org/gitlab/-/issues/262081) in GitLab 14.1.
Publish NuGet packages in your project's Package Registry. Then, install the
packages whenever you need to use them as a dependency.
......@@ -394,6 +395,24 @@ dotnet add package <package_id> \
- `<package_id>` is the package ID.
- `<package_version>` is the package version. Optional.
## Symbol packages
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/262081) in GitLab 14.1.
If you push a `.nupkg`, symbol package files in the `.snupkg` format are uploaded automatically. You
can also push them manually:
```shell
nuget push My.Package.snupkg -Source <source_name>
```
Consuming symbol packages is not yet guaranteed using clients such as Visual Studio or
dotnet-symbol. The `.snupkg` files are available for download through the UI or the
[API](../../../api/packages/nuget.md#download-a-package-file).
Follow the [NuGet symbol package issue](https://gitlab.com/gitlab-org/gitlab/-/issues/262081)
for further updates.
## Supported CLI commands
The GitLab NuGet repository supports the following commands for the NuGet CLI (`nuget`) and the .NET
......
......@@ -16,6 +16,7 @@ module API
feature_category :package_registry
PACKAGE_FILENAME = 'package.nupkg'
SYMBOL_PACKAGE_FILENAME = 'package.snupkg'
default_format :json
......@@ -33,6 +34,10 @@ module API
end
helpers do
params :file_params do
requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
end
def project_or_group
authorized_user_project
end
......@@ -40,6 +45,49 @@ module API
def snowplow_gitlab_standard_context
{ project: authorized_user_project, namespace: authorized_user_project.namespace }
end
def authorize_nuget_upload
authorize_workhorse!(
subject: project_or_group,
has_length: false,
maximum_size: project_or_group.actual_limits.nuget_max_file_size
)
end
def temp_file_name(symbol_package)
return ::Packages::Nuget::TEMPORARY_SYMBOL_PACKAGE_NAME if symbol_package
::Packages::Nuget::TEMPORARY_PACKAGE_NAME
end
def file_name(symbol_package)
return SYMBOL_PACKAGE_FILENAME if symbol_package
PACKAGE_FILENAME
end
def upload_nuget_package_file(symbol_package: false)
authorize_upload!(project_or_group)
bad_request!('File is too large') if project_or_group.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
file_params = params.merge(
file: params[:package],
file_name: file_name(symbol_package)
)
package = ::Packages::CreateTemporaryPackageService.new(
project_or_group, current_user, declared_params.merge(build: current_authenticated_job)
).execute(:nuget, name: temp_file_name(symbol_package))
package_file = ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job))
.execute
yield(package) if block_given?
::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker
created!
end
end
params do
......@@ -55,40 +103,45 @@ module API
end
params do
requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
use :file_params
end
put do
authorize_upload!(project_or_group)
bad_request!('File is too large') if project_or_group.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
file_params = params.merge(
file: params[:package],
file_name: PACKAGE_FILENAME
)
package = ::Packages::CreateTemporaryPackageService.new(
project_or_group, current_user, declared_params.merge(build: current_authenticated_job)
).execute(:nuget, name: ::Packages::Nuget::TEMPORARY_PACKAGE_NAME)
package_file = ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job))
.execute
upload_nuget_package_file do |package|
track_package_event(
'push_package',
:nuget,
category: 'API::NugetPackages',
user: current_user,
project: package.project,
namespace: package.project.namespace
)
end
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id })
track_package_event('push_package', :nuget, category: 'API::NugetPackages', user: current_user, project: package.project, namespace: package.project.namespace)
forbidden!
end
put 'authorize' do
authorize_nuget_upload
end
::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker
# https://docs.microsoft.com/en-us/nuget/api/symbol-package-publish-resource
desc 'The NuGet Symbol Package Publish endpoint' do
detail 'This feature was introduced in GitLab 14.1'
end
created!
params do
use :file_params
end
put 'symbolpackage' do
upload_nuget_package_file(symbol_package: true)
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id })
forbidden!
end
put 'authorize' do
authorize_workhorse!(
subject: project_or_group,
has_length: false,
maximum_size: project_or_group.actual_limits.nuget_max_file_size
)
put 'symbolpackage/authorize' do
authorize_nuget_upload
end
# https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource
......@@ -115,14 +168,22 @@ module API
requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX
requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX
end
get '*package_version/*package_filename', format: :nupkg do
get '*package_version/*package_filename', format: [:nupkg, :snupkg] do
filename = "#{params[:package_filename]}.#{params[:format]}"
package_file = ::Packages::PackageFileFinder.new(find_package(params[:package_name], params[:package_version]), filename, with_file_name_like: true)
.execute
not_found!('Package') unless package_file
track_package_event('pull_package', :nuget, category: 'API::NugetPackages', project: package_file.project, namespace: package_file.project.namespace)
if params[:format] == 'nupkg'
track_package_event(
'pull_package',
:nuget,
category: 'API::NugetPackages',
project: package_file.project,
namespace: package_file.project.namespace
)
end
# nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false
present_carrierwave_file!(package_file.file, supports_direct_download: false)
......
......@@ -162,6 +162,12 @@ FactoryBot.define do
pkg.nuget_metadatum = build(:nuget_metadatum)
end
end
trait(:with_symbol_package) do
after :create do |package|
create :package_file, :snupkg, package: package, file_name: "#{package.name}.#{package.version}.snupkg"
end
end
end
factory :pypi_package do
......
......@@ -271,6 +271,14 @@ FactoryBot.define do
size { 300.kilobytes }
end
trait(:snupkg) do
package
file_fixture { 'spec/fixtures/packages/nuget/package.snupkg' }
file_name { 'package.snupkg' }
file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' }
size { 300.kilobytes }
end
trait(:gem) do
package
file_fixture { 'spec/fixtures/packages/rubygems/package-0.0.1.gem' }
......
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Test.Package</id>
<version>3.5.2</version>
<authors>Test Author</authors>
<owners>Test Owner</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Package Description</description>
<packageTypes>
<packageType name="SymbolsPackage" />
</packageTypes>
</metadata>
</package>
......@@ -27,7 +27,7 @@ RSpec.describe ::Packages::Nuget::ServiceIndexPresenter do
describe '#resources' do
subject { presenter.resources }
shared_examples 'returning valid resources' do |resources_count: 8, include_publish_service: true|
shared_examples 'returning valid resources' do |resources_count: 9, include_publish_service: true|
it 'has valid resources' do
expect(subject.size).to eq resources_count
subject.each do |resource|
......@@ -38,10 +38,15 @@ RSpec.describe ::Packages::Nuget::ServiceIndexPresenter do
end
end
it "does #{'not ' unless include_publish_service}return the publish resource" do
it "does #{'not ' unless include_publish_service}return the publish resource", :aggregate_failures do
services_types = subject.map { |res| res[:@type] }
described_class::SERVICE_VERSIONS[:publish].each do |publish_service_version|
publish_service_versions = [
described_class::SERVICE_VERSIONS[:publish],
described_class::SERVICE_VERSIONS[:symbol]
].flatten
publish_service_versions.each do |publish_service_version|
if include_publish_service
expect(services_types).to include(publish_service_version)
else
......@@ -54,7 +59,7 @@ RSpec.describe ::Packages::Nuget::ServiceIndexPresenter do
context 'for a group' do
let(:target) { group }
# at the group level we don't have the publish and download service
# at the group level we don't have the publish, symbol, and download service
it_behaves_like 'returning valid resources', resources_count: 6, include_publish_service: false
end
......
......@@ -92,9 +92,10 @@ RSpec.describe API::NugetProjectPackages do
describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
let_it_be(:package_name) { 'Dummy.Package' }
let_it_be(:package) { create(:nuget_package, project: project, name: package_name) }
let_it_be(:package) { create(:nuget_package, :with_symbol_package, project: project, name: package_name) }
let(:url) { "/projects/#{target.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" }
let(:format) { 'nupkg' }
let(:url) { "/projects/#{target.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.#{format}" }
subject { get api(url) }
......@@ -154,50 +155,7 @@ RSpec.describe API::NugetProjectPackages do
subject { put api(url), headers: headers }
context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_headers) }
before do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'deploy token for package uploads'
it_behaves_like 'job token for package uploads', authorize_endpoint: true do
let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
end
it_behaves_like 'rejects nuget access with unknown target id'
it_behaves_like 'rejects nuget access with invalid target id'
it_behaves_like 'nuget authorize upload endpoint'
end
describe 'PUT /api/v4/projects/:id/packages/nuget' do
......@@ -221,63 +179,42 @@ RSpec.describe API::NugetProjectPackages do
)
end
context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_headers) }
let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace } }
before do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'nuget upload endpoint'
end
it_behaves_like 'deploy token for package uploads'
describe 'PUT /api/v4/projects/:id/packages/nuget/symbolpackage/authorize' do
include_context 'workhorse headers'
it_behaves_like 'job token for package uploads' do
let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
end
let(:url) { "/projects/#{target.id}/packages/nuget/symbolpackage/authorize" }
let(:headers) { {} }
it_behaves_like 'rejects nuget access with unknown target id'
subject { put api(url), headers: headers }
it_behaves_like 'rejects nuget access with invalid target id'
it_behaves_like 'nuget authorize upload endpoint'
end
context 'file size above maximum limit' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
describe 'PUT /api/v4/projects/:id/packages/nuget/symbolpackage' do
include_context 'workhorse headers'
before do
allow_next_instance_of(UploadedFile) do |uploaded_file|
allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1)
end
end
let_it_be(:file_name) { 'package.snupkg' }
let(:url) { "/projects/#{target.id}/packages/nuget/symbolpackage" }
let(:headers) { {} }
let(:params) { { package: temp_file(file_name) } }
let(:file_key) { :package }
let(:send_rewritten_field) { true }
it_behaves_like 'returning response status', :bad_request
subject do
workhorse_finalize(
api(url),
method: :put,
file_key: file_key,
params: params,
headers: headers,
send_rewritten_field: send_rewritten_field
)
end
it_behaves_like 'nuget upload endpoint', symbol_package: true
end
def update_visibility_to(visibility)
......
......@@ -21,7 +21,8 @@ RSpec.describe Packages::Nuget::MetadataExtractionService do
version: '12.0.3'
}
],
package_tags: []
package_tags: [],
package_types: []
}
it { is_expected.to eq(expected_metadata) }
......@@ -47,6 +48,16 @@ RSpec.describe Packages::Nuget::MetadataExtractionService do
end
end
context 'with package types' do
let(:nuspec_filepath) { 'packages/nuget/with_package_types.nuspec' }
it { is_expected.to have_key(:package_types) }
it 'extracts package types' do
expect(subject[:package_types]).to include('SymbolsPackage')
end
end
context 'with a nuspec file with metadata' do
let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
......
......@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let(:package) { create(:nuget_package, :processing) }
let(:package) { create(:nuget_package, :processing, :with_symbol_package) }
let(:package_file) { package.package_files.first }
let(:service) { described_class.new(package_file) }
let(:package_name) { 'DummyProject.DummyPackage' }
......@@ -201,6 +201,41 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
it_behaves_like 'raising an', ::Packages::Nuget::MetadataExtractionService::ExtractionError
end
context 'with a symbol package' do
let(:package_file) { package.package_files.last }
let(:package_file_name) { 'dummyproject.dummypackage.1.0.0.snupkg' }
context 'with no existing package' do
let(:package_id) { package.id }
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
context 'with existing package' do
let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) }
let(:package_id) { existing_package.id }
it 'link existing package and updates package file', :aggregate_failures do
expect(service).to receive(:try_obtain_lease).and_call_original
expect(::Packages::Nuget::SyncMetadatumService).not_to receive(:new)
expect(::Packages::UpdateTagsService).not_to receive(:new)
expect { subject }
.to change { ::Packages::Package.count }.by(-1)
.and change { Packages::Dependency.count }.by(0)
.and change { Packages::DependencyLink.count }.by(0)
.and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(0)
.and change { ::Packages::Nuget::Metadatum.count }.by(0)
expect(package_file.reload.file_name).to eq(package_file_name)
expect(package_file.package).to eq(existing_package)
end
it_behaves_like 'taking the lease'
it_behaves_like 'not updating the package if the lease is taken'
end
end
context 'with an invalid package name' do
invalid_names = [
'',
......
......@@ -136,8 +136,8 @@ RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, sta
end
end
RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = true|
RSpec.shared_examples 'creates nuget package files' do
RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = true, symbol_package = false|
shared_examples 'creates nuget package files' do
it 'creates package files' do
expect(::Packages::Nuget::ExtractionWorker).to receive(:perform_async).once
expect { subject }
......@@ -146,7 +146,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
expect(response).to have_gitlab_http_status(status)
package_file = target.packages.last.package_files.reload.last
expect(package_file.file_name).to eq('package.nupkg')
expect(package_file.file_name).to eq(file_name)
end
end
......@@ -169,7 +169,10 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
context 'with correct params' do
it_behaves_like 'package workhorse uploads'
it_behaves_like 'creates nuget package files'
it_behaves_like 'a package tracking event', 'API::NugetPackages', 'push_package'
unless symbol_package
it_behaves_like 'a package tracking event', 'API::NugetPackages', 'push_package'
end
end
end
......@@ -300,6 +303,16 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
end
context 'with symbol package' do
let(:format) { 'snupkg' }
it 'returns a valid package archive' do
subject
expect(response.media_type).to eq('application/octet-stream')
end
end
context 'with lower case package name' do
let_it_be(:package_name) { 'dummy.package' }
......@@ -407,3 +420,114 @@ RSpec.shared_examples 'rejects nuget access with unknown target id' do
end
end
end
RSpec.shared_examples 'nuget authorize upload endpoint' do
using RSpec::Parameterized::TableSyntax
context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_headers) }
before do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'deploy token for package uploads'
it_behaves_like 'job token for package uploads', authorize_endpoint: true do
let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
end
it_behaves_like 'rejects nuget access with unknown target id'
it_behaves_like 'rejects nuget access with invalid target id'
end
RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
using RSpec::Parameterized::TableSyntax
context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_headers) }
let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace } }
before do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member], symbol_package
end
end
it_behaves_like 'deploy token for package uploads'
it_behaves_like 'job token for package uploads' do
let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
end
it_behaves_like 'rejects nuget access with unknown target id'
it_behaves_like 'rejects nuget access with invalid target id'
context 'file size above maximum limit' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
before do
allow_next_instance_of(UploadedFile) do |uploaded_file|
allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1)
end
end
it_behaves_like 'returning response status', :bad_request
end
end
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