Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-12-07 15:12:19 +00:00
parent 8e81ce5076
commit 9a940dabf0
81 changed files with 826 additions and 380 deletions

View File

@ -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'

View File

@ -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');

View File

@ -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"
/>
<slot name="modal-footer"></slot>
</div>

View File

@ -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 {
<p class="gl-mb-0">
<strong>{{ errorLabel }}</strong>
</p>
<p>{{ errorDescription }}</p>
<p>
<gl-sprintf :message="errorDescription">
<template #incomingEmail>
<code>{{ incomingEmail }}</code>
</template>
</gl-sprintf>
</p>
</template>
<p>{{ resetNote }}</p>

View File

@ -216,6 +216,7 @@ export default {
<custom-email
v-if="customEmail"
:incoming-email="incomingEmail"
:custom-email="customEmail"
:smtp-address="smtpAddress"
:verification-state="verificationState"

View File

@ -132,6 +132,12 @@ export const I18N_ERROR_READ_TIMEOUT_LABEL = s__('ServiceDesk|Read timeout');
export const I18N_ERROR_READ_TIMEOUT_DESC = s__(
'ServiceDesk|The SMTP server did not respond in time.',
);
export const I18N_ERROR_INCORRECT_FORWARDING_TARGET_LABEL = s__(
'ServiceDesk|Incorrect forwarding target',
);
export const I18N_ERROR_INCORRECT_FORWARDING_TARGET_DESC = s__(
'ServiceDesk|Forward all emails to the custom email address to %{incomingEmail}.',
);
export const I18N_VERIFICATION_ERRORS = {
smtp_host_issue: {
@ -158,4 +164,8 @@ export const I18N_VERIFICATION_ERRORS = {
label: I18N_ERROR_READ_TIMEOUT_LABEL,
description: I18N_ERROR_READ_TIMEOUT_DESC,
},
incorrect_forwarding_target: {
label: I18N_ERROR_INCORRECT_FORWARDING_TARGET_LABEL,
description: I18N_ERROR_INCORRECT_FORWARDING_TARGET_DESC,
},
};

View File

@ -57,7 +57,7 @@ export default {
type: Function,
required: true,
},
fetchInitialSelectionText: {
fetchInitialSelection: {
type: Function,
required: false,
default: null,
@ -77,35 +77,23 @@ export default {
searchString: '',
items: [],
page: 1,
selectedValue: null,
selectedText: null,
selected: this.initialSelection || '',
initialSelectedItem: {},
errorMessage: '',
};
},
computed: {
selected: {
set(value) {
this.selectedValue = value;
this.selectedText =
value === null ? null : this.items.find((item) => 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 {
<slot name="list-item" :item="item"></slot>
</template>
</gl-collapsible-listbox>
<input :id="inputId" data-testid="input" type="hidden" :name="inputName" :value="inputValue" />
<input :id="inputId" data-testid="input" type="hidden" :name="inputName" :value="selected" />
</gl-form-group>
</template>

View File

@ -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"
>
<template #error>

View File

@ -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"
>

View File

@ -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"

View File

@ -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'),

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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?

View File

@ -11,6 +11,7 @@
- verify_email_address = @service_desk_setting.custom_email_address_for_verification
- code_open = '<code>'.html_safe
- code_end = '</code>'.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 }

View File

@ -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: '' } %>

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
b2ddeca2009bfa06b7672e816f018047b5191492c612713cec8a12c17c6c20b5

View File

@ -0,0 +1 @@
b3129b32e869fd6420421a13a8ae0cae873dd89cef90bfbced738884069a7445

View File

@ -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';

View File

@ -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

View File

@ -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.

View File

@ -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 |

View File

@ -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)

View File

@ -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
}
```

View File

@ -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:

View File

@ -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.

View File

@ -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 <kbd>/</kbd> key to enable.
1. On the left sidebar, select **Search or go to** or use the <kbd>/</kbd> key to enable.
1. Type one of the special characters:
- <kbd>></kbd> - Create a new object or find a menu item.

View File

@ -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

View File

@ -7,6 +7,7 @@ module API
expose :note
expose :namespace_id
expose :created_by, with: UserBasic
expose :email_reset_offered_at
end
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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}; \

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -49,7 +49,7 @@ describe('DeleteModal', () => {
attributes: {
variant: 'danger',
disabled: true,
'data-qa-selector': 'confirm_delete_button',
'data-testid': 'confirm-delete-button',
},
},
actionCancel: {

View File

@ -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);
});
});

View File

@ -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,

View File

@ -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({});
});
});
});

View File

@ -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: '<div><slot /></div>',
};
// 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);

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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 <custom-support-email@example.com>
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 }

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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=