Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
34b7bee25c
commit
3b328dfc1d
|
|
@ -0,0 +1,29 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: { GlIcon },
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
headerText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<gl-icon :name="icon" /> <span>{{ headerText }}</span>
|
||||
<ul v-if="$scopedSlots.list" class="gl-mb-0 gl-ml-2 gl-pl-7 gl-text-subtle">
|
||||
<slot name="list"></slot>
|
||||
</ul>
|
||||
<div v-if="$scopedSlots.default" class="gl-pl-5 gl-text-subtle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
<script>
|
||||
import { GlFormRadioGroup, GlFormRadio, GlCard } from '@gitlab/ui';
|
||||
import { GlFormRadioGroup, GlFormRadio, GlCard, GlLink, GlIcon, GlSprintf } from '@gitlab/ui';
|
||||
import AdminRoleDropdown from 'ee_component/admin/users/components/user_type/admin_role_dropdown.vue';
|
||||
import { s__ } from '~/locale';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import AccessSummarySection from './access_summary_section.vue';
|
||||
|
||||
export const USER_TYPE_REGULAR = {
|
||||
value: 'regular',
|
||||
|
|
@ -21,7 +24,28 @@ export const USER_TYPE_ADMIN = { value: 'admin', text: s__('AdminUsers|Administr
|
|||
// This component is rendered inside an HTML form, so it doesn't submit any data directly. It only sets up the input
|
||||
// values so that when the form is submitted, the values selected in this component are submitted as well.
|
||||
export default {
|
||||
components: { GlFormRadioGroup, GlFormRadio, GlCard, AdminRoleDropdown },
|
||||
i18n: {
|
||||
regularRole: s__(
|
||||
'AdminUsers|Based on member role in groups and projects. %{linkStart}Learn more about member roles.%{linkEnd}',
|
||||
),
|
||||
auditorRole: s__(
|
||||
'AdminUsers|May be directly added to groups and projects. %{linkStart}Learn more about auditor role.%{linkEnd}',
|
||||
),
|
||||
settingsText: s__(
|
||||
'AdminUsers|Requires at least Maintainer role in specific groups and projects.',
|
||||
),
|
||||
},
|
||||
components: {
|
||||
GlFormRadioGroup,
|
||||
GlFormRadio,
|
||||
GlCard,
|
||||
GlLink,
|
||||
GlIcon,
|
||||
GlSprintf,
|
||||
AccessSummarySection,
|
||||
AdminRoleDropdown,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
userType: {
|
||||
type: String,
|
||||
|
|
@ -47,6 +71,18 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
selectedUserTypeName() {
|
||||
return this.userTypeItems.find(({ value }) => value === this.currentUserType)?.text;
|
||||
},
|
||||
isRegularSelected() {
|
||||
return this.currentUserType === USER_TYPE_REGULAR.value;
|
||||
},
|
||||
isAuditorSelected() {
|
||||
return this.currentUserType === USER_TYPE_AUDITOR.value;
|
||||
},
|
||||
isAdminSelected() {
|
||||
return this.currentUserType === USER_TYPE_ADMIN.value;
|
||||
},
|
||||
userTypeItems() {
|
||||
USER_TYPE_ADMIN.description = this.isCurrentUser
|
||||
? s__(
|
||||
|
|
@ -60,7 +96,14 @@ export default {
|
|||
? [USER_TYPE_REGULAR, USER_TYPE_AUDITOR, USER_TYPE_ADMIN]
|
||||
: [USER_TYPE_REGULAR, USER_TYPE_ADMIN];
|
||||
},
|
||||
canChangeAdminRole() {
|
||||
return (
|
||||
this.glFeatures.customRoles && this.glFeatures.customAdminRoles && !this.isAdminSelected
|
||||
);
|
||||
},
|
||||
},
|
||||
ROLES_DOC_LINK: helpPagePath('user/permissions'),
|
||||
AUDITOR_USER_DOC_LINK: helpPagePath('administration/auditor_users'),
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -74,7 +117,7 @@ export default {
|
|||
<gl-form-radio-group
|
||||
v-model="currentUserType"
|
||||
name="user[access_level]"
|
||||
class="gl-flex gl-flex-col gl-gap-3"
|
||||
class="gl-mb-3 gl-flex gl-flex-col gl-gap-3"
|
||||
>
|
||||
<gl-form-radio
|
||||
v-for="item in userTypeItems"
|
||||
|
|
@ -89,7 +132,74 @@ export default {
|
|||
</gl-form-radio-group>
|
||||
|
||||
<gl-card class="gl-mb-7 gl-bg-transparent">
|
||||
<admin-role-dropdown :role-id="adminRoleId" />
|
||||
<div class="gl-mb-4" data-testid="summary-header">
|
||||
<label class="gl-mb-0">
|
||||
<gl-sprintf :message="s__('AdminUsers|Access summary for %{userType} user')">
|
||||
<template #userType>{{ selectedUserTypeName }}</template>
|
||||
</gl-sprintf>
|
||||
</label>
|
||||
<p v-if="canChangeAdminRole" class="gl-mb-0 gl-text-subtle">
|
||||
{{ s__('AdminUsers|Review and set Admin area access with a custom admin role.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<access-summary-section icon="admin" :header-text="__('Admin area')" class="gl-mb-4">
|
||||
<admin-role-dropdown v-if="canChangeAdminRole" :role-id="adminRoleId" class="gl-mt-1" />
|
||||
|
||||
<template v-else-if="isAdminSelected">
|
||||
<gl-icon name="check" variant="success" />
|
||||
{{ s__('AdminUsers|Full read and write access.') }}
|
||||
</template>
|
||||
|
||||
<template v-if="!canChangeAdminRole && !isAdminSelected" #list>
|
||||
<li>{{ s__('AdminUsers|No access.') }}</li>
|
||||
</template>
|
||||
</access-summary-section>
|
||||
|
||||
<access-summary-section icon="group" :header-text="__('Groups and projects')" class="gl-mb-4">
|
||||
<template v-if="isRegularSelected" #list>
|
||||
<li>
|
||||
<gl-sprintf :message="$options.i18n.regularRole">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="$options.ROLES_DOC_LINK" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isAuditorSelected" #list>
|
||||
<li>{{ s__('AdminUsers|Read access to all groups and projects.') }}</li>
|
||||
<li>
|
||||
<gl-sprintf :message="$options.i18n.auditorRole">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="$options.AUDITOR_USER_DOC_LINK" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<template v-if="isAdminSelected">
|
||||
<gl-icon name="check" variant="success" />
|
||||
{{ s__('AdminUsers|Full read and write access.') }}
|
||||
</template>
|
||||
</access-summary-section>
|
||||
|
||||
<access-summary-section
|
||||
icon="settings"
|
||||
:header-text="s__('AdminUsers|Groups and project settings')"
|
||||
class="gl-mb-1"
|
||||
>
|
||||
<template v-if="isRegularSelected || isAuditorSelected" #list>
|
||||
<li>{{ $options.i18n.settingsText }}</li>
|
||||
</template>
|
||||
|
||||
<template v-if="isAdminSelected">
|
||||
<gl-icon name="check" variant="success" />
|
||||
{{ s__('AdminUsers|Full read and write access.') }}
|
||||
</template>
|
||||
</access-summary-section>
|
||||
</gl-card>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|||
import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown/timezone_dropdown.vue';
|
||||
import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue';
|
||||
import PipelineInputsForm from '~/ci/common/pipeline_inputs/pipeline_inputs_form.vue';
|
||||
import PipelineVariablesPermissionsMixin from '~/ci/mixins/pipeline_variables_permissions_mixin';
|
||||
import createPipelineScheduleMutation from '../graphql/mutations/create_pipeline_schedule.mutation.graphql';
|
||||
import updatePipelineScheduleMutation from '../graphql/mutations/update_pipeline_schedule.mutation.graphql';
|
||||
import getPipelineSchedulesQuery from '../graphql/queries/get_pipeline_schedules.query.graphql';
|
||||
|
|
@ -38,7 +37,7 @@ export default {
|
|||
RefSelector,
|
||||
TimezoneDropdown,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin(), PipelineVariablesPermissionsMixin],
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
inject: [
|
||||
'projectPath',
|
||||
'projectId',
|
||||
|
|
@ -46,7 +45,6 @@ export default {
|
|||
'dailyLimit',
|
||||
'settingsLink',
|
||||
'schedulesPath',
|
||||
'userRole',
|
||||
],
|
||||
props: {
|
||||
timezoneData: {
|
||||
|
|
@ -62,6 +60,10 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canSetPipelineVariables: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
schedule: {
|
||||
|
|
@ -333,7 +335,7 @@ export default {
|
|||
/>
|
||||
<!--Variable List-->
|
||||
<pipeline-variables-form-group
|
||||
v-if="canViewPipelineVariables"
|
||||
v-if="canSetPipelineVariables"
|
||||
:initial-variables="variables"
|
||||
:editing="editing"
|
||||
@update-variables="updatedVariables = $event"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default (selector, editing = false) => {
|
|||
projectPath,
|
||||
schedulesPath,
|
||||
settingsLink,
|
||||
userRole,
|
||||
canSetPipelineVariables,
|
||||
timezoneData,
|
||||
} = containerEl.dataset;
|
||||
|
||||
|
|
@ -43,7 +43,6 @@ export default (selector, editing = false) => {
|
|||
projectPath,
|
||||
schedulesPath,
|
||||
settingsLink,
|
||||
userRole,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(PipelineSchedulesForm, {
|
||||
|
|
@ -51,6 +50,7 @@ export default (selector, editing = false) => {
|
|||
timezoneData: JSON.parse(timezoneData),
|
||||
refParam: defaultBranch,
|
||||
editing,
|
||||
canSetPipelineVariables: parseBoolean(canSetPipelineVariables),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ export default {
|
|||
v-gl-tooltip="badge.tooltip"
|
||||
:variant="badge.variant"
|
||||
:icon="badge.icon"
|
||||
icon-optically-aligned
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<span class="gl-truncate">
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import DockerHubRateLimitsAlert from '~/vue_shared/components/docker_hub_rate_limits_alert.vue';
|
||||
|
||||
export default (selector = '#js-docker-hub-rate-limits-alert') => {
|
||||
const containerEl = document.querySelector(selector);
|
||||
|
||||
if (!containerEl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Vue({
|
||||
el: containerEl,
|
||||
render(createElement) {
|
||||
return createElement(DockerHubRateLimitsAlert);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -12,6 +12,7 @@ import {
|
|||
import { localeDateFormat, newDate } from '~/lib/utils/datetime_utility';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { n__ } from '~/locale';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
|
|
@ -54,6 +55,7 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
tag: {
|
||||
type: Object,
|
||||
|
|
@ -166,6 +168,9 @@ export default {
|
|||
this.tag.protection?.minimumAccessLevelForPush != null
|
||||
);
|
||||
},
|
||||
isImmutable() {
|
||||
return this.glFeatures.containerRegistryImmutableTags && this.tag.protection?.immutable;
|
||||
},
|
||||
tagRowId() {
|
||||
return `${this.tag.name}_badge`;
|
||||
},
|
||||
|
|
@ -227,6 +232,22 @@ export default {
|
|||
</gl-popover>
|
||||
</template>
|
||||
|
||||
<template v-if="isImmutable">
|
||||
<gl-badge
|
||||
:id="tagRowId"
|
||||
boundary="viewport"
|
||||
class="gl-ml-4"
|
||||
data-testid="immutable-badge"
|
||||
>
|
||||
{{ s__('ContainerRegistry|immutable') }}
|
||||
</gl-badge>
|
||||
<gl-popover :target="tagRowId" data-testid="immutable-popover">
|
||||
{{
|
||||
s__('ContainerRegistry|This container image tag cannot be overwritten or deleted.')
|
||||
}}
|
||||
</gl-popover>
|
||||
</template>
|
||||
|
||||
<clipboard-button
|
||||
v-if="tag.location"
|
||||
:title="$options.i18n.COPY_IMAGE_PATH_TITLE"
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ query getContainerRepositoryTags(
|
|||
protection {
|
||||
minimumAccessLevelForPush
|
||||
minimumAccessLevelForDelete
|
||||
immutable
|
||||
}
|
||||
referrers {
|
||||
artifactType
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import Tracking from '~/tracking';
|
|||
import PersistedPagination from '~/packages_and_registries/shared/components/persisted_pagination.vue';
|
||||
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
|
||||
import MetadataDatabaseAlert from '~/packages_and_registries/shared/components/container_registry_metadata_database_alert.vue';
|
||||
import DockerHubRateLimitsAlert from '~/vue_shared/components/docker_hub_rate_limits_alert.vue';
|
||||
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import {
|
||||
getPageParams,
|
||||
|
|
@ -65,7 +64,6 @@ export default {
|
|||
/* webpackChunkName: 'container_registry_components' */ '~/packages_and_registries/shared/components/cli_commands.vue'
|
||||
),
|
||||
DeleteModal,
|
||||
DockerHubRateLimitsAlert,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
GlAlert,
|
||||
|
|
@ -254,8 +252,6 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<docker-hub-rate-limits-alert class="gl-my-5" />
|
||||
|
||||
<metadata-database-alert v-if="!config.isMetadataDatabaseEnabled" />
|
||||
<gl-alert
|
||||
v-if="showDeleteAlert"
|
||||
|
|
|
|||
|
|
@ -7,14 +7,12 @@ import {
|
|||
UPDATE_SETTINGS_SUCCESS_MESSAGE,
|
||||
} from '~/packages_and_registries/settings/project/constants';
|
||||
import MetadataDatabaseAlert from '~/packages_and_registries/shared/components/container_registry_metadata_database_alert.vue';
|
||||
import DockerHubRateLimitsAlert from '~/vue_shared/components/docker_hub_rate_limits_alert.vue';
|
||||
import PackageRegistrySection from '~/packages_and_registries/settings/project/components/package_registry_section.vue';
|
||||
import ContainerRegistrySection from '~/packages_and_registries/settings/project/components/container_registry_section.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContainerRegistrySection,
|
||||
DockerHubRateLimitsAlert,
|
||||
GlAlert,
|
||||
MetadataDatabaseAlert,
|
||||
PackageRegistrySection,
|
||||
|
|
@ -66,7 +64,6 @@ export default {
|
|||
>
|
||||
{{ $options.i18n.UPDATE_SETTINGS_SUCCESS_MESSAGE }}
|
||||
</gl-alert>
|
||||
<docker-hub-rate-limits-alert class="gl-my-5" />
|
||||
<metadata-database-alert v-if="!isContainerRegistryMetadataDatabaseEnabled" class="gl-mt-5" />
|
||||
<package-registry-section v-if="showPackageRegistrySettings" />
|
||||
<container-registry-section
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import initDeployTokens from '~/deploy_tokens';
|
|||
import { initProjectRunnersRegistrationDropdown } from '~/ci/runner/project_runners/register';
|
||||
import { initProjectRunnersSettings } from '~/ci/runner/project_runners_settings/index';
|
||||
import { initGeneralPipelinesOptions } from '~/ci_settings_general_pipeline';
|
||||
import initDockerHubRateLimitsAlert from '~/docker_hub_rate_limits';
|
||||
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
||||
|
||||
// Initialize expandable settings panels
|
||||
|
|
@ -45,7 +44,6 @@ initRefSwitcherBadges();
|
|||
initTokenAccess();
|
||||
initCiSecureFiles();
|
||||
initGeneralPipelinesOptions();
|
||||
initDockerHubRateLimitsAlert();
|
||||
initProjectRunnersSettings();
|
||||
|
||||
renderGFM(document.getElementById('js-shared-runners-markdown'));
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ const mountPipelineChartsApp = (el) => {
|
|||
coverageChartPath,
|
||||
defaultBranch,
|
||||
testRunsEmptyStateImagePath,
|
||||
projectQualitySummaryFeedbackImagePath,
|
||||
} = el.dataset;
|
||||
|
||||
const shouldRenderDoraCharts = parseBoolean(el.dataset.shouldRenderDoraCharts);
|
||||
|
|
@ -46,7 +45,6 @@ const mountPipelineChartsApp = (el) => {
|
|||
defaultBranch,
|
||||
projectBranchCount,
|
||||
testRunsEmptyStateImagePath,
|
||||
projectQualitySummaryFeedbackImagePath,
|
||||
contextId,
|
||||
},
|
||||
render: (createElement) => createElement(ProjectPipelinesCharts, {}),
|
||||
|
|
|
|||
|
|
@ -254,11 +254,7 @@ export default {
|
|||
return this.showApprovers || this.showSquashSetting;
|
||||
},
|
||||
showSquashSetting() {
|
||||
return (
|
||||
this.glFeatures.branchRuleSquashSettings &&
|
||||
!this.branch?.includes('*') &&
|
||||
!this.isAllProtectedBranchesRule
|
||||
); // Squash settings are not available for wildcards or All protected branches
|
||||
return !this.branch?.includes('*') && !this.isAllProtectedBranchesRule; // Squash settings are not available for wildcards or All protected branches
|
||||
},
|
||||
showEditSquashSetting() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -41,9 +41,6 @@ export default {
|
|||
const squashOptions = project?.branchRules?.nodes || [];
|
||||
return squashOptions.find((option) => option.name === this.name)?.squashOption;
|
||||
},
|
||||
skip() {
|
||||
return !this.glFeatures.branchRuleSquashSettings;
|
||||
},
|
||||
error(error) {
|
||||
createAlert({ message: error });
|
||||
},
|
||||
|
|
@ -170,7 +167,7 @@ export default {
|
|||
if (this.pushAccessLevels.total > 0) {
|
||||
approvalDetails.push(this.pushAccessLevelsText);
|
||||
}
|
||||
if (this.glFeatures.branchRuleSquashSettings && this.squashOption) {
|
||||
if (this.squashOption) {
|
||||
approvalDetails.push(this.squashSettingText);
|
||||
}
|
||||
return approvalDetails;
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
<script>
|
||||
// This component and it's instances should be removed on 4/15/25
|
||||
// See https://gitlab.com/gitlab-org/gitlab/-/issues/527721
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
|
||||
const DOCKER_HUB_KEY = 'docker_hub_rate_limits_dismissed';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlAlert,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isBannerDismissed: localStorage.getItem(DOCKER_HUB_KEY) === 'true',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleDismissBanner() {
|
||||
localStorage.setItem(DOCKER_HUB_KEY, 'true');
|
||||
this.isBannerDismissed = true;
|
||||
},
|
||||
},
|
||||
authenticatePath: helpPagePath('user/packages/dependency_proxy/_index', {
|
||||
anchor: 'authenticate-with-docker-hub',
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-alert
|
||||
v-if="!isBannerDismissed"
|
||||
variant="warning"
|
||||
:primary-button-text="__('Authenticate with Docker Hub')"
|
||||
:primary-button-link="$options.authenticatePath"
|
||||
:secondary-button-text="__('Learn more')"
|
||||
secondary-button-link="https://about.gitlab.com/blog/2025/03/24/prepare-now-docker-hub-rate-limits-will-impact-gitlab-ci-cd"
|
||||
@dismiss="handleDismissBanner"
|
||||
>
|
||||
{{
|
||||
__(
|
||||
'Docker Hub pull rate limits begin April 1, 2025 and might affect CI/CD pipelines that pull Docker images. To prevent pipeline failures, configure the GitLab Dependency Proxy to authenticate with Docker Hub.',
|
||||
)
|
||||
}}
|
||||
</gl-alert>
|
||||
</template>
|
||||
|
|
@ -10,6 +10,10 @@ module Projects
|
|||
push_frontend_feature_flag(:show_container_registry_tag_signatures, project)
|
||||
end
|
||||
|
||||
before_action only: [:index, :show] do
|
||||
push_frontend_feature_flag(:container_registry_immutable_tags, project)
|
||||
end
|
||||
|
||||
before_action :authorize_update_container_image!, only: [:destroy]
|
||||
|
||||
def index
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ module Projects
|
|||
before_action :authorize_admin_project!
|
||||
before_action do
|
||||
push_frontend_feature_flag(:edit_branch_rules, @project)
|
||||
push_frontend_feature_flag(:branch_rule_squash_settings, @project)
|
||||
end
|
||||
|
||||
feature_category :source_code_management
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ module Projects
|
|||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:edit_branch_rules, @project)
|
||||
push_frontend_feature_flag(:branch_rule_squash_settings, @project)
|
||||
push_frontend_ability(ability: :admin_project, resource: @project, user: current_user)
|
||||
push_frontend_ability(ability: :admin_protected_branch, resource: @project, user: current_user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,10 +25,6 @@ module Mutations
|
|||
def resolve(branch_rule_id:, squash_option:)
|
||||
branch_rule = authorized_find!(id: branch_rule_id)
|
||||
|
||||
if feature_disabled?(branch_rule.project)
|
||||
raise_resource_not_available_error! 'Squash options feature disabled'
|
||||
end
|
||||
|
||||
service_response = ::Projects::BranchRules::SquashOptions::UpdateService.new(
|
||||
branch_rule,
|
||||
squash_option: squash_option,
|
||||
|
|
@ -40,12 +36,6 @@ module Mutations
|
|||
errors: service_response.errors
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def feature_disabled?(project)
|
||||
Feature.disabled?(:branch_rule_squash_settings, project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ module Resolvers
|
|||
|
||||
# BranchRules for 'All branches' i.e. no associated ProtectedBranch
|
||||
def custom_branch_rules(args)
|
||||
return [] unless squash_settings_enabled?
|
||||
|
||||
[all_branches_rule]
|
||||
end
|
||||
|
||||
|
|
@ -37,10 +35,6 @@ module Resolvers
|
|||
def protected_branches
|
||||
apply_lookahead(project.all_protected_branches.sorted_by_name)
|
||||
end
|
||||
|
||||
def squash_settings_enabled?
|
||||
Feature.enabled?(:branch_rule_squash_settings, project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,8 +49,7 @@ module Types
|
|||
field :squash_option,
|
||||
type: ::Types::Projects::BranchRules::SquashOptionType,
|
||||
null: true,
|
||||
description: 'The default behavior for squashing in merge requests. ' \
|
||||
'Returns null if `branch_rule_squash_settings` feature flag is disabled.',
|
||||
description: 'Default behavior for squashing in merge requests. ',
|
||||
experiment: { milestone: '17.9' }
|
||||
field :updated_at,
|
||||
Types::TimeType,
|
||||
|
|
@ -58,8 +57,6 @@ module Types
|
|||
description: 'Timestamp of when the branch rule was last updated.'
|
||||
|
||||
def squash_option
|
||||
return unless ::Feature.enabled?(:branch_rule_squash_settings, branch_rule.project)
|
||||
|
||||
branch_rule.squash_option
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ module Types
|
|||
value 'PATH_ASC', 'Sort by path, ascending order.', value: :path_asc
|
||||
value 'PATH_DESC', 'Sort by path, descending order.', value: :path_desc
|
||||
|
||||
value 'FULL_PATH_ASC', 'Sort by full path, ascending order.', value: :full_path_asc
|
||||
value 'FULL_PATH_DESC', 'Sort by full path, descending order.', value: :full_path_desc
|
||||
|
||||
value 'REPOSITORY_SIZE_ASC', 'Sort by total repository size, ascending order.', value: :repository_size_asc
|
||||
value 'REPOSITORY_SIZE_DESC', 'Sort by total repository size, descending order.', value: :repository_size_desc
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ module Ci
|
|||
schedules_path: pipeline_schedules_path(project),
|
||||
settings_link: project_settings_ci_cd_path(project),
|
||||
timezone_data: timezone_data.to_json,
|
||||
user_role: current_user ? project.team.human_max_access(current_user.id) : nil
|
||||
can_set_pipeline_variables: Ability.allowed?(current_user, :set_pipeline_variables, project).to_s
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,28 +6,27 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def db_key_base(attribute)
|
||||
dynamic_encryption_key(:db_key_base, attribute)
|
||||
def db_key_base
|
||||
dynamic_encryption_key(:db_key_base)
|
||||
end
|
||||
|
||||
def db_key_base_32(attribute)
|
||||
dynamic_encryption_key(:db_key_base_32, attribute)
|
||||
def db_key_base_32
|
||||
dynamic_encryption_key(:db_key_base_32)
|
||||
end
|
||||
|
||||
def db_key_base_truncated(attribute)
|
||||
dynamic_encryption_key(:db_key_base_truncated, attribute)
|
||||
def db_key_base_truncated
|
||||
dynamic_encryption_key(:db_key_base_truncated)
|
||||
end
|
||||
|
||||
def dynamic_encryption_key(key_type, attribute)
|
||||
dynamic_encryption_key_for_operation(key_type, attr_encrypted_attributes[attribute][:operation])
|
||||
def dynamic_encryption_key(key_type)
|
||||
dynamic_encryption_key_for_operation(key_type)
|
||||
end
|
||||
|
||||
def dynamic_encryption_key_for_operation(key_type, operation)
|
||||
if operation == :encrypting
|
||||
Gitlab::Encryption::KeyProvider[key_type].encryption_key.secret
|
||||
else
|
||||
Gitlab::Encryption::KeyProvider[key_type].decryption_keys.map(&:secret)
|
||||
end
|
||||
def dynamic_encryption_key_for_operation(key_type)
|
||||
# We always use the encryption key, which is the only key defined since
|
||||
# we don't support multiple keys in attr_encrypted but only with
|
||||
# Active Record Encryption.
|
||||
Gitlab::Encryption::KeyProvider[key_type].encryption_key.secret
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -685,6 +685,18 @@ class Project < ApplicationRecord
|
|||
scope :sorted_by_stars_asc, -> { reorder(self.arel_table['star_count'].asc) }
|
||||
scope :sorted_by_path_asc, -> { reorder(self.arel_table['path'].asc) }
|
||||
scope :sorted_by_path_desc, -> { reorder(self.arel_table['path'].desc) }
|
||||
scope :sorted_by_full_path_asc, -> { order_by_full_path(:asc) }
|
||||
scope :sorted_by_full_path_desc, -> { order_by_full_path(:desc) }
|
||||
scope :order_by_full_path, ->(direction) do
|
||||
build_keyset_order_on_joined_column(
|
||||
scope: joins(:route),
|
||||
attribute_name: 'project_full_path',
|
||||
column: Route.arel_table[:path],
|
||||
direction: direction,
|
||||
nullable: :nulls_first
|
||||
)
|
||||
end
|
||||
|
||||
# Sometimes queries (e.g. using CTEs) require explicit disambiguation with table name
|
||||
scope :projects_order_id_asc, -> { reorder(self.arel_table['id'].asc) }
|
||||
scope :projects_order_id_desc, -> { reorder(self.arel_table['id'].desc) }
|
||||
|
|
@ -1118,6 +1130,8 @@ class Project < ApplicationRecord
|
|||
when 'latest_activity_asc' then sorted_by_updated_asc
|
||||
when 'path_desc'then sorted_by_path_desc
|
||||
when 'path_asc' then sorted_by_path_asc
|
||||
when 'full_path_desc'then sorted_by_full_path_desc
|
||||
when 'full_path_asc' then sorted_by_full_path_asc
|
||||
when 'stars_desc' then sorted_by_stars_desc
|
||||
when 'stars_asc' then sorted_by_stars_asc
|
||||
else
|
||||
|
|
@ -3257,10 +3271,10 @@ class Project < ApplicationRecord
|
|||
ci_cd_settings.restrict_user_defined_variables?
|
||||
end
|
||||
|
||||
def override_pipeline_variables_allowed?(access_level)
|
||||
def override_pipeline_variables_allowed?(access_level, user)
|
||||
return false unless ci_cd_settings
|
||||
|
||||
ci_cd_settings.override_pipeline_variables_allowed?(access_level)
|
||||
ci_cd_settings.override_pipeline_variables_allowed?(access_level, user)
|
||||
end
|
||||
|
||||
def ci_push_repository_for_job_token_allowed?
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class ProjectCiCdSetting < ApplicationRecord
|
|||
Gitlab::CurrentSettings.current_application_settings.keep_latest_artifact? && keep_latest_artifact?
|
||||
end
|
||||
|
||||
def override_pipeline_variables_allowed?(role_access_level)
|
||||
def override_pipeline_variables_allowed?(role_access_level, user)
|
||||
return true unless restrict_user_defined_variables?
|
||||
|
||||
project_minimum_access_level = pipeline_variables_minimum_override_role_for_database
|
||||
|
|
@ -77,7 +77,7 @@ class ProjectCiCdSetting < ApplicationRecord
|
|||
|
||||
role_project_minimum_access_level = role_map_pipeline_variables_minimum_override_role[project_minimum_access_level]
|
||||
|
||||
role_access_level >= role_project_minimum_access_level
|
||||
role_access_level >= role_project_minimum_access_level || user&.can_admin_all_resources?
|
||||
end
|
||||
|
||||
def pipeline_variables_minimum_override_role=(value)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ module Users
|
|||
namespace_storage_limit_alert_warning_threshold: 56, # EE-only
|
||||
namespace_storage_limit_alert_alert_threshold: 57, # EE-only
|
||||
namespace_storage_limit_alert_error_threshold: 58, # EE-only
|
||||
project_quality_summary_feedback: 59, # EE-only
|
||||
# 60 removed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154140
|
||||
new_top_level_group_alert: 61,
|
||||
# 62, removed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131314
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
condition(:user_defined_variables_allowed) do
|
||||
@subject.override_pipeline_variables_allowed?(team_access_level)
|
||||
@subject.override_pipeline_variables_allowed?(team_access_level, @user)
|
||||
end
|
||||
|
||||
condition(:push_repository_for_job_token_allowed) do
|
||||
|
|
|
|||
|
|
@ -10,6 +10,5 @@
|
|||
failed_pipelines_link: project_pipelines_path(@project, page: '1', scope: 'all', status: 'failed'),
|
||||
coverage_chart_path: charts_project_graph_path(@project, @project.default_branch),
|
||||
test_runs_empty_state_image_path: image_path('illustrations/empty-state/empty-pipeline-md.svg'),
|
||||
project_quality_summary_feedback_image_path: image_path('illustrations/chat-sm.svg'),
|
||||
project_branch_count: @project.repository_exists? ? @project.repository.branch_count : 0,
|
||||
default_branch: @project.default_branch } }
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
%h1.gl-sr-only= @breadcrumb_title
|
||||
|
||||
#js-docker-hub-rate-limits-alert
|
||||
|
||||
- if can?(current_user, :admin_pipeline, @project)
|
||||
= render ::Layouts::SettingsBlockComponent.new(_("General pipelines"),
|
||||
id: 'js-general-pipeline-settings',
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
= render 'shared/issuable/form/metadata', issuable: issuable, form: form, project: project, presenter: presenter
|
||||
|
||||
= render 'shared/issuable/form/merge_params', issuable: issuable, project: project
|
||||
= render 'shared/issuable/form/merge_params', issuable: issuable
|
||||
|
||||
= render 'shared/issuable/form/contribution', issuable: issuable, form: form
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@
|
|||
= _("Squash commits when merge request is accepted.")
|
||||
= link_to sprite_icon('question-o'), help_page_path('user/project/merge_requests/squash_and_merge.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
- c.with_help_text do
|
||||
-# When we remove the feature flag we should also remove the local_assigns[:project]
|
||||
- if Feature.enabled?(:branch_rule_squash_settings, local_assigns.fetch(:project))
|
||||
= _('Required in this branch.')
|
||||
- else
|
||||
= _('Required in this project.')
|
||||
= _('Required in this branch.')
|
||||
- else
|
||||
= hidden_field_tag 'merge_request[squash]', '0', id: nil
|
||||
= render Pajamas::CheckboxTagComponent.new(name: 'merge_request[squash]', checked: merge_request_squash_option?(merge_request), value: '1', checkbox_options: { class: 'js-form-update' }) do |c|
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ module Projects
|
|||
project_id = last_processed_project_id
|
||||
|
||||
Project.where('projects.id > ?', project_id).each_batch(of: 100) do |batch| # rubocop: disable CodeReuse/ActiveRecord
|
||||
inactive_projects = batch.inactive.without_deleted
|
||||
inactive_projects = batch.inactive.not_aimed_for_deletion
|
||||
|
||||
inactive_projects.each do |project|
|
||||
if over_time?
|
||||
|
|
@ -42,9 +42,10 @@ module Projects
|
|||
with_context(project: project, user: admin_bot) do
|
||||
deletion_warning_email_sent_on = notified_inactive_projects["project:#{project.id}"]
|
||||
|
||||
if send_deletion_warning_email?(deletion_warning_email_sent_on, project)
|
||||
send_notification(project, admin_bot)
|
||||
elsif deletion_warning_email_sent_on && delete_due_to_inactivity?(deletion_warning_email_sent_on)
|
||||
if deletion_warning_email_sent_on.blank?
|
||||
send_notification(project)
|
||||
log_audit_event(project, admin_bot)
|
||||
elsif grace_period_is_over?(deletion_warning_email_sent_on)
|
||||
Gitlab::InactiveProjectsDeletionWarningTracker.new(project.id).reset
|
||||
delete_project(project, admin_bot)
|
||||
end
|
||||
|
|
@ -58,6 +59,10 @@ module Projects
|
|||
|
||||
private
|
||||
|
||||
def grace_period_is_over?(deletion_warning_email_sent_on)
|
||||
deletion_warning_email_sent_on < grace_months_after_deletion_notification.ago
|
||||
end
|
||||
|
||||
def grace_months_after_deletion_notification
|
||||
strong_memoize(:grace_months_after_deletion_notification) do
|
||||
(::Gitlab::CurrentSettings.inactive_projects_delete_after_months -
|
||||
|
|
@ -65,26 +70,26 @@ module Projects
|
|||
end
|
||||
end
|
||||
|
||||
def send_deletion_warning_email?(deletion_warning_email_sent_on, project)
|
||||
deletion_warning_email_sent_on.blank?
|
||||
end
|
||||
|
||||
def delete_due_to_inactivity?(deletion_warning_email_sent_on)
|
||||
deletion_warning_email_sent_on < grace_months_after_deletion_notification.ago
|
||||
end
|
||||
|
||||
def deletion_date
|
||||
grace_months_after_deletion_notification.from_now.to_date.to_s
|
||||
end
|
||||
|
||||
def delete_project(project, user)
|
||||
::Projects::DestroyService.new(project, user, {}).async_execute
|
||||
if project.adjourned_deletion?
|
||||
::Projects::MarkForDeletionService.new(project, user, {}).execute
|
||||
else
|
||||
::Projects::DestroyService.new(project, user, {}).async_execute
|
||||
end
|
||||
end
|
||||
|
||||
def send_notification(project, user)
|
||||
def send_notification(project)
|
||||
::Projects::InactiveProjectsDeletionNotificationWorker.perform_async(project.id, deletion_date)
|
||||
end
|
||||
|
||||
def log_audit_event(_project, _user)
|
||||
# Defined in EE
|
||||
end
|
||||
|
||||
def over_time?
|
||||
(::Gitlab::Metrics::System.monotonic_time - @start_time) > MAX_RUN_TIME
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
name: branch_rule_squash_settings
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/498701
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173991
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/506542
|
||||
milestone: '17.7'
|
||||
group: group::source code
|
||||
type: beta
|
||||
default_enabled: true
|
||||
|
|
@ -4,6 +4,6 @@ feature_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/3
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147101
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/456667
|
||||
milestone: '17.0'
|
||||
group: group::scalability
|
||||
group: group::durability
|
||||
type: ops
|
||||
default_enabled: false
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128706
|
|||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/421499
|
||||
milestone: '16.3'
|
||||
type: ops
|
||||
group: group::scalability
|
||||
group: group::durability
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ feature_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/work_ite
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170895
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/501502
|
||||
milestone: '17.6'
|
||||
group: group::scalability
|
||||
group: group::durability
|
||||
type: ops
|
||||
default_enabled: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ feature_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/2
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145495
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/444293
|
||||
milestone: '16.10'
|
||||
group: group::scalability
|
||||
group: group::durability
|
||||
type: ops
|
||||
default_enabled: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/500768#note_217
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170751
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/501156
|
||||
milestone: '17.6'
|
||||
group: group::scalability
|
||||
group: group::durability
|
||||
type: ops
|
||||
default_enabled: false
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ name: remote_mirror_no_delay
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92093
|
||||
milestone: '15.2'
|
||||
type: ops
|
||||
group: group::scalability
|
||||
group: group::durability
|
||||
default_enabled: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ feature_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/3
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168335
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/503486
|
||||
milestone: '17.6'
|
||||
group: group::scalability
|
||||
group: group::durability
|
||||
type: ops
|
||||
default_enabled: false
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
- title: "Rename options to skip GitGuardian secret detection" # The name of the feature to be deprecated
|
||||
announcement_milestone: "17.3" # The milestone when this feature was first announced as deprecated.
|
||||
removal_milestone: "Pending" # The milestone when this feature is planned to be removed
|
||||
breaking_change: true
|
||||
- title: "Rename options to skip GitGuardian secret detection"
|
||||
announcement_milestone: "17.3"
|
||||
removal_milestone: "18.0"
|
||||
breaking_change: false
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
The options to skip GitGuardian secret detection, `[skip secret detection]` and `secret_detection.skip_all`, are deprecated and will be removed in GitLab 18.0. You should use `[skip secret push protection]` and `secret_push_protection.skip_all` instead.
|
||||
# The following items are not published on the docs page, but may be used in the future.
|
||||
stage: secure # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
|
||||
tiers: premium, ultimate # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/470119 # (optional) This is a link to the deprecation issue in GitLab
|
||||
documentation_url: https://docs.gitlab.com/user/project/integrations/git_guardian/#skip-secret-detection # (optional) This is a link to the current documentation page
|
||||
image_url: # (optional) This is a link to a thumbnail image depicting the feature
|
||||
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
|
||||
The options to skip GitGuardian secret detection, `[skip secret detection]` and `secret_detection.skip_all`, are deprecated. You should use `[skip secret push protection]` and `secret_push_protection.skip_all` instead.
|
||||
stage: secure
|
||||
tiers: premium, ultimate
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/470119
|
||||
documentation_url: https://docs.gitlab.com/user/project/integrations/git_guardian/#skip-secret-detection
|
||||
|
|
|
|||
|
|
@ -22202,7 +22202,7 @@ Branch rules configured for a rule target.
|
|||
| <a id="branchruleisprotected"></a>`isProtected` | [`Boolean!`](#boolean) | Check if the branch rule protects access for the branch. |
|
||||
| <a id="branchrulematchingbranchescount"></a>`matchingBranchesCount` | [`Int!`](#int) | Number of existing branches that match the branch rule. |
|
||||
| <a id="branchrulename"></a>`name` | [`String!`](#string) | Name of the branch rule target. Includes wildcards. |
|
||||
| <a id="branchrulesquashoption"></a>`squashOption` {{< icon name="warning-solid" >}} | [`SquashOption`](#squashoption) | **Introduced** in GitLab 17.9. **Status**: Experiment. The default behavior for squashing in merge requests. Returns null if `branch_rule_squash_settings` feature flag is disabled. |
|
||||
| <a id="branchrulesquashoption"></a>`squashOption` {{< icon name="warning-solid" >}} | [`SquashOption`](#squashoption) | **Introduced** in GitLab 17.9. **Status**: Experiment. Default behavior for squashing in merge requests. |
|
||||
| <a id="branchruleupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp of when the branch rule was last updated. |
|
||||
|
||||
### `BurnupChartDailyTotals`
|
||||
|
|
@ -43985,6 +43985,8 @@ Values for sorting projects.
|
|||
| <a id="namespaceprojectsortcontainer_registry_size_asc"></a>`CONTAINER_REGISTRY_SIZE_ASC` | Sort by total container registry size, ascending order. |
|
||||
| <a id="namespaceprojectsortcontainer_registry_size_desc"></a>`CONTAINER_REGISTRY_SIZE_DESC` | Sort by total container registry size, descending order. |
|
||||
| <a id="namespaceprojectsortexcess_repo_storage_size_desc"></a>`EXCESS_REPO_STORAGE_SIZE_DESC` | Sort by excess repository storage size, descending order. |
|
||||
| <a id="namespaceprojectsortfull_path_asc"></a>`FULL_PATH_ASC` | Sort by full path, ascending order. |
|
||||
| <a id="namespaceprojectsortfull_path_desc"></a>`FULL_PATH_DESC` | Sort by full path, descending order. |
|
||||
| <a id="namespaceprojectsortlfs_objects_size_asc"></a>`LFS_OBJECTS_SIZE_ASC` | Sort by total LFS object size, ascending order. |
|
||||
| <a id="namespaceprojectsortlfs_objects_size_desc"></a>`LFS_OBJECTS_SIZE_DESC` | Sort by total LFS object size, descending order. |
|
||||
| <a id="namespaceprojectsortpackages_size_asc"></a>`PACKAGES_SIZE_ASC` | Sort by total package size, ascending order. |
|
||||
|
|
@ -45031,7 +45033,6 @@ Name of the feature that the callout is for.
|
|||
| <a id="usercalloutfeaturenameenumproduct_analytics_dashboard_feedback"></a>`PRODUCT_ANALYTICS_DASHBOARD_FEEDBACK` | Callout feature name for product_analytics_dashboard_feedback. |
|
||||
| <a id="usercalloutfeaturenameenumproduct_usage_data_collection_changes"></a>`PRODUCT_USAGE_DATA_COLLECTION_CHANGES` | Callout feature name for product_usage_data_collection_changes. |
|
||||
| <a id="usercalloutfeaturenameenumprofile_personal_access_token_expiry"></a>`PROFILE_PERSONAL_ACCESS_TOKEN_EXPIRY` | Callout feature name for profile_personal_access_token_expiry. |
|
||||
| <a id="usercalloutfeaturenameenumproject_quality_summary_feedback"></a>`PROJECT_QUALITY_SUMMARY_FEEDBACK` | Callout feature name for project_quality_summary_feedback. |
|
||||
| <a id="usercalloutfeaturenameenumproject_repository_limit_alert_warning_threshold"></a>`PROJECT_REPOSITORY_LIMIT_ALERT_WARNING_THRESHOLD` | Callout feature name for project_repository_limit_alert_warning_threshold. |
|
||||
| <a id="usercalloutfeaturenameenumregistration_enabled_callout"></a>`REGISTRATION_ENABLED_CALLOUT` | Callout feature name for registration_enabled_callout. |
|
||||
| <a id="usercalloutfeaturenameenumsecurity_configuration_devops_alert"></a>`SECURITY_CONFIGURATION_DEVOPS_ALERT` | Callout feature name for security_configuration_devops_alert. |
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ gdk restart
|
|||
- If you need to set up a Duo Pro add-on instead, run this Rake task:
|
||||
|
||||
```shell
|
||||
GITLAB_SIMULATE_SAAS=1 bundle exec 'rake gitlab:duo:setup[pro]'
|
||||
GITLAB_SIMULATE_SAAS=1 bundle exec 'rake gitlab:duo:setup[duo_pro]'
|
||||
```
|
||||
|
||||
### Pros
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ Also, keep the following guidance in mind:
|
|||
- For [UI text](#ui-text), allow for up to 30% expansion and contraction in translation.
|
||||
To see how much a string expands or contracts in another language, paste the string
|
||||
into [Google Translate](https://translate.google.com/) and review the results.
|
||||
You can ask a colleague who speaks the language to verify if the translation is clear.
|
||||
Ask a colleague who speaks the language to verify if the translation is clear.
|
||||
|
||||
## Markdown
|
||||
|
||||
|
|
@ -179,7 +179,7 @@ Instead of:
|
|||
- Application code is written by the developer.
|
||||
|
||||
Sometimes, using `GitLab` as the subject can be awkward. For example, `GitLab exports the report`.
|
||||
In this case, you can use passive voice instead. For example, `The report is exported`.
|
||||
In this case, use passive voice instead. For example, `The report is exported`.
|
||||
|
||||
### Customer perspective
|
||||
|
||||
|
|
@ -211,7 +211,7 @@ without the addition of sales or marketing text.
|
|||
Instead, focus on facts and achievable goals. Be specific. For example:
|
||||
|
||||
- The build time can decrease when you use this feature.
|
||||
- You can use this feature to save time when you create a project. The API creates the file and you
|
||||
- Use this feature to save time when you create a project. The API creates the file and you
|
||||
do not need to manually intervene.
|
||||
|
||||
### Self-referential writing
|
||||
|
|
@ -303,8 +303,8 @@ For screenshots:
|
|||
1. Close the dialog. All of the user data in the web page should now be replaced with the example data you entered.
|
||||
1. Take the screenshot.
|
||||
|
||||
- Alternatively, you can create example accounts in a test environment, and take the screenshot there.
|
||||
- If you can't easily reproduce the environment, you can blur the user data by using an image editing tool like Preview on macOS.
|
||||
- Alternatively, create example accounts in a test environment, and take the screenshot there.
|
||||
- If you can't easily reproduce the environment, blur the user data by using an image editing tool like Preview on macOS.
|
||||
|
||||
### Fake URLs
|
||||
|
||||
|
|
@ -320,7 +320,7 @@ There may be times where a token is needed to demonstrate an API call using
|
|||
cURL or a variable used in CI. It is strongly advised not to use real tokens in
|
||||
documentation even if the probability of a token being exploited is low.
|
||||
|
||||
You can use these fake tokens as examples:
|
||||
Use these fake tokens as examples:
|
||||
|
||||
| Token type | Token value |
|
||||
|:----------------------|:-------------------------------------------------------------------|
|
||||
|
|
@ -436,7 +436,7 @@ When spacing content:
|
|||
|
||||
- Use one space between sentences. (Use of more than one space is tested in [`SentenceSpacing.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab_base/SentenceSpacing.yml).)
|
||||
- Do not use non-breaking spaces. Use standard spaces instead. (Tested in [`lint-doc.sh`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/lint-doc.sh).)
|
||||
- Do not use tabs for indentation. Use spaces instead. You can configure your code editor to output spaces instead of tabs when pressing the <kbd>Tab</kbd> key.
|
||||
- Do not use tabs for indentation. Use spaces instead. Consider configuring your code editor to output spaces instead of tabs when pressing the <kbd>Tab</kbd> key.
|
||||
|
||||
Do not use these punctuation characters:
|
||||
|
||||
|
|
@ -612,7 +612,7 @@ Use lists to present information in a format that is easier to scan.
|
|||
For example:
|
||||
|
||||
```markdown
|
||||
You can:
|
||||
To complete a task:
|
||||
|
||||
- Do this thing.
|
||||
- Do this other thing.
|
||||
|
|
@ -650,8 +650,8 @@ These things are imported:
|
|||
|
||||
### Nesting inside a list item
|
||||
|
||||
You can nest items under a list item, so they render with the same
|
||||
indentation as the list item. You can do this with:
|
||||
The following items can be nested under a list item, so they render with the same
|
||||
indentation as the list item:
|
||||
|
||||
- [Code blocks](#code-blocks)
|
||||
- [Blockquotes](#blockquotes)
|
||||
|
|
@ -721,7 +721,7 @@ To keep tables accessible and scannable, tables should not have any
|
|||
empty cells. If there is no otherwise meaningful value for a cell, consider entering
|
||||
**N/A** for 'not applicable' or **None**.
|
||||
|
||||
To help keep tables easier to maintain, you can:
|
||||
To make tables easier to maintain:
|
||||
|
||||
- Add additional spaces to make the column widths consistent. For example:
|
||||
|
||||
|
|
@ -767,7 +767,8 @@ To enable the setting:
|
|||
To format a table with this extension, select the entire table, right-click the selection,
|
||||
and select **Format Selection With**. Select **Markdown Table Formatter** in the VS Code Command Palette.
|
||||
|
||||
Alternatively, if you use Sublime Text you can try the [Markdown Table Formatter](https://packagecontrol.io/packages/Markdown%20Table%20Formatter)
|
||||
If you use Sublime Text, try the
|
||||
[Markdown Table Formatter](https://packagecontrol.io/packages/Markdown%20Table%20Formatter)
|
||||
plugin, but it does not have a **Follow header row length** setting.
|
||||
|
||||
### Updates to existing tables
|
||||
|
|
@ -848,7 +849,7 @@ The table and footnotes would render as follows:
|
|||
##### Five or more footnotes
|
||||
|
||||
If you have five or more footnotes that you cannot include in the table itself,
|
||||
you can use consecutive numbers for the list items.
|
||||
use consecutive numbers for the list items.
|
||||
If you use consecutive numbers, you must disable Markdown rule `029`:
|
||||
|
||||
```markdown
|
||||
|
|
@ -909,7 +910,7 @@ To link to another documentation (`.md`) file in the same repository:
|
|||
- Put the entire link on a single line, even if the link is very long. ([Vale](../testing/vale.md) rule: [`MultiLineLinks.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab_base/MultiLineLinks.yml)).
|
||||
|
||||
To link to a file outside of the documentation files, for example to link from development
|
||||
documentation to a specific code file, you can:
|
||||
documentation to a specific code file:
|
||||
|
||||
- Use a full URL. For example: ``[`app/views/help/show.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/views/help/show.html.haml)``
|
||||
- (Optional) Use a full URL with a specific ref. For example: ``[`app/views/help/show.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/6d01aa9f1cfcbdfa88edf9d003bd073f1a6fff1d/app/views/help/show.html.haml)``
|
||||
|
|
@ -954,7 +955,7 @@ For example:
|
|||
- `For more information, see [merge requests](link.md).`
|
||||
- `To create a review app, see [review apps](link.md).`
|
||||
|
||||
You can expand on this text by using phrases like
|
||||
To expand on this text, use phrases like
|
||||
`For more information about this feature, see...`
|
||||
|
||||
Do not use the following constructions:
|
||||
|
|
@ -1330,7 +1331,7 @@ If you use macOS and want all screenshots to be compressed automatically, read
|
|||
[One simple trick to make your screenshots 80% smaller](https://about.gitlab.com/blog/2020/01/30/simple-trick-for-smaller-screenshots/).
|
||||
|
||||
GitLab has a [Ruby script](https://gitlab.com/gitlab-org/gitlab/-/blob/master/bin/pngquant)
|
||||
that you can use to simplify the manual process. In the root directory of your local
|
||||
to simplify the manual process. In the root directory of your local
|
||||
copy of `https://gitlab.com/gitlab-org/gitlab`, run in a terminal:
|
||||
|
||||
- Before compressing, if you want, check that all documentation PNG images have
|
||||
|
|
@ -1556,8 +1557,8 @@ flowchart TD
|
|||
|
||||
Use either the [Draw.io](https://draw.io) web application or the (unofficial)
|
||||
VS Code [Draw.io Integration](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio)
|
||||
extension to create the diagram. Each tool provides the same diagram editing experience, however the web
|
||||
application provides example diagrams that you can edit to suit your needs.
|
||||
extension to create the diagram. Each tool provides the same diagram editing experience, but the web
|
||||
application provides editable example diagrams.
|
||||
|
||||
##### Use the web application
|
||||
|
||||
|
|
@ -1623,7 +1624,7 @@ You can use icons from the [GitLab SVG library](https://gitlab-org.gitlab.io/git
|
|||
directly in the documentation. For example, `{{</* icon name="tanuki" */>}}` renders as: {{< icon name="tanuki" >}}.
|
||||
|
||||
In most cases, you should avoid using the icons in text.
|
||||
However, you can use an icon when hover text is the only
|
||||
However, use the icon when hover text is the only
|
||||
available way to describe a UI element. For example, **Delete** or **Edit** buttons
|
||||
often have hover text only.
|
||||
|
||||
|
|
@ -1637,7 +1638,7 @@ Do not use words to describe the icon:
|
|||
- Avoid: `Select **Erase job log** (the trash icon).`
|
||||
- Use instead: `Select **Erase job log** ({{</* icon name="remove" */>}}).` This generates as: Select **Erase job log** ({{< icon name="remove" >}}).
|
||||
|
||||
When the button doesn't have any hover text, you can describe the icon.
|
||||
When the button doesn't have any hover text, describe the icon.
|
||||
Follow up by creating a
|
||||
[UX bug issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Bug)
|
||||
to add hover text to the button to improve accessibility.
|
||||
|
|
@ -1848,7 +1849,7 @@ It renders on the GitLab documentation site as:
|
|||
|
||||
> This is a blockquote.
|
||||
|
||||
If the text spans multiple lines, you can split them.
|
||||
If the text spans multiple lines, split them.
|
||||
|
||||
For multiple paragraphs, use the symbol `>` before every line:
|
||||
|
||||
|
|
@ -1872,7 +1873,7 @@ It renders on the GitLab documentation site as:
|
|||
|
||||
## Tabs
|
||||
|
||||
On the documentation site, you can format text so it's displayed as tabs.
|
||||
On the documentation site, you can format text to display as tabs.
|
||||
|
||||
{{< alert type="warning" >}}
|
||||
|
||||
|
|
@ -2041,8 +2042,7 @@ GitLab, or restart GitLab. In this case:
|
|||
- The final step to reconfigure or restart GitLab can be used verbatim since it's
|
||||
the same every time.
|
||||
|
||||
When describing a configuration edit, you can use and edit to your liking the
|
||||
following snippet:
|
||||
When describing a configuration edit, use this snippet, editing it as needed:
|
||||
|
||||
````markdown
|
||||
{{</* tabs */>}}
|
||||
|
|
|
|||
|
|
@ -193,3 +193,11 @@ This allows you to reveal existing RuboCop exceptions during your daily work cyc
|
|||
Define `Include`s and permanent `Exclude`s in `.rubocop.yml` instead of `.rubocop_todo/**/*.yml`.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
## RuboCop documentation
|
||||
|
||||
When creating internal RuboCop rules, these should include RDoc style docs.
|
||||
|
||||
These docs are used to generate a static site using Hugo, and are published to <https://gitlab-org.gitlab.io/gitlab/rubocop-docs/>.
|
||||
|
||||
The site includes all the internal cops from the `gitlab` and `gitlab-styles` projects, along with "good" and "bad" examples.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Software Supply Chain Security
|
||||
group: Authentication
|
||||
stage: Plan
|
||||
group: Project Management
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
title: Proxying assets
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1722,6 +1722,22 @@ In 18.0 we are removing the `duoProAssignedUsersCount` GraphQL field. Users may
|
|||
|
||||
</div>
|
||||
|
||||
<div class="deprecation " data-milestone="18.0">
|
||||
|
||||
### Rename options to skip GitGuardian secret detection
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
||||
- Announced in GitLab <span class="milestone">17.3</span>
|
||||
- Removal in GitLab <span class="milestone">18.0</span>
|
||||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/470119).
|
||||
|
||||
</div>
|
||||
|
||||
The options to skip GitGuardian secret detection, `[skip secret detection]` and `secret_detection.skip_all`, are deprecated. You should use `[skip secret push protection]` and `secret_push_protection.skip_all` instead.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation breaking-change" data-milestone="18.0">
|
||||
|
||||
### Replace `add_on_purchase` GraphQL field with `add_on_purchases`
|
||||
|
|
@ -7859,26 +7875,6 @@ Following [new guidance](https://docs.gitlab.com/development/api_styleguide/#wha
|
|||
|
||||
<div class="deprecation breaking-change">
|
||||
|
||||
### Rename options to skip GitGuardian secret detection
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
||||
- Announced in GitLab <span class="milestone">17.3</span>
|
||||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/470119).
|
||||
|
||||
</div>
|
||||
{{< alert type="note" >}}
|
||||
|
||||
This change has been removed from its original milestone and is being reassessed.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
The options to skip GitGuardian secret detection, `[skip secret detection]` and `secret_detection.skip_all`, are deprecated and will be removed in GitLab 18.0. You should use `[skip secret push protection]` and `secret_push_protection.skip_all` instead.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation breaking-change">
|
||||
|
||||
### SAST jobs no longer use global cache settings
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
|
|
|||
|
|
@ -2,35 +2,78 @@
|
|||
stage: Application Security Testing
|
||||
group: Dynamic Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
title: Cross-site Scripting
|
||||
title: Cross Site Scripting
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Reflected Cross-site Scripting (XSS) occurs when malicious scripts are injected into web applications
|
||||
through request parameters that are immediately returned to a user without proper sanitization.
|
||||
Unlike stored Cross-site Scripting attacks, reflected XSS are not persistent in the application's
|
||||
database but are "reflected" back in the immediate response.
|
||||
Cross Site Scripting (XSS) is an attack which exploits a web application or system to treat user input
|
||||
as markup or script code. It is important to encode the data depending on the specific context it
|
||||
is used in. There are at least six context types:
|
||||
|
||||
- Inside HTML tags `<div>context 1</div>`
|
||||
- Inside attributes: ```<div class="context 2"></div>```
|
||||
- Inside event attributes ```<button onclick="context 3">button</button>```
|
||||
- Inside script blocks: ```<script>var x = "context 4"</script>```
|
||||
- Unsafe element HTML assignment: ```element.innerHTML = "context 5"```
|
||||
- Inside URLs: ```<iframe src="context 6"></iframe><a href="context 6">link</a>```
|
||||
|
||||
Script blocks alone can be encoded in multiple ways. Exercise caution if user
|
||||
input must be written outside script tags.
|
||||
|
||||
## Remediation
|
||||
|
||||
Input validation: implement strict input validation for all user-controlled data. Use allowlist approaches
|
||||
rather than denylists when validating input. Validate data type, length, and range as appropriate.
|
||||
User input displayed in the application must be encoded, sanitized, or validated
|
||||
so it isn't treated as HTML or executed as JavaScript code. Be careful not to
|
||||
mix server-side templating with client-side templating, because the server-side doesn't encode
|
||||
text like `{{ 7*7 }}`, which might execute client-side features.
|
||||
|
||||
Output encoding: apply context-specific output encoding when rendering user input. Use HTML entity
|
||||
encoding for HTML contexts. Use JavaScript string escaping for JS context. CSS hex encoding should be
|
||||
used for style attributes.
|
||||
Do not encode user input before inserting it into a data store. The data must be
|
||||
encoded based on its output context. It is much safer to force the displaying system to
|
||||
handle the encoding.
|
||||
|
||||
Content Security Policy: implement a strict CSP that specifies trusted sources for scripts and other
|
||||
resources. User `script-src 'self'` to restrict execution to same-origin source. Consider using nonces
|
||||
or hashes for inline scripts when necessary.
|
||||
Consider using built-in framework capabilities for automatically encoding user input. If you can't
|
||||
automatically encode input, be careful to use the proper output encoding. The following recommendations
|
||||
are a best effort, and might not work in all circumstances.
|
||||
|
||||
Framework Protections: leverage built-in XSS protections in modern frameworks. Use template engines
|
||||
that automatically escape output. Avoid unsafe methods that bypass framework protections
|
||||
(for example, `innerHTML`, `dangerouslySetInnerHTML`).
|
||||
|
||||
HTTP Headers: set X-XSS-Protection headers for legacy browsers. Implement X-Content-Type-Options: nosniff.
|
||||
Use Strict-Transport-Security to enforce HTTPS.
|
||||
- Encode the following inside HTML tags, *excluding* `script`:
|
||||
- `<` to `<`
|
||||
- `>` to `>`
|
||||
- `'` to `'`
|
||||
- `"` to `"`
|
||||
- `=` to `=`
|
||||
- Encode the following inside attributes, *excluding* event attributes:
|
||||
- `<` to `<`
|
||||
- `>` to `>`
|
||||
- `'` to `'`
|
||||
- `"` to `"`
|
||||
- `=` to `=`
|
||||
- Encode the following inside event attributes, script blocks, and unsafe HTML assignment:
|
||||
- Literal tab (`\t`) to `\\t`
|
||||
- Literal new line (`\n`) to `\\n`
|
||||
- Literal vertical tab (`\v`) to `\u000b`
|
||||
- Literal form feed (`\f`) to `\\f`
|
||||
- Literal carriage return (`\r`) to `\\r`
|
||||
- Literal equal sign (`=`) to `\u0061`
|
||||
- Literal back tick (`\`) to `\u0060`
|
||||
- Literal double quote (`"`) to `\u0022`
|
||||
- Literal ampersand (`&`) to `\u0026`
|
||||
- Literal single quote (`'`) to `\u0027`
|
||||
- Literal plus symbol (`+`) to `\u002b`
|
||||
- Literal forward slash (`/`) to `\/`
|
||||
- Literal less than symbol (`<`) to `\u003c`
|
||||
- Literal greater than symbol (`>`) to `\u003e`
|
||||
- Literal open parenthesis (`(`) to `\u0028`
|
||||
- Literal close parenthesis (`)`) to `\u0029`
|
||||
- Literal open bracket (`[`) to `\u005b`
|
||||
- Literal close bracket (`]`) to `\u005d`
|
||||
- Literal open brace (`{`) to `\u007b`
|
||||
- Literal close brace (`}`) to `\u007d`
|
||||
- Literal back slash (`\`) to `\\`
|
||||
|
||||
This list is not exhaustive. You might need to encode additional characters depending on context.
|
||||
- Inside URLs:
|
||||
- Never allow user input to be printed in URLs. Attackers could inject `javascript:...` code or malicious links.
|
||||
|
||||
## Details
|
||||
|
||||
|
|
@ -40,5 +83,5 @@ Use Strict-Transport-Security to enforce HTTPS.
|
|||
|
||||
## Links
|
||||
|
||||
- [OWASP](https://owasp.org/www-community/attacks/xss/)
|
||||
- [CWE](https://cwe.mitre.org/data/definitions/79.html)
|
||||
- [Cross-site Scripting - Wikipedia](https://en.wikipedia.org/wiki/Cross-site_scripting)
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ scan for vulnerabilities in the site under test.
|
|||
| [611.1](611.1.md) | External XML Entity Injection (XXE) | High | Active |
|
||||
| [74.1](74.1.md) | XSLT Injection | High | Active |
|
||||
| [78.1](78.1.md) | OS Command Injection | High | Active |
|
||||
| [79.1](79.1.md) | Cross-site Scripting | High | Active |
|
||||
| [79.1](79.1.md) | Cross Site Scripting | High | Active |
|
||||
| [89.1](89.1.md) | SQL Injection | High | Active |
|
||||
| [917.1](917.1.md) | Expression Language Injection | High | Active |
|
||||
| [918.1](918.1.md) | Server-Side Request Forgery | High | Active |
|
||||
|
|
|
|||
|
|
@ -296,7 +296,6 @@ Validating a site is required to run an active scan.
|
|||
Prerequisites:
|
||||
|
||||
- A runner must be available in the project to run a validation job.
|
||||
- The GitLab server's certificate must be trusted and must not use a self-signed certificate.
|
||||
|
||||
To validate a site profile:
|
||||
|
||||
|
|
|
|||
|
|
@ -42,14 +42,43 @@ Behavioral testing tools include:
|
|||
- API security testing: Test your application's API for known attacks and vulnerabilities to input.
|
||||
- Coverage-guided fuzz testing: Test your application for unexpected behavior.
|
||||
|
||||
## Lifecycle coverage
|
||||
## Early detection
|
||||
|
||||
You should enable vulnerability detection from before the first commit through to when your
|
||||
application can be deployed and run. Early detection has many benefits, including easier and quicker
|
||||
remediation.
|
||||
Enable GitLab application security scanning tools from before the first commit. Early detection
|
||||
provides benefits such as easier, quicker, and cheaper remediation, compared to detection later in
|
||||
the software development lifecycle. GitLab provides developers immediate feedback of security
|
||||
scanning, enabling them to address vulnerabilities early.
|
||||
|
||||
All GitLab application security scanning tools can be run in a CI/CD pipeline, triggered by code
|
||||
changes. Security scans can also be run on a schedule, outside the context of code changes, and some
|
||||
can be run manually. It's important to also perform detection outside the CI/CD pipeline because
|
||||
risks can arise outside the context of code changes. For example, a newly-discovered vulnerability
|
||||
in a dependency might be a risk to any application using it.
|
||||
Security scans:
|
||||
|
||||
- Run automatically in the CI/CD pipeline when developers commit changes. Vulnerabilities detected
|
||||
in a feature branch are listed, enabling you to investigate and address them before they're merged
|
||||
into the default branch. For more details, see
|
||||
[Security scan results](security_scan_results.md).
|
||||
- Can be scheduled or run manually to detect vulnerabilities. When a project is idle and no changes
|
||||
are being made, security scans configured to run in a CI/CD pipeline are not run. Risks such as
|
||||
newly-discovered vulnerabilities can go undetected in this situation. Running security scans
|
||||
outside a CI/CD pipeline helps address this risk. For more details, see
|
||||
[Scan execution policies](../policies/scan_execution_policies.md).
|
||||
|
||||
## Prevention
|
||||
|
||||
Security scanning in the pipeline can help minimize the risk of vulnerabilities in the default
|
||||
branch:
|
||||
|
||||
- Extra approval can be enforced on merge requests according to the results of pipeline
|
||||
security scanning. For example, you can require that a member of the security team **also**
|
||||
approve a merge request if one or more critical vulnerabilities are detected in the code
|
||||
changes. For more details, see
|
||||
[Merge request approval policies](../policies/merge_request_approval_policies.md).
|
||||
- Secret push protection can prevent commits being pushed to GitLab if they contain secret
|
||||
information - for example, a GitLab personal access token.
|
||||
|
||||
## Vulnerability management workflow
|
||||
|
||||
Vulnerabilities detected in the default branch are listed in the vulnerability report. To address
|
||||
these vulnerabilities, follow the vulnerability management workflow:
|
||||
|
||||
- Triage: Evaluate vulnerabilities to identify those that need immediate attention.
|
||||
- Analyze: Examine details of a vulnerability to determine if it can and should be remediated.
|
||||
- Remediate: Resolve the root cause of the vulnerability, reduce the associated risks, or both.
|
||||
|
|
|
|||
|
|
@ -455,6 +455,8 @@ Audit event types belong to the following product categories.
|
|||
|
||||
| Type name | Event triggered when | Saved to database | Introduced in | Scope |
|
||||
|:----------|:---------------------|:------------------|:--------------|:------|
|
||||
| [`admin_role_assigned_to_user`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186570) | A custom admin role is assigned to a user | {{< icon name="check-circle" >}} Yes | GitLab [18.0](https://gitlab.com/gitlab-org/gitlab/-/issues/507958) | User |
|
||||
| [`admin_role_unassigned_from_user`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186570) | A custom admin role is unassigned from a user | {{< icon name="check-circle" >}} Yes | GitLab [18.0](https://gitlab.com/gitlab-org/gitlab/-/issues/507958) | User |
|
||||
| [`member_role_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137087) | A custom role is created | {{< icon name="check-circle" >}} Yes | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/issues/388934) | Group, Instance |
|
||||
| [`member_role_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141630) | A custom role is deleted | {{< icon name="check-circle" >}} Yes | GitLab [16.9](https://gitlab.com/gitlab-org/gitlab/-/issues/437672) | Group, Instance |
|
||||
| [`member_role_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141630) | A custom role is updated | {{< icon name="check-circle" >}} Yes | GitLab [16.9](https://gitlab.com/gitlab-org/gitlab/-/issues/437672) | Group, Instance |
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@ token for [Git operations](personal_access_tokens.md#clone-repository-using-pers
|
|||
- [Configurable rate limits](../../security/rate_limits.md#configurable-limits).
|
||||
- [Non-configurable rate limits](../../security/rate_limits.md#non-configurable-limits).
|
||||
|
||||
Service accounts were previously managed exclusively through the API. You can now use either the UI or the API.
|
||||
You can also manage service accounts through the API.
|
||||
|
||||
- For instance-level service accounts, use the [Service account users API](../../api/user_service_accounts.md).
|
||||
- For group-level service accounts, use the [Group service accounts API](../../api/group_service_accounts.md).
|
||||
- For instance-level service accounts, use the [service account users API](../../api/user_service_accounts.md).
|
||||
- For group-level service accounts, use the [group service accounts API](../../api/group_service_accounts.md).
|
||||
|
||||
## View and manage service accounts
|
||||
|
||||
|
|
@ -168,13 +168,18 @@ The personal access tokens page displays information about the personal access t
|
|||
- Rotate personal access tokens.
|
||||
- Revoke personal access tokens.
|
||||
|
||||
You can also manage personal access tokens for service accounts through the API.
|
||||
|
||||
- For instance-level service accounts, use the [personal access tokens API](../../api/user_service_accounts.md).
|
||||
- For group-level service accounts, use the [group service accounts API](../../api/group_service_accounts.md).
|
||||
|
||||
To view the personal access tokens page for a service account:
|
||||
|
||||
1. Go to the [Service Accounts](#view-and-manage-service-accounts) page.
|
||||
1. Identify a service account.
|
||||
1. Select the vertical ellipsis ({{< icon name="ellipsis_v" >}}) > **Manage Access Tokens**.
|
||||
|
||||
## Create a personal access token for a service account
|
||||
### Create a personal access token for a service account
|
||||
|
||||
To use a service account, you must create a personal access token to authenticate requests.
|
||||
|
||||
|
|
@ -198,25 +203,31 @@ To create a personal access token:
|
|||
1. Select the [desired scopes](personal_access_tokens.md#personal-access-token-scopes).
|
||||
1. Select **Create personal access token**.
|
||||
|
||||
## Rotate a personal access token
|
||||
### Rotate a personal access token
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- For service accounts created by top-level group Owners, you must have the Owner role in the top-level group or be an administrator.
|
||||
- For service accounts created by administrators, you must be an administrator for your GitLab Self-Managed instance.
|
||||
- For instance-level service accounts, you must be an administrator for the instance.
|
||||
- For group-level service accounts, you must have the Owner role in a top-level group.
|
||||
|
||||
Use the groups API to [rotate the personal access token](../../api/group_service_accounts.md#rotate-a-personal-access-token-for-a-service-account-user) for a service account user.
|
||||
1. Go to the [Service Accounts](#view-and-manage-service-accounts) page.
|
||||
1. Identify a service account.
|
||||
1. Select the vertical ellipsis ({{< icon name="ellipsis_v" >}}) > **Manage Access Tokens**.
|
||||
1. Select **Rotate**.
|
||||
1. On the confirmation dialog, select **Rotate**.
|
||||
|
||||
## Revoke a personal access token
|
||||
### Revoke a personal access token
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must be signed in as the service account user.
|
||||
- For instance-level service accounts, you must be an administrator for the instance.
|
||||
- For group-level service accounts, you must have the Owner role in a top-level group.
|
||||
|
||||
To revoke a personal access token, use the [personal access tokens API](../../api/personal_access_tokens.md#revoke-a-personal-access-token). You can use either of the following methods:
|
||||
|
||||
- Use a [personal access token ID](../../api/personal_access_tokens.md#revoke-a-personal-access-token). The token used to perform the revocation must have the [`admin_mode`](personal_access_tokens.md#personal-access-token-scopes) scope.
|
||||
- Use a [request header](../../api/personal_access_tokens.md#self-revoke). The token used to perform the request is revoked.
|
||||
1. Go to the [Service Accounts](#view-and-manage-service-accounts) page.
|
||||
1. Identify a service account.
|
||||
1. Select the vertical ellipsis ({{< icon name="ellipsis_v" >}}) > **Manage Access Tokens**.
|
||||
1. Select **Revoke**.
|
||||
1. On the confirmation dialog, select **Revoke**.
|
||||
|
||||
## Delete a service account via API
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,6 @@ configured to generate a Pages site.
|
|||
1. On the left sidebar, at the top, select **Create new** ({{< icon name="plus" >}}) and **New project/repository**.
|
||||
1. Select **Create from Template**.
|
||||
1. Next to one of the templates starting with **Pages**, select **Use template**.
|
||||
|
||||

|
||||
|
||||
1. Complete the form and select **Create project**.
|
||||
1. On the left sidebar, select **Build > Pipelines**
|
||||
and select **New pipeline** to trigger GitLab CI/CD to build and deploy your
|
||||
|
|
@ -37,3 +34,29 @@ that immediately publishes your changes to the Pages site.
|
|||
|
||||
To view the HTML and other assets that were created for the site,
|
||||
[download the job artifacts](../../../../ci/jobs/job_artifacts.md#download-job-artifacts).
|
||||
|
||||
## Project templates
|
||||
|
||||
{{< history >}}
|
||||
|
||||
- [Removed](https://gitlab.com/groups/gitlab-org/-/epics/13847) the following templates from
|
||||
project templates in GitLab 18.0:
|
||||
[`Bridgetown`](https://gitlab.com/pages/bridgetown), [`Gatsby`](https://gitlab.com/pages/gatsby),
|
||||
[`Hexo`](https://gitlab.com/pages/hexo), [`Middleman`](https://gitlab.com/pages/middleman),
|
||||
`Netlify/GitBook`, [`Netlify/Hexo`](https://gitlab.com/pages/nfhexo),
|
||||
[`Netlify/Hugo`](https://gitlab.com/pages/nfhugo), [`Netlify/Jekyll`](https://gitlab.com/pages/nfjekyll),
|
||||
[`Netlify/Plain HTML`](https://gitlab.com/pages/nfplain-html), and [`Pelican`](https://gitlab.com/pages/pelican).
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
GitLab maintains template projects for these frameworks:
|
||||
|
||||
| Realm | Framework | Available project templates |
|
||||
|----------------|-----------------------------------------------------|-----------------------------|
|
||||
| **Go** | [`hugo`](https://gitlab.com/pages/hugo) | Pages/Hugo |
|
||||
| **Markdown** | [`astro`](https://gitlab.com/pages/astro) | Pages/Astro |
|
||||
| **Markdown** | [`docusaurus`](https://gitlab.com/pages/docusaurus) | Pages/Docusaurus |
|
||||
| **Plain HTML** | [`plain-html`](https://gitlab.com/pages/plain-html) | Pages/Plain HTML |
|
||||
| **React** | [`next.js`](https://gitlab.com/pages/nextjs) | Pages/Next.js |
|
||||
| **Ruby** | [`jekyll`](https://gitlab.com/pages/jekyll) | Pages/Jekyll |
|
||||
| **Vue.js** | [`nuxt`](https://gitlab.com/pages/nuxt) | Pages/Nuxt |
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 55 KiB |
|
|
@ -172,16 +172,10 @@ For additional information, see [Approval rules](../../merge_requests/approvals/
|
|||
|
||||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181370) in GitLab 17.9 with a flag named `branch_rule_squash_settings`. Disabled by default.
|
||||
- [Enabled on GitLab.com, GitLab Self-Managed, and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/issues/506542) in GitLab 17.10.
|
||||
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/524860) in GitLab 17.11. Feature flag `branch_rule_squash_settings` removed.
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
{{< alert type="flag" >}}
|
||||
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Maintainer role for the project.
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ module Ci
|
|||
|
||||
override :coerced_value
|
||||
def coerced_value(value)
|
||||
super.to_s
|
||||
value.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -96,6 +96,11 @@ msgid_plural "%d Approvals"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d Artifact"
|
||||
msgid_plural "%d Artifacts"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d Module"
|
||||
msgid_plural "%d Modules"
|
||||
msgstr[0] ""
|
||||
|
|
@ -4929,6 +4934,9 @@ msgstr ""
|
|||
msgid "AdminUsers|Access level"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Access summary for %{userType} user"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Access the API"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -4989,6 +4997,9 @@ msgstr ""
|
|||
msgid "AdminUsers|Banned"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Based on member role in groups and projects. %{linkStart}Learn more about member roles.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Be added to groups and projects"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -5112,9 +5123,15 @@ msgstr ""
|
|||
msgid "AdminUsers|Full access to all groups, projects, users, features, and the Admin area. You cannot remove your own administrator access."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Full read and write access."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Group Managed Account"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Groups and project settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Here are some helpful links to help you manage your instance:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -5154,12 +5171,18 @@ msgstr ""
|
|||
msgid "AdminUsers|Manage (accept/reject) pending user sign ups"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|May be directly added to groups and projects. %{linkStart}Learn more about auditor role.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Name not matching"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|New user"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|No access."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Owned groups will be left"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -5187,6 +5210,9 @@ msgstr ""
|
|||
msgid "AdminUsers|Reactivating a user will:"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Read access to all groups and projects."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Read-only access to all groups and projects. No access to the Admin area by default."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -5205,12 +5231,18 @@ msgstr ""
|
|||
msgid "AdminUsers|Rejected users:"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Requires at least Maintainer role in specific groups and projects."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Reset link will be generated and sent to the user. User will be forced to set the password on first sign in."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Restore user access to the account, including web, Git and API."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Review and set Admin area access with a custom admin role."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Role Promotions"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -8940,9 +8972,6 @@ msgstr ""
|
|||
msgid "Authenticate user SSH keys without requiring additional configuration. Performance of GitLab can be improved by using the GitLab database instead. %{link_start}How do I configure authentication using the GitLab database? %{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Authenticate with Docker Hub"
|
||||
msgstr ""
|
||||
|
||||
msgid "Authenticate with GitHub"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17353,6 +17382,9 @@ msgstr ""
|
|||
msgid "ContainerRegistry|There are no container images stored for this project"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|This container image tag cannot be overwritten or deleted."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|This field is required."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17410,6 +17442,9 @@ msgstr ""
|
|||
msgid "ContainerRegistry|You can add an image to this registry with the following commands:"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|immutable"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|index"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -22685,9 +22720,6 @@ msgstr ""
|
|||
msgid "Do you want to remove this deploy key?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Docker Hub pull rate limits begin April 1, 2025 and might affect CI/CD pipelines that pull Docker images. To prevent pipeline failures, configure the GitLab Dependency Proxy to authenticate with Docker Hub."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dockerfile"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -47351,9 +47383,6 @@ msgstr ""
|
|||
msgid "ProjectQualitySummary|Get insight into the overall percentage of tests in your project that succeed, fail and are skipped."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectQualitySummary|Help us improve this page"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectQualitySummary|Latest pipeline results"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -47369,9 +47398,6 @@ msgstr ""
|
|||
msgid "ProjectQualitySummary|Measure of how much of your code is covered by tests."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectQualitySummary|Provide feedback"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectQualitySummary|See full report"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -47399,9 +47425,6 @@ msgstr ""
|
|||
msgid "ProjectQualitySummary|The percentage of tests that succeed, fail, or are skipped."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectQualitySummary|This page helps you understand the code testing trends for your project. Let us know how we can improve it!"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectQualitySummary|Violations"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -66405,6 +66428,9 @@ msgstr ""
|
|||
msgid "VirtualRegistries|Virtual Registries"
|
||||
msgstr ""
|
||||
|
||||
msgid "VirtualRegistry|Maven"
|
||||
msgstr ""
|
||||
|
||||
msgid "VirtualRegistry|Virtual registries"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
import { GlFormRadioGroup, GlFormRadio, GlCard } from '@gitlab/ui';
|
||||
import { GlFormRadioGroup, GlFormRadio, GlCard, GlSprintf, GlLink, GlIcon } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import UserTypeSelector from '~/admin/users/components/user_type/user_type_selector.vue';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import AdminRoleDropdown from 'ee_component/admin/users/components/user_type/admin_role_dropdown.vue';
|
||||
import AccessSummarySection from '~/admin/users/components/user_type/access_summary_section.vue';
|
||||
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
|
||||
|
||||
const ADMIN_ROLE_ENABLED_FLAG = { customRoles: true, customAdminRoles: true };
|
||||
const ADMIN_ROLE_DISABLED_FLAGS = [
|
||||
{ customRoles: false, customAdminRoles: false },
|
||||
{ customRoles: true, customAdminRoles: false },
|
||||
{ customRoles: false, customAdminRoles: true },
|
||||
];
|
||||
const ALL_FLAG_STATES = [ADMIN_ROLE_ENABLED_FLAG, ...ADMIN_ROLE_DISABLED_FLAGS];
|
||||
|
||||
describe('UserTypeSelector component', () => {
|
||||
let wrapper;
|
||||
|
|
@ -12,10 +22,17 @@ describe('UserTypeSelector component', () => {
|
|||
isCurrentUser = false,
|
||||
licenseAllowsAuditorUser = true,
|
||||
adminRoleId = 1,
|
||||
customRoles = true,
|
||||
customAdminRoles = true,
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(UserTypeSelector, {
|
||||
propsData: { userType, isCurrentUser, licenseAllowsAuditorUser, adminRoleId },
|
||||
provide: {
|
||||
glFeatures: { customRoles, customAdminRoles },
|
||||
},
|
||||
stubs: {
|
||||
AccessSummarySection,
|
||||
GlSprintf,
|
||||
AdminRoleDropdown: stubComponent(AdminRoleDropdown, { props: ['roleId'] }),
|
||||
GlFormRadio: stubComponent(GlFormRadio, {
|
||||
template: `<div>
|
||||
|
|
@ -32,6 +49,28 @@ describe('UserTypeSelector component', () => {
|
|||
const findRadioFor = (value) => wrapper.findByTestId(`user-type-${value}`);
|
||||
const findSummaryCard = () => wrapper.findComponent(GlCard);
|
||||
const findAdminRoleDropdown = () => wrapper.findComponent(AdminRoleDropdown);
|
||||
const findSummarySectionAt = (index) => wrapper.findAllComponents(AccessSummarySection).at(index);
|
||||
const findSummaryHeaderLabel = () => wrapper.findByTestId('summary-header').find('label');
|
||||
const findSummaryHeaderHelpText = () => wrapper.findByTestId('summary-header').find('p');
|
||||
|
||||
const expectRegularUserGroupSectionText = () => {
|
||||
const listItems = findSummarySectionAt(1).findAll('li');
|
||||
|
||||
expect(listItems).toHaveLength(1);
|
||||
expect(listItems.at(0).text()).toBe(
|
||||
'Based on member role in groups and projects. Learn more about member roles.',
|
||||
);
|
||||
};
|
||||
|
||||
const expectAuditorGroupSectionText = () => {
|
||||
const listItems = findSummarySectionAt(1).findAll('li');
|
||||
|
||||
expect(listItems).toHaveLength(2);
|
||||
expect(listItems.at(0).text()).toBe('Read access to all groups and projects.');
|
||||
expect(listItems.at(1).text()).toMatchInterpolatedText(
|
||||
'May be directly added to groups and projects. Learn more about auditor role.',
|
||||
);
|
||||
};
|
||||
|
||||
describe('user type radio group', () => {
|
||||
beforeEach(() => createWrapper());
|
||||
|
|
@ -73,15 +112,143 @@ describe('UserTypeSelector component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('access summary card', () => {
|
||||
beforeEach(() => createWrapper());
|
||||
describe.each`
|
||||
userType | helpLinkText | helpLinkUrl | expectGroupSectionTextFn
|
||||
${'regular'} | ${'Learn more about member roles.'} | ${'/help/user/permissions'} | ${expectRegularUserGroupSectionText}
|
||||
${'auditor'} | ${'Learn more about auditor role.'} | ${'/help/administration/auditor_users'} | ${expectAuditorGroupSectionText}
|
||||
`(
|
||||
'access summary card for $userType user',
|
||||
({ userType, helpLinkText, helpLinkUrl, expectGroupSectionTextFn }) => {
|
||||
describe('for all feature flag states', () => {
|
||||
describe.each(ALL_FLAG_STATES)('for feature flag state %s', (state) => {
|
||||
beforeEach(() => createWrapper({ ...state, userType }));
|
||||
|
||||
it('shows the card', () => {
|
||||
expect(findSummaryCard().exists()).toBe(true);
|
||||
});
|
||||
it('shows card', () => {
|
||||
expect(findSummaryCard().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('shows admin role dropdown', () => {
|
||||
expect(findAdminRoleDropdown().props('roleId')).toBe(1);
|
||||
it('shows card header', () => {
|
||||
expect(findSummaryHeaderLabel().text()).toBe(
|
||||
`Access summary for ${capitalizeFirstCharacter(userType)} user`,
|
||||
);
|
||||
});
|
||||
|
||||
it('shows admin section', () => {
|
||||
expect(findSummarySectionAt(0).props()).toEqual({
|
||||
icon: 'admin',
|
||||
headerText: 'Admin area',
|
||||
});
|
||||
});
|
||||
|
||||
describe('group section', () => {
|
||||
it('shows section', () => {
|
||||
expect(findSummarySectionAt(1).props()).toEqual({
|
||||
icon: 'group',
|
||||
headerText: 'Groups and projects',
|
||||
});
|
||||
});
|
||||
|
||||
it('shows text', () => {
|
||||
expectGroupSectionTextFn();
|
||||
});
|
||||
|
||||
it('shows docs link', () => {
|
||||
const link = findSummarySectionAt(1).findComponent(GlLink);
|
||||
|
||||
expect(link.text()).toBe(helpLinkText);
|
||||
expect(link.props('href')).toBe(helpLinkUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('settings section', () => {
|
||||
it('shows section', () => {
|
||||
expect(findSummarySectionAt(2).props()).toEqual({
|
||||
icon: 'settings',
|
||||
headerText: 'Groups and project settings',
|
||||
});
|
||||
});
|
||||
|
||||
it('shows text', () => {
|
||||
expect(findSummarySectionAt(2).text()).toContain(
|
||||
'Requires at least Maintainer role in specific groups and projects.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when admin role feature is enabled', () => {
|
||||
beforeEach(() => createWrapper({ ...ADMIN_ROLE_ENABLED_FLAG, userType: 'regular' }));
|
||||
|
||||
it('shows header help text', () => {
|
||||
expect(findSummaryHeaderHelpText().text()).toBe(
|
||||
'Review and set Admin area access with a custom admin role.',
|
||||
);
|
||||
});
|
||||
|
||||
it('shows admin role dropdown in admin section', () => {
|
||||
expect(findSummarySectionAt(0).findComponent(AdminRoleDropdown).props('roleId')).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for disabled admin role feature', () => {
|
||||
describe.each(ADMIN_ROLE_DISABLED_FLAGS)('for feature flag state %s', (state) => {
|
||||
beforeEach(() => createWrapper({ ...state, userType: 'regular' }));
|
||||
|
||||
it('does not show header help text', () => {
|
||||
expect(findSummaryHeaderHelpText().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not show admin role dropdown', () => {
|
||||
expect(findAdminRoleDropdown().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows no access text in admin section', () => {
|
||||
const listItems = findSummarySectionAt(0).findAll('li');
|
||||
|
||||
expect(listItems).toHaveLength(1);
|
||||
expect(listItems.at(0).text()).toBe('No access.');
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe('access summary card for admin user', () => {
|
||||
describe.each(ALL_FLAG_STATES)('for feature flags %s', (state) => {
|
||||
beforeEach(() => createWrapper({ ...state, userType: 'admin' }));
|
||||
|
||||
it('shows header', () => {
|
||||
expect(findSummaryHeaderLabel().text()).toBe('Access summary for Administrator user');
|
||||
});
|
||||
|
||||
it('does not show help text', () => {
|
||||
expect(findSummaryHeaderHelpText().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe.each`
|
||||
section | index
|
||||
${'admin'} | ${0}
|
||||
${'group'} | ${1}
|
||||
${'settings'} | ${2}
|
||||
`('for $section section', ({ index }) => {
|
||||
let sectionContents;
|
||||
|
||||
beforeEach(() => {
|
||||
sectionContents = findSummarySectionAt(index).find('div');
|
||||
});
|
||||
|
||||
it('shows check icon', () => {
|
||||
expect(sectionContents.findComponent(GlIcon).props()).toMatchObject({
|
||||
name: 'check',
|
||||
variant: 'success',
|
||||
});
|
||||
});
|
||||
|
||||
it('shows full access text', () => {
|
||||
expect(sectionContents.text()).toBe('Full read and write access.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -130,14 +130,3 @@ export const mockPipelineVariablesPermissions = (value) => ({
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const minimumRoleResponse = {
|
||||
data: {
|
||||
project: {
|
||||
id: mockId,
|
||||
ciCdSettings: {
|
||||
pipelineVariablesMinimumOverrideRole: 'developer',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ import createPipelineScheduleMutation from '~/ci/pipeline_schedules/graphql/muta
|
|||
import updatePipelineScheduleMutation from '~/ci/pipeline_schedules/graphql/mutations/update_pipeline_schedule.mutation.graphql';
|
||||
import getPipelineVariablesMinimumOverrideRoleQuery from '~/ci/pipeline_variables_minimum_override_role/graphql/queries/get_pipeline_variables_minimum_override_role_project_setting.query.graphql';
|
||||
import getPipelineSchedulesQuery from '~/ci/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql';
|
||||
import {
|
||||
mockPipelineVariablesPermissions,
|
||||
minimumRoleResponse,
|
||||
} from 'jest/ci/job_details/mock_data';
|
||||
import { timezoneDataFixture } from '../../../vue_shared/components/timezone_dropdown/helpers';
|
||||
import {
|
||||
createScheduleMutationResponse,
|
||||
|
|
@ -65,7 +61,6 @@ describe('Pipeline schedules form', () => {
|
|||
dailyLimit,
|
||||
settingsLink: '',
|
||||
schedulesPath: '/root/ci-project/-/pipeline_schedules',
|
||||
userRole: 'maintainer',
|
||||
};
|
||||
|
||||
const querySuccessHandler = jest.fn().mockResolvedValue(mockSinglePipelineScheduleNode);
|
||||
|
|
@ -76,11 +71,9 @@ describe('Pipeline schedules form', () => {
|
|||
const updateMutationHandlerSuccess = jest.fn().mockResolvedValue(updateScheduleMutationResponse);
|
||||
const updateMutationHandlerFailed = jest.fn().mockRejectedValue(new Error('GraphQL error'));
|
||||
|
||||
const minimumRoleHandler = jest.fn().mockResolvedValue(minimumRoleResponse);
|
||||
|
||||
const createMockApolloProvider = (
|
||||
requestHandlers = [
|
||||
[getPipelineVariablesMinimumOverrideRoleQuery, minimumRoleHandler],
|
||||
[getPipelineVariablesMinimumOverrideRoleQuery],
|
||||
[createPipelineScheduleMutation, createMutationHandlerSuccess],
|
||||
],
|
||||
) => {
|
||||
|
|
@ -89,15 +82,16 @@ describe('Pipeline schedules form', () => {
|
|||
|
||||
const createComponent = ({
|
||||
editing = false,
|
||||
pipelineVariablesPermissionsMixin = mockPipelineVariablesPermissions(true),
|
||||
requestHandlers,
|
||||
ciInputsForPipelines = false,
|
||||
canSetPipelineVariables = true,
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(PipelineSchedulesForm, {
|
||||
propsData: {
|
||||
timezoneData: timezoneDataFixture,
|
||||
refParam: 'master',
|
||||
editing,
|
||||
canSetPipelineVariables,
|
||||
},
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
|
|
@ -105,7 +99,7 @@ describe('Pipeline schedules form', () => {
|
|||
ciInputsForPipelines,
|
||||
},
|
||||
},
|
||||
mixins: [glFeatureFlagMixin(), pipelineVariablesPermissionsMixin],
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
apolloProvider: createMockApolloProvider(requestHandlers),
|
||||
});
|
||||
};
|
||||
|
|
@ -205,7 +199,7 @@ describe('Pipeline schedules form', () => {
|
|||
|
||||
it('does not display variable list when the user has no permissions', () => {
|
||||
createComponent({
|
||||
pipelineVariablesPermissionsMixin: mockPipelineVariablesPermissions(false),
|
||||
canSetPipelineVariables: false,
|
||||
});
|
||||
|
||||
expect(findPipelineVariables().exists()).toBe(false);
|
||||
|
|
|
|||
|
|
@ -58,8 +58,13 @@ describe('tags list row', () => {
|
|||
const getTooltipFor = (component) => getBinding(component.element, 'gl-tooltip');
|
||||
const findProtectedBadge = () => wrapper.findByTestId('protected-badge');
|
||||
const findProtectedPopover = () => wrapper.findByTestId('protected-popover');
|
||||
const findImmutableBadge = () => wrapper.findByTestId('immutable-badge');
|
||||
const findImmutablePopover = () => wrapper.findByTestId('immutable-popover');
|
||||
|
||||
const mountComponent = (propsData = defaultProps) => {
|
||||
const mountComponent = (
|
||||
propsData = defaultProps,
|
||||
{ immutableTagsFeatureFlagState = false } = {},
|
||||
) => {
|
||||
wrapper = shallowMountExtended(TagsListRow, {
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
|
|
@ -70,6 +75,11 @@ describe('tags list row', () => {
|
|||
},
|
||||
propsData,
|
||||
directives: { GlTooltip: createMockDirective('gl-tooltip') },
|
||||
provide: {
|
||||
glFeatures: {
|
||||
containerRegistryImmutableTags: immutableTagsFeatureFlagState,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -191,7 +201,7 @@ describe('tags list row', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(findProtectedBadge().exists()).toBe(true);
|
||||
expect(findProtectedBadge().text()).toBe('protected');
|
||||
});
|
||||
|
||||
it('has the correct text for the popover', () => {
|
||||
|
|
@ -214,6 +224,56 @@ describe('tags list row', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('immutable tag', () => {
|
||||
const immutableProtection = {
|
||||
minimumAccessLevelForDelete: null,
|
||||
minimumAccessLevelForPush: null,
|
||||
immutable: true,
|
||||
};
|
||||
|
||||
it('hidden if tag.protection does not exists', () => {
|
||||
mountComponent(defaultProps, { immutableTagsFeatureFlagState: true });
|
||||
|
||||
expect(findImmutableBadge().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('displays if tag.protection.immutable exists', () => {
|
||||
mountComponent(
|
||||
{
|
||||
...defaultProps,
|
||||
tag: {
|
||||
...tag,
|
||||
protection: {
|
||||
...immutableProtection,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ immutableTagsFeatureFlagState: true },
|
||||
);
|
||||
|
||||
expect(findImmutableBadge().text()).toBe('immutable');
|
||||
});
|
||||
|
||||
it('has the correct text for the popover', () => {
|
||||
mountComponent(
|
||||
{
|
||||
...defaultProps,
|
||||
tag: {
|
||||
...tag,
|
||||
protection: {
|
||||
...immutableProtection,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ immutableTagsFeatureFlagState: true },
|
||||
);
|
||||
|
||||
const popoverText = findImmutablePopover().text();
|
||||
|
||||
expect(popoverText).toBe('This container image tag cannot be overwritten or deleted.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('warning icon', () => {
|
||||
it('is normally hidden', () => {
|
||||
mountComponent();
|
||||
|
|
|
|||
|
|
@ -200,10 +200,7 @@ export const tagsMock = [
|
|||
userPermissions: {
|
||||
destroyContainerRepositoryTag: true,
|
||||
},
|
||||
protection: {
|
||||
minimumAccessLevelForPush: null,
|
||||
minimumAccessLevelForDelete: null,
|
||||
},
|
||||
protection: null,
|
||||
__typename: 'ContainerRepositoryTag',
|
||||
},
|
||||
{
|
||||
|
|
@ -221,10 +218,7 @@ export const tagsMock = [
|
|||
userPermissions: {
|
||||
destroyContainerRepositoryTag: true,
|
||||
},
|
||||
protection: {
|
||||
minimumAccessLevelForPush: null,
|
||||
minimumAccessLevelForDelete: null,
|
||||
},
|
||||
protection: null,
|
||||
__typename: 'ContainerRepositoryTag',
|
||||
},
|
||||
{
|
||||
|
|
@ -242,10 +236,7 @@ export const tagsMock = [
|
|||
userPermissions: {
|
||||
destroyContainerRepositoryTag: true,
|
||||
},
|
||||
protection: {
|
||||
minimumAccessLevelForPush: null,
|
||||
minimumAccessLevelForDelete: null,
|
||||
},
|
||||
protection: null,
|
||||
__typename: 'ContainerRepositoryTag',
|
||||
},
|
||||
{
|
||||
|
|
@ -263,10 +254,7 @@ export const tagsMock = [
|
|||
userPermissions: {
|
||||
destroyContainerRepositoryTag: true,
|
||||
},
|
||||
protection: {
|
||||
minimumAccessLevelForPush: null,
|
||||
minimumAccessLevelForDelete: null,
|
||||
},
|
||||
protection: null,
|
||||
__typename: 'ContainerRepositoryTag',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ describe('View branch rules', () => {
|
|||
const { bindInternalEventDocument } = useMockInternalEventsTracking();
|
||||
|
||||
const createComponent = async ({
|
||||
glFeatures = { editBranchRules: true, branchRuleSquashSettings: true },
|
||||
glFeatures = { editBranchRules: true },
|
||||
canAdminProtectedBranches = true,
|
||||
allowEditSquashSetting = true,
|
||||
branchRulesQueryHandler = branchRulesMockRequestHandler,
|
||||
|
|
@ -171,12 +171,6 @@ describe('View branch rules', () => {
|
|||
);
|
||||
|
||||
describe('Squash settings', () => {
|
||||
it('does not render squash settings section when feature flag is disabled', async () => {
|
||||
await createComponent({ glFeatures: { branchRuleSquashSettings: false } });
|
||||
|
||||
expect(findSquashSettingSection().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it.each`
|
||||
scenario | canAdminProtectedBranches | expectedIsEditAvailable | description
|
||||
${'user has permission'} | ${true} | ${true} | ${'shows edit button'}
|
||||
|
|
@ -185,7 +179,6 @@ describe('View branch rules', () => {
|
|||
'$description when $scenario',
|
||||
async ({ canAdminProtectedBranches, expectedIsEditAvailable }) => {
|
||||
await createComponent({
|
||||
glFeatures: { branchRuleSquashSettings: true },
|
||||
canAdminProtectedBranches,
|
||||
});
|
||||
|
||||
|
|
@ -204,7 +197,6 @@ describe('View branch rules', () => {
|
|||
jest.spyOn(util, 'getParameterByName').mockReturnValueOnce(branch);
|
||||
|
||||
await createComponent({
|
||||
glFeatures: { branchRuleSquashSettings: true },
|
||||
canAdminProtectedBranches: true,
|
||||
allowEditSquashSetting,
|
||||
});
|
||||
|
|
@ -329,9 +321,7 @@ describe('View branch rules', () => {
|
|||
`('$description', async ({ branch, expectedExists }) => {
|
||||
jest.spyOn(util, 'getParameterByName').mockReturnValueOnce(branch);
|
||||
|
||||
await createComponent({
|
||||
glFeatures: { branchRuleSquashSettings: true },
|
||||
});
|
||||
await createComponent();
|
||||
|
||||
expect(findSquashSettingSection().exists()).toBe(expectedExists);
|
||||
});
|
||||
|
|
@ -603,7 +593,9 @@ describe('View branch rules', () => {
|
|||
});
|
||||
|
||||
it('does not render Protect Branch section', () => {
|
||||
expect(findSettingsSection().exists()).toBe(false);
|
||||
const sections = wrapper.findAllComponents(SettingsSection);
|
||||
expect(sections).toHaveLength(1);
|
||||
expect(sections.at(0).attributes('heading')).toBe('Merge requests');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,13 @@ describe('Branch rule', () => {
|
|||
let wrapper;
|
||||
const squashOptionMockRequestHandler = jest.fn().mockResolvedValue(squashOptionMockResponse);
|
||||
|
||||
const createComponent = async (props = {}, features = { branchRuleSquashSettings: false }) => {
|
||||
const createComponent = async (props = {}) => {
|
||||
const fakeApollo = createMockApollo([[squashOptionQuery, squashOptionMockRequestHandler]]);
|
||||
|
||||
wrapper = shallowMountExtended(BranchRule, {
|
||||
apolloProvider: fakeApollo,
|
||||
provide: {
|
||||
...branchRuleProvideMock,
|
||||
glFeatures: features,
|
||||
},
|
||||
stubs: {
|
||||
ProtectedBadge,
|
||||
|
|
@ -92,12 +91,12 @@ describe('Branch rule', () => {
|
|||
});
|
||||
|
||||
describe('squash settings', () => {
|
||||
it('renders squash settings when branchRuleSquashSettings is true', async () => {
|
||||
it('renders squash settings', async () => {
|
||||
const branchRuleProps = {
|
||||
...branchRulePropsMock,
|
||||
};
|
||||
|
||||
await createComponent(branchRuleProps, { branchRuleSquashSettings: true });
|
||||
await createComponent(branchRuleProps);
|
||||
expect(findProtectionDetailsListItems().at(2).text()).toBe('Squash commits: Encourage');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -129,6 +129,52 @@ RSpec.describe Resolvers::NamespaceProjectsResolver, feature_category: :groups_a
|
|||
end
|
||||
end
|
||||
|
||||
context 'full path sorting' do
|
||||
let_it_be(:parent_group) { create(:group) }
|
||||
let_it_be(:nested_group_1) { create(:group, parent: parent_group, path: 'alpha') }
|
||||
let_it_be(:nested_group_2) { create(:group, parent: parent_group, path: 'beta') }
|
||||
let_it_be(:deeply_nested_group) { create(:group, parent: nested_group_1, path: 'gamma') }
|
||||
|
||||
let_it_be(:projects_with_various_paths) do
|
||||
[
|
||||
create(:project, path: 'zebra', namespace: parent_group),
|
||||
create(:project, path: 'apple', namespace: nested_group_1),
|
||||
create(:project, path: 'banana', namespace: nested_group_2),
|
||||
create(:project, path: 'cherry', namespace: deeply_nested_group)
|
||||
]
|
||||
end
|
||||
|
||||
let(:namespace) { parent_group }
|
||||
let(:args) { default_args.merge(include_subgroups: true, sort: :full_path_asc) }
|
||||
let(:project_full_paths) { subject.map(&:full_path) }
|
||||
|
||||
before_all do
|
||||
projects_with_various_paths.each { |p| p.add_developer(current_user) }
|
||||
end
|
||||
|
||||
it 'returns projects sorted by full path in ascending order' do
|
||||
expect(project_full_paths).to eq([
|
||||
"#{parent_group.path}/alpha/apple",
|
||||
"#{parent_group.path}/alpha/gamma/cherry",
|
||||
"#{parent_group.path}/beta/banana",
|
||||
"#{parent_group.path}/zebra"
|
||||
])
|
||||
end
|
||||
|
||||
context 'when sorting by full path in descending order' do
|
||||
let(:args) { default_args.merge(include_subgroups: true, sort: :full_path_desc) }
|
||||
|
||||
it 'returns projects sorted by full path in descending order' do
|
||||
expect(project_full_paths).to eq([
|
||||
"#{parent_group.path}/zebra",
|
||||
"#{parent_group.path}/beta/banana",
|
||||
"#{parent_group.path}/alpha/gamma/cherry",
|
||||
"#{parent_group.path}/alpha/apple"
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'ids filtering' do
|
||||
let(:args) { default_args.merge(include_subgroups: false) }
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ RSpec.describe GitlabSchema.types['NamespaceProjectSort'], feature_category: :gr
|
|||
STORAGE_SIZE_DESC
|
||||
PATH_ASC
|
||||
PATH_DESC
|
||||
FULL_PATH_ASC
|
||||
FULL_PATH_DESC
|
||||
REPOSITORY_SIZE_ASC
|
||||
REPOSITORY_SIZE_DESC
|
||||
SNIPPETS_SIZE_ASC
|
||||
|
|
|
|||
|
|
@ -15,30 +15,6 @@ RSpec.describe Ci::PipelineSchedulesHelper, feature_category: :continuous_integr
|
|||
describe '#js_pipeline_schedules_form_data' do
|
||||
before do
|
||||
allow(helper).to receive_messages(timezone_data: timezones, current_user: user, can_view_pipeline_editor?: true)
|
||||
allow(project.team).to receive(:human_max_access).with(user.id).and_return('Owner')
|
||||
end
|
||||
|
||||
describe 'user_role' do
|
||||
context 'when there is no current user' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(nil)
|
||||
end
|
||||
|
||||
it 'is nil' do
|
||||
expect(helper.js_pipeline_schedules_form_data(project, pipeline_schedule)[:user_role]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a current_user' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(user)
|
||||
allow(project.team).to receive(:human_max_access).with(user.id).and_return('Developer')
|
||||
end
|
||||
|
||||
it "returns the human readable access level that the current user has in the project" do
|
||||
expect(helper.js_pipeline_schedules_form_data(project, pipeline_schedule)[:user_role]).to eq('Developer')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns pipeline schedule form data' do
|
||||
|
|
@ -51,7 +27,7 @@ RSpec.describe Ci::PipelineSchedulesHelper, feature_category: :continuous_integr
|
|||
schedules_path: pipeline_schedules_path(project),
|
||||
settings_link: project_settings_ci_cd_path(project),
|
||||
timezone_data: timezones.to_json,
|
||||
user_role: 'Owner'
|
||||
can_set_pipeline_variables: 'false'
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'oj'
|
||||
require_relative Rails.root.join('lib/ci/pipeline_creation/inputs/spec_inputs.rb')
|
||||
|
||||
RSpec.describe Ci::PipelineCreation::Inputs::SpecInputs, feature_category: :pipeline_composition do
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'oj'
|
||||
require_relative Rails.root.join('lib/gitlab/ci/config/interpolation/inputs.rb')
|
||||
|
||||
RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs, feature_category: :pipeline_composition do
|
||||
|
|
@ -195,6 +196,16 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs, feature_category: :pip
|
|||
expect(inputs.to_hash).to eq(foo: 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a hash value is passed as string' do
|
||||
let(:specs) { { test_input: { type: 'string' } } }
|
||||
let(:args) { { test_input: '{"key": "value"}' } }
|
||||
|
||||
it 'is valid and behaves like an unparsed string' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(test_input: '{"key": "value"}')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'number validation' do
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ RSpec.describe Gitlab::EncryptedAttribute, feature_category: :shared do
|
|||
record.attr_encrypted_attributes[:token][:operation] = :encrypting
|
||||
end
|
||||
|
||||
it 'returns correct secret' do
|
||||
expect(record.__send__(key_method, :token))
|
||||
it 'returns the encryption key secret' do
|
||||
expect(record.__send__(key_method))
|
||||
.to eq(Gitlab::Encryption::KeyProvider[key_method].encryption_key.secret)
|
||||
end
|
||||
end
|
||||
|
|
@ -38,9 +38,9 @@ RSpec.describe Gitlab::EncryptedAttribute, feature_category: :shared do
|
|||
record.attr_encrypted_attributes[:token][:operation] = :decrypting
|
||||
end
|
||||
|
||||
it 'returns correct secrets' do
|
||||
expect(record.__send__(key_method, :token))
|
||||
.to eq(Gitlab::Encryption::KeyProvider[key_method].decryption_keys.map(&:secret))
|
||||
it 'returns the encryption key secret' do
|
||||
expect(record.__send__(key_method))
|
||||
.to eq(Gitlab::Encryption::KeyProvider[key_method].encryption_key.secret)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2355,6 +2355,18 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
|
|||
expect(projects).to eq([project1, project2, project3].sort_by(&:path).reverse)
|
||||
end
|
||||
|
||||
it 'reorders the input relation by full path asc' do
|
||||
projects = described_class.sort_by_attribute(:full_path_asc)
|
||||
|
||||
expect(projects).to eq([project1, project2, project3].sort_by(&:full_path))
|
||||
end
|
||||
|
||||
it 'reorders the input relation by full path desc' do
|
||||
projects = described_class.sort_by_attribute(:full_path_desc)
|
||||
|
||||
expect(projects).to eq([project1, project2, project3].sort_by(&:full_path).reverse)
|
||||
end
|
||||
|
||||
context 'with project_statistics' do
|
||||
describe '.sort_by_attribute with project_statistics' do
|
||||
def create_project_statistics_with_size(project, size)
|
||||
|
|
|
|||
|
|
@ -992,63 +992,79 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
context 'when `pipeline_variables_minimum_override_role` is defined' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:user_role, :minimum_role, :restrict_variables, :allowed) do
|
||||
:developer | :no_one_allowed | true | false
|
||||
:maintainer | :no_one_allowed | true | false
|
||||
:owner | :no_one_allowed | true | false
|
||||
:guest | :no_one_allowed | true | false
|
||||
:planner | :no_one_allowed | true | false
|
||||
:reporter | :no_one_allowed | true | false
|
||||
:anonymous | :no_one_allowed | true | false
|
||||
:developer | :developer | true | true
|
||||
:maintainer | :developer | true | true
|
||||
:owner | :developer | true | true
|
||||
:guest | :developer | true | true
|
||||
:planner | :developer | true | true
|
||||
:reporter | :developer | true | true
|
||||
:anonymous | :developer | true | true
|
||||
:developer | :maintainer | true | false
|
||||
:maintainer | :maintainer | true | true
|
||||
:owner | :maintainer | true | true
|
||||
:guest | :maintainer | true | false
|
||||
:planner | :maintainer | true | false
|
||||
:reporter | :maintainer | true | false
|
||||
:anonymous | :maintainer | true | false
|
||||
:developer | :owner | true | false
|
||||
:maintainer | :owner | true | false
|
||||
:owner | :owner | true | true
|
||||
:guest | :owner | true | false
|
||||
:planner | :owner | true | false
|
||||
:reporter | :owner | true | false
|
||||
:anonymous | :owner | true | false
|
||||
:developer | :no_one_allowed | false | true
|
||||
:maintainer | :no_one_allowed | false | true
|
||||
:owner | :no_one_allowed | false | true
|
||||
:guest | :no_one_allowed | false | true
|
||||
:planner | :no_one_allowed | false | true
|
||||
:reporter | :no_one_allowed | false | true
|
||||
:anonymous | :no_one_allowed | false | true
|
||||
:developer | :developer | false | true
|
||||
:maintainer | :developer | false | true
|
||||
:owner | :developer | false | true
|
||||
:guest | :developer | false | true
|
||||
:planner | :developer | false | true
|
||||
:reporter | :developer | false | true
|
||||
:anonymous | :developer | false | true
|
||||
:developer | :maintainer | false | true
|
||||
:maintainer | :maintainer | false | true
|
||||
:owner | :maintainer | false | true
|
||||
:guest | :maintainer | false | true
|
||||
:planner | :maintainer | false | true
|
||||
:reporter | :maintainer | false | true
|
||||
:anonymous | :maintainer | false | true
|
||||
:developer | :owner | false | true
|
||||
:maintainer | :owner | false | true
|
||||
:owner | :owner | false | true
|
||||
:guest | :owner | false | true
|
||||
:planner | :owner | false | true
|
||||
:reporter | :owner | false | true
|
||||
:anonymous | :owner | false | true
|
||||
where(:user_role, :admin_mode, :minimum_role, :restrict_variables, :allowed) do
|
||||
:developer | false | :no_one_allowed | true | false
|
||||
:maintainer | false | :no_one_allowed | true | false
|
||||
:owner | false | :no_one_allowed | true | false
|
||||
:guest | false | :no_one_allowed | true | false
|
||||
:planner | false | :no_one_allowed | true | false
|
||||
:reporter | false | :no_one_allowed | true | false
|
||||
:anonymous | false | :no_one_allowed | true | false
|
||||
:developer | false | :developer | true | true
|
||||
:maintainer | false | :developer | true | true
|
||||
:owner | false | :developer | true | true
|
||||
:guest | false | :developer | true | true
|
||||
:planner | false | :developer | true | true
|
||||
:reporter | false | :developer | true | true
|
||||
:anonymous | false | :developer | true | true
|
||||
:developer | false | :maintainer | true | false
|
||||
:maintainer | false | :maintainer | true | true
|
||||
:owner | false | :maintainer | true | true
|
||||
:guest | false | :maintainer | true | false
|
||||
:planner | false | :maintainer | true | false
|
||||
:reporter | false | :maintainer | true | false
|
||||
:anonymous | false | :maintainer | true | false
|
||||
:developer | false | :owner | true | false
|
||||
:maintainer | false | :owner | true | false
|
||||
:owner | false | :owner | true | true
|
||||
:guest | false | :owner | true | false
|
||||
:planner | false | :owner | true | false
|
||||
:reporter | false | :owner | true | false
|
||||
:anonymous | false | :owner | true | false
|
||||
:developer | false | :no_one_allowed | false | true
|
||||
:maintainer | false | :no_one_allowed | false | true
|
||||
:owner | false | :no_one_allowed | false | true
|
||||
:guest | false | :no_one_allowed | false | true
|
||||
:planner | false | :no_one_allowed | false | true
|
||||
:reporter | false | :no_one_allowed | false | true
|
||||
:anonymous | false | :no_one_allowed | false | true
|
||||
:developer | false | :developer | false | true
|
||||
:maintainer | false | :developer | false | true
|
||||
:owner | false | :developer | false | true
|
||||
:guest | false | :developer | false | true
|
||||
:planner | false | :developer | false | true
|
||||
:reporter | false | :developer | false | true
|
||||
:anonymous | false | :developer | false | true
|
||||
:developer | false | :maintainer | false | true
|
||||
:maintainer | false | :maintainer | false | true
|
||||
:owner | false | :maintainer | false | true
|
||||
:guest | false | :maintainer | false | true
|
||||
:planner | false | :maintainer | false | true
|
||||
:reporter | false | :maintainer | false | true
|
||||
:anonymous | false | :maintainer | false | true
|
||||
:developer | false | :owner | false | true
|
||||
:maintainer | false | :owner | false | true
|
||||
:owner | false | :owner | false | true
|
||||
:guest | false | :owner | false | true
|
||||
:planner | false | :owner | false | true
|
||||
:reporter | false | :owner | false | true
|
||||
:anonymous | false | :owner | false | true
|
||||
:admin | false | :no_one_allowed | false | true
|
||||
:admin | false | :owner | false | true
|
||||
:admin | false | :maintainer | false | true
|
||||
:admin | false | :developer | false | true
|
||||
:admin | false | :no_one_allowed | true | false
|
||||
:admin | false | :owner | true | false
|
||||
:admin | false | :maintainer | true | false
|
||||
:admin | false | :developer | true | true
|
||||
:admin | true | :no_one_allowed | false | true
|
||||
:admin | true | :developer | false | true
|
||||
:admin | true | :maintainer | false | true
|
||||
:admin | true | :owner | false | true
|
||||
:admin | true | :no_one_allowed | true | false
|
||||
:admin | true | :developer | true | true
|
||||
:admin | true | :maintainer | true | true
|
||||
:admin | true | :owner | true | true
|
||||
end
|
||||
with_them do
|
||||
let(:current_user) { public_send(user_role) }
|
||||
|
|
@ -1058,6 +1074,8 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
ci_cd_settings[:pipeline_variables_minimum_override_role] = minimum_role
|
||||
ci_cd_settings[:restrict_user_defined_variables] = restrict_variables
|
||||
ci_cd_settings.save!
|
||||
|
||||
enable_admin_mode!(current_user) if admin_mode
|
||||
end
|
||||
|
||||
it 'allows/disallows set pipeline variables based on project defined minimum role' do
|
||||
|
|
|
|||
|
|
@ -889,10 +889,14 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
|
|||
{ 'Content-Type' => 'application/x-www-form-urlencoded' }
|
||||
end
|
||||
|
||||
let(:transformed_values) do
|
||||
inputs.transform_values { |value| value.is_a?(String) ? value : value.to_json }
|
||||
end
|
||||
|
||||
subject(:post_request) do
|
||||
post api("/projects/#{project.id}/pipeline", user),
|
||||
headers: headers,
|
||||
params: { ref: project.default_branch, inputs: inputs.transform_values(&:to_json) }
|
||||
params: { ref: project.default_branch, inputs: transformed_values }
|
||||
end
|
||||
|
||||
it_behaves_like 'creating a succesful pipeline'
|
||||
|
|
|
|||
|
|
@ -237,10 +237,14 @@ RSpec.describe API::Ci::Triggers, feature_category: :pipeline_composition do
|
|||
{ 'Content-Type' => 'application/x-www-form-urlencoded' }
|
||||
end
|
||||
|
||||
let(:transformed_values) do
|
||||
inputs.transform_values { |value| value.is_a?(String) ? value : value.to_json }
|
||||
end
|
||||
|
||||
subject(:post_request) do
|
||||
post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{token}"),
|
||||
headers: headers,
|
||||
params: { ref: 'refs/heads/other-branch', inputs: inputs.transform_values(&:to_json) }
|
||||
params: { ref: 'refs/heads/other-branch', inputs: transformed_values }
|
||||
end
|
||||
|
||||
it_behaves_like 'creating a succesful pipeline'
|
||||
|
|
|
|||
|
|
@ -40,17 +40,6 @@ RSpec.describe 'Updating a squash option', feature_category: :source_code_manage
|
|||
project.add_maintainer(current_user)
|
||||
end
|
||||
|
||||
context 'and the branch_rule_squash_settings feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(branch_rule_squash_settings: false)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
mutation_request
|
||||
expect(graphql_errors).to include(a_hash_including('message' => 'Squash options feature disabled'))
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates the squash option' do
|
||||
expect do
|
||||
mutation_request
|
||||
|
|
|
|||
|
|
@ -4,12 +4,4 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe 'getting merge access levels for a branch protection', feature_category: :source_code_management do
|
||||
it_behaves_like 'a GraphQL query for access levels', :merge
|
||||
|
||||
context 'when the branch_rule_squash_settings not enabled' do
|
||||
before do
|
||||
stub_feature_flags(branch_rule_squash_settings: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a GraphQL query for access levels', :merge
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,12 +4,4 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe 'getting push access levels for a branch protection', feature_category: :source_code_management do
|
||||
it_behaves_like 'a GraphQL query for access levels', :push
|
||||
|
||||
context 'when the branch_rule_squash_settings not enabled' do
|
||||
before do
|
||||
stub_feature_flags(branch_rule_squash_settings: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a GraphQL query for access levels', :push
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,44 +33,28 @@ RSpec.describe 'getting branch protection for a branch rule', feature_category:
|
|||
GQL
|
||||
end
|
||||
|
||||
shared_examples_for 'branch protection graphql query' do
|
||||
context 'when the user does not have read_protected_branch abilities' do
|
||||
before do
|
||||
project.add_guest(current_user)
|
||||
post_graphql(query, current_user: current_user, variables: variables)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it { expect(branch_protection_data).not_to be_present }
|
||||
context 'when the user does not have read_protected_branch abilities' do
|
||||
before do
|
||||
project.add_guest(current_user)
|
||||
post_graphql(query, current_user: current_user, variables: variables)
|
||||
end
|
||||
|
||||
context 'when the user does have read_protected_branch abilities' do
|
||||
before do
|
||||
project.add_maintainer(current_user)
|
||||
post_graphql(query, current_user: current_user, variables: variables)
|
||||
end
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'includes allow_force_push' do
|
||||
expect(branch_protection_data['allowForcePush']).to be_in([true, false])
|
||||
expect(branch_protection_data['allowForcePush']).to eq(branch_rule.allow_force_push)
|
||||
end
|
||||
end
|
||||
it { expect(branch_protection_data).not_to be_present }
|
||||
end
|
||||
|
||||
it_behaves_like 'branch protection graphql query'
|
||||
|
||||
context 'when the branch_rule_squash_settings flag is not enabled' do
|
||||
context 'when the user does have read_protected_branch abilities' do
|
||||
before do
|
||||
stub_feature_flags(branch_rule_squash_settings: false)
|
||||
project.add_maintainer(current_user)
|
||||
post_graphql(query, current_user: current_user, variables: variables)
|
||||
end
|
||||
|
||||
it_behaves_like 'branch protection graphql query' do
|
||||
let(:branch_protection_data) do
|
||||
graphql_data_at('project', 'branchRules', 'nodes', 0, 'branchProtection')
|
||||
end
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'includes allow_force_push' do
|
||||
expect(branch_protection_data['allowForcePush']).to be_in([true, false])
|
||||
expect(branch_protection_data['allowForcePush']).to eq(branch_rule.allow_force_push)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -99,6 +99,9 @@ RSpec.describe 'getting list of branch rules for a project', feature_category: :
|
|||
|
||||
describe 'response' do
|
||||
let_it_be(:branch_name_a) { TestEnv::BRANCH_SHA.each_key.first }
|
||||
let(:branch_rule_a_data) { branch_rules_data.dig(2, 'node') }
|
||||
let(:branch_rule_b_data) { branch_rules_data.dig(1, 'node') }
|
||||
let(:all_branches_rule_data) { branch_rules_data.dig(0, 'node') }
|
||||
let_it_be(:branch_name_b) { 'diff-*' }
|
||||
let_it_be(:protected_branch_a) do
|
||||
create(:protected_branch, project: project, name: branch_name_a)
|
||||
|
|
@ -119,85 +122,46 @@ RSpec.describe 'getting list of branch rules for a project', feature_category: :
|
|||
end
|
||||
end
|
||||
|
||||
let(:branch_rule_squash_settings) { true }
|
||||
|
||||
before do
|
||||
stub_feature_flags(branch_rule_squash_settings: branch_rule_squash_settings)
|
||||
post_graphql(query, current_user: current_user, variables: variables)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
context 'when the branch_rule_squash_settings flag is enabled' do
|
||||
let(:all_branches_rule_data) { branch_rules_data.dig(0, 'node') }
|
||||
let(:branch_rule_b_data) { branch_rules_data.dig(1, 'node') }
|
||||
let(:branch_rule_a_data) { branch_rules_data.dig(2, 'node') }
|
||||
it 'includes all fields', :use_sql_query_cache, :aggregate_failures do
|
||||
expect(all_branches_rule_data).to include(
|
||||
'id' => all_branches_rule.to_global_id.to_s,
|
||||
'name' => 'All branches',
|
||||
'isDefault' => false,
|
||||
'isProtected' => false,
|
||||
'matchingBranchesCount' => project.repository.branch_count,
|
||||
'branchProtection' => nil,
|
||||
"squashOption" => be_kind_of(Hash),
|
||||
'createdAt' => nil,
|
||||
'updatedAt' => nil
|
||||
)
|
||||
|
||||
it 'includes all fields', :use_sql_query_cache, :aggregate_failures do
|
||||
expect(all_branches_rule_data).to include(
|
||||
'id' => all_branches_rule.to_global_id.to_s,
|
||||
'name' => 'All branches',
|
||||
'isDefault' => false,
|
||||
'isProtected' => false,
|
||||
'matchingBranchesCount' => project.repository.branch_count,
|
||||
'branchProtection' => nil,
|
||||
"squashOption" => be_kind_of(Hash),
|
||||
'createdAt' => nil,
|
||||
'updatedAt' => nil
|
||||
)
|
||||
expect(branch_rule_a_data).to include(
|
||||
'id' => branch_rule_a.to_global_id.to_s,
|
||||
'name' => branch_name_a,
|
||||
'isDefault' => be_boolean,
|
||||
'isProtected' => true,
|
||||
'matchingBranchesCount' => 1,
|
||||
'branchProtection' => be_kind_of(Hash),
|
||||
'createdAt' => be_kind_of(String),
|
||||
'updatedAt' => be_kind_of(String)
|
||||
)
|
||||
|
||||
expect(branch_rule_a_data).to include(
|
||||
'id' => branch_rule_a.to_global_id.to_s,
|
||||
'name' => branch_name_a,
|
||||
'isDefault' => be_boolean,
|
||||
'isProtected' => true,
|
||||
'matchingBranchesCount' => 1,
|
||||
'branchProtection' => be_kind_of(Hash),
|
||||
'createdAt' => be_kind_of(String),
|
||||
'updatedAt' => be_kind_of(String)
|
||||
)
|
||||
|
||||
expect(branch_rule_b_data).to include(
|
||||
'id' => branch_rule_b.to_global_id.to_s,
|
||||
'name' => branch_name_b,
|
||||
'isDefault' => be_boolean,
|
||||
'isProtected' => true,
|
||||
'matchingBranchesCount' => wildcard_count,
|
||||
'branchProtection' => be_kind_of(Hash),
|
||||
'createdAt' => be_kind_of(String),
|
||||
'updatedAt' => be_kind_of(String)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the branch_rule_squash_settings flag is not enabled' do
|
||||
let(:branch_rule_squash_settings) { false }
|
||||
let(:branch_rule_b_data) { branch_rules_data.dig(0, 'node') }
|
||||
let(:branch_rule_a_data) { branch_rules_data.dig(1, 'node') }
|
||||
|
||||
it 'includes all fields', :use_sql_query_cache, :aggregate_failures do
|
||||
expect(branch_rule_a_data).to include(
|
||||
'id' => branch_rule_a.to_global_id.to_s,
|
||||
'name' => branch_name_a,
|
||||
'isDefault' => be_boolean,
|
||||
'isProtected' => true,
|
||||
'matchingBranchesCount' => 1,
|
||||
'branchProtection' => be_kind_of(Hash),
|
||||
'createdAt' => be_kind_of(String),
|
||||
'updatedAt' => be_kind_of(String)
|
||||
)
|
||||
|
||||
expect(branch_rule_b_data).to include(
|
||||
'id' => branch_rule_b.to_global_id.to_s,
|
||||
'name' => branch_name_b,
|
||||
'isDefault' => be_boolean,
|
||||
'isProtected' => true,
|
||||
'matchingBranchesCount' => wildcard_count,
|
||||
'branchProtection' => be_kind_of(Hash),
|
||||
'createdAt' => be_kind_of(String),
|
||||
'updatedAt' => be_kind_of(String)
|
||||
)
|
||||
end
|
||||
expect(branch_rule_b_data).to include(
|
||||
'id' => branch_rule_b.to_global_id.to_s,
|
||||
'name' => branch_name_b,
|
||||
'isDefault' => be_boolean,
|
||||
'isProtected' => true,
|
||||
'matchingBranchesCount' => wildcard_count,
|
||||
'branchProtection' => be_kind_of(Hash),
|
||||
'createdAt' => be_kind_of(String),
|
||||
'updatedAt' => be_kind_of(String)
|
||||
)
|
||||
end
|
||||
|
||||
context 'when limiting the number of results' do
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ RSpec.describe Projects::Registry::RepositoriesController, feature_category: :co
|
|||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:ok) }
|
||||
|
||||
it_behaves_like 'pushed feature flag', :container_registry_immutable_tags
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
|
|
@ -32,5 +34,7 @@ RSpec.describe Projects::Registry::RepositoriesController, feature_category: :co
|
|||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:ok) }
|
||||
|
||||
it_behaves_like 'pushed feature flag', :container_registry_immutable_tags
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ RSpec.describe Projects::InactiveProjectsDeletionCronWorker, feature_category: :
|
|||
|
||||
it 'does not invoke Projects::InactiveProjectsDeletionNotificationWorker' do
|
||||
expect(::Projects::InactiveProjectsDeletionNotificationWorker).not_to receive(:perform_async)
|
||||
expect(::Projects::MarkForDeletionService).not_to receive(:new)
|
||||
expect(::Projects::DestroyService).not_to receive(:new)
|
||||
|
||||
worker.perform
|
||||
|
|
@ -100,11 +101,28 @@ RSpec.describe Projects::InactiveProjectsDeletionCronWorker, feature_category: :
|
|||
end
|
||||
expect(::Projects::InactiveProjectsDeletionNotificationWorker).to receive(:perform_async).with(
|
||||
inactive_large_project.id, deletion_date).and_call_original
|
||||
expect(::Projects::MarkForDeletionService).not_to receive(:new)
|
||||
expect(::Projects::DestroyService).not_to receive(:new)
|
||||
|
||||
worker.perform
|
||||
end
|
||||
|
||||
it 'does not invoke InactiveProjectsDeletionNotificationWorker for inactive projects marked for deletion' do
|
||||
inactive_large_project.update!(marked_for_deletion_at: Date.current)
|
||||
|
||||
expect(::Projects::InactiveProjectsDeletionNotificationWorker).not_to receive(:perform_async)
|
||||
expect(::Projects::MarkForDeletionService).not_to receive(:new)
|
||||
expect(::Projects::DestroyService).not_to receive(:new)
|
||||
|
||||
worker.perform
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(
|
||||
redis.hget('inactive_projects_deletion_warning_email_notified', "project:#{inactive_large_project.id}")
|
||||
).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not invoke InactiveProjectsDeletionNotificationWorker for already notified inactive projects' do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.hset(
|
||||
|
|
@ -115,6 +133,7 @@ RSpec.describe Projects::InactiveProjectsDeletionCronWorker, feature_category: :
|
|||
end
|
||||
|
||||
expect(::Projects::InactiveProjectsDeletionNotificationWorker).not_to receive(:perform_async)
|
||||
expect(::Projects::MarkForDeletionService).not_to receive(:new)
|
||||
expect(::Projects::DestroyService).not_to receive(:new)
|
||||
|
||||
worker.perform
|
||||
|
|
@ -131,6 +150,7 @@ RSpec.describe Projects::InactiveProjectsDeletionCronWorker, feature_category: :
|
|||
end
|
||||
|
||||
expect(::Projects::InactiveProjectsDeletionNotificationWorker).not_to receive(:perform_async)
|
||||
expect(::Projects::MarkForDeletionService).not_to receive(:new)
|
||||
expect(::Projects::DestroyService).to receive(:new).with(inactive_large_project, admin_bot, {})
|
||||
.at_least(:once).and_call_original
|
||||
|
||||
|
|
|
|||
|
|
@ -240,22 +240,15 @@ module AttrEncrypted
|
|||
def attr_decrypt(attribute, encrypted_value, options = {})
|
||||
options = attr_encrypted_attributes[attribute.to_sym].merge(options)
|
||||
if options[:if] && !options[:unless] && not_empty?(encrypted_value)
|
||||
keys = Array(options[:key])
|
||||
|
||||
keys.each.with_index do |key, index|
|
||||
encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
|
||||
value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value, key: key))
|
||||
if options[:marshal]
|
||||
value = options[:marshaler].send(options[:load_method], value)
|
||||
elsif defined?(Encoding)
|
||||
encoding = Encoding.default_internal || Encoding.default_external
|
||||
value = value.force_encoding(encoding.name)
|
||||
end
|
||||
|
||||
return value
|
||||
rescue OpenSSL::Cipher::CipherError
|
||||
raise if index == keys.length - 1
|
||||
encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
|
||||
value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
|
||||
if options[:marshal]
|
||||
value = options[:marshaler].send(options[:load_method], value)
|
||||
elsif defined?(Encoding)
|
||||
encoding = Encoding.default_internal || Encoding.default_external
|
||||
value = value.force_encoding(encoding.name)
|
||||
end
|
||||
value
|
||||
else
|
||||
encrypted_value
|
||||
end
|
||||
|
|
@ -389,7 +382,7 @@ module AttrEncrypted
|
|||
evaluated_options.tap do |options|
|
||||
if options[:if] && !options[:unless] && options[:value_present] || options[:allow_empty_value]
|
||||
(attributes.keys - evaluated_options.keys).each do |option|
|
||||
options[option] = evaluate_attr_encrypted_option(attributes[option], attribute.to_sym)
|
||||
options[option] = evaluate_attr_encrypted_option(attributes[option])
|
||||
end
|
||||
|
||||
unless options[:mode] == :single_iv_and_salt
|
||||
|
|
@ -406,17 +399,9 @@ module AttrEncrypted
|
|||
# Evaluates symbol (method reference) or proc (responds to call) options
|
||||
#
|
||||
# If the option is not a symbol or proc then the original option is returned
|
||||
def evaluate_attr_encrypted_option(option, attribute = nil)
|
||||
def evaluate_attr_encrypted_option(option)
|
||||
if option.is_a?(Symbol) && respond_to?(option, true)
|
||||
method = method(option)
|
||||
|
||||
# Allows a dynamic key method to receive the attribute name as argument
|
||||
if method.arity == 1
|
||||
send(option, attribute)
|
||||
else
|
||||
send(option)
|
||||
end
|
||||
|
||||
send(option)
|
||||
elsif option.respond_to?(:call)
|
||||
option.call(self)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ class User
|
|||
attr_encrypted :email, :without_encoding, :key => SECRET_KEY
|
||||
attr_encrypted :password, :prefix => 'crypted_', :suffix => '_test'
|
||||
attr_encrypted :ssn, :key => :secret_key, :attribute => 'ssn_encrypted'
|
||||
attr_encrypted :ssn2, :key => :dynamic_secret_key, :attribute => 'ssn2_encrypted'
|
||||
attr_encrypted :credit_card, :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt, :some_arg => 'test'
|
||||
attr_encrypted :with_encoding, :key => SECRET_KEY, :encode => true
|
||||
attr_encrypted :with_custom_encoding, :key => SECRET_KEY, :encode => 'm'
|
||||
|
|
@ -48,16 +47,6 @@ class User
|
|||
def secret_key
|
||||
SECRET_KEY
|
||||
end
|
||||
|
||||
def dynamic_secret_key(attribute)
|
||||
operation = attr_encrypted_attributes[attribute][:operation]
|
||||
|
||||
if operation == :encrypting
|
||||
SECRET_KEY
|
||||
else
|
||||
[OLD_SECRET_KEY, SECRET_KEY]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Admin < User
|
||||
|
|
@ -215,17 +204,6 @@ class AttrEncryptedTest < Minitest::Test
|
|||
assert_equal encrypted, @user.ssn_encrypted
|
||||
end
|
||||
|
||||
def test_should_evaluate_a_key_passed_as_a_symbol_with_argument
|
||||
@user = User.new
|
||||
assert_nil @user.ssn2_encrypted
|
||||
@user.ssn2 = 'testing'
|
||||
refute_nil @user.ssn2_encrypted
|
||||
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.ssn2_encrypted_iv.unpack("m")
|
||||
.first, :salt => @user.ssn2_encrypted_salt.unpack("m").first )
|
||||
assert_equal encrypted, @user.ssn2_encrypted
|
||||
assert_equal 'testing', @user.ssn2
|
||||
end
|
||||
|
||||
def test_should_evaluate_a_key_passed_as_a_proc
|
||||
@user = User.new
|
||||
assert_nil @user.crypted_password_test
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ end
|
|||
# This plugin re-enables it.
|
||||
Sequel::Model.plugin :after_initialize
|
||||
|
||||
OLD_SECRET_KEY = SecureRandom.random_bytes(32)
|
||||
SECRET_KEY = SecureRandom.random_bytes(32)
|
||||
|
||||
def base64_encoding_regex
|
||||
|
|
|
|||
Loading…
Reference in New Issue