Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8e81ce5076
commit
9a940dabf0
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -216,6 +216,7 @@ export default {
|
|||
|
||||
<custom-email
|
||||
v-if="customEmail"
|
||||
:incoming-email="incomingEmail"
|
||||
:custom-email="customEmail"
|
||||
:smtp-address="smtpAddress"
|
||||
:verification-state="verificationState"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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: '' } %>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
b2ddeca2009bfa06b7672e816f018047b5191492c612713cec8a12c17c6c20b5
|
||||
|
|
@ -0,0 +1 @@
|
|||
b3129b32e869fd6420421a13a8ae0cae873dd89cef90bfbced738884069a7445
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module API
|
|||
expose :note
|
||||
expose :namespace_id
|
||||
expose :created_by, with: UserBasic
|
||||
expose :email_reset_offered_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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}; \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ describe('DeleteModal', () => {
|
|||
attributes: {
|
||||
variant: 'danger',
|
||||
disabled: true,
|
||||
'data-qa-selector': 'confirm_delete_button',
|
||||
'data-testid': 'confirm-delete-button',
|
||||
},
|
||||
},
|
||||
actionCancel: {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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=
|
||||
|
|
|
|||
Loading…
Reference in New Issue