Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-06-12 00:12:54 +00:00
parent 2848e0bf51
commit ab5132651a
182 changed files with 1535 additions and 704 deletions

View File

@ -2,6 +2,27 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 18.0.2 (2025-06-11)
### Fixed (5 changes)
- [Fix Upgrade to 18.0: No such column](https://gitlab.com/gitlab-org/security/gitlab/-/commit/bde20c3f31d324493d032be57be4465f0919760e)
- [Fix IDE links returns about:blank in old code dropdown](https://gitlab.com/gitlab-org/security/gitlab/-/commit/633864727f574f9d9b93826bb76d66a790382915)
- [Fix the title/body issue for todo apis when it is a duo todo](https://gitlab.com/gitlab-org/security/gitlab/-/commit/d8080ea15af34cf804ce024b207f2fa4817c87a6) **GitLab Enterprise Edition**
- [Fix gitpod button is missing in the edit dropdown](https://gitlab.com/gitlab-org/security/gitlab/-/commit/4bbef760c63924f2821233d98dc04c1982751430)
- [Move fork_networks organization_id NOT NULL to post-migrate](https://gitlab.com/gitlab-org/security/gitlab/-/commit/2bbea09c16044981bf316dd43544a87e4bf67147)
### Security (8 changes)
- [Protect webhook from excessive payload lengths](https://gitlab.com/gitlab-org/security/gitlab/-/commit/990fae5b6be86c6769c2086578ae2096762e21a8) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5059))
- [Endless Redirect Loop in any project when query param "format" is "git"](https://gitlab.com/gitlab-org/security/gitlab/-/commit/fdbfb6cd14973800abeec182823bcfa647a1a5a8) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5038))
- [Backport for "Add validation for board name length" to 18-0 stable](https://gitlab.com/gitlab-org/security/gitlab/-/commit/ba616a03359751fc3add6f8504c79f4381efa703) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5044))
- [Fix # #1329 - IDOR in compliance framework export endpoint](https://gitlab.com/gitlab-org/security/gitlab/-/commit/ffea57e8e171b120f5f66fe81da39a21e5ab0258) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5041))
- [Fix authorization for compliance frameworks projects](https://gitlab.com/gitlab-org/security/gitlab/-/commit/0d783852162009bc5286a939534f2a5e2f1ae7ef) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5034))
- [security: Git redirection inconsistency](https://gitlab.com/gitlab-org/security/gitlab/-/commit/3fb95759edb3e7729b981bf48140ef9a05a32761) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5020))
- [Fix XSS with CSP bypass in JSON tables](https://gitlab.com/gitlab-org/security/gitlab/-/commit/fcfebf2f188ed90eea3f7db92ebeedcbadc6504d) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5022))
- [Limit HTTP response size](https://gitlab.com/gitlab-org/security/gitlab/-/commit/f8bf80825e1bd802be7be374905600059abd2726) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5014))
## 18.0.1 (2025-05-21)
### Fixed (1 change)
@ -856,6 +877,24 @@ entry.
- [Finalize migration BackfillContainerRepositoryStatesProjectId](https://gitlab.com/gitlab-org/gitlab/-/commit/78f333c76a39d0a85938318b3be49905c19074e6) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185869))
- [Finalize migration BackfillPackagesRpmMetadataProjectId](https://gitlab.com/gitlab-org/gitlab/-/commit/d066d88be1fff7cfcf64017124af797e085a4b4f) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/184553))
## 17.11.4 (2025-06-11)
### Fixed (2 changes)
- [Fix gitpod button is missing in the edit dropdown](https://gitlab.com/gitlab-org/security/gitlab/-/commit/813a005dc240c1bfafc313ded694317a96f1a877)
- [Attempt to migrate ci_runner_taggings table (try 2)](https://gitlab.com/gitlab-org/security/gitlab/-/commit/706a075f79838d5d8421c5eae2e96a7601164201)
### Security (8 changes)
- [Protect webhook from excessive payload lengths](https://gitlab.com/gitlab-org/security/gitlab/-/commit/a0d74cdeed26661b221446efc90fb5bd19b54d95) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5060))
- [Endless Redirect Loop in any project when query param "format" is "git"](https://gitlab.com/gitlab-org/security/gitlab/-/commit/24d25f0b270337679bcfe282370ad169d137471f) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5039))
- [Backport for "Add validation for board name length" to 17-11-stable](https://gitlab.com/gitlab-org/security/gitlab/-/commit/5ed051286369ec256431faeb44a16c848b6d0edc) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5045))
- [Fix # #1329 - IDOR in compliance framework export endpoint](https://gitlab.com/gitlab-org/security/gitlab/-/commit/071c88429e0974fdf1c0d67e7ba9d1f419843244) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5043))
- [security: Git redirection inconsistency](https://gitlab.com/gitlab-org/security/gitlab/-/commit/373f9840af59eae05b14ea200fa10c1e4ecd7367) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5019))
- [Fix XSS with CSP bypass in JSON tables](https://gitlab.com/gitlab-org/security/gitlab/-/commit/862a14acb446e9f7ce962404d8d472b19d832ff8) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4987))
- [Limit HTTP response size](https://gitlab.com/gitlab-org/security/gitlab/-/commit/94d20db29203681d75da5642fe4d1da51238863e) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5015))
- [Fix authorization for compliance frameworks projects](https://gitlab.com/gitlab-org/security/gitlab/-/commit/0eecdfe1df4254e2674efe9c0e309d9325db5c4b) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5035))
## 17.11.3 (2025-05-21)
### Fixed (1 change)
@ -1683,6 +1722,23 @@ entry.
- [Remove feature flag allow_merge_request_pipelines_from_fork](https://gitlab.com/gitlab-org/gitlab/-/commit/b62f9187a57cc5ba66ce26889516cc55a425181a) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182862))
- [Finalize migration BackfillNewAuditEventTables](https://gitlab.com/gitlab-org/gitlab/-/commit/1bc0f07ffd3af5b9fab8a0ea0b1af5f2759d25db) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181881))
## 17.10.8 (2025-06-11)
### Fixed (2 changes)
- [Fix gitpod button is missing in the edit dropdown](https://gitlab.com/gitlab-org/security/gitlab/-/commit/c3ad6f66e6f17a5bf8fa2489a7335dfa58fc55a6)
- [Attempt to migrate ci_runner_taggings table (try 2)](https://gitlab.com/gitlab-org/security/gitlab/-/commit/c2520ea439dcb4fee531fcc39efc85ab4b607a6c)
### Security (7 changes)
- [Protect webhook from excessive payload lengths](https://gitlab.com/gitlab-org/security/gitlab/-/commit/1fb7390786ae5c22ec7f1bc172423a76835aa14c) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5061))
- [Endless Redirect Loop in any project when query param "format" is "git"](https://gitlab.com/gitlab-org/security/gitlab/-/commit/fddb00a30506eb534dc9e1f5c1923eee3e33c0b3) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5040))
- [Backport for "Add validation for board name length" to 17-10-stable](https://gitlab.com/gitlab-org/security/gitlab/-/commit/a69cf8ef367ef1897158af0619cd537fe5d2a5df) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5046))
- [Fix # #1329 - IDOR in compliance framework export endpoint](https://gitlab.com/gitlab-org/security/gitlab/-/commit/7b4f9e9fb7411a18185ada44dc88dd264e6a228b) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5042))
- [security: Git redirection inconsistency](https://gitlab.com/gitlab-org/security/gitlab/-/commit/12003cbfb9b4081a352724922e6ed9aa97656ace) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4900))
- [Fix XSS with CSP bypass in JSON tables](https://gitlab.com/gitlab-org/security/gitlab/-/commit/1b02f9ed79b3a999baae5c02fa4f26c487927cba) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4988))
- [Limit HTTP response size](https://gitlab.com/gitlab-org/security/gitlab/-/commit/1411cb581f68400b5370d694cce3c67e5f0e2294) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5016))
## 17.10.7 (2025-05-21)
### Security (9 changes)

View File

@ -1 +1 @@
3a09d6c84e9ac6d32832372afd9c6100a56a2b24
060d5cb6dbc76ef1344a8fabf21cbae551ecab90

View File

@ -2,7 +2,7 @@
import { GlTable, GlFormInput } from '@gitlab/ui';
import { memoize } from 'lodash';
import { __ } from '~/locale';
import { sanitize } from '~/lib/dompurify';
import { sanitize, defaultConfig } from '~/lib/dompurify';
import SafeHtml from '~/vue_shared/directives/safe_html';
const domParser = new DOMParser();
@ -74,6 +74,11 @@ export default {
return `cell(${field.key})`;
},
},
safeHtmlConfig: {
...defaultConfig,
FORBID_ATTR: [...defaultConfig.FORBID_ATTR, 'class', 'style'],
ALLOW_DATA_ATTR: false,
},
};
</script>
<template>
@ -92,11 +97,11 @@ export default {
class="!gl-mt-0"
>
<template v-if="isHtmlSafe" #cell()="data">
<div v-safe-html="data.value"></div>
<div v-safe-html:[$options.safeHtmlConfig]="data.value"></div>
</template>
<template v-else #cell()="data">{{ data.value }}</template>
<template v-if="caption" #table-caption>
<small v-if="isHtmlSafe" v-safe-html="caption"></small>
<small v-if="isHtmlSafe" v-safe-html:[$options.safeHtmlConfig]="caption"></small>
<small v-else>{{ caption }}</small>
</template>
</gl-table>

View File

@ -29,8 +29,8 @@ const mountParseError = (element) => {
});
};
const mountJSONTableVueComponent = (userData, element) => {
const { fields = [], items = [], filter, caption, isHtmlSafe } = userData;
const mountJSONTableVueComponent = (userData, element, isHtmlSafe = false) => {
const { fields = [], items = [], filter, caption } = userData;
const container = document.createElement('div');
element.classList.add('js-json-table');
@ -97,7 +97,7 @@ const renderTableHTML = (element) => {
),
);
mountJSONTableVueComponent({ fields, filter, caption, items, isHtmlSafe: markdown }, parent);
mountJSONTableVueComponent({ fields, filter, caption, items }, parent, Boolean(markdown));
} catch (e) {
mountParseError(parent);
}

View File

@ -34,7 +34,7 @@ export default {
i18n: {
inputs: {
name: {
placeholder: __('My awesome group'),
placeholder: __('My group'),
description: s__(
'Groups|Must start with letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses.',
),

View File

@ -96,7 +96,7 @@ export default {
],
inputAttrs: {
width: { md: 'lg' },
placeholder: __('My awesome group'),
placeholder: __('My group'),
},
groupAttrs: {
description: s__(

View File

@ -173,7 +173,7 @@ export default {
:pattern="$options.projectNamePattern"
name="name"
required
:placeholder="s__('ProjectsNew|My awesome project')"
:placeholder="s__('ProjectsNew|My project')"
data-testid="project-name"
/>
</gl-form-group>

View File

@ -9,7 +9,9 @@ export default {
i18n: {
settingsBlock: {
title: __('Advanced'),
description: s__('Organization|Perform advanced options such as deleting the organization.'),
description: s__(
'Organization|Perform advanced options such as changing the organization URL.',
),
},
},
props: {

View File

@ -16,9 +16,6 @@ export default {
inject: ['organization'],
i18n: {
cardHeaderTitle: s__('Organization|Change organization URL'),
cardHeaderDescription: s__(
"Organization|Changing an organization's URL can have unintended side effects.",
),
submitButtonText: s__('Organization|Change organization URL'),
errorMessage: s__(
'Organization|An error occurred changing your organization URL. Please try again.',
@ -45,11 +42,6 @@ export default {
errors: [],
};
},
computed: {
isSubmitButtonDisabled() {
return this.formValues.path === this.organization.path;
},
},
methods: {
async onSubmit() {
this.errors = [];
@ -84,7 +76,6 @@ export default {
]);
} catch (error) {
createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
} finally {
this.loading = false;
}
},
@ -100,7 +91,6 @@ export default {
<div class="gl-flex gl-grow">
<h4 class="gl-m-0 gl-text-base gl-leading-24">{{ $options.i18n.cardHeaderTitle }}</h4>
</div>
<p class="gl-m-0 gl-text-sm gl-text-subtle">{{ $options.i18n.cardHeaderDescription }}</p>
</template>
<gl-form :id="$options.formId">
<gl-form-fields
@ -120,14 +110,9 @@ export default {
</template>
</gl-form-fields>
<div class="gl-flex gl-gap-3">
<gl-button
type="submit"
variant="danger"
class="js-no-auto-disable"
:loading="loading"
:disabled="isSubmitButtonDisabled"
>{{ $options.i18n.submitButtonText }}</gl-button
>
<gl-button type="submit" variant="danger" class="js-no-auto-disable" :loading="loading">{{
$options.i18n.submitButtonText
}}</gl-button>
</div>
</gl-form>
</gl-card>

View File

@ -1,4 +1,5 @@
<script>
import { GlSprintf } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { createAlert } from '~/alert';
import { visitUrlWithAlerts } from '~/lib/utils/url_utility';
@ -11,19 +12,22 @@ import {
} from '~/organizations/shared/constants';
import FormErrorsAlert from '~/organizations/shared/components/errors_alert.vue';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
import HelpPageLink from '~/vue_shared/components/help_page_link/help_page_link.vue';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPE_ORGANIZATION } from '~/graphql_shared/constants';
import organizationUpdateMutation from '../graphql/mutations/organization_update.mutation.graphql';
export default {
name: 'OrganizationSettings',
components: { NewEditForm, SettingsBlock, FormErrorsAlert },
components: { NewEditForm, SettingsBlock, FormErrorsAlert, HelpPageLink, GlSprintf },
inject: ['organization'],
i18n: {
submitButtonText: __('Save changes'),
settingsBlock: {
title: s__('Organization|Organization settings'),
description: s__('Organization|Update your organization name, description, and avatar.'),
description: s__(
'Organization|Update your organization name, description, and avatar. %{linkStart}Learn more about organizations%{linkEnd}.',
),
},
errorMessage: s__(
'Organization|An error occurred updating your organization. Please try again.',
@ -116,7 +120,15 @@ export default {
:title="$options.i18n.settingsBlock.title"
@toggle-expand="$emit('toggle-expand', $event)"
>
<template #description>{{ $options.i18n.settingsBlock.description }}</template>
<template #description>
<gl-sprintf :message="$options.i18n.settingsBlock.description">
<template #link="{ content }">
<help-page-link href="user/organization/_index.md" target="_blank">{{
content
}}</help-page-link>
</template>
</gl-sprintf>
</template>
<template #default>
<form-errors-alert v-model="errors" :scroll-on-error="true" />
<new-edit-form

View File

@ -85,7 +85,7 @@ export default {
},
inputAttrs: {
class: 'gl-md-form-input-lg',
placeholder: s__('ProjectsNewEdit|My awesome project'),
placeholder: s__('ProjectsNewEdit|My project'),
},
},
[FORM_FIELD_ID]: {

View File

@ -141,7 +141,7 @@ export default {
:state="form.fields['project[name]'].state"
name="project[name]"
required
:placeholder="s__('ProjectsNew|My awesome project')"
:placeholder="s__('ProjectsNew|My project')"
data-testid="project-name-input"
@input="updateSlug"
/>

View File

@ -2,6 +2,7 @@
import { GlLink, GlLabel } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
import { mapState } from 'vuex';
import { sanitize } from '~/lib/dompurify';
import GlSafeHtmlDirective from '~/vue_shared/directives/safe_html';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
@ -58,19 +59,21 @@ export default {
computed: {
...mapState(['query']),
gfmCopyText() {
return `\`${this.filePath}\``;
return `\`${this.purePath(this.filePath)}\``;
},
highlightedFilePath() {
const cleanFilePath = this.purePath(this.filePath);
if (!this?.query?.search) {
return this.filePath;
return cleanFilePath;
}
if (containsPotentialRegex(this.query.search)) {
return this.filePath;
return cleanFilePath;
}
const regex = new RegExp(`(${this.query.search})`, 'g');
return this.filePath.replace(
return cleanFilePath.replace(
regex,
(match, p1) =>
`<span class="highlight-search-term ${this.systemMatchCodeTheme}">${p1}</span>`,
@ -93,6 +96,9 @@ export default {
trackHeaderClick() {
this.trackEvent(EVENT_CLICK_HEADER_LINK);
},
purePath(path) {
return sanitize(path, { ALLOWED_TAGS: [] });
},
},
DEFAULT_HEADER_LABEL_COLOR,
};

View File

@ -287,14 +287,16 @@ export default {
});
}
const selectedWorkItemType = this.workItemTypes?.find(
(workItemType) => workItemType.name === this.preselectedWorkItemType,
);
const selectedWorkItemType = this.findWorkItemType(this.preselectedWorkItemType);
if (selectedWorkItemType) {
this.selectedWorkItemTypeId = selectedWorkItemType?.id;
} else {
this.showWorkItemTypeSelect = true;
const defaultSelectedWorkItemType =
this.findWorkItemType(WORK_ITEM_TYPE_NAME_ISSUE) || this.workItemTypes?.at(0);
this.selectedWorkItemTypeId = defaultSelectedWorkItemType?.id;
this.$emit('changeType', defaultSelectedWorkItemType);
}
},
error() {
@ -606,6 +608,9 @@ export default {
document.removeEventListener('keydown', this.handleKeydown);
},
methods: {
findWorkItemType(workItemTypeName) {
return this.workItemTypes?.find((workItemType) => workItemType.name === workItemTypeName);
},
initialSelectedProject() {
if (this.relatedItem) {
return this.relatedItem.reference.substring(0, this.relatedItem.reference.lastIndexOf('#'));
@ -913,12 +918,14 @@ export default {
v-if="showProjectSelector"
class="gl-max-w-26 gl-flex-grow"
:label="__('Project')"
label-for="create-work-item-project"
>
<work-item-projects-listbox
v-model="selectedProjectFullPath"
:full-path="fullPath"
:is-group="isGroup"
:current-project-name="namespaceFullName"
toggle-id="create-work-item-project"
/>
</gl-form-group>
</template>

View File

@ -52,7 +52,6 @@ export default {
return {
fullPath: this.rootPath,
projectSearch: this.searchKey,
includeArchived: false,
};
},
update(data) {

View File

@ -47,7 +47,7 @@ export default {
return __('Epics');
}
return this.isGroup ? s__('WorkItem|Work items') : __('Issues');
return __('Issues');
},
issueAsWorkItem() {
return (

View File

@ -39,6 +39,11 @@ export default {
type: String,
default: null,
},
toggleId: {
type: String,
required: false,
default: undefined,
},
},
data() {
return {
@ -57,14 +62,15 @@ export default {
return {
fullPath: this.fullPath,
projectSearch: this.searchKey,
includeArchived: false,
};
},
update(data) {
return data.namespace?.projects?.nodes;
},
result() {
this.selectedProject = this.findSelectedProject(this.selectedProjectFullPath);
this.selectedProject =
this.findSelectedProject(this.selectedProjectFullPath) || this.projects?.at(0);
this.$emit('selectProject', this.selectedProject?.fullPath);
},
},
},
@ -78,7 +84,7 @@ export default {
* name_with_namespace doesn't exist. Therefore we rely on
* namespace directly.
* */
return this.selectedProject.nameWithNamespace || this.selectedProject.namespace;
return this.selectedProject.name || this.selectedProject.namespace;
}
return this.selectedProjectFullPath && this.currentProjectName
? this.currentProjectName
@ -213,6 +219,7 @@ export default {
is-check-centered
:items="listItems"
:selected="selectedProjectFullPath"
:toggle-id="toggleId"
:toggle-text="dropdownToggleText"
:searching="projectsLoading"
fluid-width

View File

@ -1,8 +1,4 @@
query namespaceProjectsForLinksWidget(
$fullPath: ID!
$projectSearch: String
$includeArchived: Boolean = false
) {
query namespaceProjectsForLinksWidget($fullPath: ID!, $projectSearch: String) {
namespace(fullPath: $fullPath) {
id
projects(
@ -10,7 +6,8 @@ query namespaceProjectsForLinksWidget(
includeSubgroups: true
includeSiblingProjects: true
sort: ACTIVITY_DESC
includeArchived: $includeArchived
includeArchived: false
withIssuesEnabled: true
) {
nodes {
id

View File

@ -11,11 +11,12 @@ import { apolloProvider } from '~/graphql_shared/issuable_client';
import App from './components/app.vue';
import WorkItemBreadcrumb from './components/work_item_breadcrumb.vue';
import activeDiscussionQuery from './components/design_management/graphql/client/active_design_discussion.query.graphql';
import { WORK_ITEM_TYPE_NAME_EPIC } from './constants';
import { createRouter } from './router';
Vue.use(VueApollo);
export const initWorkItemsRoot = ({ workspaceType, withTabs } = {}) => {
export const initWorkItemsRoot = ({ workItemType, workspaceType, withTabs } = {}) => {
const el = document.querySelector('#js-work-items');
if (!el) {
@ -46,7 +47,6 @@ export const initWorkItemsRoot = ({ workspaceType, withTabs } = {}) => {
defaultBranch,
initialSort,
isSignedIn,
workItemType,
hasEpicsFeature,
showNewWorkItem,
canCreateEpic,
@ -71,7 +71,7 @@ export const initWorkItemsRoot = ({ workspaceType, withTabs } = {}) => {
const breadcrumbParams = { workItemType, isGroup };
if (isGroup) {
if (workItemType === WORK_ITEM_TYPE_NAME_EPIC) {
listPath = epicsListPath;
breadcrumbParams.listPath = epicsListPath;
} else {

View File

@ -8,8 +8,9 @@ import {
ROUTES,
RELATED_ITEM_ID_URL_QUERY_PARAM,
BASE_ALLOWED_CREATE_TYPES,
WORK_ITEM_TYPE_NAME_ISSUE,
WORK_ITEM_TYPE_NAME_EPIC,
WORK_ITEM_TYPE_NAME_INCIDENT,
WORK_ITEM_TYPE_NAME_ISSUE,
WORK_ITEM_TYPE_NAME_TASK,
} from '../constants';
import workItemRelatedItemQuery from '../graphql/work_item_related_item.query.graphql';
@ -74,6 +75,9 @@ export default {
},
},
computed: {
isEpic() {
return this.workItemType === WORK_ITEM_TYPE_NAME_EPIC;
},
isIncident() {
return this.workItemType === WORK_ITEM_TYPE_NAME_INCIDENT;
},
@ -90,13 +94,16 @@ export default {
return [];
},
isNewGroupWorkItem() {
return !this.isEpic && this.isGroup;
},
},
methods: {
updateWorkItemType(type) {
this.workItemType = type;
},
workItemCreated({ workItem, numberOfDiscussionsResolved }) {
if (this.$router && !this.isIncident) {
if (this.$router && !this.isIncident && !this.isNewGroupWorkItem) {
const routerPushObject = {
name: ROUTES.workItem,
params: { iid: workItem.iid },
@ -159,7 +166,8 @@ export default {
:is-group="isGroup"
:related-item="relatedItem"
:should-discard-draft="shouldDiscardDraft"
:always-show-work-item-type-select="!isGroup"
:always-show-work-item-type-select="!isEpic"
:show-project-selector="isNewGroupWorkItem"
:allowed-work-item-types="allowedWorkItemTypes"
@updateType="updateWorkItemType($event)"
@confirmCancel="handleConfirmCancellation"

View File

@ -26,7 +26,7 @@ module RapidDiffs
end
def viewer_component
return Viewers::NoPreviewComponent if empty_diff?
return Viewers::NoPreviewComponent if @diff_file.no_preview?
if @diff_file.diffable_text?
return Viewers::Text::ParallelViewComponent if @parallel_view
@ -39,16 +39,12 @@ module RapidDiffs
Viewers::NoPreviewComponent
end
def empty_diff?
@diff_file.collapsed? || !@diff_file.modified_file?
end
def default_header
render RapidDiffs::DiffFileHeaderComponent.new(diff_file: @diff_file)
end
def total_rows
return 0 unless !empty_diff? && @diff_file.diffable_text?
return 0 unless !@diff_file.no_preview? && @diff_file.diffable_text?
count = 0
@diff_file.viewer_hunks.each do |hunk|

View File

@ -74,34 +74,36 @@ module RapidDiffs
return render_empty_state if diff_files.empty?
each_growing_slice(diff_files, 5, 2) do |slice|
response.stream.write(render_diff_files_collection(slice, view_context))
skipped = []
diff_files.each do |diff_file|
if diff_file.no_preview?
skipped << diff_file
else
unless skipped.empty?
response.stream.write(diff_files_collection(skipped).render_in(view_context))
skipped = []
end
response.stream.write(diff_file_component(diff_file).render_in(view_context))
end
end
response.stream.write(diff_files_collection(skipped).render_in(view_context)) unless skipped.empty?
end
def each_growing_slice(collection, initial_size, growth_factor = 2)
position = 0
size = initial_size
total = collection.size
while position < total
yield collection.drop(position).first(size)
position = [position + size, total].min
size = (size * growth_factor).to_i
end
def diff_file_component(diff_file)
::RapidDiffs::DiffFileComponent.new(diff_file: diff_file, parallel_view: view == :parallel)
end
def render_diff_files_collection(diff_files, view_context)
def diff_files_collection(diff_files)
::RapidDiffs::DiffFileComponent.with_collection(diff_files, parallel_view: view == :parallel)
.render_in(view_context)
end
def stream_diff_blobs(options, view_context)
return render_empty_state if resource.diffs_for_streaming(options).count == 0
resource.diffs_for_streaming(options) do |diff_files_batch|
response.stream.write(render_diff_files_collection(diff_files_batch, view_context))
response.stream.write(diff_files_collection(diff_files_batch).render_in(view_context))
end
end

View File

@ -54,7 +54,9 @@ module Glql
end
def logs
super.map do |log|
graphql_logs = super.presence || [{}]
graphql_logs.map do |log|
log.merge(
glql_referer: request.headers["Referer"],
glql_query_sha: query_sha

View File

@ -11,7 +11,6 @@ class Groups::UploadsController < Groups::ApplicationController
before_action :disallow_new_uploads!, only: :show
feature_category :portfolio_management
urgency :low, [:show]
private

View File

@ -37,11 +37,9 @@ module Groups
def handle_new_work_item_path
return unless show_params[:iid] == 'new'
if group.supports_group_work_items?
render :show
else
not_found
end
authenticate_user!
render :show
end
def show_params

View File

@ -88,8 +88,7 @@ class Projects::IssuesController < Projects::ApplicationController
:can_create_branch, :create_merge_request
]
urgency :low, [
:index, :calendar, :show, :new, :create, :edit, :update,
:destroy, :move, :reorder, :designs, :toggle_subscription,
:index, :calendar, :show, :new, :update, :move, :reorder, :designs, :toggle_subscription,
:discussions, :bulk_update, :realtime_changes,
:toggle_award_emoji, :mark_as_spam, :related_branches,
:can_create_branch, :create_merge_request

View File

@ -11,10 +11,14 @@ module Projects
@merge_request
end
def render_diff_files_collection(diff_files, view_context)
def diff_file_component(diff_file)
::RapidDiffs::MergeRequestDiffFileComponent
.new(diff_file: diff_file, merge_request: @merge_request, parallel_view: view == :parallel)
end
def diff_files_collection(diff_files)
::RapidDiffs::MergeRequestDiffFileComponent
.with_collection(diff_files, merge_request: @merge_request, parallel_view: view == :parallel)
.render_in(view_context)
end
def sorted?

View File

@ -603,13 +603,16 @@ class ProjectsController < Projects::ApplicationController
def redirect_git_extension
return unless params[:format] == 'git'
git_extension_regex = %r{\.git/?\Z}
return unless request.path.match?(git_extension_regex)
# `project` calls `find_routable!`, so this will trigger the usual not-found
# behaviour when the user isn't authorized to see the project
return if project.nil? || performed?
uri = URI(request.original_url)
# Strip the '.git' part from the path
uri.path = uri.path.sub(%r{\.git/?\Z}, '')
uri.path = uri.path.sub(git_extension_regex, '')
redirect_to(uri.to_s)
end

View File

@ -16,6 +16,13 @@ module Mutations
required: true,
description: 'Name of the integration.'
argument :type, Types::AlertManagement::IntegrationTypeEnum,
as: :type_identifier,
required: false,
default_value: :http,
replace_null_with_default: true,
description: 'Type of integration to create. Cannot be changed after creation.'
argument :active, GraphQL::Types::Boolean,
required: true,
description: 'Whether the integration is receiving alerts.'

View File

@ -7,7 +7,7 @@ module Mutations
field :integration,
Types::AlertManagement::HttpIntegrationType,
null: true,
description: "HTTP integration."
description: "Alerting integration."
authorize :admin_operations
@ -22,7 +22,7 @@ module Mutations
# overriden in EE
def http_integration_params(_project, args)
args.slice(:name, :active)
args.slice(:name, :active, :type_identifier)
end
end
end

View File

@ -377,6 +377,8 @@ module ApplicationSettingsHelper
:max_export_size,
:max_github_response_size_limit,
:max_github_response_json_value_count,
:max_http_decompressed_size,
:max_http_response_size_limit,
:max_import_size,
:max_import_remote_file_size,
:max_login_attempts,

View File

@ -655,6 +655,8 @@ class ApplicationSetting < ApplicationRecord
:max_export_size,
:max_github_response_size_limit,
:max_github_response_json_value_count,
:max_http_decompressed_size,
:max_http_response_size_limit,
:max_import_remote_file_size,
:max_import_size,
:max_pages_custom_domains_per_project,
@ -706,6 +708,8 @@ class ApplicationSetting < ApplicationRecord
validates :clickhouse, json_schema: { filename: "application_setting_clickhouse" }
jsonb_accessor :response_limits,
max_http_response_size_limit: [:integer, { default: 100 }],
max_http_decompressed_size: [:integer, { default: 100 }],
max_github_response_size_limit: [:integer, { default: 8 }],
max_github_response_json_value_count: [:integer, { default: 250_000 }]

View File

@ -142,6 +142,8 @@ module ApplicationSettingImplementation
max_export_size: 0,
max_github_response_size_limit: 8,
max_github_response_json_value_count: 250_000,
max_http_decompressed_size: 100,
max_http_response_size_limit: 100,
max_import_size: 0,
max_import_remote_file_size: 10240,
max_login_attempts: nil,

View File

@ -11,7 +11,7 @@ class Board < ApplicationRecord
has_many :lists, -> { ordered }, dependent: :delete_all, inverse_of: :board # rubocop:disable Cop/ActiveRecordDependent
has_many :destroyable_lists, -> { destroyable.ordered }, class_name: "List", inverse_of: :board
validates :name, presence: true
validates :name, presence: true, length: { maximum: 255, if: :name_changed? }
validates :project, presence: true, if: :project_needed?
validates :group, presence: true, unless: :project
validates :group, absence: {

View File

@ -112,7 +112,7 @@ module Integrations
begin
story_on_task_url = format(STORY_URL_TEMPLATE, task_gid: task_id)
Gitlab::HTTP.post(
Clients::HTTP.post(
story_on_task_url,
headers: { "Authorization" => "Bearer #{api_key}" },
body: { text: "#{push_msg} #{message}" }
@ -120,7 +120,8 @@ module Integrations
if prepended_text.match?(proceded_keyword_finder)
task_url = format(TASK_URL_TEMPLATE, task_gid: task_id)
Gitlab::HTTP.put(task_url, headers: { "Authorization" => "Bearer #{api_key}" }, body: { completed: true })
Clients::HTTP.put(task_url, headers: { "Authorization" => "Bearer #{api_key}" },
body: { completed: true })
end
rescue StandardError => e
log_error(e.message)
@ -130,7 +131,7 @@ module Integrations
end
def test(_)
result = Gitlab::HTTP.get(PERSONAL_ACCESS_TOKEN_TEST_URL, headers: { "Authorization" => "Bearer #{api_key}" })
result = Clients::HTTP.get(PERSONAL_ACCESS_TOKEN_TEST_URL, headers: { "Authorization" => "Bearer #{api_key}" })
if result.success?
{ success: true }

View File

@ -46,7 +46,7 @@ module Integrations
url = "https://atlas.assembla.com/spaces/#{URI.encode_www_form_component(subdomain)}/github_tool?secret_key=#{URI.encode_www_form_component(token)}"
body = { payload: data }
Gitlab::HTTP.post(
Clients::HTTP.post(
url,
body: Gitlab::Json.dump(body),
headers: { 'Content-Type' => 'application/json' }

View File

@ -154,11 +154,11 @@ module Integrations
params = build_get_params(query_params)
params[:extra_log_info] = { project_id: project_id }
Gitlab::HTTP.try_get(build_url(path), params)
Clients::HTTP.try_get(build_url(path), params)
end
def get_path(path, query_params = {})
Gitlab::HTTP.get(build_url(path), build_get_params(query_params))
Clients::HTTP.get(build_url(path), build_get_params(query_params))
end
def build_url(path)

View File

@ -54,7 +54,7 @@ module Integrations
end
def execute(_data)
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true)
response = Clients::HTTP.get(properties['external_wiki_url'], verify: true)
response.body if response.code == 200
rescue StandardError
nil

View File

@ -69,7 +69,7 @@ module Integrations
key = parse_thread_key(message)
payload = { text: parse_simple_text_message(message), thread: { threadKey: key }.compact }.compact_blank!
Gitlab::HTTP.post(
Clients::HTTP.post(
url,
body: payload.to_json,
headers: { 'Content-Type' => 'application/json' },

View File

@ -106,7 +106,7 @@ module Integrations
result = false
begin
response = Gitlab::HTTP.head(project_url, verify: true)
response = Clients::HTTP.head(project_url, verify: true)
if response
message = "#{type} received response #{response.code} when attempting to connect to #{project_url}"

View File

@ -56,7 +56,7 @@ module Integrations
# # => 'running'
#
def commit_status(sha, _ref)
response = Gitlab::HTTP.get(commit_status_path(sha), verify: enable_ssl_verification)
response = Clients::HTTP.get(commit_status_path(sha), verify: enable_ssl_verification)
read_commit_status(response)
rescue Errno::ECONNREFUSED
:error

View File

@ -66,7 +66,7 @@ module Integrations
'message' => commit[:message]
}
}
Gitlab::HTTP.post(
Clients::HTTP.post(
API_ENDPOINT,
body: Gitlab::Json.dump(message),
headers: {

View File

@ -62,7 +62,7 @@ module Integrations
def notify(message, _opts)
header = { 'Content-Type' => 'application/json' }
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump({ text: message.summary }))
response = Clients::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump({ text: message.summary }))
response if response.success?
end

View File

@ -154,7 +154,7 @@ module Integrations
# Sound parameter MUST NOT be sent to API if not selected
pushover_data[:sound] = sound if sound
Gitlab::HTTP.post('/messages.json', base_uri: BASE_URI, body: pushover_data)
Clients::HTTP.post('/messages.json', base_uri: BASE_URI, body: pushover_data)
end
end
end

View File

@ -167,7 +167,7 @@ module Integrations
end
def get_path(path)
Gitlab::HTTP.try_get(
Clients::HTTP.try_get(
build_url(path),
verify: enable_ssl_verification,
basic_auth: basic_auth,
@ -176,7 +176,7 @@ module Integrations
end
def post_to_build_queue(_data, branch)
Gitlab::HTTP.post(
Clients::HTTP.post(
build_url('httpAuth/app/rest/buildQueue'),
body: "<build branchName=#{branch.encode(xml: :attr)}>" \
"<buildType id=#{build_type.encode(xml: :attr)}/>" \

View File

@ -113,13 +113,13 @@ module Integrations
}.compact_blank
header = { 'Content-Type' => 'application/json' }
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
response = Clients::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
# We're retrying the request with a different format to ensure accurate formatting and
# avoid receiving a 400 response due to invalid markdown.
if response.bad_request?
body.except!(:parse_mode)
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
response = Clients::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
end
response if response.success?

View File

@ -64,7 +64,7 @@ module Integrations
markdown: true
}
response = Gitlab::HTTP.post(webhook, body: Gitlab::Json.dump(body))
response = Clients::HTTP.post(webhook, body: Gitlab::Json.dump(body))
response if response.success?
end

View File

@ -55,7 +55,8 @@ module Integrations
def notify(message, _opts)
header = { 'Content-Type' => 'application/json' }
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump({ markdown: message.summary }))
response = Clients::HTTP.post(webhook, headers: header,
body: Gitlab::Json.dump({ markdown: message.summary }))
response if response.success?
end

View File

@ -34,7 +34,7 @@ module Integrations
class HTTPClient
def self.post(uri, params = {})
params.delete(:http_options) # these are internal to the client and we do not want them
Gitlab::HTTP.post(uri, body: params)
Clients::HTTP.post(uri, body: params)
end
end
end

View File

@ -7,6 +7,7 @@ module WebHooks
InterpolationError = Class.new(StandardError)
SECRET_MASK = '************'
MAX_PARAM_LENGTH = 8192
# See app/validators/json_schemas/web_hooks_url_variables.json
VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/
@ -45,9 +46,14 @@ module WebHooks
encode_iv: false
validates :url, presence: true
validates :url, public_url: true, if: ->(hook) { hook.validate_public_url? && !hook.url_variables? }
validates :url, length: { maximum: MAX_PARAM_LENGTH }
validates :url, public_url: true, if: ->(hook) {
# Apply the validation up to the point where the length validation above would make the record invalid.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/524020#note_2529307579
(hook.url&.length&.<= MAX_PARAM_LENGTH) && hook.validate_public_url? && !hook.url_variables?
}
validates :token, format: { without: /\n/ }
validates :token, length: { maximum: MAX_PARAM_LENGTH }, format: { without: /\n/ }
after_initialize :initialize_url_variables
after_initialize :initialize_custom_headers

View File

@ -101,7 +101,7 @@ module Integrations
end
def calculate_reactive_cache(sha, ref)
response = Gitlab::HTTP.try_get(commit_status_path(sha), request_options)
response = Clients::HTTP.try_get(commit_status_path(sha), request_options)
status =
if response&.code == 200 && response['status']

View File

@ -109,14 +109,14 @@ module Integrations
}
}
}
res = Gitlab::HTTP.post(path, base_uri: base_uri, **auth.merge(body))
res = Clients::HTTP.post(path, base_uri: base_uri, **auth.merge(body))
res.code == 201 ? res : nil
end
# Returns a list of rooms, or [].
# https://github.com/basecamp/campfire-api/blob/master/sections/rooms.md#get-rooms
def rooms(auth)
res = Gitlab::HTTP.get("/rooms.json", base_uri: base_uri, **auth)
res = Clients::HTTP.get("/rooms.json", base_uri: base_uri, **auth)
res.code == 200 ? res["rooms"] : []
end

View File

@ -61,7 +61,7 @@ module Integrations
end
def calculate_reactive_cache(sha, ref)
response = Gitlab::HTTP.try_get(
response = Clients::HTTP.try_get(
commit_status_path(sha, ref),
verify: enable_ssl_verification,
extra_log_info: { project_id: project_id }

View File

@ -105,7 +105,7 @@ module Integrations
}
url = URI.parse(webhook)
url.path << (Time.current.to_f * 1000).round.to_s
response = Gitlab::HTTP.put(url, headers: header, body: Gitlab::Json.dump(body))
response = Clients::HTTP.put(url, headers: header, body: Gitlab::Json.dump(body))
response if response.success?
end

View File

@ -53,9 +53,9 @@ module Import
def headers
return {} if file_url.blank?
@headers ||= Gitlab::HTTP.head(file_url, timeout: 1.second).headers
@headers ||= Clients::HTTP.head(file_url, timeout: 1.second).headers
rescue StandardError => e
errors.add(:base, "Failed to retrive headers: #{e.message}")
errors.add(:base, "Failed to retrieve headers: #{e.message}")
{}
end

View File

@ -81,7 +81,7 @@ module Integrations
redirect_uri: redirect_uri
}
Gitlab::HTTP.get(SLACK_EXCHANGE_TOKEN_URL, query: query).to_hash
Clients::HTTP.get(SLACK_EXCHANGE_TOKEN_URL, query: query).to_hash
end
# Due to our modelling (mentioned in epic 9418) we create a SlackIntegration record

View File

@ -39,7 +39,7 @@ module Integrations
request_body = Gitlab::Json.dump(close_request_body)
response_url = params.dig(:view, :private_metadata)
Gitlab::HTTP.post(response_url, body: request_body, headers: headers)
Clients::HTTP.post(response_url, body: request_body, headers: headers)
end
def close_request_body

View File

@ -83,7 +83,7 @@ module Integrations
text: text
}
Gitlab::HTTP.post(
Integrations::Clients::HTTP.post(
response_url,
body: Gitlab::Json.dump(body),
headers: { 'Content-Type' => 'application/json' }

View File

@ -41,7 +41,7 @@ module JiraConnectInstallations
attr_reader :installation, :event
def send_hook
Gitlab::HTTP.post(hook_uri, body: body)
Integrations::Clients::HTTP.post(hook_uri, body: body)
end
def hook_uri

View File

@ -51,12 +51,12 @@ module Projects
end
def download_links_for(oids)
response = Gitlab::HTTP.post(remote_uri, body: request_body(oids), headers: headers)
response = ::Import::Clients::HTTP.post(remote_uri, body: request_body(oids), headers: headers)
raise DownloadLinksRequestEntityTooLargeError if response.request_entity_too_large?
raise DownloadLinksError, response.message unless response.success?
# Since the LFS Batch API may return a Content-Ttpe of
# Since the LFS Batch API may return a Content-Type of
# application/vnd.git-lfs+json
# (https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md#requests),
# HTTParty does not know this is actually JSON.

View File

@ -79,7 +79,10 @@ module Projects
end
def download_options
http_options = { headers: lfs_headers, stream_body: true }
# Set accept-encoding to identity to request web servers not to send a compressed response to avoid using too
# much memory to decompress the file. In case the response is encoded, the response size will be limited by
# `max_http_decompressed_size application` application setting.
http_options = { headers: lfs_headers.merge('accept-encoding' => 'identity'), stream_body: true }
return http_options if lfs_download_object.has_authorization_header?

View File

@ -149,6 +149,7 @@ class WebHookService
headers: build_custom_headers.merge(build_headers),
verify: hook.enable_ssl_verification,
basic_auth: basic_auth,
max_bytes: Gitlab::CurrentSettings.max_http_response_size_limit.megabytes,
**request_options)
end

View File

@ -13,6 +13,16 @@
"type": "integer",
"minimum": 0,
"description": "Maximum allowed object count for GitHub API responses. Count is an estimate based on the number of : , { and [ occurrences in the response."
},
"max_http_decompressed_size": {
"type": "integer",
"minimum": 0,
"description": "Maximum allowed size in MB for Gzip-compressed HTTP responses after decompression."
},
"max_http_response_size_limit": {
"type": "integer",
"minimum": 0,
"description": "Maximum allowed size in MB for HTTP responses."
}
}
}

View File

@ -2,6 +2,8 @@
- add_page_specific_style 'page_bundles/labels'
- page_description = s_('AdminLabels|Labels created here will be automatically added to new projects.')
%h1.gl-sr-only= page_title
%div{ data: { event_tracking_load: 'true', event_tracking: 'view_admin_labels_pageload' } }
- if @labels.present?

View File

@ -5,6 +5,8 @@
- labels_or_filters = @labels.exists? || search.present? || subscribed.present?
- add_page_specific_style 'page_bundles/labels'
%h1.gl-sr-only= page_title
- if labels_or_filters
#js-promote-label-modal
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label

View File

@ -1,7 +1,7 @@
.row
.form-group.project-name.col-sm-12
= label_tag :name, _('Project name'), class: 'label-bold'
= text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control gl-form-input input-lg", autofocus: true, required: true, aria: { required: true }, data: { testid: 'project-name-field' }
= text_field_tag :name, @name, placeholder: s_('ProjectsNew|My project'), class: "js-project-name form-control gl-form-input input-lg", autofocus: true, required: true, aria: { required: true }, data: { testid: 'project-name-field' }
.form-group.col-12.col-sm-6.gl-pr-0
= label_tag :namespace_id, _('Project URL'), class: 'label-bold'
.input-group.gl-flex-nowrap

View File

@ -10,7 +10,7 @@
.form-group.gl-form-group.project-name.col-sm-12
= f.label :name, class: 'label-bold' do
%span= _("Project name")
= f.text_field :name, placeholder: "My awesome project", class: "form-control gl-form-input input-lg", data: { testid: 'project-name', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_name", track_value: "" }, required: true, aria: { required: true, describedby: 'js-project-name-description' }
= f.text_field :name, placeholder: s_('ProjectsNew|My project'), class: "form-control gl-form-input input-lg", data: { testid: 'project-name', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_name", track_value: "" }, required: true, aria: { required: true, describedby: 'js-project-name-description' }
%small#js-project-name-description.form-text.gl-text-subtle
= s_("ProjectsNew|Must start with a lowercase or uppercase letter, digit, emoji, or underscore. Can also contain dots, pluses, dashes, or spaces.")
#js-project-name-error.gl-field-error.gl-mt-2.gl-hidden

View File

@ -5,6 +5,8 @@
- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
- add_page_specific_style 'page_bundles/labels'
%h1.gl-sr-only= page_title
- if labels_or_filters
#js-promote-label-modal
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
@ -23,8 +25,8 @@
body_options: { class: '!gl-m-0' },
description: _('Drag to reorder prioritized labels and change their relative priority.')) do |c|
- c.with_body do
.js-prioritized-labels.gl-rounded-base.manage-labels-list{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
#js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
%ul.js-prioritized-labels.gl-rounded-base.manage-labels-list{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
%li#js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
= render 'shared/empty_states/priority_labels'
- if @prioritized_labels.any?
= render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project }
@ -35,7 +37,7 @@
- if @labels.any?
= render ::Layouts::CrudComponent.new(hide ? _('Labels') : _('Other labels'), options: { class: 'other-labels' }, count: number_with_delimiter(@labels.total_count), icon: 'label', body_options: { class: '!gl-m-0' }) do |c|
- c.with_body do
.js-other-labels.manage-labels-list
%ul.js-other-labels.manage-labels-list
= render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project }
- c.with_pagination do
= paginate @labels, theme: 'gitlab'

View File

@ -1,8 +1,8 @@
.text-center.gl-mt-1.gl-mb-5
.svg-content{ data: { testid: 'label-svg-content' } }
= image_tag 'illustrations/empty-state/empty-labels-starred-md.svg'
= image_tag 'illustrations/empty-state/empty-labels-starred-md.svg', role: 'presentation'
- if can?(current_user, :admin_label, @project)
%h5.gl-my-0
%h3.gl-heading-5.gl-my-0
= _("No prioritized labels yet!")
%p.gl-text-subtle
= _("Star labels to start sorting by priority.")

View File

@ -12,7 +12,7 @@ module JiraConnect
worker_has_external_dependencies!
def perform(proxy_url, jwt, attempts = 3)
r = Gitlab::HTTP.post(proxy_url, headers: { 'Authorization' => "JWT #{jwt}" })
r = Integrations::Clients::HTTP.post(proxy_url, headers: { 'Authorization' => "JWT #{jwt}" })
self.class.perform_in(1.hour, proxy_url, jwt, attempts - 1) if r.code >= 400 && attempts > 0
rescue *Gitlab::HTTP::HTTP_ERRORS

View File

@ -1,24 +1,58 @@
# frozen_string_literal: true
MONKEY_PATCH_METHOD_CHECKSUM = "7fd222f9cffc7ab25d4782d380fdb0b5e83b35d495f9e2fbd8e5057891c73108"
unless Rails.env.production?
source = "Net::HTTPResponse::Inflater".safe_constantize&.instance_method(:inflate_adapter)&.source.to_s.strip
unless OpenSSL::Digest::SHA256.hexdigest(source) == MONKEY_PATCH_METHOD_CHECKSUM
raise "Original Net::HTTPResponse::Inflater code was modified. Please update the patch accordingly, then update" \
"the checksum."
end
end
module Net
class HTTPResponse
module FinishOverride
# rubocop:disable Gitlab/ModuleWithInstanceVariables -- This is a Monkey Patch
def finish
if Gitlab.config.gitlab.log_decompressed_response_bytesize > 0 &&
@inflate.total_out > Gitlab.config.gitlab.log_decompressed_response_bytesize
Gitlab::AppJsonLogger.debug(message: 'net/http: response decompressed', size: @inflate.total_out,
caller: Gitlab::BacktraceCleaner.clean_backtrace(caller))
# This is a monkey patch over an existing method of net/http to limit maximum decompression size
#
# To disable the decomposition limit validation see `::Gitlab::HTTP.without_decompression_limit`.
#
# Original code from
# https://github.com/ruby/ruby/blob/d5f94941d87743d6563fa1a038665917dea70201/lib/net/http/response.rb#L693-L707
class Inflater
def inflate_adapter(dest)
if dest.respond_to?(:set_encoding)
dest.set_encoding(Encoding::ASCII_8BIT)
elsif dest.respond_to?(:force_encoding)
dest.force_encoding(Encoding::ASCII_8BIT)
end
super
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
block = proc do |compressed_chunk|
@inflate.inflate(compressed_chunk) do |chunk|
compressed_chunk.clear
# Limit the maximum decompression size
class Inflater
prepend FinishOverride
if validate_decompressed_size? && @inflate.total_out > max_http_decompressed_size
Gitlab::AppJsonLogger.error(message: 'Net::HTTP - Response size too large', size: @inflate.total_out,
caller: Gitlab::BacktraceCleaner.clean_backtrace(caller))
raise Gitlab::HTTP::MaxDecompressionSizeError, "Response size over #{max_http_decompressed_size} bytes"
end
dest << chunk
end
end
Net::ReadAdapter.new(block)
end
def validate_decompressed_size?
Gitlab::CurrentSettings.max_http_decompressed_size > 0 &&
!Gitlab::SafeRequestStore[:disable_net_http_decompression]
end
def max_http_decompressed_size
Gitlab::CurrentSettings.max_http_decompressed_size.megabytes
end
end
end
end

View File

@ -5,4 +5,4 @@ feature_category: importers
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180432
milestone: '17.9'
queued_migration_version: 20250205194220
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250610152957'

View File

@ -5,4 +5,4 @@ feature_category: geo_replication
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180426
milestone: '17.9'
queued_migration_version: 20250205193115
finalized_by: # version of the migration that finalized this BBM
finalized_by: 20250611100647

View File

@ -5,4 +5,4 @@ feature_category: team_planning
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180837
milestone: '17.9'
queued_migration_version: 20250209005146
finalized_by: # version of the migration that finalized this BBM
finalized_by: 20250611095947

View File

@ -5,4 +5,4 @@ feature_category: incident_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175719
milestone: '17.8'
queued_migration_version: 20241213142263
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250605204337'

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class FinalizeBackfillIssuableSlasNamespaceId < Gitlab::Database::Migration[2.3]
milestone '18.1'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillIssuableSlasNamespaceId',
table_name: :issuable_slas,
column_name: :id,
job_arguments: [:namespace_id, :issues, :namespace_id, :issue_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class FinalizeBackfillBulkImportExportBatchesGroupId < Gitlab::Database::Migration[2.3]
milestone '18.1'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillBulkImportExportBatchesGroupId',
table_name: :bulk_import_export_batches,
column_name: :id,
job_arguments: [:group_id, :bulk_import_exports, :group_id, :export_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeBackfillDesignUserMentionsNamespaceId < Gitlab::Database::Migration[2.3]
milestone '18.1'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillDesignUserMentionsNamespaceId',
table_name: :design_user_mentions,
column_name: :id,
job_arguments: [:namespace_id, :design_management_designs, :namespace_id, :design_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeBackfillDesignManagementRepositoryStatesNamespaceId < Gitlab::Database::Migration[2.3]
milestone '18.1'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillDesignManagementRepositoryStatesNamespaceId',
table_name: :design_management_repository_states,
column_name: :design_management_repository_id,
job_arguments: [:namespace_id, :design_management_repositories, :namespace_id, :design_management_repository_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
7cea86680a0475a789c1df120bbea3a5c010fdcb21fb531647c5f1c40fa862e5

View File

@ -0,0 +1 @@
cae7e8295a5216ee5b4d4c886ae87e529634b9faac7d6a6b04a0972dbd0858d7

View File

@ -0,0 +1 @@
4a5720e055d17c41a01f9cfb68c5da9af258c9cf14900aa821453089b6489937

View File

@ -0,0 +1 @@
9cfda522c1e97f313318d6861d540d3ef2d61075e3d0922d73609a69168623b5

View File

@ -275,6 +275,15 @@ instructions only work on the Linux package-provided PostgreSQL:
Replace `<PRAEFECT_SQL_PASSWORD_HASH>` with the hash of the password you generated in the
preparation step. It is prefixed with `md5` literal.
1. Create a new user `pgbouncer` to be used by PgBouncer:
```sql
CREATE ROLE pgbouncer WITH LOGIN;
ALTER USER pgbouncer WITH password 'md5<PGBOUNCER_SQL_PASSWORD_HASH>';
```
Replace `PGBOUNCER_SQL_PASSWORD_HASH` with the strong password hash you generated in the preparation step.
1. The PgBouncer that is shipped with the Linux package is configured to use [`auth_query`](https://www.pgbouncer.org/config.html#generic-settings)
and uses `pg_shadow_lookup` function. You need to create this function in `praefect_production`
database:

View File

@ -307,6 +307,38 @@ There is a limit when embedding metrics in GitLab Flavored Markdown (GLFM) for p
- **Max limit**: 100 embeds.
## HTTP response limits
### Maximum Gzip-compressed size
This setting is used to restrict the maximum allowed size in MiB for Gzip-compressed
HTTP responses after decompression to prevent DoS.
The default maximum size is 100 MiB. To disable this limit, set the value to 0.
If the value is too high, it could expose the instance to DoS attacks.
You can change this limit by using the GitLab Rails console or use
[application setting API](../api/settings.md)
```ruby
ApplicationSetting.update(max_http_decompressed_size: 50)
```
### Maximum HTTP responses size
This setting is used to restrict the maximum allowed size in MiB for decompressed
HTTP responses to prevent DoS. It applies to integrations, importers, and webhooks.
The default maximum size is 100 MiB. To disable this limit, set the value to 0.
If the value is too high, it could expose the instance to DoS attacks.
You can change this limit by using the GitLab Rails console or use
[application setting API](../api/settings.md)
```ruby
ApplicationSetting.update(max_http_response_size_limit: 60)
```
## Webhook limits
Also see [Webhook rate limits](#webhook-rate-limit).

View File

@ -6697,6 +6697,7 @@ Input type: `HttpIntegrationCreateInput`
| <a id="mutationhttpintegrationcreatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | Custom mapping of GitLab alert attributes to fields from the payload example. |
| <a id="mutationhttpintegrationcreatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | Example of an alert payload. |
| <a id="mutationhttpintegrationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Project to create the integration in. |
| <a id="mutationhttpintegrationcreatetype"></a>`type` | [`AlertManagementIntegrationType`](#alertmanagementintegrationtype) | Type of integration to create. Cannot be changed after creation. |
#### Fields
@ -6704,7 +6705,7 @@ Input type: `HttpIntegrationCreateInput`
| ---- | ---- | ----------- |
| <a id="mutationhttpintegrationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationhttpintegrationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationhttpintegrationcreateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
| <a id="mutationhttpintegrationcreateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | Alerting integration. |
### `Mutation.httpIntegrationDestroy`
@ -6723,7 +6724,7 @@ Input type: `HttpIntegrationDestroyInput`
| ---- | ---- | ----------- |
| <a id="mutationhttpintegrationdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationhttpintegrationdestroyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationhttpintegrationdestroyintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
| <a id="mutationhttpintegrationdestroyintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | Alerting integration. |
### `Mutation.httpIntegrationResetToken`
@ -6742,7 +6743,7 @@ Input type: `HttpIntegrationResetTokenInput`
| ---- | ---- | ----------- |
| <a id="mutationhttpintegrationresettokenclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationhttpintegrationresettokenerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationhttpintegrationresettokenintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
| <a id="mutationhttpintegrationresettokenintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | Alerting integration. |
### `Mutation.httpIntegrationUpdate`
@ -6765,7 +6766,7 @@ Input type: `HttpIntegrationUpdateInput`
| ---- | ---- | ----------- |
| <a id="mutationhttpintegrationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationhttpintegrationupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationhttpintegrationupdateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
| <a id="mutationhttpintegrationupdateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | Alerting integration. |
### `Mutation.importSourceUserCancelReassignment`

View File

@ -614,6 +614,8 @@ to configure other related settings. These requirements are
| `max_export_size` | integer | no | Maximum export size in MB. 0 for unlimited. Default = 0 (unlimited). |
| `max_github_response_size_limit` | integer | no | Maximum allowed GitHub API response size in MB. 0 for unlimited. |
| `max_github_response_json_value_count` | integer | no | Maximum allowed value count for GitHub API responses. 0 for unlimited. Count is an estimate based on the number of `:` `,` `{` and `[` occurrences in the response. |
| `max_http_decompressed_size` | integer | no | Maximum allowed size in MiB for Gzip-compressed HTTP responses after decompression. 0 for unlimited. |
| `max_http_response_size_limit` | integer | no | Maximum allowed size in MiB for HTTP responses. 0 for unlimited. |
| `max_import_size` | integer | no | Maximum import size in MB. 0 for unlimited. Default = 0 (unlimited). |
| `max_import_remote_file_size` | integer | no | Maximum remote file size for imports from external object storages. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/384976) in GitLab 16.3. |
| `max_login_attempts` | integer | no | Maximum number of sign-in attempts before locking out the user. |

View File

@ -13,9 +13,11 @@ title: Principles of Importer Design
- Importers must not add third-party Ruby gems that make HTTP calls.
Importers use the same
[Ruby gem policy as for integrations](../integrations/_index.md#no-ruby-gems-that-make-http-calls), for more information about Ruby gem use for importers see that page.
- All HTTP calls must use `Gitlab::HTTP`.
`Gitlab::HTTP` ensures that [network settings](../../security/webhooks.md) of the instance
are enforced and has other [security hardening](../../security/webhooks.md#enforce-dns-rebinding-attack-protection) measures.
- All HTTP calls must use `Import::Clients::HTTP`, which:
- Ensures that [network settings](../../security/webhooks.md) are enforced for HTTP calls.
- Has additional [security hardening](../../security/webhooks.md#enforce-dns-rebinding-attack-protection) features.
- Is our single source of truth for making secure HTTP calls.
- Ensure all response sizes are validated.
## Logging

View File

@ -208,13 +208,14 @@ For example, to create metric definitions for the Slack integration, you copy th
### Security requirements
#### All HTTP calls must use `Gitlab::HTTP`
#### All HTTP calls must use `Integrations::Clients::HTTP`
Integrations must always make HTTP calls using `Gitlab::HTTP`, which:
Integrations must always make HTTP calls using `Integrations::Clients::HTTP`, which:
- Ensures that [network settings](../../security/webhooks.md) are enforced for HTTP calls.
- Has additional [security hardening](../../security/webhooks.md#enforce-dns-rebinding-attack-protection) features.
- Is our single source of truth for making secure HTTP calls.
- Ensure all response sizes are validated.
#### Masking channel values
@ -244,7 +245,7 @@ but they offer minimal benefit compared to the costs involved:
- They increase the potential surface area of security problems and the effort required to fix them.
- Often these gems make HTTP calls on your behalf. As integrations can make HTTP calls to remote
servers configured by users, it is critical that we
[fully control the network calls](#all-http-calls-must-use-gitlabhttp).
[fully control the network calls](#all-http-calls-must-use-integrationsclientshttp).
- There is a maintenance cost of managing gem upgrades.
- They can block us from using newer features.

View File

@ -10,7 +10,8 @@ title: GitLab Duo Chat
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Core, Pro, or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- LLMs: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet), Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2), Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet), Anthropic [Claude 3.5 Haiku](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-haiku), and [Vertex AI Search](https://cloud.google.com/enterprise-search). The LLM depends on the question asked.
- LLMs: Anthropic [Claude 4.0 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-sonnet-4),
Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet), Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2), Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet), Anthropic [Claude 3.5 Haiku](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-haiku), and [Vertex AI Search](https://cloud.google.com/enterprise-search). The LLM depends on the question asked.
- LLM for Amazon Q: Amazon Q Developer
{{< /details >}}

View File

@ -162,6 +162,8 @@ Prerequisites:
- Review the list of [items that are exported](#project-items-that-are-exported). Not all items are exported.
- You must have at least the Maintainer role for the project.
- For significantly improved performance for repositories with a large number of Git references, use GitLab 18.0 or later. For more information, see our
[blog post about decreasing GitLab repository backup times](https://about.gitlab.com/blog/2025/06/05/how-we-decreased-gitlab-repo-backup-times-from-48-hours-to-41-minutes/).
To export a project and its data, follow these steps:

View File

@ -9,6 +9,9 @@ Naming/ClassAndModuleCamelCase:
AllowedNames:
- HTTP_V2
Style/NumericPredicate:
EnforcedStyle: comparison
Style/SpecialGlobalVars:
Enabled: false

View File

@ -73,21 +73,18 @@ module Gitlab
start_time = nil
read_total_timeout = options.fetch(:timeout, DEFAULT_READ_TOTAL_TIMEOUT)
byte_size = 0
already_logged = false
max_bytesize = options.fetch(:max_bytes, 0)
promise = Concurrent::Promise.new do
Gitlab::Utils.restrict_within_concurrent_ruby do
httparty_perform_request(http_method, path, options_with_timeouts) do |fragment|
start_time ||= system_monotonic_time
elapsed = system_monotonic_time - start_time
byte_size += fragment.bytesize if should_log_response_size?
byte_size += fragment.bytesize
raise ReadTotalTimeout, "Request timed out after #{elapsed} seconds" if elapsed > read_total_timeout
if should_log_response_size? && byte_size > expected_max_response_size && !already_logged
configuration.log_with_level(:debug, message: 'gitlab/http: response size', size: byte_size)
already_logged = true
end
check_max_byte_size_limit!(max_bytesize, byte_size)
yield fragment if block
end
@ -101,20 +98,17 @@ module Gitlab
start_time = nil
read_total_timeout = options.fetch(:timeout, DEFAULT_READ_TOTAL_TIMEOUT)
byte_size = 0
already_logged = false
max_bytesize = options.fetch(:max_bytes, 0)
httparty_perform_request(http_method, path, options_with_timeouts) do |fragment|
start_time ||= system_monotonic_time
elapsed = system_monotonic_time - start_time
byte_size += fragment.bytesize if should_log_response_size?
if should_log_response_size? && byte_size > expected_max_response_size && !already_logged
configuration.log_with_level(:debug, message: 'gitlab/http: response size', size: byte_size)
already_logged = true
end
byte_size += fragment.bytesize
raise ReadTotalTimeout, "Request timed out after #{elapsed} seconds" if elapsed > read_total_timeout
check_max_byte_size_limit!(max_bytesize, byte_size)
yield fragment if block
end
rescue HTTParty::RedirectionTooDeep
@ -141,19 +135,18 @@ module Gitlab
raise SilentModeBlockedError, 'only get, head, options, and trace methods are allowed in silent mode'
end
def check_max_byte_size_limit!(max_bytesize, byte_size)
return unless max_bytesize > 0 && byte_size > max_bytesize
configuration.log_with_level(:error,
{ message: 'Gitlab::HTTP - Response size too large', size: byte_size })
raise ResponseSizeTooLarge, "Response size over #{max_bytesize} bytes"
end
def system_monotonic_time
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
end
def should_log_response_size?
return @should_log_response_size if instance_variable_defined?(:@should_log_response_size)
@should_log_response_size = ENV["GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE"].to_i.positive?
end
def expected_max_response_size
ENV["GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE"].to_i
end
end
end
end

View File

@ -9,6 +9,8 @@ module Gitlab
ReadTotalTimeout = Class.new(Net::ReadTimeout)
HeaderReadTimeout = Class.new(Net::ReadTimeout)
SilentModeBlockedError = Class.new(StandardError)
ResponseSizeTooLarge = Class.new(StandardError)
MaxDecompressionSizeError = Class.new(StandardError)
HTTP_TIMEOUT_ERRORS = [
Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Gitlab::HTTP_V2::ReadTotalTimeout
@ -18,7 +20,7 @@ module Gitlab
EOFError, SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH,
Gitlab::HTTP_V2::BlockedUrlError, Gitlab::HTTP_V2::RedirectionTooDeep,
Net::HTTPBadResponse
Net::HTTPBadResponse, Gitlab::HTTP_V2::ResponseSizeTooLarge, Gitlab::HTTP_V2::ResponseSizeTooLarge
].freeze
end
end

View File

@ -246,67 +246,72 @@ RSpec.describe Gitlab::HTTP_V2, feature_category: :shared do
end
end
describe 'logging response size' do
context 'when GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE is not set' do
before do
stub_full_request('http://example.org', method: :any).to_return(status: 200, body: 'hello world')
end
it 'does not log response size' do
expect(described_class.configuration)
.not_to receive(:log_with_level)
described_class.get('http://example.org')
end
context 'when the request is async' do
it 'does not log response size' do
expect(described_class.configuration)
.not_to receive(:log_with_level)
described_class.get('http://example.org', async: true).execute.value
end
end
describe 'response size limits' do
before do
stub_full_request('http://example.org', method: :any).to_return(status: 200, body: 'hello world')
end
context 'when GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE is set' do
before do
described_class::Client.remove_instance_variable(:@should_log_response_size)
stub_env('GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE', 5)
stub_full_request('http://example.org', method: :any).to_return(status: 200, body: 'hello world')
it 'logs and raises an error if response size is greater than max_bytes' do
expect(described_class.configuration).to receive(:log_with_level)
.with(:error, { message: "Gitlab::HTTP - Response size too large", size: 11 })
expect do
described_class.get('http://example.org', max_bytes: 1.byte)
end.to raise_error(Gitlab::HTTP_V2::ResponseSizeTooLarge)
end
it 'returns the response if size is less than max_bytes' do
expect(described_class.configuration).not_to receive(:log_with_level)
result = described_class.put('http://example.org', max_bytes: 16.bytes)
expect(result.body).to eq('hello world')
end
it 'returns the response if max_bytes is not provided' do
result = described_class.put('http://example.org')
expect(result.body).to eq('hello world')
end
it 'returns the response if max_bytes is 0' do
result = described_class.put('http://example.org', max_bytes: 0)
expect(result.body).to eq('hello world')
end
context 'when the request is async' do
it 'logs and raises an error if response size is greater than max_bytes' do
expect(described_class.configuration).to receive(:log_with_level)
.with(:error, { message: "Gitlab::HTTP - Response size too large", size: 11 })
expect do
described_class.get('http://example.org', max_bytes: 1.byte, async: true).execute.value
end.to raise_error(Gitlab::HTTP_V2::ResponseSizeTooLarge)
end
it 'logs the response size' do
expect(described_class.configuration)
.to receive(:log_with_level)
.with(:debug, { message: "gitlab/http: response size", size: 11 })
.once
it 'returns the response if size is less than max_bytes' do
expect(described_class.configuration).not_to receive(:log_with_level)
described_class.get('http://example.org')
result = described_class.put('http://example.org', max_bytes: 16.bytes, async: true)
expect(result.execute.value.body).to eq('hello world')
end
context 'when the request is async' do
it 'logs response size' do
expect(described_class.configuration)
.to receive(:log_with_level)
.with(:debug, { message: "gitlab/http: response size", size: 11 })
.once
it 'returns the response if max_bytes is not provided' do
expect(described_class.configuration).not_to receive(:log_with_level)
described_class.get('http://example.org', async: true).execute.value
end
result = described_class.put('http://example.org', async: true)
expect(result.execute.value.body).to eq('hello world')
end
context 'and the response size is smaller than the limit' do
before do
stub_env('GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE', 50)
end
it 'returns the response if max_bytes is 0' do
expect(described_class.configuration).not_to receive(:log_with_level)
it 'does not log the response size' do
expect(described_class.configuration)
.not_to receive(:log_with_level)
result = described_class.put('http://example.org', max_bytes: 0, async: true)
described_class.get('http://example.org')
end
expect(result.execute.value.body).to eq('hello world')
end
end
end

View File

@ -15,7 +15,9 @@ module API
urgency :low, [
'/projects/:id/merge_requests/:noteable_id/notes',
'/projects/:id/merge_requests/:noteable_id/notes/:note_id'
'/projects/:id/merge_requests/:noteable_id/notes/:note_id',
'/projects/:id/issues/:noteable_id/notes',
'/projects/:id/issues/:noteable_id/notes/:note_id'
]
Helpers::NotesHelpers.noteable_types.each do |noteable_type|

View File

@ -2,7 +2,7 @@
module Atlassian
module JiraConnect
class Client < Gitlab::HTTP
class Client
def self.generate_update_sequence_id
(Time.now.utc.to_f * 1000).round
end
@ -46,7 +46,7 @@ module Atlassian
uri = URI.join(@base_uri, path)
uri.query = URI.encode_www_form(query_params)
self.class.get(uri, headers: headers(uri, 'GET'))
Integrations::Clients::HTTP.get(uri, headers: headers(uri, 'GET'))
end
def store_ff_info(project:, feature_flags:, **opts)
@ -124,13 +124,13 @@ module Atlassian
def post(path, payload)
uri = URI.join(@base_uri, path)
self.class.post(uri, headers: headers(uri), body: metadata.merge(payload).to_json)
Integrations::Clients::HTTP.post(uri, headers: headers(uri), body: metadata.merge(payload).to_json)
end
def delete(path)
uri = URI.join(@base_uri, path)
self.class.delete(uri, headers: headers(uri, 'DELETE'))
Integrations::Clients::HTTP.delete(uri, headers: headers(uri, 'DELETE'))
end
def headers(uri, http_method = 'POST')

View File

@ -17,7 +17,7 @@ module Bitbucket
def get(path, extra_query = {})
response = retry_with_exponential_backoff do
Gitlab::HTTP.get(build_url(path), basic_auth: basic_auth, headers: headers, query: extra_query)
Import::Clients::HTTP.get(build_url(path), basic_auth: basic_auth, headers: headers, query: extra_query)
end
response.parsed_response

View File

@ -33,7 +33,7 @@ module BitbucketServer
def get(path, extra_query = {})
response = retry_with_delay do
Gitlab::HTTP.get(build_url(path), basic_auth: auth, headers: accept_headers, query: extra_query)
Import::Clients::HTTP.get(build_url(path), basic_auth: auth, headers: accept_headers, query: extra_query)
end
check_errors!(response)
@ -45,7 +45,7 @@ module BitbucketServer
def post(path, body)
response = retry_with_delay do
Gitlab::HTTP.post(build_url(path), basic_auth: auth, headers: post_headers, body: body)
Import::Clients::HTTP.post(build_url(path), basic_auth: auth, headers: post_headers, body: body)
end
check_errors!(response)
@ -63,7 +63,7 @@ module BitbucketServer
url = delete_url(resource, path)
response = retry_with_delay do
Gitlab::HTTP.delete(url, basic_auth: auth, headers: post_headers, body: body)
Import::Clients::HTTP.delete(url, basic_auth: auth, headers: post_headers, body: body)
end
check_errors!(response)

View File

@ -13,7 +13,7 @@ module BulkImports
end
def execute(query:, variables: {})
response = ::Gitlab::HTTP.post(
response = Import::Clients::HTTP.post(
url,
headers: headers,
follow_redirects: false,

Some files were not shown because too many files have changed in this diff Show More