Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
fdf32113c3
commit
9979d2afd6
|
|
@ -1258,7 +1258,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'ee/spec/presenters/ci/minutes/usage_presenter_spec.rb'
|
||||
- 'ee/spec/presenters/ci/pipeline_presenter_spec.rb'
|
||||
- 'ee/spec/presenters/dast/site_profile_presenter_spec.rb'
|
||||
- 'ee/spec/presenters/ee/blob_presenter_spec.rb'
|
||||
- 'ee/spec/presenters/ee/clusters/cluster_presenter_spec.rb'
|
||||
- 'ee/spec/presenters/ee/instance_clusterable_presenter_spec.rb'
|
||||
- 'ee/spec/presenters/ee/issue_presenter_spec.rb'
|
||||
|
|
@ -3335,7 +3334,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/lib/gitlab/conflict/file_spec.rb'
|
||||
- 'spec/lib/gitlab/consul/internal_spec.rb'
|
||||
- 'spec/lib/gitlab/container_repository/tags/cache_spec.rb'
|
||||
- 'spec/lib/gitlab/content_security_policy/config_loader_spec.rb'
|
||||
- 'spec/lib/gitlab/counters/buffered_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/counters/legacy_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/cross_project_access/check_collection_spec.rb'
|
||||
|
|
@ -5122,7 +5120,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/policies/work_item_policy_spec.rb'
|
||||
- 'spec/presenters/alert_management/alert_presenter_spec.rb'
|
||||
- 'spec/presenters/award_emoji_presenter_spec.rb'
|
||||
- 'spec/presenters/blob_presenter_spec.rb'
|
||||
- 'spec/presenters/blobs/notebook_presenter_spec.rb'
|
||||
- 'spec/presenters/blobs/unfold_presenter_spec.rb'
|
||||
- 'spec/presenters/ci/bridge_presenter_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
6eb139660ed3ebc5921f565672a00c71973fc620
|
||||
b3ea2f8bb802758d667f642b1228d31990559343
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -407,7 +407,7 @@ group :development, :test do
|
|||
|
||||
gem 'database_cleaner', '~> 1.7.0'
|
||||
gem 'factory_bot_rails', '~> 6.2.0'
|
||||
gem 'rspec-rails', '~> 6.0.1'
|
||||
gem 'rspec-rails', '~> 6.0.3'
|
||||
|
||||
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
|
||||
gem 'minitest', '~> 5.11.0'
|
||||
|
|
|
|||
|
|
@ -525,13 +525,13 @@
|
|||
{"name":"rqrcode-rails3","version":"0.1.7","platform":"ruby","checksum":"6f0582f26485123e5ed6f2a8a2871f00d86d353e0f58c8429a5a13212bcf48c4"},
|
||||
{"name":"rspec","version":"3.12.0","platform":"ruby","checksum":"ccc41799a43509dc0be84070e3f0410ac95cbd480ae7b6c245543eb64162399c"},
|
||||
{"name":"rspec-benchmark","version":"0.6.0","platform":"ruby","checksum":"1014adb57ec2599a2455c63884229f367a2fff6a63a77fd68ce5d804c83dd6cf"},
|
||||
{"name":"rspec-core","version":"3.12.0","platform":"ruby","checksum":"c466f4137966526e177d2156ca45c249eeecc7ed519b23ae2fb80c4675406bc5"},
|
||||
{"name":"rspec-expectations","version":"3.12.2","platform":"ruby","checksum":"8652db70b25ae3378b7274477a906b6ad1833a7b7cfbb001a03f49dd1c1d6a0d"},
|
||||
{"name":"rspec-core","version":"3.12.2","platform":"ruby","checksum":"155b54480f28e2b2813185077fe435c2d663031616360ed3b179a9d6a55d2551"},
|
||||
{"name":"rspec-expectations","version":"3.12.3","platform":"ruby","checksum":"093d18e2e7e0a2c619ef8f7343d442fc6c0793fb7897d56f16f26c8a9d244416"},
|
||||
{"name":"rspec-mocks","version":"3.12.6","platform":"ruby","checksum":"de51a4148ba2ce6f1c1646a2a03e9df2f52da9a42b164f2e7467b2cbe37e07bf"},
|
||||
{"name":"rspec-parameterized","version":"1.0.0","platform":"ruby","checksum":"9c07b043c72afbd23dd9a1dd48c06f46bc2fb1a6d875c6703e254932ba28b386"},
|
||||
{"name":"rspec-parameterized-core","version":"1.0.0","platform":"ruby","checksum":"287b494985e79821160af63aba4f91db8dbfa9a21cb200db34ba38f40e16ccc1"},
|
||||
{"name":"rspec-parameterized-table_syntax","version":"1.0.0","platform":"ruby","checksum":"d7df951eff9c5dd367ca7d5f9ae4853bb7ab7941f9d5b35bba361d112704988c"},
|
||||
{"name":"rspec-rails","version":"6.0.1","platform":"ruby","checksum":"016c8ebd5b38ce5cbce949de2f5b28f2bde7bb78d4de26940516713597b26e34"},
|
||||
{"name":"rspec-rails","version":"6.0.3","platform":"ruby","checksum":"6d1812cfaf18dba5a08d7e30c85149b24a220fae064853a96e451376be6fd820"},
|
||||
{"name":"rspec-retry","version":"0.6.2","platform":"ruby","checksum":"6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858"},
|
||||
{"name":"rspec-support","version":"3.12.0","platform":"ruby","checksum":"dd4d44b247ff679b95b5607ac5641d197a5f9b1d33f916123cb98fc5f917c58b"},
|
||||
{"name":"rspec_junit_formatter","version":"0.6.0","platform":"ruby","checksum":"40dde674e6ae4e6cc0ff560da25497677e34fefd2338cc467a8972f602b62b15"},
|
||||
|
|
|
|||
16
Gemfile.lock
16
Gemfile.lock
|
|
@ -1348,9 +1348,9 @@ GEM
|
|||
benchmark-perf (~> 0.6)
|
||||
benchmark-trend (~> 0.4)
|
||||
rspec (>= 3.0)
|
||||
rspec-core (3.12.0)
|
||||
rspec-core (3.12.2)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-expectations (3.12.2)
|
||||
rspec-expectations (3.12.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-mocks (3.12.6)
|
||||
|
|
@ -1367,14 +1367,14 @@ GEM
|
|||
rspec-parameterized-table_syntax (1.0.0)
|
||||
binding_of_caller
|
||||
rspec-parameterized-core (< 2)
|
||||
rspec-rails (6.0.1)
|
||||
rspec-rails (6.0.3)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
rspec-core (~> 3.11)
|
||||
rspec-expectations (~> 3.11)
|
||||
rspec-mocks (~> 3.11)
|
||||
rspec-support (~> 3.11)
|
||||
rspec-core (~> 3.12)
|
||||
rspec-expectations (~> 3.12)
|
||||
rspec-mocks (~> 3.12)
|
||||
rspec-support (~> 3.12)
|
||||
rspec-retry (0.6.2)
|
||||
rspec-core (> 3.3)
|
||||
rspec-support (3.12.0)
|
||||
|
|
@ -1968,7 +1968,7 @@ DEPENDENCIES
|
|||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-benchmark (~> 0.6.0)
|
||||
rspec-parameterized (~> 1.0)
|
||||
rspec-rails (~> 6.0.1)
|
||||
rspec-rails (~> 6.0.3)
|
||||
rspec-retry (~> 0.6.2)
|
||||
rspec_flaky!
|
||||
rspec_junit_formatter
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ export default {
|
|||
i18n: {
|
||||
emptyField: __('Never'),
|
||||
expired: __('Expired'),
|
||||
header: __('Active %{accessTokenTypePlural} (%{totalAccessTokens})'),
|
||||
modalMessage: __(
|
||||
'Are you sure you want to revoke this %{accessTokenType}? This action cannot be undone.',
|
||||
),
|
||||
|
|
@ -45,7 +44,6 @@ export default {
|
|||
'initialActiveAccessTokens',
|
||||
'noActiveTokensMessage',
|
||||
'showRole',
|
||||
'information',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -74,12 +72,6 @@ export default {
|
|||
|
||||
return FIELDS.filter(({ key }) => !ignoredFields.includes(key));
|
||||
},
|
||||
header() {
|
||||
return sprintf(this.$options.i18n.header, {
|
||||
accessTokenTypePlural: this.accessTokenTypePlural,
|
||||
totalAccessTokens: this.activeAccessTokens.length,
|
||||
});
|
||||
},
|
||||
modalMessage() {
|
||||
return sprintf(this.$options.i18n.modalMessage, {
|
||||
accessTokenType: this.accessTokenType,
|
||||
|
|
@ -114,65 +106,66 @@ export default {
|
|||
|
||||
<template>
|
||||
<dom-element-listener :selector="$options.FORM_SELECTOR" @[$options.EVENT_SUCCESS]="onSuccess">
|
||||
<div class="gl-pt-6">
|
||||
<h5>{{ header }}</h5>
|
||||
|
||||
<p v-if="information" data-testid="information-section">
|
||||
{{ information }}
|
||||
</p>
|
||||
|
||||
<gl-table
|
||||
data-testid="active-tokens"
|
||||
:empty-text="noActiveTokensMessage"
|
||||
:fields="filteredFields"
|
||||
:items="activeAccessTokens"
|
||||
:per-page="$options.PAGE_SIZE"
|
||||
:current-page="currentPage"
|
||||
:sort-compare="sortingChanged"
|
||||
show-empty
|
||||
<div>
|
||||
<div
|
||||
class="gl-new-card-body gl-px-0 gl-overflow-hidden gl-bg-gray-10 gl-border-l gl-border-r gl-border-b gl-rounded-bottom-base gl-mb-5 gl-md-mb-0"
|
||||
>
|
||||
<template #cell(createdAt)="{ item: { createdAt } }">
|
||||
<user-date :date="createdAt" />
|
||||
</template>
|
||||
|
||||
<template #head(lastUsedAt)="{ label }">
|
||||
<span>{{ label }}</span>
|
||||
<gl-link :href="$options.lastUsedHelpLink"
|
||||
><gl-icon name="question-o" /><span class="gl-sr-only">{{
|
||||
s__('AccessTokens|The last time a token was used')
|
||||
}}</span></gl-link
|
||||
>
|
||||
</template>
|
||||
|
||||
<template #cell(lastUsedAt)="{ item: { lastUsedAt } }">
|
||||
<time-ago-tooltip v-if="lastUsedAt" :time="lastUsedAt" />
|
||||
<template v-else> {{ $options.i18n.emptyField }}</template>
|
||||
</template>
|
||||
|
||||
<template #cell(expiresAt)="{ item: { expiresAt, expired, expiresSoon } }">
|
||||
<template v-if="expiresAt">
|
||||
<span v-if="expired" class="text-danger">{{ $options.i18n.expired }}</span>
|
||||
<time-ago-tooltip v-else :class="{ 'text-warning': expiresSoon }" :time="expiresAt" />
|
||||
<gl-table
|
||||
data-testid="active-tokens"
|
||||
:empty-text="noActiveTokensMessage"
|
||||
:fields="filteredFields"
|
||||
:items="activeAccessTokens"
|
||||
:per-page="$options.PAGE_SIZE"
|
||||
:current-page="currentPage"
|
||||
:sort-compare="sortingChanged"
|
||||
show-empty
|
||||
stacked="sm"
|
||||
>
|
||||
<template #cell(createdAt)="{ item: { createdAt } }">
|
||||
<user-date :date="createdAt" />
|
||||
</template>
|
||||
<span v-else v-gl-tooltip :title="$options.i18n.tokenValidity">{{
|
||||
$options.i18n.emptyField
|
||||
}}</span>
|
||||
</template>
|
||||
|
||||
<template #cell(action)="{ item: { revokePath } }">
|
||||
<gl-button
|
||||
v-if="revokePath"
|
||||
category="tertiary"
|
||||
:aria-label="$options.i18n.revokeButton"
|
||||
:data-confirm="modalMessage"
|
||||
data-confirm-btn-variant="danger"
|
||||
data-qa-selector="revoke_button"
|
||||
data-method="put"
|
||||
:href="revokePath"
|
||||
icon="remove"
|
||||
/>
|
||||
</template>
|
||||
</gl-table>
|
||||
<template #head(lastUsedAt)="{ label }">
|
||||
<span>{{ label }}</span>
|
||||
<gl-link :href="$options.lastUsedHelpLink"
|
||||
><gl-icon name="question-o" /><span class="gl-sr-only">{{
|
||||
s__('AccessTokens|The last time a token was used')
|
||||
}}</span></gl-link
|
||||
>
|
||||
</template>
|
||||
|
||||
<template #cell(lastUsedAt)="{ item: { lastUsedAt } }">
|
||||
<time-ago-tooltip v-if="lastUsedAt" :time="lastUsedAt" />
|
||||
<template v-else> {{ $options.i18n.emptyField }}</template>
|
||||
</template>
|
||||
|
||||
<template #cell(expiresAt)="{ item: { expiresAt, expired, expiresSoon } }">
|
||||
<template v-if="expiresAt">
|
||||
<span v-if="expired" class="text-danger">{{ $options.i18n.expired }}</span>
|
||||
<time-ago-tooltip v-else :class="{ 'text-warning': expiresSoon }" :time="expiresAt" />
|
||||
</template>
|
||||
<span v-else v-gl-tooltip :title="$options.i18n.tokenValidity">{{
|
||||
$options.i18n.emptyField
|
||||
}}</span>
|
||||
</template>
|
||||
|
||||
<template #cell(action)="{ item: { revokePath } }">
|
||||
<gl-button
|
||||
v-if="revokePath"
|
||||
category="tertiary"
|
||||
:title="$options.i18n.revokeButton"
|
||||
:aria-label="$options.i18n.revokeButton"
|
||||
:data-confirm="modalMessage"
|
||||
data-confirm-btn-variant="danger"
|
||||
data-qa-selector="revoke_button"
|
||||
data-method="put"
|
||||
:href="revokePath"
|
||||
icon="remove"
|
||||
class="has-tooltip"
|
||||
/>
|
||||
</template>
|
||||
</gl-table>
|
||||
</div>
|
||||
<gl-pagination
|
||||
v-if="showPagination"
|
||||
v-model="currentPage"
|
||||
|
|
@ -183,6 +176,7 @@ export default {
|
|||
:label-next-page="__('Go to next page')"
|
||||
:label-prev-page="__('Go to previous page')"
|
||||
align="center"
|
||||
class="gl-mt-5"
|
||||
/>
|
||||
</div>
|
||||
</dom-element-listener>
|
||||
|
|
|
|||
|
|
@ -93,6 +93,12 @@ export default {
|
|||
this.form.querySelectorAll('input[type=checkbox]').forEach((el) => {
|
||||
el.checked = false;
|
||||
});
|
||||
document.querySelectorAll('.js-token-card').forEach((el) => {
|
||||
el.querySelector('.js-add-new-token-form').style.display = '';
|
||||
el.querySelector('.js-toggle-button').style.display = 'block';
|
||||
el.querySelector('.js-token-count').innerText =
|
||||
parseInt(el.querySelector('.js-token-count').innerText, 10) + 1;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -105,7 +111,12 @@ export default {
|
|||
@[$options.EVENT_SUCCESS]="onSuccess"
|
||||
>
|
||||
<div ref="container" data-testid="access-token-section" data-qa-selector="access_token_section">
|
||||
<template v-if="newToken">
|
||||
<gl-alert
|
||||
v-if="newToken"
|
||||
variant="success"
|
||||
data-testid="success-message"
|
||||
@dismiss="newToken = null"
|
||||
>
|
||||
<input-copy-toggle-visibility
|
||||
:copy-button-title="copyButtonTitle"
|
||||
:label="label"
|
||||
|
|
@ -113,16 +124,22 @@ export default {
|
|||
:value="newToken"
|
||||
:form-input-group-props="formInputGroupProps"
|
||||
readonly
|
||||
size="lg"
|
||||
class="gl-mb-0"
|
||||
>
|
||||
<template #description>
|
||||
{{ $options.i18n.description }}
|
||||
</template>
|
||||
</input-copy-toggle-visibility>
|
||||
<hr />
|
||||
</template>
|
||||
</gl-alert>
|
||||
|
||||
<template v-if="errors">
|
||||
<gl-alert :title="alertDangerTitle" variant="danger" @dismiss="errors = null">
|
||||
<gl-alert
|
||||
:title="alertDangerTitle"
|
||||
variant="danger"
|
||||
data-testid="error-message"
|
||||
@dismiss="errors = null"
|
||||
>
|
||||
<ul class="gl-m-0">
|
||||
<li v-for="error in errors" :key="error">
|
||||
{{ error }}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
formInputGroupProps() {
|
||||
|
|
@ -40,6 +45,7 @@ export default {
|
|||
:value="token"
|
||||
:copy-button-title="copyButtonTitle"
|
||||
readonly
|
||||
:size="size"
|
||||
>
|
||||
<template #description>
|
||||
<slot name="input-description"></slot>
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export default {
|
|||
:input-label="$options.i18n[tokenType].label"
|
||||
:copy-button-title="$options.i18n[tokenType].copyButtonTitle"
|
||||
:data-testid="$options.htmlAttributes[tokenType].containerTestId"
|
||||
size="md"
|
||||
>
|
||||
<template #title>
|
||||
<div class="settings-sticky-header">
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ export const initAccessTokenTableApp = () => {
|
|||
const {
|
||||
accessTokenType,
|
||||
accessTokenTypePlural,
|
||||
information,
|
||||
initialActiveAccessTokens: initialActiveAccessTokensJson,
|
||||
noActiveTokensMessage: noTokensMessage,
|
||||
} = el.dataset;
|
||||
|
|
@ -39,7 +38,6 @@ export const initAccessTokenTableApp = () => {
|
|||
provide: {
|
||||
accessTokenType,
|
||||
accessTokenTypePlural,
|
||||
information,
|
||||
initialActiveAccessTokens,
|
||||
noActiveTokensMessage,
|
||||
showRole,
|
||||
|
|
|
|||
|
|
@ -74,9 +74,8 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-display-flex gl-align-items-flex-start">
|
||||
<div v-if="tertiaryButtons.length" class="gl-display-flex gl-align-items-flex-start">
|
||||
<gl-dropdown
|
||||
v-if="tertiaryButtons.length"
|
||||
v-gl-tooltip
|
||||
:title="__('Options')"
|
||||
:text="dropdownLabel"
|
||||
|
|
@ -102,33 +101,31 @@ export default {
|
|||
{{ btn.text }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
<template v-if="tertiaryButtons.length">
|
||||
<gl-button
|
||||
v-for="(btn, index) in tertiaryButtons"
|
||||
:id="btn.id"
|
||||
:key="index"
|
||||
v-gl-tooltip.hover
|
||||
:title="setTooltip(btn)"
|
||||
:href="btn.href"
|
||||
:target="btn.target"
|
||||
:class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]"
|
||||
:data-clipboard-text="btn.dataClipboardText"
|
||||
:data-qa-selector="actionButtonQaSelector(btn)"
|
||||
:data-method="btn.dataMethod"
|
||||
:icon="btn.icon"
|
||||
:data-testid="btn.testId || 'extension-actions-button'"
|
||||
:variant="btn.variant || 'confirm'"
|
||||
:loading="btn.loading"
|
||||
:disabled="btn.loading"
|
||||
category="tertiary"
|
||||
size="small"
|
||||
class="gl-display-none gl-md-display-block gl-float-left"
|
||||
@click="onClickAction(btn)"
|
||||
>
|
||||
<template v-if="btn.text">
|
||||
{{ btn.text }}
|
||||
</template>
|
||||
</gl-button>
|
||||
</template>
|
||||
<gl-button
|
||||
v-for="(btn, index) in tertiaryButtons"
|
||||
:id="btn.id"
|
||||
:key="index"
|
||||
v-gl-tooltip.hover
|
||||
:title="setTooltip(btn)"
|
||||
:href="btn.href"
|
||||
:target="btn.target"
|
||||
:class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]"
|
||||
:data-clipboard-text="btn.dataClipboardText"
|
||||
:data-qa-selector="actionButtonQaSelector(btn)"
|
||||
:data-method="btn.dataMethod"
|
||||
:icon="btn.icon"
|
||||
:data-testid="btn.testId || 'extension-actions-button'"
|
||||
:variant="btn.variant || 'confirm'"
|
||||
:loading="btn.loading"
|
||||
:disabled="btn.loading"
|
||||
category="tertiary"
|
||||
size="small"
|
||||
class="gl-display-none gl-md-display-block gl-float-left"
|
||||
@click="onClickAction(btn)"
|
||||
>
|
||||
<template v-if="btn.text">
|
||||
{{ btn.text }}
|
||||
</template>
|
||||
</gl-button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -73,10 +73,16 @@ export default {
|
|||
<template #body>
|
||||
<div class="gl-w-full gl-display-flex" :class="{ 'gl-flex-direction-column': level === 1 }">
|
||||
<div class="gl-display-flex gl-flex-grow-1">
|
||||
<div class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column">
|
||||
<p v-safe-html="generatedText" class="gl-mb-0 gl-mr-1"></p>
|
||||
<gl-link v-if="data.link" :href="data.link.href">{{ data.link.text }}</gl-link>
|
||||
<p v-if="data.supportingText" v-safe-html="generatedSupportingText" class="gl-mb-0"></p>
|
||||
<div class="gl-display-flex gl-flex-grow-1 gl-align-items-baseline">
|
||||
<div>
|
||||
<p v-safe-html="generatedText" class="gl-mb-0 gl-mr-1"></p>
|
||||
<gl-link v-if="data.link" :href="data.link.href">{{ data.link.text }}</gl-link>
|
||||
<p
|
||||
v-if="data.supportingText"
|
||||
v-safe-html="generatedSupportingText"
|
||||
class="gl-mb-0"
|
||||
></p>
|
||||
</div>
|
||||
<gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
|
||||
{{ data.badge.text }}
|
||||
</gl-badge>
|
||||
|
|
|
|||
|
|
@ -65,6 +65,11 @@ export default {
|
|||
return {};
|
||||
},
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
if (!this.readonly && !this.value) {
|
||||
|
|
@ -151,6 +156,7 @@ export default {
|
|||
<gl-form-input
|
||||
ref="input"
|
||||
:readonly="readonly"
|
||||
:size="size"
|
||||
class="gl-font-monospace! gl-cursor-default!"
|
||||
v-bind="formInputGroupProps"
|
||||
:value="value"
|
||||
|
|
|
|||
|
|
@ -91,7 +91,9 @@
|
|||
@include gl-border-gray-100;
|
||||
@include gl-rounded-base;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-new-card-body {
|
||||
// Table adjustments
|
||||
@mixin new-card-table-adjustments {
|
||||
tbody > tr {
|
||||
|
|
|
|||
|
|
@ -76,14 +76,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
@include gl-pt-6;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
@include gl-pb-5;
|
||||
}
|
||||
.settings-section::after {
|
||||
content: '';
|
||||
display: block;
|
||||
@include gl-pb-7;
|
||||
}
|
||||
|
||||
.settings-section,
|
||||
|
|
@ -91,6 +87,11 @@
|
|||
@include gl-pt-0;
|
||||
}
|
||||
|
||||
// Fix for sticky header when there is no search bar.
|
||||
.flash-container + .settings-section {
|
||||
@include gl-pt-3;
|
||||
}
|
||||
|
||||
.settings-section ~ .settings-section {
|
||||
@include gl-pt-6;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,7 @@ module Repositories
|
|||
|
||||
def download
|
||||
lfs_object = LfsObject.find_by_oid(oid)
|
||||
unless lfs_object && lfs_object.file.exists?
|
||||
render_lfs_not_found
|
||||
return
|
||||
end
|
||||
return render_lfs_not_found unless lfs_object&.file&.exists?
|
||||
|
||||
send_upload(lfs_object.file, send_params: { content_type: "application/octet-stream" })
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ module Types
|
|||
|
||||
authorize :read_custom_emoji
|
||||
|
||||
connection_type_class(Types::CountableConnectionType)
|
||||
|
||||
expose_permissions Types::PermissionTypes::CustomEmoji
|
||||
|
||||
field :id, ::Types::GlobalIDType[::CustomEmoji],
|
||||
null: false,
|
||||
description: 'ID of the emoji.'
|
||||
|
|
@ -23,5 +27,9 @@ module Types
|
|||
field :external, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
description: 'Whether the emoji is an external link.'
|
||||
|
||||
field :created_at, Types::TimeType,
|
||||
null: false,
|
||||
description: 'Timestamp of when the custom emoji was created.'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module Types
|
|||
class Group < BasePermissionType
|
||||
graphql_name 'GroupPermissions'
|
||||
|
||||
abilities :read_group, :create_projects
|
||||
abilities :read_group, :create_projects, :create_custom_emoji
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ class Commit
|
|||
delegate :project, to: :repository, allow_nil: true
|
||||
|
||||
MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
|
||||
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
|
||||
MAX_SHA_LENGTH = Gitlab::Git::Commit::MAX_SHA_LENGTH
|
||||
COMMIT_SHA_PATTERN = Gitlab::Git::Commit::SHA_PATTERN.freeze
|
||||
EXACT_COMMIT_SHA_PATTERN = /\A#{COMMIT_SHA_PATTERN}\z/.freeze
|
||||
# Used by GFM to match and present link extensions on node texts and hrefs.
|
||||
LINK_EXTENSION_PATTERN = /(patch)/.freeze
|
||||
|
|
|
|||
|
|
@ -31,9 +31,8 @@ class CommitRange
|
|||
REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/.freeze
|
||||
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/.freeze
|
||||
|
||||
# In text references, the beginning and ending refs can only be SHAs
|
||||
# between 7 and 40 hex characters.
|
||||
STRICT_PATTERN = /\h{7,40}\.{2,3}\h{7,40}/.freeze
|
||||
# In text references, the beginning and ending refs can only be valid SHAs.
|
||||
STRICT_PATTERN = /#{Gitlab::Git::Commit::RAW_SHA_PATTERN}\.{2,3}#{Gitlab::Git::Commit::RAW_SHA_PATTERN}/.freeze
|
||||
|
||||
def self.reference_prefix
|
||||
'@'
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
|
|||
include DiffHelper
|
||||
include TreeHelper
|
||||
include ChecksCollaboration
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
presents ::Blob, as: :blob
|
||||
|
||||
|
|
@ -149,6 +150,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
|
|||
end
|
||||
|
||||
def external_storage_url
|
||||
return lfs_object_url if lfs_blob?
|
||||
return unless static_objects_external_storage_enabled?
|
||||
|
||||
external_storage_url_or_path(url_helpers.project_raw_url(project, ref_qualified_path), project)
|
||||
|
|
@ -164,6 +166,25 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
|
|||
|
||||
private
|
||||
|
||||
def lfs_object_url
|
||||
return unless lfs_object.present?
|
||||
|
||||
if LfsObjectUploader.proxy_download_enabled? || lfs_object.file.file_storage?
|
||||
"#{project.http_url_to_repo}/gitlab-lfs/objects/#{lfs_object.oid}"
|
||||
else
|
||||
lfs_object.file.url(content_type: "application/octet-stream")
|
||||
end
|
||||
end
|
||||
|
||||
def lfs_object
|
||||
project.lfs_objects.find_by_oid(blob.lfs_oid) if lfs_blob?
|
||||
end
|
||||
strong_memoize_attr :lfs_object
|
||||
|
||||
def lfs_blob?
|
||||
blob.stored_externally? && blob.lfs_pointer?
|
||||
end
|
||||
|
||||
def path_params
|
||||
if ref_type.present?
|
||||
[project, ref_qualified_path, { ref_type: ref_type }]
|
||||
|
|
|
|||
|
|
@ -6,11 +6,24 @@
|
|||
|
||||
= render 'admin/users/head'
|
||||
|
||||
.row.gl-mt-3
|
||||
.col-lg-12
|
||||
#js-new-access-token-app{ data: { access_token_type: type } }
|
||||
#js-new-access-token-app{ data: { access_token_type: type } }
|
||||
|
||||
= render 'shared/access_tokens/form',
|
||||
= render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card gl-border-b-0 gl-rounded-bottom-left-none gl-rounded-bottom-right-none js-toggle-container js-token-card' }, header_options: { class: 'gl-new-card-header' }, body_options: { class: 'gl-new-card-body' }) do |c|
|
||||
- c.with_header do
|
||||
.gl-new-card-title-wrapper.gl-flex-direction-column
|
||||
%h3.gl-new-card-title
|
||||
= _('Impersonation tokens')
|
||||
.gl-new-card-count
|
||||
= sprite_icon('token', css_class: 'gl-mr-2')
|
||||
%span.js-token-count= @active_impersonation_tokens.size
|
||||
.gl-new-card-description
|
||||
= _("To see all the user's personal access tokens you must impersonate them first.")
|
||||
.gl-new-card-actions
|
||||
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: 'js-toggle-button js-toggle-content' }) do
|
||||
= _('Add new token')
|
||||
- c.with_body do
|
||||
.gl-new-card-add-form.gl-mt-3.gl-display-none.js-toggle-content.js-add-new-token-form
|
||||
= render 'shared/access_tokens/form',
|
||||
ajax: true,
|
||||
type: type,
|
||||
title: _('Add an impersonation token'),
|
||||
|
|
@ -20,4 +33,4 @@
|
|||
scopes: @scopes,
|
||||
help_path: help_page_path('api/rest/index', anchor: 'impersonation-tokens')
|
||||
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_impersonation_tokens.to_json, information: _("To see all the user's personal access tokens you must impersonate them first.") } }
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_impersonation_tokens.to_json } }
|
||||
|
|
|
|||
|
|
@ -25,18 +25,31 @@
|
|||
|
||||
#js-new-access-token-app{ data: { access_token_type: type } }
|
||||
|
||||
- if current_user.can?(:create_resource_access_tokens, @group)
|
||||
= render 'shared/access_tokens/form',
|
||||
ajax: true,
|
||||
type: type,
|
||||
path: group_settings_access_tokens_path(@group),
|
||||
resource: @group,
|
||||
token: @resource_access_token,
|
||||
scopes: @scopes,
|
||||
access_levels: GroupMember.access_level_roles,
|
||||
default_access_level: Gitlab::Access::GUEST,
|
||||
prefix: :resource_access_token,
|
||||
help_path: help_page_path('user/group/settings/group_access_tokens', anchor: 'scopes-for-a-group-access-token')
|
||||
= render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card gl-border-b-0 gl-rounded-bottom-left-none gl-rounded-bottom-right-none js-toggle-container js-token-card' }, header_options: { class: 'gl-new-card-header' }, body_options: { class: 'gl-new-card-body' }) do |c|
|
||||
- c.with_header do
|
||||
.gl-new-card-title-wrapper
|
||||
%h3.gl-new-card-title
|
||||
= _('Active group access tokens')
|
||||
.gl-new-card-count
|
||||
= sprite_icon('token', css_class: 'gl-mr-2')
|
||||
%span.js-token-count= @active_access_tokens.size
|
||||
- if current_user.can?(:create_resource_access_tokens, @group)
|
||||
.gl-new-card-actions
|
||||
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: 'js-toggle-button js-toggle-content' }) do
|
||||
= _('Add new token')
|
||||
- c.with_body do
|
||||
- if current_user.can?(:create_resource_access_tokens, @group)
|
||||
.gl-new-card-add-form.gl-mt-3.gl-display-none.js-toggle-content.js-add-new-token-form
|
||||
= render 'shared/access_tokens/form',
|
||||
ajax: true,
|
||||
type: type,
|
||||
path: group_settings_access_tokens_path(@group),
|
||||
resource: @group,
|
||||
token: @resource_access_token,
|
||||
scopes: @scopes,
|
||||
access_levels: GroupMember.access_level_roles,
|
||||
default_access_level: Gitlab::Access::GUEST,
|
||||
prefix: :resource_access_token,
|
||||
help_path: help_page_path('user/group/settings/group_access_tokens', anchor: 'scopes-for-a-group-access-token')
|
||||
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json, no_active_tokens_message: _('This group has no active access tokens.'), show_role: true
|
||||
} }
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json, no_active_tokens_message: _('This group has no active access tokens.'), show_role: true } }
|
||||
|
|
|
|||
|
|
@ -16,13 +16,26 @@
|
|||
|
||||
#js-new-access-token-app{ data: { access_token_type: type } }
|
||||
|
||||
= render 'shared/access_tokens/form',
|
||||
ajax: true,
|
||||
type: type,
|
||||
path: profile_personal_access_tokens_path,
|
||||
token: @personal_access_token,
|
||||
scopes: @scopes,
|
||||
help_path: help_page_path('user/profile/personal_access_tokens.md', anchor: 'personal-access-token-scopes')
|
||||
= render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card gl-border-b-0 gl-rounded-bottom-left-none gl-rounded-bottom-right-none js-toggle-container js-token-card' }, header_options: { class: 'gl-new-card-header' }, body_options: { class: 'gl-new-card-body' }) do |c|
|
||||
- c.with_header do
|
||||
.gl-new-card-title-wrapper
|
||||
%h3.gl-new-card-title
|
||||
= _('Active personal access tokens')
|
||||
.gl-new-card-count
|
||||
= sprite_icon('token', css_class: 'gl-mr-2')
|
||||
%span.js-token-count= @active_access_tokens.size
|
||||
.gl-new-card-actions
|
||||
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: 'js-toggle-button js-toggle-content' }) do
|
||||
= _('Add new token')
|
||||
- c.with_body do
|
||||
.gl-new-card-add-form.gl-mt-3.gl-display-none.js-toggle-content.js-add-new-token-form
|
||||
= render 'shared/access_tokens/form',
|
||||
ajax: true,
|
||||
type: type,
|
||||
path: profile_personal_access_tokens_path,
|
||||
token: @personal_access_token,
|
||||
scopes: @scopes,
|
||||
help_path: help_page_path('user/profile/personal_access_tokens.md', anchor: 'personal-access-token-scopes')
|
||||
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json } }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
- type_plural = _('project access tokens')
|
||||
- @force_desktop_expanded_sidebar = true
|
||||
|
||||
.gl-mt-5.js-search-settings-section
|
||||
%h4.gl-my-0
|
||||
= page_title
|
||||
.settings-section.js-search-settings-section
|
||||
.settings-sticky-header
|
||||
.settings-sticky-header-inner
|
||||
%h4.gl-my-0
|
||||
= page_title
|
||||
%p.gl-text-secondary
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/settings/project_access_tokens') }
|
||||
- if current_user.can?(:create_resource_access_tokens, @project)
|
||||
|
|
@ -23,9 +25,21 @@
|
|||
|
||||
#js-new-access-token-app{ data: { access_token_type: type } }
|
||||
|
||||
- if current_user.can?(:create_resource_access_tokens, @project)
|
||||
= render_if_exists 'projects/settings/access_tokens/form',
|
||||
type: type
|
||||
= render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card gl-border-b-0 gl-rounded-bottom-left-none gl-rounded-bottom-right-none js-toggle-container js-token-card' }, header_options: { class: 'gl-new-card-header' }, body_options: { class: 'gl-new-card-body' }) do |c|
|
||||
- c.with_header do
|
||||
.gl-new-card-title-wrapper
|
||||
%h3.gl-new-card-title
|
||||
= _('Active project access tokens')
|
||||
.gl-new-card-count
|
||||
= sprite_icon('token', css_class: 'gl-mr-2')
|
||||
%span.js-token-count= @active_access_tokens.size
|
||||
- if current_user.can?(:create_resource_access_tokens, @project)
|
||||
.gl-new-card-actions
|
||||
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: 'js-toggle-button js-toggle-content' }) do
|
||||
= _('Add new token')
|
||||
- c.with_body do
|
||||
- if current_user.can?(:create_resource_access_tokens, @project)
|
||||
.gl-new-card-add-form.gl-mt-3.gl-display-none.js-toggle-content.js-add-new-token-form
|
||||
= render_if_exists 'projects/settings/access_tokens/form', type: type
|
||||
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json, no_active_tokens_message: _('This project has no active access tokens.'), show_role: true
|
||||
} }
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json, no_active_tokens_message: _('This project has no active access tokens.'), show_role: true } }
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
- access_levels = local_assigns.fetch(:access_levels, false)
|
||||
- default_access_level = local_assigns.fetch(:default_access_level, false)
|
||||
|
||||
%h5.gl-font-lg.gl-mt-0
|
||||
%h4.gl-mt-0
|
||||
= title
|
||||
|
||||
= gitlab_ui_form_for token, as: prefix, url: path, method: :post, html: { id: 'js-new-access-token-form', class: 'js-requires-input' }, remote: ajax do |f|
|
||||
|
|
@ -43,3 +43,5 @@
|
|||
|
||||
.gl-mt-3
|
||||
= f.submit s_('AccessTokens|Create %{type}') % { type: type }, data: { qa_selector: 'create_token_button' }, pajamas_button: true
|
||||
= render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do
|
||||
= _('Cancel')
|
||||
|
|
|
|||
|
|
@ -683,7 +683,7 @@ scope path: '(/-/jira)', constraints: ::Constraints::JiraEncodedUrlConstrainer.n
|
|||
)
|
||||
}
|
||||
|
||||
get 'commit/:id', constraints: { id: /\h{7,40}/ }, to: redirect { |params, req|
|
||||
get 'commit/:id', constraints: { id: Gitlab::Git::Commit::SHA_PATTERN }, to: redirect { |params, req|
|
||||
project_full_path = ::Gitlab::Jira::Dvcs.restore_full_path(
|
||||
namespace: params[:namespace_id],
|
||||
project: params[:project_id]
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ scope format: false do
|
|||
end
|
||||
end
|
||||
|
||||
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
|
||||
resources :commit, only: [:show], constraints: { id: Gitlab::Git::Commit::SHA_PATTERN } do
|
||||
member do
|
||||
get :branches
|
||||
get :pipelines
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
migration_job_name: BackfillDefaultBranchProtectionNamespaceSetting
|
||||
description: This migration back fills column default_branch_protection_defaults of namespace settings table
|
||||
feature_category: database
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127335/
|
||||
milestone: 16.3
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddEmailsToX509Certificates < Gitlab::Database::Migration[2.1]
|
||||
def change
|
||||
add_column :x509_certificates, :emails, :string, array: true, default: [], null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackfillDefaultBranchProtectionNamespaceSetting < Gitlab::Database::Migration[2.1]
|
||||
MIGRATION = "BackfillDefaultBranchProtectionNamespaceSetting"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 10_000
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:namespace_settings,
|
||||
:namespace_id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :namespace_settings, :namespace_id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ReplaceOldFkPCiBuildsMetadataToBuildsV3 < Gitlab::Database::Migration[2.1]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
return if new_foreign_key_exists?
|
||||
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists :p_ci_builds_metadata, :ci_builds,
|
||||
name: :fk_e20479742e_p, reverse_lock_order: true
|
||||
|
||||
rename_constraint :p_ci_builds_metadata, :temp_fk_e20479742e_p, :fk_e20479742e_p
|
||||
|
||||
Gitlab::Database::PostgresPartitionedTable.each_partition(:p_ci_builds_metadata) do |partition|
|
||||
rename_constraint partition.identifier, :temp_fk_e20479742e_p, :fk_e20479742e_p
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
return unless new_foreign_key_exists?
|
||||
|
||||
add_concurrent_partitioned_foreign_key :p_ci_builds_metadata, :ci_builds,
|
||||
name: :temp_fk_e20479742e_p,
|
||||
column: [:partition_id, :build_id],
|
||||
target_column: [:partition_id, :id],
|
||||
on_update: :cascade,
|
||||
on_delete: :cascade,
|
||||
validate: true,
|
||||
reverse_lock_order: true
|
||||
|
||||
switch_constraint_names :p_ci_builds_metadata, :fk_e20479742e_p, :temp_fk_e20479742e_p
|
||||
|
||||
Gitlab::Database::PostgresPartitionedTable.each_partition(:p_ci_builds_metadata) do |partition|
|
||||
switch_constraint_names partition.identifier, :fk_e20479742e_p, :temp_fk_e20479742e_p
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_foreign_key_exists?
|
||||
foreign_key_exists?(:p_ci_builds_metadata, :p_ci_builds, name: :fk_e20479742e_p)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ReplaceOldFkPCiRunnerMachineBuildsToBuildsV3 < Gitlab::Database::Migration[2.1]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
return if new_foreign_key_exists?
|
||||
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists :p_ci_runner_machine_builds, :ci_builds,
|
||||
name: :fk_bb490f12fe_p, reverse_lock_order: true
|
||||
|
||||
rename_constraint :p_ci_runner_machine_builds, :temp_fk_bb490f12fe_p, :fk_bb490f12fe_p
|
||||
|
||||
Gitlab::Database::PostgresPartitionedTable.each_partition(:p_ci_runner_machine_builds) do |partition|
|
||||
rename_constraint partition.identifier, :temp_fk_bb490f12fe_p, :fk_bb490f12fe_p
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
return unless new_foreign_key_exists?
|
||||
|
||||
add_concurrent_partitioned_foreign_key :p_ci_runner_machine_builds, :ci_builds,
|
||||
name: :temp_fk_bb490f12fe_p,
|
||||
column: [:partition_id, :build_id],
|
||||
target_column: [:partition_id, :id],
|
||||
on_update: :cascade,
|
||||
on_delete: :cascade,
|
||||
validate: true,
|
||||
reverse_lock_order: true
|
||||
|
||||
switch_constraint_names :p_ci_runner_machine_builds, :fk_bb490f12fe_p, :temp_fk_bb490f12fe_p
|
||||
|
||||
Gitlab::Database::PostgresPartitionedTable.each_partition(:p_ci_runner_machine_builds) do |partition|
|
||||
switch_constraint_names partition.identifier, :fk_bb490f12fe_p, :temp_fk_bb490f12fe_p
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_foreign_key_exists?
|
||||
foreign_key_exists?(:p_ci_runner_machine_builds, :p_ci_builds, name: :fk_bb490f12fe_p)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
0a9e2fd92a64fbd4f2dc0d9fc866b3c5bb582c10503b0a85f1164db2e42c7cd0
|
||||
|
|
@ -0,0 +1 @@
|
|||
3ace146e873db96f6dcb556e701339081620aa268f013979c9062b50957d9e13
|
||||
|
|
@ -0,0 +1 @@
|
|||
3523475202ec758dc33748af5c2e85392d1ab55af4a5c7f7b76412b6723d1bf6
|
||||
|
|
@ -0,0 +1 @@
|
|||
e4236ae465987e86f4a68ee552856afe054dccf5b13c5d71abb72cad186266d1
|
||||
|
|
@ -24966,7 +24966,8 @@ CREATE TABLE x509_certificates (
|
|||
email character varying(255) NOT NULL,
|
||||
serial_number bytea NOT NULL,
|
||||
certificate_status smallint DEFAULT 0 NOT NULL,
|
||||
x509_issuer_id bigint NOT NULL
|
||||
x509_issuer_id bigint NOT NULL,
|
||||
emails character varying[] DEFAULT '{}'::character varying[] NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE x509_certificates_id_seq
|
||||
|
|
|
|||
|
|
@ -385,9 +385,29 @@ To list streaming destinations for an instance and see the verification tokens:
|
|||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. On the left sidebar, select **Monitoring > Audit Events**.
|
||||
1. On the main area, select the **Streams**.
|
||||
1. On the main area, select the **Streams** tab.
|
||||
1. View the verification token on the right side of each item.
|
||||
|
||||
### Update event filters
|
||||
|
||||
> Event type filtering in the UI with a defined list of audit event types [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/415013) in GitLab 16.3.
|
||||
|
||||
When this feature is enabled, you can permit users to filter streamed audit events per destination.
|
||||
If the feature is enabled with no filters, the destination receives all audit events.
|
||||
|
||||
A streaming destination that has an event type filter set has a **filtered** (**{filter}**) label.
|
||||
|
||||
To update a streaming destination's event filters:
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. On the left sidebar, select **Monitoring > Audit Events**.
|
||||
1. On the main area, select the **Streams** tab.
|
||||
1. Select the stream to expand.
|
||||
1. Locate the **Filter by audit event type** dropdown list.
|
||||
1. Select the dropdown list and select or clear the required event types.
|
||||
1. Select **Save** to update the event filters.
|
||||
|
||||
### Override default content type header
|
||||
|
||||
By default, streaming destinations use a `content-type` header of `application/x-www-form-urlencoded`. However, you
|
||||
|
|
|
|||
|
|
@ -8751,6 +8751,7 @@ The connection type for [`CustomEmoji`](#customemoji).
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="customemojiconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
|
||||
| <a id="customemojiconnectionedges"></a>`edges` | [`[CustomEmojiEdge]`](#customemojiedge) | A list of edges. |
|
||||
| <a id="customemojiconnectionnodes"></a>`nodes` | [`[CustomEmoji]`](#customemoji) | A list of nodes. |
|
||||
| <a id="customemojiconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
|
@ -14336,10 +14337,22 @@ A custom emoji uploaded by user.
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="customemojicreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the custom emoji was created. |
|
||||
| <a id="customemojiexternal"></a>`external` | [`Boolean!`](#boolean) | Whether the emoji is an external link. |
|
||||
| <a id="customemojiid"></a>`id` | [`CustomEmojiID!`](#customemojiid) | ID of the emoji. |
|
||||
| <a id="customemojiname"></a>`name` | [`String!`](#string) | Name of the emoji. |
|
||||
| <a id="customemojiurl"></a>`url` | [`String!`](#string) | Link to file of the emoji. |
|
||||
| <a id="customemojiuserpermissions"></a>`userPermissions` | [`CustomEmojiPermissions!`](#customemojipermissions) | Permissions for the current user on the resource. |
|
||||
|
||||
### `CustomEmojiPermissions`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="customemojipermissionscreatecustomemoji"></a>`createCustomEmoji` | [`Boolean!`](#boolean) | Indicates the user can perform `create_custom_emoji` on this resource. |
|
||||
| <a id="customemojipermissionsdeletecustomemoji"></a>`deleteCustomEmoji` | [`Boolean!`](#boolean) | Indicates the user can perform `delete_custom_emoji` on this resource. |
|
||||
| <a id="customemojipermissionsreadcustomemoji"></a>`readCustomEmoji` | [`Boolean!`](#boolean) | Indicates the user can perform `read_custom_emoji` on this resource. |
|
||||
|
||||
### `CustomerRelationsContact`
|
||||
|
||||
|
|
@ -17314,6 +17327,7 @@ Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction).
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="grouppermissionscreatecustomemoji"></a>`createCustomEmoji` | [`Boolean!`](#boolean) | Indicates the user can perform `create_custom_emoji` on this resource. |
|
||||
| <a id="grouppermissionscreateprojects"></a>`createProjects` | [`Boolean!`](#boolean) | Indicates the user can perform `create_projects` on this resource. |
|
||||
| <a id="grouppermissionsreadgroup"></a>`readGroup` | [`Boolean!`](#boolean) | Indicates the user can perform `read_group` on this resource. |
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ improving this behavior.
|
|||
Sometimes, when a job is running, things don't go as you would expect, and it
|
||||
would be helpful if one can have a shell to aid debugging. When a job is
|
||||
running, on the right panel you can see a button `debug` that opens the terminal
|
||||
for the current job.
|
||||
for the current job. Only the person who started a job can debug it.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -18,34 +18,29 @@ Running GitLab within an OpenShift cluster is officially supported using the Git
|
|||
[setting up GitLab on OpenShift on the GitLab Operator's documentation](https://docs.gitlab.com/charts/installation/operator.html).
|
||||
Some components (documented on the GitLab Operator doc) are not supported yet.
|
||||
|
||||
## Deploy to and integrate with OpenShift from GitLab
|
||||
|
||||
Deploying custom or COTS applications on top of OpenShift from GitLab is supported using [the GitLab agent](../../user/clusters/agent/index.md).
|
||||
|
||||
## Use OpenShift to run a GitLab Runner Fleet
|
||||
|
||||
The GitLab Operator does not include the GitLab Runner. To install and manage a GitLab Runner fleet in an OpenShift cluster, use the
|
||||
[GitLab Runner Operator](https://gitlab.com/gitlab-org/gl-openshift/gitlab-runner-operator).
|
||||
|
||||
## Unsupported GitLab features
|
||||
### Deploy to and integrate with OpenShift from GitLab
|
||||
|
||||
### Secure
|
||||
Deploying custom or COTS applications on top of OpenShift from GitLab is supported using [the GitLab agent](../../user/clusters/agent/index.md).
|
||||
|
||||
- [License Compliance via the `License-Scanning.gitlab-ci.yml` CI/CD template](../../user/compliance/license_compliance/index.md). [License scanning of CycloneDX files](../../user/compliance/license_scanning_of_cyclonedx_files/index.md) is supported on OpenShift.
|
||||
- [Code Quality scanning](../../ci/testing/code_quality.md)
|
||||
- [Operational Container Scanning](../../user/clusters/agent/vulnerabilities.md) (Note: Pipeline [Container Scanning](../../user/application_security/container_scanning/index.md) is supported)
|
||||
### Unsupported GitLab features
|
||||
|
||||
### Docker-in-Docker
|
||||
#### Docker-in-Docker
|
||||
|
||||
When using OpenShift to run a GitLab Runner Fleet, we do not support some GitLab features given OpenShift's security model.
|
||||
Features requiring Docker-in-Docker might not work.
|
||||
|
||||
For Auto DevOps, the following features are not supported yet:
|
||||
|
||||
- Auto Code Quality
|
||||
- Auto License Compliance
|
||||
- [Auto Code Quality](../../ci/testing/code_quality.md)
|
||||
- [Auto License Compliance](../../user/compliance/license_compliance/index.md) ([License scanning of CycloneDX files](../../user/compliance/license_scanning_of_cyclonedx_files/index.md) is supported on OpenShift)
|
||||
- Auto Browser Performance Testing
|
||||
- Auto Build
|
||||
- [Operational Container Scanning](../../user/clusters/agent/vulnerabilities.md) (Note: Pipeline [Container Scanning](../../user/application_security/container_scanning/index.md) is supported)
|
||||
|
||||
For Auto Build, there's a [possible workaround using `kaniko`](../../ci/docker/using_kaniko.md).
|
||||
You can check the progress of the implementation in this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/332560).
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ If you use Jira Data Center or Jira Server, use the [Jira DVCS connector](dvcs/i
|
|||
|
||||
## Install the GitLab for Jira Cloud app **(FREE SAAS)**
|
||||
|
||||
> **Add namespace** [renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/331432) to **Link groups** in GitLab 16.1.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have [site administrator](https://support.atlassian.com/user-management/docs/give-users-admin-permissions/#Make-someone-a-site-admin) access to the Jira instance.
|
||||
|
|
@ -42,13 +40,14 @@ For an overview, see
|
|||
|
||||
## Configure the GitLab for Jira Cloud app **(FREE SAAS)**
|
||||
|
||||
> **Add namespace** [renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/331432) to **Link groups** in GitLab 16.1.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Maintainer role for the GitLab group.
|
||||
- You must have [site administrator](https://support.atlassian.com/user-management/docs/give-users-admin-permissions/#Make-someone-a-site-admin) access to the Jira instance.
|
||||
|
||||
You can sync data from GitLab to Jira by linking the GitLab for Jira Cloud app to one or more GitLab groups.
|
||||
|
||||
To configure the GitLab for Jira Cloud app:
|
||||
|
||||
1. In Jira, on the top bar, select **Apps > Manage your apps**.
|
||||
|
|
|
|||
|
|
@ -13,3 +13,5 @@ configure the infrastructure for your application.
|
|||
|-------|-------------|--------------------|
|
||||
| [Use GitOps with GitLab](https://about.gitlab.com/blog/2022/04/07/the-ultimate-guide-to-gitops-with-gitlab/) | Learn how to provision and manage a base infrastructure, connect to a Kubernetes cluster, deploy third-party applications, and carry out other infrastructure automation tasks. | |
|
||||
| [Set up Flux for GitOps](../user/clusters/agent/gitops/flux_tutorial.md) | Learn how to set up Flux for GitOps in a sample project. | |
|
||||
| [Structure your repository for GitOps](../user/clusters/agent/gitops/example_repository_structure.md) | Learn how to organize a GitLab project for GitOps. | |
|
||||
| [Deploy an OCI artifact using Flux](../user/clusters/agent/gitops/flux_oci_tutorial.md) | Learn how to package and deploy your Kubernetes manifests as an OCI artifact. | |
|
||||
|
|
|
|||
|
|
@ -230,6 +230,25 @@ Provide feedback on this experimental feature in [issue 407779](https://gitlab.c
|
|||
**Data usage**: When you use this feature, the text of public comments on the issue are sent to the large
|
||||
language model referenced above.
|
||||
|
||||
### Show deployment frequency forecast **(ULTIMATE SAAS)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10228) in GitLab 16.2 as an [Experiment](../policy/experiment-beta-support.md#experiment).
|
||||
|
||||
This feature is an [Experiment](../policy/experiment-beta-support.md) on GitLab.com.
|
||||
|
||||
In CI/CD Analytics, you can view a forecast of deployment frequency:
|
||||
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Analyze > CI/CD analytics**.
|
||||
1. Select the **Deployment frequency** tab.
|
||||
1. Turn on the **Show forecast** toggle.
|
||||
1. On the confirmation dialog, select **Accept testing terms**.
|
||||
|
||||
The forecast is displayed as a dotted line on the chart. Data is forecasted for a duration that is half of the selected date range.
|
||||
For example, if you select a 30-day range, a forecast for the following 15 days is displayed.
|
||||
|
||||
Provide feedback on this experimental feature in [issue 416833](https://gitlab.com/gitlab-org/gitlab/-/issues/416833).
|
||||
|
||||
## Data Usage
|
||||
|
||||
GitLab AI features leverage generative AI to help increase velocity and aim to help make you more productive. Each feature operates independently of other features and is not required for other features to function.
|
||||
|
|
|
|||
|
|
@ -611,6 +611,7 @@ Payload example:
|
|||
}
|
||||
},
|
||||
"work_in_progress": false,
|
||||
"draft": false,
|
||||
"assignee": {
|
||||
"name": "User1",
|
||||
"username": "user1",
|
||||
|
|
@ -898,6 +899,7 @@ Payload example:
|
|||
"state": "opened",
|
||||
"blocking_discussions_resolved": true,
|
||||
"work_in_progress": false,
|
||||
"draft": false,
|
||||
"first_contribution": true,
|
||||
"merge_status": "unchecked",
|
||||
"target_project_id": 14,
|
||||
|
|
@ -983,6 +985,10 @@ Payload example:
|
|||
"previous": null,
|
||||
"current": 1
|
||||
},
|
||||
"draft": {
|
||||
"previous": true,
|
||||
"current": false
|
||||
}
|
||||
"updated_at": {
|
||||
"previous": "2017-09-15 16:50:55 UTC",
|
||||
"current":"2017-09-15 16:52:00 UTC"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# This class is used to update the default_branch_protection_defaults column
|
||||
# for user namespaces of the namespace_settings table.
|
||||
class BackfillDefaultBranchProtectionNamespaceSetting < BatchedMigrationJob
|
||||
operation_name :set_default_branch_protection_defaults
|
||||
feature_category :database
|
||||
|
||||
# Migration only version of `namespaces` table
|
||||
class Namespace < ::ApplicationRecord
|
||||
self.table_name = 'namespaces'
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
has_one :namespace_setting,
|
||||
class_name: '::Gitlab::BackgroundMigration::BackfillDefaultBranchProtectionNamespaceSetting::NamespaceSetting'
|
||||
end
|
||||
|
||||
# Migration only version of `namespace_settings` table
|
||||
class NamespaceSetting < ::ApplicationRecord
|
||||
self.table_name = 'namespace_settings'
|
||||
belongs_to :namespace,
|
||||
class_name: '::Gitlab::BackgroundMigration::BackfillDefaultBranchProtectionNamespaceSetting::Namespace'
|
||||
end
|
||||
|
||||
# Migration only version of Gitlab::Access:BranchProtection application code.
|
||||
class BranchProtection
|
||||
attr_reader :level
|
||||
|
||||
def initialize(level)
|
||||
@level = level
|
||||
end
|
||||
|
||||
PROTECTION_NONE = 0
|
||||
PROTECTION_DEV_CAN_PUSH = 1
|
||||
PROTECTION_FULL = 2
|
||||
PROTECTION_DEV_CAN_MERGE = 3
|
||||
PROTECTION_DEV_CAN_INITIAL_PUSH = 4
|
||||
|
||||
DEVELOPER = 30
|
||||
MAINTAINER = 40
|
||||
|
||||
def to_hash
|
||||
case level
|
||||
when PROTECTION_NONE
|
||||
self.class.protection_none
|
||||
when PROTECTION_DEV_CAN_PUSH
|
||||
self.class.protection_partial
|
||||
when PROTECTION_FULL
|
||||
self.class.protected_fully
|
||||
when PROTECTION_DEV_CAN_MERGE
|
||||
self.class.protected_against_developer_pushes
|
||||
when PROTECTION_DEV_CAN_INITIAL_PUSH
|
||||
self.class.protected_after_initial_push
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def protection_none
|
||||
{
|
||||
allowed_to_push: [{ 'access_level' => DEVELOPER }],
|
||||
allowed_to_merge: [{ 'access_level' => DEVELOPER }],
|
||||
allow_force_push: true
|
||||
}
|
||||
end
|
||||
|
||||
def protection_partial
|
||||
protection_none.merge(allow_force_push: false)
|
||||
end
|
||||
|
||||
def protected_fully
|
||||
{
|
||||
allowed_to_push: [{ 'access_level' => MAINTAINER }],
|
||||
allowed_to_merge: [{ 'access_level' => MAINTAINER }],
|
||||
allow_force_push: false
|
||||
}
|
||||
end
|
||||
|
||||
def protected_against_developer_pushes
|
||||
{
|
||||
allowed_to_push: [{ 'access_level' => MAINTAINER }],
|
||||
allowed_to_merge: [{ 'access_level' => DEVELOPER }],
|
||||
allow_force_push: true
|
||||
}
|
||||
end
|
||||
|
||||
def protected_after_initial_push
|
||||
{
|
||||
allowed_to_push: [{ 'access_level' => MAINTAINER }],
|
||||
allowed_to_merge: [{ 'access_level' => DEVELOPER }],
|
||||
allow_force_push: true,
|
||||
developer_can_initial_push: true
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def perform
|
||||
each_sub_batch do |sub_batch|
|
||||
update_default_protection_branch_defaults(sub_batch)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_default_protection_branch_defaults(batch)
|
||||
namespace_settings = NamespaceSetting.where(namespace_id: batch.pluck(:namespace_id)).includes(:namespace)
|
||||
|
||||
values_list = namespace_settings.map do |namespace_setting|
|
||||
level = namespace_setting.namespace.default_branch_protection.to_i
|
||||
value = BranchProtection.new(level).to_hash.to_json
|
||||
"(#{namespace_setting.namespace_id}, '#{value}'::jsonb)"
|
||||
end.join(", ")
|
||||
|
||||
sql = <<~SQL
|
||||
WITH new_values (namespace_id, default_branch_protection_defaults) AS (
|
||||
VALUES
|
||||
#{values_list}
|
||||
)
|
||||
UPDATE namespace_settings
|
||||
SET default_branch_protection_defaults = new_values.default_branch_protection_defaults
|
||||
FROM new_values
|
||||
WHERE namespace_settings.namespace_id = new_values.namespace_id;
|
||||
SQL
|
||||
|
||||
connection.execute(sql)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -43,7 +43,7 @@ module Gitlab
|
|||
def prohibited_branch_checks
|
||||
return if deletion?
|
||||
|
||||
if branch_name =~ %r{\A\h{40}(/|\z)}
|
||||
if branch_name =~ %r{\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}(/|\z)}o
|
||||
raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,66 +3,30 @@
|
|||
module Gitlab
|
||||
module ContentSecurityPolicy
|
||||
class ConfigLoader
|
||||
DIRECTIVES = %w(base_uri child_src connect_src default_src font_src
|
||||
form_action frame_ancestors frame_src img_src manifest_src
|
||||
media_src object_src report_uri script_src style_src worker_src).freeze
|
||||
|
||||
DIRECTIVES = %w[
|
||||
base_uri child_src connect_src default_src font_src form_action
|
||||
frame_ancestors frame_src img_src manifest_src media_src object_src
|
||||
report_uri script_src style_src worker_src
|
||||
].freeze
|
||||
DEFAULT_FALLBACK_VALUE = '<default_value>'
|
||||
HTTP_PORTS = [80, 443].freeze
|
||||
|
||||
def self.default_enabled
|
||||
Rails.env.development? || Rails.env.test?
|
||||
end
|
||||
|
||||
def self.default_directives
|
||||
directives = {
|
||||
'default_src' => "'self'",
|
||||
'base_uri' => "'self'",
|
||||
'connect_src' => ContentSecurityPolicy::Directives.connect_src,
|
||||
'font_src' => "'self'",
|
||||
'form_action' => "'self' https: http:",
|
||||
'frame_ancestors' => "'self'",
|
||||
'frame_src' => ContentSecurityPolicy::Directives.frame_src,
|
||||
'img_src' => "'self' data: blob: http: https:",
|
||||
'manifest_src' => "'self'",
|
||||
'media_src' => "'self' data: blob: http: https:",
|
||||
'script_src' => ContentSecurityPolicy::Directives.script_src,
|
||||
'style_src' => ContentSecurityPolicy::Directives.style_src,
|
||||
'worker_src' => "#{Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'assets/')} blob: data:",
|
||||
'object_src' => "'none'",
|
||||
'report_uri' => nil
|
||||
}
|
||||
|
||||
# connect_src with 'self' includes https/wss variations of the origin,
|
||||
# however, safari hasn't covered this yet and we need to explicitly add
|
||||
# support for websocket origins until Safari catches up with the specs
|
||||
if Rails.env.development?
|
||||
allow_webpack_dev_server(directives)
|
||||
allow_letter_opener(directives)
|
||||
allow_snowplow_micro(directives) if Gitlab::Tracking.snowplow_micro_enabled?
|
||||
end
|
||||
directives = default_directives_defaults
|
||||
|
||||
allow_development_tooling(directives)
|
||||
allow_websocket_connections(directives)
|
||||
allow_cdn(directives, Settings.gitlab.cdn_host) if Settings.gitlab.cdn_host.present?
|
||||
allow_zuora(directives) if Gitlab.com?
|
||||
# Support for Sentry setup via configuration files will be removed in 16.0
|
||||
# in favor of Gitlab::CurrentSettings.
|
||||
allow_legacy_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
|
||||
allow_sentry(directives) if Gitlab::CurrentSettings.try(:sentry_enabled) && Gitlab::CurrentSettings.try(:sentry_clientside_dsn)
|
||||
allow_cdn(directives)
|
||||
allow_zuora(directives)
|
||||
allow_sentry(directives)
|
||||
allow_framed_gitlab_paths(directives)
|
||||
allow_customersdot(directives) if ENV['CUSTOMER_PORTAL_URL'].present?
|
||||
allow_review_apps(directives) if ENV['REVIEW_APPS_ENABLED']
|
||||
|
||||
# The follow section contains workarounds to patch Safari's lack of support for CSP Level 3
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/343579
|
||||
# frame-src was deprecated in CSP level 2 in favor of child-src
|
||||
# CSP level 3 "undeprecated" frame-src and browsers fall back on child-src if it's missing
|
||||
# However Safari seems to read child-src first so we'll just keep both equal
|
||||
append_to_directive(directives, 'child_src', directives['frame_src'])
|
||||
|
||||
# Safari also doesn't support worker-src and only checks child-src
|
||||
# So for compatibility until it catches up to other browsers we need to
|
||||
# append worker-src's content to child-src
|
||||
append_to_directive(directives, 'child_src', directives['worker_src'])
|
||||
allow_customersdot(directives)
|
||||
allow_review_apps(directives)
|
||||
csp_level_3_backport(directives)
|
||||
|
||||
directives
|
||||
end
|
||||
|
|
@ -87,29 +51,35 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def arguments_for(directive)
|
||||
# In order to disable a directive, the user can explicitly
|
||||
# set a falsy value like nil, false or empty string
|
||||
arguments = @merged_csp_directives[directive]
|
||||
return unless arguments.present? && arguments.is_a?(String)
|
||||
|
||||
arguments.strip.split(' ').map(&:strip)
|
||||
def self.default_directives_defaults
|
||||
{
|
||||
'default_src' => "'self'",
|
||||
'base_uri' => "'self'",
|
||||
'connect_src' => ContentSecurityPolicy::Directives.connect_src,
|
||||
'font_src' => "'self'",
|
||||
'form_action' => "'self' https: http:",
|
||||
'frame_ancestors' => "'self'",
|
||||
'frame_src' => ContentSecurityPolicy::Directives.frame_src,
|
||||
'img_src' => "'self' data: blob: http: https:",
|
||||
'manifest_src' => "'self'",
|
||||
'media_src' => "'self' data: blob: http: https:",
|
||||
'script_src' => ContentSecurityPolicy::Directives.script_src,
|
||||
'style_src' => ContentSecurityPolicy::Directives.style_src,
|
||||
'worker_src' => "#{Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'assets/')} blob: data:",
|
||||
'object_src' => "'none'",
|
||||
'report_uri' => nil
|
||||
}
|
||||
end
|
||||
|
||||
def self.allow_websocket_connections(directives)
|
||||
http_ports = [80, 443]
|
||||
host = Gitlab.config.gitlab.host
|
||||
port = Gitlab.config.gitlab.port
|
||||
secure = Gitlab.config.gitlab.https
|
||||
protocol = secure ? 'wss' : 'ws'
|
||||
# connect_src with 'self' includes https/wss variations of the origin,
|
||||
# however, safari hasn't covered this yet and we need to explicitly add
|
||||
# support for websocket origins until Safari catches up with the specs
|
||||
def self.allow_development_tooling(directives)
|
||||
return unless Rails.env.development?
|
||||
|
||||
ws_url = "#{protocol}://#{host}"
|
||||
|
||||
unless http_ports.include?(port)
|
||||
ws_url = "#{ws_url}:#{port}"
|
||||
end
|
||||
|
||||
append_to_directive(directives, 'connect_src', ws_url)
|
||||
allow_webpack_dev_server(directives)
|
||||
allow_letter_opener(directives)
|
||||
allow_snowplow_micro(directives) if Gitlab::Tracking.snowplow_micro_enabled?
|
||||
end
|
||||
|
||||
def self.allow_webpack_dev_server(directives)
|
||||
|
|
@ -121,7 +91,32 @@ module Gitlab
|
|||
append_to_directive(directives, 'connect_src', "#{http_url} #{ws_url}")
|
||||
end
|
||||
|
||||
def self.allow_cdn(directives, cdn_host)
|
||||
def self.allow_letter_opener(directives)
|
||||
url = Gitlab::Utils.append_path(Gitlab.config.gitlab.url, '/rails/letter_opener/')
|
||||
append_to_directive(directives, 'frame_src', url)
|
||||
end
|
||||
|
||||
def self.allow_snowplow_micro(directives)
|
||||
url = URI.join(Gitlab::Tracking::Destinations::SnowplowMicro.new.uri, '/').to_s
|
||||
append_to_directive(directives, 'connect_src', url)
|
||||
end
|
||||
|
||||
def self.allow_websocket_connections(directives)
|
||||
host = Gitlab.config.gitlab.host
|
||||
port = Gitlab.config.gitlab.port
|
||||
secure = Gitlab.config.gitlab.https
|
||||
protocol = secure ? 'wss' : 'ws'
|
||||
|
||||
ws_url = "#{protocol}://#{host}"
|
||||
ws_url = "#{ws_url}:#{port}" unless HTTP_PORTS.include?(port)
|
||||
|
||||
append_to_directive(directives, 'connect_src', ws_url)
|
||||
end
|
||||
|
||||
def self.allow_cdn(directives)
|
||||
cdn_host = Settings.gitlab.cdn_host.presence
|
||||
return unless cdn_host
|
||||
|
||||
append_to_directive(directives, 'script_src', cdn_host)
|
||||
append_to_directive(directives, 'style_src', cdn_host)
|
||||
append_to_directive(directives, 'font_src', cdn_host)
|
||||
|
|
@ -129,47 +124,35 @@ module Gitlab
|
|||
append_to_directive(directives, 'frame_src', cdn_host)
|
||||
end
|
||||
|
||||
def self.zuora_host
|
||||
"https://*.zuora.com/apps/PublicHostedPageLite.do"
|
||||
end
|
||||
|
||||
def self.allow_zuora(directives)
|
||||
return unless Gitlab.com?
|
||||
|
||||
append_to_directive(directives, 'frame_src', zuora_host)
|
||||
end
|
||||
|
||||
def self.append_to_directive(directives, directive, text)
|
||||
directives[directive] = "#{directives[directive]} #{text}".strip
|
||||
end
|
||||
def self.allow_sentry(directives)
|
||||
allow_legacy_sentry(directives) if legacy_sentry_configured?
|
||||
return unless sentry_client_side_dsn_enabled?
|
||||
|
||||
def self.allow_customersdot(directives)
|
||||
customersdot_host = ENV['CUSTOMER_PORTAL_URL']
|
||||
sentry_uri = URI(Gitlab::CurrentSettings.sentry_clientside_dsn)
|
||||
|
||||
append_to_directive(directives, 'frame_src', customersdot_host)
|
||||
append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
|
||||
end
|
||||
|
||||
def self.allow_legacy_sentry(directives)
|
||||
# Support for Sentry setup via configuration files will be removed in 16.0
|
||||
# in favor of Gitlab::CurrentSettings.
|
||||
sentry_dsn = Gitlab.config.sentry.clientside_dsn
|
||||
sentry_uri = URI(sentry_dsn)
|
||||
sentry_uri = URI(Gitlab.config.sentry.clientside_dsn)
|
||||
|
||||
append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
|
||||
end
|
||||
|
||||
def self.allow_sentry(directives)
|
||||
sentry_dsn = Gitlab::CurrentSettings.sentry_clientside_dsn
|
||||
sentry_uri = URI(sentry_dsn)
|
||||
|
||||
append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
|
||||
def self.legacy_sentry_configured?
|
||||
Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
|
||||
end
|
||||
|
||||
def self.allow_letter_opener(directives)
|
||||
append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, '/rails/letter_opener/'))
|
||||
end
|
||||
|
||||
def self.allow_snowplow_micro(directives)
|
||||
url = URI.join(Gitlab::Tracking::Destinations::SnowplowMicro.new.uri, '/').to_s
|
||||
append_to_directive(directives, 'connect_src', url)
|
||||
def self.sentry_client_side_dsn_enabled?
|
||||
Gitlab::CurrentSettings.try(:sentry_enabled) && Gitlab::CurrentSettings.try(:sentry_clientside_dsn)
|
||||
end
|
||||
|
||||
# Using 'self' in the CSP introduces several CSP bypass opportunities
|
||||
|
|
@ -180,10 +163,50 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def self.allow_customersdot(directives)
|
||||
customersdot_host = ENV['CUSTOMER_PORTAL_URL'].presence
|
||||
return unless customersdot_host
|
||||
|
||||
append_to_directive(directives, 'frame_src', customersdot_host)
|
||||
end
|
||||
|
||||
def self.allow_review_apps(directives)
|
||||
return unless ENV['REVIEW_APPS_ENABLED'].presence
|
||||
|
||||
# Allow-listed to allow POSTs to https://gitlab.com/api/v4/projects/278964/merge_requests/:merge_request_iid/visual_review_discussions
|
||||
append_to_directive(directives, 'connect_src', 'https://gitlab.com/api/v4/projects/278964/merge_requests/')
|
||||
end
|
||||
|
||||
# The follow contains workarounds to patch Safari's lack of support for CSP Level 3
|
||||
def self.csp_level_3_backport(directives)
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/343579
|
||||
# frame-src was deprecated in CSP level 2 in favor of child-src
|
||||
# CSP level 3 "undeprecated" frame-src and browsers fall back on child-src if it's missing
|
||||
# However Safari seems to read child-src first so we'll just keep both equal
|
||||
append_to_directive(directives, 'child_src', directives['frame_src'])
|
||||
|
||||
# Safari also doesn't support worker-src and only checks child-src
|
||||
# So for compatibility until it catches up to other browsers we need to
|
||||
# append worker-src's content to child-src
|
||||
append_to_directive(directives, 'child_src', directives['worker_src'])
|
||||
end
|
||||
|
||||
def self.append_to_directive(directives, directive, text)
|
||||
directives[directive] = "#{directives[directive]} #{text}".strip
|
||||
end
|
||||
|
||||
def self.zuora_host
|
||||
"https://*.zuora.com/apps/PublicHostedPageLite.do"
|
||||
end
|
||||
|
||||
def arguments_for(directive)
|
||||
# In order to disable a directive, the user can explicitly
|
||||
# set a falsy value like nil, false or empty string
|
||||
arguments = @merged_csp_directives[directive]
|
||||
return unless arguments.is_a?(String)
|
||||
|
||||
arguments.split(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ module Gitlab
|
|||
'continuous_integration'
|
||||
],
|
||||
[
|
||||
%r(\Apipelines/sha/\w{7,40}\z),
|
||||
%r(\Apipelines/sha/\w{#{Gitlab::Git::Commit::MIN_SHA_LENGTH},#{Gitlab::Git::Commit::MAX_SHA_LENGTH}}\z)o,
|
||||
'ci_editor',
|
||||
'pipeline_composition'
|
||||
],
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module Gitlab
|
|||
# https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
|
||||
EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
|
||||
BLANK_SHA = ('0' * 40).freeze
|
||||
COMMIT_ID = /\A[0-9a-f]{40}\z/.freeze
|
||||
COMMIT_ID = /\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}\z/.freeze
|
||||
TAG_REF_PREFIX = "refs/tags/"
|
||||
BRANCH_REF_PREFIX = "refs/heads/"
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,20 @@ module Gitlab
|
|||
attr_accessor :raw_commit, :head
|
||||
|
||||
MAX_COMMIT_MESSAGE_DISPLAY_SIZE = 10.megabytes
|
||||
|
||||
SHA1_LENGTH = 40
|
||||
SHA256_LENGTH = 64
|
||||
|
||||
MIN_SHA_LENGTH = 7
|
||||
MAX_SHA_LENGTH = SHA256_LENGTH
|
||||
|
||||
RAW_SHA_PATTERN = "\\h{#{MIN_SHA_LENGTH},#{MAX_SHA_LENGTH}}".freeze
|
||||
SHA_PATTERN = /#{RAW_SHA_PATTERN}/
|
||||
# Match a full SHA. Note that because this expression is not anchored it will match any SHA that is at
|
||||
# least SHA1_LENGTH long.
|
||||
RAW_FULL_SHA_PATTERN = "\\h{#{SHA1_LENGTH}}(?:\\h{#{SHA256_LENGTH - SHA1_LENGTH}})?".freeze
|
||||
FULL_SHA_PATTERN = /#{RAW_FULL_SHA_PATTERN}/
|
||||
|
||||
SERIALIZE_KEYS = [
|
||||
:id, :message, :parent_ids,
|
||||
:authored_date, :author_name, :author_email,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ module Gitlab
|
|||
blocking_discussions_resolved
|
||||
created_at
|
||||
description
|
||||
draft
|
||||
head_pipeline_id
|
||||
id
|
||||
iid
|
||||
|
|
@ -55,6 +56,7 @@ module Gitlab
|
|||
target: merge_request.target_project.hook_attrs,
|
||||
last_commit: merge_request.diff_head_commit&.hook_attrs,
|
||||
work_in_progress: merge_request.draft?,
|
||||
draft: merge_request.draft?,
|
||||
total_time_spent: merge_request.total_time_spent,
|
||||
time_change: merge_request.time_change,
|
||||
human_total_time_spent: merge_request.human_total_time_spent,
|
||||
|
|
|
|||
|
|
@ -42,11 +42,13 @@ module Gitlab
|
|||
!verified_signature ||
|
||||
signed_by_user.nil?
|
||||
|
||||
if signed_by_user.verified_emails.include?(@email.downcase) && certificate_email.casecmp?(@email)
|
||||
:verified
|
||||
else
|
||||
:unverified
|
||||
if signed_by_user.verified_emails.include?(@email.downcase)
|
||||
return :verified if certificate_emails.find do |ce|
|
||||
ce.casecmp?(@email)
|
||||
end
|
||||
end
|
||||
|
||||
:unverified
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -173,18 +175,13 @@ module Gitlab
|
|||
end
|
||||
|
||||
def certificate_email
|
||||
email = nil
|
||||
certificate_emails.first
|
||||
end
|
||||
|
||||
get_certificate_extension('subjectAltName').split(',').each do |item|
|
||||
if item.strip.start_with?("email")
|
||||
email = item.split('email:')[1]
|
||||
break
|
||||
end
|
||||
def certificate_emails
|
||||
get_certificate_extension('subjectAltName').split(',').each.with_object([]) do |item, emails|
|
||||
emails << item.split('email:')[1] if item.strip.start_with?("email")
|
||||
end
|
||||
|
||||
return if email.nil?
|
||||
|
||||
email
|
||||
end
|
||||
|
||||
def x509_issuer
|
||||
|
|
@ -206,6 +203,7 @@ module Gitlab
|
|||
subject_key_identifier: certificate_subject_key_identifier,
|
||||
subject: certificate_subject,
|
||||
email: certificate_email,
|
||||
emails: certificate_emails,
|
||||
serial_number: cert.serial.to_i,
|
||||
x509_issuer_id: x509_issuer.id
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2638,15 +2638,21 @@ msgstr ""
|
|||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
msgid "Active %{accessTokenTypePlural} (%{totalAccessTokens})"
|
||||
msgstr ""
|
||||
|
||||
msgid "Active Sessions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Active chat names (%{count})"
|
||||
msgstr ""
|
||||
|
||||
msgid "Active group access tokens"
|
||||
msgstr ""
|
||||
|
||||
msgid "Active personal access tokens"
|
||||
msgstr ""
|
||||
|
||||
msgid "Active project access tokens"
|
||||
msgstr ""
|
||||
|
||||
msgid "Activity"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2866,6 +2872,9 @@ msgstr ""
|
|||
msgid "Add new key"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add new token"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add new webhook"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -23585,6 +23594,9 @@ msgstr ""
|
|||
msgid "Impersonation has been disabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Impersonation tokens"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37793,6 +37805,9 @@ msgstr ""
|
|||
msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProtectedEnvironment|Protected Environments"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProtectedEnvironment|Required approvals"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37808,12 +37823,15 @@ msgstr ""
|
|||
msgid "ProtectedEnvironment|Select users"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProtectedEnvironment|There are currently no protected environments. Protect an environment with this form."
|
||||
msgid "ProtectedEnvironment|There are currently no protected environments."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProtectedEnvironment|Unprotect"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProtectedEnvironment|Unprotect environment"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProtectedEnvironment|Users with at least the Developer role can write to unprotected environments. Are you sure you want to unprotect %{environment_name}?"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ RSpec.describe 'Admin > Users > Impersonation Tokens', :js, feature_category: :s
|
|||
name = 'Hello World'
|
||||
|
||||
visit admin_user_impersonation_tokens_path(user_id: user.username)
|
||||
click_button 'Add new token'
|
||||
fill_in "Token name", with: name
|
||||
|
||||
# Set date to 1st of next month
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ RSpec.describe 'Profile > Personal Access Tokens', :js, feature_category: :user_
|
|||
name = 'My PAT'
|
||||
|
||||
visit profile_personal_access_tokens_path
|
||||
|
||||
click_button 'Add new token'
|
||||
fill_in "Token name", with: name
|
||||
|
||||
# Set date to 1st of next month
|
||||
|
|
@ -43,6 +45,8 @@ RSpec.describe 'Profile > Personal Access Tokens', :js, feature_category: :user_
|
|||
it "displays an error message" do
|
||||
number_tokens_before = PersonalAccessToken.count
|
||||
visit profile_personal_access_tokens_path
|
||||
|
||||
click_button 'Add new token'
|
||||
fill_in "Token name", with: 'My PAT'
|
||||
|
||||
click_on "Create personal access token"
|
||||
|
|
@ -145,6 +149,7 @@ RSpec.describe 'Profile > Personal Access Tokens', :js, feature_category: :user_
|
|||
|
||||
visit profile_personal_access_tokens_path({ name: name, scopes: scopes })
|
||||
|
||||
click_button 'Add new token'
|
||||
expect(page).to have_field("Token name", with: name)
|
||||
expect(find("#personal_access_token_scopes_api")).to be_checked
|
||||
expect(find("#personal_access_token_scopes_read_user")).to be_checked
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ RSpec.describe 'Project > Settings > Access Tokens', :js, feature_category: :use
|
|||
it 'shows Owner option' do
|
||||
visit resource_settings_access_tokens_path
|
||||
|
||||
click_button 'Add new token'
|
||||
expect(role_dropdown_options).to include('Owner')
|
||||
end
|
||||
end
|
||||
|
|
@ -63,6 +64,7 @@ RSpec.describe 'Project > Settings > Access Tokens', :js, feature_category: :use
|
|||
it 'does not show Owner option for a maintainer' do
|
||||
visit resource_settings_access_tokens_path
|
||||
|
||||
click_button 'Add new token'
|
||||
expect(role_dropdown_options).not_to include('Owner')
|
||||
end
|
||||
end
|
||||
|
|
@ -81,6 +83,7 @@ RSpec.describe 'Project > Settings > Access Tokens', :js, feature_category: :use
|
|||
it 'shows access token creation form and text' do
|
||||
visit project_settings_access_tokens_path(personal_project)
|
||||
|
||||
click_button 'Add new token'
|
||||
expect(page).to have_selector('#js-new-access-token-form')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ RSpec.describe 'User searches project settings', :js, feature_category: :groups_
|
|||
visit project_settings_access_tokens_path(project)
|
||||
end
|
||||
|
||||
it_behaves_like 'can highlight results', 'Expiration date'
|
||||
it_behaves_like 'can highlight results', 'Token name'
|
||||
end
|
||||
|
||||
context 'in Repository page' do
|
||||
|
|
|
|||
|
|
@ -91,24 +91,6 @@ describe('~/access_tokens/components/access_token_table_app', () => {
|
|||
expect(cells.at(0).text()).toBe(noTokensMessage);
|
||||
});
|
||||
|
||||
it('should show a title indicating the amount of tokens', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find('h5').text()).toBe(
|
||||
sprintf(__('Active %{accessTokenTypePlural} (%{totalAccessTokens})'), {
|
||||
accessTokenTypePlural,
|
||||
totalAccessTokens: defaultActiveAccessTokens.length,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should render information section', () => {
|
||||
const info = 'This is my information';
|
||||
createComponent({ information: info });
|
||||
|
||||
expect(wrapper.findByTestId('information-section').text()).toBe(info);
|
||||
});
|
||||
|
||||
describe('table headers', () => {
|
||||
it('should include `Action` column', () => {
|
||||
createComponent();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ describe('~/access_tokens/components/new_access_token_app', () => {
|
|||
};
|
||||
|
||||
const findButtonEl = () => document.querySelector('[type=submit]');
|
||||
const findGlAlertError = () => wrapper.findByTestId('error-message');
|
||||
const findGlAlertSuccess = () => wrapper.findByTestId('success-message');
|
||||
|
||||
const triggerSuccess = async (newToken = 'new token') => {
|
||||
wrapper
|
||||
|
|
@ -57,7 +59,7 @@ describe('~/access_tokens/components/new_access_token_app', () => {
|
|||
|
||||
it('should render nothing', () => {
|
||||
expect(wrapper.findComponent(InputCopyToggleVisibility).exists()).toBe(false);
|
||||
expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
|
||||
expect(findGlAlertError().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('on success', () => {
|
||||
|
|
@ -65,7 +67,8 @@ describe('~/access_tokens/components/new_access_token_app', () => {
|
|||
const newToken = '12345';
|
||||
await triggerSuccess(newToken);
|
||||
|
||||
expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
|
||||
expect(findGlAlertError().exists()).toBe(false);
|
||||
expect(findGlAlertSuccess().exists()).toBe(true);
|
||||
|
||||
const InputCopyToggleVisibilityComponent = wrapper.findComponent(InputCopyToggleVisibility);
|
||||
expect(InputCopyToggleVisibilityComponent.props('value')).toBe(newToken);
|
||||
|
|
@ -82,7 +85,7 @@ describe('~/access_tokens/components/new_access_token_app', () => {
|
|||
const newToken = '12345';
|
||||
await triggerSuccess(newToken);
|
||||
|
||||
expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
|
||||
expect(findGlAlertError().exists()).toBe(false);
|
||||
|
||||
const inputAttributes = wrapper
|
||||
.findByLabelText(sprintf(__('Your new %{accessTokenType}'), { accessTokenType }))
|
||||
|
|
@ -135,7 +138,7 @@ describe('~/access_tokens/components/new_access_token_app', () => {
|
|||
|
||||
expect(wrapper.findComponent(InputCopyToggleVisibility).exists()).toBe(false);
|
||||
|
||||
let GlAlertComponent = wrapper.findComponent(GlAlert);
|
||||
let GlAlertComponent = findGlAlertError();
|
||||
expect(GlAlertComponent.props('title')).toBe(__('The form contains the following errors:'));
|
||||
expect(GlAlertComponent.props('variant')).toBe('danger');
|
||||
let itemEls = wrapper.findAll('li');
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ describe('access tokens', () => {
|
|||
initialActiveAccessTokens,
|
||||
|
||||
// Default values
|
||||
information: undefined,
|
||||
noActiveTokensMessage: sprintf(__('This user has no active %{accessTokenTypePlural}.'), {
|
||||
accessTokenTypePlural,
|
||||
}),
|
||||
|
|
@ -59,14 +58,12 @@ describe('access tokens', () => {
|
|||
});
|
||||
|
||||
it('mounts the component and provides all values', () => {
|
||||
const information = 'Additional information';
|
||||
const noActiveTokensMessage = 'This group has no active access tokens.';
|
||||
setHTMLFixture(
|
||||
`<div id="js-access-token-table-app"
|
||||
data-access-token-type="${accessTokenType}"
|
||||
data-access-token-type-plural="${accessTokenTypePlural}"
|
||||
data-initial-active-access-tokens=${JSON.stringify(initialActiveAccessTokens)}
|
||||
data-information="${information}"
|
||||
data-no-active-tokens-message="${noActiveTokensMessage}"
|
||||
data-show-role
|
||||
>
|
||||
|
|
@ -82,7 +79,6 @@ describe('access tokens', () => {
|
|||
accessTokenType,
|
||||
accessTokenTypePlural,
|
||||
initialActiveAccessTokens,
|
||||
information,
|
||||
noActiveTokensMessage,
|
||||
showRole: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render
|
|||
<status-icon-stub level=\\"2\\" name=\\"MyWidget\\" iconname=\\"success\\"></status-icon-stub>
|
||||
<div class=\\"gl-w-full gl-display-flex\\">
|
||||
<div class=\\"gl-display-flex gl-flex-grow-1\\">
|
||||
<div class=\\"gl-display-flex gl-flex-grow-1 gl-flex-direction-column\\">
|
||||
<p class=\\"gl-mb-0 gl-mr-1\\">Main text for the row</p>
|
||||
<gl-link-stub href=\\"https://gitlab.com\\">Optional link to display after text</gl-link-stub>
|
||||
<!---->
|
||||
<div class=\\"gl-display-flex gl-flex-grow-1 gl-align-items-baseline\\">
|
||||
<div>
|
||||
<p class=\\"gl-mb-0 gl-mr-1\\">Main text for the row</p>
|
||||
<gl-link-stub href=\\"https://gitlab.com\\">Optional link to display after text</gl-link-stub>
|
||||
<!---->
|
||||
</div>
|
||||
<gl-badge-stub size=\\"md\\" variant=\\"info\\" iconsize=\\"md\\">
|
||||
Badge is optional. Text to be displayed inside badge
|
||||
</gl-badge-stub>
|
||||
|
|
@ -44,10 +46,12 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render
|
|||
<!---->
|
||||
<div class=\\"gl-w-full gl-display-flex\\">
|
||||
<div class=\\"gl-display-flex gl-flex-grow-1\\">
|
||||
<div class=\\"gl-display-flex gl-flex-grow-1 gl-flex-direction-column\\">
|
||||
<p class=\\"gl-mb-0 gl-mr-1\\">This is recursive. It will be listed in level 3.</p>
|
||||
<!---->
|
||||
<!---->
|
||||
<div class=\\"gl-display-flex gl-flex-grow-1 gl-align-items-baseline\\">
|
||||
<div>
|
||||
<p class=\\"gl-mb-0 gl-mr-1\\">This is recursive. It will be listed in level 3.</p>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
<actions-stub widget=\\"MyWidget\\" tertiarybuttons=\\"\\" class=\\"gl-ml-auto gl-pl-3\\"></actions-stub>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,20 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['CustomEmoji'] do
|
||||
expected_fields = %w[
|
||||
id
|
||||
name
|
||||
url
|
||||
external
|
||||
created_at
|
||||
user_permissions
|
||||
]
|
||||
|
||||
specify { expect(described_class.graphql_name).to eq('CustomEmoji') }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:read_custom_emoji) }
|
||||
|
||||
specify { expect(described_class).to have_graphql_fields(:id, :name, :url, :external) }
|
||||
specify { expect(described_class).to have_graphql_fields(*expected_fields) }
|
||||
|
||||
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::CustomEmoji) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ RSpec.describe API::Validations::Validators::GitSha do
|
|||
let(:sha) { RepoHelpers.sample_commit.id }
|
||||
let(:short_sha) { sha[0, Gitlab::Git::Commit::MIN_SHA_LENGTH] }
|
||||
let(:too_short_sha) { sha[0, Gitlab::Git::Commit::MIN_SHA_LENGTH - 1] }
|
||||
let(:too_long_sha) { "a" * (Gitlab::Git::Commit::MAX_SHA_LENGTH + 1) }
|
||||
|
||||
subject do
|
||||
described_class.new(['test'], {}, false, scope.new)
|
||||
|
|
@ -29,7 +30,7 @@ RSpec.describe API::Validations::Validators::GitSha do
|
|||
|
||||
context 'invalid sha' do
|
||||
it 'raises a validation error' do
|
||||
expect_validation_error('test' => "#{sha}2") # Sha length > 40
|
||||
expect_validation_error('test' => too_long_sha) # too long SHA
|
||||
expect_validation_error('test' => 'somestring')
|
||||
expect_validation_error('test' => too_short_sha) # sha length < MIN_SHA_LENGTH (7)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillDefaultBranchProtectionNamespaceSetting,
|
||||
schema: 20230724071541,
|
||||
feature_category: :database do
|
||||
let(:namespaces_table) { table(:namespaces) }
|
||||
let(:namespace_settings_table) { table(:namespace_settings) }
|
||||
|
||||
subject(:perform_migration) do
|
||||
described_class.new(
|
||||
start_id: 1,
|
||||
end_id: 30,
|
||||
batch_table: :namespace_settings,
|
||||
batch_column: :namespace_id,
|
||||
sub_batch_size: 2,
|
||||
pause_ms: 0,
|
||||
connection: ActiveRecord::Base.connection
|
||||
).perform
|
||||
end
|
||||
|
||||
before do
|
||||
namespaces_table.create!(id: 1, name: 'group_namespace', path: 'path-1', type: 'Group',
|
||||
default_branch_protection: 0)
|
||||
namespaces_table.create!(id: 2, name: 'user_namespace', path: 'path-2', type: 'User', default_branch_protection: 1)
|
||||
namespaces_table.create!(id: 3, name: 'user_three_namespace', path: 'path-3', type: 'User',
|
||||
default_branch_protection: 2)
|
||||
namespaces_table.create!(id: 4, name: 'group_four_namespace', path: 'path-4', type: 'Group',
|
||||
default_branch_protection: 3)
|
||||
namespaces_table.create!(id: 5, name: 'group_five_namespace', path: 'path-5', type: 'Group',
|
||||
default_branch_protection: 4)
|
||||
|
||||
namespace_settings_table.create!(namespace_id: 1, default_branch_protection_defaults: {})
|
||||
namespace_settings_table.create!(namespace_id: 2, default_branch_protection_defaults: {})
|
||||
namespace_settings_table.create!(namespace_id: 3, default_branch_protection_defaults: {})
|
||||
namespace_settings_table.create!(namespace_id: 4, default_branch_protection_defaults: {})
|
||||
namespace_settings_table.create!(namespace_id: 5, default_branch_protection_defaults: {})
|
||||
end
|
||||
|
||||
it 'updates default_branch_protection_defaults to a correct value', :aggregate_failures do
|
||||
expect(ActiveRecord::QueryRecorder.new { perform_migration }.count).to eq(16)
|
||||
|
||||
expect(migrated_attribute(1)).to eq({ "allow_force_push" => true,
|
||||
"allowed_to_merge" => [{ "access_level" => 30 }],
|
||||
"allowed_to_push" => [{ "access_level" => 30 }] })
|
||||
expect(migrated_attribute(2)).to eq({ "allow_force_push" => false,
|
||||
"allowed_to_merge" => [{ "access_level" => 30 }],
|
||||
"allowed_to_push" => [{ "access_level" => 30 }] })
|
||||
expect(migrated_attribute(3)).to eq({ "allow_force_push" => false,
|
||||
"allowed_to_merge" => [{ "access_level" => 40 }],
|
||||
"allowed_to_push" => [{ "access_level" => 40 }] })
|
||||
expect(migrated_attribute(4)).to eq({ "allow_force_push" => true,
|
||||
"allowed_to_merge" => [{ "access_level" => 30 }],
|
||||
"allowed_to_push" => [{ "access_level" => 40 }] })
|
||||
expect(migrated_attribute(5)).to eq({ "allow_force_push" => true,
|
||||
"allowed_to_merge" => [{ "access_level" => 30 }],
|
||||
"allowed_to_push" => [{ "access_level" => 40 }],
|
||||
"developer_can_initial_push" => true })
|
||||
end
|
||||
|
||||
def migrated_attribute(namespace_id)
|
||||
namespace_settings_table.find(namespace_id).default_branch_protection_defaults
|
||||
end
|
||||
end
|
||||
|
|
@ -32,6 +32,18 @@ RSpec.describe Gitlab::Checks::BranchCheck, feature_category: :source_code_manag
|
|||
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
||||
end
|
||||
|
||||
it "prohibits 64-character hexadecimal branch names" do
|
||||
allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175")
|
||||
|
||||
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
||||
end
|
||||
|
||||
it "prohibits 64-character hexadecimal branch names as the start of a path" do
|
||||
allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175/test")
|
||||
|
||||
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
||||
end
|
||||
|
||||
it "doesn't prohibit a nested hexadecimal in a branch name" do
|
||||
allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-fix")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
||||
RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader, feature_category: :shared do
|
||||
let(:policy) { ActionDispatch::ContentSecurityPolicy.new }
|
||||
let(:csp_config) do
|
||||
{
|
||||
|
|
@ -29,7 +29,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
|
||||
context 'when in production' do
|
||||
before do
|
||||
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
|
||||
stub_rails_env('production')
|
||||
end
|
||||
|
||||
it 'is disabled' do
|
||||
|
|
@ -40,6 +40,16 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
|
||||
describe '.default_directives' do
|
||||
let(:directives) { described_class.default_directives }
|
||||
let(:child_src) { directives['child_src'] }
|
||||
let(:connect_src) { directives['connect_src'] }
|
||||
let(:font_src) { directives['font_src'] }
|
||||
let(:frame_src) { directives['frame_src'] }
|
||||
let(:img_src) { directives['img_src'] }
|
||||
let(:media_src) { directives['media_src'] }
|
||||
let(:report_uri) { directives['report_uri'] }
|
||||
let(:script_src) { directives['script_src'] }
|
||||
let(:style_src) { directives['style_src'] }
|
||||
let(:worker_src) { directives['worker_src'] }
|
||||
|
||||
it 'returns default directives' do
|
||||
directive_names = (described_class::DIRECTIVES - ['report_uri'])
|
||||
|
|
@ -49,68 +59,167 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
end
|
||||
|
||||
expect(directives.has_key?('report_uri')).to be_truthy
|
||||
expect(directives['report_uri']).to be_nil
|
||||
expect(directives['child_src']).to eq("#{directives['frame_src']} #{directives['worker_src']}")
|
||||
expect(report_uri).to be_nil
|
||||
expect(child_src).to eq("#{frame_src} #{worker_src}")
|
||||
end
|
||||
|
||||
describe 'the images-src directive' do
|
||||
it 'can be loaded from anywhere' do
|
||||
expect(directives['img_src']).to include('http: https:')
|
||||
expect(img_src).to include('http: https:')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'the media-src directive' do
|
||||
it 'can be loaded from anywhere' do
|
||||
expect(directives['media_src']).to include('http: https:')
|
||||
expect(media_src).to include('http: https:')
|
||||
end
|
||||
end
|
||||
|
||||
context 'adds all websocket origins to support Safari' do
|
||||
describe 'Webpack dev server websocket connections' do
|
||||
let(:webpack_dev_server_host) { 'webpack-dev-server.com' }
|
||||
let(:webpack_dev_server_port) { '9999' }
|
||||
let(:webpack_dev_server_https) { true }
|
||||
|
||||
before do
|
||||
stub_config_setting(
|
||||
webpack: { dev_server: {
|
||||
host: webpack_dev_server_host,
|
||||
webpack_dev_server_port: webpack_dev_server_port,
|
||||
https: webpack_dev_server_https
|
||||
} }
|
||||
)
|
||||
end
|
||||
|
||||
context 'when in production' do
|
||||
before do
|
||||
stub_rails_env('production')
|
||||
end
|
||||
|
||||
context 'with secure domain' do
|
||||
it 'does not include webpack dev server in connect-src' do
|
||||
expect(connect_src).not_to include(webpack_dev_server_host)
|
||||
expect(connect_src).not_to include(webpack_dev_server_port)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with insecure domain' do
|
||||
let(:webpack_dev_server_https) { false }
|
||||
|
||||
it 'does not include webpack dev server in connect-src' do
|
||||
expect(connect_src).not_to include(webpack_dev_server_host)
|
||||
expect(connect_src).not_to include(webpack_dev_server_port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when in development' do
|
||||
before do
|
||||
stub_rails_env('development')
|
||||
end
|
||||
|
||||
context 'with secure domain' do
|
||||
before do
|
||||
stub_config_setting(host: webpack_dev_server_host, port: webpack_dev_server_port, https: true)
|
||||
end
|
||||
|
||||
it 'includes secure websocket url for webpack dev server in connect-src' do
|
||||
expect(connect_src).to include("wss://#{webpack_dev_server_host}:#{webpack_dev_server_port}")
|
||||
expect(connect_src).not_to include("ws://#{webpack_dev_server_host}:#{webpack_dev_server_port}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with insecure domain' do
|
||||
before do
|
||||
stub_config_setting(host: webpack_dev_server_host, port: webpack_dev_server_port, https: false)
|
||||
end
|
||||
|
||||
it 'includes insecure websocket url for webpack dev server in connect-src' do
|
||||
expect(connect_src).not_to include("wss://#{webpack_dev_server_host}:#{webpack_dev_server_port}")
|
||||
expect(connect_src).to include("ws://#{webpack_dev_server_host}:#{webpack_dev_server_port}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Websocket connections' do
|
||||
it 'with insecure domain' do
|
||||
stub_config_setting(host: 'example.com', https: false)
|
||||
expect(directives['connect_src']).to eq("'self' ws://example.com")
|
||||
expect(connect_src).to eq("'self' ws://example.com")
|
||||
end
|
||||
|
||||
it 'with secure domain' do
|
||||
stub_config_setting(host: 'example.com', https: true)
|
||||
expect(directives['connect_src']).to eq("'self' wss://example.com")
|
||||
expect(connect_src).to eq("'self' wss://example.com")
|
||||
end
|
||||
|
||||
it 'with custom port' do
|
||||
stub_config_setting(host: 'example.com', port: '1234')
|
||||
expect(directives['connect_src']).to eq("'self' ws://example.com:1234")
|
||||
expect(connect_src).to eq("'self' ws://example.com:1234")
|
||||
end
|
||||
|
||||
it 'with custom port and secure domain' do
|
||||
stub_config_setting(host: 'example.com', https: true, port: '1234')
|
||||
expect(directives['connect_src']).to eq("'self' wss://example.com:1234")
|
||||
expect(connect_src).to eq("'self' wss://example.com:1234")
|
||||
end
|
||||
|
||||
it 'when port is included in HTTP_PORTS' do
|
||||
described_class::HTTP_PORTS.each do |port|
|
||||
stub_config_setting(host: 'example.com', https: true, port: port)
|
||||
expect(connect_src).to eq("'self' wss://example.com")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when CDN host is defined' do
|
||||
describe 'CDN connections' do
|
||||
before do
|
||||
stub_config_setting(cdn_host: 'https://cdn.example.com')
|
||||
allow(described_class).to receive(:allow_letter_opener)
|
||||
allow(described_class).to receive(:allow_zuora)
|
||||
allow(described_class).to receive(:allow_framed_gitlab_paths)
|
||||
allow(described_class).to receive(:allow_customersdot)
|
||||
allow(described_class).to receive(:csp_level_3_backport)
|
||||
end
|
||||
|
||||
it 'adds CDN host to CSP' do
|
||||
expect(directives['script_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.script_src + " https://cdn.example.com")
|
||||
expect(directives['style_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.style_src + " https://cdn.example.com")
|
||||
expect(directives['font_src']).to eq("'self' https://cdn.example.com")
|
||||
expect(directives['worker_src']).to eq('http://localhost/assets/ blob: data: https://cdn.example.com')
|
||||
expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " https://cdn.example.com http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html http://localhost/-/sandbox/")
|
||||
context 'when CDN host is defined' do
|
||||
let(:cdn_host) { 'https://cdn.example.com' }
|
||||
|
||||
before do
|
||||
stub_config_setting(cdn_host: cdn_host)
|
||||
end
|
||||
|
||||
it 'adds CDN host to CSP' do
|
||||
expect(script_src).to include(cdn_host)
|
||||
expect(style_src).to include(cdn_host)
|
||||
expect(font_src).to include(cdn_host)
|
||||
expect(worker_src).to include(cdn_host)
|
||||
expect(frame_src).to include(cdn_host)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when CDN host is undefined' do
|
||||
before do
|
||||
stub_config_setting(cdn_host: nil)
|
||||
end
|
||||
|
||||
it 'does not include CDN host in CSP' do
|
||||
expect(script_src).to eq(::Gitlab::ContentSecurityPolicy::Directives.script_src)
|
||||
expect(style_src).to eq(::Gitlab::ContentSecurityPolicy::Directives.style_src)
|
||||
expect(font_src).to eq("'self'")
|
||||
expect(worker_src).to eq("http://localhost/assets/ blob: data:")
|
||||
expect(frame_src).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Zuora directives' do
|
||||
context 'when on SaaS', :saas do
|
||||
it 'adds Zuora host to CSP' do
|
||||
expect(directives['frame_src']).to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
|
||||
expect(frame_src).to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when is not Gitlab.com?' do
|
||||
it 'does not add Zuora host to CSP' do
|
||||
expect(directives['frame_src']).not_to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
|
||||
expect(frame_src).not_to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -131,7 +240,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
end
|
||||
|
||||
it 'adds legacy sentry path to CSP' do
|
||||
expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://legacy-sentry.example.com")
|
||||
expect(connect_src).to eq("'self' ws://gitlab.example.com dummy://legacy-sentry.example.com")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -143,7 +252,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
end
|
||||
|
||||
it 'adds new sentry path to CSP' do
|
||||
expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://sentry.example.com")
|
||||
expect(connect_src).to eq("'self' ws://gitlab.example.com dummy://sentry.example.com")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -159,11 +268,22 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
end
|
||||
|
||||
it 'config is backwards compatible, does not add sentry path to CSP' do
|
||||
expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com")
|
||||
expect(connect_src).to eq("'self' ws://gitlab.example.com")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when legacy sentry and sentry are both configured' do
|
||||
let(:connect_src_expectation) do
|
||||
# rubocop:disable Lint/PercentStringArray
|
||||
%w[
|
||||
'self'
|
||||
ws://gitlab.example.com
|
||||
dummy://legacy-sentry.example.com
|
||||
dummy://sentry.example.com
|
||||
].join(' ')
|
||||
# rubocop:enable Lint/PercentStringArray
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab.config.sentry).to receive(:enabled).and_return(true)
|
||||
allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return(legacy_dsn)
|
||||
|
|
@ -173,24 +293,57 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
end
|
||||
|
||||
it 'adds both sentry paths to CSP' do
|
||||
expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://legacy-sentry.example.com dummy://sentry.example.com")
|
||||
expect(connect_src).to eq(connect_src_expectation)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when CUSTOMER_PORTAL_URL is set' do
|
||||
let(:customer_portal_url) { 'https://customers.example.com' }
|
||||
describe 'Customer portal frames' do
|
||||
context 'when CUSTOMER_PORTAL_URL is set' do
|
||||
let(:customer_portal_url) { 'https://customers.example.com' }
|
||||
let(:frame_src_expectation) do
|
||||
[
|
||||
::Gitlab::ContentSecurityPolicy::Directives.frame_src,
|
||||
'http://localhost/admin/',
|
||||
'http://localhost/assets/',
|
||||
'http://localhost/-/speedscope/index.html',
|
||||
'http://localhost/-/sandbox/',
|
||||
customer_portal_url
|
||||
].join(' ')
|
||||
end
|
||||
|
||||
before do
|
||||
stub_env('CUSTOMER_PORTAL_URL', customer_portal_url)
|
||||
before do
|
||||
stub_env('CUSTOMER_PORTAL_URL', customer_portal_url)
|
||||
end
|
||||
|
||||
it 'adds CUSTOMER_PORTAL_URL to CSP' do
|
||||
expect(frame_src).to eq(frame_src_expectation)
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds CUSTOMER_PORTAL_URL to CSP' do
|
||||
expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html http://localhost/-/sandbox/ #{customer_portal_url}")
|
||||
context 'when CUSTOMER_PORTAL_URL is blank' do
|
||||
let(:customer_portal_url) { '' }
|
||||
let(:frame_src_expectation) do
|
||||
[
|
||||
::Gitlab::ContentSecurityPolicy::Directives.frame_src,
|
||||
'http://localhost/admin/',
|
||||
'http://localhost/assets/',
|
||||
'http://localhost/-/speedscope/index.html',
|
||||
'http://localhost/-/sandbox/'
|
||||
].join(' ')
|
||||
end
|
||||
|
||||
before do
|
||||
stub_env('CUSTOMER_PORTAL_URL', customer_portal_url)
|
||||
end
|
||||
|
||||
it 'adds CUSTOMER_PORTAL_URL to CSP' do
|
||||
expect(frame_src).to eq(frame_src_expectation)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'letter_opener application URL' do
|
||||
describe 'letter_opener application URL' do
|
||||
let(:gitlab_url) { 'http://gitlab.example.com' }
|
||||
let(:letter_opener_url) { "#{gitlab_url}/rails/letter_opener/" }
|
||||
|
||||
|
|
@ -200,21 +353,21 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
|
||||
context 'when in production' do
|
||||
before do
|
||||
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
|
||||
stub_rails_env('production')
|
||||
end
|
||||
|
||||
it 'does not add letter_opener to CSP' do
|
||||
expect(directives['frame_src']).not_to include(letter_opener_url)
|
||||
expect(frame_src).not_to include(letter_opener_url)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when in development' do
|
||||
before do
|
||||
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
|
||||
stub_rails_env('development')
|
||||
end
|
||||
|
||||
it 'adds letter_opener to CSP' do
|
||||
expect(directives['frame_src']).to include(letter_opener_url)
|
||||
expect(frame_src).to include(letter_opener_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -234,7 +387,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
end
|
||||
|
||||
it 'does not add Snowplow Micro URL to connect-src' do
|
||||
expect(directives['connect_src']).not_to include(snowplow_micro_url)
|
||||
expect(connect_src).not_to include(snowplow_micro_url)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -244,7 +397,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
end
|
||||
|
||||
it 'adds Snowplow Micro URL with trailing slash to connect-src' do
|
||||
expect(directives['connect_src']).to match(Regexp.new(snowplow_micro_url))
|
||||
expect(connect_src).to match(Regexp.new(snowplow_micro_url))
|
||||
end
|
||||
|
||||
context 'when not enabled using config' do
|
||||
|
|
@ -253,7 +406,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
end
|
||||
|
||||
it 'does not add Snowplow Micro URL to connect-src' do
|
||||
expect(directives['connect_src']).not_to include(snowplow_micro_url)
|
||||
expect(connect_src).not_to include(snowplow_micro_url)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -262,8 +415,18 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
stub_env('REVIEW_APPS_ENABLED', 'true')
|
||||
end
|
||||
|
||||
it 'adds gitlab-org/gitlab merge requests API endpoint to CSP' do
|
||||
expect(directives['connect_src']).to include('https://gitlab.com/api/v4/projects/278964/merge_requests/')
|
||||
it "includes review app's merge requests API endpoint in the CSP" do
|
||||
expect(connect_src).to include('https://gitlab.com/api/v4/projects/278964/merge_requests/')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when REVIEW_APPS_ENABLED is blank' do
|
||||
before do
|
||||
stub_env('REVIEW_APPS_ENABLED', '')
|
||||
end
|
||||
|
||||
it "does not include review app's merge requests API endpoint in the CSP" do
|
||||
expect(connect_src).not_to include('https://gitlab.com/api/v4/projects/278964/merge_requests/')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -691,6 +691,100 @@ RSpec.describe Gitlab::Git::Commit, feature_category: :source_code_management do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'SHA patterns' do
|
||||
shared_examples 'a SHA-matching pattern' do
|
||||
let(:expected_match) { sha }
|
||||
|
||||
shared_examples 'a match' do
|
||||
it 'matches the pattern' do
|
||||
expect(value).to match(pattern)
|
||||
expect(pattern.match(value).to_a).to eq([expected_match])
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'no match' do
|
||||
it 'does not match the pattern' do
|
||||
expect(value).not_to match(pattern)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a SHA pattern' do
|
||||
context "with too short value" do
|
||||
let(:value) { sha[0, described_class::MIN_SHA_LENGTH - 1] }
|
||||
|
||||
it_behaves_like 'no match'
|
||||
end
|
||||
|
||||
context "with full length" do
|
||||
let(:value) { sha }
|
||||
|
||||
it_behaves_like 'a match'
|
||||
end
|
||||
|
||||
context "with exceeeding length" do
|
||||
let(:value) { sha + sha }
|
||||
|
||||
# This case is not exactly pretty for SHA1 as we would still match the full SHA256 length. It's arguable what
|
||||
# the correct behaviour would be, but without starting to distinguish SHA1 and SHA256 hashes this is the best
|
||||
# we can do.
|
||||
let(:expected_match) { (sha + sha)[0, described_class::MAX_SHA_LENGTH] }
|
||||
|
||||
it_behaves_like 'a match'
|
||||
end
|
||||
|
||||
context "with embedded SHA" do
|
||||
let(:value) { "xxx#{sha}xxx" }
|
||||
|
||||
it_behaves_like 'a match'
|
||||
end
|
||||
end
|
||||
|
||||
context 'abbreviated SHA pattern' do
|
||||
let(:pattern) { described_class::SHA_PATTERN }
|
||||
|
||||
context "with minimum length" do
|
||||
let(:value) { sha[0, described_class::MIN_SHA_LENGTH] }
|
||||
let(:expected_match) { value }
|
||||
|
||||
it_behaves_like 'a match'
|
||||
end
|
||||
|
||||
context "with medium length" do
|
||||
let(:value) { sha[0, described_class::MIN_SHA_LENGTH + 20] }
|
||||
let(:expected_match) { value }
|
||||
|
||||
it_behaves_like 'a match'
|
||||
end
|
||||
|
||||
it_behaves_like 'a SHA pattern'
|
||||
end
|
||||
|
||||
context 'full SHA pattern' do
|
||||
let(:pattern) { described_class::FULL_SHA_PATTERN }
|
||||
|
||||
context 'with abbreviated length' do
|
||||
let(:value) { sha[0, described_class::SHA1_LENGTH - 1] }
|
||||
|
||||
it_behaves_like 'no match'
|
||||
end
|
||||
|
||||
it_behaves_like 'a SHA pattern'
|
||||
end
|
||||
end
|
||||
|
||||
context 'SHA1' do
|
||||
let(:sha) { "5716ca5987cbf97d6bb54920bea6adde242d87e6" }
|
||||
|
||||
it_behaves_like 'a SHA-matching pattern'
|
||||
end
|
||||
|
||||
context 'SHA256' do
|
||||
let(:sha) { "a52e146ac2ab2d0efbb768ab8ebd1e98a6055764c81fe424fbae4522f5b4cb92" }
|
||||
|
||||
it_behaves_like 'a SHA-matching pattern'
|
||||
end
|
||||
end
|
||||
|
||||
def sample_commit_hash
|
||||
{
|
||||
author_email: "dmitriy.zaporozhets@gmail.com",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
|
|||
title
|
||||
updated_at
|
||||
updated_by_id
|
||||
draft
|
||||
].freeze
|
||||
|
||||
expect(safe_attribute_keys).to match_array(expected_safe_attribute_keys)
|
||||
|
|
@ -66,6 +67,7 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
|
|||
url
|
||||
last_commit
|
||||
work_in_progress
|
||||
draft
|
||||
total_time_spent
|
||||
time_change
|
||||
human_total_time_spent
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ RSpec.describe Gitlab::X509::Signature do
|
|||
|
||||
it 'returns a verified signature if email does match' do
|
||||
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
|
||||
|
||||
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
||||
expect(signature.verified_signature).to be_truthy
|
||||
expect(signature.verification_status).to eq(:verified)
|
||||
|
|
@ -55,6 +56,27 @@ RSpec.describe Gitlab::X509::Signature do
|
|||
expect(signature.verification_status).to eq(:verified)
|
||||
end
|
||||
|
||||
context 'when the certificate contains multiple emails' do
|
||||
before do
|
||||
allow_any_instance_of(described_class).to receive(:get_certificate_extension).and_call_original
|
||||
|
||||
allow_any_instance_of(described_class).to receive(:get_certificate_extension)
|
||||
.with('subjectAltName')
|
||||
.and_return("email:gitlab2@example.com, othername:<unsupported>, email:#{X509Helpers::User1.certificate_email}")
|
||||
end
|
||||
|
||||
context 'and the email matches one of them' do
|
||||
it 'returns a verified signature' do
|
||||
expect(signature.x509_certificate).to have_attributes(certificate_attributes.except(:email, :emails))
|
||||
expect(signature.x509_certificate.email).to eq('gitlab2@example.com')
|
||||
expect(signature.x509_certificate.emails).to contain_exactly('gitlab2@example.com', X509Helpers::User1.certificate_email)
|
||||
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
||||
expect(signature.verified_signature).to be_truthy
|
||||
expect(signature.verification_status).to eq(:verified)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "if the email matches but isn't confirmed" do
|
||||
let!(:user) { create(:user, :unconfirmed, email: X509Helpers::User1.certificate_email) }
|
||||
|
||||
|
|
@ -106,6 +128,7 @@ RSpec.describe Gitlab::X509::Signature do
|
|||
subject_key_identifier: X509Helpers::User1.certificate_subject_key_identifier,
|
||||
subject: X509Helpers::User1.certificate_subject,
|
||||
email: X509Helpers::User1.certificate_email,
|
||||
emails: [X509Helpers::User1.certificate_email],
|
||||
serial_number: X509Helpers::User1.certificate_serial
|
||||
}
|
||||
end
|
||||
|
|
@ -248,15 +271,31 @@ RSpec.describe Gitlab::X509::Signature do
|
|||
.and_return("email:gitlab@example.com, othername:<unsupported>")
|
||||
end
|
||||
|
||||
it 'extracts email' do
|
||||
signature = described_class.new(
|
||||
let(:signature) do
|
||||
described_class.new(
|
||||
X509Helpers::User1.signed_commit_signature,
|
||||
X509Helpers::User1.signed_commit_base_data,
|
||||
'gitlab@example.com',
|
||||
X509Helpers::User1.signed_commit_time
|
||||
)
|
||||
end
|
||||
|
||||
it 'extracts email' do
|
||||
expect(signature.x509_certificate.email).to eq("gitlab@example.com")
|
||||
expect(signature.x509_certificate.emails).to contain_exactly("gitlab@example.com")
|
||||
end
|
||||
|
||||
context 'when there are multiple emails' do
|
||||
before do
|
||||
allow_any_instance_of(described_class).to receive(:get_certificate_extension)
|
||||
.with('subjectAltName')
|
||||
.and_return("email:gitlab@example.com, othername:<unsupported>, email:gitlab2@example.com")
|
||||
end
|
||||
|
||||
it 'extracts all the emails' do
|
||||
expect(signature.x509_certificate.email).to eq("gitlab@example.com")
|
||||
expect(signature.x509_certificate.emails).to contain_exactly("gitlab@example.com", "gitlab2@example.com")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -311,6 +350,7 @@ RSpec.describe Gitlab::X509::Signature do
|
|||
subject_key_identifier: X509Helpers::User1.tag_certificate_subject_key_identifier,
|
||||
subject: X509Helpers::User1.certificate_subject,
|
||||
email: X509Helpers::User1.certificate_email,
|
||||
emails: [X509Helpers::User1.certificate_email],
|
||||
serial_number: X509Helpers::User1.tag_certificate_serial
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackfillDefaultBranchProtectionNamespaceSetting, feature_category: :database do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :namespace_settings,
|
||||
column_name: :namespace_id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -831,7 +831,8 @@ eos
|
|||
expect(described_class.valid_hash?('a' * 6)).to be false
|
||||
expect(described_class.valid_hash?('a' * 7)).to be true
|
||||
expect(described_class.valid_hash?('a' * 40)).to be true
|
||||
expect(described_class.valid_hash?('a' * 41)).to be false
|
||||
expect(described_class.valid_hash?('a' * 64)).to be true
|
||||
expect(described_class.valid_hash?('a' * 65)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -180,7 +180,9 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do
|
|||
|
||||
subject(:import) { target_project.team.import(source_project, current_user) }
|
||||
|
||||
it { is_expected.to match(imported_members) }
|
||||
it 'matches the imported members', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/419394' do
|
||||
is_expected.to match(imported_members)
|
||||
end
|
||||
|
||||
it 'target project includes source member with the same access' do
|
||||
import
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BlobPresenter do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
RSpec.describe BlobPresenter, feature_category: :source_code_management do
|
||||
let_it_be(:project) { create(:project, :repository, lfs: true) }
|
||||
let_it_be(:user) { project.first_owner }
|
||||
|
||||
let(:repository) { project.repository }
|
||||
|
|
@ -228,6 +228,83 @@ RSpec.describe BlobPresenter do
|
|||
it { expect(presenter.code_owners).to match_array([]) }
|
||||
end
|
||||
|
||||
describe '#external_storage_url' do
|
||||
let(:static_objects_external_storage_url) { nil }
|
||||
let(:raw_path) { Rails.application.routes.url_helpers.project_raw_path(project, File.join(ref, path)) }
|
||||
let(:token) { '' }
|
||||
let(:cdn_fronted_url) do
|
||||
"#{static_objects_external_storage_url}#{raw_path}#{token}"
|
||||
end
|
||||
|
||||
subject { presenter.external_storage_url }
|
||||
|
||||
before do
|
||||
stub_application_setting(static_objects_external_storage_url: static_objects_external_storage_url)
|
||||
end
|
||||
|
||||
context 'when blob is an lfs pointer' do
|
||||
let_it_be(:blob) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
|
||||
let_it_be(:lfs_object) { project.lfs_objects.find_by_oid(blob.lfs_oid) }
|
||||
|
||||
let(:lfs_object_url) { lfs_object.file.url(content_type: "application/octet-stream") }
|
||||
let(:lfs_object_proxy_url) { "#{project.http_url_to_repo}/gitlab-lfs/objects/#{lfs_object.oid}" }
|
||||
let(:proxy_download) { true }
|
||||
let(:lfs_config) do
|
||||
Gitlab.config.lfs.deep_merge(
|
||||
'enabled' => true,
|
||||
'object_store' => {
|
||||
'remote_directory' => 'lfs-objects',
|
||||
'enabled' => true,
|
||||
'proxy_download' => proxy_download,
|
||||
'connection' => {
|
||||
'endpoint' => 'http:127.0.0.1:9000',
|
||||
'path_style' => true
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_lfs_setting(lfs_config)
|
||||
stub_lfs_object_storage(proxy_download: proxy_download)
|
||||
end
|
||||
|
||||
context 'when direct download is enabled' do
|
||||
let(:proxy_download) { false }
|
||||
|
||||
it { is_expected.to eq(lfs_object_url) }
|
||||
end
|
||||
|
||||
context 'when proxy download is enabled' do
|
||||
it { is_expected.to eq(lfs_object_proxy_url) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when static_objects_external_storage_enabled?' do
|
||||
let(:static_objects_external_storage_url) { 'https://cdn.gitlab.com' }
|
||||
|
||||
context 'and project is private' do
|
||||
let(:token) { "?token=#{user.static_object_token}" }
|
||||
|
||||
it { is_expected.to eq(cdn_fronted_url) }
|
||||
end
|
||||
|
||||
context 'and project is public' do
|
||||
let(:token) { '' }
|
||||
|
||||
before do
|
||||
allow(project).to receive(:public?).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(cdn_fronted_url) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not static_objects_external_storage_enabled?' do
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ide_edit_path' do
|
||||
it { expect(presenter.ide_edit_path).to eq("/-/ide/project/#{project.full_path}/edit/HEAD/-/files/ruby/regex.rb") }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -380,13 +380,14 @@ RSpec.describe 'project routing' do
|
|||
it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/hooks/hook_logs/1', '/gitlab/gitlabhq/-/hooks/hook_logs/1'
|
||||
end
|
||||
|
||||
# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/}
|
||||
# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: Gitlab::Git::Commit::SHA_PATTERN, project_id: /[^\/]+/}
|
||||
describe Projects::CommitController, 'routing' do
|
||||
it 'to #show' do
|
||||
expect(get('/gitlab/gitlabhq/-/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd')
|
||||
expect(get('/gitlab/gitlabhq/-/commit/4246fbd.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'diff')
|
||||
expect(get('/gitlab/gitlabhq/-/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch')
|
||||
expect(get('/gitlab/gitlabhq/-/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
|
||||
expect(get('/gitlab/gitlabhq/-/commit/6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321')
|
||||
end
|
||||
|
||||
it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/commit/4246fbd", "/gitlab/gitlabhq/-/commit/4246fbd"
|
||||
|
|
@ -652,6 +653,10 @@ RSpec.describe 'project routing' do
|
|||
it 'to #show' do
|
||||
expect(get('/gitlab/gitlabhq/-/compare/master...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'master', to: 'stable')
|
||||
expect(get('/gitlab/gitlabhq/-/compare/issue/1234...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable')
|
||||
expect(get('/gitlab/gitlabhq/-/compare/257cc5642cb1a054f08cc83f2d943e56fd3ebe99...5716ca5987cbf97d6bb54920bea6adde242d87e6'))
|
||||
.to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: '257cc5642cb1a054f08cc83f2d943e56fd3ebe99', to: '5716ca5987cbf97d6bb54920bea6adde242d87e6')
|
||||
expect(get('/gitlab/gitlabhq/-/compare/47d6aca82756ff2e61e53520bfdf1faa6c86d933be4854eb34840c57d12e0c85...a52e146ac2ab2d0efbb768ab8ebd1e98a6055764c81fe424fbae4522f5b4cb92'))
|
||||
.to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: '47d6aca82756ff2e61e53520bfdf1faa6c86d933be4854eb34840c57d12e0c85', to: 'a52e146ac2ab2d0efbb768ab8ebd1e98a6055764c81fe424fbae4522f5b4cb92')
|
||||
end
|
||||
|
||||
it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/compare', '/gitlab/gitlabhq/-/compare'
|
||||
|
|
|
|||
|
|
@ -94,9 +94,9 @@ module FilterSpecHelper
|
|||
when /\A(.+)?[^\d]\d+\z/
|
||||
# Integer-based reference with optional project prefix
|
||||
reference.gsub(/\d+\z/) { |i| i.to_i + 10_000 }
|
||||
when /\A(.+@)?(\h{7,40}\z)/
|
||||
when /\A(.+@)?(#{Gitlab::Git::Commit::RAW_SHA_PATTERN}\z)/o
|
||||
# SHA-based reference with optional prefix
|
||||
reference.gsub(/\h{7,40}\z/) { |v| v.reverse }
|
||||
reference.gsub(/#{Gitlab::Git::Commit::RAW_SHA_PATTERN}\z/o) { |v| v.reverse }
|
||||
else
|
||||
reference.gsub(/\w+\z/) { |v| v.reverse }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ RSpec.shared_examples 'resource access tokens creation' do |resource_type|
|
|||
name = 'My access token'
|
||||
|
||||
visit resource_settings_access_tokens_path
|
||||
|
||||
click_button 'Add new token'
|
||||
fill_in 'Token name', with: name
|
||||
|
||||
# Set date to 1st of next month
|
||||
|
|
|
|||
Loading…
Reference in New Issue