diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index fe126b38871..b1095a278d0 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -dbf0cf7484ba1265ee1cd5e3a49e04da933e931c +d15b9c84faee3eb178e7c7d9360832f26d4107a2 diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue index f790c7b1430..0c3494ea812 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue @@ -16,10 +16,6 @@ import { export default { name: 'DeleteModal', - i18n: { - DELETE_MODAL_CONTENT, - DELETE_PACKAGES_MODAL_DESCRIPTION, - }, components: { GlLink, GlModal, @@ -38,15 +34,21 @@ export default { }, computed: { itemToBeDeleted() { - if (this.itemsToBeDeleted.length === 1) { - const [itemToBeDeleted] = this.itemsToBeDeleted; - return itemToBeDeleted; - } - return null; + return this.itemsToBeDeleted.length === 1 ? this.itemsToBeDeleted[0] : null; }, title() { return this.itemToBeDeleted ? DELETE_MODAL_TITLE : DELETE_PACKAGES_MODAL_TITLE; }, + packageDescription() { + return this.showRequestForwardingContent + ? DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT + : DELETE_MODAL_CONTENT; + }, + packagesDescription() { + return this.showRequestForwardingContent + ? DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT + : DELETE_PACKAGES_MODAL_DESCRIPTION; + }, packagesDeletePrimaryActionProps() { let text = DELETE_PACKAGE_MODAL_PRIMARY_ACTION; @@ -62,11 +64,6 @@ export default { attributes: { variant: 'danger', category: 'primary' }, }; }, - requestForwardingContentMessage() { - return this.itemToBeDeleted - ? DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT - : DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT; - }, }, modal: { cancelAction: { @@ -95,24 +92,23 @@ export default { @primary="$emit('confirm')" @cancel="$emit('cancel')" > -

- - - - diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js index ad5edcd7602..b4276d69ed6 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js @@ -136,7 +136,7 @@ export const DELETE_PACKAGES_WITH_REQUEST_FORWARDING_PRIMARY_ACTION = s__( 'PackageRegistry|Yes, delete selected packages', ); export const DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT = s__( - 'PackageRegistry|Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete the package anyway? %{docLinkStart}What are the risks?%{docLinkEnd}', + 'PackageRegistry|Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete %{name} version %{version} anyway? %{docLinkStart}What are the risks?%{docLinkEnd}', ); export const DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT = s__( 'PackageRegistry|Some of the selected package formats allow request forwarding. Deleting a package while request forwarding is enabled for the project can pose a security risk. Do you want to proceed with deleting the selected packages? %{docLinkStart}What are the risks?%{docLinkEnd}', diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql index 99864f7ad0c..984996b829a 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql +++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql @@ -1,3 +1,5 @@ +#import "~/packages_and_registries/package_registry/graphql/fragments/package_group_settings.fragment.graphql" + query getPackageDetails($id: PackagesPackageID!) { package(id: $id) { id @@ -23,6 +25,9 @@ query getPackageDetails($id: PackagesPackageID!) { path name fullPath + group { + ...GroupPackageSettings + } } tags(first: 10) { nodes { diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue index 0f1c63a04ad..6d4979ac785 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue @@ -2,6 +2,7 @@ import { GlBadge, GlButton, + GlLink, GlModal, GlModalDirective, GlTooltipDirective, @@ -37,6 +38,7 @@ import { DELETE_PACKAGE_FILE_TRACKING_ACTION, DELETE_PACKAGE_FILES_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION, + REQUEST_FORWARDING_HELP_PAGE_PATH, CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION, SHOW_DELETE_SUCCESS_ALERT, FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, @@ -44,6 +46,7 @@ import { DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, DELETE_PACKAGE_FILES_ERROR_MESSAGE, DELETE_PACKAGE_FILES_SUCCESS_MESSAGE, + DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT, DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION, DELETE_MODAL_TITLE, DELETE_MODAL_CONTENT, @@ -64,6 +67,7 @@ export default { GlButton, GlEmptyState, GlModal, + GlLink, GlTab, GlTabs, GlSprintf, @@ -124,6 +128,11 @@ export default { }, }, computed: { + deleteModalContent() { + return this.isRequestForwardingEnabled + ? DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT + : this.deletePackageModalContent; + }, projectName() { return this.packageEntity.project?.name; }, @@ -170,6 +179,12 @@ export default { showFiles() { return this.packageType !== PACKAGE_TYPE_COMPOSER; }, + groupSettings() { + return this.packageEntity.project?.group?.packageSettings ?? {}; + }, + isRequestForwardingEnabled() { + return this.groupSettings[`${this.packageType.toLowerCase()}PackageRequestsForwarding`]; + }, showMetadata() { return [ PACKAGE_TYPE_COMPOSER, @@ -291,6 +306,9 @@ export default { ), otherVersionsTabTitle: s__('PackageRegistry|Other versions'), }, + links: { + REQUEST_FORWARDING_HELP_PAGE_PATH, + }, modal: { packageDeletePrimaryAction: { text: s__('PackageRegistry|Permanently delete'), @@ -398,6 +416,7 @@ export default { :can-destroy="packageEntity.canDestroy" :count="packageVersionsCount" :is-mutation-loading="versionsMutationLoading" + :is-request-forwarding-enabled="isRequestForwardingEnabled" :package-id="packageEntity.id" @delete="deletePackages" > @@ -429,15 +448,23 @@ export default { @canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE)" > - - +

+ + - - + + + + +

diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js index ec894586803..f8dcf1a5c9c 100644 --- a/app/assets/javascripts/pages/projects/blob/show/index.js +++ b/app/assets/javascripts/pages/projects/blob/show/index.js @@ -115,8 +115,8 @@ const initForkInfo = () => { canSyncBranch, aheadComparePath, behindComparePath, - canUserCreateMrInFork, createMrPath, + viewMrPath, } = forkEl.dataset; return new Vue({ el: forkEl, @@ -132,8 +132,8 @@ const initForkInfo = () => { sourceDefaultBranch, aheadComparePath, behindComparePath, - canUserCreateMrInFork, createMrPath, + viewMrPath, }, }); }, diff --git a/app/assets/javascripts/repository/components/fork_info.vue b/app/assets/javascripts/repository/components/fork_info.vue index f3dbf98312e..1da445a7906 100644 --- a/app/assets/javascripts/repository/components/fork_info.vue +++ b/app/assets/javascripts/repository/components/fork_info.vue @@ -26,6 +26,7 @@ export const i18n = { error: s__('ForksDivergence|Failed to fetch fork details. Try again later.'), updateFork: s__('ForksDivergence|Update fork'), createMergeRequest: s__('ForksDivergence|Create merge request'), + viewMergeRequest: s__('ForksDivergence|View merge request'), successMessage: s__( 'ForksDivergence|Successfully fetched and merged from the upstream repository.', ), @@ -121,10 +122,10 @@ export default { required: false, default: '', }, - canUserCreateMrInFork: { - type: Boolean, + viewMrPath: { + type: String, required: false, - default: false, + default: '', }, }, data() { @@ -203,7 +204,10 @@ export default { ); }, hasCreateMrButton() { - return this.canUserCreateMrInFork && this.ahead && this.createMrPath; + return this.ahead && this.createMrPath; + }, + hasViewMrButton() { + return this.viewMrPath; }, forkDivergenceMessage() { if (!this.forkDetails) { @@ -319,6 +323,14 @@ export default { > {{ $options.i18n.createMergeRequest }} + + {{ $options.i18n.viewMergeRequest }} + _('No access'), diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index b98fdba44ec..382f861a802 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -10,7 +10,6 @@ module Ci include Presentable include ChronicDurationAttribute include Gitlab::Utils::StrongMemoize - include IgnorableColumns include SafelyChangeColumnDefault self.table_name = 'p_ci_builds_metadata' @@ -19,8 +18,6 @@ module Ci partitionable scope: :build - ignore_column :runner_machine_id, remove_with: '16.0', remove_after: '2023-04-22' - belongs_to :build, class_name: 'CommitStatus' belongs_to :project diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 1f8e003b09a..d84ba880e71 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -36,7 +36,20 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy condition(:request_access_enabled) { @subject.request_access_enabled } condition(:create_projects_disabled, scope: :subject) do - @subject.project_creation_level == ::Gitlab::Access::NO_ONE_PROJECT_ACCESS + next true if @user.nil? + + visibility_levels = if @user.can_admin_all_resources? + # admin can create projects even with restricted visibility levels + Gitlab::VisibilityLevel.values + else + Gitlab::VisibilityLevel.allowed_levels + end + + allowed_visibility_levels = visibility_levels.select do |level| + Project.new(namespace: @subject).visibility_level_allowed?(level) + end + + @subject.project_creation_level == ::Gitlab::Access::NO_ONE_PROJECT_ACCESS || allowed_visibility_levels.empty? end condition(:developer_maintainer_access, scope: :subject) do diff --git a/db/post_migrate/20230314144640_reschedule_migration_for_links.rb b/db/post_migrate/20230314144640_reschedule_migration_for_links.rb index 311420f4f37..26d2f5b1d2c 100644 --- a/db/post_migrate/20230314144640_reschedule_migration_for_links.rb +++ b/db/post_migrate/20230314144640_reschedule_migration_for_links.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -# rubocop: disable BackgroundMigration/MissingDictionaryFile - class RescheduleMigrationForLinks < Gitlab::Database::Migration[2.1] MIGRATION = 'MigrateLinksForVulnerabilityFindings' DELAY_INTERVAL = 2.minutes @@ -13,20 +11,10 @@ class RescheduleMigrationForLinks < Gitlab::Database::Migration[2.1] restrict_gitlab_migration gitlab_schema: :gitlab_main def up - delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, []) - - queue_batched_background_migration( - MIGRATION, - :vulnerability_occurrences, - :id, - job_interval: DELAY_INTERVAL, - batch_size: BATCH_SIZE, - sub_batch_size: SUB_BATCH_SIZE - ) + # no-op as it is rescheduled via db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb end def down - delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, []) + # no-op as it is rescheduled via db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb end end -# rubocop: enable BackgroundMigration/MissingDictionaryFile diff --git a/db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb b/db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb new file mode 100644 index 00000000000..9c6213c60ee --- /dev/null +++ b/db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# rubocop: disable BackgroundMigration/MissingDictionaryFile + +class RescheduleLinksAvoidingDuplication < Gitlab::Database::Migration[2.1] + MIGRATION = 'MigrateLinksForVulnerabilityFindings' + DELAY_INTERVAL = 2.minutes + SUB_BATCH_SIZE = 500 + BATCH_SIZE = 10000 + + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_main + + def up + delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, []) + + queue_batched_background_migration( + MIGRATION, + :vulnerability_occurrences, + :id, + job_interval: DELAY_INTERVAL, + batch_size: BATCH_SIZE, + sub_batch_size: SUB_BATCH_SIZE + ) + end + + def down + delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, []) + end +end +# rubocop: enable BackgroundMigration/MissingDictionaryFile diff --git a/db/post_migrate/20230414075119_add_namespaces_by_top_level_namespace_index.rb b/db/post_migrate/20230414075119_add_namespaces_by_top_level_namespace_index.rb index 2cc433de605..fda776f3bc5 100644 --- a/db/post_migrate/20230414075119_add_namespaces_by_top_level_namespace_index.rb +++ b/db/post_migrate/20230414075119_add_namespaces_by_top_level_namespace_index.rb @@ -1,15 +1,11 @@ # frozen_string_literal: true class AddNamespacesByTopLevelNamespaceIndex < Gitlab::Database::Migration[2.1] - disable_ddl_transaction! - - INDEX_NAME = 'index_on_namespaces_namespaces_by_top_level_namespace' - def up - prepare_async_index :namespaces, '((traversal_ids[1]), type, id)', name: INDEX_NAME + # no-op: re-implemented in AddNamespacesByTopLevelNamespaceIndexV2 end def down - unprepare_async_index_by_name :ci_builds, INDEX_NAME + # no-op end end diff --git a/db/post_migrate/20230419121943_add_namespaces_by_top_level_namespace_index_v2.rb b/db/post_migrate/20230419121943_add_namespaces_by_top_level_namespace_index_v2.rb new file mode 100644 index 00000000000..5433313407d --- /dev/null +++ b/db/post_migrate/20230419121943_add_namespaces_by_top_level_namespace_index_v2.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddNamespacesByTopLevelNamespaceIndexV2 < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + INDEX_NAME = 'index_on_namespaces_namespaces_by_top_level_namespace' + + def up + unprepare_async_index_by_name :namespaces, INDEX_NAME + prepare_async_index :namespaces, '(traversal_ids[1]), type, id', name: INDEX_NAME + end + + def down + unprepare_async_index_by_name :namespaces, INDEX_NAME + end +end diff --git a/db/schema_migrations/20230412141541 b/db/schema_migrations/20230412141541 new file mode 100644 index 00000000000..5ad6e38d98b --- /dev/null +++ b/db/schema_migrations/20230412141541 @@ -0,0 +1 @@ +777065413e8eb5605037885fb1a38c74b1f464733afb2718380f081edb9ab8a8 \ No newline at end of file diff --git a/db/schema_migrations/20230419121943 b/db/schema_migrations/20230419121943 new file mode 100644 index 00000000000..255098a5411 --- /dev/null +++ b/db/schema_migrations/20230419121943 @@ -0,0 +1 @@ +101dac198ed6204b5b74f809765d2a7f1907907fdfcfbe579989b8fcf61610c0 \ No newline at end of file diff --git a/doc/api/import.md b/doc/api/import.md index 4aeb5277b4a..1de2a92b5ea 100644 --- a/doc/api/import.md +++ b/doc/api/import.md @@ -8,12 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w Use the Import API to import repositories from GitHub or Bitbucket Server. -Related APIs include: - -- [Group migration by direct transfer API](bulk_imports.md). -- [Group import and export API](group_import_export.md). -- [Project import and export API](project_import_export.md). - ## Prerequisites For information on prerequisites for using the Import API, see: @@ -214,3 +208,9 @@ curl --request POST \ For information on automating user, group, and project import API calls, see [Automate group and project import](../user/project/import/index.md#automate-group-and-project-import). + +## Related topics + +- [Group migration by direct transfer API](bulk_imports.md). +- [Group import and export API](group_import_export.md). +- [Project import and export API](project_import_export.md). diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md index 34e043a30d6..0652b88617b 100644 --- a/doc/development/api_styleguide.md +++ b/doc/development/api_styleguide.md @@ -57,6 +57,59 @@ get do end ``` +## Breaking changes + +We must not make breaking changes to our REST API v4, even in major GitLab releases. + +Our REST API maintains its own versioning independent of GitLab versioning. +The current REST API version is `4`. [We commit to follow semantic versioning for our REST API](../api/rest/index.md#compatibility-guidelines), +which means we cannot make breaking changes until a major version change (most likely, `5`). + +Because version `5` is not scheduled, we allow rare [exceptions](#exceptions). + +### Accommodating backward compatibility instead of breaking changes + +Backward compatibility can often be accommodated in the API by continuing to adapt a changed feature to +the old API schema. For example, our REST API +[exposes](https://gitlab.com/gitlab-org/gitlab/-/blob/c104f6b8/lib/api/entities/merge_request_basic.rb#L43-47) both +`work_in_progress` and `draft` fields. + +### Exceptions + +The exception is only when: + +- A feature must be removed in a major GitLab release. +- Backward compatibility cannot be maintained + [in any form](#accommodating-backward-compatibility-instead-of-breaking-changes). + +This exception should be rare. + +Even in this exception, rather than removing a field or argument, we must always do the following: + +- Return an empty response for a field (for example, `"null"` or `[]`). +- Turn an argument into a no-op. + +## What is a breaking change + +Some examples of breaking changes are: + +- Removing or renaming fields, arguments, or enum values. +- Removing endpoints. +- Adding new redirects (not all clients follow redirects). +- Changing the type of fields in the response (for example, from `String` to `Integer`). +- Adding a new **required** argument. +- Changing authentication, authorization, or other header requirements. +- Changing [any status code](../api/rest/index.md#status-codes) other than `500`. + +## What is not a breaking change + +Some examples of non-breaking changes: + +- Any additive change, such as adding endpoints, non-required arguments, fields, or enum values. +- Changes to error messages. +- Changes from a `500` status code to [any supported status code](../api/rest/index.md#status-codes) (this is a bugfix). +- Changes to the order of fields returned in a response. + ## Declared parameters Grape allows you to access only the parameters that have been declared by your diff --git a/doc/development/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md index 3d5e6aae25a..98a497518cb 100644 --- a/doc/development/integrations/jira_connect.md +++ b/doc/development/integrations/jira_connect.md @@ -104,7 +104,7 @@ The following steps describe setting up an environment to test the GitLab OAuth - Confidential: **No** 1. Copy the Application ID. 1. Go to **Admin > Settings > General**. -1. Scroll down and expand the GitLab for Jira App section. +1. Expand **GitLab for Jira App**. 1. Go to [gitpod.io/variables](https://gitpod.io/variables). 1. Paste the Application ID into the **Jira Connect Application ID** field and select **Save changes**. diff --git a/doc/integration/jira/connect-app.md b/doc/integration/jira/connect-app.md index fa7d48ba44d..c1958f150e9 100644 --- a/doc/integration/jira/connect-app.md +++ b/doc/integration/jira/connect-app.md @@ -10,7 +10,7 @@ With the [GitLab for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/ you can integrate GitLab and Jira Cloud. If you use GitLab.com and Jira Cloud, you can install the GitLab for Jira Cloud app. -If you do not use both of these environments, use the [Jira DVCS Connector](dvcs/index.md) instead +If you do not use both of these environments, use the [Jira DVCS connector](dvcs/index.md) instead or [install the GitLab for Jira Cloud app manually](#install-the-gitlab-for-jira-cloud-app-manually). You should use the GitLab for Jira Cloud app because data is synchronized in real time. The Jira DVCS connector updates data only once per hour. @@ -79,7 +79,7 @@ To create an OAuth application: 1. Select **Save application**. 1. Copy the **Application ID** value. 1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`). -1. Expand the **GitLab for Jira App** section. +1. Expand **GitLab for Jira App**. 1. Paste the **Application ID** value into **Jira Connect Application ID**. 1. Select **Save changes**. 1. Optional. Enable the `jira_connect_oauth` [feature flag](../../administration/feature_flags.md) to avoid [authentication problems in some browsers](#browser-displays-a-sign-in-message-when-already-signed-in). @@ -113,7 +113,7 @@ To set up your self-managed instance for the GitLab for Jira Cloud app in GitLab 1. On the top bar, select **Main menu > Admin**. 1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`). -1. Expand the **GitLab for Jira App** section. +1. Expand **GitLab for Jira App**. 1. In **Jira Connect Proxy URL**, enter `https://gitlab.com`. 1. Select **Save changes**. @@ -207,7 +207,7 @@ To configure your GitLab instance to serve as a proxy: 1. On the top bar, select **Main menu > Admin**. 1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`). -1. Expand the **GitLab for Jira App** section. +1. Expand **GitLab for Jira App**. 1. Select **Enable public key storage**. 1. Select **Save changes**. 1. [Install the GitLab for Jira Cloud app manually](#install-the-gitlab-for-jira-cloud-app-manually). @@ -270,7 +270,7 @@ You might get an error if you have installed the GitLab for Jira Cloud app from 1. On the top bar, select **Main menu > Admin**. 1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`). - 1. Expand the **GitLab for Jira App** section. + 1. Expand **GitLab for Jira App**. 1. Clear the **Jira Connect Proxy URL** text box. 1. Select **Save changes**. @@ -296,7 +296,7 @@ To resolve this issue on GitLab self-managed, follow one of the solutions below, - In GitLab 14.9 and later: - Contact the [Jira Software Cloud support](https://support.atlassian.com/jira-software-cloud/) and ask to trigger a new installed lifecycle event for the GitLab for Jira Cloud app in your namespace. - In all GitLab versions: - - Re-install the GitLab for Jira Cloud app. This might remove all already synced development panel data. + - Re-install the GitLab for Jira Cloud app. This method might remove all synced data from the Jira development panel. ### `Failed to update GitLab version` error when setting up the GitLab for Jira Cloud app for self-managed instances diff --git a/doc/integration/jira/dvcs/index.md b/doc/integration/jira/dvcs/index.md index 759fea41ba5..79b3effaf3f 100644 --- a/doc/integration/jira/dvcs/index.md +++ b/doc/integration/jira/dvcs/index.md @@ -81,7 +81,7 @@ or with a [user-owned application](../../oauth_provider.md#create-a-user-owned-a 1. In the **Redirect URI** text box, enter the generated **Redirect URL** from [linking GitLab accounts](https://confluence.atlassian.com/adminjiraserver/linking-gitlab-accounts-1027142272.html). 1. In **Scopes**, select `api` and clear any other checkboxes. - The DVCS connector requires a **write-enabled** `api` scope to automatically create and manage required webhooks. + The Jira DVCS connector requires a **write-enabled** `api` scope to automatically create and manage required webhooks. 1. Select **Submit**. 1. Copy the **Application ID** and **Secret** values. You need these values to configure Jira. diff --git a/doc/integration/jira/dvcs/troubleshooting.md b/doc/integration/jira/dvcs/troubleshooting.md index 6a2e1e052ba..04430fe5509 100644 --- a/doc/integration/jira/dvcs/troubleshooting.md +++ b/doc/integration/jira/dvcs/troubleshooting.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Troubleshooting Jira DVCS connector **(FREE)** -Refer to the items in this section if you're having problems with your DVCS connector. +Refer to the items in this section if you're having problems with your Jira DVCS connector. ## Jira cannot access GitLab server @@ -32,12 +32,12 @@ Problems with SSL and TLS can cause this error message: Error obtaining access token. Cannot access https://gitlab.example.com from Jira. ``` -- The [GitLab Jira integration](../index.md) requires +- The [Jira integration](../index.md) requires GitLab to connect to Jira. Any TLS issues that arise from a private certificate authority or self-signed certificate are resolved [on the GitLab server](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates), as GitLab is the TLS client. -- The Jira Development panel integration requires Jira to connect to GitLab, which +- The Jira development panel requires Jira to connect to GitLab, which causes Jira to be the TLS client. If your GitLab server's certificate is not issued by a public certificate authority, add the appropriate certificate (such as your organization's root certificate) to the Java Truststore on Jira Server. @@ -125,7 +125,7 @@ For more information, see the ## `Sync Failed` error when refreshing repository data -If you get a `Sync Failed` error in Jira when [refreshing repository data](index.md#refresh-data-imported-to-jira) for specific projects, check your DVCS connector logs. Look for errors that occur when executing requests to API resources in GitLab. For example: +If you get a `Sync Failed` error in Jira when [refreshing repository data](index.md#refresh-data-imported-to-jira) for specific projects, check your Jira DVCS connector logs. Look for errors that occur when executing requests to API resources in GitLab. For example: ```plaintext Failed to execute request [https://gitlab.com/api/v4/projects/:id/merge_requests?page=1&per_page=100 GET https://gitlab.com/api/v4/projects/:id/merge_requests?page=1&per_page=100 returned a response status of 403 Forbidden] errors: diff --git a/doc/integration/jira/index.md b/doc/integration/jira/index.md index 41d6cda4c84..5b60994e94c 100644 --- a/doc/integration/jira/index.md +++ b/doc/integration/jira/index.md @@ -10,7 +10,7 @@ If your organization uses [Jira](https://www.atlassian.com/software/jira) issues you can [migrate your issues from Jira](../../user/project/import/jira.md) and work exclusively in GitLab. However, if you'd like to continue to use Jira, you can integrate it with GitLab. GitLab offers two types of Jira integrations, and you -can use one or both depending on the capabilities you need. We recommend you enable both. +can use one or both depending on the capabilities you need. ## Compare integrations @@ -19,29 +19,25 @@ in your GitLab project with any of your projects in Jira. ### Jira integration -This integration connects one or more GitLab projects to a Jira instance. You can host +The Jira integration connects one or more GitLab projects to a Jira instance. You can host the Jira instance yourself or in [Atlassian Cloud](https://www.atlassian.com/migration/assess/why-cloud). The supported Jira versions are `6.x`, `7.x`, `8.x`, and `9.x`. - -For an overview, see [Agile Management - GitLab-Jira Basic Integration](https://www.youtube.com/watch?v=fWvwkx5_00E&feature=youtu.be). +For more information, see [Jira integration](configure.md). -To set up the integration, [configure the settings](configure.md) in GitLab. +### Jira development panel -### Jira development panel integration +The [Jira development panel](development_panel.md) connects all GitLab projects in a group or personal namespace. +When you configure the Jira development panel, you can +[view GitLab activity for an issue](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/) +including related branches, commits, and merge requests. -The [Jira development panel integration](development_panel.md) -connects all GitLab projects under a group or personal namespace. When configured, -relevant GitLab information, including related branches, commits, and merge requests, -displays in the [development panel](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/). - -To set up the Jira development panel integration, use the GitLab for Jira Cloud app -or the Jira DVCS (distributed version control system) connector, -[depending on your installation](development_panel.md#configure-the-panel). +To set up the Jira development panel, use the GitLab for Jira Cloud app +or the Jira DVCS connector. For more information, see [Configure the panel](development_panel.md#configure-the-panel). ### Direct feature comparison -| Capability | Jira integration | Jira development panel integration | +| Capability | Jira integration | Jira development panel | |-|-|-| | Mention a Jira issue ID in a GitLab commit or merge request, and a link to the Jira issue is created. | Yes. | No. | | Mention a Jira issue ID in GitLab and the Jira issue shows the GitLab issue or merge request. | Yes. A Jira comment with the GitLab issue or MR title links to GitLab. The first mention is also added to the Jira issue under **Web links**. | Yes, in the issue's [development panel](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/). | @@ -72,8 +68,8 @@ All Jira integrations share data with Jira to make it visible outside of GitLab. If you integrate a private GitLab project with Jira, the private data is shared with users who have access to your Jira project. -The [**Jira project integration**](#jira-integration) posts GitLab data in the form of comments in Jira issues. -The GitLab for Jira Cloud app and Jira DVCS connector share this data through the [**Jira Development Panel**](development_panel.md). +The [Jira integration](#jira-integration) posts GitLab data in the form of comments in Jira issues. +The GitLab for Jira Cloud app and Jira DVCS connector share this data through the [Jira development panel](development_panel.md). This method provides more fine-grained access control because access can be restricted to certain user groups or roles. ## Third-party Jira integrations diff --git a/lib/api/concerns/packages/debian_package_endpoints.rb b/lib/api/concerns/packages/debian_package_endpoints.rb index db6206a240c..25c97932e31 100644 --- a/lib/api/concerns/packages/debian_package_endpoints.rb +++ b/lib/api/concerns/packages/debian_package_endpoints.rb @@ -100,7 +100,7 @@ module API end authenticate_with do |accept| - accept.token_types(:personal_access_token, :deploy_token, :job_token) + accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username) .sent_through(:http_basic_auth) end @@ -126,7 +126,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'Release.gpg' do distribution_from!(project_or_group).file_signature end @@ -145,7 +144,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'Release' do distribution = distribution_from!(project_or_group) track_debian_package_event LIST_PACKAGE @@ -166,7 +164,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'InRelease' do distribution = distribution_from!(project_or_group) track_debian_package_event LIST_PACKAGE @@ -200,7 +197,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'Packages' do present_index_file!(:di_packages) end @@ -222,7 +218,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'by-hash/SHA256/:file_sha256' do present_index_file!(:di_packages) end @@ -246,7 +241,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'Sources' do present_index_file!(:sources) end @@ -268,7 +262,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'by-hash/SHA256/:file_sha256' do present_index_file!(:sources) end @@ -296,7 +289,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'Packages' do present_index_file!(:packages) end @@ -318,7 +310,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'by-hash/SHA256/:file_sha256' do present_index_file!(:packages) end diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb index 67416e62f7d..7c64dc2f877 100644 --- a/lib/api/debian_group_packages.rb +++ b/lib/api/debian_group_packages.rb @@ -9,16 +9,16 @@ module API resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do helpers do def project_or_group - user_group + find_authorized_group! end end after_validation do require_packages_enabled! - not_found! unless ::Feature.enabled?(:debian_group_packages, user_group) + not_found! unless ::Feature.enabled?(:debian_group_packages, project_or_group) - authorize_read_package!(user_group) + authorize_read_package!(project_or_group) end params do @@ -45,7 +45,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do present_distribution_package_file!(find_project!(params[:project_id])) end diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index b7350efb49f..4f78ac926d8 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -17,7 +17,7 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do helpers do def project_or_group - user_project(action: :read_package) + authorized_user_project(action: :read_package) end end @@ -52,7 +52,6 @@ module API tags %w[debian_packages] end - route_setting :authentication, authenticate_non_public: true get 'pool/:distribution/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do present_distribution_package_file!(project_or_group) end @@ -85,10 +84,9 @@ module API requires :file_name, type: String, desc: 'The filename', regexp: { value: Gitlab::Regex.debian_direct_upload_filename_regex, message: 'Only debs, udebs and ddebs can be directly added to a distribution' } end end - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true put do - authorize_upload!(authorized_user_project) - bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:debian_max_file_size, params[:file].size) + authorize_upload!(project_or_group) + bad_request!('File is too large') if project_or_group.actual_limits.exceeded?(:debian_max_file_size, params[:file].size) file_params = { file: params['file'], @@ -101,10 +99,10 @@ module API package = if params[:distribution].present? ::Packages::CreateTemporaryPackageService.new( - authorized_user_project, current_user, declared_params.merge(build: current_authenticated_job) + project_or_group, current_user, declared_params.merge(build: current_authenticated_job) ).execute(:debian, name: ::Packages::Debian::TEMPORARY_PACKAGE_NAME) else - ::Packages::Debian::FindOrCreateIncomingService.new(authorized_user_project, current_user).execute + ::Packages::Debian::FindOrCreateIncomingService.new(project_or_group, current_user).execute end ::Packages::Debian::CreatePackageFileService.new(package: package, current_user: current_user, params: file_params).execute @@ -113,7 +111,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 @@ -137,11 +135,10 @@ module API requires :file_name, type: String, desc: 'The filename', regexp: { value: Gitlab::Regex.debian_direct_upload_filename_regex, message: 'Only debs, udebs and ddebs can be directly added to a distribution' } end end - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true put 'authorize' do authorize_workhorse!( - subject: authorized_user_project, - maximum_size: authorized_user_project.actual_limits.debian_max_file_size + subject: project_or_group, + maximum_size: project_or_group.actual_limits.debian_max_file_size ) end end diff --git a/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings.rb b/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings.rb index 0e38be3b4c9..0b79bc143db 100644 --- a/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings.rb +++ b/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings.rb @@ -20,33 +20,28 @@ module Gitlab end def perform - each_sub_batch do |sub_batch| - migrate_remediations(sub_batch) + each_sub_batch(batching_scope: ->(relation) { relation.select(:id, :raw_metadata) }) do |findings| + migrate_remediations( + findings, + Link + .where(vulnerability_occurrence_id: findings.map(&:id)) + .group(:vulnerability_occurrence_id, :name, :url) + .count + ) end end private - def migrate_remediations(sub_batch) - sub_batch.each do |finding| - links = extract_links(finding.raw_metadata) - - list_of_attrs = links.map do |link| - build_link(finding, link) - end - - next unless list_of_attrs.present? - - begin - create_links(list_of_attrs) - rescue ActiveRecord::RecordNotUnique - rescue StandardError => e - logger.error( - message: e.message, - class: self.class.name, - model_id: finding.id - ) - end + def migrate_remediations(findings, existing_links) + findings.each do |finding| + create_links(build_links_from(finding, existing_links)) + rescue ActiveRecord::StatementInvalid => e + logger.error( + message: e.message, + class: self.class.name, + model_id: finding.id + ) end end @@ -61,7 +56,16 @@ module Gitlab } end + def build_links_from(finding, existing_links) + extract_links(finding.raw_metadata).filter_map do |link| + key = [finding.id, link['name'], link['url']] + build_link(finding, link) unless existing_links.key?(key) + end + end + def create_links(attributes) + return if attributes.empty? + Link.upsert_all(attributes, returning: false) end @@ -72,6 +76,12 @@ module Gitlab return [] if parsed_links.blank? parsed_links.select { |link| link.try(:[], 'url').present? }.uniq + rescue JSON::ParserError => e + logger.warn( + message: e.message, + class: self.class.name + ) + [] end def logger diff --git a/lib/gitlab/utils/error_message.rb b/lib/gitlab/utils/error_message.rb index 72b69fb078f..cd9af95bfc3 100644 --- a/lib/gitlab/utils/error_message.rb +++ b/lib/gitlab/utils/error_message.rb @@ -12,7 +12,7 @@ module Gitlab end def prefixed_error_message(message, prefix) - "#{prefix}: #{message}" + "#{prefix} #{message}" end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f5f07e0eed0..6f958831f98 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18729,6 +18729,9 @@ msgstr "" msgid "ForksDivergence|Update fork" msgstr "" +msgid "ForksDivergence|View merge request" +msgstr "" + msgid "Format: %{dateFormat}" msgstr "" @@ -31270,7 +31273,7 @@ msgstr "" msgid "PackageRegistry|Deleting the last package asset will remove version %{version} of %{name}. Are you sure?" msgstr "" -msgid "PackageRegistry|Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete the package anyway? %{docLinkStart}What are the risks?%{docLinkEnd}" +msgid "PackageRegistry|Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete %{name} version %{version} anyway? %{docLinkStart}What are the risks?%{docLinkEnd}" msgstr "" msgid "PackageRegistry|Duplicate packages" diff --git a/package.json b/package.json index 1470a17c60b..ad3431fa9bc 100644 --- a/package.json +++ b/package.json @@ -59,12 +59,12 @@ "@gitlab/svgs": "3.38.0", "@gitlab/ui": "60.1.0", "@gitlab/visual-review-tools": "1.7.3", - "@gitlab/web-ide": "0.0.1-dev-20230407181558", + "@gitlab/web-ide": "0.0.1-dev-20230418150125", "@mattiasbuelens/web-streams-adapter": "^0.1.0", "@popperjs/core": "^2.11.2", "@rails/actioncable": "6.1.4-7", "@rails/ujs": "6.1.4-7", - "@sourcegraph/code-host-integration": "0.0.84", + "@sourcegraph/code-host-integration": "0.0.91", "@tiptap/core": "^2.0.3", "@tiptap/extension-blockquote": "^2.0.3", "@tiptap/extension-bold": "^2.0.3", diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index e91e6673498..088b5b11a9a 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -510,6 +510,33 @@ RSpec.describe 'Group', feature_category: :subgroups do end end end + + context 'when in a private group' do + before do + group.update!( + visibility_level: Gitlab::VisibilityLevel::PRIVATE, + project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS + ) + end + + context 'when visibility levels have been restricted to private only by an administrator' do + before do + stub_application_setting( + restricted_visibility_levels: [ + Gitlab::VisibilityLevel::PRIVATE + ] + ) + end + + it 'does not display the "New project" button' do + visit group_path(group) + + page.within '[data-testid="group-buttons"]' do + expect(page).not_to have_link('New project') + end + end + end + end end describe 'group README', :js do diff --git a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js index b0fc9ef0f0c..4ab81182c9a 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js @@ -1,13 +1,13 @@ -import { GlModal as RealGlModal, GlSprintf } from '@gitlab/ui'; +import { GlModal as RealGlModal, GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { stubComponent } from 'helpers/stub_component'; import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue'; import { DELETE_PACKAGE_MODAL_PRIMARY_ACTION, - DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT, DELETE_PACKAGE_WITH_REQUEST_FORWARDING_PRIMARY_ACTION, DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT, DELETE_PACKAGES_WITH_REQUEST_FORWARDING_PRIMARY_ACTION, + REQUEST_FORWARDING_HELP_PAGE_PATH, } from '~/packages_and_registries/package_registry/constants'; const GlModal = stubComponent(RealGlModal, { @@ -21,16 +21,17 @@ describe('DeleteModal', () => { const defaultItemsToBeDeleted = [ { - name: 'package 01', + name: 'package-1', version: '1.0.0', }, { - name: 'package 02', + name: 'package-2', version: '1.0.0', }, ]; const findModal = () => wrapper.findComponent(GlModal); + const findLink = () => wrapper.findComponent(GlLink); const mountComponent = ({ itemsToBeDeleted = defaultItemsToBeDeleted, @@ -73,7 +74,7 @@ describe('DeleteModal', () => { mountComponent({ itemsToBeDeleted: [defaultItemsToBeDeleted[0]] }); expect(findModal().text()).toMatchInterpolatedText( - 'You are about to delete version 1.0.0 of package 01. Are you sure?', + 'You are about to delete version 1.0.0 of package-1. Are you sure?', ); }); @@ -92,6 +93,13 @@ describe('DeleteModal', () => { ); }); + it('contains link to help page', () => { + mountComponent({ showRequestForwardingContent: true }); + + expect(findLink().exists()).toBe(true); + expect(findLink().attributes('href')).toBe(REQUEST_FORWARDING_HELP_PAGE_PATH); + }); + it('sets the right action primary text', () => { mountComponent({ showRequestForwardingContent: true }); @@ -110,10 +118,15 @@ describe('DeleteModal', () => { it('renders correct description', () => { expect(findModal().text()).toMatchInterpolatedText( - DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT, + 'Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete package-1 version 1.0.0 anyway? What are the risks?', ); }); + it('contains link to help page', () => { + expect(findLink().exists()).toBe(true); + expect(findLink().attributes('href')).toBe(REQUEST_FORWARDING_HELP_PAGE_PATH); + }); + it('sets the right action primary text', () => { expect(findModal().props('actionPrimary')).toMatchObject({ text: DELETE_PACKAGE_WITH_REQUEST_FORWARDING_PRIMARY_ACTION, diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js index a700f42d367..e9f2a2c5095 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js @@ -7,7 +7,6 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import { stubComponent } from 'helpers/stub_component'; import waitForPromises from 'helpers/wait_for_promises'; import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue'; -import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue'; import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; @@ -46,7 +45,6 @@ describe('PackageVersionsList', () => { findListRow: () => wrapper.findComponent(VersionRow), findAllListRow: () => wrapper.findAllComponents(VersionRow), findDeletePackagesModal: () => wrapper.findComponent(DeleteModal), - findPackageListDeleteModal: () => wrapper.findComponent(DeletePackageModal), }; const mountComponent = ({ @@ -247,7 +245,7 @@ describe('PackageVersionsList', () => { `('$description', ({ finderFunction, deletePayload }) => { let eventSpy; const category = 'UI::NpmPackages'; - const { findPackageListDeleteModal } = uiElements; + const { findDeletePackagesModal } = uiElements; beforeEach(async () => { eventSpy = jest.spyOn(Tracking, 'event'); @@ -256,10 +254,10 @@ describe('PackageVersionsList', () => { finderFunction().vm.$emit('delete', deletePayload); }); - it('passes itemToBeDeleted to the modal', () => { - expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual( + it('passes itemsToBeDeleted to the modal', () => { + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual([ packageVersions()[0], - ); + ]); }); it('requesting delete tracks the right action', () => { @@ -272,7 +270,7 @@ describe('PackageVersionsList', () => { describe('when modal confirms', () => { beforeEach(() => { - findPackageListDeleteModal().vm.$emit('ok'); + findDeletePackagesModal().vm.$emit('confirm'); }); it('emits delete when modal confirms', () => { @@ -288,14 +286,14 @@ describe('PackageVersionsList', () => { }); }); - it.each(['ok', 'cancel'])('resets itemToBeDeleted when modal emits %s', async (event) => { - await findPackageListDeleteModal().vm.$emit(event); + it.each(['confirm', 'cancel'])('resets itemsToBeDeleted when modal emits %s', async (event) => { + await findDeletePackagesModal().vm.$emit(event); - expect(findPackageListDeleteModal().props('itemToBeDeleted')).toBeNull(); + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual([]); }); it('canceling delete tracks the right action', () => { - findPackageListDeleteModal().vm.$emit('cancel'); + findDeletePackagesModal().vm.$emit('cancel'); expect(eventSpy).toHaveBeenCalledWith( category, @@ -383,4 +381,15 @@ describe('PackageVersionsList', () => { }); }); }); + + describe('with isRequestForwardingEnabled prop', () => { + const { findDeletePackagesModal } = uiElements; + + it.each([true, false])('sets modal prop showRequestForwardingContent to %s', async (value) => { + mountComponent({ props: { isRequestForwardingEnabled: value } }); + await waitForPromises(); + + expect(findDeletePackagesModal().props('showRequestForwardingContent')).toBe(value); + }); + }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/mock_data.js b/spec/frontend/packages_and_registries/package_registry/mock_data.js index 9054e4998bb..5fb53566d4e 100644 --- a/spec/frontend/packages_and_registries/package_registry/mock_data.js +++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js @@ -16,7 +16,7 @@ export const packagePipelines = (extend) => [ ref: 'master', sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0', project: { - id: '1', + id: '14', name: 'project14', webUrl: 'http://gdk.test:3000/namespace14/project14', __typename: 'Project', @@ -219,7 +219,10 @@ export const pagination = (extend) => ({ ...extend, }); -export const packageDetailsQuery = (extendPackage) => ({ +export const packageDetailsQuery = ({ + extendPackage = {}, + packageSettings = defaultPackageGroupSettings, +} = {}) => ({ data: { package: { ...packageData(), @@ -235,6 +238,12 @@ export const packageDetailsQuery = (extendPackage) => ({ path: 'projectPath', name: 'gitlab-test', fullPath: 'gitlab-test', + group: { + id: '1', + packageSettings, + __typename: 'Group', + }, + __typename: 'Project', }, tags: { nodes: packageTags(), diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js index e1765917035..0962b4fa757 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js @@ -1,4 +1,4 @@ -import { GlEmptyState, GlModal, GlTabs, GlTab, GlSprintf } from '@gitlab/ui'; +import { GlEmptyState, GlModal, GlTabs, GlTab, GlSprintf, GlLink } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -18,6 +18,7 @@ import PackageTitle from '~/packages_and_registries/package_registry/components/ import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; import { + REQUEST_FORWARDING_HELP_PAGE_PATH, FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, PACKAGE_TYPE_COMPOSER, DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, @@ -43,6 +44,7 @@ import { packageFiles, packageDestroyFilesMutation, packageDestroyFilesMutationError, + defaultPackageGroupSettings, } from '../mock_data'; jest.mock('~/alert'); @@ -125,6 +127,7 @@ describe('PackagesApp', () => { const findDependencyRows = () => wrapper.findAllComponents(DependencyRow); const findDeletePackageModal = () => wrapper.findAllComponents(DeletePackages).at(1); const findDeletePackages = () => wrapper.findComponent(DeletePackages); + const findLink = () => wrapper.findComponent(GlLink); it('renders an empty state component', async () => { createComponent({ resolver: jest.fn().mockResolvedValue(emptyPackageDetailsQuery) }); @@ -184,7 +187,9 @@ describe('PackagesApp', () => { createComponent({ resolver: jest.fn().mockResolvedValue( packageDetailsQuery({ - packageType, + extendPackage: { + packageType, + }, }), ), }); @@ -239,16 +244,55 @@ describe('PackagesApp', () => { }); }); - it('shows the delete confirmation modal when delete is clicked', async () => { - createComponent(); + describe('when delete button is clicked', () => { + describe('with request forwarding enabled', () => { + beforeEach(async () => { + const resolver = jest.fn().mockResolvedValue( + packageDetailsQuery({ + packageSettings: { + ...defaultPackageGroupSettings, + npmPackageRequestsForwarding: true, + }, + }), + ); + createComponent({ resolver }); - await waitForPromises(); + await waitForPromises(); - await findDeleteButton().trigger('click'); + await findDeleteButton().trigger('click'); + }); - expect(findDeleteModal().text()).toBe( - 'You are about to delete version 1.0.0 of @gitlab-org/package-15. Are you sure?', - ); + it('shows the delete confirmation modal with request forwarding content', () => { + expect(findDeleteModal().text()).toBe( + 'Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete @gitlab-org/package-15 version 1.0.0 anyway? What are the risks?', + ); + }); + + it('contains link to help page', () => { + expect(findLink().exists()).toBe(true); + expect(findLink().attributes('href')).toBe(REQUEST_FORWARDING_HELP_PAGE_PATH); + }); + }); + + it('shows the delete confirmation modal without request forwarding content', async () => { + const resolver = jest.fn().mockResolvedValue( + packageDetailsQuery({ + packageSettings: { + ...defaultPackageGroupSettings, + npmPackageRequestsForwarding: false, + }, + }), + ); + createComponent({ resolver }); + + await waitForPromises(); + + await findDeleteButton().trigger('click'); + + expect(findDeleteModal().text()).toBe( + 'You are about to delete version 1.0.0 of @gitlab-org/package-15. Are you sure?', + ); + }); }); describe('successful request', () => { @@ -302,7 +346,9 @@ describe('PackagesApp', () => { createComponent({ resolver: jest .fn() - .mockResolvedValue(packageDetailsQuery({ packageType: PACKAGE_TYPE_COMPOSER })), + .mockResolvedValue( + packageDetailsQuery({ extendPackage: { packageType: PACKAGE_TYPE_COMPOSER } }), + ), }); await waitForPromises(); @@ -341,12 +387,18 @@ describe('PackagesApp', () => { const [packageFile] = packageFiles(); const resolver = jest.fn().mockResolvedValue( packageDetailsQuery({ - packageFiles: { - pageInfo: { - hasNextPage: false, + extendPackage: { + packageFiles: { + pageInfo: { + hasNextPage: false, + }, + nodes: [packageFile], + __typename: 'PackageFileConnection', }, - nodes: [packageFile], - __typename: 'PackageFileConnection', + }, + packageSettings: { + ...defaultPackageGroupSettings, + npmPackageRequestsForwarding: false, }, }), ); @@ -523,11 +575,17 @@ describe('PackagesApp', () => { it('opens the delete package confirmation modal', async () => { const resolver = jest.fn().mockResolvedValue( packageDetailsQuery({ - packageFiles: { - pageInfo: { - hasNextPage: false, + extendPackage: { + packageFiles: { + pageInfo: { + hasNextPage: false, + }, + nodes: packageFiles(), }, - nodes: packageFiles(), + }, + packageSettings: { + ...defaultPackageGroupSettings, + npmPackageRequestsForwarding: false, }, }), ); @@ -565,8 +623,10 @@ describe('PackagesApp', () => { createComponent({ resolver: jest.fn().mockResolvedValue( packageDetailsQuery({ - versions: { - count: 0, + extendPackage: { + versions: { + count: 0, + }, }, }), ), @@ -591,6 +651,7 @@ describe('PackagesApp', () => { count: packageVersions().length, isMutationLoading: false, packageId: 'gid://gitlab/Packages::Package/1', + isRequestForwardingEnabled: true, }); }); @@ -646,8 +707,10 @@ describe('PackagesApp', () => { createComponent({ resolver: jest.fn().mockResolvedValue( packageDetailsQuery({ - packageType: PACKAGE_TYPE_NUGET, - dependencyLinks: { nodes: [] }, + extendPackage: { + packageType: PACKAGE_TYPE_NUGET, + dependencyLinks: { nodes: [] }, + }, }), ), }); @@ -663,7 +726,9 @@ describe('PackagesApp', () => { createComponent({ resolver: jest.fn().mockResolvedValue( packageDetailsQuery({ - packageType: PACKAGE_TYPE_NUGET, + extendPackage: { + packageType: PACKAGE_TYPE_NUGET, + }, }), ), }); diff --git a/spec/frontend/repository/components/fork_info_spec.js b/spec/frontend/repository/components/fork_info_spec.js index 8521f91a6c7..62a66e59d24 100644 --- a/spec/frontend/repository/components/fork_info_spec.js +++ b/spec/frontend/repository/components/fork_info_spec.js @@ -86,6 +86,7 @@ describe('ForkInfo component', () => { const findIcon = () => wrapper.findComponent(GlIcon); const findUpdateForkButton = () => wrapper.findByTestId('update-fork-button'); const findCreateMrButton = () => wrapper.findByTestId('create-mr-button'); + const findViewMrButton = () => wrapper.findByTestId('view-mr-button'); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findDivergenceMessage = () => wrapper.findByTestId('divergence-message'); const findInaccessibleMessage = () => wrapper.findByTestId('inaccessible-project'); @@ -145,8 +146,14 @@ describe('ForkInfo component', () => { expect(findCreateMrButton().attributes('href')).toBe(propsForkInfo.createMrPath); }); - it('does not render create MR button if user had no permission to Create MR in fork', async () => { - await createComponent({ canUserCreateMrInFork: false }); + it('renders View MR Button with correct path', async () => { + const viewMrPath = 'path/to/view/mr'; + await createComponent({ viewMrPath }); + expect(findViewMrButton().attributes('href')).toBe(viewMrPath); + }); + + it('does not render create MR button if create MR path is blank', async () => { + await createComponent({ createMrPath: '' }); expect(findCreateMrButton().exists()).toBe(false); }); diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js index afa183c0616..a8768e2a825 100644 --- a/spec/frontend/repository/mock_data.js +++ b/spec/frontend/repository/mock_data.js @@ -127,7 +127,6 @@ export const propsForkInfo = { aheadComparePath: '/nataliia/myGitLab/-/compare/main...ref?from_project_id=1', behindComparePath: 'gitlab-org/gitlab/-/compare/ref...main?from_project_id=2', createMrPath: 'path/to/new/mr', - canUserCreateMrInFork: true, }; export const propsConflictsModal = { diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 80cb0ea67da..2e42cd2daad 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -291,7 +291,7 @@ RSpec.describe GitlabSchema.types['Project'] do let_it_be(:project) { create(:project_empty_repo) } it 'raises an error' do - expect(subject['errors'][0]['message']).to eq('UF: You must add at least one file to the ' \ 'repository before using Security features.') diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 7cf2de820fc..c4ab9fb115f 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -1359,11 +1359,16 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do end context 'when fork source is available' do - it 'returns the data related to fork divergence' do - source_project = project_with_repo + let_it_be(:fork_network) { create(:fork_network, root_project: project_with_repo) } + let_it_be(:source_project) { project_with_repo } - allow(helper).to receive(:visible_fork_source).with(project).and_return(source_project) - allow(helper).to receive(:can_user_create_mr_in_fork).with(source_project).and_return(false) + before_all do + project.fork_network = fork_network + project.add_developer(user) + source_project.add_developer(user) + end + + it 'returns the data related to fork divergence' do allow(helper).to receive(:current_user).and_return(user) ahead_path = @@ -1382,9 +1387,44 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do behind_compare_path: behind_path, source_default_branch: source_project.default_branch, create_mr_path: create_mr_path, - can_user_create_mr_in_fork: false + view_mr_path: nil }) end + + it 'returns view_mr_path if a merge request for the branch exists' do + allow(helper).to receive(:current_user).and_return(user) + + merge_request = + create(:merge_request, source_project: project, target_project: project_with_repo, + source_branch: 'ref', target_branch: project_with_repo.default_branch) + + expect(helper.vue_fork_divergence_data(project, 'ref')).to include({ + create_mr_path: nil, + view_mr_path: "/#{source_project.full_path}/-/merge_requests/#{merge_request.iid}" + }) + end + + context 'when a user cannot create a merge request' do + using RSpec::Parameterized::TableSyntax + + where(:project_role, :source_project_role) do + :guest | :developer + :developer | :guest + end + + with_them do + it 'create_mr_path is nil' do + allow(helper).to receive(:current_user).and_return(user) + + project.add_member(user, project_role) + source_project.add_member(user, source_project_role) + + expect(helper.vue_fork_divergence_data(project, 'ref')).to include({ + create_mr_path: nil, view_mr_path: nil + }) + end + end + end end end diff --git a/spec/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings_spec.rb b/spec/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings_spec.rb index 9a90af968e2..b973f9d4350 100644 --- a/spec/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings_spec.rb @@ -139,33 +139,18 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateLinksForVulnerabilityFindings expect { perform_migration }.to change { vulnerability_finding_links.count }.by(2) end + end - context 'when create throws exception ActiveRecord::RecordNotUnique' do - before do - allow(migration).to receive(:create_links).and_raise(ActiveRecord::RecordNotUnique) - end - - it 'does not log this error nor create new records' do - expect(Gitlab::AppLogger).not_to receive(:error) - - expect { perform_migration }.not_to change { vulnerability_finding_links.count } - end + context 'when Gitlab::Json throws exception JSON::ParserError' do + before do + create_finding!(project1.id, scanner1.id, { links: [link_hash] }) + allow(Gitlab::Json).to receive(:parse).and_raise(JSON::ParserError) end - context 'when create throws exception StandardError' do - before do - allow(migration).to receive(:create_links).and_raise(StandardError) - end + it 'does not log this error nor create new records' do + expect(Gitlab::AppLogger).not_to receive(:error) - it 'logs StandardError' do - expect(Gitlab::AppLogger).to receive(:error).with({ - class: described_class.name, message: StandardError.to_s, model_id: finding1.id - }) - expect(Gitlab::AppLogger).to receive(:error).with({ - class: described_class.name, message: StandardError.to_s, model_id: finding2.id - }) - expect { perform_migration }.not_to change { vulnerability_finding_links.count } - end + expect { perform_migration }.not_to change { vulnerability_finding_links.count } end end @@ -181,6 +166,10 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateLinksForVulnerabilityFindings expect { perform_migration }.not_to change { vulnerability_finding_links.count } end + + it 'does not raise ActiveRecord::RecordNotUnique' do + expect { perform_migration }.not_to raise_error + end end private diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index 3a885d70eb4..1fb442a74fb 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -73,9 +73,8 @@ RSpec.describe Gitlab::BareRepositoryImport::Importer do end it 'does not schedule an import' do - expect_next_instance_of(Project) do |instance| - expect(instance).not_to receive(:import_schedule) - end + project = Project.find_by_full_path(project_path) + expect(project).not_to receive(:import_schedule) importer.create_project_if_needed end diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb index 236e04a041b..7ecdc5d25ea 100644 --- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -27,8 +27,8 @@ RSpec.describe Gitlab::BitbucketImport::ProjectCreator do end it 'creates project' do - expect_next_instance_of(Project) do |project| - expect(project).to receive(:add_import_job) + allow_next_instance_of(Project) do |project| + allow(project).to receive(:add_import_job) end project_creator = described_class.new(repo, 'vim', namespace, user, access_params) diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb index 53bf1db3438..59a98987f7d 100644 --- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -24,8 +24,8 @@ RSpec.describe Gitlab::GitlabImport::ProjectCreator do end it 'creates project' do - expect_next_instance_of(Project) do |project| - expect(project).to receive(:add_import_job) + allow_next_instance_of(Project) do |project| + allow(project).to receive(:add_import_job) end project_creator = described_class.new(repo, namespace, user, access_params) diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb index 17ecd183ac9..f1dbe2bf8e2 100644 --- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Gitlab::LegacyGithubImport::ProjectCreator do before do namespace.add_owner(user) - expect_next_instance_of(Project) do |project| + allow_next_instance_of(Project) do |project| allow(project).to receive(:add_import_job) end end diff --git a/spec/lib/gitlab/utils/error_message_spec.rb b/spec/lib/gitlab/utils/error_message_spec.rb index 17786f2c8ef..a6de2520c5e 100644 --- a/spec/lib/gitlab/utils/error_message_spec.rb +++ b/spec/lib/gitlab/utils/error_message_spec.rb @@ -15,14 +15,14 @@ RSpec.describe Gitlab::Utils::ErrorMessage, feature_category: :error_tracking do describe '#to_user_facing' do it 'returns a user-facing error message with the UF prefix' do - expect(described_class.to_user_facing(message)).to eq("UF: #{message}") + expect(described_class.to_user_facing(message)).to eq("UF #{message}") end end describe '#prefixed_error_message' do it 'returns a message with the given prefix' do prefix = 'ERROR' - expect(described_class.prefixed_error_message(message, prefix)).to eq("#{prefix}: #{message}") + expect(described_class.prefixed_error_message(message, prefix)).to eq("#{prefix} #{message}") end end end diff --git a/spec/migrations/20230314144640_reschedule_migration_for_links_spec.rb b/spec/migrations/20230314144640_reschedule_migration_for_links_spec.rb index 45c00416bcc..b28b1ea0730 100644 --- a/spec/migrations/20230314144640_reschedule_migration_for_links_spec.rb +++ b/spec/migrations/20230314144640_reschedule_migration_for_links_spec.rb @@ -10,13 +10,7 @@ RSpec.describe RescheduleMigrationForLinks, :migration, feature_category: :vulne it 'schedules a batched background migration' do migrate! - expect(migration).to have_scheduled_batched_migration( - table_name: :vulnerability_occurrences, - column_name: :id, - interval: described_class::DELAY_INTERVAL, - batch_size: described_class::BATCH_SIZE, - sub_batch_size: described_class::SUB_BATCH_SIZE - ) + expect(migration).not_to have_scheduled_batched_migration end end diff --git a/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb b/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb new file mode 100644 index 00000000000..06eccf03ca4 --- /dev/null +++ b/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe RescheduleLinksAvoidingDuplication, :migration, feature_category: :vulnerability_management do + let(:migration) { described_class::MIGRATION } + + describe '#up' do + it 'schedules a batched background migration' do + migrate! + + expect(migration).to have_scheduled_batched_migration( + table_name: :vulnerability_occurrences, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + end + end + + describe '#down' do + it 'deletes all batched migration records' do + migrate! + schema_migrate_down! + + expect(migration).not_to have_scheduled_batched_migration + end + end +end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 935b9124534..462848bc832 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe GroupPolicy, feature_category: :system_access do include AdminModeHelper include_context 'GroupPolicy context' + using RSpec::Parameterized::TableSyntax context 'public group with no user' do let(:group) { create(:group, :public, :crm_enabled) } @@ -668,6 +669,59 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:create_projects) } end end + + context 'with visibility levels restricted by the administrator' do + let_it_be(:public) { Gitlab::VisibilityLevel::PUBLIC } + let_it_be(:internal) { Gitlab::VisibilityLevel::INTERNAL } + let_it_be(:private) { Gitlab::VisibilityLevel::PRIVATE } + let_it_be(:policy) { :create_projects } + + where(:restricted_visibility_levels, :group_visibility, :can_create_project?) do + [] | ref(:public) | true + [] | ref(:internal) | true + [] | ref(:private) | true + [ref(:public)] | ref(:public) | true + [ref(:public)] | ref(:internal) | true + [ref(:public)] | ref(:private) | true + [ref(:internal)] | ref(:public) | true + [ref(:internal)] | ref(:internal) | true + [ref(:internal)] | ref(:private) | true + [ref(:private)] | ref(:public) | true + [ref(:private)] | ref(:internal) | true + [ref(:private)] | ref(:private) | false + [ref(:public), ref(:internal)] | ref(:public) | true + [ref(:public), ref(:internal)] | ref(:internal) | true + [ref(:public), ref(:internal)] | ref(:private) | true + [ref(:public), ref(:private)] | ref(:public) | true + [ref(:public), ref(:private)] | ref(:internal) | true + [ref(:public), ref(:private)] | ref(:private) | false + [ref(:private), ref(:internal)] | ref(:public) | true + [ref(:private), ref(:internal)] | ref(:internal) | false + [ref(:private), ref(:internal)] | ref(:private) | false + [ref(:public), ref(:internal), ref(:private)] | ref(:public) | false + [ref(:public), ref(:internal), ref(:private)] | ref(:internal) | false + [ref(:public), ref(:internal), ref(:private)] | ref(:private) | false + end + + with_them do + before do + group.update!(visibility_level: group_visibility) + stub_application_setting(restricted_visibility_levels: restricted_visibility_levels) + end + + context 'with non-admin user' do + let(:current_user) { owner } + + it { is_expected.to(can_create_project? ? be_allowed(policy) : be_disallowed(policy)) } + end + + context 'with admin user', :enable_admin_mode do + let(:current_user) { admin } + + it { is_expected.to be_allowed(policy) } + end + end + end end context 'import_projects' do @@ -878,7 +932,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do end end - %w(guest reporter developer maintainer owner).each do |role| + %w[guest reporter developer maintainer owner].each do |role| context role do let(:current_user) { send(role) } @@ -1046,8 +1100,6 @@ RSpec.describe GroupPolicy, feature_category: :system_access do end describe 'observability' do - using RSpec::Parameterized::TableSyntax - let(:allowed_admin) { be_allowed(:read_observability) && be_allowed(:admin_observability) } let(:allowed_read) { be_allowed(:read_observability) && be_disallowed(:admin_observability) } let(:disallowed) { be_disallowed(:read_observability) && be_disallowed(:admin_observability) } @@ -1661,8 +1713,6 @@ RSpec.describe GroupPolicy, feature_category: :system_access do describe 'read_usage_quotas policy' do context 'reading usage quotas' do - using RSpec::Parameterized::TableSyntax - let(:policy) { :read_usage_quotas } where(:role, :admin_mode, :allowed) do diff --git a/spec/requests/api/debian_group_packages_spec.rb b/spec/requests/api/debian_group_packages_spec.rb index 18e5bfd711e..9c726e5a5f7 100644 --- a/spec/requests/api/debian_group_packages_spec.rb +++ b/spec/requests/api/debian_group_packages_spec.rb @@ -18,7 +18,7 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do shared_examples 'not a Debian package tracking event' do include_context 'Debian repository access', :public, :developer, :basic do - it_behaves_like 'not a package tracking event' + it_behaves_like 'not a package tracking event', described_class.name, /.*/ end end diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb index 17a413ed059..030962044c6 100644 --- a/spec/requests/api/debian_project_packages_spec.rb +++ b/spec/requests/api/debian_project_packages_spec.rb @@ -18,7 +18,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d shared_examples 'not a Debian package tracking event' do include_context 'Debian repository access', :public, :developer, :basic do - it_behaves_like 'not a package tracking event' + it_behaves_like 'not a package tracking event', described_class.name, /.*/ end end @@ -180,6 +180,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' } it_behaves_like 'Debian packages write endpoint', 'upload', :created, nil + it_behaves_like 'Debian packages endpoint catching ObjectStorage::RemoteStoreError' it_behaves_like 'a Debian package tracking event', 'push_package' context 'with codename and component' do diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb index e51ea0e2ad6..b2d087819a1 100644 --- a/spec/support/shared_examples/features/packages_shared_examples.rb +++ b/spec/support/shared_examples/features/packages_shared_examples.rb @@ -19,6 +19,10 @@ RSpec.shared_examples 'packages list' do |check_project_name: false| end RSpec.shared_examples 'package details link' do |property| + before do + stub_application_setting(npm_package_requests_forwarding: false) + end + it 'navigates to the correct url' do page.within(packages_table_selector) do click_link package.name diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb index 66554f18e80..bc7ad570441 100644 --- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb @@ -166,6 +166,18 @@ RSpec.shared_examples 'Debian packages write endpoint' do |desired_behavior, suc it_behaves_like 'rejects Debian access with unknown container id', :unauthorized, :basic end +RSpec.shared_examples 'Debian packages endpoint catching ObjectStorage::RemoteStoreError' do + include_context 'Debian repository access', :public, :developer, :basic do + it "returns forbidden" do + expect(::Packages::Debian::CreatePackageFileService).to receive(:new).and_raise ObjectStorage::RemoteStoreError + + subject + + expect(response).to have_gitlab_http_status(:forbidden) + end + end +end + RSpec.shared_examples 'Debian packages index endpoint' do |success_body| it_behaves_like 'Debian packages read endpoint', 'GET', :success, success_body diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb index 1f2450c864b..678c73637ee 100644 --- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb @@ -161,11 +161,11 @@ RSpec.shared_examples 'a package tracking event' do |category, action, service_p end end -RSpec.shared_examples 'not a package tracking event' do +RSpec.shared_examples 'not a package tracking event' do |category, action| it 'does not create a gitlab tracking event', :snowplow, :aggregate_failures do subject - expect_no_snowplow_event + expect_no_snowplow_event category: category, action: action end end diff --git a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb index 094c91f2ab5..21dc3c2bf70 100644 --- a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb +++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb @@ -169,7 +169,7 @@ RSpec.shared_examples_for 'services security ci configuration create service' do it 'returns an error' do expect { result }.to raise_error { |error| expect(error).to be_a(Gitlab::Graphql::Errors::MutationError) - expect(error.message).to eq('UF: You must add at least one file to the repository' \ ' before using Security features.') diff --git a/yarn.lock b/yarn.lock index b55329a7f1b..527cd89721e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1134,10 +1134,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.3.tgz#9ea641146436da388ffbad25d7f2abe0df52c235" integrity sha512-NMV++7Ew1FSBDN1xiZaauU9tfeSfgDHcOLpn+8bGpP+O5orUPm2Eu66R5eC5gkjBPaXosNAxNWtriee+aFk4+g== -"@gitlab/web-ide@0.0.1-dev-20230407181558": - version "0.0.1-dev-20230407181558" - resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20230407181558.tgz#69c5243e8c567ea3ef4879ede68566f99d47705a" - integrity sha512-8YdZxGvXOX3hU/2F1XSwMWFRz56a/QLvY+amne7ie1NRzIVx6SmjKOxot+F1FhSXjc0zbwooGoOn8j6wkgdHCg== +"@gitlab/web-ide@0.0.1-dev-20230418150125": + version "0.0.1-dev-20230418150125" + resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20230418150125.tgz#ea1cb4952c41e19f76465379c96967e98a1217ab" + integrity sha512-rdh8gsvGY6WAl0pGFGRVJIlKSiVY37NimKdOndUcUc62xiZqdT1+4aBmnIk9XjAromURsyFRD6Z9ABNhYx7SBA== "@graphql-eslint/eslint-plugin@3.18.0": version "3.18.0" @@ -1797,10 +1797,10 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@sourcegraph/code-host-integration@0.0.84": - version "0.0.84" - resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.84.tgz#680f5eadde4d05c1dfccb32e14b0a7e58365ebad" - integrity sha512-xHaplY49vBHeXggcc0K3LNQ0VebKy3BwCSokXGxqHDaRJ8JmUwp/DyZX1RFp0Rx/UR6OPXQXKyfVqFcz/UlkUw== +"@sourcegraph/code-host-integration@0.0.91": + version "0.0.91" + resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.91.tgz#2d284ea2ea1023fb3cdaac9aa61a86322d6801ff" + integrity sha512-sC/q64XUDZWoxyAkzpxaw5u2BFNDiPp9K4PybTJFRW1e3VQwul2/iXcUvMf1RzQv4AkQWO8uLeHHG0wRGfCPcA== "@testing-library/dom@^7.16.2": version "7.24.5"