diff options
32 files changed, 1404 insertions, 552 deletions
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 46245286820..62208d838c1 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -33,10 +33,8 @@ class UsersController < ApplicationController end format.json do - # In 13.8, this endpoint will be removed: - # https://gitlab.com/gitlab-org/gitlab/-/issues/289972 - load_events - pager_json("events/_events", @events.count, events: @events) + msg = "This endpoint is deprecated. Use %s instead." % user_activity_path + render json: { message: msg }, status: :not_found end end end diff --git a/app/finders/concerns/packages/finder_helper.rb b/app/finders/concerns/packages/finder_helper.rb new file mode 100644 index 00000000000..524e7aa7ff9 --- /dev/null +++ b/app/finders/concerns/packages/finder_helper.rb @@ -0,0 +1,29 @@ +# 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 diff --git a/app/finders/packages/nuget/package_finder.rb b/app/finders/packages/nuget/package_finder.rb index e6fb6712d47..8f585f045a1 100644 --- a/app/finders/packages/nuget/package_finder.rb +++ b/app/finders/packages/nuget/package_finder.rb @@ -1,11 +1,15 @@ # 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 diff --git a/app/presenters/packages/nuget/service_index_presenter.rb b/app/presenters/packages/nuget/service_index_presenter.rb index ed00b36b362..0bbdb0f9043 100644 --- a/app/presenters/packages/nuget/service_index_presenter.rb +++ b/app/presenters/packages/nuget/service_index_presenter.rb @@ -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 diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb index 3588cda180f..5fcf2d711b0 100644 --- a/app/services/members/create_service.rb +++ b/app/services/members/create_service.rb @@ -54,7 +54,8 @@ module Members end def enqueue_onboarding_progress_action(source) - Namespaces::OnboardingUserAddedWorker.perform_async(source.id) + namespace_id = source.is_a?(Project) ? source.namespace_id : source.id + Namespaces::OnboardingUserAddedWorker.perform_async(namespace_id) end end end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 826a48fdcf9..1d0ab403f12 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -126,9 +126,23 @@ module MergeRequests override :handle_quick_actions def handle_quick_actions(merge_request) super + + # Ensure this parameter does not get used as an attribute + rebase = params.delete(:rebase) + + if rebase + rebase_from_quick_action(merge_request) + # Ignore "/merge" if "/rebase" is used to avoid an unexpected race + params.delete(:merge) + end + merge_from_quick_action(merge_request) if params[:merge] end + def rebase_from_quick_action(merge_request) + merge_request.rebase_async(current_user.id) + end + def merge_from_quick_action(merge_request) last_diff_sha = params.delete(:merge) diff --git a/app/services/packages/nuget/search_service.rb b/app/services/packages/nuget/search_service.rb index b95aa30bec1..de2a4890ef9 100644 --- a/app/services/packages/nuget/search_service.rb +++ b/app/services/packages/nuget/search_service.rb @@ -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 diff --git a/changelogs/unreleased/289972-remove-users-show-json-completely.yml b/changelogs/unreleased/289972-remove-users-show-json-completely.yml new file mode 100644 index 00000000000..7b07c342213 --- /dev/null +++ b/changelogs/unreleased/289972-remove-users-show-json-completely.yml @@ -0,0 +1,5 @@ +--- +title: Remove users#show.json completely +merge_request: 49670 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/36423-nuget-group-level-project-api.yml b/changelogs/unreleased/36423-nuget-group-level-project-api.yml new file mode 100644 index 00000000000..eaf72e7d9aa --- /dev/null +++ b/changelogs/unreleased/36423-nuget-group-level-project-api.yml @@ -0,0 +1,5 @@ +--- +title: Add the NuGet group level API +merge_request: 48356 +author: +type: added diff --git a/changelogs/unreleased/sh-quick-action-rebase.yml b/changelogs/unreleased/sh-quick-action-rebase.yml new file mode 100644 index 00000000000..a790d15228a --- /dev/null +++ b/changelogs/unreleased/sh-quick-action-rebase.yml @@ -0,0 +1,5 @@ +--- +title: Add a quick action for /rebase +merge_request: 49800 +author: +type: added diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md index bdf50ecef0b..1684974809b 100644 --- a/doc/user/packages/nuget_repository/index.md +++ b/doc/user/packages/nuget_repository/index.md @@ -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: diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index 9f849051f40..c758b2775d2 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -56,6 +56,7 @@ The following quick actions are applicable to descriptions, discussions and thre | `/promote` | ✓ | | | Promote issue to epic. **(PREMIUM)** | | `/publish` | ✓ | | | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md) ([Introduced in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906)) **(ULTIMATE)** | | `/reassign @user1 @user2` | ✓ | ✓ | | Replace current assignees with those specified. **(STARTER)** | +| `/rebase` | | ✓ | | Rebase source branch. This will schedule a background task that attempt to rebase the changes in the source branch on the latest commit of the target branch. If `/rebase` is used, `/merge` will be ignored to avoid a race condition where the source branch is merged or deleted before it is rebased. | | `/relabel ~label1 ~label2` | ✓ | ✓ | ✓ | Replace current labels with those specified. | | `/relate #issue1 #issue2` | ✓ | | | Mark issues as related. **(STARTER)** | | `/remove_child_epic <epic>` | | | ✓ | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). **(ULTIMATE)** | diff --git a/lib/api/api.rb b/lib/api/api.rb index adb9a431e01..41c6281d661 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -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 diff --git a/lib/api/concerns/packages/nuget_endpoints.rb b/lib/api/concerns/packages/nuget_endpoints.rb index 5177c4d23c0..dfc1d2957b0 100644 --- a/lib/api/concerns/packages/nuget_endpoints.rb +++ b/lib/api/concerns/packages/nuget_endpoints.rb @@ -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 diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb index 0784efc11d6..c32ce199dd6 100644 --- a/lib/api/helpers/packages/basic_auth_helpers.rb +++ b/lib/api/helpers/packages/basic_auth_helpers.rb @@ -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) diff --git a/lib/api/nuget_group_packages.rb b/lib/api/nuget_group_packages.rb new file mode 100644 index 00000000000..fac4dd4780e --- /dev/null +++ b/lib/api/nuget_group_packages.rb @@ -0,0 +1,54 @@ +# 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 diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb index b2516cc91f8..005493a90aa 100644 --- a/lib/api/nuget_project_packages.rb +++ b/lib/api/nuget_project_packages.rb @@ -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 diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb index aafd493549b..9ac8b98f595 100644 --- a/lib/gitlab/quick_actions/merge_request_actions.rb +++ b/lib/gitlab/quick_actions/merge_request_actions.rb @@ -38,6 +38,32 @@ module Gitlab @updates[:merge] = params[:merge_request_diff_head_sha] end + types MergeRequest + desc do + _('Rebase source branch') + end + explanation do + _('Rebase source branch on the target branch.') + end + condition do + merge_request = quick_action_target + + next false unless merge_request.source_branch_exists? + + access_check = ::Gitlab::UserAccess + .new(current_user, container: merge_request.source_project) + + access_check.can_push_to_branch?(merge_request.source_branch) + end + command :rebase do + # This will be used to avoid simultaneous "/merge" and "/rebase" actions + @updates[:rebase] = true + + branch = quick_action_target.source_branch + + @execution_message[:rebase] = _('Scheduled a rebase of branch %{branch}.') % { branch: branch } + end + desc 'Toggle the Draft status' explanation do noun = quick_action_target.to_ability_name.humanize(capitalize: false) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0a9615bf369..88d9f3f3cbf 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -19556,6 +19556,9 @@ msgstr "" msgid "OnDemandScans|Create a new site profile" msgstr "" +msgid "OnDemandScans|Edit on-demand DAST scan" +msgstr "" + msgid "OnDemandScans|Manage profiles" msgstr "" @@ -22975,6 +22978,12 @@ msgstr "" msgid "Rebase in progress" msgstr "" +msgid "Rebase source branch" +msgstr "" + +msgid "Rebase source branch on the target branch." +msgstr "" + msgid "Receive alerts from manually configured Prometheus servers." msgstr "" @@ -24328,6 +24337,9 @@ msgstr "" msgid "Scheduled Deletion At - %{permanent_deletion_time}" msgstr "" +msgid "Scheduled a rebase of branch %{branch}." +msgstr "" + msgid "Scheduled to merge this merge request (%{strategy})." msgstr "" diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index 04a2e046f42..b48659353ec 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -41,5 +41,6 @@ RSpec.describe 'Merge request > User uses quick actions', :js do end it_behaves_like 'merge quick action' + it_behaves_like 'rebase quick action' end end diff --git a/spec/finders/concerns/packages/finder_helper_spec.rb b/spec/finders/concerns/packages/finder_helper_spec.rb new file mode 100644 index 00000000000..73f77647573 --- /dev/null +++ b/spec/finders/concerns/packages/finder_helper_spec.rb @@ -0,0 +1,161 @@ +# 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 diff --git a/spec/finders/packages/nuget/package_finder_spec.rb b/spec/finders/packages/nuget/package_finder_spec.rb index 9295d0c7a2f..10b5f6c8ec2 100644 --- a/spec/finders/packages/nuget/package_finder_spec.rb +++ b/spec/finders/packages/nuget/package_finder_spec.rb @@ -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 diff --git a/spec/presenters/packages/nuget/service_index_presenter_spec.rb b/spec/presenters/packages/nuget/service_index_presenter_spec.rb index 19ef890e19f..9c95fbc8fd2 100644 --- a/spec/presenters/packages/nuget/service_index_presenter_spec.rb +++ b/spec/presenters/packages/nuget/service_index_presenter_spec.rb @@ -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 diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb new file mode 100644 index 00000000000..aeda51ad315 --- /dev/null +++ b/spec/requests/api/nuget_group_packages_spec.rb @@ -0,0 +1,122 @@ +# 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 diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb index df1daf39144..c64ac6f3a94 100644 --- a/spec/requests/api/nuget_project_packages_spec.rb +++ b/spec/requests/api/nuget_project_packages_spec.rb @@ -2,99 +2,202 @@ require 'spec_helper' RSpec.describe API::NugetProjectPackages do - include WorkhorseHelpers - include PackagesManagerApiSpecHelpers - include HttpBasicAuthHelpers + include_context 'nuget api setup' - let_it_be(:user) { create(:user) } - let_it_be(:project, reload: true) { create(:project, :public) } - let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:project) { create(:project, :public) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } + let(:target) { project } + let(:target_type) { 'projects' } + describe 'GET /api/v4/projects/:id/packages/nuget' do it_behaves_like 'handling nuget service requests' do - let(:url) { "/projects/#{project.id}/packages/nuget/index.json" } + let(:url) { "/projects/#{target.id}/packages/nuget/index.json" } end end describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do it_behaves_like 'handling nuget metadata requests with package name' do - let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" } + let(:url) { "/projects/#{target.id}/packages/nuget/metadata/#{package_name}/index.json" } end end describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do it_behaves_like 'handling nuget metadata requests with package name and package version' do - let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" } + let(:url) { "/projects/#{target.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" } end end describe 'GET /api/v4/projects/:id/packages/nuget/query' do it_behaves_like 'handling nuget search requests' do - let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" } + let(:url) { "/projects/#{target.id}/packages/nuget/query?#{query_parameters.to_query}" } end end + describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do + let_it_be(:package_name) { 'Dummy.Package' } + let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) } + + let(:url) { "/projects/#{target.id}/packages/nuget/download/#{package_name}/index.json" } + + subject { get api(url) } + + context 'with valid target' do + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success + 'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success + 'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success + 'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success + 'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success + 'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success + 'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success + 'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success + 'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success + 'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :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(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + subject { get api(url), headers: 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 GET requests' + + it_behaves_like 'rejects nuget access with unknown target id' + + it_behaves_like 'rejects nuget access with invalid target id' + end + + 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(:url) { "/projects/#{target.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" } + + subject { get api(url) } + + context 'with valid target' do + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success + 'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success + 'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success + 'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success + 'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success + 'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success + 'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success + 'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success + 'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success + 'PRIVATE' | :developer | true | true | 'process nuget download content request' | :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(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + subject { get api(url), headers: 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 GET requests' do + before do + update_visibility_to(Gitlab::VisibilityLevel::PRIVATE) + end + end + + it_behaves_like 'rejects nuget access with unknown target id' + + it_behaves_like 'rejects nuget access with invalid target id' + end + describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } } - let(:url) { "/projects/#{project.id}/packages/nuget/authorize" } + let(:url) { "/projects/#{target.id}/packages/nuget/authorize" } let(:headers) { {} } subject { put api(url), headers: headers } - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_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_header) } + 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 - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - 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_header) } - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + 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 'deploy token for package uploads' - it_behaves_like 'rejects nuget access with unknown project id' + it_behaves_like 'rejects nuget access with unknown target id' - it_behaves_like 'rejects nuget access with invalid project id' - end + it_behaves_like 'rejects nuget access with invalid target id' end describe 'PUT /api/v4/projects/:id/packages/nuget' do let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } } let_it_be(:file_name) { 'package.nupkg' } - let(:url) { "/projects/#{project.id}/packages/nuget" } + let(:url) { "/projects/#{target.id}/packages/nuget" } let(:headers) { {} } let(:params) { { package: temp_file(file_name) } } let(:file_key) { :package } @@ -111,170 +214,61 @@ RSpec.describe API::NugetProjectPackages do ) end - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_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_header) } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - 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 - it_behaves_like 'deploy token for package uploads' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' - - context 'file size above maximum limit' do - let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) } + 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_header) } 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 + update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false)) end - it_behaves_like 'returning response status', :bad_request + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] end end - end - - describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do - let_it_be(:package_name) { 'Dummy.Package' } - let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) } - let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" } - - subject { get api(url) } - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success - 'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success - 'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success - 'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success - 'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success - 'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success - 'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :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 + it_behaves_like 'deploy token for package uploads' - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + it_behaves_like 'rejects nuget access with unknown target id' - subject { get api(url), headers: headers } + it_behaves_like 'rejects nuget access with invalid target id' - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end + context 'file size above maximum limit' do + let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) } - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + 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 'deploy token for package GET requests' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' + it_behaves_like 'returning response status', :bad_request end end - 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(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" } - - subject { get api(url) } - - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success - 'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success - 'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success - 'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success - 'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success - 'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success - 'PRIVATE' | :developer | true | true | 'process nuget download content request' | :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(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - - subject { get api(url), headers: headers } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_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 GET requests' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' - end + def update_visibility_to(visibility) + project.update!(visibility_level: visibility) end end diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index c13010b8b47..136a30387b4 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -76,7 +76,7 @@ RSpec.describe UsersController do end end - context 'json with events' do + context 'requested in json format' do let(:project) { create(:project) } before do @@ -86,31 +86,13 @@ RSpec.describe UsersController do sign_in(user) end - it 'loads events' do + it 'returns 404 with deprecation message' do # Requesting "/username?format=json" instead of "/username.json" get user_url user.username, params: { format: :json } + expect(response).to have_gitlab_http_status(:not_found) expect(response.media_type).to eq('application/json') - expect(Gitlab::Json.parse(response.body)['count']).to eq(1) - end - - it 'hides events if the user cannot read cross project' do - allow(Ability).to receive(:allowed?).and_call_original - expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } - - get user_url user.username, params: { format: :json } - - expect(response.media_type).to eq('application/json') - expect(Gitlab::Json.parse(response.body)['count']).to eq(0) - end - - it 'hides events if the user has a private profile' do - Gitlab::DataBuilder::Push.build_sample(project, private_user) - - get user_url private_user.username, params: { format: :json } - - expect(response.media_type).to eq('application/json') - expect(Gitlab::Json.parse(response.body)['count']).to eq(0) + expect(Gitlab::Json.parse(response.body)['message']).to include('This endpoint is deprecated.') end end end @@ -183,7 +165,7 @@ RSpec.describe UsersController do end end - context 'json with events' do + context 'requested in json format' do let(:project) { create(:project) } before do diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb index e8a4a798b20..7fd6b3e5b8d 100644 --- a/spec/services/members/create_service_spec.rb +++ b/spec/services/members/create_service_spec.rb @@ -2,26 +2,35 @@ require 'spec_helper' -RSpec.describe Members::CreateService do - let_it_be(:project) { create(:project) } +RSpec.describe Members::CreateService, :clean_gitlab_redis_shared_state, :sidekiq_inline do + let_it_be(:source) { create(:project) } let_it_be(:user) { create(:user) } - let_it_be(:project_user) { create(:user) } - let_it_be(:user_ids) { project_user.id.to_s } + let_it_be(:member) { create(:user) } + let_it_be(:user_ids) { member.id.to_s } let_it_be(:access_level) { Gitlab::Access::GUEST } let(:params) { { user_ids: user_ids, access_level: access_level } } - subject(:execute_service) { described_class.new(user, params).execute(project) } + subject(:execute_service) { described_class.new(user, params).execute(source) } before do - project.add_maintainer(user) - allow(Namespaces::OnboardingUserAddedWorker).to receive(:idempotent?).and_return(false) + source.is_a?(Project) ? source.add_maintainer(user) : source.add_owner(user) end context 'when passing valid parameters' do it 'adds a user to members' do expect(execute_service[:status]).to eq(:success) - expect(project.users).to include project_user - expect(Namespaces::OnboardingUserAddedWorker.jobs.last['args'][0]).to eq(project.id) + expect(source.users).to include member + expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(true) + end + + context 'when executing on a group' do + let_it_be(:source) { create(:group) } + + it 'adds a user to members' do + expect(execute_service[:status]).to eq(:success) + expect(source.users).to include member + expect(NamespaceOnboardingAction.completed?(source, :user_added)).to be(true) + end end end @@ -31,8 +40,8 @@ RSpec.describe Members::CreateService do it 'does not add a member' do expect(execute_service[:status]).to eq(:error) expect(execute_service[:message]).to be_present - expect(project.users).not_to include project_user - expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0) + expect(source.users).not_to include member + expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(false) end end @@ -42,8 +51,8 @@ RSpec.describe Members::CreateService do it 'limits the number of users to 100' do expect(execute_service[:status]).to eq(:error) expect(execute_service[:message]).to be_present - expect(project.users).not_to include project_user - expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0) + expect(source.users).not_to include member + expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(false) end end @@ -52,19 +61,19 @@ RSpec.describe Members::CreateService do it 'does not add a member' do expect(execute_service[:status]).to eq(:error) - expect(execute_service[:message]).to include("#{project_user.username}: Access level is not included in the list") - expect(project.users).not_to include project_user - expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0) + expect(execute_service[:message]).to include("#{member.username}: Access level is not included in the list") + expect(source.users).not_to include member + expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(false) end end context 'when passing an existing invite user id' do - let(:user_ids) { create(:project_member, :invited, project: project).invite_email } + let(:user_ids) { create(:project_member, :invited, project: source).invite_email } it 'does not add a member' do expect(execute_service[:status]).to eq(:error) expect(execute_service[:message]).to eq('Invite email has already been taken') - expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0) + expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(false) end end end diff --git a/spec/services/packages/nuget/search_service_spec.rb b/spec/services/packages/nuget/search_service_spec.rb index d163e7087e4..db758dc6672 100644 --- a/spec/services/packages/nuget/search_service_spec.rb +++ b/spec/services/packages/nuget/search_service_spec.rb @@ -1,8 +1,12 @@ # 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) diff --git a/spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb new file mode 100644 index 00000000000..f877d6299bd --- /dev/null +++ b/spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb @@ -0,0 +1,10 @@ +# 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 diff --git a/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb new file mode 100644 index 00000000000..2e8db16ad68 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'rebase quick action' do + context 'when updating the description' do + before do + sign_in(user) + visit edit_project_merge_request_path(project, merge_request) + end + + it 'rebases the MR', :sidekiq_inline do + fill_in('Description', with: '/rebase') + click_button('Save changes') + + expect(page).not_to have_content('commit behind the target branch') + expect(merge_request.reload).not_to be_merged + end + + it 'ignores /merge if /rebase is specified', :sidekiq_inline do + fill_in('Description', with: "/merge\n/rebase") + click_button('Save changes') + + expect(page).not_to have_content('commit behind the target branch') + expect(merge_request.reload).not_to be_merged + end + end + + context 'when creating a new note' do + context 'when the current user can rebase the MR' do + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) + end + + it 'rebase the MR', :sidekiq_inline do + add_note("/rebase") + + expect(page).to have_content "Scheduled a rebase of branch #{merge_request.source_branch}." + end + end + + context 'when the current user cannot rebase the MR' do + before do + project.add_guest(guest) + sign_in(guest) + visit project_merge_request_path(project, merge_request) + end + + it 'does not rebase the MR' do + add_note("/rebase") + + expect(page).not_to have_content 'Your commands have been executed!' + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb index f808d12baf4..689bbf5c3db 100644 --- a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb @@ -1,31 +1,31 @@ # frozen_string_literal: true -RSpec.shared_examples 'handling nuget service requests' do +RSpec.shared_examples 'handling nuget service requests' do |anonymous_requests_example_name: 'process nuget service index request', anonymous_requests_status: :success| subject { get api(url) } - context 'with valid project' do + context 'with valid target' do using RSpec::Parameterized::TableSyntax context 'personal token' do - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success - 'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success - 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success - 'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success - 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :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 + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success + 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success + 'PUBLIC' | :developer | true | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :guest | true | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success + 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success + 'PUBLIC' | :developer | false | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :guest | false | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status + 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :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 @@ -35,7 +35,7 @@ RSpec.shared_examples 'handling nuget service requests' do subject { get api(url), headers: headers } before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + 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] @@ -43,7 +43,7 @@ RSpec.shared_examples 'handling nuget service requests' do end context 'with job token' do - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized @@ -52,7 +52,7 @@ RSpec.shared_examples 'handling nuget service requests' do 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success + 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized @@ -71,7 +71,7 @@ RSpec.shared_examples 'handling nuget service requests' do subject { get api(url), headers: headers } before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + 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] @@ -79,14 +79,18 @@ RSpec.shared_examples 'handling nuget service requests' do end end - it_behaves_like 'deploy token for package GET requests' + it_behaves_like 'deploy token for package GET requests' do + before do + update_visibility_to(Gitlab::VisibilityLevel::PRIVATE) + end + end - it_behaves_like 'rejects nuget access with unknown project id' + it_behaves_like 'rejects nuget access with unknown target id' - it_behaves_like 'rejects nuget access with invalid project id' + it_behaves_like 'rejects nuget access with invalid target id' end -RSpec.shared_examples 'handling nuget metadata requests with package name' do +RSpec.shared_examples 'handling nuget metadata requests with package name' do |anonymous_requests_example_name: 'process nuget metadata request at package name level', anonymous_requests_status: :success| include_context 'with expected presenters dependency groups' let_it_be(:package_name) { 'Dummy.Package' } @@ -99,19 +103,19 @@ RSpec.shared_examples 'handling nuget metadata requests with package name' do packages.each { |pkg| create_dependencies_for(pkg) } end - context 'with valid project' do + context 'with valid target' do using RSpec::Parameterized::TableSyntax - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :developer | true | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :guest | true | false | anonymous_requests_example_name | anonymous_requests_status 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :developer | false | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :guest | false | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized @@ -130,21 +134,25 @@ RSpec.shared_examples 'handling nuget metadata requests with package name' do subject { get api(url), headers: headers } before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + 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 - it_behaves_like 'deploy token for package GET requests' + it_behaves_like 'deploy token for package GET requests' do + before do + update_visibility_to(Gitlab::VisibilityLevel::PRIVATE) + end + end - it_behaves_like 'rejects nuget access with unknown project id' + it_behaves_like 'rejects nuget access with unknown target id' - it_behaves_like 'rejects nuget access with invalid project id' + it_behaves_like 'rejects nuget access with invalid target id' end end -RSpec.shared_examples 'handling nuget metadata requests with package name and package version' do +RSpec.shared_examples 'handling nuget metadata requests with package name and package version' do |anonymous_requests_example_name: 'process nuget metadata request at package name and package version level', anonymous_requests_status: :success| include_context 'with expected presenters dependency groups' let_it_be(:package_name) { 'Dummy.Package' } @@ -157,19 +165,19 @@ RSpec.shared_examples 'handling nuget metadata requests with package name and pa create_dependencies_for(package) end - context 'with valid project' do + context 'with valid target' do using RSpec::Parameterized::TableSyntax - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :developer | true | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :guest | true | false | anonymous_requests_example_name | anonymous_requests_status 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :developer | false | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :guest | false | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized @@ -188,23 +196,25 @@ RSpec.shared_examples 'handling nuget metadata requests with package name and pa subject { get api(url), headers: headers } before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + 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 GET requests' + it_behaves_like 'deploy token for package GET requests' do + before do + update_visibility_to(Gitlab::VisibilityLevel::PRIVATE) + end + end - context 'with invalid package name' do - let_it_be(:package_name) { 'Unkown' } + it_behaves_like 'rejects nuget access with unknown target id' - it_behaves_like 'rejects nuget packages access', :developer, :not_found - end + it_behaves_like 'rejects nuget access with invalid target id' end -RSpec.shared_examples 'handling nuget search requests' do +RSpec.shared_examples 'handling nuget search requests' do |anonymous_requests_example_name: 'process nuget search request', anonymous_requests_status: :success| let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) } let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') } let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) } @@ -219,19 +229,19 @@ RSpec.shared_examples 'handling nuget search requests' do subject { get api(url) } - context 'with valid project' do + context 'with valid target' do using RSpec::Parameterized::TableSyntax - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do 'PUBLIC' | :developer | true | true | 'process nuget search request' | :success 'PUBLIC' | :guest | true | true | 'process nuget search request' | :success - 'PUBLIC' | :developer | true | false | 'process nuget search request' | :success - 'PUBLIC' | :guest | true | false | 'process nuget search request' | :success + 'PUBLIC' | :developer | true | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :guest | true | false | anonymous_requests_example_name | anonymous_requests_status 'PUBLIC' | :developer | false | true | 'process nuget search request' | :success 'PUBLIC' | :guest | false | true | 'process nuget search request' | :success - 'PUBLIC' | :developer | false | false | 'process nuget search request' | :success - 'PUBLIC' | :guest | false | false | 'process nuget search request' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success + 'PUBLIC' | :developer | false | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :guest | false | false | anonymous_requests_example_name | anonymous_requests_status + 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status 'PRIVATE' | :developer | true | true | 'process nuget search request' | :success 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized @@ -250,16 +260,20 @@ RSpec.shared_examples 'handling nuget search requests' do subject { get api(url), headers: headers } before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + 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 GET requests' + it_behaves_like 'deploy token for package GET requests' do + before do + update_visibility_to(Gitlab::VisibilityLevel::PRIVATE) + end + end - it_behaves_like 'rejects nuget access with unknown project id' + it_behaves_like 'rejects nuget access with unknown target id' - it_behaves_like 'rejects nuget access with invalid project id' + it_behaves_like 'rejects nuget access with invalid target id' end diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb index dc6ac5f0371..8b60857cdaf 100644 --- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb @@ -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 |