diff --git a/app/graphql/mutations/work_items/update.rb b/app/graphql/mutations/work_items/update.rb index f22e9bcf393..1115dc74a9b 100644 --- a/app/graphql/mutations/work_items/update.rb +++ b/app/graphql/mutations/work_items/update.rb @@ -10,7 +10,7 @@ module Mutations include Mutations::WorkItems::UpdateArguments include Mutations::WorkItems::Widgetable - authorize :update_work_item + authorize :read_work_item field :work_item, Types::WorkItemType, null: true, @@ -22,9 +22,11 @@ module Mutations work_item = authorized_find!(id: id) widget_params = extract_widget_params!(work_item.work_item_type, attributes) - interpret_quick_actions!(work_item, current_user, widget_params, attributes) + # Only checks permissions for base attributes because widgets define their own permissions independently + raise_resource_not_available_error! unless attributes.empty? || can_update?(work_item) + update_result = ::WorkItems::UpdateService.new( container: work_item.project, current_user: current_user, @@ -62,6 +64,10 @@ module Mutations widget_params.merge!(parsed_params[:widgets]) attributes.merge!(parsed_params[:common]) end + + def can_update?(work_item) + current_user.can?(:update_work_item, work_item) + end end end end diff --git a/app/services/work_items/widgets/labels_service/update_service.rb b/app/services/work_items/widgets/labels_service/update_service.rb index b880398677d..b0791571924 100644 --- a/app/services/work_items/widgets/labels_service/update_service.rb +++ b/app/services/work_items/widgets/labels_service/update_service.rb @@ -11,6 +11,7 @@ module WorkItems end return if params.blank? + return unless has_permission?(:set_work_item_metadata) service_params.merge!(params.slice(:add_label_ids, :remove_label_ids)) end diff --git a/app/services/work_items/widgets/start_and_due_date_service/update_service.rb b/app/services/work_items/widgets/start_and_due_date_service/update_service.rb index 0dbf3aa31d9..5d47b3a1516 100644 --- a/app/services/work_items/widgets/start_and_due_date_service/update_service.rb +++ b/app/services/work_items/widgets/start_and_due_date_service/update_service.rb @@ -8,6 +8,7 @@ module WorkItems return widget.work_item.assign_attributes({ start_date: nil, due_date: nil }) if new_type_excludes_widget? return if params.blank? + return unless has_permission?(:set_work_item_metadata) widget.work_item.assign_attributes(params.slice(:start_date, :due_date)) end diff --git a/doc/api/packages/nuget.md b/doc/api/packages/nuget.md index e28f42fa931..794c14fb5bc 100644 --- a/doc/api/packages/nuget.md +++ b/doc/api/packages/nuget.md @@ -115,7 +115,7 @@ Upload a NuGet package file: - For NuGet v2 feed: - ```shell + ```shell curl --request PUT \ --form 'package=@path/to/mynugetpkg.1.3.0.17.nupkg' \ --user : \ @@ -425,6 +425,37 @@ Example response: } ``` +## Delete service + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/38275) in GitLab 16.5. + +Delete a NuGet package: + +```plaintext +DELETE projects/:id/packages/nuget/:package_name/:package_version +``` + +| Attribute | Type | Required | Description | +| ----------------- | ------ | -------- | ----------- | +| `id` | string | yes | The ID or full path of the project. | +| `package_name` | string | yes | The name of the package. | +| `package_version` | string | yes | The version of the package. | + +```shell +curl --request DELETE \ + --user : \ + "https://gitlab.example.com/api/v4/projects/1/packages/nuget/MyNuGetPkg/1.3.0.17" +``` + +Possible request responses: + +| Status | Description | +| ------ | ----------- | +| `204` | Package deleted | +| `401` | Unauthorized | +| `403` | Forbidden | +| `404` | Not found | + ## V2 Feed Metadata Endpoints > Introduced in GitLab 16.3. diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md index 21b58ac5ab4..e594ff725a4 100644 --- a/doc/ci/environments/protected_environments.md +++ b/doc/ci/environments/protected_environments.md @@ -40,7 +40,7 @@ To protect an environment: - There are two roles to choose from: - **Maintainers**: Allows access to all of the project's users with the Maintainer role. - **Developers**: Allows access to all of the project's users with the Maintainer and Developer role. - - You can select groups that are already associated with the project only. + - You can only select groups that are already [invited](../../user/project/members/share_project_with_groups.md#share-a-project-with-a-group) to the project. - Users must have at least the Developer role to appear in the **Allowed to deploy** list. 1. In the **Approvers** list, select the role, users, or groups you @@ -49,7 +49,7 @@ To protect an environment: - There are two roles to choose from: - **Maintainers**: Allows access to all of the project's users with the Maintainer role. - **Developers**: Allows access to all of the project's users with the Maintainer and Developer role. - - You can select groups that are already associated with the project only. + - You can only select groups that are already [invited](../../user/project/members/share_project_with_groups.md#share-a-project-with-a-group) to the project. - Users must have at least the Developer role to appear in the **Approvers** list. diff --git a/doc/ci/runners/new_creation_workflow.md b/doc/ci/runners/new_creation_workflow.md index 400e0ec9ee3..3465aaf94fc 100644 --- a/doc/ci/runners/new_creation_workflow.md +++ b/doc/ci/runners/new_creation_workflow.md @@ -72,10 +72,9 @@ To continue using registration tokens after GitLab 17.0: Plans to implement a UI setting to re-enable registration tokens are proposed in [issue 411923](https://gitlab.com/gitlab-org/gitlab/-/issues/411923) -## Using runners registered with a runner registration token +## Impact on existing runners -Existing runners will not be affected by these changes and will continue to -work after the legacy registration method is removed in GitLab 18.0. +Existing runners will continue to work as usual even after 18.0. This change only affects registration of new runners. ## Changes to the `gitlab-runner register` command syntax @@ -140,10 +139,6 @@ for each job. The specific runner can be identified by the unique system ID that is generated when the runner process is started. -## Impact on existing runners - -Existing runners will continue to work as usual. This change only affects registration of new runners. - ## Creating runners programmatically In GitLab 15.11 and later, you can use the [POST /user/runners REST API](../../api/users.md#create-a-runner) diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 86fdf5ea112..5f4ef2cfeac 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -2345,6 +2345,9 @@ In this example, a new pipeline causes a running pipeline to be: like a build job. Deployment jobs usually shouldn't be cancelled, to prevent partial deployments. - To completely cancel a running pipeline, all jobs must have `interruptible: true`, or `interruptible: false` jobs must not have started. +- Running jobs are only cancelled if the newer pipeline has new changes. + For example, a running job is not be cancelled if you run a new pipeline for the same + commit by selecting **Run pipeline** in the UI. ### `needs` diff --git a/doc/editor_extensions/gitlab_cli/index.md b/doc/editor_extensions/gitlab_cli/index.md index 8397d96d4f9..5f5bdfe091e 100644 --- a/doc/editor_extensions/gitlab_cli/index.md +++ b/doc/editor_extensions/gitlab_cli/index.md @@ -1,6 +1,6 @@ --- stage: Create -group: Editor Extensions +group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md index 4f8bb56bda8..f5430c5328c 100644 --- a/doc/user/packages/nuget_repository/index.md +++ b/doc/user/packages/nuget_repository/index.md @@ -541,6 +541,36 @@ For example: choco upgrade MyPackage -Source gitlab -Version 1.0.3 ``` +## Delete a package + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/38275) in GitLab 16.5. + +WARNING: +Deleting a package is a permanent action that cannot be undone. + +Prerequisites: + +- You must have the [Maintainer](../../../user/permissions.md#project-members-permissions) role or higher in the project. +- You must have both the package name and version. + +To delete a package with the NuGet CLI: + +```shell +nuget delete -Source -ApiKey +``` + +In this command: + +- `` is the package ID. +- `` is the package version. +- `` is the source name. + +For example: + +```shell +nuget delete MyPackage 1.0.0 -Source gitlab -ApiKey +``` + ## Symbol packages > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/262081) in GitLab 14.1. @@ -561,6 +591,8 @@ for further updates. ## Supported CLI commands +> `nuget delete` and `dotnet nuget delete` commands [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/38275) in GitLab 16.5. + The GitLab NuGet repository supports the following commands for the NuGet CLI (`nuget`) and the .NET CLI (`dotnet`): @@ -568,6 +600,8 @@ CLI (`dotnet`): - `dotnet nuget push`: Upload a package to the registry. - `nuget install`: Install a package from the registry. - `dotnet add`: Install a package from the registry. +- `nuget delete`: Delete a package from the registry. +- `dotnet nuget delete`: Delete a package from the registry. ## Example project diff --git a/lib/api/concerns/packages/nuget/private_endpoints.rb b/lib/api/concerns/packages/nuget/private_endpoints.rb index a166a7294f4..3a6261160e4 100644 --- a/lib/api/concerns/packages/nuget/private_endpoints.rb +++ b/lib/api/concerns/packages/nuget/private_endpoints.rb @@ -20,41 +20,6 @@ module API NON_NEGATIVE_INTEGER_REGEX = %r{\A(0|[1-9]\d*)\z} included do - helpers do - def find_packages(package_name) - packages = package_finder(package_name).execute - - not_found!('Packages') unless packages.exists? - - packages - end - - 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(package_name, package_version = nil) - ::Packages::Nuget::PackageFinder.new( - current_user, - project_or_group, - package_name: package_name, - package_version: package_version, - client_version: headers['X-Nuget-Client-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/registration-base-url-resource params do requires :package_name, type: String, desc: 'The NuGet package name', diff --git a/lib/api/helpers/packages/nuget.rb b/lib/api/helpers/packages/nuget.rb new file mode 100644 index 00000000000..19192b31b16 --- /dev/null +++ b/lib/api/helpers/packages/nuget.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module API + module Helpers + module Packages + module Nuget + def find_packages(package_name) + packages = package_finder(package_name).execute + + not_found!('Packages') unless packages.exists? + + packages + end + + 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(package_name, package_version = nil) + ::Packages::Nuget::PackageFinder.new( + current_user, + project_or_group, + package_name: package_name, + package_version: package_version, + client_version: headers['X-Nuget-Client-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 + end + end +end diff --git a/lib/api/nuget_group_packages.rb b/lib/api/nuget_group_packages.rb index 229032f7a5a..7a6872ee82f 100644 --- a/lib/api/nuget_group_packages.rb +++ b/lib/api/nuget_group_packages.rb @@ -11,6 +11,7 @@ module API class NugetGroupPackages < ::API::Base helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::Packages::BasicAuthHelpers + helpers ::API::Helpers::Packages::Nuget include ::API::Helpers::Authentication feature_category :package_registry diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb index 3da05b4e7d9..46b388a2fda 100644 --- a/lib/api/nuget_project_packages.rb +++ b/lib/api/nuget_project_packages.rb @@ -11,6 +11,7 @@ module API class NugetProjectPackages < ::API::Base helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::Packages::BasicAuthHelpers + helpers ::API::Helpers::Packages::Nuget include ::API::Helpers::Authentication feature_category :package_registry @@ -233,7 +234,7 @@ module API end end - # To support an additional authentication option for publish endpoints, + # To support an additional authentication option for publish/delete endpoints, # we redefine the `authenticate_with` method by combining the previous # authentication option with the new one. authenticate_with do |accept| @@ -311,6 +312,31 @@ module API authorize_nuget_upload end + desc 'The NuGet Package Delete endpoint' do + detail 'This feature was introduced in GitLab 16.5' + success code: 204 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end + params do + requires :package_name, type: String, allow_blank: false, desc: 'The NuGet package name', regexp: Gitlab::Regex.nuget_package_name_regex, documentation: { example: 'mynugetpkg' } + requires :package_version, type: String, allow_blank: false, desc: 'The NuGet package version', regexp: Gitlab::Regex.nuget_version_regex, documentation: { example: '1.0.1' } + end + delete '*package_name/*package_version', format: false, urgency: :low do + authorize_destroy_package!(project_or_group) + + package = find_package(params[:package_name], params[:package_version]) + destroy_conditionally!(package) do |package| + ::Packages::MarkPackageForDestructionService.new(container: package, current_user: current_user).execute + + track_package_event('delete_package', :nuget, category: 'API::NugetPackages', project: package.project, namespace: package.project.namespace) + end + end + namespace '/v2' do desc 'The NuGet V2 Feed Package Publish endpoint' do detail 'This feature was introduced in GitLab 16.2' diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 2cded4a55bb..004617c412a 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -68,7 +68,7 @@ module Backup end puts_time "Dumping #{definition.human_name} ... ".color(:blue) - definition.task.dump(File.join(Gitlab.config.backup.path, definition.destination_path), full_backup_id) + definition.task.dump(File.join(Gitlab.config.backup.path, definition.destination_path), backup_id) puts_time "Dumping #{definition.human_name} ... ".color(:blue) + "done".color(:green) rescue Backup::DatabaseBackupError, Backup::FileBackupError => e diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml index d53f3ddcad4..c19a08bd11d 100644 --- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml @@ -12,15 +12,10 @@ image: python:latest variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" -# Pip's cache doesn't store the python packages # https://pip.pypa.io/en/stable/topics/caching/ -# -# If you want to also cache the installed packages, you have to install -# them in a virtualenv and cache it as well. cache: paths: - .cache/pip - - venv/ before_script: - python --version ; pip --version # For debugging diff --git a/lib/gitlab/github_import/importer/diff_note_importer.rb b/lib/gitlab/github_import/importer/diff_note_importer.rb index 44ffcd7a1e4..6e0748ed423 100644 --- a/lib/gitlab/github_import/importer/diff_note_importer.rb +++ b/lib/gitlab/github_import/importer/diff_note_importer.rb @@ -71,6 +71,7 @@ module Gitlab discussion_id: note.discussion_id, noteable_id: merge_request_id, project_id: project.id, + namespace_id: project.project_namespace_id, author_id: author_id, note: note_body, commit_id: note.original_commit_id, diff --git a/lib/gitlab/github_import/importer/note_importer.rb b/lib/gitlab/github_import/importer/note_importer.rb index 04da015a33f..fe13741e42e 100644 --- a/lib/gitlab/github_import/importer/note_importer.rb +++ b/lib/gitlab/github_import/importer/note_importer.rb @@ -25,6 +25,7 @@ module Gitlab noteable_type: note.noteable_type, noteable_id: noteable_id, project_id: project.id, + namespace_id: project.project_namespace_id, author_id: author_id, note: note_body(author_found), discussion_id: note.discussion_id, @@ -36,8 +37,8 @@ module Gitlab note = Note.new(attributes.merge(importing: true)) note.validate! - # We're using bulk_insert here so we can bypass any validations and - # callbacks. Running these would result in a lot of unnecessary SQL + # We're using bulk_insert here so we can bypass any callbacks. + # Running these would result in a lot of unnecessary SQL # queries being executed when importing large projects. # Note: if you're going to replace `legacy_bulk_insert` with something that trigger callback # to generate HTML version - you also need to regenerate it in diff --git a/lib/gitlab/sidekiq_middleware/skip_jobs.rb b/lib/gitlab/sidekiq_middleware/skip_jobs.rb index 6cc394aa5f4..34ad843e8ee 100644 --- a/lib/gitlab/sidekiq_middleware/skip_jobs.rb +++ b/lib/gitlab/sidekiq_middleware/skip_jobs.rb @@ -67,6 +67,7 @@ module Gitlab # always returns true by default for all workers unless the FF is specifically disabled, e.g. during an incident Feature.enabled?( :"#{RUN_FEATURE_FLAG_PREFIX}_#{worker_class.name}", + Feature.current_request, type: :worker, default_enabled_if_undefined: true ) @@ -94,6 +95,7 @@ module Gitlab def drop_job?(worker_class) Feature.enabled?( :"#{DROP_FEATURE_FLAG_PREFIX}_#{worker_class.name}", + Feature.current_request, type: :worker, default_enabled_if_undefined: false ) diff --git a/qa/gdk/Dockerfile.gdk b/qa/gdk/Dockerfile.gdk index 33deab8f78d..dbd1bd7eff4 100644 --- a/qa/gdk/Dockerfile.gdk +++ b/qa/gdk/Dockerfile.gdk @@ -97,8 +97,9 @@ RUN set -eux; \ (cd gitlab && git init . && git add --all && git commit --quiet -m "Init repository") &> /dev/null; \ gdk config set gitaly.skip_setup true \ && gdk config set workhorse.skip_setup true \ - && gdk config set gitlab_shell.skip_setup true; \ - make redis/redis.conf all \ + && gdk config set gitlab_shell.skip_setup true \ + && cp .tool-versions ./gitlab/ \ + && make redis/redis.conf all \ && gdk kill ENTRYPOINT [ "/home/gdk/entrypoint" ] diff --git a/qa/qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb new file mode 100644 index 00000000000..5d3f4564f20 --- /dev/null +++ b/qa/qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'net/http' + +module QA + RSpec.describe 'Create' do + describe 'Merge Requests', :reliable, product_group: :code_review do + let(:address) { Runtime::Address.new(:gitlab, path) } + + context 'with a malformed URL' do + let(:path) { %(/-/merge_requests?sort=created_date&state= true }) + end + end + + shared_examples 'defers the job' do + it 'does not yield control' do + expect { |b| subject.call(TestWorker.new, job, queue, &b) }.not_to yield_control + end + + it 'delays the job' do + expect(TestWorker).to receive(:perform_in).with(described_class::DELAY, *job['args']) + + subject.call(TestWorker.new, job, queue) { nil } + end + + it 'increments counter' do + expect(metric).to receive(:increment).with({ worker: "TestWorker", action: "deferred" }) + + subject.call(TestWorker.new, job, queue) { nil } + end + + it 'has deferred related fields in job payload' do + subject.call(TestWorker.new, job, queue) { nil } + + expect(job).to include({ 'deferred' => true, 'deferred_by' => :feature_flag, 'deferred_count' => 1 }) + end + end + describe "with all combinations of drop and defer FFs" do using RSpec::Parameterized::TableSyntax - let(:metric) { instance_double(Prometheus::Client::Counter, increment: true) } - - shared_examples 'runs the job normally' do - it 'yields control' do - expect { |b| subject.call(TestWorker.new, job, queue, &b) }.to yield_control - end - - it 'does not increment any metric counter' do - expect(metric).not_to receive(:increment) - - subject.call(TestWorker.new, job, queue) { nil } - end - - it 'does not increment deferred_count' do - subject.call(TestWorker.new, job, queue) { nil } - - expect(job).not_to include('deferred_count') - end - end - - shared_examples 'drops the job' do - it 'does not yield control' do - expect { |b| subject.call(TestWorker.new, job, queue, &b) }.not_to yield_control - end - - it 'increments counter' do - expect(metric).to receive(:increment).with({ worker: "TestWorker", action: "dropped" }) - - subject.call(TestWorker.new, job, queue) { nil } - end - - it 'does not increment deferred_count' do - subject.call(TestWorker.new, job, queue) { nil } - - expect(job).not_to include('deferred_count') - end - - it 'has dropped field in job equal to true' do - subject.call(TestWorker.new, job, queue) { nil } - - expect(job).to include({ 'dropped' => true }) - end - end - - shared_examples 'defers the job' do - it 'does not yield control' do - expect { |b| subject.call(TestWorker.new, job, queue, &b) }.not_to yield_control - end - - it 'delays the job' do - expect(TestWorker).to receive(:perform_in).with(described_class::DELAY, *job['args']) - - subject.call(TestWorker.new, job, queue) { nil } - end - - it 'increments counter' do - expect(metric).to receive(:increment).with({ worker: "TestWorker", action: "deferred" }) - - subject.call(TestWorker.new, job, queue) { nil } - end - - it 'has deferred related fields in job payload' do - subject.call(TestWorker.new, job, queue) { nil } - - expect(job).to include({ 'deferred' => true, 'deferred_by' => :feature_flag, 'deferred_count' => 1 }) - end - end - before do stub_feature_flags("drop_sidekiq_jobs_#{TestWorker.name}": drop_ff) stub_feature_flags("run_sidekiq_jobs_#{TestWorker.name}": run_ff) @@ -112,6 +112,45 @@ RSpec.describe Gitlab::SidekiqMiddleware::SkipJobs, feature_category: :scalabili it_behaves_like params[:resulting_behavior] end end + + describe 'using current_request actor', :request_store do + before do + allow(Gitlab::Metrics).to receive(:counter).and_call_original + allow(Gitlab::Metrics).to receive(:counter).with(described_class::COUNTER, anything).and_return(metric) + end + + context 'with drop_sidekiq_jobs FF' do + before do + stub_feature_flags("drop_sidekiq_jobs_#{TestWorker.name}": Feature.current_request) + end + + it_behaves_like 'drops the job' + + context 'for different request' do + before do + stub_with_new_feature_current_request + end + + it_behaves_like 'runs the job normally' + end + end + + context 'with run_sidekiq_jobs FF' do + before do + stub_feature_flags("run_sidekiq_jobs_#{TestWorker.name}": Feature.current_request) + end + + it_behaves_like 'runs the job normally' + + context 'for different request' do + before do + stub_with_new_feature_current_request + end + + it_behaves_like 'defers the job' + end + end + end end context 'with worker opted for database health check' do diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb index c7c68696888..b4261fb83c7 100644 --- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb @@ -1025,7 +1025,7 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do end context 'when updating notifications subscription' do - let_it_be(:current_user) { reporter } + let_it_be(:current_user) { guest } let(:input) { { 'notificationsWidget' => { 'subscribed' => desired_state } } } let(:fields) do @@ -1059,7 +1059,7 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do update_work_item subscription.reload end.to change(subscription, :subscribed).to(desired_state) - .and(change { work_item.reload.subscribed?(reporter, project) }.to(desired_state)) + .and(change { work_item.reload.subscribed?(guest, project) }.to(desired_state)) expect(response).to have_gitlab_http_status(:success) expect(mutation_response['workItem']['widgets']).to include( @@ -1159,7 +1159,7 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do end context 'when updating currentUserTodos' do - let_it_be(:current_user) { reporter } + let_it_be(:current_user) { guest } let(:fields) do <<~FIELDS @@ -1185,7 +1185,7 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do context 'when adding a new todo' do let(:input) { { 'currentUserTodosWidget' => { 'action' => 'ADD' } } } - context 'when user has access to the work item' do + context 'when user can create todos' do it 'adds a new todo for the user on the work item' do expect { update_work_item }.to change { current_user.todos.count }.by(1) @@ -1203,6 +1203,17 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do } ) end + + context 'when a base attribute is present' do + before do + input.merge!('title' => 'new title') + end + + it_behaves_like 'a mutation that returns top-level errors', errors: [ + 'The resource that you are attempting to access does not exist or you don\'t have permission to ' \ + 'perform this action' + ] + end end context 'when user has no access' do diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb index 1dcae4cf295..a116be84b3e 100644 --- a/spec/requests/api/nuget_project_packages_spec.rb +++ b/spec/requests/api/nuget_project_packages_spec.rb @@ -6,7 +6,6 @@ RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do 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_it_be(:package_name) { 'Dummy.Package' } @@ -15,11 +14,9 @@ RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do let(:target_type) { 'projects' } let(:snowplow_gitlab_standard_context) { snowplow_context } - def snowplow_context(user_role: :developer) - if user_role == :anonymous - { project: target, namespace: target.namespace, property: 'i_package_nuget_user' } - else - { project: target, namespace: target.namespace, property: 'i_package_nuget_user', user: user } + def snowplow_context(user_role: :developer, event_user: user) + { project: target, namespace: target.namespace, property: 'i_package_nuget_user' }.tap do |context| + context[:user] = event_user unless user_role == :anonymous end end @@ -319,6 +316,97 @@ RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do end end + describe 'DELETE /api/v4/projects/:id/packages/nuget/*package_name/*package_version' do + let_it_be(:package) { create(:nuget_package, project: project, name: package_name) } + + let(:url) { "/projects/#{target.id}/packages/nuget/#{package_name}/#{package.version}" } + + subject { delete api(url), headers: headers } + + it { is_expected.to have_request_urgency(:low) } + + context 'with valid target' do + where(:auth, :visibility, :user_role, :shared_examples_name, :expected_status) do + nil | :public | :anonymous | 'rejects nuget packages access' | :unauthorized + nil | :private | :anonymous | 'rejects nuget packages access' | :unauthorized + nil | :internal | :anonymous | 'rejects nuget packages access' | :unauthorized + + :personal_access_token | :public | :guest | 'rejects nuget packages access' | :forbidden + :personal_access_token | :public | :developer | 'rejects nuget packages access' | :forbidden + :personal_access_token | :public | :maintainer | 'process nuget delete request' | :no_content + :personal_access_token | :private | :guest | 'rejects nuget packages access' | :forbidden + :personal_access_token | :private | :developer | 'rejects nuget packages access' | :forbidden + :personal_access_token | :private | :maintainer | 'process nuget delete request' | :no_content + :personal_access_token | :internal | :guest | 'rejects nuget packages access' | :forbidden + :personal_access_token | :internal | :developer | 'rejects nuget packages access' | :forbidden + :personal_access_token | :internal | :maintainer | 'process nuget delete request' | :no_content + + :job_token | :public | :guest | 'rejects nuget packages access' | :forbidden + :job_token | :public | :developer | 'rejects nuget packages access' | :forbidden + :job_token | :public | :maintainer | 'process nuget delete request' | :no_content + :job_token | :private | :guest | 'rejects nuget packages access' | :forbidden + :job_token | :private | :developer | 'rejects nuget packages access' | :forbidden + :job_token | :private | :maintainer | 'process nuget delete request' | :no_content + :job_token | :internal | :guest | 'rejects nuget packages access' | :forbidden + :job_token | :internal | :developer | 'rejects nuget packages access' | :forbidden + :job_token | :internal | :maintainer | 'process nuget delete request' | :no_content + + :deploy_token | :public | nil | 'process nuget delete request' | :no_content + :deploy_token | :private | nil | 'process nuget delete request' | :no_content + :deploy_token | :internal | nil | 'process nuget delete request' | :no_content + + :api_key | :public | :guest | 'rejects nuget packages access' | :forbidden + :api_key | :public | :developer | 'rejects nuget packages access' | :forbidden + :api_key | :public | :maintainer | 'process nuget delete request' | :no_content + :api_key | :private | :guest | 'rejects nuget packages access' | :forbidden + :api_key | :private | :developer | 'rejects nuget packages access' | :forbidden + :api_key | :private | :maintainer | 'process nuget delete request' | :no_content + :api_key | :internal | :guest | 'rejects nuget packages access' | :forbidden + :api_key | :internal | :developer | 'rejects nuget packages access' | :forbidden + :api_key | :internal | :maintainer | 'process nuget delete request' | :no_content + end + + with_them do + let(:snowplow_gitlab_standard_context) do + snowplow_context(user_role: user_role, event_user: auth == :deploy_token ? deploy_token : user) + end + + let(:headers) do + case auth + when :personal_access_token + basic_auth_header(user.username, personal_access_token.token) + when :job_token + basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) + when :deploy_token + basic_auth_header(deploy_token.username, deploy_token.token) + when :api_key + { 'X-NuGet-ApiKey' => personal_access_token.token } + else + {} + end + end + + before do + update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility.to_s.upcase, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status] + end + end + + it_behaves_like 'rejects nuget access with unknown target id' + + it_behaves_like 'rejects nuget access with invalid target id' + + ['%20', '..%2F..', '../..'].each do |value| + context "with invalid package name #{value}" do + let(:package_name) { value } + + it_behaves_like 'returning response status', :bad_request + end + end + end + describe 'PUT /api/v4/projects/:id/packages/nuget/v2/authorize' do it_behaves_like 'nuget authorize upload endpoint' do let(:url) { "/projects/#{target.id}/packages/nuget/v2/authorize" } diff --git a/spec/services/work_items/widgets/labels_service/update_service_spec.rb b/spec/services/work_items/widgets/labels_service/update_service_spec.rb index 17daec2b1ea..43d9d46a268 100644 --- a/spec/services/work_items/widgets/labels_service/update_service_spec.rb +++ b/spec/services/work_items/widgets/labels_service/update_service_spec.rb @@ -8,7 +8,7 @@ RSpec.describe WorkItems::Widgets::LabelsService::UpdateService, feature_categor let_it_be(:label1) { create(:label, project: project) } let_it_be(:label2) { create(:label, project: project) } let_it_be(:label3) { create(:label, project: project) } - let_it_be(:current_user) { create(:user) } + let_it_be(:current_user) { create(:user).tap { |user| project.add_reporter(user) } } let(:work_item) { create(:work_item, project: project, labels: [label1, label2]) } let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Labels) } } @@ -26,6 +26,14 @@ RSpec.describe WorkItems::Widgets::LabelsService::UpdateService, feature_categor } ) end + + context "and user doesn't have permissions to update labels" do + let_it_be(:current_user) { create(:user) } + + it 'removes label params' do + expect(service.prepare_update_params(params: params)).to be_nil + end + end end context 'when widget does not exist in new type' do diff --git a/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb b/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb index 0196e7c2b02..f9708afd313 100644 --- a/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb +++ b/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe WorkItems::Widgets::StartAndDueDateService::UpdateService, feature_category: :portfolio_management do - let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user).tap { |user| project.add_reporter(user) } } let_it_be_with_reload(:work_item) { create(:work_item, project: project) } let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::StartAndDueDate) } } @@ -26,6 +26,14 @@ RSpec.describe WorkItems::Widgets::StartAndDueDateService::UpdateService, featur change(work_item, :due_date).from(nil).to(due_date) ) end + + context "and user doesn't have permissions to update start and due date" do + let_it_be(:user) { create(:user) } + + it 'removes start and due date params params' do + expect(update_params).to be_nil + end + end end context 'when date params are not present' do 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 index f877d6299bd..2543195e779 100644 --- a/spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb +++ b/spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb @@ -6,5 +6,7 @@ RSpec.shared_context 'nuget api setup' do include HttpBasicAuthHelpers let_it_be(:user) { create(:user) } + let_it_be_with_reload(:project) { create(:project, :public) } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } + let_it_be_with_reload(:job) { create(:ci_build, user: user, status: :running, project: project) } 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 0b67f12fb8d..f8e78c8c9b1 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 @@ -732,3 +732,19 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false| end end end + +RSpec.shared_examples 'process nuget delete request' do |user_type, status| + context "for user type #{user_type}" do + before do + target.send("add_#{user_type}", user) if user_type + end + + it_behaves_like 'returning response status', status + + it_behaves_like 'a package tracking event', 'API::NugetPackages', 'delete_package' + + it 'marks package for deletion' do + expect { subject }.to change { package.reset.status }.from('default').to('pending_destruction') + end + end +end