Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-07-27 15:10:15 +00:00
parent fdf32113c3
commit 9979d2afd6
78 changed files with 1374 additions and 395 deletions

View File

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

View File

@ -1 +1 @@
6eb139660ed3ebc5921f565672a00c71973fc620
b3ea2f8bb802758d667f642b1228d31990559343

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
0a9e2fd92a64fbd4f2dc0d9fc866b3c5bb582c10503b0a85f1164db2e42c7cd0

View File

@ -0,0 +1 @@
3ace146e873db96f6dcb556e701339081620aa268f013979c9062b50957d9e13

View File

@ -0,0 +1 @@
3523475202ec758dc33748af5c2e85392d1ab55af4a5c7f7b76412b6723d1bf6

View File

@ -0,0 +1 @@
e4236ae465987e86f4a68ee552856afe054dccf5b13c5d71abb72cad186266d1

View File

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

View File

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

View File

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

View File

@ -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.
![Example of job running with terminal available](img/interactive_web_terminal_running_job.png)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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