Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-04-14 18:12:09 +00:00
parent 34b7bee25c
commit 3b328dfc1d
86 changed files with 1007 additions and 637 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -42,6 +42,7 @@ query getContainerRepositoryTags(
protection {
minimumAccessLevelForPush
minimumAccessLevelForDelete
immutable
}
referrers {
artifactType

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 `&lt;`
- `>` to `&gt;`
- `'` to `&apos;`
- `"` to `&quot;`
- `=` to `&#61;`
- Encode the following inside attributes, *excluding* event attributes:
- `<` to `&lt;`
- `>` to `&gt;`
- `'` to `&apos;`
- `"` to `&quot;`
- `=` to `&#61;`
- 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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**.
![Project templates for Pages](../img/pages_project_templates_v13_1.png)
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

View File

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

View File

@ -54,7 +54,7 @@ module Ci
override :coerced_value
def coerced_value(value)
super.to_s
value.to_s
end
end
end

View File

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

View File

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

View File

@ -130,14 +130,3 @@ export const mockPipelineVariablesPermissions = (value) => ({
},
},
});
export const minimumRoleResponse = {
data: {
project: {
id: mockId,
ciCdSettings: {
pipelineVariablesMinimumOverrideRole: 'developer',
},
},
},
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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