diff --git a/app/assets/javascripts/admin/users/components/user_type/access_summary_section.vue b/app/assets/javascripts/admin/users/components/user_type/access_summary_section.vue new file mode 100644 index 00000000000..2a934f65665 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/user_type/access_summary_section.vue @@ -0,0 +1,29 @@ + + + diff --git a/app/assets/javascripts/admin/users/components/user_type/user_type_selector.vue b/app/assets/javascripts/admin/users/components/user_type/user_type_selector.vue index 7621d4a395f..ba053ad0f2e 100644 --- a/app/assets/javascripts/admin/users/components/user_type/user_type_selector.vue +++ b/app/assets/javascripts/admin/users/components/user_type/user_type_selector.vue @@ -1,7 +1,10 @@ @@ -74,7 +117,7 @@ export default { - +
+ +

+ {{ s__('AdminUsers|Review and set Admin area access with a custom admin role.') }} +

+
+ + + + + + + + + + + + + + + + + + + + + +
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue index 371fb760573..50d90485763 100644 --- a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue +++ b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue @@ -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 { /> { 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), }, }); }, diff --git a/app/assets/javascripts/ci/runner/components/runner_status_badge.vue b/app/assets/javascripts/ci/runner/components/runner_status_badge.vue index f0e8b7b6f22..2791daaf349 100644 --- a/app/assets/javascripts/ci/runner/components/runner_status_badge.vue +++ b/app/assets/javascripts/ci/runner/components/runner_status_badge.vue @@ -120,6 +120,7 @@ export default { v-gl-tooltip="badge.tooltip" :variant="badge.variant" :icon="badge.icon" + icon-optically-aligned v-bind="$attrs" > diff --git a/app/assets/javascripts/docker_hub_rate_limits/index.js b/app/assets/javascripts/docker_hub_rate_limits/index.js deleted file mode 100644 index e5b7e67df67..00000000000 --- a/app/assets/javascripts/docker_hub_rate_limits/index.js +++ /dev/null @@ -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); - }, - }); -}; diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue index 3a8657c204f..377f7d25920 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue @@ -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 { + +
- - {{ $options.i18n.UPDATE_SETTINGS_SUCCESS_MESSAGE }} - { 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, {}), diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue index e87ef0a11a4..7f13ce598d3 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue @@ -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 ( diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue index af008538046..7cf99419779 100644 --- a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue +++ b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue @@ -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; diff --git a/app/assets/javascripts/vue_shared/components/docker_hub_rate_limits_alert.vue b/app/assets/javascripts/vue_shared/components/docker_hub_rate_limits_alert.vue deleted file mode 100644 index 2ebd4622690..00000000000 --- a/app/assets/javascripts/vue_shared/components/docker_hub_rate_limits_alert.vue +++ /dev/null @@ -1,45 +0,0 @@ - - diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb index 9194690ce82..6f8f77f0143 100644 --- a/app/controllers/projects/registry/repositories_controller.rb +++ b/app/controllers/projects/registry/repositories_controller.rb @@ -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 diff --git a/app/controllers/projects/settings/branch_rules_controller.rb b/app/controllers/projects/settings/branch_rules_controller.rb index 2ff58d9da18..ab537ea42b7 100644 --- a/app/controllers/projects/settings/branch_rules_controller.rb +++ b/app/controllers/projects/settings/branch_rules_controller.rb @@ -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 diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index a81023f0484..9bb71de9f83 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -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 diff --git a/app/graphql/mutations/projects/branch_rules/squash_options/update.rb b/app/graphql/mutations/projects/branch_rules/squash_options/update.rb index a1c3ed15f8d..8752f50ac41 100644 --- a/app/graphql/mutations/projects/branch_rules/squash_options/update.rb +++ b/app/graphql/mutations/projects/branch_rules/squash_options/update.rb @@ -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 diff --git a/app/graphql/resolvers/projects/branch_rules_resolver.rb b/app/graphql/resolvers/projects/branch_rules_resolver.rb index 7af4f550579..d73b1b7ef01 100644 --- a/app/graphql/resolvers/projects/branch_rules_resolver.rb +++ b/app/graphql/resolvers/projects/branch_rules_resolver.rb @@ -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 diff --git a/app/graphql/types/projects/branch_rule_type.rb b/app/graphql/types/projects/branch_rule_type.rb index cf97472d9fd..e0fde255844 100644 --- a/app/graphql/types/projects/branch_rule_type.rb +++ b/app/graphql/types/projects/branch_rule_type.rb @@ -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 diff --git a/app/graphql/types/projects/namespace_project_sort_enum.rb b/app/graphql/types/projects/namespace_project_sort_enum.rb index 6209b0cc87d..7feb9d64ebd 100644 --- a/app/graphql/types/projects/namespace_project_sort_enum.rb +++ b/app/graphql/types/projects/namespace_project_sort_enum.rb @@ -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 diff --git a/app/helpers/ci/pipeline_schedules_helper.rb b/app/helpers/ci/pipeline_schedules_helper.rb index 0a050f13913..1821e0976c6 100644 --- a/app/helpers/ci/pipeline_schedules_helper.rb +++ b/app/helpers/ci/pipeline_schedules_helper.rb @@ -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 diff --git a/app/models/concerns/gitlab/encrypted_attribute.rb b/app/models/concerns/gitlab/encrypted_attribute.rb index 2103a89ef87..331d092c672 100644 --- a/app/models/concerns/gitlab/encrypted_attribute.rb +++ b/app/models/concerns/gitlab/encrypted_attribute.rb @@ -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 diff --git a/app/models/project.rb b/app/models/project.rb index 3ca346d11e7..b33d6e75b35 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -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? diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb index b17f29403bc..726feae71a9 100644 --- a/app/models/project_ci_cd_setting.rb +++ b/app/models/project_ci_cd_setting.rb @@ -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) diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb index a10a9023b5b..48bba2637f3 100644 --- a/app/models/users/callout.rb +++ b/app/models/users/callout.rb @@ -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 diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 74177fa035c..f8c8f030326 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -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 diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index e4fd39e8991..17a2826458b 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -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 } } diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index dfd1f7361bb..7cde6d2ed2a 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -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', diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 7d072823dde..8b0f29da0fd 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -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 diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml index 2d96838e8b2..cdacf8c1982 100644 --- a/app/views/shared/issuable/form/_merge_params.html.haml +++ b/app/views/shared/issuable/form/_merge_params.html.haml @@ -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| diff --git a/app/workers/projects/inactive_projects_deletion_cron_worker.rb b/app/workers/projects/inactive_projects_deletion_cron_worker.rb index 075427bb27f..9aff5c346a1 100644 --- a/app/workers/projects/inactive_projects_deletion_cron_worker.rb +++ b/app/workers/projects/inactive_projects_deletion_cron_worker.rb @@ -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 diff --git a/config/feature_flags/beta/branch_rule_squash_settings.yml b/config/feature_flags/beta/branch_rule_squash_settings.yml deleted file mode 100644 index 4d67ebc5f10..00000000000 --- a/config/feature_flags/beta/branch_rule_squash_settings.yml +++ /dev/null @@ -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 diff --git a/config/feature_flags/ops/emit_db_transaction_sli_metrics.yml b/config/feature_flags/ops/emit_db_transaction_sli_metrics.yml index d2372243057..1bda62dd7dc 100644 --- a/config/feature_flags/ops/emit_db_transaction_sli_metrics.yml +++ b/config/feature_flags/ops/emit_db_transaction_sli_metrics.yml @@ -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 diff --git a/config/feature_flags/ops/emit_sidekiq_histogram_metrics.yml b/config/feature_flags/ops/emit_sidekiq_histogram_metrics.yml index 3433dc263cc..6120400b6ba 100644 --- a/config/feature_flags/ops/emit_sidekiq_histogram_metrics.yml +++ b/config/feature_flags/ops/emit_sidekiq_histogram_metrics.yml @@ -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 diff --git a/config/feature_flags/ops/enable_sidekiq_resource_usage_tracking.yml b/config/feature_flags/ops/enable_sidekiq_resource_usage_tracking.yml index f54adf3ad93..0bc924bc30b 100644 --- a/config/feature_flags/ops/enable_sidekiq_resource_usage_tracking.yml +++ b/config/feature_flags/ops/enable_sidekiq_resource_usage_tracking.yml @@ -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 diff --git a/config/feature_flags/ops/enable_sidekiq_shard_router.yml b/config/feature_flags/ops/enable_sidekiq_shard_router.yml index e2addadd950..9344e8900ac 100644 --- a/config/feature_flags/ops/enable_sidekiq_shard_router.yml +++ b/config/feature_flags/ops/enable_sidekiq_shard_router.yml @@ -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 diff --git a/config/feature_flags/ops/omit_aggregated_db_log_fields.yml b/config/feature_flags/ops/omit_aggregated_db_log_fields.yml index 7c17e4d2436..c9a0fcae44b 100644 --- a/config/feature_flags/ops/omit_aggregated_db_log_fields.yml +++ b/config/feature_flags/ops/omit_aggregated_db_log_fields.yml @@ -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 diff --git a/config/feature_flags/ops/remote_mirror_no_delay.yml b/config/feature_flags/ops/remote_mirror_no_delay.yml index 17937b35cf0..79b02a92328 100644 --- a/config/feature_flags/ops/remote_mirror_no_delay.yml +++ b/config/feature_flags/ops/remote_mirror_no_delay.yml @@ -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 diff --git a/config/feature_flags/ops/sample_pg_stat_activity.yml b/config/feature_flags/ops/sample_pg_stat_activity.yml index a3406ef15e0..55e3a2d63d7 100644 --- a/config/feature_flags/ops/sample_pg_stat_activity.yml +++ b/config/feature_flags/ops/sample_pg_stat_activity.yml @@ -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 diff --git a/data/deprecations/17-2-deprecation-rename-skip-gitguardian-scanning.yml b/data/deprecations/17-2-deprecation-rename-skip-gitguardian-scanning.yml index 1a451a4c69d..bc89148878a 100644 --- a/data/deprecations/17-2-deprecation-rename-skip-gitguardian-scanning.yml +++ b/data/deprecations/17-2-deprecation-rename-skip-gitguardian-scanning.yml @@ -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 diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index eb4616f5894..5f5bf542638 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -22202,7 +22202,7 @@ Branch rules configured for a rule target. | `isProtected` | [`Boolean!`](#boolean) | Check if the branch rule protects access for the branch. | | `matchingBranchesCount` | [`Int!`](#int) | Number of existing branches that match the branch rule. | | `name` | [`String!`](#string) | Name of the branch rule target. Includes wildcards. | -| `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. | +| `squashOption` {{< icon name="warning-solid" >}} | [`SquashOption`](#squashoption) | **Introduced** in GitLab 17.9. **Status**: Experiment. Default behavior for squashing in merge requests. | | `updatedAt` | [`Time`](#time) | Timestamp of when the branch rule was last updated. | ### `BurnupChartDailyTotals` @@ -43985,6 +43985,8 @@ Values for sorting projects. | `CONTAINER_REGISTRY_SIZE_ASC` | Sort by total container registry size, ascending order. | | `CONTAINER_REGISTRY_SIZE_DESC` | Sort by total container registry size, descending order. | | `EXCESS_REPO_STORAGE_SIZE_DESC` | Sort by excess repository storage size, descending order. | +| `FULL_PATH_ASC` | Sort by full path, ascending order. | +| `FULL_PATH_DESC` | Sort by full path, descending order. | | `LFS_OBJECTS_SIZE_ASC` | Sort by total LFS object size, ascending order. | | `LFS_OBJECTS_SIZE_DESC` | Sort by total LFS object size, descending order. | | `PACKAGES_SIZE_ASC` | Sort by total package size, ascending order. | @@ -45031,7 +45033,6 @@ Name of the feature that the callout is for. | `PRODUCT_ANALYTICS_DASHBOARD_FEEDBACK` | Callout feature name for product_analytics_dashboard_feedback. | | `PRODUCT_USAGE_DATA_COLLECTION_CHANGES` | Callout feature name for product_usage_data_collection_changes. | | `PROFILE_PERSONAL_ACCESS_TOKEN_EXPIRY` | Callout feature name for profile_personal_access_token_expiry. | -| `PROJECT_QUALITY_SUMMARY_FEEDBACK` | Callout feature name for project_quality_summary_feedback. | | `PROJECT_REPOSITORY_LIMIT_ALERT_WARNING_THRESHOLD` | Callout feature name for project_repository_limit_alert_warning_threshold. | | `REGISTRATION_ENABLED_CALLOUT` | Callout feature name for registration_enabled_callout. | | `SECURITY_CONFIGURATION_DEVOPS_ALERT` | Callout feature name for security_configuration_devops_alert. | diff --git a/doc/development/ai_features/ai_development_license.md b/doc/development/ai_features/ai_development_license.md index 83f688cd72f..ce559061dc8 100644 --- a/doc/development/ai_features/ai_development_license.md +++ b/doc/development/ai_features/ai_development_license.md @@ -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 diff --git a/doc/development/documentation/styleguide/_index.md b/doc/development/documentation/styleguide/_index.md index f59fb7fe71f..69255b9901b 100644 --- a/doc/development/documentation/styleguide/_index.md +++ b/doc/development/documentation/styleguide/_index.md @@ -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 Tab key. +- Do not use tabs for indentation. Use spaces instead. Consider configuring your code editor to output spaces instead of tabs when pressing the Tab 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, `{{}}` 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** ({{}}).` 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 {{}} diff --git a/doc/development/rubocop_development_guide.md b/doc/development/rubocop_development_guide.md index c0caaea42df..a5f9b643892 100644 --- a/doc/development/rubocop_development_guide.md +++ b/doc/development/rubocop_development_guide.md @@ -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 . + +The site includes all the internal cops from the `gitlab` and `gitlab-styles` projects, along with "good" and "bad" examples. diff --git a/doc/security/asset_proxy.md b/doc/security/asset_proxy.md index af255fd6ecb..51a02bbeefa 100644 --- a/doc/security/asset_proxy.md +++ b/doc/security/asset_proxy.md @@ -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 --- diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 53d74b5127f..7f7544520dc 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -1722,6 +1722,22 @@ In 18.0 we are removing the `duoProAssignedUsersCount` GraphQL field. Users may
+
+ +### Rename options to skip GitGuardian secret detection + +
+ +- Announced in GitLab 17.3 +- Removal in GitLab 18.0 +- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/470119). + +
+ +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. + +
+
### 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
-### Rename options to skip GitGuardian secret detection - -
- -- Announced in GitLab 17.3 -- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/470119). - -
-{{< 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. - -
- -
- ### SAST jobs no longer use global cache settings
diff --git a/doc/user/application_security/dast/browser/checks/79.1.md b/doc/user/application_security/dast/browser/checks/79.1.md index 039cc67785d..274fff0403c 100644 --- a/doc/user/application_security/dast/browser/checks/79.1.md +++ b/doc/user/application_security/dast/browser/checks/79.1.md @@ -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 `
context 1
` +- Inside attributes: ```
``` +- Inside event attributes `````` +- Inside script blocks: `````` +- Unsafe element HTML assignment: ```element.innerHTML = "context 5"``` +- Inside URLs: ```link``` + +Script blocks alone can be encoded in multiple ways. Exercise caution if user +input must be written outside script tags. ## Remediation -Input validation: implement strict input validation for all user-controlled data. Use allowlist approaches -rather than denylists when validating input. Validate data type, length, and range as appropriate. +User input displayed in the application must be encoded, sanitized, or validated +so it isn't treated as HTML or executed as JavaScript code. Be careful not to +mix server-side templating with client-side templating, because the server-side doesn't encode +text like `{{ 7*7 }}`, which might execute client-side features. -Output encoding: apply context-specific output encoding when rendering user input. Use HTML entity -encoding for HTML contexts. Use JavaScript string escaping for JS context. CSS hex encoding should be -used for style attributes. +Do not encode user input before inserting it into a data store. The data must be +encoded based on its output context. It is much safer to force the displaying system to +handle the encoding. -Content Security Policy: implement a strict CSP that specifies trusted sources for scripts and other -resources. User `script-src 'self'` to restrict execution to same-origin source. Consider using nonces -or hashes for inline scripts when necessary. +Consider using built-in framework capabilities for automatically encoding user input. If you can't +automatically encode input, be careful to use the proper output encoding. The following recommendations +are a best effort, and might not work in all circumstances. -Framework Protections: leverage built-in XSS protections in modern frameworks. Use template engines -that automatically escape output. Avoid unsafe methods that bypass framework protections -(for example, `innerHTML`, `dangerouslySetInnerHTML`). - -HTTP Headers: set X-XSS-Protection headers for legacy browsers. Implement X-Content-Type-Options: nosniff. -Use Strict-Transport-Security to enforce HTTPS. +- Encode the following inside HTML tags, *excluding* `script`: + - `<` to `<` + - `>` to `>` + - `'` to `'` + - `"` to `"` + - `=` to `=` +- Encode the following inside attributes, *excluding* event attributes: + - `<` to `<` + - `>` to `>` + - `'` to `'` + - `"` to `"` + - `=` to `=` +- Encode the following inside event attributes, script blocks, and unsafe HTML assignment: + - Literal tab (`\t`) to `\\t` + - Literal new line (`\n`) to `\\n` + - Literal vertical tab (`\v`) to `\u000b` + - Literal form feed (`\f`) to `\\f` + - Literal carriage return (`\r`) to `\\r` + - Literal equal sign (`=`) to `\u0061` + - Literal back tick (`\`) to `\u0060` + - Literal double quote (`"`) to `\u0022` + - Literal ampersand (`&`) to `\u0026` + - Literal single quote (`'`) to `\u0027` + - Literal plus symbol (`+`) to `\u002b` + - Literal forward slash (`/`) to `\/` + - Literal less than symbol (`<`) to `\u003c` + - Literal greater than symbol (`>`) to `\u003e` + - Literal open parenthesis (`(`) to `\u0028` + - Literal close parenthesis (`)`) to `\u0029` + - Literal open bracket (`[`) to `\u005b` + - Literal close bracket (`]`) to `\u005d` + - Literal open brace (`{`) to `\u007b` + - Literal close brace (`}`) to `\u007d` + - Literal back slash (`\`) to `\\` + + This list is not exhaustive. You might need to encode additional characters depending on context. +- Inside URLs: + - Never allow user input to be printed in URLs. Attackers could inject `javascript:...` code or malicious links. ## Details @@ -40,5 +83,5 @@ Use Strict-Transport-Security to enforce HTTPS. ## Links +- [OWASP](https://owasp.org/www-community/attacks/xss/) - [CWE](https://cwe.mitre.org/data/definitions/79.html) -- [Cross-site Scripting - Wikipedia](https://en.wikipedia.org/wiki/Cross-site_scripting) diff --git a/doc/user/application_security/dast/browser/checks/_index.md b/doc/user/application_security/dast/browser/checks/_index.md index 9196152e440..74dba0544a8 100644 --- a/doc/user/application_security/dast/browser/checks/_index.md +++ b/doc/user/application_security/dast/browser/checks/_index.md @@ -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 | diff --git a/doc/user/application_security/dast/on-demand_scan.md b/doc/user/application_security/dast/on-demand_scan.md index 7cd0ff7ad66..045fea3f07b 100644 --- a/doc/user/application_security/dast/on-demand_scan.md +++ b/doc/user/application_security/dast/on-demand_scan.md @@ -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: diff --git a/doc/user/application_security/detect/_index.md b/doc/user/application_security/detect/_index.md index 8c1f9aa1e83..acc1d2be9ae 100644 --- a/doc/user/application_security/detect/_index.md +++ b/doc/user/application_security/detect/_index.md @@ -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. diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index 7a465199006..947a463e41c 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -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 | diff --git a/doc/user/profile/service_accounts.md b/doc/user/profile/service_accounts.md index 266df7ae5ae..20f394166d4 100644 --- a/doc/user/profile/service_accounts.md +++ b/doc/user/profile/service_accounts.md @@ -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 diff --git a/doc/user/project/pages/getting_started/pages_new_project_template.md b/doc/user/project/pages/getting_started/pages_new_project_template.md index c69165864e6..eb360de4178 100644 --- a/doc/user/project/pages/getting_started/pages_new_project_template.md +++ b/doc/user/project/pages/getting_started/pages_new_project_template.md @@ -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 | diff --git a/doc/user/project/pages/img/pages_project_templates_v13_1.png b/doc/user/project/pages/img/pages_project_templates_v13_1.png deleted file mode 100644 index d67a82f0623..00000000000 Binary files a/doc/user/project/pages/img/pages_project_templates_v13_1.png and /dev/null differ diff --git a/doc/user/project/repository/branches/branch_rules.md b/doc/user/project/repository/branches/branch_rules.md index c9d44a338e1..b192e4d50e9 100644 --- a/doc/user/project/repository/branches/branch_rules.md +++ b/doc/user/project/repository/branches/branch_rules.md @@ -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. diff --git a/lib/ci/pipeline_creation/inputs/string_input.rb b/lib/ci/pipeline_creation/inputs/string_input.rb index bb5ca29efc8..7ea9746ade5 100644 --- a/lib/ci/pipeline_creation/inputs/string_input.rb +++ b/lib/ci/pipeline_creation/inputs/string_input.rb @@ -54,7 +54,7 @@ module Ci override :coerced_value def coerced_value(value) - super.to_s + value.to_s end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 34c433d102a..1779817f9fb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/frontend/admin/users/components/user_type/user_type_selector_spec.js b/spec/frontend/admin/users/components/user_type/user_type_selector_spec.js index c6b8ff09b61..74f08e6fd7b 100644 --- a/spec/frontend/admin/users/components/user_type/user_type_selector_spec.js +++ b/spec/frontend/admin/users/components/user_type/user_type_selector_spec.js @@ -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: `
@@ -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.'); + }); + }); }); }); diff --git a/spec/frontend/ci/job_details/mock_data.js b/spec/frontend/ci/job_details/mock_data.js index 5bc517fe0f9..3e98940ad59 100644 --- a/spec/frontend/ci/job_details/mock_data.js +++ b/spec/frontend/ci/job_details/mock_data.js @@ -130,14 +130,3 @@ export const mockPipelineVariablesPermissions = (value) => ({ }, }, }); - -export const minimumRoleResponse = { - data: { - project: { - id: mockId, - ciCdSettings: { - pipelineVariablesMinimumOverrideRole: 'developer', - }, - }, - }, -}; diff --git a/spec/frontend/ci/pipeline_schedules/components/pipeline_schedules_form_spec.js b/spec/frontend/ci/pipeline_schedules/components/pipeline_schedules_form_spec.js index 88fff395d2b..8dd7090df62 100644 --- a/spec/frontend/ci/pipeline_schedules/components/pipeline_schedules_form_spec.js +++ b/spec/frontend/ci/pipeline_schedules/components/pipeline_schedules_form_spec.js @@ -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); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js index 93d09871344..b3db2b55872 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js @@ -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(); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js index 5a0e05e9186..831d1e9e212 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js @@ -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', }, ]; diff --git a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js index c2d4e340dd4..3221041d1a0 100644 --- a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js @@ -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'); }); }); diff --git a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js index 906e751f0b8..e2c486708ff 100644 --- a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js +++ b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js @@ -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'); }); }); diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb index c05dc6eae33..f643753bdc8 100644 --- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb @@ -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) } diff --git a/spec/graphql/types/namespace_project_sort_enum_spec.rb b/spec/graphql/types/namespace_project_sort_enum_spec.rb index c9c821a9fa6..a8ac907e8af 100644 --- a/spec/graphql/types/namespace_project_sort_enum_spec.rb +++ b/spec/graphql/types/namespace_project_sort_enum_spec.rb @@ -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 diff --git a/spec/helpers/ci/pipeline_schedules_helper_spec.rb b/spec/helpers/ci/pipeline_schedules_helper_spec.rb index 6ff2b09e8ab..f0f9e0bc840 100644 --- a/spec/helpers/ci/pipeline_schedules_helper_spec.rb +++ b/spec/helpers/ci/pipeline_schedules_helper_spec.rb @@ -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 diff --git a/spec/lib/ci/pipeline_creation/inputs/spec_inputs_spec.rb b/spec/lib/ci/pipeline_creation/inputs/spec_inputs_spec.rb index 6d6d97d9e43..18e0fd3a25f 100644 --- a/spec/lib/ci/pipeline_creation/inputs/spec_inputs_spec.rb +++ b/spec/lib/ci/pipeline_creation/inputs/spec_inputs_spec.rb @@ -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 diff --git a/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb index ba23a6c8df7..c8dd2ad00a8 100644 --- a/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb @@ -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 diff --git a/spec/models/concerns/gitlab/encrypted_attribute_spec.rb b/spec/models/concerns/gitlab/encrypted_attribute_spec.rb index 133d8da7c0f..04770b85ccd 100644 --- a/spec/models/concerns/gitlab/encrypted_attribute_spec.rb +++ b/spec/models/concerns/gitlab/encrypted_attribute_spec.rb @@ -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 diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1b53d3f0d31..a849420c004 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -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) diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 6885ed44339..5c6f7437fed 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -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 diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb index c1025f34ca2..4fd0615d58f 100644 --- a/spec/requests/api/ci/pipelines_spec.rb +++ b/spec/requests/api/ci/pipelines_spec.rb @@ -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' diff --git a/spec/requests/api/ci/triggers_spec.rb b/spec/requests/api/ci/triggers_spec.rb index 330e271d3a1..0311666f3ce 100644 --- a/spec/requests/api/ci/triggers_spec.rb +++ b/spec/requests/api/ci/triggers_spec.rb @@ -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' diff --git a/spec/requests/api/graphql/mutations/projects/branch_rules/squash_options/update_spec.rb b/spec/requests/api/graphql/mutations/projects/branch_rules/squash_options/update_spec.rb index 65495371e99..7b5c25c5ca7 100644 --- a/spec/requests/api/graphql/mutations/projects/branch_rules/squash_options/update_spec.rb +++ b/spec/requests/api/graphql/mutations/projects/branch_rules/squash_options/update_spec.rb @@ -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 diff --git a/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb b/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb index c45b8da2792..d7672cc5116 100644 --- a/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb +++ b/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb @@ -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 diff --git a/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb b/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb index 28594b61d2e..65b9bc93a6b 100644 --- a/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb +++ b/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb @@ -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 diff --git a/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb b/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb index 37939404634..bc699904db5 100644 --- a/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb +++ b/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb @@ -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 diff --git a/spec/requests/api/graphql/project/branch_rules_spec.rb b/spec/requests/api/graphql/project/branch_rules_spec.rb index d948d0a01bc..397c1000797 100644 --- a/spec/requests/api/graphql/project/branch_rules_spec.rb +++ b/spec/requests/api/graphql/project/branch_rules_spec.rb @@ -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 diff --git a/spec/requests/projects/registry/repositories_controller_spec.rb b/spec/requests/projects/registry/repositories_controller_spec.rb index 15adb7398f3..0c0b91ee47c 100644 --- a/spec/requests/projects/registry/repositories_controller_spec.rb +++ b/spec/requests/projects/registry/repositories_controller_spec.rb @@ -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 diff --git a/spec/workers/projects/inactive_projects_deletion_cron_worker_spec.rb b/spec/workers/projects/inactive_projects_deletion_cron_worker_spec.rb index 208ca500e82..d3ae2f04971 100644 --- a/spec/workers/projects/inactive_projects_deletion_cron_worker_spec.rb +++ b/spec/workers/projects/inactive_projects_deletion_cron_worker_spec.rb @@ -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 diff --git a/vendor/gems/attr_encrypted/lib/attr_encrypted.rb b/vendor/gems/attr_encrypted/lib/attr_encrypted.rb index 278a8fa7954..3e398b934f9 100644 --- a/vendor/gems/attr_encrypted/lib/attr_encrypted.rb +++ b/vendor/gems/attr_encrypted/lib/attr_encrypted.rb @@ -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 diff --git a/vendor/gems/attr_encrypted/test/attr_encrypted_test.rb b/vendor/gems/attr_encrypted/test/attr_encrypted_test.rb index ed1b998a64c..a57bd773b92 100644 --- a/vendor/gems/attr_encrypted/test/attr_encrypted_test.rb +++ b/vendor/gems/attr_encrypted/test/attr_encrypted_test.rb @@ -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 diff --git a/vendor/gems/attr_encrypted/test/test_helper.rb b/vendor/gems/attr_encrypted/test/test_helper.rb index d418813a81c..75d7a6d9a20 100644 --- a/vendor/gems/attr_encrypted/test/test_helper.rb +++ b/vendor/gems/attr_encrypted/test/test_helper.rb @@ -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