diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index 1120d9e8a14..8026dfc4357 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -2398,7 +2398,6 @@ RSpec/ContextWording:
- 'spec/requests/api/usage_data_spec.rb'
- 'spec/requests/api/users_preferences_spec.rb'
- 'spec/requests/api/users_spec.rb'
- - 'spec/requests/content_security_policy_spec.rb'
- 'spec/requests/dashboard/projects_controller_spec.rb'
- 'spec/requests/dashboard_controller_spec.rb'
- 'spec/requests/git_http_spec.rb'
diff --git a/app/assets/javascripts/group_settings/constants.js b/app/assets/javascripts/group_settings/constants.js
index d4ac7d94bf4..3b595bac686 100644
--- a/app/assets/javascripts/group_settings/constants.js
+++ b/app/assets/javascripts/group_settings/constants.js
@@ -1,7 +1,7 @@
import { __, s__ } from '~/locale';
export const I18N_CONFIRM_MESSAGE = s__(
- 'Runners|Shared runners will be disabled for all projects and subgroups in this group. If you proceed, you must manually re-enable shared runners in the settings of each project and subgroup.',
+ 'Runners|Shared runners will be disabled for all projects and subgroups in this group.',
);
export const I18N_CONFIRM_OK = s__('Runners|Yes, disable shared runners');
export const I18N_CONFIRM_CANCEL = s__('Runners|No, keep shared runners enabled');
diff --git a/app/assets/javascripts/projects/components/shared/delete_modal.vue b/app/assets/javascripts/projects/components/shared/delete_modal.vue
index 44e29d00d45..db2e283f9d2 100644
--- a/app/assets/javascripts/projects/components/shared/delete_modal.vue
+++ b/app/assets/javascripts/projects/components/shared/delete_modal.vue
@@ -74,7 +74,7 @@ export default {
attributes: {
variant: 'danger',
disabled: this.confirmDisabled,
- 'data-qa-selector': 'confirm_delete_button',
+ 'data-testid': 'confirm-delete-button',
},
},
cancel: {
@@ -147,7 +147,7 @@ export default {
v-model="userInput"
name="confirm_name_input"
type="text"
- data-qa-selector="confirm_name_field"
+ data-testid="confirm-name-field"
/>
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/custom_email.vue b/app/assets/javascripts/projects/settings_service_desk/components/custom_email.vue
index f7a9949db4b..6d5443a5df0 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/custom_email.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/custom_email.vue
@@ -25,6 +25,10 @@ export default {
I18N_STATE_VERIFICATION_FINISHED_TOGGLE_HELP,
I18N_RESET_BUTTON_LABEL,
props: {
+ incomingEmail: {
+ type: String,
+ required: true,
+ },
customEmail: {
type: String,
required: true,
@@ -128,7 +132,13 @@ export default {
{{ errorLabel }}
- {{ errorDescription }}
+
+
+
+ {{ incomingEmail }}
+
+
+
{{ resetNote }}
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/custom_email_wrapper.vue b/app/assets/javascripts/projects/settings_service_desk/components/custom_email_wrapper.vue
index f72aa19bdf2..2fe3ea4215a 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/custom_email_wrapper.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/custom_email_wrapper.vue
@@ -216,6 +216,7 @@ export default {
item.value === value).text;
- this.$emit('input', {
- value: this.selectedValue,
- text: this.selectedText,
- });
- },
- get() {
- return this.selectedValue;
- },
+ selectedItem() {
+ const item = this.items.find(({ value }) => value === this.selected);
+
+ return item || this.initialSelectedItem;
},
toggleText() {
- return this.selectedText ?? this.defaultToggleText;
+ return this.selectedItem?.text ?? this.defaultToggleText;
},
resetButtonLabel() {
return this.clearable ? RESET_LABEL : '';
},
- inputValue() {
- return this.selectedValue ? this.selectedValue : '';
- },
isSearchQueryTooShort() {
return this.searchString && this.searchString.length < MINIMUM_QUERY_LENGTH;
},
@@ -115,8 +103,13 @@ export default {
: this.$options.i18n.noResultsText;
},
},
+ watch: {
+ selected() {
+ this.$emit('input', this.selectedItem);
+ },
+ },
created() {
- this.fetchInitialSelection();
+ this.getInitialSelection();
},
methods: {
search: debounce(function debouncedSearch(searchString) {
@@ -148,23 +141,20 @@ export default {
this.searching = false;
this.infiniteScrollLoading = false;
},
- async fetchInitialSelection() {
+ async getInitialSelection() {
if (!this.initialSelection) {
this.pristine = false;
return;
}
- if (!this.fetchInitialSelectionText) {
+ if (!this.fetchInitialSelection) {
throw new Error(
'`initialSelection` is provided but lacks `fetchInitialSelectionText` to retrieve the corresponding text',
);
}
this.searching = true;
- const name = await this.fetchInitialSelectionText(this.initialSelection);
-
- this.selectedValue = this.initialSelection;
- this.selectedText = name;
+ this.initialSelectedItem = await this.fetchInitialSelection(this.initialSelection);
this.pristine = false;
this.searching = false;
},
@@ -218,6 +208,6 @@ export default {
-
+
diff --git a/app/assets/javascripts/vue_shared/components/entity_select/group_select.vue b/app/assets/javascripts/vue_shared/components/entity_select/group_select.vue
index 8a338551fbe..da42c017541 100644
--- a/app/assets/javascripts/vue_shared/components/entity_select/group_select.vue
+++ b/app/assets/javascripts/vue_shared/components/entity_select/group_select.vue
@@ -76,11 +76,7 @@ export default {
try {
const url = groupsPath(this.groupsFilter, this.parentGroupID);
const { data = [], headers } = await axios.get(url, { params });
- groups = data.map((group) => ({
- ...group,
- text: group.full_name,
- value: String(group.id),
- }));
+ groups = data.map((group) => this.mapGroupData(group));
totalPages = parseIntPagination(normalizeHeaders(headers)).totalPages;
} catch (error) {
@@ -88,15 +84,19 @@ export default {
}
return { items: groups, totalPages };
},
- async fetchGroupName(groupId) {
- let groupName = '';
+ async fetchInitialGroup(groupId) {
try {
const group = await Api.group(groupId);
- groupName = group.full_name;
+
+ return this.mapGroupData(group);
} catch (error) {
this.handleError({ message: FETCH_GROUP_ERROR, error });
+
+ return {};
}
- return groupName;
+ },
+ mapGroupData(group) {
+ return { ...group, text: group.full_name, value: String(group.id) };
},
handleError({ message, error }) {
Sentry.captureException(error);
@@ -123,7 +123,7 @@ export default {
:header-text="$options.i18n.selectGroup"
:default-toggle-text="$options.i18n.toggleText"
:fetch-items="fetchGroups"
- :fetch-initial-selection-text="fetchGroupName"
+ :fetch-initial-selection="fetchInitialGroup"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/vue_shared/components/entity_select/organization_select.vue b/app/assets/javascripts/vue_shared/components/entity_select/organization_select.vue
index c509c5715dc..9f4671abbb1 100644
--- a/app/assets/javascripts/vue_shared/components/entity_select/organization_select.vue
+++ b/app/assets/javascripts/vue_shared/components/entity_select/organization_select.vue
@@ -73,25 +73,16 @@ export default {
}
try {
- const {
- data: {
- currentUser: {
- organizations: { nodes, pageInfo },
- },
- },
- } = await this.$apollo.query({
+ const response = await this.$apollo.query({
query: getCurrentUserOrganizationsQuery,
// TODO: implement search support - https://gitlab.com/gitlab-org/gitlab/-/issues/433954.
variables: { after: this.endCursor, first: DEFAULT_PER_PAGE },
});
-
+ const { nodes, pageInfo } = response.data.currentUser.organizations;
this.endCursor = pageInfo.endCursor;
return {
- items: nodes.map((organization) => ({
- text: organization.name,
- value: getIdFromGraphQLId(organization.id),
- })),
+ items: nodes.map((organization) => this.mapOrganizationData(organization)),
// `EntitySelect` expects a `totalPages` key but GraphQL requests don't provide this data
// because it uses keyset pagination. Since the dropdown uses infinite scroll it
// only needs to know if there is a next page. We pass `page + 1` if there is a next page,
@@ -105,24 +96,27 @@ export default {
return { items: [], totalPages: 0 };
}
},
- async fetchOrganizationName(id) {
+ async fetchInitialOrganization(id) {
try {
- const {
- data: {
- organization: { name },
- },
- } = await this.$apollo.query({
+ const response = await this.$apollo.query({
query: getOrganizationQuery,
variables: { id: convertToGraphQLId(TYPE_ORGANIZATION, id) },
});
- return name;
+ return this.mapOrganizationData(response.data.organization);
} catch (error) {
this.handleError({ message: FETCH_ORGANIZATION_ERROR, error });
- return '';
+ return {};
}
},
+ mapOrganizationData(organization) {
+ return {
+ ...organization,
+ text: organization.name,
+ value: getIdFromGraphQLId(organization.id),
+ };
+ },
handleError({ message, error }) {
Sentry.captureException(error);
this.errorMessage = message;
@@ -150,7 +144,7 @@ export default {
:header-text="$options.i18n.selectGroup"
:default-toggle-text="$options.i18n.toggleText"
:fetch-items="fetchOrganizations"
- :fetch-initial-selection-text="fetchOrganizationName"
+ :fetch-initial-selection="fetchInitialOrganization"
:toggle-class="toggleClass"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/vue_shared/components/entity_select/project_select.vue b/app/assets/javascripts/vue_shared/components/entity_select/project_select.vue
index 8c371e3d4ce..8c873d39496 100644
--- a/app/assets/javascripts/vue_shared/components/entity_select/project_select.vue
+++ b/app/assets/javascripts/vue_shared/components/entity_select/project_select.vue
@@ -120,24 +120,29 @@ export default {
membership: this.membership,
});
})();
- projects = data.map((item) => ({
- text: item.name_with_namespace || item.name,
- value: String(item.id),
- }));
+ projects = data.map((project) => this.mapProjectData(project));
} catch (error) {
this.handleError({ message: FETCH_PROJECTS_ERROR, error });
}
return { items: projects, totalPages: 1 };
},
- async fetchProjectName(projectId) {
- let projectName = '';
+ async fetchInitialProject(projectId) {
try {
- const { data: project } = await Api.project(projectId);
- projectName = project.name_with_namespace;
+ const response = await Api.project(projectId);
+
+ return this.mapProjectData(response.data);
} catch (error) {
this.handleError({ message: FETCH_PROJECT_ERROR, error });
+
+ return {};
}
- return projectName;
+ },
+ mapProjectData(project) {
+ return {
+ ...project,
+ text: project.name_with_namespace || project.name,
+ value: String(project.id),
+ };
},
handleError({ message, error }) {
Sentry.captureException(error);
@@ -163,7 +168,7 @@ export default {
:header-text="$options.i18n.selectProject"
:default-toggle-text="$options.i18n.searchForProject"
:fetch-items="fetchProjects"
- :fetch-initial-selection-text="fetchProjectName"
+ :fetch-initial-selection="fetchInitialProject"
:block="block"
clearable
v-on="$listeners"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
index dd0a26c0b9c..1e323d99c93 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
@@ -201,10 +201,12 @@ export default {
},
},
i18n: {
- title: s__('WorkItem|Tasks'),
- fetchError: s__('WorkItem|Something went wrong when fetching tasks. Please refresh this page.'),
+ title: s__('WorkItem|Child items'),
+ fetchError: s__(
+ 'WorkItem|Something went wrong when fetching child items. Please refresh this page.',
+ ),
emptyStateMessage: s__(
- 'WorkItem|No tasks are currently assigned. Use tasks to break down this issue into smaller parts.',
+ 'WorkItem|No child items are currently assigned. Use child items to break down this issue into smaller parts.',
),
addChildButtonLabel: s__('WorkItem|Add'),
addChildOptionLabel: s__('WorkItem|Existing task'),
diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb
index bdd9d00ca7f..0bb9ed2fe2f 100644
--- a/app/channels/application_cable/connection.rb
+++ b/app/channels/application_cable/connection.rb
@@ -3,13 +3,14 @@
module ApplicationCable
class Connection < ActionCable::Connection::Base
include Logging
+ include Gitlab::Auth::AuthFinders
identified_by :current_user
public :request
def connect
- self.current_user = find_user_from_session_store
+ self.current_user = find_user_from_bearer_token || find_user_from_session_store
end
private
diff --git a/app/controllers/acme_challenges_controller.rb b/app/controllers/acme_challenges_controller.rb
index 4a7706db94e..a187e43b3df 100644
--- a/app/controllers/acme_challenges_controller.rb
+++ b/app/controllers/acme_challenges_controller.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-# rubocop:disable Rails/ApplicationController
-class AcmeChallengesController < ActionController::Base
+class AcmeChallengesController < BaseActionController
def show
if acme_order
render plain: acme_order.challenge_file_content, content_type: 'text/plain'
@@ -16,4 +15,3 @@ class AcmeChallengesController < ActionController::Base
@acme_order ||= PagesDomainAcmeOrder.find_by_domain_and_token(params[:domain], params[:token])
end
end
-# rubocop:enable Rails/ApplicationController
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index f4d9d616851..8156cf8e165 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -3,7 +3,7 @@
require 'gon'
require 'fogbugz'
-class ApplicationController < ActionController::Base
+class ApplicationController < BaseActionController
include Gitlab::GonHelper
include Gitlab::NoCacheHeaders
include GitlabRoutingHelper
@@ -25,7 +25,6 @@ class ApplicationController < ActionController::Base
include FlocOptOut
include CheckRateLimit
include RequestPayloadLogger
- extend ContentSecurityPolicyPatch
before_action :limit_session_time, if: -> { !current_user }
before_action :authenticate_user!, except: [:route_not_found]
@@ -113,33 +112,6 @@ class ApplicationController < ActionController::Base
render plain: e.message, status: :service_unavailable
end
- content_security_policy do |p|
- next if p.directives.blank?
-
- if helpers.vite_enabled?
- vite_host = ViteRuby.instance.config.host
- vite_port = ViteRuby.instance.config.port
- vite_origin = "#{vite_host}:#{vite_port}"
- http_origin = "http://#{vite_origin}"
- ws_origin = "ws://#{vite_origin}"
- wss_origin = "wss://#{vite_origin}"
- gitlab_ws_origin = Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'vite-dev/')
- http_path = Gitlab::Utils.append_path(http_origin, 'vite-dev/')
-
- connect_sources = p.directives['connect-src']
- p.connect_src(*(Array.wrap(connect_sources) | [ws_origin, wss_origin, http_path]))
-
- worker_sources = p.directives['worker-src']
- p.worker_src(*(Array.wrap(worker_sources) | [gitlab_ws_origin, http_path]))
- end
-
- next unless Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank?
-
- default_connect_src = p.directives['connect-src'] || p.directives['default-src']
- connect_src_values = Array.wrap(default_connect_src) | [Gitlab::CurrentSettings.snowplow_collector_hostname]
- p.connect_src(*connect_src_values)
- end
-
def redirect_back_or_default(default: root_path, options: {})
redirect_back(fallback_location: default, **options)
end
diff --git a/app/controllers/base_action_controller.rb b/app/controllers/base_action_controller.rb
new file mode 100644
index 00000000000..05ba00426c2
--- /dev/null
+++ b/app/controllers/base_action_controller.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+# GitLab lightweight base action controller
+#
+# This class should be limited to content that
+# is desired/required for *all* controllers in
+# GitLab.
+#
+# Most controllers inherit from `ApplicationController`.
+# Some controllers don't want or need all of that
+# logic and instead inherit from `ActionController::Base`.
+# This makes it difficult to set security headers and
+# handle other critical logic across *all* controllers.
+#
+# Between this controller and `ApplicationController`
+# no controller should ever inherit directly from
+# `ActionController::Base`
+#
+# rubocop:disable Rails/ApplicationController -- This class is specifically meant as a base class for controllers that
+# don't inherit from ApplicationController
+# rubocop:disable Gitlab/NamespacedClass -- Base controllers live in the global namespace
+class BaseActionController < ActionController::Base
+ extend ContentSecurityPolicyPatch
+
+ content_security_policy do |p|
+ next if p.directives.blank?
+
+ if helpers.vite_enabled?
+ vite_host = ViteRuby.instance.config.host
+ vite_port = ViteRuby.instance.config.port
+ vite_origin = "#{vite_host}:#{vite_port}"
+ http_origin = "http://#{vite_origin}"
+ ws_origin = "ws://#{vite_origin}"
+ wss_origin = "wss://#{vite_origin}"
+ gitlab_ws_origin = Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'vite-dev/')
+ http_path = Gitlab::Utils.append_path(http_origin, 'vite-dev/')
+
+ connect_sources = p.directives['connect-src']
+ p.connect_src(*(Array.wrap(connect_sources) | [ws_origin, wss_origin, http_path]))
+
+ worker_sources = p.directives['worker-src']
+ p.worker_src(*(Array.wrap(worker_sources) | [gitlab_ws_origin, http_path]))
+ end
+
+ next unless Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank?
+
+ default_connect_src = p.directives['connect-src'] || p.directives['default-src']
+ connect_src_values = Array.wrap(default_connect_src) | [Gitlab::CurrentSettings.snowplow_collector_hostname]
+ p.connect_src(*connect_src_values)
+ end
+end
+# rubocop:enable Gitlab/NamespacedClass
+# rubocop:enable Rails/ApplicationController
diff --git a/app/controllers/chaos_controller.rb b/app/controllers/chaos_controller.rb
index 7328b793b09..b61a8c5ff12 100644
--- a/app/controllers/chaos_controller.rb
+++ b/app/controllers/chaos_controller.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-# rubocop:disable Rails/ApplicationController
-class ChaosController < ActionController::Base
+class ChaosController < BaseActionController
before_action :validate_chaos_secret, unless: :development_or_test?
def leakmem
@@ -95,4 +94,3 @@ class ChaosController < ActionController::Base
Rails.env.development? || Rails.env.test?
end
end
-# rubocop:enable Rails/ApplicationController
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
index 1381999ab4c..2b2db2f950c 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-# rubocop:disable Rails/ApplicationController
-class HealthController < ActionController::Base
+class HealthController < BaseActionController
protect_from_forgery with: :exception, prepend: true
include RequiresAllowlistedMonitoringClient
@@ -40,4 +39,3 @@ class HealthController < ActionController::Base
render json: result.json, status: result.http_status
end
end
-# rubocop:enable Rails/ApplicationController
diff --git a/app/controllers/metrics_controller.rb b/app/controllers/metrics_controller.rb
index 9f41c092fa0..61851fd1c60 100644
--- a/app/controllers/metrics_controller.rb
+++ b/app/controllers/metrics_controller.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-# rubocop:disable Rails/ApplicationController
-class MetricsController < ActionController::Base
+class MetricsController < BaseActionController
include RequiresAllowlistedMonitoringClient
protect_from_forgery with: :exception, prepend: true
@@ -36,4 +35,3 @@ class MetricsController < ActionController::Base
)
end
end
-# rubocop:enable Rails/ApplicationController
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index c21b2aae749..a2a850ae05f 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -40,13 +40,10 @@ module ApplicationSettingsHelper
def storage_weights
# Instead of using a `Struct` we could wrap this into an object.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/358419
- weights = Struct.new(*Gitlab.config.repositories.storages.keys.map(&:to_sym))
+ storages_weighted = @application_setting.repository_storages_with_default_weight
- values = Gitlab.config.repositories.storages.keys.map do |storage|
- @application_setting.repository_storages_weighted[storage] || 0
- end
-
- weights.new(*values)
+ weights = Struct.new(*storages_weighted.keys.map(&:to_sym))
+ weights.new(*storages_weighted.values)
end
def all_protocols_enabled?
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 72cb6149bea..2ffad4498bf 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -272,6 +272,10 @@ class NotifyPreview < ActionMailer::Preview
end
end
+ def service_desk_verification_result_email_for_incorrect_forwarding_target_error
+ service_desk_verification_result_email_for_error_state(error: :incorrect_forwarding_target)
+ end
+
def service_desk_verification_result_email_for_read_timeout_error
service_desk_verification_result_email_for_error_state(error: :read_timeout)
end
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 18db6ee3ba4..851b65055d0 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -571,6 +571,16 @@ module ApplicationSettingImplementation
end
end
+ def repository_storages_with_default_weight
+ # config file config/gitlab.yml becomes SSOT for this API
+ # see https://gitlab.com/gitlab-org/gitlab/-/issues/426091#note_1675160909
+ storages_map = Gitlab.config.repositories.storages.keys.map do |storage|
+ [storage, repository_storages_weighted[storage] || 0]
+ end
+
+ Hash[storages_map]
+ end
+
private
def set_max_key_restriction!(key_type)
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
index 9c7b3d6ab3d..3ca7337dd53 100644
--- a/app/models/packages/package.rb
+++ b/app/models/packages/package.rb
@@ -367,6 +367,12 @@ class Packages::Package < ApplicationRecord
::Packages::Maven::Metadata::SyncWorker.perform_async(user.id, project_id, name)
end
+ def sync_npm_metadata_cache
+ return unless npm?
+
+ ::Packages::Npm::CreateMetadataCacheWorker.perform_async(project_id, name)
+ end
+
def create_build_infos!(build)
return unless build&.pipeline
diff --git a/app/models/service_desk/custom_email_verification.rb b/app/models/service_desk/custom_email_verification.rb
index 67192f8434b..a03c984c3a6 100644
--- a/app/models/service_desk/custom_email_verification.rb
+++ b/app/models/service_desk/custom_email_verification.rb
@@ -11,7 +11,8 @@ module ServiceDesk
mail_not_received_within_timeframe: 2,
invalid_credentials: 3,
smtp_host_issue: 4,
- read_timeout: 5
+ read_timeout: 5,
+ incorrect_forwarding_target: 6
}
attr_encrypted :token,
diff --git a/app/services/packages/mark_package_for_destruction_service.rb b/app/services/packages/mark_package_for_destruction_service.rb
index 8ccc242ae36..b41f1c0a291 100644
--- a/app/services/packages/mark_package_for_destruction_service.rb
+++ b/app/services/packages/mark_package_for_destruction_service.rb
@@ -11,6 +11,7 @@ module Packages
package.mark_package_files_for_destruction
package.sync_maven_metadata(current_user)
+ package.sync_npm_metadata_cache
service_response_success('Package was successfully marked as pending destruction')
rescue StandardError => e
diff --git a/app/services/packages/mark_packages_for_destruction_service.rb b/app/services/packages/mark_packages_for_destruction_service.rb
index ade9ad2c974..2c81a52ea24 100644
--- a/app/services/packages/mark_packages_for_destruction_service.rb
+++ b/app/services/packages/mark_packages_for_destruction_service.rb
@@ -43,6 +43,7 @@ module Packages
.update_all(status: :pending_destruction)
sync_maven_metadata(loaded_packages)
+ sync_npm_metadata(loaded_packages)
mark_package_files_for_destruction(loaded_packages)
end
@@ -73,6 +74,15 @@ module Packages
)
end
+ def sync_npm_metadata(packages)
+ npm_packages = packages.select(&:npm?)
+ ::Packages::Npm::CreateMetadataCacheWorker.bulk_perform_async_with_contexts(
+ npm_packages,
+ arguments_proc: -> (package) { [package.project_id, package.name] },
+ context_proc: -> (package) { { project: package.project, user: @current_user } }
+ )
+ end
+
def can_destroy_packages?(packages)
packages.all? do |package|
can?(@current_user, :destroy_package, package)
diff --git a/app/services/service_desk/custom_email_verifications/update_service.rb b/app/services/service_desk/custom_email_verifications/update_service.rb
index e3e8900b9e6..1b0e5e3c61a 100644
--- a/app/services/service_desk/custom_email_verifications/update_service.rb
+++ b/app/services/service_desk/custom_email_verifications/update_service.rb
@@ -44,6 +44,7 @@ module ServiceDesk
def verify
return :mail_not_received_within_timeframe if mail_not_received_within_timeframe?
+ return :incorrect_forwarding_target if forwarded_to_service_desk_alias_address?
return :incorrect_from if incorrect_from?
return :incorrect_token if incorrect_token?
@@ -55,6 +56,16 @@ module ServiceDesk
mail.blank? || !verification.in_timeframe?
end
+ def forwarded_to_service_desk_alias_address?
+ return false unless Gitlab::Email::ServiceDeskEmail.enabled?
+
+ # Users must use the Service Desk address created from `incoming_email`
+ # so all reply by email features work as expected.
+ # Using the Service Desk alias address generated from `service_desk_email`
+ # doesn't allow to ingest email replies, so we'd always add a new issue.
+ addresses_from_headers.include?(project.service_desk_alias_address)
+ end
+
def incorrect_from?
# Does the email forwarder preserve the FROM header?
mail.from.first != settings.custom_email
@@ -70,6 +81,12 @@ module ServiceDesk
scan_result.first.first != verification.token
end
+ def addresses_from_headers
+ # Common headers for forwarding target addresses are
+ # `To` and `Delivered-To`. We may expand that list if necessary.
+ (Array(mail.to) + Array(mail['Delivered-To']).map(&:value)).uniq
+ end
+
def error_parameter_missing
error_response(s_('ServiceDesk|Service Desk setting or verification object missing'))
end
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index bee7e10906b..46fe6bed05e 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -128,6 +128,11 @@
%strong
= Gitlab::Access.human_access_with_none(@user.highest_role)
+ %li
+ %span.light= _("Email reset removed at:")
+ %strong
+ = @user.email_reset_offered_at || _('never')
+
= render_if_exists 'admin/users/using_license_seat', user: @user
- if @user.ldap_user?
diff --git a/app/views/notify/service_desk_verification_result_email.html.haml b/app/views/notify/service_desk_verification_result_email.html.haml
index 651f94533ec..dfb5a1785e7 100644
--- a/app/views/notify/service_desk_verification_result_email.html.haml
+++ b/app/views/notify/service_desk_verification_result_email.html.haml
@@ -11,6 +11,7 @@
- verify_email_address = @service_desk_setting.custom_email_address_for_verification
- code_open = ''.html_safe
- code_end = ''.html_safe
+- service_desk_incoming_address = @service_desk_setting.project.service_desk_incoming_address
%tr
%td.text-content
@@ -59,5 +60,10 @@
%b
= s_('Notify|Read timeout:')
= s_('Notify|The SMTP server did not respond in time.')
+ - if @verification.incorrect_forwarding_target?
+ %p
+ %b
+ = s_('Notify|Incorrect forwarding target:')
+ = html_escape(s_('Notify|Forward all emails to the custom email address to %{code_open}%{service_desk_incoming_address}%{code_end}.')) % { code_open: code_open, service_desk_incoming_address: service_desk_incoming_address, code_end: code_end }
%p
= html_escape(s_('Notify|To restart the verification process, go to your %{settings_link_start}project\'s Service Desk settings page%{settings_link_end}.')) % { settings_link_start: settings_link_start, settings_link_end: settings_link_end }
diff --git a/app/views/notify/service_desk_verification_result_email.text.erb b/app/views/notify/service_desk_verification_result_email.text.erb
index 134b6592197..89487dcf185 100644
--- a/app/views/notify/service_desk_verification_result_email.text.erb
+++ b/app/views/notify/service_desk_verification_result_email.text.erb
@@ -1,6 +1,7 @@
<% project_name = @service_desk_setting.project.human_name %>
<% email_address = @service_desk_setting.custom_email %>
<% verify_email_address = @service_desk_setting.custom_email_address_for_verification %>
+<% service_desk_incoming_address = @service_desk_setting.project.service_desk_incoming_address %>
<% if @verification.finished? %>
<%= s_("Notify|Email successfully verified") %>
@@ -35,6 +36,9 @@
<% elsif @verification.read_timeout? %>
<%= s_('Notify|Read timeout:') %>
<%= s_('Notify|The SMTP server did not respond in time.') %>
+ <% elsif @verification.incorrect_forwarding_target? %>
+ <%= s_('Notify|Incorrect forwarding target:') %>
+ <%= s_('Notify|Forward all emails to the custom email address to %{code_open}%{service_desk_incoming_address}%{code_end}.') % { code_open: '', service_desk_incoming_address: service_desk_incoming_address, code_end: '' } %>
<% end %>
<%= s_('Notify|To restart the verification process, go to your %{settings_link_start}project\'s Service Desk settings page%{settings_link_end}.') % { settings_link_start: '', settings_link_end: '' } %>
diff --git a/db/post_migrate/20231205111453_prepare_indexes_for_partitioning_ci_stages.rb b/db/post_migrate/20231205111453_prepare_indexes_for_partitioning_ci_stages.rb
new file mode 100644
index 00000000000..81f132db0ff
--- /dev/null
+++ b/db/post_migrate/20231205111453_prepare_indexes_for_partitioning_ci_stages.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class PrepareIndexesForPartitioningCiStages < Gitlab::Database::Migration[2.2]
+ milestone '16.7'
+
+ TABLE_NAME = :ci_stages
+ PK_INDEX_NAME = :index_ci_stages_on_id_partition_id_unique
+ UNIQUE_INDEX_PIPELINE_ID_AND_NAME = :index_ci_stages_on_pipeline_id_name_partition_id_unique
+
+ def up
+ prepare_async_index TABLE_NAME, %i[id partition_id], name: PK_INDEX_NAME, unique: true
+ prepare_async_index TABLE_NAME, %i[pipeline_id name partition_id], name: UNIQUE_INDEX_PIPELINE_ID_AND_NAME,
+ unique: true
+ end
+
+ def down
+ unprepare_async_index_by_name(TABLE_NAME, PK_INDEX_NAME)
+ unprepare_async_index_by_name(TABLE_NAME, UNIQUE_INDEX_PIPELINE_ID_AND_NAME)
+ end
+end
diff --git a/db/post_migrate/20231206115306_remove_index_users_with_static_object_token.rb b/db/post_migrate/20231206115306_remove_index_users_with_static_object_token.rb
new file mode 100644
index 00000000000..cac8f84f5d4
--- /dev/null
+++ b/db/post_migrate/20231206115306_remove_index_users_with_static_object_token.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveIndexUsersWithStaticObjectToken < Gitlab::Database::Migration[2.2]
+ disable_ddl_transaction!
+
+ milestone '16.7'
+
+ INDEX_NAME = :index_users_with_static_object_token
+ TABLE_NAME = :users
+ WHERE_STATEMENT = 'static_object_token IS NOT NULL AND static_object_token_encrypted IS NULL'
+
+ def up
+ remove_concurrent_index_by_name TABLE_NAME, INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index TABLE_NAME, :id, where: WHERE_STATEMENT, name: INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20231205111453 b/db/schema_migrations/20231205111453
new file mode 100644
index 00000000000..af662100d37
--- /dev/null
+++ b/db/schema_migrations/20231205111453
@@ -0,0 +1 @@
+b2ddeca2009bfa06b7672e816f018047b5191492c612713cec8a12c17c6c20b5
\ No newline at end of file
diff --git a/db/schema_migrations/20231206115306 b/db/schema_migrations/20231206115306
new file mode 100644
index 00000000000..0681ae30d91
--- /dev/null
+++ b/db/schema_migrations/20231206115306
@@ -0,0 +1 @@
+b3129b32e869fd6420421a13a8ae0cae873dd89cef90bfbced738884069a7445
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 28a71e98a85..c3c25cfe267 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -34967,8 +34967,6 @@ CREATE INDEX index_users_star_projects_on_project_id ON users_star_projects USIN
CREATE UNIQUE INDEX index_users_star_projects_on_user_id_and_project_id ON users_star_projects USING btree (user_id, project_id);
-CREATE INDEX index_users_with_static_object_token ON users USING btree (id) WHERE ((static_object_token IS NOT NULL) AND (static_object_token_encrypted IS NULL));
-
CREATE UNIQUE INDEX index_verification_codes_on_phone_and_visitor_id_code ON ONLY verification_codes USING btree (visitor_id_code, phone, created_at);
COMMENT ON INDEX index_verification_codes_on_phone_and_visitor_id_code IS 'JiHu-specific index';
diff --git a/doc/administration/backup_restore/backup_gitlab.md b/doc/administration/backup_restore/backup_gitlab.md
index 0889037c804..cf653b108ef 100644
--- a/doc/administration/backup_restore/backup_gitlab.md
+++ b/doc/administration/backup_restore/backup_gitlab.md
@@ -349,6 +349,7 @@ Caveats:
- If you specify a command that is not packaged with GitLab, then you must install it yourself.
- The resultant file names will still end in `.gz`.
- The default decompression command, used during restore, is `gzip -cd`. Therefore if you override the compression command to use a format that cannot be decompressed by `gzip -cd`, you must override the decompression command during restore.
+- [Do not place environment variables after the backup command](https://gitlab.com/gitlab-org/gitlab/-/issues/433227). For example, `gitlab-backup create COMPRESS_CMD="pigz -c --best"` doesn't work as intended.
##### Default compression: Gzip with fastest method
@@ -359,7 +360,7 @@ gitlab-backup create
##### Gzip with slowest method
```shell
-gitlab-backup create COMPRESS_CMD="gzip -c --best"
+COMPRESS_CMD="gzip -c --best" gitlab-backup create
```
If `gzip` was used for backup, then restore does not require any options:
@@ -375,13 +376,13 @@ If your backup destination has built-in automatic compression, then you may wish
The `tee` command pipes `stdin` to `stdout`.
```shell
-gitlab-backup create COMPRESS_CMD=tee
+COMPRESS_CMD=tee gitlab-backup create
```
And on restore:
```shell
-gitlab-backup restore DECOMPRESS_CMD=tee
+DECOMPRESS_CMD=tee gitlab-backup restore
```
##### Parallel compression with `pigz`
@@ -395,13 +396,13 @@ NOTE:
An example of compressing backups with `pigz` using 4 processes:
```shell
-sudo gitlab-backup create COMPRESS_CMD="pigz --compress --stdout --fast --processes=4"
+COMPRESS_CMD="pigz --compress --stdout --fast --processes=4" sudo gitlab-backup create
```
Because `pigz` compresses to the `gzip` format, it is not required to use `pigz` to decompress backups which were compressed by `pigz`. However, it can still have a performance benefit over `gzip`. An example of decompressing backups with `pigz`:
```shell
-sudo gitlab-backup restore DECOMPRESS_CMD="pigz --decompress --stdout"
+DECOMPRESS_CMD="pigz --decompress --stdout" sudo gitlab-backup restore
```
##### Parallel compression with `zstd`
@@ -415,13 +416,13 @@ NOTE:
An example of compressing backups with `zstd` using 4 threads:
```shell
-sudo gitlab-backup create COMPRESS_CMD="zstd --compress --stdout --fast --threads=4"
+COMPRESS_CMD="zstd --compress --stdout --fast --threads=4" sudo gitlab-backup create
```
An example of decompressing backups with `zstd`:
```shell
-sudo gitlab-backup restore DECOMPRESS_CMD="zstd --decompress --stdout"
+DECOMPRESS_CMD="zstd --decompress --stdout" sudo gitlab-backup restore
```
#### Confirm archive can be transferred
diff --git a/doc/administration/geo/setup/database.md b/doc/administration/geo/setup/database.md
index f39343d7b55..13615825a14 100644
--- a/doc/administration/geo/setup/database.md
+++ b/doc/administration/geo/setup/database.md
@@ -762,7 +762,7 @@ defaults
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
frontend internal-postgresql-tcp-in
- bind *:5000
+ bind *:5432
mode tcp
option tcplog
@@ -899,6 +899,10 @@ For each node running a Patroni instance on the secondary site:
gitlab_rails['auto_migrate'] = false
```
+ When configuring `patroni['standby_cluster']['host']` and `patroni['standby_cluster']['port']`:
+ - `INTERNAL_LOAD_BALANCER_PRIMARY_IP` must point to the primary internal load balancer IP.
+ - `INTERNAL_LOAD_BALANCER_PRIMARY_PORT` must point to the frontend port [configured for the primary Patroni cluster leader](#step-2-configure-the-internal-load-balancer-on-the-primary-site). **Do not** use the PgBouncer frontend port.
+
1. Reconfigure GitLab for the changes to take effect.
This step is required to bootstrap PostgreSQL users and settings.
diff --git a/doc/administration/package_information/postgresql_versions.md b/doc/administration/package_information/postgresql_versions.md
index aef703b8f96..149f6b90ec9 100644
--- a/doc/administration/package_information/postgresql_versions.md
+++ b/doc/administration/package_information/postgresql_versions.md
@@ -31,18 +31,18 @@ Read more about update policies and warnings in the PostgreSQL
| First GitLab version | PostgreSQL versions | Default version for fresh installs | Default version for upgrades | Notes |
| -------------- | ------------------- | ---------------------------------- | ---------------------------- | ----- |
-| 16.4.3, 16.5.3, 16.6.1 | 13.12, 14.9 | 13.12 | 13.12 | |
-| 16.2.0 | 13.11, 14.8 | 13.11 | 13.11 | For upgrades, users can manually upgrade to 14.8 following the [upgrade documentation](https://docs.gitlab.com/omnibus/settings/database.html#gitlab-162-and-later). |
+| 16.4.3, 16.5.3, 16.6.1 | 13.12, 14.9 | 13.12 | 13.12 | For upgrades, you can manually upgrade to 14.9 following the [upgrade documentation](../../update/versions/gitlab_16_changes.md#linux-package-installations-2). |
+| 16.2.0 | 13.11, 14.8 | 13.11 | 13.11 | For upgrades, you can manually upgrade to 14.8 following the [upgrade documentation](../../update/versions/gitlab_16_changes.md#linux-package-installations-2). |
| 16.0.2 | 13.11 | 13.11 | 13.11 | |
| 16.0.0 | 13.8 | 13.8 | 13.8 | |
| 15.11.7 | 13.11 | 13.11 | 12.12 | |
| 15.10.8 | 13.11 | 13.11 | 12.12 | |
-| 15.6 | 12.12, 13.8 | 13.8 | 12.12 | For upgrades, users can manually upgrade to 13.8 following the [upgrade documentation](https://docs.gitlab.com/omnibus/settings/database.html#gitlab-150-and-later). |
-| 15.0 | 12.10, 13.6 | 13.6 | 12.10 | For upgrades, users can manually upgrade to 13.6 following the [upgrade documentation](https://docs.gitlab.com/omnibus/settings/database.html#gitlab-150-and-later). |
+| 15.6 | 12.12, 13.8 | 13.8 | 12.12 | For upgrades, you can manually upgrade to 13.8 following the [upgrade documentation](../../update/versions/gitlab_15_changes.md#linux-package-installations-2). |
+| 15.0 | 12.10, 13.6 | 13.6 | 12.10 | For upgrades, you can manually upgrade to 13.6 following the [upgrade documentation](../../update/versions/gitlab_15_changes.md#linux-package-installations-2). |
| 14.1 | 12.7, 13.3 | 12.7 | 12.7 | PostgreSQL 13 available for fresh installations if not using [Geo](../geo/index.md#requirements-for-running-geo) or [Patroni](../postgresql/index.md#postgresql-replication-and-failover-for-linux-package-installations).
| 14.0 | 12.7 | 12.7 | 12.7 | HA installations with repmgr are no longer supported and are prevented from upgrading to Linux package 14.0 |
| 13.8 | 11.9, 12.4 | 12.4 | 12.4 | Package upgrades automatically performed PostgreSQL upgrade for nodes that are not part of a Geo or HA cluster. |
-| 13.7 | 11.9, 12.4 | 12.4 | 11.9 | For upgrades users can manually upgrade to 12.4 following the [upgrade documentation](https://docs.gitlab.com/omnibus/settings/database.html#gitlab-133-and-later). |
+| 13.7 | 11.9, 12.4 | 12.4 | 11.9 | For upgrades users can manually upgrade to 12.4 following the [upgrade documentation](https://docs.gitlab.com/omnibus/settings/database.html#upgrade-packaged-postgresql-server). |
| 13.4 | 11.9, 12.4 | 11.9 | 11.9 | Package upgrades aborted if users not running PostgreSQL 11 already |
| 13.3 | 11.7, 12.3 | 11.7 | 11.7 | Package upgrades aborted if users not running PostgreSQL 11 already |
| 13.0 | 11.7 | 11.7 | 11.7 | Package upgrades aborted if users not running PostgreSQL 11 already |
diff --git a/doc/api/rest/index.md b/doc/api/rest/index.md
index ec6af7eb3d7..ff823dcdea3 100644
--- a/doc/api/rest/index.md
+++ b/doc/api/rest/index.md
@@ -839,6 +839,10 @@ For questions about these integrations, use the [GitLab community forum](https:/
- [Ruby wrapper and CLI for the GitLab REST API](https://github.com/NARKOZ/gitlab)
+### Rust
+
+- [`gitlab` crate](https://crates.io/crates/gitlab)
+
### Swift
- [`RxGitLabKit`](https://github.com/Qase/RxGitLabKit)
diff --git a/doc/api/users.md b/doc/api/users.md
index a2582e3c63c..21596cd7146 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -136,6 +136,7 @@ GET /users?without_project_bots=true
> - The `created_by` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93092) in GitLab 15.6.
> - The `scim_identities` field in the response [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/324247) in GitLab 16.1.
> - The `auditors` field in the response [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418023) in GitLab 16.2.
+> - The `email_reset_offered_at` field in the response [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137610) in GitLab 16.7.
```plaintext
GET /users
@@ -197,7 +198,8 @@ You can use all [parameters available for everyone](#for-non-administrator-users
"current_sign_in_ip": "196.165.1.102",
"last_sign_in_ip": "172.127.2.22",
"namespace_id": 1,
- "created_by": null
+ "created_by": null,
+ "email_reset_offered_at": null
},
{
"id": 2,
@@ -235,7 +237,8 @@ You can use all [parameters available for everyone](#for-non-administrator-users
"current_sign_in_ip": "10.165.1.102",
"last_sign_in_ip": "172.127.2.22",
"namespace_id": 2,
- "created_by": null
+ "created_by": null,
+ "email_reset_offered_at": null
}
]
```
@@ -380,6 +383,7 @@ Parameters:
> - The `namespace_id` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82045) in GitLab 14.10.
> - The `created_by` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93092) in GitLab 15.6.
+> - The `email_reset_offered_at` field in the response [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137610) in GitLab 16.7.
```plaintext
GET /users/:id
@@ -445,7 +449,8 @@ Example Responses:
"trial": true,
"sign_in_count": 1337,
"namespace_id": 1,
- "created_by": null
+ "created_by": null,
+ "email_reset_offered_at": null
}
```
@@ -728,6 +733,7 @@ Users on [GitLab Premium or Ultimate](https://about.gitlab.com/pricing/) also se
> - The `namespace_id` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82045) in GitLab 14.10.
> - The `created_by` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93092) in GitLab 15.6.
+> - The `email_reset_offered_at` field in the response [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137610) in GitLab 16.7.
```plaintext
GET /user
@@ -783,6 +789,7 @@ Parameters:
"last_sign_in_ip": "172.127.2.22",
"namespace_id": 1,
"created_by": null,
+ "email_reset_offered_at": null,
"note": null
}
```
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 954f6989df8..063b3a9ad95 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Merge requests **(FREE ALL)**
-To incorporate changes from a source branch to a target branch, you use a *merge request* (MR).
+A merge request (MR) is a proposal to incorporate changes from a source branch to a target branch.
When you open a merge request, you can visualize and collaborate on the changes before merge.
Merge requests include:
diff --git a/doc/user/project/service_desk/configure.md b/doc/user/project/service_desk/configure.md
index faabacd9b14..9acf4b4909b 100644
--- a/doc/user/project/service_desk/configure.md
+++ b/doc/user/project/service_desk/configure.md
@@ -264,6 +264,26 @@ To troubleshoot this:
1. Select one of the supported authentication methods in the custom email setup form.
+##### Incorrect forwarding target
+
+You might get an error that states that an incorrect forwarding target was used.
+
+This occurs when the verification email was forwarded to a different email address than the
+project-specific Service Desk address that's displayed in the custom email configuration form.
+
+You must use the Service Desk address generated from `incoming_email`. Forwarding to the additional
+Service Desk alias address generated from `service_desk_email` is not supported because it doesn't support
+all reply by email functionalities.
+
+To troubleshoot this:
+
+1. Find the correct email address to forward emails to. Either:
+ - Note the address from the verification result email that all project owners and the user that
+ triggered the verification process receive.
+ - Copy the address from the **Service Desk email address to forward emails to** input in the
+ custom email setup form.
+1. Forward all emails to the custom email address to the correct target email address.
+
### Enable or disable the custom email address
After the custom email address has been verified, administrators can enable or disable sending Service Desk emails via the custom email address.
diff --git a/doc/user/search/command_palette.md b/doc/user/search/command_palette.md
index 720dbbf8963..21c0a915e3d 100644
--- a/doc/user/search/command_palette.md
+++ b/doc/user/search/command_palette.md
@@ -16,7 +16,7 @@ find an object more quickly.
To open the command palette:
-1. On the left sidebar, at the top, select **Search or go to** or use the / key to enable.
+1. On the left sidebar, select **Search or go to** or use the / key to enable.
1. Type one of the special characters:
- > - Create a new object or find a menu item.
diff --git a/lib/api/entities/application_setting.rb b/lib/api/entities/application_setting.rb
index 91dae5ab825..de5bd4e8d97 100644
--- a/lib/api/entities/application_setting.rb
+++ b/lib/api/entities/application_setting.rb
@@ -8,6 +8,7 @@ module API
attributes.delete(:performance_bar_allowed_group_path)
attributes.delete(:performance_bar_enabled)
attributes.delete(:allow_local_requests_from_hooks_and_services)
+ attributes.delete(:repository_storages_weighted)
# let's not expose the secret key in a response
attributes.delete(:asset_proxy_secret_key)
@@ -49,6 +50,7 @@ module API
expose(:housekeeping_full_repack_period) { |settings, _options| settings.housekeeping_optimize_repository_period }
expose(:housekeeping_gc_period) { |settings, _options| settings.housekeeping_optimize_repository_period }
expose(:housekeeping_incremental_repack_period) { |settings, _options| settings.housekeeping_optimize_repository_period }
+ expose(:repository_storages_weighted) { |settings, _options| settings.repository_storages_with_default_weight }
end
end
end
diff --git a/lib/api/entities/user_with_admin.rb b/lib/api/entities/user_with_admin.rb
index 53fef7a46e2..25c1edb4526 100644
--- a/lib/api/entities/user_with_admin.rb
+++ b/lib/api/entities/user_with_admin.rb
@@ -7,6 +7,7 @@ module API
expose :note
expose :namespace_id
expose :created_by, with: UserBasic
+ expose :email_reset_offered_at
end
end
end
diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb
index 7f531525870..1dc21982134 100644
--- a/lib/api/project_packages.rb
+++ b/lib/api/project_packages.rb
@@ -134,8 +134,6 @@ module API
destroy_conditionally!(package) do |package|
::Packages::MarkPackageForDestructionService.new(container: package, current_user: current_user).execute
-
- enqueue_sync_metadata_cache_worker(user_project, package.name) if package.npm?
end
end
end
diff --git a/lib/gitlab/base_doorkeeper_controller.rb b/lib/gitlab/base_doorkeeper_controller.rb
index c8520993b8e..91994c2fa95 100644
--- a/lib/gitlab/base_doorkeeper_controller.rb
+++ b/lib/gitlab/base_doorkeeper_controller.rb
@@ -3,8 +3,7 @@
# This is a base controller for doorkeeper.
# It adds the `can?` helper used in the views.
module Gitlab
- # rubocop:disable Rails/ApplicationController
- class BaseDoorkeeperController < ActionController::Base
+ class BaseDoorkeeperController < BaseActionController
include Gitlab::Allowable
include EnforcesTwoFactorAuthentication
include SessionsHelper
@@ -13,5 +12,4 @@ module Gitlab
helper_method :can?
end
- # rubocop:enable Rails/ApplicationController
end
diff --git a/lib/gitlab/request_forgery_protection.rb b/lib/gitlab/request_forgery_protection.rb
index d5e80053772..3a389d3363f 100644
--- a/lib/gitlab/request_forgery_protection.rb
+++ b/lib/gitlab/request_forgery_protection.rb
@@ -6,8 +6,7 @@
module Gitlab
module RequestForgeryProtection
- # rubocop:disable Rails/ApplicationController
- class Controller < ActionController::Base
+ class Controller < BaseActionController
protect_from_forgery with: :exception, prepend: true
def initialize
@@ -40,6 +39,5 @@ module Gitlab
rescue ActionController::InvalidAuthenticityToken
false
end
- # rubocop:enable Rails/ApplicationController
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index fba46976d55..c16aaa3e9b8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18303,6 +18303,9 @@ msgstr ""
msgid "Email patch"
msgstr ""
+msgid "Email reset removed at:"
+msgstr ""
+
msgid "Email sent"
msgstr ""
@@ -32599,6 +32602,9 @@ msgstr ""
msgid "Notify|Fingerprint: %{fingerprint}"
msgstr ""
+msgid "Notify|Forward all emails to the custom email address to %{code_open}%{service_desk_incoming_address}%{code_end}."
+msgstr ""
+
msgid "Notify|Here are the results for your CSV import for %{project_link}."
msgstr ""
@@ -32623,6 +32629,9 @@ msgstr ""
msgid "Notify|Incorrect %{code_open}From%{code_end} header:"
msgstr ""
+msgid "Notify|Incorrect forwarding target:"
+msgstr ""
+
msgid "Notify|Incorrect verification token:"
msgstr ""
@@ -41823,7 +41832,7 @@ msgstr ""
msgid "Runners|Shared runners are disabled."
msgstr ""
-msgid "Runners|Shared runners will be disabled for all projects and subgroups in this group. If you proceed, you must manually re-enable shared runners in the settings of each project and subgroup."
+msgid "Runners|Shared runners will be disabled for all projects and subgroups in this group."
msgstr ""
msgid "Runners|Show only inherited"
@@ -44715,9 +44724,15 @@ msgstr ""
msgid "ServiceDesk|For help setting up the Service Desk for your instance, please contact an administrator."
msgstr ""
+msgid "ServiceDesk|Forward all emails to the custom email address to %{incomingEmail}."
+msgstr ""
+
msgid "ServiceDesk|Incorrect From header"
msgstr ""
+msgid "ServiceDesk|Incorrect forwarding target"
+msgstr ""
+
msgid "ServiceDesk|Incorrect verification token"
msgstr ""
@@ -54856,6 +54871,9 @@ msgstr ""
msgid "WorkItem|Cancel"
msgstr ""
+msgid "WorkItem|Child items"
+msgstr ""
+
msgid "WorkItem|Child objectives and key results"
msgstr ""
@@ -54943,6 +54961,9 @@ msgstr ""
msgid "WorkItem|New task"
msgstr ""
+msgid "WorkItem|No child items are currently assigned. Use child items to break down this issue into smaller parts."
+msgstr ""
+
msgid "WorkItem|No iteration"
msgstr ""
@@ -55024,6 +55045,9 @@ msgstr ""
msgid "WorkItem|Something went wrong when deleting the task. Please try again."
msgstr ""
+msgid "WorkItem|Something went wrong when fetching child items. Please refresh this page."
+msgstr ""
+
msgid "WorkItem|Something went wrong when fetching items. Please refresh this page."
msgstr ""
@@ -55033,9 +55057,6 @@ msgstr ""
msgid "WorkItem|Something went wrong when fetching labels. Please try again."
msgstr ""
-msgid "WorkItem|Something went wrong when fetching tasks. Please refresh this page."
-msgstr ""
-
msgid "WorkItem|Something went wrong when fetching work item types. Please try again"
msgstr ""
diff --git a/qa/gdk/Dockerfile.gdk b/qa/gdk/Dockerfile.gdk
index 34811cdd19c..2ba1c1e10df 100644
--- a/qa/gdk/Dockerfile.gdk
+++ b/qa/gdk/Dockerfile.gdk
@@ -5,7 +5,7 @@ ENV GITLAB_LICENSE_MODE=test \
# Clone GDK at specific sha and bootstrap packages
#
-ARG GDK_SHA=58fbe61603dd882f4a28538a23629c0ed96c8612
+ARG GDK_SHA=03a5d9338ebc4ffddb3379622aa12bc69f5156c2
RUN set -eux; \
git clone --depth 1 https://gitlab.com/gitlab-org/gitlab-development-kit.git && cd gitlab-development-kit; \
git fetch --depth 1 origin ${GDK_SHA} && git -c advice.detachedHead=false checkout ${GDK_SHA}; \
diff --git a/qa/qa/page/component/delete_modal.rb b/qa/qa/page/component/delete_modal.rb
index 9fbbd9930a9..5d8924e1856 100644
--- a/qa/qa/page/component/delete_modal.rb
+++ b/qa/qa/page/component/delete_modal.rb
@@ -10,24 +10,24 @@ module QA
super
base.view 'app/assets/javascripts/projects/components/shared/delete_modal.vue' do
- element :confirm_name_field
- element :confirm_delete_button
+ element 'confirm-name-field'
+ element 'confirm-delete-button'
end
end
def fill_confirmation_path(text)
- fill_element(:confirm_name_field, text)
+ fill_element('confirm-name-field', text)
end
def wait_for_delete_button_enabled
wait_until(reload: false) do
- !find_element(:confirm_delete_button).disabled?
+ !find_element('confirm-delete-button').disabled?
end
end
def confirm_delete
wait_for_delete_button_enabled
- click_element(:confirm_delete_button)
+ click_element('confirm-delete-button')
end
end
end
diff --git a/spec/channels/application_cable/connection_spec.rb b/spec/channels/application_cable/connection_spec.rb
index 4943669bde0..fa2518e1970 100644
--- a/spec/channels/application_cable/connection_spec.rb
+++ b/spec/channels/application_cable/connection_spec.rb
@@ -43,6 +43,16 @@ RSpec.describe ApplicationCable::Connection, :clean_gitlab_redis_sessions do
end
end
+ context 'when bearer header is provided' do
+ let(:user_pat) { create(:personal_access_token) }
+
+ it 'finds user by PAT' do
+ connect(ActionCable.server.config.mount_path, headers: { Authorization: "Bearer #{user_pat.token}" })
+
+ expect(connection.current_user).to eq(user_pat.user)
+ end
+ end
+
context 'when session cookie is not set' do
it 'sets current_user to nil' do
connect
diff --git a/spec/features/projects/work_items/work_item_children_spec.rb b/spec/features/projects/work_items/work_item_children_spec.rb
index 0970752157d..28f7ee2db10 100644
--- a/spec/features/projects/work_items/work_item_children_spec.rb
+++ b/spec/features/projects/work_items/work_item_children_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe 'Work item children', :js, feature_category: :team_planning do
it 'are not displayed when issue does not have work item children', :aggregate_failures do
page.within('[data-testid="work-item-links"]') do
- expect(find('[data-testid="links-empty"]')).to have_content(_('No tasks are currently assigned.'))
+ expect(find('[data-testid="links-empty"]')).to have_content(_('No child items are currently assigned.'))
expect(page).not_to have_selector('[data-testid="add-links-form"]')
expect(page).not_to have_selector('[data-testid="links-child"]')
end
diff --git a/spec/frontend/projects/components/shared/delete_modal_spec.js b/spec/frontend/projects/components/shared/delete_modal_spec.js
index c6213fd4b6d..7e040db4beb 100644
--- a/spec/frontend/projects/components/shared/delete_modal_spec.js
+++ b/spec/frontend/projects/components/shared/delete_modal_spec.js
@@ -49,7 +49,7 @@ describe('DeleteModal', () => {
attributes: {
variant: 'danger',
disabled: true,
- 'data-qa-selector': 'confirm_delete_button',
+ 'data-testid': 'confirm-delete-button',
},
},
actionCancel: {
diff --git a/spec/frontend/projects/settings_service_desk/components/custom_email_spec.js b/spec/frontend/projects/settings_service_desk/components/custom_email_spec.js
index faef932fc7f..0a593f3812a 100644
--- a/spec/frontend/projects/settings_service_desk/components/custom_email_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/custom_email_spec.js
@@ -3,7 +3,6 @@ import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import CustomEmail from '~/projects/settings_service_desk/components/custom_email.vue';
import {
- I18N_VERIFICATION_ERRORS,
I18N_STATE_VERIFICATION_STARTED,
I18N_STATE_VERIFICATION_FAILED,
I18N_STATE_VERIFICATION_FAILED_RESET_PARAGRAPH,
@@ -15,6 +14,7 @@ describe('CustomEmail', () => {
let wrapper;
const defaultProps = {
+ incomingEmail: 'incoming+test-1-issue-@example.com',
customEmail: 'user@example.com',
smtpAddress: 'smtp.example.com',
verificationState: 'started',
@@ -70,19 +70,21 @@ describe('CustomEmail', () => {
});
describe('verification error', () => {
- it.each([
- 'smtp_host_issue',
- 'invalid_credentials',
- 'mail_not_received_within_timeframe',
- 'incorrect_from',
- 'incorrect_token',
- 'read_timeout',
- ])('displays %s label and description', (error) => {
+ it.each`
+ error | label | description
+ ${'smtp_host_issue'} | ${'SMTP host issue'} | ${'A connection to the specified host could not be made or an SSL issue occurred.'}
+ ${'invalid_credentials'} | ${'Invalid credentials'} | ${'The given credentials (username and password) were rejected by the SMTP server, or you need to explicitly set an authentication method.'}
+ ${'mail_not_received_within_timeframe'} | ${'Verification email not received within timeframe'} | ${"The verification email wasn't received in time. There is a 30 minutes timeframe for verification emails to appear in your instance's Service Desk. Make sure that you have set up email forwarding correctly."}
+ ${'incorrect_from'} | ${'Incorrect From header'} | ${'Check your forwarding settings and make sure the original email sender remains in the From header.'}
+ ${'incorrect_token'} | ${'Incorrect verification token'} | ${"The received email didn't contain the verification token that was sent to your email address."}
+ ${'read_timeout'} | ${'Read timeout'} | ${'The SMTP server did not respond in time.'}
+ ${'incorrect_forwarding_target'} | ${'Incorrect forwarding target'} | ${`Forward all emails to the custom email address to ${defaultProps.incomingEmail}`}
+ `('displays $error label and description', ({ error, label, description }) => {
createWrapper({ verificationError: error });
const text = wrapper.text();
- expect(text).toContain(I18N_VERIFICATION_ERRORS[error].label);
- expect(text).toContain(I18N_VERIFICATION_ERRORS[error].description);
+ expect(text).toContain(label);
+ expect(text).toContain(description);
});
});
diff --git a/spec/frontend/projects/settings_service_desk/components/custom_email_wrapper_spec.js b/spec/frontend/projects/settings_service_desk/components/custom_email_wrapper_spec.js
index 174e05ceeee..8d3a7a5fde5 100644
--- a/spec/frontend/projects/settings_service_desk/components/custom_email_wrapper_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/custom_email_wrapper_spec.js
@@ -38,6 +38,12 @@ describe('CustomEmailWrapper', () => {
customEmailEndpoint: '/flightjs/Flight/-/service_desk/custom_email',
};
+ const defaultCustomEmailProps = {
+ incomingEmail: defaultProps.incomingEmail,
+ customEmail: 'user@example.com',
+ smtpAddress: 'smtp.example.com',
+ };
+
const showToast = jest.fn();
const createWrapper = (props = {}) => {
@@ -117,8 +123,7 @@ describe('CustomEmailWrapper', () => {
expect(showToast).toHaveBeenCalledWith(I18N_TOAST_SAVED);
expect(findCustomEmail().props()).toEqual({
- customEmail: 'user@example.com',
- smtpAddress: 'smtp.example.com',
+ ...defaultCustomEmailProps,
verificationState: 'started',
verificationError: null,
isEnabled: false,
@@ -140,8 +145,7 @@ describe('CustomEmailWrapper', () => {
it('displays CustomEmail component', () => {
expect(findCustomEmail().props()).toEqual({
- customEmail: 'user@example.com',
- smtpAddress: 'smtp.example.com',
+ ...defaultCustomEmailProps,
verificationState: 'started',
verificationError: null,
isEnabled: false,
@@ -193,8 +197,7 @@ describe('CustomEmailWrapper', () => {
it('fetches data from endpoint and displays CustomEmail component', () => {
expect(findCustomEmail().props()).toEqual({
- customEmail: 'user@example.com',
- smtpAddress: 'smtp.example.com',
+ ...defaultCustomEmailProps,
verificationState: 'failed',
verificationError: 'smtp_host_issue',
isEnabled: false,
@@ -225,8 +228,7 @@ describe('CustomEmailWrapper', () => {
it('fetches data from endpoint and displays CustomEmail component', () => {
expect(findCustomEmail().props()).toEqual({
- customEmail: 'user@example.com',
- smtpAddress: 'smtp.example.com',
+ ...defaultCustomEmailProps,
verificationState: 'finished',
verificationError: null,
isEnabled: false,
@@ -257,8 +259,7 @@ describe('CustomEmailWrapper', () => {
expect(showToast).toHaveBeenCalledWith(I18N_TOAST_ENABLED);
expect(findCustomEmail().props()).toEqual({
- customEmail: 'user@example.com',
- smtpAddress: 'smtp.example.com',
+ ...defaultCustomEmailProps,
verificationState: 'finished',
verificationError: null,
isEnabled: true,
@@ -279,8 +280,7 @@ describe('CustomEmailWrapper', () => {
it('fetches data from endpoint and displays CustomEmail component', () => {
expect(findCustomEmail().props()).toEqual({
- customEmail: 'user@example.com',
- smtpAddress: 'smtp.example.com',
+ ...defaultCustomEmailProps,
verificationState: 'finished',
verificationError: null,
isEnabled: true,
@@ -301,8 +301,7 @@ describe('CustomEmailWrapper', () => {
expect(showToast).toHaveBeenCalledWith(I18N_TOAST_DISABLED);
expect(findCustomEmail().props()).toEqual({
- customEmail: 'user@example.com',
- smtpAddress: 'smtp.example.com',
+ ...defaultCustomEmailProps,
verificationState: 'finished',
verificationError: null,
isEnabled: false,
diff --git a/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js b/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js
index 1376133ec37..02da6079466 100644
--- a/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js
+++ b/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js
@@ -8,7 +8,7 @@ import waitForPromises from 'helpers/wait_for_promises';
describe('EntitySelect', () => {
let wrapper;
let fetchItemsMock;
- let fetchInitialSelectionTextMock;
+ let fetchInitialSelectionMock;
// Mocks
const itemMock = {
@@ -96,16 +96,16 @@ describe('EntitySelect', () => {
});
it("fetches the initially selected value's name", async () => {
- fetchInitialSelectionTextMock = jest.fn().mockImplementation(() => itemMock.text);
+ fetchInitialSelectionMock = jest.fn().mockImplementation(() => itemMock);
createComponent({
props: {
- fetchInitialSelectionText: fetchInitialSelectionTextMock,
+ fetchInitialSelection: fetchInitialSelectionMock,
initialSelection: itemMock.value,
},
});
await nextTick();
- expect(fetchInitialSelectionTextMock).toHaveBeenCalledTimes(1);
+ expect(fetchInitialSelectionMock).toHaveBeenCalledTimes(1);
expect(findListbox().props('toggleText')).toBe(itemMock.text);
});
});
@@ -188,7 +188,7 @@ describe('EntitySelect', () => {
findListbox().vm.$emit('reset');
await nextTick();
- expect(Object.keys(wrapper.emitted('input')[2][0]).length).toBe(0);
+ expect(wrapper.emitted('input')[2][0]).toEqual({});
});
});
});
diff --git a/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js b/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js
index 02a85edc523..6dc38bbd0c6 100644
--- a/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js
+++ b/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js
@@ -1,8 +1,8 @@
import VueApollo from 'vue-apollo';
-import Vue, { nextTick } from 'vue';
-import { GlCollapsibleListbox } from '@gitlab/ui';
+import Vue from 'vue';
+import { GlCollapsibleListbox, GlAlert } from '@gitlab/ui';
import { chunk } from 'lodash';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import OrganizationSelect from '~/vue_shared/components/entity_select/organization_select.vue';
import EntitySelect from '~/vue_shared/components/entity_select/entity_select.vue';
import { DEFAULT_PER_PAGE } from '~/api';
@@ -17,6 +17,7 @@ import getOrganizationQuery from '~/organizations/shared/graphql/queries/organiz
import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
Vue.use(VueApollo);
@@ -31,11 +32,6 @@ describe('OrganizationSelect', () => {
pageInfo,
};
- // Stubs
- const GlAlert = {
- template: '
',
- };
-
// Props
const label = 'label';
const description = 'description';
@@ -67,7 +63,7 @@ describe('OrganizationSelect', () => {
} = {}) => {
mockApollo = createMockApollo(handlers);
- wrapper = shallowMountExtended(OrganizationSelect, {
+ wrapper = mountExtended(OrganizationSelect, {
apolloProvider: mockApollo,
propsData: {
label,
@@ -77,10 +73,6 @@ describe('OrganizationSelect', () => {
toggleClass,
...props,
},
- stubs: {
- GlAlert,
- EntitySelect,
- },
listeners: {
input: handleInput,
},
@@ -88,10 +80,6 @@ describe('OrganizationSelect', () => {
};
const openListbox = () => findListbox().vm.$emit('shown');
- afterEach(() => {
- mockApollo = null;
- });
-
describe('entity_select props', () => {
beforeEach(() => {
createComponent();
@@ -114,15 +102,16 @@ describe('OrganizationSelect', () => {
describe('on mount', () => {
it('fetches organizations when the listbox is opened', async () => {
createComponent();
- await waitForPromises();
-
openListbox();
await waitForPromises();
- expect(findListbox().props('items')).toEqual([
- { text: nodes[0].name, value: 1 },
- { text: nodes[1].name, value: 2 },
- { text: nodes[2].name, value: 3 },
- ]);
+
+ const expectedItems = nodes.map((node) => ({
+ ...node,
+ text: node.name,
+ value: getIdFromGraphQLId(node.id),
+ }));
+
+ expect(findListbox().props('items')).toEqual(expectedItems);
});
describe('with an initial selection', () => {
@@ -136,7 +125,7 @@ describe('OrganizationSelect', () => {
it('show an error if fetching initially selected fails', async () => {
createComponent({
props: { initialSelection: organization.id },
- handlers: [[getOrganizationQuery, jest.fn().mockRejectedValueOnce(new Error())]],
+ handlers: [[getOrganizationQuery, jest.fn().mockRejectedValueOnce()]],
});
expect(findAlert().exists()).toBe(false);
@@ -183,7 +172,6 @@ describe('OrganizationSelect', () => {
await waitForPromises();
findListbox().vm.$emit('bottom-reached');
- await nextTick();
await waitForPromises();
});
@@ -198,10 +186,8 @@ describe('OrganizationSelect', () => {
it('shows an error when fetching organizations fails', async () => {
createComponent({
- handlers: [[getCurrentUserOrganizationsQuery, jest.fn().mockRejectedValueOnce(new Error())]],
+ handlers: [[getCurrentUserOrganizationsQuery, jest.fn().mockRejectedValueOnce()]],
});
- await waitForPromises();
-
openListbox();
expect(findAlert().exists()).toBe(false);
diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
index cb2d3fbcae4..e8156091f9d 100644
--- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -462,7 +462,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se
end
end
- shared_examples 'a handler that does not verify the custom email' do |error_identifier|
+ shared_examples 'a handler that does not verify the custom email' do
it 'does not verify the custom email address' do
# project has no owner, so only notify verification triggerer
expect(Notify).to receive(:service_desk_verification_result_email).once
@@ -477,20 +477,32 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se
end
end
- shared_examples 'a handler that verifies Service Desk custom email verification emails' do
+ context 'when using incoming_email address' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: 'support+%{key}@example.com')
+ end
+
it_behaves_like 'an early exiting handler'
context 'with valid service desk settings' do
let_it_be(:user) { create(:user) }
+ let_it_be(:credentials) { create(:service_desk_custom_email_credential, project: project) }
- let!(:settings) { create(:service_desk_setting, project: project, custom_email: 'custom-support-email@example.com') }
- let!(:verification) { create(:service_desk_custom_email_verification, project: project, token: 'ZROT4ZZXA-Y6', triggerer: user) }
+ let_it_be_with_reload(:settings) do
+ create(:service_desk_setting, project: project, custom_email: 'custom-support-email@example.com')
+ end
+
+ let_it_be_with_reload(:verification) do
+ create(:service_desk_custom_email_verification, project: project, token: 'ZROT4ZZXA-Y6', triggerer: user)
+ end
let(:message_delivery) { instance_double(ActionMailer::MessageDelivery) }
- before do
+ before_all do
project.add_maintainer(user)
+ end
+ before do
allow(message_delivery).to receive(:deliver_later)
allow(Notify).to receive(:service_desk_verification_result_email).and_return(message_delivery)
end
@@ -521,7 +533,9 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se
verification.update!(token: 'XXXXXXXXXXXX')
end
- it_behaves_like 'a handler that does not verify the custom email', 'incorrect_token'
+ it_behaves_like 'a handler that does not verify the custom email' do
+ let(:error_identifier) { 'incorrect_token' }
+ end
end
context 'and verification email ingested too late' do
@@ -529,7 +543,9 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se
verification.update!(triggered_at: ServiceDesk::CustomEmailVerification::TIMEFRAME.ago)
end
- it_behaves_like 'a handler that does not verify the custom email', 'mail_not_received_within_timeframe'
+ it_behaves_like 'a handler that does not verify the custom email' do
+ let(:error_identifier) { 'mail_not_received_within_timeframe' }
+ end
end
context 'and from header differs from custom email address' do
@@ -537,19 +553,13 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se
settings.update!(custom_email: 'different-from@example.com')
end
- it_behaves_like 'a handler that does not verify the custom email', 'incorrect_from'
+ it_behaves_like 'a handler that does not verify the custom email' do
+ let(:error_identifier) { 'incorrect_from' }
+ end
end
end
end
- context 'when using incoming_email address' do
- before do
- stub_incoming_email_setting(enabled: true, address: 'support+%{key}@example.com')
- end
-
- it_behaves_like 'a handler that verifies Service Desk custom email verification emails'
- end
-
context 'when using service_desk_email address' do
let(:receiver) { Gitlab::Email::ServiceDeskReceiver.new(email_raw) }
@@ -557,7 +567,35 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se
stub_service_desk_email_setting(enabled: true, address: 'support+%{key}@example.com')
end
- it_behaves_like 'a handler that verifies Service Desk custom email verification emails'
+ it_behaves_like 'an early exiting handler'
+
+ context 'with valid service desk settings' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:credentials) { create(:service_desk_custom_email_credential, project: project) }
+
+ let_it_be_with_reload(:settings) do
+ create(:service_desk_setting, project: project, custom_email: 'custom-support-email@example.com')
+ end
+
+ let_it_be_with_reload(:verification) do
+ create(:service_desk_custom_email_verification, project: project, token: 'ZROT4ZZXA-Y6', triggerer: user)
+ end
+
+ let(:message_delivery) { instance_double(ActionMailer::MessageDelivery) }
+
+ before_all do
+ project.add_maintainer(user)
+ end
+
+ before do
+ allow(message_delivery).to receive(:deliver_later)
+ allow(Notify).to receive(:service_desk_verification_result_email).and_return(message_delivery)
+ end
+
+ it_behaves_like 'a handler that does not verify the custom email' do
+ let(:error_identifier) { 'incorrect_forwarding_target' }
+ end
+ end
end
end
end
diff --git a/spec/mailers/emails/service_desk_spec.rb b/spec/mailers/emails/service_desk_spec.rb
index d876ac00e93..3ed531a16bc 100644
--- a/spec/mailers/emails/service_desk_spec.rb
+++ b/spec/mailers/emails/service_desk_spec.rb
@@ -625,5 +625,10 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
let(:error_identifier) { 'read_timeout' }
let(:expected_text) { 'Read timeout' }
end
+
+ it_behaves_like 'a custom email verification process result email with error' do
+ let(:error_identifier) { 'incorrect_forwarding_target' }
+ let(:expected_text) { 'Incorrect forwarding target' }
+ end
end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 0f786489245..05e3bffbeaf 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -738,6 +738,24 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
end
end
+ describe '#repository_storages_with_default_weight' do
+ context 'with no extra storage set-up in the config file', fips_mode: false do
+ it 'keeps existing key restrictions' do
+ expect(setting.repository_storages_with_default_weight).to eq({ 'default' => 100 })
+ end
+ end
+
+ context 'with extra storage set-up in the config file', fips_mode: false do
+ before do
+ stub_storage_settings({ 'default' => {}, 'custom' => {} })
+ end
+
+ it 'keeps existing key restrictions' do
+ expect(setting.repository_storages_with_default_weight).to eq({ 'default' => 100, 'custom' => 0 })
+ end
+ end
+ end
+
describe 'setting validated as `addressable_url` configured with external URI' do
before do
# Use any property that has the `addressable_url` validation.
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 8e3b97e55f3..0ed6f058768 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -1355,6 +1355,30 @@ RSpec.describe Packages::Package, type: :model, feature_category: :package_regis
end
end
+ describe '#sync_npm_metadata_cache' do
+ let_it_be(:package) { create(:npm_package) }
+
+ subject { package.sync_npm_metadata_cache }
+
+ it 'enqueues a sync worker job' do
+ expect(::Packages::Npm::CreateMetadataCacheWorker)
+ .to receive(:perform_async).with(package.project_id, package.name)
+
+ subject
+ end
+
+ context 'with a non npm package' do
+ let_it_be(:package) { create(:maven_package) }
+
+ it 'does not enqueue a sync worker job' do
+ expect(::Packages::Npm::CreateMetadataCacheWorker)
+ .not_to receive(:perform_async)
+
+ subject
+ end
+ end
+ end
+
describe '#mark_package_files_for_destruction' do
let_it_be(:package) { create(:npm_package, :pending_destruction) }
diff --git a/spec/requests/acme_challenges_controller_spec.rb b/spec/requests/acme_challenges_controller_spec.rb
new file mode 100644
index 00000000000..f37aefed488
--- /dev/null
+++ b/spec/requests/acme_challenges_controller_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AcmeChallengesController, type: :request, feature_category: :pages do
+ it_behaves_like 'Base action controller' do
+ subject(:request) { get acme_challenge_path }
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb b/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb
index d0980a2b43d..084958be1fb 100644
--- a/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb
@@ -38,6 +38,26 @@ RSpec.describe 'Destroying multiple packages', feature_category: :package_regist
end
it_behaves_like 'returning response status', :success
+
+ context 'when npm package' do
+ let_it_be_with_reload(:packages1) { create_list(:npm_package, 3, project: project1, name: 'test-package-1') }
+ let_it_be_with_reload(:packages2) { create_list(:npm_package, 2, project: project2, name: 'test-package-2') }
+
+ it 'enqueues the worker to sync a metadata cache' do
+ arguments = []
+
+ expect(Packages::Npm::CreateMetadataCacheWorker)
+ .to receive(:bulk_perform_async_with_contexts).and_wrap_original do |original_method, *args|
+ packages = args.first
+ arguments = packages.map(&args.second[:arguments_proc]).uniq
+ original_method.call(*args)
+ end
+
+ mutation_request
+
+ expect(arguments).to contain_exactly([project1.id, 'test-package-1'], [project2.id, 'test-package-2'])
+ end
+ end
end
shared_examples 'denying the mutation request' do
diff --git a/spec/requests/api/graphql/mutations/packages/destroy_spec.rb b/spec/requests/api/graphql/mutations/packages/destroy_spec.rb
index 86167e7116f..6e0e5bd8aae 100644
--- a/spec/requests/api/graphql/mutations/packages/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/packages/destroy_spec.rb
@@ -35,6 +35,17 @@ RSpec.describe 'Destroying a package', feature_category: :package_registry do
.to change { ::Packages::Package.pending_destruction.count }.by(1)
end
+ context 'when npm package' do
+ let_it_be_with_reload(:package) { create(:npm_package) }
+
+ it 'enqueues the worker to sync a metadata cache' do
+ expect(Packages::Npm::CreateMetadataCacheWorker)
+ .to receive(:perform_async).with(project.id, package.name)
+
+ mutation_request
+ end
+ end
+
it_behaves_like 'returning response status', :success
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 46fc61c80d8..c304ae514a6 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -8,6 +8,12 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
let_it_be(:admin) { create(:admin) }
describe "GET /application/settings" do
+ before do
+ # Testing config file config/gitlab.yml becomes SSOT for this API
+ # see https://gitlab.com/gitlab-org/gitlab/-/issues/426091#note_1675160909
+ stub_storage_settings({ 'default' => {}, 'custom' => {} })
+ end
+
it "returns application settings" do
get api("/application/settings", admin)
@@ -15,7 +21,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
expect(json_response).to be_an Hash
expect(json_response['default_projects_limit']).to eq(42)
expect(json_response['password_authentication_enabled_for_web']).to be_truthy
- expect(json_response['repository_storages_weighted']).to eq({ 'default' => 100 })
+ expect(json_response['repository_storages_weighted']).to eq({ 'default' => 100, 'custom' => 0 })
expect(json_response['password_authentication_enabled']).to be_truthy
expect(json_response['plantuml_enabled']).to be_falsey
expect(json_response['plantuml_url']).to be_nil
@@ -109,7 +115,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
}
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['repository_storages_weighted']).to eq({ 'custom' => 75 })
+ expect(json_response['repository_storages_weighted']).to eq({ 'default' => 0, 'custom' => 75 })
end
context "repository_storages_weighted value is outside a 0-100 range" do
@@ -131,7 +137,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
default_projects_limit: 3,
default_project_creation: 2,
password_authentication_enabled_for_web: false,
- repository_storages_weighted: { 'custom' => 100 },
+ repository_storages_weighted: { 'default' => 100, 'custom' => 0 },
plantuml_enabled: true,
plantuml_url: 'http://plantuml.example.com',
diagramsnet_enabled: false,
@@ -214,7 +220,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['default_project_creation']).to eq(::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS)
expect(json_response['password_authentication_enabled_for_web']).to be_falsey
- expect(json_response['repository_storages_weighted']).to eq({ 'custom' => 100 })
+ expect(json_response['repository_storages_weighted']).to eq({ 'default' => 100, 'custom' => 0 })
expect(json_response['plantuml_enabled']).to be_truthy
expect(json_response['plantuml_url']).to eq('http://plantuml.example.com')
expect(json_response['diagramsnet_enabled']).to be_falsey
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b63ea71efe6..86c4e04ef71 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -182,6 +182,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
expect(json_response.first).not_to have_key('note')
expect(json_response.first).not_to have_key('namespace_id')
expect(json_response.first).not_to have_key('created_by')
+ expect(json_response.first).not_to have_key('email_reset_offered_at')
end
end
@@ -194,6 +195,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
expect(json_response.first).not_to have_key('note')
expect(json_response.first).not_to have_key('namespace_id')
expect(json_response.first).not_to have_key('created_by')
+ expect(json_response.first).not_to have_key('email_reset_offered_at')
end
end
@@ -203,6 +205,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
expect(response).to have_gitlab_http_status(:success)
expect(json_response.first).to have_key('note')
+ expect(json_response.first).to have_key('email_reset_offered_at')
expect(json_response.first['note']).to eq '2018-11-05 | 2FA removed | user requested | www.gitlab.com'
end
diff --git a/spec/requests/application_controller_spec.rb b/spec/requests/application_controller_spec.rb
new file mode 100644
index 00000000000..52fdf6bc69e
--- /dev/null
+++ b/spec/requests/application_controller_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ApplicationController, type: :request, feature_category: :shared do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it_behaves_like 'Base action controller' do
+ subject(:request) { get root_path }
+ end
+end
diff --git a/spec/requests/chaos_controller_spec.rb b/spec/requests/chaos_controller_spec.rb
new file mode 100644
index 00000000000..d2ce618b041
--- /dev/null
+++ b/spec/requests/chaos_controller_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ChaosController, type: :request, feature_category: :tooling do
+ it_behaves_like 'Base action controller' do
+ before do
+ # Stub leak_mem so we don't actually leak memory for the base action controller tests.
+ allow(Gitlab::Chaos).to receive(:leak_mem).with(100, 30.seconds)
+ end
+
+ subject(:request) { get leakmem_chaos_path }
+ end
+end
diff --git a/spec/requests/content_security_policy_spec.rb b/spec/requests/content_security_policy_spec.rb
deleted file mode 100644
index 3ce7e33d88a..00000000000
--- a/spec/requests/content_security_policy_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-# The AnonymousController doesn't support setting the CSP
-# This is why an arbitrary test request was chosen instead
-# of testing in application_controller_spec.
-RSpec.describe 'Content Security Policy', feature_category: :application_instrumentation do
- let(:snowplow_host) { 'snowplow.example.com' }
- let(:vite_origin) { "#{ViteRuby.instance.config.host}:#{ViteRuby.instance.config.port}" }
-
- shared_examples 'snowplow is not in the CSP' do
- it 'does not add the snowplow collector hostname to the CSP' do
- get explore_root_url
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers['Content-Security-Policy']).not_to include(snowplow_host)
- end
- end
-
- describe 'GET #explore' do
- context 'snowplow is enabled' do
- before do
- stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: snowplow_host)
- end
-
- it 'adds the snowplow collector hostname to the CSP' do
- get explore_root_url
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers['Content-Security-Policy']).to include(snowplow_host)
- end
- end
-
- context 'snowplow is enabled but host is not configured' do
- before do
- stub_application_setting(snowplow_enabled: true)
- end
-
- it_behaves_like 'snowplow is not in the CSP'
- end
-
- context 'snowplow is disabled' do
- before do
- stub_application_setting(snowplow_enabled: false, snowplow_collector_hostname: snowplow_host)
- end
-
- it_behaves_like 'snowplow is not in the CSP'
- end
-
- context 'when vite enabled during development',
- quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424334' do
- before do
- stub_rails_env('development')
- stub_feature_flags(vite: true)
-
- get explore_root_url
- end
-
- it 'adds vite csp' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers['Content-Security-Policy']).to include(vite_origin)
- end
- end
-
- context 'when vite disabled' do
- before do
- stub_feature_flags(vite: false)
-
- get explore_root_url
- end
-
- it "doesn't add vite csp" do
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers['Content-Security-Policy']).not_to include(vite_origin)
- end
- end
- end
-end
diff --git a/spec/requests/health_controller_spec.rb b/spec/requests/health_controller_spec.rb
index 639f6194af9..5fb2115aac3 100644
--- a/spec/requests/health_controller_spec.rb
+++ b/spec/requests/health_controller_spec.rb
@@ -73,7 +73,9 @@ RSpec.describe HealthController, feature_category: :database do
end
describe 'GET /-/readiness' do
- subject { get '/-/readiness', params: params, headers: headers }
+ subject(:request) { get readiness_path, params: params, headers: headers }
+
+ it_behaves_like 'Base action controller'
shared_context 'endpoint responding with readiness data' do
context 'when requesting instance-checks' do
@@ -219,7 +221,6 @@ RSpec.describe HealthController, feature_category: :database do
stub_remote_addr(whitelisted_ip)
end
- it_behaves_like 'endpoint not querying database'
it_behaves_like 'endpoint responding with readiness data'
context 'when requesting all checks' do
@@ -236,7 +237,6 @@ RSpec.describe HealthController, feature_category: :database do
stub_remote_addr(not_whitelisted_ip)
end
- it_behaves_like 'endpoint not querying database'
it_behaves_like 'endpoint not found'
end
@@ -273,7 +273,6 @@ RSpec.describe HealthController, feature_category: :database do
stub_remote_addr(whitelisted_ip)
end
- it_behaves_like 'endpoint not querying database'
it_behaves_like 'endpoint responding with liveness data'
end
@@ -282,7 +281,6 @@ RSpec.describe HealthController, feature_category: :database do
stub_remote_addr(not_whitelisted_ip)
end
- it_behaves_like 'endpoint not querying database'
it_behaves_like 'endpoint not found'
context 'accessed with valid token' do
diff --git a/spec/requests/metrics_controller_spec.rb b/spec/requests/metrics_controller_spec.rb
new file mode 100644
index 00000000000..ce96906e020
--- /dev/null
+++ b/spec/requests/metrics_controller_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MetricsController, type: :request, feature_category: :metrics do
+ it_behaves_like 'Base action controller' do
+ subject(:request) { get metrics_path }
+ end
+end
diff --git a/spec/requests/oauth/authorizations_controller_spec.rb b/spec/requests/oauth/authorizations_controller_spec.rb
index 257f238d9ef..7887bf52542 100644
--- a/spec/requests/oauth/authorizations_controller_spec.rb
+++ b/spec/requests/oauth/authorizations_controller_spec.rb
@@ -20,6 +20,10 @@ RSpec.describe Oauth::AuthorizationsController, feature_category: :system_access
end
describe 'GET #new' do
+ it_behaves_like 'Base action controller' do
+ subject(:request) { get oauth_authorization_path }
+ end
+
context 'when application redirect URI has a custom scheme' do
context 'when CSP is disabled' do
before do
diff --git a/spec/requests/registrations_controller_spec.rb b/spec/requests/registrations_controller_spec.rb
index 8b857046a4d..71f2f347f0d 100644
--- a/spec/requests/registrations_controller_spec.rb
+++ b/spec/requests/registrations_controller_spec.rb
@@ -6,7 +6,9 @@ RSpec.describe RegistrationsController, type: :request, feature_category: :syste
describe 'POST #create' do
let_it_be(:user_attrs) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) }
- subject(:create_user) { post user_registration_path, params: { user: user_attrs } }
+ subject(:request) { post user_registration_path, params: { user: user_attrs } }
+
+ it_behaves_like 'Base action controller'
context 'when email confirmation is required' do
before do
@@ -15,7 +17,7 @@ RSpec.describe RegistrationsController, type: :request, feature_category: :syste
end
it 'redirects to the `users_almost_there_path`', unless: Gitlab.ee? do
- create_user
+ request
expect(response).to redirect_to(users_almost_there_path(email: user_attrs[:email]))
end
diff --git a/spec/requests/sessions_spec.rb b/spec/requests/sessions_spec.rb
index 12939acdd91..337f358d394 100644
--- a/spec/requests/sessions_spec.rb
+++ b/spec/requests/sessions_spec.rb
@@ -7,6 +7,10 @@ RSpec.describe 'Sessions', feature_category: :system_access do
let(:user) { create(:user) }
+ it_behaves_like 'Base action controller' do
+ subject(:request) { get new_user_session_path }
+ end
+
context 'for authentication', :allow_forgery_protection do
it 'logout does not require a csrf token' do
login_as(user)
diff --git a/spec/services/packages/mark_package_for_destruction_service_spec.rb b/spec/services/packages/mark_package_for_destruction_service_spec.rb
index d65e62b84a6..bd69f995c77 100644
--- a/spec/services/packages/mark_package_for_destruction_service_spec.rb
+++ b/spec/services/packages/mark_package_for_destruction_service_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe Packages::MarkPackageForDestructionService, feature_category: :pa
context 'when it is successful' do
it 'marks the package and package files as pending destruction' do
expect(package).to receive(:sync_maven_metadata).and_call_original
+ expect(package).to receive(:sync_npm_metadata_cache).and_call_original
expect(package).to receive(:mark_package_files_for_destruction).and_call_original
expect { service.execute }.to change { package.status }.from('default').to('pending_destruction')
end
@@ -45,6 +46,7 @@ RSpec.describe Packages::MarkPackageForDestructionService, feature_category: :pa
response = service.execute
expect(package).not_to receive(:sync_maven_metadata)
+ expect(package).not_to receive(:sync_npm_metadata_cache)
expect(response).to be_a(ServiceResponse)
expect(response).to be_error
expect(response.message).to eq("Failed to mark the package as pending destruction")
diff --git a/spec/services/packages/mark_packages_for_destruction_service_spec.rb b/spec/services/packages/mark_packages_for_destruction_service_spec.rb
index 22278f9927d..cd6426d39ad 100644
--- a/spec/services/packages/mark_packages_for_destruction_service_spec.rb
+++ b/spec/services/packages/mark_packages_for_destruction_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline, feature_category: :package_registry do
let_it_be(:project) { create(:project) }
- let_it_be_with_reload(:packages) { create_list(:npm_package, 3, project: project) }
+ let_it_be_with_reload(:packages) { create_list(:nuget_package, 3, project: project) }
let(:user) { project.owner }
@@ -15,6 +15,17 @@ RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline, fea
describe '#execute' do
subject { service.execute }
+ shared_examples 'returning service response' do |status:, message:, reason: nil|
+ it 'returns service response' do
+ subject
+
+ expect(subject).to be_a(ServiceResponse)
+ expect(subject.status).to eq(status)
+ expect(subject.message).to eq(message)
+ expect(subject.reason).to eq(reason) if reason
+ end
+ end
+
context 'when the user is authorized' do
before do
project.add_maintainer(user)
@@ -23,16 +34,16 @@ RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline, fea
context 'when it is successful' do
it 'marks the packages as pending destruction' do
expect(::Packages::Maven::Metadata::SyncService).not_to receive(:new)
+ expect(::Packages::Npm::CreateMetadataCacheService).not_to receive(:new)
expect { subject }.to change { ::Packages::Package.pending_destruction.count }.from(0).to(3)
.and change { Packages::PackageFile.pending_destruction.count }.from(0).to(3)
packages.each { |pkg| expect(pkg.reload).to be_pending_destruction }
-
- expect(subject).to be_a(ServiceResponse)
- expect(subject).to be_success
- expect(subject.message).to eq('Packages were successfully marked as pending destruction')
end
+ it_behaves_like 'returning service response', status: :success,
+ message: 'Packages were successfully marked as pending destruction'
+
context 'with maven packages' do
let_it_be_with_reload(:packages) { create_list(:maven_package, 3, project: project) }
@@ -42,12 +53,11 @@ RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline, fea
expect { subject }.to change { ::Packages::Package.pending_destruction.count }.from(0).to(3)
.and change { Packages::PackageFile.pending_destruction.count }.from(0).to(9)
packages.each { |pkg| expect(pkg.reload).to be_pending_destruction }
-
- expect(subject).to be_a(ServiceResponse)
- expect(subject).to be_success
- expect(subject.message).to eq('Packages were successfully marked as pending destruction')
end
+ it_behaves_like 'returning service response', status: :success,
+ message: 'Packages were successfully marked as pending destruction'
+
context 'without version' do
before do
::Packages::Package.id_in(package_ids).update_all(version: nil)
@@ -59,13 +69,27 @@ RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline, fea
expect { subject }.to change { ::Packages::Package.pending_destruction.count }.from(0).to(3)
.and change { Packages::PackageFile.pending_destruction.count }.from(0).to(9)
packages.each { |pkg| expect(pkg.reload).to be_pending_destruction }
-
- expect(subject).to be_a(ServiceResponse)
- expect(subject).to be_success
- expect(subject.message).to eq('Packages were successfully marked as pending destruction')
end
+
+ it_behaves_like 'returning service response', status: :success,
+ message: 'Packages were successfully marked as pending destruction'
end
end
+
+ context 'with npm packages' do
+ let_it_be_with_reload(:packages) { create_list(:npm_package, 3, project: project, name: 'test-package') }
+
+ it 'marks the packages as pending destruction' do
+ expect(::Packages::Npm::CreateMetadataCacheService).to receive(:new).once.and_call_original
+
+ expect { subject }.to change { ::Packages::Package.pending_destruction.count }.from(0).to(3)
+ .and change { Packages::PackageFile.pending_destruction.count }.from(0).to(3)
+ packages.each { |package| expect(package.reload).to be_pending_destruction }
+ end
+
+ it_behaves_like 'returning service response', status: :success,
+ message: 'Packages were successfully marked as pending destruction'
+ end
end
context 'when it is not successful' do
@@ -73,7 +97,7 @@ RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline, fea
allow(service).to receive(:can_destroy_packages?).and_raise(StandardError, 'test')
end
- it 'returns an error ServiceResponse' do
+ it 'does not mark the packages as pending destruction' do
expect(::Packages::Maven::Metadata::SyncService).not_to receive(:new)
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
@@ -83,30 +107,25 @@ RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline, fea
expect { subject }.to not_change { ::Packages::Package.pending_destruction.count }
.and not_change { ::Packages::PackageFile.pending_destruction.count }
-
- expect(subject).to be_a(ServiceResponse)
- expect(subject).to be_error
- expect(subject.message).to eq("Failed to mark the packages as pending destruction")
- expect(subject.status).to eq(:error)
end
+
+ it_behaves_like 'returning service response', status: :error,
+ message: 'Failed to mark the packages as pending destruction'
end
end
context 'when the user is not authorized' do
let(:user) { nil }
- it 'returns an error ServiceResponse' do
+ it 'does not mark the packages as pending destruction' do
expect(::Packages::Maven::Metadata::SyncService).not_to receive(:new)
expect { subject }.to not_change { ::Packages::Package.pending_destruction.count }
.and not_change { ::Packages::PackageFile.pending_destruction.count }
-
- expect(subject).to be_a(ServiceResponse)
- expect(subject).to be_error
- expect(subject.message).to eq("You don't have the permission to perform this action")
- expect(subject.status).to eq(:error)
- expect(subject.reason).to eq(:unauthorized)
end
+
+ it_behaves_like 'returning service response', status: :error, reason: :unauthorized,
+ message: "You don't have the permission to perform this action"
end
end
end
diff --git a/spec/services/service_desk/custom_email_verifications/update_service_spec.rb b/spec/services/service_desk/custom_email_verifications/update_service_spec.rb
index 9a51f645b4d..103caf0e6c5 100644
--- a/spec/services/service_desk/custom_email_verifications/update_service_spec.rb
+++ b/spec/services/service_desk/custom_email_verifications/update_service_spec.rb
@@ -27,6 +27,9 @@ RSpec.describe ServiceDesk::CustomEmailVerifications::UpdateService, feature_cat
before do
allow(message_delivery).to receive(:deliver_later)
allow(Notify).to receive(:service_desk_verification_result_email).and_return(message_delivery)
+
+ stub_incoming_email_setting(enabled: true, address: 'support+%{key}@example.com')
+ stub_service_desk_email_setting(enabled: true, address: 'contact+%{key}@example.com')
end
shared_examples 'a failing verification process' do |expected_error_identifier|
@@ -118,7 +121,34 @@ RSpec.describe ServiceDesk::CustomEmailVerifications::UpdateService, feature_cat
verification.update!(token: 'ZROT4ZZXA-Y6') # token from email fixture
end
- let(:email_raw) { email_fixture('emails/service_desk_custom_email_address_verification.eml') }
+ let(:service_desk_address) { project.service_desk_incoming_address }
+ let(:verification_address) { 'custom-support-email+verify@example.com' }
+ let(:verification_token) { 'ZROT4ZZXA-Y6' }
+ let(:shared_email_raw) do
+ <<~EMAIL
+ From: Flight Support
+ Subject: Verify custom email address custom-support-email@example.com for Flight
+ Auto-Submitted: no
+
+
+ This email is auto-generated. It verifies the ownership of the entered Service Desk custom email address and
+ correct functionality of email forwarding.
+
+ Verification token: #{verification_token}
+ --
+
+ You're receiving this email because of your account on 127.0.0.1.
+ EMAIL
+ end
+
+ let(:email_raw) do
+ <<~EMAIL
+ Delivered-To: #{service_desk_address}
+ To: #{verification_address}
+ #{shared_email_raw}
+ EMAIL
+ end
+
let(:mail_object) { Mail::Message.new(email_raw) }
it 'verifies and sends result emails' do
@@ -160,6 +190,38 @@ RSpec.describe ServiceDesk::CustomEmailVerifications::UpdateService, feature_cat
it_behaves_like 'a failing verification process', 'mail_not_received_within_timeframe'
end
+ context 'and service desk address from service_desk_email was used as forwarding target' do
+ let(:service_desk_address) { project.service_desk_alias_address }
+
+ it_behaves_like 'a failing verification process', 'incorrect_forwarding_target'
+
+ context 'when multiple Delivered-To headers are present' do
+ let(:email_raw) do
+ <<~EMAIL
+ Delivered-To: other@example.com
+ Delivered-To: #{service_desk_address}
+ To: #{verification_address}
+ #{shared_email_raw}
+ EMAIL
+ end
+
+ it_behaves_like 'a failing verification process', 'incorrect_forwarding_target'
+ end
+
+ context 'when multiple To headers are present' do
+ # Microsoft Exchange forwards emails this way when forwarding
+ # to an external email address using a transport rule
+ let(:email_raw) do
+ <<~EMAIL
+ To: #{service_desk_address}, #{verification_address}
+ #{shared_email_raw}
+ EMAIL
+ end
+
+ it_behaves_like 'a failing verification process', 'incorrect_forwarding_target'
+ end
+ end
+
context 'when already verified' do
let(:expected_error_message) { error_already_finished }
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index d6fa1d60dad..fe84a80dae6 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -8148,7 +8148,6 @@
- './spec/requests/api/users_spec.rb'
- './spec/requests/api/wikis_spec.rb'
- './spec/requests/concerns/planning_hierarchy_spec.rb'
-- './spec/requests/content_security_policy_spec.rb'
- './spec/requests/dashboard_controller_spec.rb'
- './spec/requests/dashboard/projects_controller_spec.rb'
- './spec/requests/git_http_spec.rb'
diff --git a/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb
new file mode 100644
index 00000000000..2eab533ef7f
--- /dev/null
+++ b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+# Requires `request` subject to be defined
+#
+# subject(:request) { get root_path }
+RSpec.shared_examples 'Base action controller' do
+ describe 'security headers' do
+ describe 'Cross-Security-Policy' do
+ context 'when configuring snowplow' do
+ let(:snowplow_host) { 'snowplow.example.com' }
+
+ shared_examples 'snowplow is not in the CSP' do
+ it 'does not add the snowplow collector hostname to the CSP' do
+ request
+
+ expect(response.headers['Content-Security-Policy']).not_to include(snowplow_host)
+ end
+ end
+
+ context 'when snowplow is enabled' do
+ before do
+ stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: snowplow_host)
+ end
+
+ it 'adds snowplow to the csp' do
+ request
+
+ expect(response.headers['Content-Security-Policy']).to include(snowplow_host)
+ end
+ end
+
+ context 'when snowplow is enabled but host is not configured' do
+ before do
+ stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: nil)
+ end
+
+ it_behaves_like 'snowplow is not in the CSP'
+ end
+
+ context 'when snowplow is disabled' do
+ before do
+ stub_application_setting(snowplow_enabled: false, snowplow_collector_hostname: snowplow_host)
+ end
+
+ it_behaves_like 'snowplow is not in the CSP'
+ end
+ end
+
+ context 'when configuring vite' do
+ let(:vite_origin) { "#{ViteRuby.instance.config.host}:#{ViteRuby.instance.config.port}" }
+
+ context 'when vite enabled during development',
+ skip: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424334' do
+ before do
+ stub_rails_env('development')
+ stub_feature_flags(vite: true)
+ end
+
+ it 'adds vite csp' do
+ request
+
+ expect(response.headers['Content-Security-Policy']).to include(vite_origin)
+ end
+ end
+
+ context 'when vite disabled' do
+ before do
+ stub_feature_flags(vite: false)
+ end
+
+ it "doesn't add vite csp" do
+ request
+
+ expect(response.headers['Content-Security-Policy']).not_to include(vite_origin)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/workhorse/go.mod b/workhorse/go.mod
index 35ccb284754..fc29ef136ba 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -3,9 +3,9 @@ module gitlab.com/gitlab-org/gitlab/workhorse
go 1.20
require (
- github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
+ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0
github.com/BurntSushi/toml v1.3.2
- github.com/alecthomas/chroma/v2 v2.9.1
+ github.com/alecthomas/chroma/v2 v2.11.1
github.com/aws/aws-sdk-go v1.45.20
github.com/disintegration/imaging v1.6.2
github.com/getsentry/raven-go v0.2.0
@@ -22,14 +22,14 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/smartystreets/goconvey v1.8.1
github.com/stretchr/testify v1.8.4
- gitlab.com/gitlab-org/gitaly/v16 v16.4.1
+ gitlab.com/gitlab-org/gitaly/v16 v16.6.1
gitlab.com/gitlab-org/labkit v1.21.0
gocloud.dev v0.34.0
golang.org/x/image v0.7.0
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.10.0
- golang.org/x/tools v0.13.0
+ golang.org/x/tools v0.14.0
google.golang.org/grpc v1.58.3
google.golang.org/protobuf v1.31.0
honnef.co/go/tools v0.4.6
@@ -77,6 +77,7 @@ require (
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
+ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0 // indirect
github.com/hashicorp/yamux v0.1.2-0.20220728231024-8f49b6f63f18 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
@@ -111,8 +112,8 @@ require (
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
- golang.org/x/mod v0.12.0 // indirect
- golang.org/x/sync v0.3.0 // indirect
+ golang.org/x/mod v0.13.0 // indirect
+ golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
diff --git a/workhorse/go.sum b/workhorse/go.sum
index 99006c779e8..cc065837dd5 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -68,8 +68,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9Orh
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8=
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg=
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4=
+github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY=
+github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
@@ -90,8 +90,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
-github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU=
-github.com/alecthomas/chroma/v2 v2.9.1/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
+github.com/alecthomas/chroma/v2 v2.11.1 h1:m9uUtgcdAwgfFNxuqj7AIG75jD2YmL61BBIJWtdzJPs=
+github.com/alecthomas/chroma/v2 v2.11.1/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/aws/aws-sdk-go v1.44.256/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
@@ -297,6 +297,8 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0 h1:2cz5kSrxzMYHiWOBbKj8itQm+nRykkB8aMv4ThcHYHA=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
@@ -345,7 +347,7 @@ github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9v
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
-github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
+github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -450,8 +452,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
-gitlab.com/gitlab-org/gitaly/v16 v16.4.1 h1:Qh5TFK+Jy/mBV8hCfNro2VCqRrhgt3M2iTrdYVF5N6o=
-gitlab.com/gitlab-org/gitaly/v16 v16.4.1/go.mod h1:TdN/Q3OqxU75pcp8V5YWpnE8Gk6dagwlC/HefNnW1IE=
+gitlab.com/gitlab-org/gitaly/v16 v16.6.1 h1:/FW8yZ0nPIa9wCO2aLmSC2nZpKmL5ZaOrRrFaXBfnPw=
+gitlab.com/gitlab-org/gitaly/v16 v16.6.1/go.mod h1:LbekHBeRUnb1jDQCXIUSNebuPMzVTq8B9PXXp4xVOF4=
gitlab.com/gitlab-org/labkit v1.21.0 h1:hLmdBDtXjD1yOmZ+uJOac3a5Tlo83QaezwhES4IYik4=
gitlab.com/gitlab-org/labkit v1.21.0/go.mod h1:zeATDAaSBelPcPLbTTq8J3ZJEHyPTLVBM1q3nva+/W4=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
@@ -494,7 +496,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE=
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@@ -529,8 +531,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
-golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
+golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -607,8 +609,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
-golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
+golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -761,8 +763,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
-golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
-golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
+golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=