Commit 27668bbb authored by David Fernandez's avatar David Fernandez Committed by Sean McGivern

Add the NuGet group level API

Re using the shareable nuget endpoint
Add related specs
parent 7d2741f9
# frozen_string_literal: true
module Packages
module FinderHelper
extend ActiveSupport::Concern
private
def packages_visible_to_user(user, within_group:)
return ::Packages::Package.none unless within_group
return ::Packages::Package.none unless Ability.allowed?(user, :read_package, within_group)
projects = projects_visible_to_reporters(user, within_group.self_and_descendants.select(:id))
::Packages::Package.for_projects(projects.select(:id))
end
def projects_visible_to_user(user, within_group:)
return ::Project.none unless within_group
return ::Project.none unless Ability.allowed?(user, :read_package, within_group)
projects_visible_to_reporters(user, within_group.self_and_descendants.select(:id))
end
def projects_visible_to_reporters(user, namespace_ids)
::Project.in_namespace(namespace_ids)
.public_or_visible_to_user(user, ::Gitlab::Access::REPORTER)
end
end
end
# frozen_string_literal: true
module Packages
module Nuget
class PackageFinder
include ::Packages::FinderHelper
MAX_PACKAGES_COUNT = 50
def initialize(project, package_name:, package_version: nil, limit: MAX_PACKAGES_COUNT)
@project = project
def initialize(current_user, project_or_group, package_name:, package_version: nil, limit: MAX_PACKAGES_COUNT)
@current_user = current_user
@project_or_group = project_or_group
@package_name = package_name
@package_version = package_version
@limit = limit
......@@ -17,15 +21,32 @@ module Packages
private
def base
if project?
@project_or_group.packages
elsif group?
packages_visible_to_user(@current_user, within_group: @project_or_group)
else
::Packages::Package.none
end
end
def packages
result = @project.packages
.nuget
.has_version
.processed
.with_name_like(@package_name)
result = base.nuget
.has_version
.processed
.with_name_like(@package_name)
result = result.with_version(@package_version) if @package_version.present?
result
end
def project?
@project_or_group.is_a?(::Project)
end
def group?
@project_or_group.is_a?(::Group)
end
end
end
end
......@@ -21,8 +21,11 @@ module Packages
VERSION = '3.0.0'.freeze
def initialize(project)
@project = project
PROJECT_LEVEL_SERVICES = %i[download publish].freeze
GROUP_LEVEL_SERVICES = %i[search metadata].freeze
def initialize(project_or_group)
@project_or_group = project_or_group
end
def version
......@@ -30,16 +33,21 @@ module Packages
end
def resources
[
build_service(:download),
build_service(:search),
build_service(:publish),
build_service(:metadata)
].flatten
available_services.map { |service| build_service(service) }
.flatten
end
private
def available_services
case scope
when :group
GROUP_LEVEL_SERVICES
when :project
(GROUP_LEVEL_SERVICES + PROJECT_LEVEL_SERVICES).flatten
end
end
def build_service(service_type)
url = build_service_url(service_type)
comment = SERVICE_COMMENTS[service_type]
......@@ -50,36 +58,72 @@ module Packages
end
def build_service_url(service_type)
base_path = api_v4_projects_packages_nuget_path(id: @project.id)
full_path = case service_type
when :download
api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
{
id: @project.id,
package_name: nil,
package_version: nil,
package_filename: nil
},
true
)
download_service_url
when :search
"#{base_path}/query"
search_service_url
when :metadata
api_v4_projects_packages_nuget_metadata_package_name_package_version_path(
{
id: @project.id,
package_name: nil,
package_version: nil
},
true
)
metadata_service_url
when :publish
base_path
publish_service_url
end
expose_url(full_path)
end
def scope
return :project if @project_or_group.is_a?(::Project)
return :group if @project_or_group.is_a?(::Group)
end
def download_service_url
params = {
id: @project_or_group.id,
package_name: nil,
package_version: nil,
package_filename: nil
}
api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
params,
true
)
end
def metadata_service_url
params = {
id: @project_or_group.id,
package_name: nil,
package_version: nil
}
case scope
when :group
api_v4_groups_packages_nuget_metadata_package_name_package_version_path(
params,
true
)
when :project
api_v4_projects_packages_nuget_metadata_package_name_package_version_path(
params,
true
)
end
end
def search_service_url
case scope
when :group
api_v4_groups_packages_nuget_query_path(id: @project_or_group.id)
when :project
api_v4_projects_packages_nuget_query_path(id: @project_or_group.id)
end
end
def publish_service_url
api_v4_projects_packages_nuget_path(id: @project_or_group.id)
end
end
end
end
......@@ -3,6 +3,7 @@
module Packages
module Nuget
class SearchService < BaseService
include ::Packages::FinderHelper
include Gitlab::Utils::StrongMemoize
include ActiveRecord::ConnectionAdapters::Quoting
......@@ -16,8 +17,11 @@ module Packages
padding: 0
}.freeze
def initialize(project, search_term, options = {})
@project = project
RESULT = Struct.new(:results, :total_count, keyword_init: true).freeze
def initialize(current_user, project_or_group, search_term, options = {})
@current_user = current_user
@project_or_group = project_or_group
@search_term = search_term
@options = DEFAULT_OPTIONS.merge(options)
......@@ -26,8 +30,8 @@ module Packages
end
def execute
OpenStruct.new(
total_count: package_names.total_count,
RESULT.new(
total_count: non_paginated_matching_package_names.count,
results: search_packages
)
end
......@@ -39,52 +43,104 @@ module Packages
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24182#technical-notes
# and https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
subquery_name = :partition_subquery
arel_table = Arel::Table.new(:partition_subquery)
arel_table = Arel::Table.new(subquery_name)
column_names = Packages::Package.column_names.map do |cn|
"#{subquery_name}.#{quote_column_name(cn)}"
end
# rubocop: disable CodeReuse/ActiveRecord
pkgs = Packages::Package.select(column_names.join(','))
.from(package_names_partition, subquery_name)
.where(arel_table[:row_number].lteq(MAX_VERSIONS_PER_PACKAGE))
pkgs = Packages::Package
pkgs = pkgs.with(project_ids_cte.to_arel) if use_project_ids_cte?
pkgs = pkgs.select(column_names.join(','))
.from(package_names_partition, subquery_name)
.where(arel_table[:row_number].lteq(MAX_VERSIONS_PER_PACKAGE))
return pkgs if include_prerelease_versions?
# we can't use pkgs.without_version_like since we have a custom from
pkgs.where.not(arel_table[:version].matches(PRE_RELEASE_VERSION_MATCHING_TERM))
# rubocop: enable CodeReuse/ActiveRecord
end
def package_names_partition
# rubocop: disable CodeReuse/ActiveRecord
table_name = quote_table_name(Packages::Package.table_name)
name_column = "#{table_name}.#{quote_column_name('name')}"
created_at_column = "#{table_name}.#{quote_column_name('created_at')}"
select_sql = "ROW_NUMBER() OVER (PARTITION BY #{name_column} ORDER BY #{created_at_column} DESC) AS row_number, #{table_name}.*"
@project.packages
.select(select_sql)
.nuget
.has_version
.without_nuget_temporary_name
.with_name(package_names)
nuget_packages.select(select_sql)
.with_name(paginated_matching_package_names)
.where(project_id: project_ids)
# rubocop: enable CodeReuse/ActiveRecord
end
def package_names
strong_memoize(:package_names) do
pkgs = @project.packages
.nuget
.has_version
.without_nuget_temporary_name
.order_name
.select_distinct_name
def paginated_matching_package_names
pkgs = base_matching_package_names
pkgs.page(0) # we're using a padding
.per(per_page)
.padding(padding)
end
def non_paginated_matching_package_names
# rubocop: disable CodeReuse/ActiveRecord
pkgs = base_matching_package_names
pkgs = pkgs.with(project_ids_cte.to_arel) if use_project_ids_cte?
pkgs
# rubocop: enable CodeReuse/ActiveRecord
end
def base_matching_package_names
strong_memoize(:base_matching_package_names) do
# rubocop: disable CodeReuse/ActiveRecord
pkgs = nuget_packages.order_name
.select_distinct_name
.where(project_id: project_ids)
pkgs = pkgs.without_version_like(PRE_RELEASE_VERSION_MATCHING_TERM) unless include_prerelease_versions?
pkgs = pkgs.search_by_name(@search_term) if @search_term.present?
pkgs.page(0) # we're using a padding
.per(per_page)
.padding(padding)
pkgs
# rubocop: enable CodeReuse/ActiveRecord
end
end
def nuget_packages
Packages::Package.nuget
.has_version
.without_nuget_temporary_name
end
def project_ids_cte
return unless use_project_ids_cte?
strong_memoize(:project_ids_cte) do
query = projects_visible_to_user(@current_user, within_group: @project_or_group)
Gitlab::SQL::CTE.new(:project_ids, query.select(:id))
end
end
def project_ids
return @project_or_group.id if project?
if use_project_ids_cte?
# rubocop: disable CodeReuse/ActiveRecord
Project.select(:id)
.from(project_ids_cte.table)
# rubocop: enable CodeReuse/ActiveRecord
end
end
def use_project_ids_cte?
group?
end
def project?
@project_or_group.is_a?(::Project)
end
def group?
@project_or_group.is_a?(::Group)
end
def include_prerelease_versions?
@options[:include_prerelease_versions]
end
......
---
title: Add the NuGet group level API
merge_request: 48356
author:
type: added
......@@ -60,6 +60,21 @@ NuGet CLI.
mono nuget.exe
```
## Use the GitLab endpoint for NuGet Packages
To use the GitLab endpoint for NuGet Packages, choose an option:
- **Project-level**: Use when you have few NuGet packages and they are not in
the same GitLab group.
- **Group-level**: Use when you have many NuGet packages in different within the
same GitLab group.
Some features such as [publishing](#publish-a-nuget-package) a package are only available on the project-level endpoint.
WARNING:
Because of how NuGet handles credentials, the Package Registry rejects anonymous requests on the group-level endpoint.
To work around this limitation, set up [authentication](#add-the-package-registry-as-a-source-for-nuget-packages).
## Add the Package Registry as a source for NuGet packages
To publish and install packages to the Package Registry, you must add the
......@@ -75,7 +90,9 @@ Prerequisites:
with the scope set to `read_package_registry`, `write_package_registry`, or
both.
- A name for your source.
- Your project ID, which is found on your project's home page.
- Depending on the [endpoint level](#use-the-gitlab-endpoint-for-nuget-packages) you use, either:
- Your project ID, which is found on your project's home page.
- Your group ID, which is found on your group's home page.
You can now add a new source to NuGet with:
......@@ -85,7 +102,9 @@ You can now add a new source to NuGet with:
### Add a source with the NuGet CLI
To add the Package Registry as a source with `nuget`:
#### Project-level endpoint
To use the [project-level](#use-the-gitlab-endpoint-for-nuget-packages) NuGet endpoint, add the Package Registry as a source with `nuget`:
```shell
nuget source Add -Name <source_name> -Source "https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username or deploy_token_username> -Password <gitlab_personal_access_token or deploy_token>
......@@ -99,9 +118,27 @@ For example:
nuget source Add -Name "GitLab" -Source "https://gitlab.example.com/api/v4/projects/10/packages/nuget/index.json" -UserName carol -Password 12345678asdf
```
#### Group-level endpoint
To use the [group-level](#use-the-gitlab-endpoint-for-nuget-packages) NuGet endpoint, add the Package Registry as a source with `nuget`:
```shell
nuget source Add -Name <source_name> -Source "https://gitlab.example.com/api/v4/groups/<your_group_id>/packages/nuget/index.json" -UserName <gitlab_username or deploy_token_username> -Password <gitlab_personal_access_token or deploy_token>
```
- `<source_name>` is the desired source name.
For example:
```shell
nuget source Add -Name "GitLab" -Source "https://gitlab.example.com/api/v4/groups/23/packages/nuget/index.json" -UserName carol -Password 12345678asdf
```
### Add a source with Visual Studio
To add the Package Registry as a source with Visual Studio:
#### Project-level endpoint
To use the [project-level](#use-the-gitlab-endpoint-for-nuget-packages) NuGet endpoint, add the Package Registry as a source with Visual Studio:
1. Open [Visual Studio](https://visualstudio.microsoft.com/vs/).
1. In Windows, select **File > Options**. On macOS, select **Visual Studio > Preferences**.
......@@ -126,9 +163,38 @@ The source is displayed in your list.
If you get a warning, ensure that the **Location**, **Username**, and
**Password** are correct.
#### Group-level endpoint
To use the [group-level](#use-the-gitlab-endpoint-for-nuget-packages) NuGet endpoint, add the Package Registry as a source with Visual Studio:
1. Open [Visual Studio](https://visualstudio.microsoft.com/vs/).
1. In Windows, select **File > Options**. On macOS, select **Visual Studio > Preferences**.
1. In the **NuGet** section, select **Sources** to view a list of all your NuGet sources.
1. Select **Add**.
1. Complete the following fields:
- **Name**: Name for the source.
- **Location**: `https://gitlab.example.com/api/v4/group/<your_group_id>/packages/nuget/index.json`,
where `<your_group_id>` is your group ID, and `gitlab.example.com` is
your domain name.
- **Username**: Your GitLab username or deploy token username.
- **Password**: Your personal access token or deploy token.
![Visual Studio Adding a NuGet source](img/visual_studio_adding_nuget_source.png)
1. Click **Save**.
The source is displayed in your list.
![Visual Studio NuGet source added](img/visual_studio_nuget_source_added.png)
If you get a warning, ensure that the **Location**, **Username**, and
**Password** are correct.
### Add a source with the .NET CLI
To add the Package Registry as a source for .NET:
#### Project-level endpoint
To use the [project-level](#use-the-gitlab-endpoint-for-nuget-packages) Package Registry as a source for .NET:
1. In the root of your project, create a file named `nuget.config`.
1. Add this content:
......@@ -138,7 +204,30 @@ To add the Package Registry as a source for .NET:
<configuration>
<packageSources>
<clear />
<add key="gitlab" value="https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" />
<add key="gitlab" value="https://gitlab.example.com/api/v4/project/<your_project_id>/packages/nuget/index.json" />
</packageSources>
<packageSourceCredentials>
<gitlab>
<add key="Username" value="<gitlab_username or deploy_token_username>" />
<add key="ClearTextPassword" value="<gitlab_personal_access_token or deploy_token>" />
</gitlab>
</packageSourceCredentials>
</configuration>
```
#### Group-level endpoint
To use the [group-level](#use-the-gitlab-endpoint-for-nuget-packages) Package Registry as a source for .NET:
1. In the root of your project, create a file named `nuget.config`.
1. Add this content:
```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="gitlab" value="https://gitlab.example.com/api/v4/group/<your_group_id>/packages/nuget/index.json" />
</packageSources>
<packageSourceCredentials>
<gitlab>
......@@ -151,6 +240,10 @@ To add the Package Registry as a source for .NET:
## Publish a NuGet package
Prerequisite:
- Set up the [source](#https://docs.gitlab.com/ee/user/packages/nuget_repository/#add-the-package-registry-as-a-source-for-nuget-packages) with a [project-level endpoint](#use-the-gitlab-endpoint-for-nuget-packages).
When publishing packages:
- The Package Registry on GitLab.com can store up to 500 MB of content.
......@@ -164,9 +257,10 @@ When publishing packages:
### Publish a package with the NuGet CLI
Prerequisite:
Prerequisites:
- [A NuGet package created with NuGet CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package).
- Set a [project-level endpoint](#use-the-gitlab-endpoint-for-nuget-packages).
Publish a package by running this command:
......@@ -179,9 +273,10 @@ nuget push <package_file> -Source <source_name>
### Publish a package with the .NET CLI
Prerequisite:
Prerequisites:
- [A NuGet package created with .NET CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package-dotnet-cli).
- Set a [project-level endpoint](#use-the-gitlab-endpoint-for-nuget-packages).
Publish a package by running this command:
......
......@@ -212,6 +212,7 @@ module API
mount ::API::GroupPackages
mount ::API::PackageFiles
mount ::API::NugetProjectPackages
mount ::API::NugetGroupPackages
mount ::API::PypiPackages
mount ::API::ComposerPackages
mount ::API::ConanProjectPackages
......
......@@ -19,29 +19,37 @@ module API
included do
helpers do
def find_packages
packages = package_finder.execute
def find_packages(package_name)
packages = package_finder(package_name).execute
not_found!('Packages') unless packages.exists?
packages
end
def find_package
package = package_finder(package_version: params[:package_version]).execute
.first
def find_package(package_name, package_version)
package = package_finder(package_name, package_version).execute
.first
not_found!('Package') unless package
package
end
def package_finder(finder_params = {})
def package_finder(package_name, package_version = nil)
::Packages::Nuget::PackageFinder.new(
authorized_user_project,
**finder_params.merge(package_name: params[:package_name])
current_user,
project_or_group,
package_name: package_name,
package_version: package_version
)
end
def search_packages(search_term, search_options)
::Packages::Nuget::SearchService
.new(current_user, project_or_group, params[:q], search_options)
.execute
end
end
# https://docs.microsoft.com/en-us/nuget/api/service-index
......@@ -52,11 +60,11 @@ module API
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get 'index', format: :json do
authorize_read_package!(authorized_user_project)
authorize_read_package!(project_or_group)
track_package_event('cli_metadata', :nuget, category: 'API::NugetPackages')
present ::Packages::Nuget::ServiceIndexPresenter.new(authorized_user_project),
with: ::API::Entities::Nuget::ServiceIndex
present ::Packages::Nuget::ServiceIndexPresenter.new(project_or_group),
with: ::API::Entities::Nuget::ServiceIndex
end
# https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource
......@@ -64,8 +72,8 @@ module API
requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX
end
namespace '/metadata/*package_name' do
before do
authorize_read_package!(authorized_user_project)
after_validation do
authorize_read_package!(project_or_group)
end
desc 'The NuGet Metadata Service - Package name level' do
......@@ -75,7 +83,7 @@ module API
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get 'index', format: :json do
present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages),
present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages(params[:package_name])),
with: ::API::Entities::Nuget::PackagesMetadata
end
......@@ -89,7 +97,7 @@ module API
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get '*package_version', format: :json do
present ::Packages::Nuget::PackageMetadataPresenter.new(find_package),
present ::Packages::Nuget::PackageMetadataPresenter.new(find_package(params[:package_name], params[:package_version])),
with: ::API::Entities::Nuget::PackageMetadata
end
end
......@@ -102,8 +110,8 @@ module API
optional :prerelease, type: ::Grape::API::Boolean, desc: 'Include prerelease versions', default: true
end
namespace '/query' do
before do
authorize_read_package!(authorized_user_project)
after_validation do
authorize_read_package!(project_or_group)
end
desc 'The NuGet Search Service' do
......@@ -118,14 +126,13 @@ module API
per_page: params[:take],
padding: params[:skip]
}
search = ::Packages::Nuget::SearchService
.new(authorized_user_project, params[:q], search_options)
.execute
results = search_packages(params[:q], search_options)
track_package_event('search_package', :nuget, category: 'API::NugetPackages')
present ::Packages::Nuget::SearchResultsPresenter.new(search),
with: ::API::Entities::Nuget::SearchResults
present ::Packages::Nuget::SearchResultsPresenter.new(results),
with: ::API::Entities::Nuget::SearchResults
end
end
end
......
......@@ -12,6 +12,7 @@ module API
end
include Constants
include Gitlab::Utils::StrongMemoize
def unauthorized_user_project
@unauthorized_user_project ||= find_project(params[:id])
......@@ -35,6 +36,18 @@ module API
project
end
def find_authorized_group!
strong_memoize(:authorized_group) do
group = find_group(params[:id])
unless group && can?(current_user, :read_group, group)
next unauthorized_or! { not_found! }
end
group
end
end
def authorize!(action, subject = :global, reason = nil)
return if can?(current_user, action, subject)
......
# frozen_string_literal: true
# NuGet Package Manager Client API
#
# These API endpoints are not meant to be consumed directly by users. They are
# called by the NuGet package manager client when users run commands
# like `nuget install` or `nuget push`.
#
# This is the group level API.
module API
class NugetGroupPackages < ::API::Base
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
feature_category :package_registry
default_format :json
rescue_from ArgumentError do |e|
render_api_error!(e.message, 400)
end
after_validation do
require_packages_enabled!
end
helpers do
def project_or_group
find_authorized_group!
end
def require_authenticated!
unauthorized! unless current_user
end
end
params do
requires :id, type: String, desc: 'The ID of a group', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
namespace ':id/packages/nuget' do
after_validation do
# This API can't be accessed anonymously
require_authenticated!
end
include ::API::Concerns::Packages::NugetEndpoints
end
end
end
end
......@@ -5,6 +5,8 @@
# These API endpoints are not meant to be consumed directly by users. They are
# called by the NuGet package manager client when users run commands
# like `nuget install` or `nuget push`.
#
# This is the project level API.
module API
class NugetProjectPackages < ::API::Base
helpers ::API::Helpers::PackagesManagerClientsHelpers
......@@ -20,10 +22,16 @@ module API
render_api_error!(e.message, 400)
end
before do
after_validation do
require_packages_enabled!
end
helpers do
def project_or_group
authorized_user_project
end
end
params do
requires :id, type: String, desc: 'The ID of a project', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX
end
......@@ -31,10 +39,6 @@ module API
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before do
authorized_user_project
end
namespace ':id/packages/nuget' do
include ::API::Concerns::Packages::NugetEndpoints
......@@ -50,24 +54,19 @@ module API
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
put do
authorize_upload!(authorized_user_project)
bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
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::Nuget::CreatePackageService.new(
authorized_user_project,
current_user,
declared_params.merge(build: current_authenticated_job)
).execute
package = ::Packages::Nuget::CreatePackageService.new(project_or_group, current_user, declared_params.merge(build: current_authenticated_job))
.execute
package_file = ::Packages::CreatePackageFileService.new(
package,
file_params.merge(build: current_authenticated_job)
).execute
package_file = ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job))
.execute
track_package_event('push_package', :nuget, category: 'API::NugetPackages')
......@@ -75,7 +74,7 @@ module API
created!
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id })
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id })
forbidden!
end
......@@ -84,9 +83,9 @@ module API
put 'authorize' do
authorize_workhorse!(
subject: authorized_user_project,
subject: project_or_group,
has_length: false,
maximum_size: authorized_user_project.actual_limits.nuget_max_file_size
maximum_size: project_or_group.actual_limits.nuget_max_file_size
)
end
......@@ -95,8 +94,8 @@ module API
requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX
end
namespace '/download/*package_name' do
before do
authorize_read_package!(authorized_user_project)
after_validation do
authorize_read_package!(project_or_group)
end
desc 'The NuGet Content Service - index request' do
......@@ -106,7 +105,7 @@ module API
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get 'index', format: :json do
present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages),
present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages(params[:package_name])),
with: ::API::Entities::Nuget::PackagesVersions
end
......@@ -122,7 +121,7 @@ module API
get '*package_version/*package_filename', format: :nupkg do
filename = "#{params[:package_filename]}.#{params[:format]}"
package_file = ::Packages::PackageFileFinder.new(find_package, filename, with_file_name_like: true)
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
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Packages::FinderHelper do
describe '#packages_visible_to_user' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:group) { create(:group) }
let_it_be_with_reload(:project1) { create(:project, namespace: group) }
let_it_be(:package1) { create(:package, project: project1) }
let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
let_it_be_with_reload(:project2) { create(:project, namespace: subgroup) }
let_it_be(:package2) { create(:package, project: project2) }
let(:finder_class) do
Class.new do
include ::Packages::FinderHelper
def initialize(user)
@current_user = user
end
def execute(group)
packages_visible_to_user(@current_user, within_group: group)
end
end
end
let(:finder) { finder_class.new(user) }
subject { finder.execute(group) }
shared_examples 'returning both packages' do
it { is_expected.to contain_exactly(package1, package2) }
end
shared_examples 'returning package1' do
it { is_expected.to eq [package1]}
end
shared_examples 'returning no packages' do
it { is_expected.to be_empty }
end
where(:group_visibility, :subgroup_visibility, :project2_visibility, :user_role, :shared_example_name) do
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :maintainer | 'returning both packages'
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :developer | 'returning both packages'
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :guest | 'returning both packages'
'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :anonymous | 'returning both packages'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :maintainer | 'returning both packages'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :developer | 'returning both packages'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :guest | 'returning package1'
'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :anonymous | 'returning package1'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both packages'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both packages'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning package1'
'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning package1'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both packages'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both packages'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning no packages'
'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning no packages'
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)
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
it_behaves_like params[:shared_example_name]
end
end
describe '#projects_visible_to_user' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:group) { create(:group) }
let_it_be_with_reload(:project1) { create(:project, namespace: group) }
let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
let_it_be_with_reload(:project2) { create(:project, namespace: subgroup) }
let(:finder_class) do
Class.new do
include ::Packages::FinderHelper
def initialize(user)
@current_user = user
end
def execute(group)
projects_visible_to_user(@current_user, within_group: group)
end
end
end
let(:finder) { finder_class.new(user) }
subject { finder.execute(group) }
shared_examples 'returning both projects' do
it { is_expected.to contain_exactly(project1, project2) }
end
shared_examples 'returning project1' do
it { is_expected.to eq [project1]}
end
shared_examples 'returning no project' do
it { is_expected.to be_empty }
end
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)
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
it_behaves_like params[:shared_example_name]
end
end
end
......@@ -2,74 +2,117 @@
require 'spec_helper'
RSpec.describe Packages::Nuget::PackageFinder do
let_it_be(:package1) { create(:nuget_package) }
let_it_be(:project) { package1.project }
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:project) { create(:project, namespace: subgroup) }
let_it_be(:package1) { create(:nuget_package, project: project) }
let_it_be(:package2) { create(:nuget_package, name: package1.name, version: '2.0.0', project: project) }
let_it_be(:package3) { create(:nuget_package, name: 'Another.Dummy.Package', project: project) }
let_it_be(:other_package_1) { create(:nuget_package, name: package1.name, version: package1.version) }
let_it_be(:other_package_2) { create(:nuget_package, name: package1.name, version: package2.version) }
let(:package_name) { package1.name }
let(:package_version) { nil }
let(:limit) { 50 }
describe '#execute!' do
subject { described_class.new(project, package_name: package_name, package_version: package_version, limit: limit).execute }
subject { described_class.new(user, target, package_name: package_name, package_version: package_version, limit: limit).execute }
it { is_expected.to match_array([package1, package2]) }
shared_examples 'handling all the conditions' do
it { is_expected.to match_array([package1, package2]) }
context 'with lower case package name' do
let(:package_name) { package1.name.downcase }
context 'with lower case package name' do
let(:package_name) { package1.name.downcase }
it { is_expected.to match_array([package1, package2]) }
end
it { is_expected.to match_array([package1, package2]) }
end
context 'with unknown package name' do
let(:package_name) { 'foobar' }
context 'with unknown package name' do
let(:package_name) { 'foobar' }
it { is_expected.to be_empty }
end
it { is_expected.to be_empty }
end
context 'with valid version' do
let(:package_version) { '2.0.0' }
context 'with valid version' do
let(:package_version) { '2.0.0' }
it { is_expected.to match_array([package2]) }
end
it { is_expected.to match_array([package2]) }
end
context 'with unknown version' do
let(:package_version) { 'foobar' }
context 'with unknown version' do
let(:package_version) { 'foobar' }
it { is_expected.to be_empty }
end
it { is_expected.to be_empty }
end
context 'with limit hit' do
let_it_be(:package4) { create(:nuget_package, name: package1.name, project: project) }
let_it_be(:package5) { create(:nuget_package, name: package1.name, project: project) }
let_it_be(:package6) { create(:nuget_package, name: package1.name, project: project) }
let(:limit) { 2 }
it { is_expected.to match_array([package5, package6]) }
end
context 'with downcase package name' do
let(:package_name) { package1.name.downcase }
it { is_expected.to match_array([package1, package2]) }
end
context 'with limit hit' do
let_it_be(:package4) { create(:nuget_package, name: package1.name, project: project) }
let_it_be(:package5) { create(:nuget_package, name: package1.name, project: project) }
let_it_be(:package6) { create(:nuget_package, name: package1.name, project: project) }
let(:limit) { 2 }
context 'with prefix wildcard' do
let(:package_name) { "%#{package1.name[3..-1]}" }
it { is_expected.to match_array([package5, package6]) }
it { is_expected.to match_array([package1, package2]) }
end
context 'with suffix wildcard' do
let(:package_name) { "#{package1.name[0..-3]}%" }
it { is_expected.to match_array([package1, package2]) }
end
context 'with surrounding wildcards' do
let(:package_name) { "%#{package1.name[3..-3]}%" }
it { is_expected.to match_array([package1, package2]) }
end
end
context 'with downcase package name' do
let(:package_name) { package1.name.downcase }
context 'with a project' do
let(:target) { project }
it { is_expected.to match_array([package1, package2]) }
before do
project.add_developer(user)
end
it_behaves_like 'handling all the conditions'
end
context 'with prefix wildcard' do
let(:package_name) { "%#{package1.name[3..-1]}" }
context 'with a subgroup' do
let(:target) { subgroup }
it { is_expected.to match_array([package1, package2]) }
before do
subgroup.add_developer(user)
end
it_behaves_like 'handling all the conditions'
end
context 'with suffix wildcard' do
let(:package_name) { "#{package1.name[0..-3]}%" }
context 'with a group' do
let(:target) { group }
it { is_expected.to match_array([package1, package2]) }
before do
group.add_developer(user)
end
it_behaves_like 'handling all the conditions'
end
context 'with surrounding wildcards' do
let(:package_name) { "%#{package1.name[3..-3]}%" }
context 'with nil' do
let(:target) { nil }
it { is_expected.to match_array([package1, package2]) }
it { is_expected.to be_empty }
end
end
end
......@@ -4,25 +4,64 @@ require 'spec_helper'
RSpec.describe ::Packages::Nuget::ServiceIndexPresenter do
let_it_be(:project) { create(:project) }
let_it_be(:presenter) { described_class.new(project) }
let_it_be(:group) { create(:group) }
let(:presenter) { described_class.new(target) }
describe '#version' do
subject { presenter.version }
it { is_expected.to eq '3.0.0' }
context 'for a group' do
let(:target) { group }
it { is_expected.to eq '3.0.0' }
end
context 'for a project' do
let(:target) { project }
it { is_expected.to eq '3.0.0' }
end
end
describe '#resources' do
subject { presenter.resources }
it 'has valid resources' do
expect(subject.size).to eq 8
subject.each do |resource|
%i[@id @type comment].each do |field|
expect(resource).to have_key(field)
expect(resource[field]).to be_a(String)
shared_examples 'returning valid resources' do |resources_count: 8, include_publish_service: true|
it 'has valid resources' do
expect(subject.size).to eq resources_count
subject.each do |resource|
%i[@id @type comment].each do |field|
expect(resource).to have_key(field)
expect(resource[field]).to be_a(String)
end
end
end
it "does #{'not ' unless include_publish_service}return the publish resource" do
services_types = subject.map { |res| res[:@type] }
described_class::SERVICE_VERSIONS[:publish].each do |publish_service_version|
if include_publish_service
expect(services_types).to include(publish_service_version)
else
expect(services_types).not_to include(publish_service_version)
end
end
end
end
context 'for a group' do
let(:target) { group }
# at the group level we don't have the publish and download service
it_behaves_like 'returning valid resources', resources_count: 6, include_publish_service: false
end
context 'for a project' do
let(:target) { project }
it_behaves_like 'returning valid resources'
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::NugetGroupPackages do
include_context 'nuget api setup'
using RSpec::Parameterized::TableSyntax
let_it_be_with_reload(:group) { create(:group) }
let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
let_it_be_with_reload(:project) { create(:project, namespace: subgroup) }
let_it_be(:deploy_token) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) }
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: group) }
let(:target_type) { 'groups' }
shared_examples 'handling all endpoints' do
describe 'GET /api/v4/groups/:id/packages/nuget' do
it_behaves_like 'handling nuget service requests', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do
let(:url) { "/groups/#{target.id}/packages/nuget/index.json" }
end
end
describe 'GET /api/v4/groups/:id/packages/nuget/metadata/*package_name/index' do
it_behaves_like 'handling nuget metadata requests with package name', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do
let(:url) { "/groups/#{target.id}/packages/nuget/metadata/#{package_name}/index.json" }
end
end
describe 'GET /api/v4/groups/:id/packages/nuget/metadata/*package_name/*package_version' do
it_behaves_like 'handling nuget metadata requests with package name and package version', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do
let(:url) { "/groups/#{target.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" }
end
end
describe 'GET /api/v4/groups/:id/packages/nuget/query' do
it_behaves_like 'handling nuget search requests', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do
let(:url) { "/groups/#{target.id}/packages/nuget/query?#{query_parameters.to_query}" }
end
end
end
context 'with a subgroup' do
# Bug: deploy tokens at parent group will not see the subgroup.
# https://gitlab.com/gitlab-org/gitlab/-/issues/285495
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: subgroup) }
let(:target) { subgroup }
it_behaves_like 'handling all endpoints'
def update_visibility_to(visibility)
project.update!(visibility_level: visibility)
subgroup.update!(visibility_level: visibility)
end
end
context 'a group' do
let(:target) { group }
it_behaves_like 'handling all endpoints'
context 'with dummy packages and anonymous request' do
let_it_be(:package_name) { 'Dummy.Package' }
let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
let(:search_term) { 'umm' }
let(:take) { 26 }
let(:skip) { 0 }
let(:include_prereleases) { true }
let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } }
subject { get api(url), headers: {}}
shared_examples 'handling mixed visibilities' do
where(:group_visibility, :subgroup_visibility, :expected_status) do
'PUBLIC' | 'PUBLIC' | :unauthorized
'PUBLIC' | 'INTERNAL' | :unauthorized
'PUBLIC' | 'PRIVATE' | :unauthorized
'INTERNAL' | 'INTERNAL' | :unauthorized
'INTERNAL' | 'PRIVATE' | :unauthorized
'PRIVATE' | 'PRIVATE' | :unauthorized
end
with_them do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false))
subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false))
group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false))
end
it_behaves_like 'returning response status', params[:expected_status]
end
end
describe 'GET /api/v4/groups/:id/packages/nuget/metadata/*package_name/index' do
it_behaves_like 'handling mixed visibilities' do
let(:url) { "/groups/#{target.id}/packages/nuget/metadata/#{package_name}/index.json" }
end
end
describe 'GET /api/v4/groups/:id/packages/nuget/metadata/*package_name/*package_version' do
it_behaves_like 'handling mixed visibilities' do
let(:url) { "/groups/#{target.id}/packages/nuget/metadata/#{package_name}/#{packages.first.version}.json" }
end
end
describe 'GET /api/v4/groups/:id/packages/nuget/query' do
it_behaves_like 'handling mixed visibilities' do
let(:url) { "/groups/#{target.id}/packages/nuget/query?#{query_parameters.to_query}" }
end
end
end
def update_visibility_to(visibility)
project.update!(visibility_level: visibility)
subgroup.update!(visibility_level: visibility)
group.update!(visibility_level: visibility)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Nuget::SearchService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:project) { create(:project, namespace: subgroup) }
let_it_be(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') }
let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') }
let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
......@@ -16,94 +20,126 @@ RSpec.describe Packages::Nuget::SearchService do
let(:options) { { include_prerelease_versions: include_prerelease_versions, per_page: per_page, padding: padding } }
describe '#execute' do
subject { described_class.new(project, search_term, options).execute }
subject { described_class.new(user, target, search_term, options).execute }
it { expect_search_results 3, package_a, packages_b, packages_c }
shared_examples 'handling all the conditions' do
it { expect_search_results 3, package_a, packages_b, packages_c }
context 'with a smaller per page count' do
let(:per_page) { 2 }
context 'with a smaller per page count' do
let(:per_page) { 2 }
it { expect_search_results 3, package_a, packages_b }
end
it { expect_search_results 3, package_a, packages_b }
end
context 'with 0 per page count' do
let(:per_page) { 0 }
context 'with 0 per page count' do
let(:per_page) { 0 }
it { expect_search_results 3, [] }
end
it { expect_search_results 3, [] }
end
context 'with a negative per page count' do
let(:per_page) { -1 }
context 'with a negative per page count' do
let(:per_page) { -1 }
it { expect { subject }.to raise_error(ArgumentError, 'negative per_page') }
end
it { expect { subject }.to raise_error(ArgumentError, 'negative per_page') }
end
context 'with a padding' do
let(:padding) { 2 }
context 'with a padding' do
let(:padding) { 2 }
it { expect_search_results 3, packages_c }
end
it { expect_search_results 3, packages_c }
end
context 'with a too big padding' do
let(:padding) { 5 }
context 'with a too big padding' do
let(:padding) { 5 }
it { expect_search_results 3, [] }
end
it { expect_search_results 3, [] }
end
context 'with a negative padding' do
let(:padding) { -1 }
context 'with a negative padding' do
let(:padding) { -1 }
it { expect { subject }.to raise_error(ArgumentError, 'negative padding') }
end
it { expect { subject }.to raise_error(ArgumentError, 'negative padding') }
end
context 'with search term' do
let(:search_term) { 'umm' }
context 'with search term' do
let(:search_term) { 'umm' }
it { expect_search_results 3, package_a, packages_b, packages_c }
end
it { expect_search_results 3, package_a, packages_b, packages_c }
end
context 'with nil search term' do
let(:search_term) { nil }
context 'with nil search term' do
let(:search_term) { nil }
it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
end
it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
end
context 'with empty search term' do
let(:search_term) { '' }
context 'with empty search term' do
let(:search_term) { '' }
it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
end
it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
end
context 'with prefix search term' do
let(:search_term) { 'dummy' }
context 'with prefix search term' do
let(:search_term) { 'dummy' }
it { expect_search_results 3, package_a, packages_b, packages_c }
end
it { expect_search_results 3, package_a, packages_b, packages_c }
end
context 'with suffix search term' do
let(:search_term) { 'packagec' }
it { expect_search_results 1, packages_c }
end
context 'with pre release packages' do
let_it_be(:package_e) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1-alpha') }
context 'including them' do
it { expect_search_results 4, package_a, packages_b, packages_c, package_e }
end
context 'excluding them' do
let(:include_prerelease_versions) { false }
context 'with suffix search term' do
let(:search_term) { 'packagec' }
it { expect_search_results 3, package_a, packages_b, packages_c }
it { expect_search_results 1, 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') }
it { expect_search_results 4, package_a, packages_b, packages_c, package_e_release }
end
end
end
end
context 'with pre release packages' do
let_it_be(:package_e) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1-alpha') }
context 'with project' do
let(:target) { project }
context 'including them' do
it { expect_search_results 4, package_a, packages_b, packages_c, package_e }
before do
project.add_developer(user)
end
context 'excluding them' do
let(:include_prerelease_versions) { false }
it_behaves_like 'handling all the conditions'
end
it { expect_search_results 3, package_a, packages_b, packages_c }
context 'with subgroup' do
let(:target) { subgroup }
context 'when mixed with release versions' do
let_it_be(:package_e_release) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1') }
before do
subgroup.add_developer(user)
end
it { expect_search_results 4, package_a, packages_b, packages_c, package_e_release }
end
it_behaves_like 'handling all the conditions'
end
context 'with group' do
let(:target) { group }
before do
group.add_developer(user)
end
it_behaves_like 'handling all the conditions'
end
def expect_search_results(total_count, *results)
......
# frozen_string_literal: true
RSpec.shared_context 'nuget api setup' do
include WorkhorseHelpers
include PackagesManagerApiSpecHelpers
include HttpBasicAuthHelpers
let_it_be(:user) { create(:user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
end
......@@ -3,7 +3,7 @@
RSpec.shared_examples 'rejects nuget packages access' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
......@@ -21,7 +21,7 @@ end
RSpec.shared_examples 'process nuget service index request' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
......@@ -37,7 +37,7 @@ RSpec.shared_examples 'process nuget service index request' do |user_type, statu
end
context 'with invalid format' do
let(:url) { "/projects/#{project.id}/packages/nuget/index.xls" }
let(:url) { "/#{target_type}/#{target.id}/packages/nuget/index.xls" }
it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
end
......@@ -57,7 +57,7 @@ end
RSpec.shared_examples 'process nuget metadata request at package name level' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
......@@ -65,7 +65,7 @@ RSpec.shared_examples 'process nuget metadata request at package name level' do
it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
context 'with invalid format' do
let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.xls" }
let(:url) { "/#{target_type}/#{target.id}/packages/nuget/metadata/#{package_name}/index.xls" }
it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
end
......@@ -83,7 +83,7 @@ end
RSpec.shared_examples 'process nuget metadata request at package name and package version level' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
......@@ -91,7 +91,7 @@ RSpec.shared_examples 'process nuget metadata request at package name and packag
it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
context 'with invalid format' do
let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.xls" }
let(:url) { "/#{target_type}/#{target.id}/packages/nuget/metadata/#{package_name}/#{package.version}.xls" }
it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
end
......@@ -109,7 +109,7 @@ end
RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
......@@ -128,7 +128,7 @@ RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, sta
end
before do
project.add_maintainer(user)
target.add_maintainer(user)
end
it_behaves_like 'returning response status', :forbidden
......@@ -141,18 +141,18 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
it 'creates package files' do
expect(::Packages::Nuget::ExtractionWorker).to receive(:perform_async).once
expect { subject }
.to change { project.packages.count }.by(1)
.to change { target.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(status)
package_file = project.packages.last.package_files.reload.last
package_file = target.packages.last.package_files.reload.last
expect(package_file.file_name).to eq('package.nupkg')
end
end
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
context 'with object storage disabled' do
......@@ -206,7 +206,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
context 'with crafted package.path param' do
let(:crafted_file) { Tempfile.new('nuget.crafted.package.path') }
let(:url) { "/projects/#{project.id}/packages/nuget?package.path=#{crafted_file.path}" }
let(:url) { "/#{target_type}/#{target.id}/packages/nuget?package.path=#{crafted_file.path}" }
let(:params) { { file: temp_file(file_name) } }
let(:file_key) { :file }
......@@ -255,7 +255,7 @@ RSpec.shared_examples 'process nuget download versions request' do |user_type, s
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
......@@ -263,7 +263,7 @@ RSpec.shared_examples 'process nuget download versions request' do |user_type, s
it_behaves_like 'returns a valid nuget download versions json response'
context 'with invalid format' do
let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.xls" }
let(:url) { "/#{target_type}/#{target.id}/packages/nuget/download/#{package_name}/index.xls" }
it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
end
......@@ -281,7 +281,7 @@ end
RSpec.shared_examples 'process nuget download content request' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
......@@ -295,7 +295,7 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
end
context 'with invalid format' do
let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.xls" }
let(:url) { "/#{target_type}/#{target.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.xls" }
it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
end
......@@ -331,7 +331,7 @@ RSpec.shared_examples 'process nuget search request' do |user_type, status, add_
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returns a valid json search response', status, 4, [1, 5, 5, 1]
......@@ -370,20 +370,20 @@ RSpec.shared_examples 'process nuget search request' do |user_type, status, add_
end
end
RSpec.shared_examples 'rejects nuget access with invalid project id' do
context 'with a project id with invalid integers' do
RSpec.shared_examples 'rejects nuget access with invalid target id' do
context 'with a target id with invalid integers' do
using RSpec::Parameterized::TableSyntax
let(:project) { OpenStruct.new(id: id) }
let(:target) { OpenStruct.new(id: id) }
where(:id, :status) do
'/../' | :unauthorized
'/../' | :bad_request
'' | :not_found
'%20' | :unauthorized
'%2e%2e%2f' | :unauthorized
'NaN' | :unauthorized
'%20' | :bad_request
'%2e%2e%2f' | :bad_request
'NaN' | :bad_request
00002345 | :unauthorized
'anything25' | :unauthorized
'anything25' | :bad_request
end
with_them do
......@@ -392,9 +392,9 @@ RSpec.shared_examples 'rejects nuget access with invalid project id' do
end
end
RSpec.shared_examples 'rejects nuget access with unknown project id' do
context 'with an unknown project' do
let(:project) { OpenStruct.new(id: 1234567890) }
RSpec.shared_examples 'rejects nuget access with unknown target id' do
context 'with an unknown target' do
let(:target) { OpenStruct.new(id: 1234567890) }
context 'as anonymous' do
it_behaves_like 'rejects nuget packages access', :anonymous, :unauthorized
......
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