diff options
50 files changed, 531 insertions, 104 deletions
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index e51d2581626..c1074247761 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -14.14.0 +14.15.0 diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index efc2b2ebfb3..b8211bf5928 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -536,10 +536,11 @@ a.gl-badge.badge-warning:active { color: #89888d; } .gl-search-box-by-type-search-icon { - margin: 0.5rem; color: #89888d; width: 1rem; position: absolute; + left: 0.5rem; + top: calc(50% - 16px / 2); } .gl-search-box-by-type { display: flex; diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss index 85512e04808..408292ed7c7 100644 --- a/app/assets/stylesheets/startup/startup-general.scss +++ b/app/assets/stylesheets/startup/startup-general.scss @@ -536,10 +536,11 @@ a.gl-badge.badge-warning:active { color: #737278; } .gl-search-box-by-type-search-icon { - margin: 0.5rem; color: #737278; width: 1rem; position: absolute; + left: 0.5rem; + top: calc(50% - 16px / 2); } .gl-search-box-by-type { display: flex; diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 04787360622..cf638f9b16c 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -84,6 +84,7 @@ class Namespace < ApplicationRecord has_many :timelog_categories, class_name: 'TimeTracking::TimelogCategory' has_many :achievements, class_name: 'Achievements::Achievement' + has_many :namespace_commit_emails, class_name: 'Users::NamespaceCommitEmail' validates :owner, presence: true, if: ->(n) { n.owner_required? } validates :name, diff --git a/app/models/user.rb b/app/models/user.rb index c3ca6af2650..3483d70c228 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -266,7 +266,7 @@ class User < ApplicationRecord has_many :resource_state_events, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent has_many :authored_events, class_name: 'Event', dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent - has_many :namespace_commit_emails + has_many :namespace_commit_emails, class_name: 'Users::NamespaceCommitEmail' has_many :user_achievements, class_name: 'Achievements::UserAchievement', inverse_of: :user has_many :awarded_user_achievements, class_name: 'Achievements::UserAchievement', foreign_key: 'awarded_by_user_id', inverse_of: :awarded_by_user @@ -2184,6 +2184,13 @@ class User < ApplicationRecord public_email.presence || _('[REDACTED]') end + def namespace_commit_email_for_project(project) + return if project.nil? + + namespace_commit_emails.find_by(namespace: project.project_namespace) || + namespace_commit_emails.find_by(namespace: project.root_namespace) + end + protected # override, from Devise::Validatable diff --git a/app/models/users/namespace_commit_email.rb b/app/models/users/namespace_commit_email.rb index 4ec02f12717..883b17187ca 100644 --- a/app/models/users/namespace_commit_email.rb +++ b/app/models/users/namespace_commit_email.rb @@ -9,6 +9,22 @@ module Users validates :user, presence: true validates :namespace, presence: true validates :email, presence: true - validates :user_id, uniqueness: { scope: [:namespace_id] } + validates :user, uniqueness: { scope: :namespace_id } + validate :validate_root_group + + def self.delete_for_namespace(namespace) + where(namespace: namespace).delete_all + end + + private + + def validate_root_group + # Due to the way Rails validations are invoked all at once, + # namespace sometimes won't exist when this is ran even though we have a validation for presence first. + return unless namespace&.group_namespace? + return if namespace.root? + + errors.add(:namespace, _('must be a root group.')) + end end end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 1055f5ff088..8f722de2019 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -9,7 +9,7 @@ module Files git_user = Gitlab::Git::User.from_gitlab(current_user) if current_user.present? - @author_email = params[:author_email] || git_user&.email + @author_email = commit_email(git_user) @author_name = params[:author_name] || git_user&.name @commit_message = params[:commit_message] @last_commit_sha = params[:last_commit_sha] @@ -33,5 +33,18 @@ module Files last_commit.sha != commit_id end + + private + + def commit_email(git_user) + return params[:author_email] if params[:author_email].present? + return unless current_user + + namespace_commit_email = current_user.namespace_commit_email_for_project(@start_project) + + return namespace_commit_email.email.email if namespace_commit_email + + git_user.email + end end end diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb index 6fbf7daeb81..0a9705181ba 100644 --- a/app/services/groups/transfer_service.rb +++ b/app/services/groups/transfer_service.rb @@ -34,6 +34,7 @@ module Groups update_integrations remove_issue_contacts(old_root_ancestor_id, was_root_group) update_crm_objects(was_root_group) + remove_namespace_commit_emails(was_root_group) end post_update_hooks(@updated_project_ids, old_root_ancestor_id) @@ -279,6 +280,10 @@ module Groups Gitlab::EventStore.publish(event) end + + def remove_namespace_commit_emails(was_root_group) + Users::NamespaceCommitEmail.delete_for_namespace(@group) if was_root_group + end end end diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 3eb1f698367..693afdea43a 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -2282,7 +2282,7 @@ :tags: [] - :name: chat_notification :worker_name: ChatNotificationWorker - :feature_category: :chatops + :feature_category: :integrations :has_external_dependencies: true :urgency: :low :resource_boundary: :unknown diff --git a/app/workers/chat_notification_worker.rb b/app/workers/chat_notification_worker.rb index 23d8a1ec29d..4ee32a43808 100644 --- a/app/workers/chat_notification_worker.rb +++ b/app/workers/chat_notification_worker.rb @@ -8,7 +8,7 @@ class ChatNotificationWorker # rubocop:disable Scalability/IdempotentWorker TimeoutExceeded = Class.new(StandardError) sidekiq_options retry: false - feature_category :chatops + feature_category :integrations urgency :low # Can't be high as it has external dependencies weight 2 worker_has_external_dependencies! diff --git a/config/feature_categories.yml b/config/feature_categories.yml index 11be4eb2e70..4282fa32a1b 100644 --- a/config/feature_categories.yml +++ b/config/feature_categories.yml @@ -21,7 +21,6 @@ - billing_and_payments - build - build_artifacts -- chatops - cloud_native_installation - cluster_cost_management - code_quality @@ -35,7 +34,6 @@ - container_scanning - continuous_delivery - continuous_integration -- continuous_integration_scaling - continuous_verification - credential_management - customersdot_application diff --git a/config/feature_flags/development/jira_connect_oauth_self_managed.yml b/config/feature_flags/development/jira_connect_oauth_self_managed.yml index dd7a8b6cd29..02c4b9fa398 100644 --- a/config/feature_flags/development/jira_connect_oauth_self_managed.yml +++ b/config/feature_flags/development/jira_connect_oauth_self_managed.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/359940 milestone: '15.0' type: development group: group::integrations -default_enabled: false +default_enabled: true diff --git a/config/feature_flags/development/new_fonts.yml b/config/feature_flags/development/new_fonts.yml index 20bf283e704..43d22a34bc7 100644 --- a/config/feature_flags/development/new_fonts.yml +++ b/config/feature_flags/development/new_fonts.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/379147 milestone: '15.7' type: development group: group::foundations -default_enabled: false +default_enabled: true diff --git a/data/deprecations/15-8-deprecate-deploy-PAT-with-external-auth.yml b/data/deprecations/15-8-deprecate-deploy-PAT-with-external-auth.yml new file mode 100644 index 00000000000..f1b93f725b0 --- /dev/null +++ b/data/deprecations/15-8-deprecate-deploy-PAT-with-external-auth.yml @@ -0,0 +1,9 @@ +- title: "Limit personal access token and deploy token's access with external authorization" # (required) Actionable title. e.g., The `confidential` field for a `Note` is deprecated. Use `internal` instead. + announcement_milestone: "15.8" # (required) The milestone when this feature was first announced as deprecated. + removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed + breaking_change: true # (required) If this deprecation is a breaking change, set this value to true + reporter: adil.farrukh # (required) GitLab username of the person reporting the deprecation + stage: Manage # (required) String value of the stage that the feature was created in. e.g., Growth + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/387721 # (required) Link to the deprecation issue in GitLab + body: | # (required) Do not modify this line, instead modify the lines below. + With external authorization enabled, personal access tokens (PATs) and deploy tokens must no longer be able to access container or package registries. This defense-in-depth security measure will be deployed in 16.0. For users that use PATs and deploy tokens to access these registries, this measure breaks this use of these tokens. Disable external authorization to use tokens with container or package registries. diff --git a/doc/api/settings.md b/doc/api/settings.md index 4e684b1721e..1fa491ef41c 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -282,6 +282,7 @@ listed in the descriptions of the relevant settings. | `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. | | `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It automatically builds, tests, and deploys applications based on a predefined CI/CD configuration. | | `automatic_purchased_storage_allocation` | boolean | no | Enabling this permits automatic allocation of purchased storage in a namespace. | +| `bulk_import_enabled` | boolean | no | Enable migrating GitLab groups by direct transfer. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/383268) in GitLab 15.8. Requires [`bulk_import_projects` feature flag](../user/group/import/index.md#migrate-groups-by-direct-transfer-recommended) to also migrate projects. | | `can_create_group` | boolean | no | Indicates whether users can create top-level groups. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367754) in GitLab 15.5. Defaults to `true`. | | `check_namespace_plan` **(PREMIUM)** | boolean | no | Enabling this makes only licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public. | | `commit_email_hostname` | string | no | Custom hostname (for private commit emails). | diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md index f774f5bd0cc..fcebe3b3649 100644 --- a/doc/development/documentation/styleguide/word_list.md +++ b/doc/development/documentation/styleguide/word_list.md @@ -313,6 +313,13 @@ Users can set the default branch by using a UI setting. For examples that use the default branch, use `main` instead of [`master`](#master). +## delete + +Use **delete** when an object is completely deleted. **Delete** is the opposite of **create**. + +When the object continues to exist, use [**remove**](#remove) instead. +For example, you can remove an issue from an epic, but the issue still exists. + ## Dependency Proxy Use title case for the GitLab Dependency Proxy. @@ -945,6 +952,12 @@ we would talk to a colleague, and to avoid differentiation between `we` and `the Use **register** instead of **sign up** when talking about creating an account. +## remove + +Use **remove** when an object continues to exist. For example, you can remove an issue from an epic, but the issue still exists. + +When an object is completely deleted, use [**delete**](#delete) instead. + ## Reporter When writing about the Reporter role: diff --git a/doc/development/workspace/index.md b/doc/development/workspace/index.md index b0e7781d889..0e0b6943a0b 100644 --- a/doc/development/workspace/index.md +++ b/doc/development/workspace/index.md @@ -4,19 +4,19 @@ type: index, dev stage: none group: Development info: "See the Technical Writers assigned to Development Guidelines: https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-development-guidelines" -description: "Development Guidelines: learn about workspace when developing GitLab." +description: "Development Guidelines: learn about organization when developing GitLab." --- -# Workspace +# Organization -The [Workspace initiative](../../user/workspace/index.md) focuses on reaching feature parity between +The [Organization initiative](../../user/workspace/index.md) focuses on reaching feature parity between SaaS and self-managed installations. ## Consolidate groups and projects - [Architecture blueprint](../../architecture/blueprints/consolidating_groups_and_projects/index.md) -One facet of the workspace initiative is to consolidate groups and projects, +One facet of the Organization initiative is to consolidate groups and projects, addressing the feature disparity between them. Some features, such as epics, are only available at the group level. Some features, such as issues, are only available at the project level. Other features, such as milestones, are available to both groups @@ -156,4 +156,4 @@ When working on settings, we need to make sure that: - [Consolidating groups and projects](../../architecture/blueprints/consolidating_groups_and_projects/index.md) architecture documentation -- [Workspace user documentation](../../user/workspace/index.md) +- [Organization user documentation](../../user/workspace/index.md) diff --git a/doc/integration/jira/connect-app.md b/doc/integration/jira/connect-app.md index 4ddf538ce73..85c0adc6100 100644 --- a/doc/integration/jira/connect-app.md +++ b/doc/integration/jira/connect-app.md @@ -75,14 +75,7 @@ If the app requires additional permissions, [the update must first be manually a ## Connect the GitLab.com for Jira Cloud app for self-managed instances **(FREE SELF)** -> - Introduced in GitLab 15.6 [with flags](../../administration/feature_flags.md) named [`jira_connect_oauth_self_managed_setting`](https://gitlab.com/gitlab-org/gitlab/-/issues/377679), [`jira_connect_oauth`](https://gitlab.com/gitlab-org/gitlab/-/issues/355048), and [`jira_connect_oauth_self_managed`](https://gitlab.com/gitlab-org/gitlab/-/issues/359940). Disabled by default. -> - Feature flag `jira_connect_oauth_self_managed_setting` [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105070) in GitLab 15.7. - -FLAG: -On self-managed GitLab, by default this feature is not available. To make it available, -ask an administrator to [enable the feature flags](../../administration/feature_flags.md) named -`jira_connect_oauth` and `jira_connect_oauth_self_managed`. On GitLab.com, this feature -is not available. The feature is not ready for production use. +> Introduced in GitLab 15.7 Prerequisites: diff --git a/doc/integration/jira/development_panel.md b/doc/integration/jira/development_panel.md index fc0de843d37..03fea57bcc7 100644 --- a/doc/integration/jira/development_panel.md +++ b/doc/integration/jira/development_panel.md @@ -69,7 +69,7 @@ To simplify administration, we recommend that a GitLab group maintainer or group | Jira usage | GitLab.com customers need | GitLab self-managed customers need | |------------|---------------------------|------------------------------------| -| [Atlassian cloud](https://www.atlassian.com/migration/assess/why-cloud) | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This method offers real-time sync between GitLab.com and Jira. For more information, see [GitLab.com for Jira Cloud app](connect-app.md). | The GitLab.com for Jira Cloud app [using a workaround](connect-app.md#install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances). When the `jira_connect_oauth_self_managed` feature flag is enabled, you can install the app from the [Atlassian Marketplace](https://marketplace.atlassian.com/). For more information, see [Connect the GitLab.com for Jira Cloud app for self-managed instances](connect-app.md#connect-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances). | +| [Atlassian cloud](https://www.atlassian.com/migration/assess/why-cloud) | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This method offers real-time sync between GitLab.com and Jira. For more information, see [GitLab.com for Jira Cloud app](connect-app.md). | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/). For more information, see [Connect the GitLab.com for Jira Cloud app for self-managed instances](connect-app.md#connect-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances). | | Your own server | The [Jira DVCS (distributed version control system) connector](dvcs.md). This syncs data hourly. | The [Jira DVCS (distributed version control system) connector](dvcs.md). This syncs data hourly. | Each GitLab project can be configured to connect to an entire Jira instance. That means after diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index ab93adaabab..60df8262423 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -128,6 +128,20 @@ GitLab has deprecated Dependency Scanning support for Java versions 13, 14, 15, <div class="deprecation removal-160 breaking-change"> +### Limit personal access token and deploy token's access with external authorization + +Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span> + +WARNING: +This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). +Review the details carefully before upgrading. + +With external authorization enabled, personal access tokens (PATs) and deploy tokens must no longer be able to access container or package registries. This defense-in-depth security measure will be deployed in 16.0. For users that use PATs and deploy tokens to access these registries, this measure breaks this use of these tokens. Disable external authorization to use tokens with container or package registries. + +</div> + +<div class="deprecation removal-160 breaking-change"> + ### Owner permissions are required to update Package settings Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span> diff --git a/doc/update/index.md b/doc/update/index.md index a761ffe3630..5f42ed735af 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -277,6 +277,14 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap - Git 2.38.0 and later is required by Gitaly. For installations from source, you should use the [Git version provided by Gitaly](../install/installation.md#git). +### 15.7.2 + +- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.x and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.7.3 or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover. + +### 15.7.1 + +- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.x and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.7.3 or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover. + ### 15.7.0 - This version validates a `NOT NULL DB` constraint on the `issues.work_item_type_id` column. @@ -316,6 +324,23 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap Sites that have configured `max_concurrency` will not be affected by this change. [Read more about the Sidekiq concurrency setting](../administration/sidekiq/extra_sidekiq_processes.md#concurrency). +- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.x and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.7.3 or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover. + +### 15.6.4 + +- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.x and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.7.3 or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover. + +### 15.6.3 + +- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.x and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.7.3 or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover. + +### 15.6.2 + +- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.x and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.7.3 or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover. + +### 15.6.1 + +- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.x and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.7.3 or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover. ### 15.6.0 @@ -335,6 +360,7 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap This issue was [fixed in GitLab 15.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105375) and backported to GitLab 15.6.2. The issue can also be worked around: [read about how to create these indexes](https://gitlab.com/gitlab-org/gitlab/-/issues/378343#note_1199863087). +- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.x and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.7.3 or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover. ### 15.5.0 diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md index 82237d42440..494c87eb58b 100644 --- a/doc/user/admin_area/settings/visibility_and_access_controls.md +++ b/doc/user/admin_area/settings/visibility_and_access_controls.md @@ -195,6 +195,22 @@ To enable the export of 1. Select **Project export enabled**. 1. Select **Save changes**. +## Enable migration of groups and projects by direct transfer + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/383268) in GitLab 15.8. + +You can enable migration of groups by direct transfer. To also migrate projects with the groups, you must enable the +[`bulk_import_projects` feature flag](../../group/import/index.md#migrate-groups-by-direct-transfer-recommended). + +To enable migration of groups by direct transfer: + +1. Sign in to GitLab as a user with Administrator access level. +1. On the top bar, select **Main menu > Admin**. +1. On the left sidebar, select **Settings > General**. +1. Expand the **Visibility and access controls** section. +1. Select **Enable migrating GitLab groups and projects by direct transfer**. +1. Select **Save changes**. + ## Configure enabled Git access protocols With GitLab access restrictions, you can select the protocols users can use to diff --git a/doc/user/group/import/index.md b/doc/user/group/import/index.md index 6050307401d..322b0ba30e7 100644 --- a/doc/user/group/import/index.md +++ b/doc/user/group/import/index.md @@ -26,13 +26,15 @@ If you migrate from GitLab.com to self-managed GitLab, an administrator can crea > - Group items [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/338985) in GitLab 14.3. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267945) in GitLab 14.4 for project resources [with a flag](../../feature_flags.md) named `bulk_import_projects`. Disabled by default. > - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/339941) in GitLab 15.6. +> - New application setting `bulk_import_enabled` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/383268) in GitLab 15.8. `bulk_import` feature flag removed. FLAG: -On self-managed GitLab, by default [migrating group items](#migrated-group-items) is available. To hide the -feature, ask an administrator to [disable the feature flag](../../../administration/feature_flags.md) named `bulk_import`. -On self-managed GitLab, by default [migrating project items](#migrated-project-items) is not available. To show +On self-managed GitLab, by default [migrating group items](#migrated-group-items) is not available. To show the +feature, ask an administrator to [enable it in application settings](../../admin_area/settings/visibility_and_access_controls.md#enable-migration-of-groups-and-projects-by-direct-transfer). +Also on self-managed GitLab, by default [migrating project items](#migrated-project-items) is not available. To show this feature, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named -`bulk_import_projects`. On GitLab.com, migration of both groups and projects is available. +`bulk_import_projects`. The feature is not ready for production use. On GitLab.com, migration of both groups and projects +is available. Prerequisites: diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md index 9490ab151aa..90def3aa773 100644 --- a/doc/user/profile/personal_access_tokens.md +++ b/doc/user/profile/personal_access_tokens.md @@ -115,6 +115,7 @@ A personal access token can perform actions based on the assigned scopes. | `read_registry` | Grants read-only (pull) access to a [Container Registry](../packages/container_registry/index.md) images if a project is private and authorization is required. Available only when the Container Registry is enabled. | | `write_registry` | Grants read-write (push) access to a [Container Registry](../packages/container_registry/index.md) images if a project is private and authorization is required. Available only when the Container Registry is enabled. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28958) in GitLab 12.10.) | | `sudo` | Grants permission to perform API actions as any user in the system, when authenticated as an administrator. | +| `admin_mode` | Grants permission to perform API actions as an administrator, when Admin Mode is enabled. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107875) in GitLab 15.8.) | ## When personal access tokens expire diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md index c905c3e771c..83885a5fc70 100644 --- a/doc/user/project/repository/branches/index.md +++ b/doc/user/project/repository/branches/index.md @@ -119,7 +119,7 @@ The Swap revisions feature allows you to swap the Source and Target revisions. W ![After swap revisions](img/swap_revisions_after_v13_12.png) -## View branches with configured rules **(FREE SELF)** +## View branches with configured protections **(FREE SELF)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88279) in GitLab 15.1 with a flag named `branch_rules`. Disabled by default. diff --git a/lib/api/concerns/packages/nuget_endpoints.rb b/lib/api/concerns/packages/nuget_endpoints.rb index 31ecb529c3c..5f32f0544f4 100644 --- a/lib/api/concerns/packages/nuget_endpoints.rb +++ b/lib/api/concerns/packages/nuget_endpoints.rb @@ -64,7 +64,7 @@ module API tags %w[nuget_packages] end get 'index', format: :json, urgency: :default do - authorize_read_package!(project_or_group) + authorize_packages_access!(project_or_group, required_permission) track_package_event('cli_metadata', :nuget, **snowplow_gitlab_standard_context.merge(category: 'API::NugetPackages')) @@ -78,7 +78,7 @@ module API end namespace '/metadata/*package_name' do after_validation do - authorize_read_package!(project_or_group) + authorize_packages_access!(project_or_group, required_permission) end desc 'The NuGet Metadata Service - Package name level' do @@ -124,7 +124,7 @@ module API end namespace '/query' do after_validation do - authorize_read_package!(project_or_group) + authorize_packages_access!(project_or_group, required_permission) end desc 'The NuGet Search Service' do diff --git a/lib/api/helpers/packages_helpers.rb b/lib/api/helpers/packages_helpers.rb index 8d913268405..1d35c316913 100644 --- a/lib/api/helpers/packages_helpers.rb +++ b/lib/api/helpers/packages_helpers.rb @@ -6,6 +6,7 @@ module API extend ::Gitlab::Utils::Override MAX_PACKAGE_FILE_SIZE = 50.megabytes.freeze + ALLOWED_REQUIRED_PERMISSIONS = %i[read_package read_group].freeze def require_packages_enabled! not_found! unless ::Gitlab.config.packages.enabled @@ -27,9 +28,15 @@ module API authorize!(:destroy_package, subject) end - def authorize_packages_access!(subject = user_project) + def authorize_packages_access!(subject = user_project, required_permission = :read_package) require_packages_enabled! - authorize_read_package!(subject) + return forbidden! unless required_permission.in?(ALLOWED_REQUIRED_PERMISSIONS) + + if required_permission == :read_package + authorize_read_package!(subject) + else + authorize!(required_permission, subject) + end end def authorize_workhorse!(subject: user_project, has_length: true, maximum_size: MAX_PACKAGE_FILE_SIZE) diff --git a/lib/api/nuget_group_packages.rb b/lib/api/nuget_group_packages.rb index c93b24ee544..2afcb915b06 100644 --- a/lib/api/nuget_group_packages.rb +++ b/lib/api/nuget_group_packages.rb @@ -42,6 +42,10 @@ module API def snowplow_gitlab_standard_context { namespace: find_authorized_group! } end + + def required_permission + :read_group + end end params do diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb index aa517661791..8e974cb9cbe 100644 --- a/lib/api/nuget_project_packages.rb +++ b/lib/api/nuget_project_packages.rb @@ -90,6 +90,10 @@ module API created! end + + def required_permission + :read_package + end end params do diff --git a/lib/sidebars/projects/menus/deployments_menu.rb b/lib/sidebars/projects/menus/deployments_menu.rb index bd4ff83a286..4d4e65e9795 100644 --- a/lib/sidebars/projects/menus/deployments_menu.rb +++ b/lib/sidebars/projects/menus/deployments_menu.rb @@ -80,7 +80,9 @@ module Sidebars end def pages_menu_item - return ::Sidebars::NilMenuItem.new(item_id: :pages) unless context.project.pages_available? + unless context.project.pages_available? && context.current_user&.can?(:update_pages, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :pages) + end ::Sidebars::MenuItem.new( title: _('Pages'), diff --git a/lib/sidebars/projects/menus/settings_menu.rb b/lib/sidebars/projects/menus/settings_menu.rb index 1b8b71e1a2b..eea32d8b626 100644 --- a/lib/sidebars/projects/menus/settings_menu.rb +++ b/lib/sidebars/projects/menus/settings_menu.rb @@ -17,7 +17,7 @@ module Sidebars add_item(ci_cd_menu_item) add_item(packages_and_registries_menu_item) - if Feature.disabled?(:show_pages_in_deployments_menu, context.project, type: :experiment) + if Feature.disabled?(:show_pages_in_deployments_menu, context.current_user, type: :experiment) add_item(pages_menu_item) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 24f33c09bcd..75af3d66541 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -50511,6 +50511,9 @@ msgstr "" msgid "must be a boolean value" msgstr "" +msgid "must be a root group." +msgstr "" + msgid "must be a root namespace" msgstr "" diff --git a/package.json b/package.json index 1f57e1aaf33..3b5b39aba9b 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", "@gitlab/fonts": "^1.1.0", - "@gitlab/svgs": "3.14.0", - "@gitlab/ui": "52.7.2", + "@gitlab/svgs": "3.15.0", + "@gitlab/ui": "52.9.0", "@gitlab/visual-review-tools": "1.7.3", "@gitlab/web-ide": "0.0.1-dev-20230102181448", "@rails/actioncable": "6.1.4-7", diff --git a/qa/qa/page/component/invite_members_modal.rb b/qa/qa/page/component/invite_members_modal.rb index 295b5134bd5..eb791594d22 100644 --- a/qa/qa/page/component/invite_members_modal.rb +++ b/qa/qa/page/component/invite_members_modal.rb @@ -41,7 +41,7 @@ module QA click_element :invite_a_group_button end - def add_member(username, access_level = 'Developer') + def add_member(username, access_level = 'Developer', refresh_page: true) open_invite_members_modal within_element(:invite_members_modal_content) do @@ -51,10 +51,10 @@ module QA set_access_level(access_level) end - send_invite + send_invite(refresh_page) end - def invite_group(group_name, access_level = 'Guest') + def invite_group(group_name, access_level = 'Guest', refresh_page: true) open_invite_group_modal within_element(:invite_members_modal_content) do @@ -69,7 +69,13 @@ module QA set_access_level(access_level) end - send_invite + send_invite(refresh_page) + end + + def send_invite(refresh = false) + click_element :invite_button + Support::WaitForRequests.wait_for_requests + page.refresh if refresh end private @@ -78,12 +84,6 @@ module QA # Guest option is selected by default, skipping these steps if desired option is 'Guest' select_element(:access_level_dropdown, access_level) unless access_level == 'Guest' end - - def send_invite - click_element :invite_button - Support::WaitForRequests.wait_for_requests - page.refresh - end end end end diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb index c5b1a4ecea0..5e2a567119d 100644 --- a/qa/qa/resource/group_base.rb +++ b/qa/qa/resource/group_base.rb @@ -16,7 +16,8 @@ module QA :name, :full_path, # Add visibility to enable create private group - :visibility + :visibility, + :shared_with_groups # Get group projects # @@ -140,7 +141,8 @@ module QA :require_two_factor_authentication, :share_with_group_lock, :subgroup_creation_level, - :two_factor_grace_perion + :shared_with_groups, + :two_factor_grace_period # TODO: Add back visibility comparison once https://gitlab.com/gitlab-org/gitlab/-/issues/331252 is fixed # :visibility ) diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb index ea19d9ef332..0081b1c1d46 100644 --- a/qa/qa/support/api.rb +++ b/qa/qa/support/api.rb @@ -144,7 +144,7 @@ module QA end def with_paginated_response_body(url, attempts: 0) - not_ok_error = lambda do |resp| + not_ok_error = ->(resp) do raise "Failed to GET #{masked_url(url)} - (#{resp.code}): `#{resp}`." end @@ -164,7 +164,7 @@ module QA yield parse_body(response) - break if next_page.empty? + break if next_page.blank? url = url.match?(/&page=\d+/) ? url.gsub(/&page=\d+/, "&page=#{next_page}") : "#{url}&page=#{next_page}" end diff --git a/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap b/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap index e0d6fd94b2f..a4af73dd194 100644 --- a/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap +++ b/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap @@ -12,6 +12,7 @@ exports[`Design management design version dropdown component renders design vers toggletext="Showing latest version" variant="default" > + <!----> <!----> @@ -111,12 +112,12 @@ exports[`Design management design version dropdown component renders design vers </gl-listbox-item-stub> <!----> + + <!----> </ul> <!----> - <!----> - </gl-base-dropdown-stub> `; @@ -132,6 +133,7 @@ exports[`Design management design version dropdown component renders design vers toggletext="Showing latest version" variant="default" > + <!----> <!----> @@ -231,11 +233,11 @@ exports[`Design management design version dropdown component renders design vers </gl-listbox-item-stub> <!----> + + <!----> </ul> <!----> - <!----> - </gl-base-dropdown-stub> `; diff --git a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap index 748e151f31b..40e627262db 100644 --- a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap +++ b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap @@ -150,18 +150,12 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = <input aria-label="Search" - class="gl-form-input gl-search-box-by-type-input form-control" + class="gl-form-input form-control gl-search-box-by-type-input" placeholder="Search" type="search" /> - <div - class="gl-search-box-by-type-right-icons" - > - <!----> - - <!----> - </div> + <!----> </div> <li @@ -281,18 +275,12 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = <input aria-label="Search" - class="gl-form-input gl-search-box-by-type-input form-control" + class="gl-form-input form-control gl-search-box-by-type-input" placeholder="Search" type="search" /> - <div - class="gl-search-box-by-type-right-icons" - > - <!----> - - <!----> - </div> + <!----> </div> <li diff --git a/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap b/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap index fb13a9c8f4c..d67f842d011 100644 --- a/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap +++ b/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap @@ -45,6 +45,7 @@ exports[`Code Coverage when fetching data is successful matches the snapshot 1`] toggletext="rspec" variant="default" > + <!----> <!----> @@ -80,12 +81,12 @@ exports[`Code Coverage when fetching data is successful matches the snapshot 1`] </gl-listbox-item-stub> <!----> + + <!----> </ul> <!----> - <!----> - </gl-base-dropdown-stub> </div> diff --git a/spec/lib/api/helpers/packages_helpers_spec.rb b/spec/lib/api/helpers/packages_helpers_spec.rb index d49aec1650d..de9d139a7b6 100644 --- a/spec/lib/api/helpers/packages_helpers_spec.rb +++ b/spec/lib/api/helpers/packages_helpers_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Helpers::PackagesHelpers do +RSpec.describe API::Helpers::PackagesHelpers, feature_category: :package_registry do let_it_be(:helper) { Class.new.include(API::Helpers).include(described_class).new } let_it_be(:project) { create(:project) } let_it_be(:group) { create(:group) } @@ -17,6 +17,31 @@ RSpec.describe API::Helpers::PackagesHelpers do expect(subject).to eq nil end + + context 'with an allowed required permission' do + subject { helper.authorize_packages_access!(project, :read_group) } + + it 'authorizes packages access' do + expect(helper).to receive(:require_packages_enabled!) + expect(helper).not_to receive(:authorize_read_package!) + expect(helper).to receive(:authorize!).with(:read_group, project) + + expect(subject).to eq nil + end + end + + context 'with a not allowed permission' do + subject { helper.authorize_packages_access!(project, :read_permission) } + + it 'rejects packages access' do + expect(helper).to receive(:require_packages_enabled!) + expect(helper).not_to receive(:authorize_read_package!) + expect(helper).not_to receive(:authorize!).with(:test_permission, project) + expect(helper).to receive(:forbidden!) + + expect(subject).to eq nil + end + end end describe 'authorize_read_package!' do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index bdf49227aaa..d063f4713c7 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -35,6 +35,7 @@ RSpec.describe Namespace do it { is_expected.to have_one :cluster_enabled_grant } it { is_expected.to have_many(:work_items) } it { is_expected.to have_many :achievements } + it { is_expected.to have_many(:namespace_commit_emails).class_name('Users::NamespaceCommitEmail') } it do is_expected.to have_one(:ci_cd_settings).class_name('NamespaceCiCdSetting').inverse_of(:namespace).autosave(true) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8e1c0591e0d..590b81b2cbc 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -170,6 +170,7 @@ RSpec.describe User do it { is_expected.to have_many(:awarded_user_achievements).class_name('Achievements::UserAchievement').with_foreign_key('awarded_by_user_id').inverse_of(:awarded_by_user) } it { is_expected.to have_many(:revoked_user_achievements).class_name('Achievements::UserAchievement').with_foreign_key('revoked_by_user_id').inverse_of(:revoked_by_user) } it { is_expected.to have_many(:achievements).through(:user_achievements).class_name('Achievements::Achievement').inverse_of(:users) } + it { is_expected.to have_many(:namespace_commit_emails).class_name('Users::NamespaceCommitEmail') } describe 'default values' do let(:user) { described_class.new } @@ -7415,4 +7416,84 @@ RSpec.describe User do end end end + + describe '#namespace_commit_email_for_project' do + let_it_be(:user) { create(:user) } + + let(:emails) { user.namespace_commit_email_for_project(project) } + + context 'when project is nil' do + let(:project) {} + + it 'returns nil' do + expect(emails).to be(nil) + end + end + + context 'with a group project' do + let_it_be(:root_group) { create(:group) } + let_it_be(:group) { create(:group, parent: root_group) } + let_it_be(:project) { create(:project, group: group) } + + context 'without a defined root group namespace_commit_email' do + context 'without a defined project namespace_commit_email' do + it 'returns nil' do + expect(emails).to be(nil) + end + end + + context 'with a defined project namespace_commit_email' do + it 'returns the defined namespace_commit_email' do + project_commit_email = create(:namespace_commit_email, + user: user, + namespace: project.project_namespace) + + expect(emails).to eq(project_commit_email) + end + end + end + + context 'with a defined root group namespace_commit_email' do + let_it_be(:root_group_commit_email) do + create(:namespace_commit_email, user: user, namespace: root_group) + end + + context 'without a defined project namespace_commit_email' do + it 'returns the defined namespace_commit_email' do + expect(emails).to eq(root_group_commit_email) + end + end + + context 'with a defined project namespace_commit_email' do + it 'returns the defined namespace_commit_email' do + project_commit_email = create(:namespace_commit_email, + user: user, + namespace: project.project_namespace) + + expect(emails).to eq(project_commit_email) + end + end + end + end + + context 'with personal project' do + let_it_be(:project) { create(:project, namespace: user.namespace) } + + context 'without a defined project namespace_commit_email' do + it 'returns nil' do + expect(emails).to be(nil) + end + end + + context 'with a defined project namespace_commit_email' do + it 'returns the defined namespace_commit_email' do + project_commit_email = create(:namespace_commit_email, + user: user, + namespace: project.project_namespace) + + expect(emails).to eq(project_commit_email) + end + end + end + end end diff --git a/spec/models/users/namespace_commit_email_spec.rb b/spec/models/users/namespace_commit_email_spec.rb index 696dac25f9b..23fed85ab3e 100644 --- a/spec/models/users/namespace_commit_email_spec.rb +++ b/spec/models/users/namespace_commit_email_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::NamespaceCommitEmail, type: :model do +RSpec.describe Users::NamespaceCommitEmail, type: :model, feature_category: :source_code_management do subject { build(:namespace_commit_email) } describe 'associations' do @@ -15,7 +15,39 @@ RSpec.describe Users::NamespaceCommitEmail, type: :model do it { is_expected.to validate_presence_of(:user) } it { is_expected.to validate_presence_of(:namespace) } it { is_expected.to validate_presence_of(:email) } + + it { is_expected.to validate_uniqueness_of(:user).scoped_to(:namespace_id) } + + describe 'validate_root_group' do + let_it_be(:root_group) { create(:group) } + + context 'when root group' do + subject { build(:namespace_commit_email, namespace: root_group) } + + it { is_expected.to be_valid } + end + + context 'when subgroup' do + subject { build(:namespace_commit_email, namespace: create(:group, parent: root_group)) } + + it 'is invalid and reports the relevant error' do + expect(subject).to be_invalid + expect(subject.errors[:namespace]).to include('must be a root group.') + end + end + end end it { is_expected.to be_valid } + + describe '.delete_for_namespace' do + let_it_be(:group) { create(:group) } + + it 'deletes all records for namespace' do + create_list(:namespace_commit_email, 3, namespace: group) + create(:namespace_commit_email) + + expect { described_class.delete_for_namespace(group) }.to change { described_class.count }.by(-3) + end + end end diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb index 9de612f7bc7..4335ad75ab6 100644 --- a/spec/requests/api/nuget_group_packages_spec.rb +++ b/spec/requests/api/nuget_group_packages_spec.rb @@ -17,25 +17,51 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do shared_examples 'handling all endpoints' do describe 'GET /api/v4/groups/:id/-/packages/nuget' do - it_behaves_like 'handling nuget service requests', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do + it_behaves_like 'handling nuget service requests', + example_names_with_status: { + anonymous_requests_example_name: 'rejects nuget packages access', + anonymous_requests_status: :unauthorized, + guest_requests_example_name: 'process nuget service index request', + guest_requests_status: :success + } do let(:url) { "/groups/#{target.id}/-/packages/nuget/index.json" } end end describe 'GET /api/v4/groups/:id/-/packages/nuget/metadata/*package_name/index' do - it_behaves_like 'handling nuget metadata requests with package name', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do + it_behaves_like 'handling nuget metadata requests with package name', + example_names_with_status: + { + anonymous_requests_example_name: 'rejects nuget packages access', + anonymous_requests_status: :unauthorized, + guest_requests_example_name: 'rejects nuget packages access', + guest_requests_status: :not_found + } do let(:url) { "/groups/#{target.id}/-/packages/nuget/metadata/#{package_name}/index.json" } end end describe 'GET /api/v4/groups/:id/-/packages/nuget/metadata/*package_name/*package_version' do - it_behaves_like 'handling nuget metadata requests with package name and package version', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do + it_behaves_like 'handling nuget metadata requests with package name and package version', + example_names_with_status: + { + anonymous_requests_example_name: 'rejects nuget packages access', + anonymous_requests_status: :unauthorized, + guest_requests_example_name: 'rejects nuget packages access', + guest_requests_status: :not_found + } do let(:url) { "/groups/#{target.id}/-/packages/nuget/metadata/#{package_name}/#{package.version}.json" } end end describe 'GET /api/v4/groups/:id/-/packages/nuget/query' do - it_behaves_like 'handling nuget search requests', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do + it_behaves_like 'handling nuget search requests', + example_names_with_status: { + anonymous_requests_example_name: 'rejects nuget packages access', + anonymous_requests_status: :unauthorized, + guest_requests_example_name: 'process empty nuget search request', + guest_requests_status: :success + } do let(:url) { "/groups/#{target.id}/-/packages/nuget/query?#{query_parameters.to_query}" } end end @@ -133,13 +159,13 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do describe 'GET /api/v4/groups/:id/-/packages/nuget/metadata/*package_name/index' do let(:url) { "/groups/#{group.id}/-/packages/nuget/metadata/#{package_name}/index.json" } - it_behaves_like 'returning response status', :forbidden + it_behaves_like 'returning response status', :success end describe 'GET /api/v4/groups/:id/-/packages/nuget/metadata/*package_name/*package_version' do let(:url) { "/groups/#{group.id}/-/packages/nuget/metadata/#{package_name}/#{package.version}.json" } - it_behaves_like 'returning response status', :forbidden + it_behaves_like 'returning response status', :success end describe 'GET /api/v4/groups/:id/-/packages/nuget/query' do @@ -150,7 +176,7 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases }.compact } let(:url) { "/groups/#{group.id}/-/packages/nuget/query?#{query_parameters.to_query}" } - it_behaves_like 'returning response status', :forbidden + it_behaves_like 'returning response status', :success end end diff --git a/spec/services/files/base_service_spec.rb b/spec/services/files/base_service_spec.rb new file mode 100644 index 00000000000..57fb378f1a0 --- /dev/null +++ b/spec/services/files/base_service_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Files::BaseService, feature_category: :source_code_management do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:user) { create(:user) } + let(:params) { {} } + + subject(:author_email) { described_class.new(project, user, params).instance_variable_get(:@author_email) } + + before do + group.add_developer(user) + end + + context 'with no namespace_commit_emails' do + it 'sets @author_email to user default email' do + expect(author_email).to eq(user.email) + end + end + + context 'with an author_email in params and namespace_commit_email' do + let(:params) { { author_email: 'email_from_params@example.com' } } + + before do + create(:namespace_commit_email, user: user, namespace: group) + end + + it 'gives precedence to the parameter value for @author_email' do + expect(author_email).to eq('email_from_params@example.com') + end + end + + context 'with a project namespace_commit_email' do + it 'sets @author_email to the project namespace_commit_email' do + namespace_commit_email = create(:namespace_commit_email, user: user, namespace: project.project_namespace) + + expect(author_email).to eq(namespace_commit_email.email.email) + end + end + + context 'with a group namespace_commit_email' do + it 'sets @author_email to the group namespace_commit_email' do + namespace_commit_email = create(:namespace_commit_email, user: user, namespace: group) + + expect(author_email).to eq(namespace_commit_email.email.email) + end + end + + context 'with a project and group namespace_commit_email' do + it 'sets @author_email to the project namespace_commit_email' do + namespace_commit_email = create(:namespace_commit_email, user: user, namespace: project.project_namespace) + create(:namespace_commit_email, user: user, namespace: group) + + expect(author_email).to eq(namespace_commit_email.email.email) + end + end +end diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index 75a1374a248..8939382c023 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -1005,5 +1005,38 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do end end end + + context 'with namespace_commit_emails concerns' do + let_it_be(:group, reload: true) { create(:group) } + let_it_be(:target) { create(:group) } + + before do + group.add_owner(user) + target.add_owner(user) + end + + context 'when origin is a root group' do + before do + create_list(:namespace_commit_email, 2, namespace: group) + end + + it 'deletes all namespace_commit_emails' do + expect { transfer_service.execute(target) } + .to change { group.namespace_commit_emails.count }.by(-2) + end + + it_behaves_like 'publishes a GroupTransferedEvent' + end + + context 'when origin is not a root group' do + let(:group) { create(:group, parent: create(:group)) } + + it 'does not attempt to delete namespace_commit_emails' do + expect(Users::NamespaceCommitEmail).not_to receive(:delete_for_namespace) + + transfer_service.execute(target) + end + end + end end end diff --git a/spec/services/repositories/changelog_service_spec.rb b/spec/services/repositories/changelog_service_spec.rb index 47ebd55022f..42b586637ad 100644 --- a/spec/services/repositories/changelog_service_spec.rb +++ b/spec/services/repositories/changelog_service_spec.rb @@ -79,7 +79,7 @@ RSpec.describe Repositories::ChangelogService do recorder = ActiveRecord::QueryRecorder.new { service.execute(commit_to_changelog: commit_to_changelog) } changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data - expect(recorder.count).to eq(10) + expect(recorder.count).to eq(12) expect(changelog).to include('Title 1', 'Title 2') end diff --git a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb index 290bf58fb6b..17d8b9c7fab 100644 --- a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb @@ -1,6 +1,11 @@ # frozen_string_literal: true -RSpec.shared_examples 'handling nuget service requests' do |anonymous_requests_example_name: 'process nuget service index request', anonymous_requests_status: :success| +RSpec.shared_examples 'handling nuget service requests' do |example_names_with_status: {}| + anonymous_requests_example_name = example_names_with_status.fetch(:anonymous_requests_example_name, 'process nuget service index request') + anonymous_requests_status = example_names_with_status.fetch(:anonymous_requests_status, :success) + guest_requests_example_name = example_names_with_status.fetch(:guest_requests_example_name, 'rejects nuget packages access') + guest_requests_status = example_names_with_status.fetch(:guest_requests_status, :forbidden) + subject { get api(url) } context 'with valid target' do @@ -18,7 +23,7 @@ RSpec.shared_examples 'handling nuget service requests' do |anonymous_requests_e 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :guest | true | true | guest_requests_example_name | guest_requests_status 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found @@ -54,7 +59,7 @@ RSpec.shared_examples 'handling nuget service requests' do |anonymous_requests_e 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :guest | true | true | guest_requests_example_name | guest_requests_status 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found @@ -90,9 +95,14 @@ RSpec.shared_examples 'handling nuget service requests' do |anonymous_requests_e it_behaves_like 'rejects nuget access with invalid target id' end -RSpec.shared_examples 'handling nuget metadata requests with package name' do |anonymous_requests_example_name: 'process nuget metadata request at package name level', anonymous_requests_status: :success| +RSpec.shared_examples 'handling nuget metadata requests with package name' do |example_names_with_status: {}| include_context 'with expected presenters dependency groups' + anonymous_requests_example_name = example_names_with_status.fetch(:anonymous_requests_example_name, 'process nuget metadata request at package name level') + anonymous_requests_status = example_names_with_status.fetch(:anonymous_requests_status, :success) + guest_requests_example_name = example_names_with_status.fetch(:guest_requests_example_name, 'rejects nuget packages access') + guest_requests_status = example_names_with_status.fetch(:guest_requests_status, :forbidden) + let_it_be(:package_name) { 'Dummy.Package' } let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) } let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } } @@ -117,7 +127,7 @@ RSpec.shared_examples 'handling nuget metadata requests with package name' do |a 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :guest | true | true | guest_requests_example_name | guest_requests_status 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found @@ -152,9 +162,14 @@ RSpec.shared_examples 'handling nuget metadata requests with package name' do |a end end -RSpec.shared_examples 'handling nuget metadata requests with package name and package version' do |anonymous_requests_example_name: 'process nuget metadata request at package name and package version level', anonymous_requests_status: :success| +RSpec.shared_examples 'handling nuget metadata requests with package name and package version' do |example_names_with_status: {}| include_context 'with expected presenters dependency groups' + anonymous_requests_example_name = example_names_with_status.fetch(:anonymous_requests_example_name, 'process nuget metadata request at package name and package version level') + anonymous_requests_status = example_names_with_status.fetch(:anonymous_requests_status, :success) + guest_requests_example_name = example_names_with_status.fetch(:guest_requests_example_name, 'rejects nuget packages access') + guest_requests_status = example_names_with_status.fetch(:guest_requests_status, :forbidden) + let_it_be(:package_name) { 'Dummy.Package' } let_it_be(:package) { create(:nuget_package, :with_metadatum, name: package_name, project: project) } let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') } @@ -179,7 +194,7 @@ RSpec.shared_examples 'handling nuget metadata requests with package name and pa 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :guest | true | true | guest_requests_example_name | guest_requests_status 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found @@ -214,7 +229,12 @@ RSpec.shared_examples 'handling nuget metadata requests with package name and pa it_behaves_like 'rejects nuget access with invalid target id' end -RSpec.shared_examples 'handling nuget search requests' do |anonymous_requests_example_name: 'process nuget search request', anonymous_requests_status: :success| +RSpec.shared_examples 'handling nuget search requests' do |example_names_with_status: {}| + anonymous_requests_example_name = example_names_with_status.fetch(:anonymous_requests_example_name, 'process nuget search request') + anonymous_requests_status = example_names_with_status.fetch(:anonymous_requests_status, :success) + guest_requests_example_name = example_names_with_status.fetch(:guest_requests_example_name, 'rejects nuget packages access') + guest_requests_status = example_names_with_status.fetch(:guest_requests_status, :forbidden) + let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) } let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') } let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) } @@ -244,7 +264,7 @@ RSpec.shared_examples 'handling nuget search requests' do |anonymous_requests_ex 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status 'PRIVATE' | :developer | true | true | 'process nuget search request' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :guest | true | true | guest_requests_example_name | guest_requests_status 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found 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 bace570e47a..3abe545db59 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 @@ -379,6 +379,26 @@ RSpec.shared_examples 'process nuget search request' do |user_type, status, add_ end end +RSpec.shared_examples 'process empty nuget search request' do |user_type, status, add_member = true| + before do + target.send("add_#{user_type}", user) if add_member && user_type != :anonymous + end + + it_behaves_like 'returning response status', status + + it 'returns a valid json response' do + subject + + expect(response.media_type).to eq('application/json') + expect(json_response).to be_a(Hash) + expect(json_response).to match_schema('public_api/v4/packages/nuget/search') + expect(json_response['totalHits']).to eq(0) + expect(json_response['data'].map { |e| e['versions'].size }).to be_empty + end + + it_behaves_like 'a package tracking event', 'API::NugetPackages', 'search_package' +end + RSpec.shared_examples 'rejects nuget access with invalid target id' do context 'with a target id with invalid integers' do using RSpec::Parameterized::TableSyntax diff --git a/yarn.lock b/yarn.lock index f205afc1d37..5acbb9919a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1140,15 +1140,15 @@ stylelint-declaration-strict-value "1.8.0" stylelint-scss "4.2.0" -"@gitlab/svgs@3.14.0": - version "3.14.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.14.0.tgz#b32a673f08bbd5ba6d406bcf3abb6e7276271b6c" - integrity sha512-mQYtW9eGHY7cF6elsWd76hUF7F3NznyzrJJy5eXBHjvRdYBtyHmwkVmh1Cwr3S/2Sl8fPC+qk41a+Nm6n+1mRQ== - -"@gitlab/ui@52.7.2": - version "52.7.2" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-52.7.2.tgz#91d5709dbd964c3bc9af48f2a4bad6b69e37ecb0" - integrity sha512-YGg6UoaqNJ2OG5BdJewb5ov58GHaQjjCoNuRpwKuiXu+gE90yt8brRl2jYQTthU0lxS2UtHveU22KcP6ZbZJ6w== +"@gitlab/svgs@3.15.0": + version "3.15.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.15.0.tgz#b7284584d1a60093334037d16e0d11b05fb4da38" + integrity sha512-l3kWuE/s+UuchouQwKnYtONuTUQtaPHkcbacRSpFEfRcw8FXknlLjUpkO1nfxFaH10jE76KYJNoFad4x74+6nQ== + +"@gitlab/ui@52.9.0": + version "52.9.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-52.9.0.tgz#940fb670570b8e4aaeffd02637273785bd5115b6" + integrity sha512-HBBcLtYGgg/ZuBxV9TOl4jc0avugsqKkBQRgV3iw06Uux/8fIl346bEfUjsLNh8rk7ccp8nKb2JozXCjlKs1Qw== dependencies: "@popperjs/core" "^2.11.2" bootstrap-vue "2.20.1" |