Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									fa91163a5b
								
							
						
					
					
						commit
						5f5a1d09aa
					
				|  | @ -213,7 +213,7 @@ semgrep-appsec-custom-rules: | |||
|   stage: lint | ||||
|   extends: | ||||
|     - .semgrep-appsec-custom-rules:rules | ||||
|   image: returntocorp/semgrep | ||||
|   image: returntocorp/semgrep:1.99.0 | ||||
|   needs: [] | ||||
|   script: | ||||
|     - git fetch origin master | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ Layout/LineLength: | |||
|     - 'app/controllers/application_controller.rb' | ||||
|     - 'app/controllers/groups/milestones_controller.rb' | ||||
|     - 'app/controllers/projects/issues_controller.rb' | ||||
|     - 'app/controllers/projects/jobs_controller.rb' | ||||
|     - 'app/controllers/projects/labels_controller.rb' | ||||
|     - 'app/controllers/projects/milestones_controller.rb' | ||||
|     - 'app/controllers/projects/notes_controller.rb' | ||||
|  |  | |||
|  | @ -2,14 +2,6 @@ | |||
| # Cop supports --autocorrect. | ||||
| Style/StringConcatenation: | ||||
|   Exclude: | ||||
|     - 'app/controllers/projects/labels_controller.rb' | ||||
|     - 'app/controllers/projects/milestones_controller.rb' | ||||
|     - 'app/models/concerns/cross_database_modification.rb' | ||||
|     - 'app/models/concerns/from_set_operator.rb' | ||||
|     - 'app/models/concerns/routable.rb' | ||||
|     - 'app/models/integrations/chat_message/merge_message.rb' | ||||
|     - 'app/models/integrations/chat_message/note_message.rb' | ||||
|     - 'app/models/namespace.rb' | ||||
|     - 'app/models/packages/go/module_version.rb' | ||||
|     - 'app/models/pool_repository.rb' | ||||
|     - 'app/models/project_wiki.rb' | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| db3ec43472f41609c94a3f0cf5382b26360638e1 | ||||
| 53cd0a7f9b29bb9c76e294e25e15c2597bcc8800 | ||||
|  |  | |||
|  | @ -8,6 +8,16 @@ | |||
|       "type": "string", | ||||
|       "format": "uri" | ||||
|     }, | ||||
|     "spec": { | ||||
|       "type": "object", | ||||
|       "markdownDescription": "Specification for pipeline configuration. Must be declared at the top of a configuration file, in a header section separated from the rest of the configuration with `---`. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#spec).", | ||||
|       "properties": { | ||||
|         "inputs": { | ||||
|           "$ref": "#/definitions/inputParameters" | ||||
|         } | ||||
|       }, | ||||
|       "additionalProperties": false | ||||
|     }, | ||||
|     "image": { | ||||
|       "$ref": "#/definitions/image", | ||||
|       "markdownDescription": "Defining `image` globally is deprecated. Use [`default`](https://docs.gitlab.com/ee/ci/yaml/#default) instead. [Learn more](https://docs.gitlab.com/ee/ci/yaml/#globally-defined-image-services-cache-before_script-after_script)." | ||||
|  | @ -389,6 +399,156 @@ | |||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     "inputParameters": { | ||||
|       "type": "object", | ||||
|       "markdownDescription": "Define parameters that can be populated in reusable CI/CD configuration files when added to a pipeline. [Learn More](https://docs.gitlab.com/ee/ci/yaml/inputs).", | ||||
|       "patternProperties": { | ||||
|         ".*": { | ||||
|           "markdownDescription": "**Input Configuration**\n\nAvailable properties:\n- `type`: string (default), array, boolean, or number\n- `description`: Human-readable explanation of the parameter (supports Markdown)\n- `options`: List of allowed values\n- `default`: Value to use when not specified (makes input optional)\n- `regex`: Pattern that string values must match", | ||||
|           "oneOf": [ | ||||
|             { | ||||
|               "type": "object", | ||||
|               "properties": { | ||||
|                 "type": { | ||||
|                   "type": "string", | ||||
|                   "markdownDescription": "Force a specific input type. Defaults to 'string' when not specified. [Learn More](https://docs.gitlab.com/ee/ci/yaml/inputs/#input-types).", | ||||
|                   "enum": [ | ||||
|                     "array", | ||||
|                     "boolean", | ||||
|                     "number", | ||||
|                     "string" | ||||
|                   ], | ||||
|                   "default": "string" | ||||
|                 }, | ||||
|                 "description": { | ||||
|                   "type": "string", | ||||
|                   "markdownDescription": "Give a description to a specific input. The description does not affect the input, but can help people understand the input details or expected values. Supports markdown.", | ||||
|                   "maxLength": 1024 | ||||
|                 }, | ||||
|                 "options": { | ||||
|                   "type": "array", | ||||
|                   "markdownDescription": "Specify a list of allowed values for an input.", | ||||
|                   "items": { | ||||
|                     "oneOf": [ | ||||
|                       { | ||||
|                         "type": "string" | ||||
|                       }, | ||||
|                       { | ||||
|                         "type": "number" | ||||
|                       }, | ||||
|                       { | ||||
|                         "type": "boolean" | ||||
|                       } | ||||
|                     ] | ||||
|                   } | ||||
|                 }, | ||||
|                 "regex": { | ||||
|                   "type": "string", | ||||
|                   "markdownDescription": "Specify a regular expression that the input must match. Only impacts inputs with a `type` of `string`." | ||||
|                 }, | ||||
|                 "default": { | ||||
|                   "markdownDescription": "Define default values for inputs when not specified. When you specify a default, the inputs are no longer mandatory." | ||||
|                 } | ||||
|               }, | ||||
|               "allOf": [ | ||||
|                 { | ||||
|                   "if": { | ||||
|                     "properties": { | ||||
|                       "type": { | ||||
|                         "enum": [ | ||||
|                           "string" | ||||
|                         ] | ||||
|                       } | ||||
|                     } | ||||
|                   }, | ||||
|                   "then": { | ||||
|                     "properties": { | ||||
|                       "default": { | ||||
|                         "type": [ | ||||
|                           "string", | ||||
|                           "null" | ||||
|                         ] | ||||
|                       } | ||||
|                     } | ||||
|                   } | ||||
|                 }, | ||||
|                 { | ||||
|                   "if": { | ||||
|                     "properties": { | ||||
|                       "type": { | ||||
|                         "enum": [ | ||||
|                           "number" | ||||
|                         ] | ||||
|                       } | ||||
|                     } | ||||
|                   }, | ||||
|                   "then": { | ||||
|                     "properties": { | ||||
|                       "default": { | ||||
|                         "type": [ | ||||
|                           "number", | ||||
|                           "null" | ||||
|                         ] | ||||
|                       } | ||||
|                     } | ||||
|                   } | ||||
|                 }, | ||||
|                 { | ||||
|                   "if": { | ||||
|                     "properties": { | ||||
|                       "type": { | ||||
|                         "enum": [ | ||||
|                           "boolean" | ||||
|                         ] | ||||
|                       } | ||||
|                     } | ||||
|                   }, | ||||
|                   "then": { | ||||
|                     "properties": { | ||||
|                       "default": { | ||||
|                         "type": [ | ||||
|                           "boolean", | ||||
|                           "null" | ||||
|                         ] | ||||
|                       } | ||||
|                     } | ||||
|                   } | ||||
|                 }, | ||||
|                 { | ||||
|                   "if": { | ||||
|                     "properties": { | ||||
|                       "type": { | ||||
|                         "enum": [ | ||||
|                           "array" | ||||
|                         ] | ||||
|                       } | ||||
|                     } | ||||
|                   }, | ||||
|                   "then": { | ||||
|                     "properties": { | ||||
|                       "default": { | ||||
|                         "oneOf": [ | ||||
|                           { | ||||
|                             "type": "array" | ||||
|                           }, | ||||
|                           { | ||||
|                             "type": "null" | ||||
|                           } | ||||
|                         ] | ||||
|                       } | ||||
|                     } | ||||
|                   } | ||||
|                 } | ||||
|               ], | ||||
|               "additionalProperties": false | ||||
|             }, | ||||
|             { | ||||
|               "type": "null" | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "include_item": { | ||||
|       "oneOf": [ | ||||
|         { | ||||
|  | @ -1760,8 +1920,59 @@ | |||
|       "default": false | ||||
|     }, | ||||
|     "inputs": { | ||||
|       "markdownDescription": "Used to pass input values to included templates or components. [Learn More](https://docs.gitlab.com/ee/ci/yaml/inputs.html#set-input-values-when-using-include).", | ||||
|       "type": "object" | ||||
|       "markdownDescription": "Used to pass input values to included templates, components, downstream pipelines, or child pipelines. [Learn More](https://docs.gitlab.com/ee/ci/yaml/inputs.html).", | ||||
|       "type": "object", | ||||
|       "patternProperties": { | ||||
|         "^[a-zA-Z0-9_-]+$": { | ||||
|           "description": "Input parameter value that matches parameter names defined in spec:inputs of the included configuration.", | ||||
|           "oneOf": [ | ||||
|             { | ||||
|               "type": "string", | ||||
|               "maxLength": 1024 | ||||
|             }, | ||||
|             { | ||||
|               "type": "number" | ||||
|             }, | ||||
|             { | ||||
|               "type": "boolean" | ||||
|             }, | ||||
|             { | ||||
|               "type": "array", | ||||
|               "items": { | ||||
|                 "oneOf": [ | ||||
|                   { | ||||
|                     "type": "string" | ||||
|                   }, | ||||
|                   { | ||||
|                     "type": "number" | ||||
|                   }, | ||||
|                   { | ||||
|                     "type": "boolean" | ||||
|                   }, | ||||
|                   { | ||||
|                     "type": "object", | ||||
|                     "additionalProperties": true | ||||
|                   }, | ||||
|                   { | ||||
|                     "type": "array", | ||||
|                     "items": { | ||||
|                       "additionalProperties": true | ||||
|                     } | ||||
|                   } | ||||
|                 ] | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "type": "object", | ||||
|               "additionalProperties": true | ||||
|             }, | ||||
|             { | ||||
|               "type": "null" | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|       }, | ||||
|       "additionalProperties": false | ||||
|     }, | ||||
|     "job": { | ||||
|       "allOf": [ | ||||
|  |  | |||
|  | @ -9,24 +9,20 @@ import { | |||
|   I18N_ERROR_MESSAGE, | ||||
| } from './constants'; | ||||
| 
 | ||||
| export const initJwtCiCdJobTokenEnabledToggle = () => { | ||||
|   const toggle = () => { | ||||
|     const toggleButton = document.querySelector('.js-jwt-ci-cd-job-token-enabled-toggle button'); | ||||
| 
 | ||||
|     toggleButton.click(); | ||||
|   }; | ||||
| 
 | ||||
| export const initSettingsToggles = () => { | ||||
|   let toastMessage = {}; | ||||
|   const displayToast = (message, options = {}) => { | ||||
|     toastMessage.hide?.(); | ||||
|     toastMessage = toast(message, options); | ||||
|   }; | ||||
| 
 | ||||
|   const el = document.querySelector('.js-jwt-ci-cd-job-token-enabled-toggle'); | ||||
|   const input = document.querySelector('.js-jwt-ci-cd-job-token-enabled-input'); | ||||
|   const elements = document.querySelectorAll('.js-setting-toggle'); | ||||
|   if (!elements.length) return null; | ||||
| 
 | ||||
|   if (el && input) { | ||||
|   return Array.from(elements).map((el) => { | ||||
|     const form = el.closest('form'); | ||||
|     const input = form.querySelector('.js-setting-input'); | ||||
|     const toggleButton = el.querySelector('button'); | ||||
|     const toggleElement = initToggle(el); | ||||
| 
 | ||||
|     toggleElement.$on('change', async (isEnabled) => { | ||||
|  | @ -43,7 +39,7 @@ export const initJwtCiCdJobTokenEnabledToggle = () => { | |||
|         displayToast(I18N_SUCCESS_MESSAGE, { | ||||
|           action: { | ||||
|             text: I18N_UNDO_ACTION_TEXT, | ||||
|             onClick: toggle, | ||||
|             onClick: () => toggleButton.click(), | ||||
|           }, | ||||
|         }); | ||||
|       } catch (_) { | ||||
|  | @ -53,7 +49,7 @@ export const initJwtCiCdJobTokenEnabledToggle = () => { | |||
|         displayToast(I18N_ERROR_MESSAGE, { | ||||
|           action: { | ||||
|             text: I18N_RETRY_ACTION_TEXT, | ||||
|             onClick: toggle, | ||||
|             onClick: () => toggleButton.click(), | ||||
|           }, | ||||
|         }); | ||||
|       } finally { | ||||
|  | @ -62,7 +58,5 @@ export const initJwtCiCdJobTokenEnabledToggle = () => { | |||
|     }); | ||||
| 
 | ||||
|     return toggleElement; | ||||
|   } | ||||
| 
 | ||||
|   return null; | ||||
|   }); | ||||
| }; | ||||
|  | @ -1,6 +1,6 @@ | |||
| import initStaleRunnerCleanupSetting from 'ee_else_ce/group_settings/stale_runner_cleanup'; | ||||
| import { initAllowRunnerRegistrationTokenToggle } from '~/group_settings/allow_runner_registration_token_toggle'; | ||||
| import { initJwtCiCdJobTokenEnabledToggle } from '~/group_settings/jwt_ci_cd_job_token_enabled_toggle'; | ||||
| import { initSettingsToggles } from '~/group_settings/settings_toggles'; | ||||
| 
 | ||||
| import initPipelineVariablesDefaultRole from '~/group_settings/pipeline_variables_default_role'; | ||||
| import initVariableList from '~/ci/ci_variable_list'; | ||||
|  | @ -12,7 +12,7 @@ import initDeployTokens from '~/deploy_tokens'; | |||
| initSettingsPanels(); | ||||
| initDeployTokens(); | ||||
| initAllowRunnerRegistrationTokenToggle(); | ||||
| initJwtCiCdJobTokenEnabledToggle(); | ||||
| initSettingsToggles(); | ||||
| initSharedRunnersForm(); | ||||
| initStaleRunnerCleanupSetting(); | ||||
| initVariableList(); | ||||
|  |  | |||
|  | @ -98,7 +98,8 @@ module Groups | |||
|         params.require(:group).permit( | ||||
|           :max_artifacts_size, | ||||
|           :allow_runner_registration_token, | ||||
|           :jwt_ci_cd_job_token_enabled | ||||
|           :jwt_ci_cd_job_token_enabled, | ||||
|           :job_token_policies_enabled | ||||
|         ) | ||||
|       end | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,8 @@ class Projects::JobsController < Projects::ApplicationController | |||
|   include ContinueParams | ||||
|   include ProjectStatsRefreshConflictsGuard | ||||
| 
 | ||||
|   urgency :low, [:index, :show, :trace, :retry, :play, :cancel, :unschedule, :erase, :viewer, :raw, :test_report_summary] | ||||
|   urgency :low, | ||||
|     [:index, :show, :trace, :retry, :play, :cancel, :unschedule, :erase, :viewer, :raw, :test_report_summary] | ||||
| 
 | ||||
|   before_action :find_job_as_build, except: [:index, :play, :retry, :show] | ||||
|   before_action :find_job_as_processable, only: [:play, :retry, :show] | ||||
|  | @ -149,7 +150,8 @@ class Projects::JobsController < Projects::ApplicationController | |||
|   def raw | ||||
|     if @build.trace.archived? | ||||
|       workhorse_set_content_type! | ||||
|       send_upload(@build.job_artifacts_trace.file, send_params: raw_send_params, redirect_params: raw_redirect_params, proxy: params[:proxy]) | ||||
|       send_upload(@build.job_artifacts_trace.file, send_params: raw_send_params, redirect_params: raw_redirect_params, | ||||
|         proxy: params[:proxy]) | ||||
|     else | ||||
|       @build.trace.read do |stream| | ||||
|         if stream.file? | ||||
|  | @ -161,7 +163,8 @@ class Projects::JobsController < Projects::ApplicationController | |||
|           # to the user but, because we have the trace content, we can calculate | ||||
|           # the proper content type and disposition here. | ||||
|           raw_data = stream.raw | ||||
|           send_data raw_data, type: 'text/plain; charset=utf-8', disposition: raw_trace_content_disposition(raw_data), filename: 'job.log' | ||||
|           send_data raw_data, type: 'text/plain; charset=utf-8', disposition: raw_trace_content_disposition(raw_data), | ||||
|             filename: 'job.log' | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  |  | |||
|  | @ -148,7 +148,8 @@ class Projects::LabelsController < Projects::ApplicationController | |||
|   end | ||||
| 
 | ||||
|   def flash_notice_for(label, group) | ||||
|     ''.html_safe + "#{label.title} promoted to " + view_context.link_to('<u>group label</u>'.html_safe, group_labels_path(group)) + '.' | ||||
|     safe_link = view_context.link_to('<u>group label</u>'.html_safe, group_labels_path(group)) | ||||
|     view_context.safe_join([ERB::Util.html_escape(label.title), " promoted to ", safe_link, "."]) | ||||
|   end | ||||
| 
 | ||||
|   protected | ||||
|  |  | |||
|  | @ -131,7 +131,8 @@ class Projects::MilestonesController < Projects::ApplicationController | |||
|   end | ||||
| 
 | ||||
|   def flash_notice_for(milestone, group) | ||||
|     ''.html_safe + "#{milestone.title} promoted to " + view_context.link_to('<u>group milestone</u>'.html_safe, group_milestone_path(group, milestone.iid)) + '.' | ||||
|     safe_link = view_context.link_to('<u>group milestone</u>'.html_safe, group_milestone_path(group, milestone.iid)) | ||||
|     view_context.safe_join([ERB::Util.html_escape(milestone.title), " promoted to ", safe_link, "."]) | ||||
|   end | ||||
| 
 | ||||
|   def destroy | ||||
|  |  | |||
|  | @ -9,7 +9,8 @@ module Mutations | |||
|         :extensions_marketplace_opt_in_status, | ||||
|         :organization_groups_projects_display, | ||||
|         :visibility_pipeline_id_type, | ||||
|         :use_work_items_view | ||||
|         :use_work_items_view, | ||||
|         :merge_request_dashboard_list_type | ||||
|       ].freeze | ||||
| 
 | ||||
|       argument :extensions_marketplace_opt_in_status, Types::ExtensionsMarketplaceOptInStatusEnum, | ||||
|  | @ -18,6 +19,9 @@ module Mutations | |||
|       argument :issues_sort, Types::IssueSortEnum, | ||||
|         required: false, | ||||
|         description: 'Sort order for issue lists.' | ||||
|       argument :merge_request_dashboard_list_type, Types::MergeRequests::DashboardListTypeEnum, | ||||
|         required: false, | ||||
|         description: 'Merge request dashboard list rendering type.' | ||||
|       argument :merge_requests_sort, Types::MergeRequestSortEnum, | ||||
|         required: false, | ||||
|         description: 'Sort order for issue lists.' | ||||
|  |  | |||
|  | @ -0,0 +1,13 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module Types | ||||
|   module MergeRequests | ||||
|     class DashboardListTypeEnum < BaseEnum | ||||
|       graphql_name 'MergeRequestsDashboardListType' | ||||
|       description 'Values for merge request dashboard list type' | ||||
| 
 | ||||
|       value 'ACTION_BASED', 'Action based list rendering.', value: 'action_based' | ||||
|       value 'ROLE_BASED', 'Role based list rendering.', value: 'role_based' | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -25,6 +25,10 @@ module Types | |||
|       description: 'Use work item view instead of legacy issue view.', | ||||
|       null: true | ||||
| 
 | ||||
|     field :merge_request_dashboard_list_type, Types::MergeRequests::DashboardListTypeEnum, | ||||
|       description: 'Merge request dashboard list rendering type.', | ||||
|       null: true | ||||
| 
 | ||||
|     field :projects_sort, | ||||
|       Types::Projects::ProjectSortEnum, | ||||
|       description: 'Sort order for projects.', | ||||
|  |  | |||
|  | @ -67,8 +67,9 @@ module Ci | |||
|         origin_project_id = authorizations[:origin_project_id] | ||||
|         return unless accessed_project_id && origin_project_id | ||||
| 
 | ||||
|         policies = authorizations.fetch(:policies, []).map(&:to_s) | ||||
|         Ci::JobToken::LogAuthorizationWorker # rubocop:disable CodeReuse/Worker -- This method is called from a middleware and it's better tested | ||||
|           .perform_in(CAPTURE_DELAY, accessed_project_id, origin_project_id) | ||||
|           .perform_in(CAPTURE_DELAY, accessed_project_id, origin_project_id, policies) | ||||
|       end | ||||
| 
 | ||||
|       def self.log_captures!(accessed_project_id:, origin_project_id:, policies: []) | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ module CrossDatabaseModification | |||
|     ].freeze | ||||
| 
 | ||||
|     def self.logger | ||||
|       @logger ||= Logger.new(LOG_FILENAME, formatter: ->(_, _, _, msg) { Gitlab::Json.dump(msg) + "\n" }) | ||||
|       @logger ||= Logger.new(LOG_FILENAME, formatter: ->(_, _, _, msg) { "#{Gitlab::Json.dump(msg)}\n" }) | ||||
|     end | ||||
| 
 | ||||
|     def self.log_gitlab_transactions_stack(action: nil, example: nil) | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ module FromSetOperator | |||
|   # of UNION, INTERSECT, and EXCEPT as defined by Gitlab::SQL::Union, | ||||
|   # Gitlab::SQL::Intersect, and Gitlab::SQL::Except respectively. | ||||
|   def define_set_operator(operator) | ||||
|     method_name = 'from_' + operator.name.demodulize.downcase | ||||
|     method_name = "from_#{operator.name.demodulize.downcase}" | ||||
|     method_name = method_name.to_sym | ||||
| 
 | ||||
|     raise "Trying to redefine method '#{method(method_name)}'" if methods.include?(method_name) | ||||
|  |  | |||
|  | @ -136,7 +136,7 @@ module Routable | |||
| 
 | ||||
|   def build_full_path | ||||
|     if parent && path | ||||
|       parent.full_path + '/' + path | ||||
|       "#{parent.full_path}/#{path}" | ||||
|     else | ||||
|       path | ||||
|     end | ||||
|  | @ -191,7 +191,7 @@ module Routable | |||
| 
 | ||||
|   def build_full_name | ||||
|     if parent && name | ||||
|       parent.human_name + ' / ' + name | ||||
|       "#{parent.human_name} / #{name}" | ||||
|     else | ||||
|       name | ||||
|     end | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ module Integrations | |||
|       private | ||||
| 
 | ||||
|       def format_title(title) | ||||
|         '*' + strip_markup(title.lines.first.chomp) + '*' | ||||
|         "*#{strip_markup(title.lines.first.chomp)}*" | ||||
|       end | ||||
| 
 | ||||
|       def message | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ module Integrations | |||
| 
 | ||||
|       def activity | ||||
|         { | ||||
|           title: "#{strip_markup(user_combined_name)} #{link('commented on ' + target, note_url)}", | ||||
|           title: "#{strip_markup(user_combined_name)} #{link("commented on #{target}", note_url)}", | ||||
|           subtitle: "in #{project_link}", | ||||
|           text: strip_markup(formatted_title), | ||||
|           image: user_avatar | ||||
|  | @ -45,7 +45,7 @@ module Integrations | |||
|       private | ||||
| 
 | ||||
|       def message | ||||
|         "#{strip_markup(user_combined_name)} #{link('commented on ' + target, note_url)} in #{project_link}: *#{strip_markup(formatted_title)}*" | ||||
|         "#{strip_markup(user_combined_name)} #{link("commented on #{target}", note_url)} in #{project_link}: *#{strip_markup(formatted_title)}*" | ||||
|       end | ||||
| 
 | ||||
|       def format_title(title) | ||||
|  |  | |||
|  | @ -198,7 +198,9 @@ class Namespace < ApplicationRecord | |||
|     :resource_access_token_notify_inherited_locked_by_ancestor?, | ||||
|     :resource_access_token_notify_inherited_locked_by_application_setting?, | ||||
|     to: :namespace_settings | ||||
|   delegate :jwt_ci_cd_job_token_enabled?, to: :namespace_settings | ||||
|   delegate :jwt_ci_cd_job_token_enabled?, | ||||
|     :job_token_policies_enabled?, | ||||
|     to: :namespace_settings | ||||
| 
 | ||||
|   before_create :sync_share_with_group_lock_with_parent | ||||
|   before_update :sync_share_with_group_lock_with_parent, if: :parent_changed? | ||||
|  | @ -571,7 +573,7 @@ class Namespace < ApplicationRecord | |||
|       path_before_last_save | ||||
|     else | ||||
|       previous_parent = Group.find_by(id: parent_id_before_last_save) | ||||
|       previous_parent.full_path + '/' + path_before_last_save | ||||
|       "#{previous_parent.full_path}/#{path_before_last_save}" | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -55,6 +55,7 @@ class NamespaceSetting < ApplicationRecord | |||
|     math_rendering_limits_enabled | ||||
|     lock_math_rendering_limits_enabled | ||||
|     jwt_ci_cd_job_token_enabled | ||||
|     job_token_policies_enabled | ||||
|   ].freeze | ||||
| 
 | ||||
|   # matches the size set in the database constraint | ||||
|  |  | |||
|  | @ -3486,7 +3486,8 @@ class Project < ApplicationRecord | |||
|   end | ||||
| 
 | ||||
|   def job_token_policies_enabled? | ||||
|     Feature.enabled?(:add_policies_to_ci_job_token, self) | ||||
|     Feature.enabled?(:add_policies_to_ci_job_token, self) || | ||||
|       namespace.root_ancestor.namespace_settings&.job_token_policies_enabled? | ||||
|   end | ||||
|   strong_memoize_attr :job_token_policies_enabled? | ||||
| 
 | ||||
|  |  | |||
|  | @ -63,6 +63,16 @@ class Upload < ApplicationRecord | |||
|         store_class.new.delete_keys_async(keys) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def destroy_for_associations!(records, uploader = AttachmentUploader) | ||||
|       return if records.blank? | ||||
| 
 | ||||
|       for_model_type_and_id(records.klass, records.pluck_primary_key) | ||||
|         .for_uploader(uploader) | ||||
|         .then { |uploads| [uploads, uploads.begin_fast_destroy] } | ||||
|         .tap { |uploads, _| uploads.delete_all } | ||||
|         .tap { |_, files| finalize_fast_destroy(files) } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def absolute_path | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ module Uploads | |||
| 
 | ||||
|     def delete_keys_async(keys_to_delete) | ||||
|       keys_to_delete.each_slice(BATCH_SIZE) do |batch| | ||||
|         DeleteStoredFilesWorker.perform_async(self.class, batch) | ||||
|         DeleteStoredFilesWorker.perform_async(self.class.name, batch) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -463,6 +463,7 @@ class User < ApplicationRecord | |||
|     :use_work_items_view, :use_work_items_view=, | ||||
|     :text_editor, :text_editor=, | ||||
|     :default_text_editor_enabled, :default_text_editor_enabled=, | ||||
|     :merge_request_dashboard_list_type, :merge_request_dashboard_list_type=, | ||||
|     to: :user_preference | ||||
| 
 | ||||
|   delegate :path, to: :namespace, allow_nil: true, prefix: true | ||||
|  |  | |||
|  | @ -46,6 +46,8 @@ class UserPreference < ApplicationRecord | |||
|   enum :extensions_marketplace_opt_in_status, Enums::WebIde::ExtensionsMarketplaceOptInStatus.statuses | ||||
|   enum :organization_groups_projects_display, { projects: 0, groups: 1 } | ||||
| 
 | ||||
|   enum :merge_request_dashboard_list_type, { action_based: 0, role_based: 1 } | ||||
| 
 | ||||
|   class << self | ||||
|     def notes_filters | ||||
|       { | ||||
|  |  | |||
|  | @ -39,6 +39,10 @@ module NamespaceSettings | |||
|         param_key: :enabled_git_access_protocol, | ||||
|         user_policy: :update_git_access_protocol | ||||
|       ) | ||||
|       validate_settings_param_for_root_group( | ||||
|         param_key: :job_token_policies_enabled, | ||||
|         user_policy: :admin_group | ||||
|       ) | ||||
| 
 | ||||
|       handle_default_branch_name | ||||
|       handle_default_branch_protection unless settings_params[:default_branch_protection].blank? | ||||
|  |  | |||
|  | @ -18,8 +18,19 @@ | |||
|           = link_to _('Learn more.'), help_page_path('ci/jobs/ci_job_token.md', anchor: 'use-legacy-format-for-cicd-tokens'), target: '_blank', rel: 'noopener noreferrer' | ||||
|       .gl-mb-5 | ||||
|       = gitlab_ui_form_for group, url: group_settings_ci_cd_path(group, anchor: 'js-general-pipeline-settings') do |f| | ||||
|         = f.hidden_field :jwt_ci_cd_job_token_enabled, class: 'js-jwt-ci-cd-job-token-enabled-input', value: group.jwt_ci_cd_job_token_enabled? | ||||
|         = render Pajamas::ToggleComponent.new(classes: 'js-jwt-ci-cd-job-token-enabled-toggle', | ||||
|         = f.hidden_field :jwt_ci_cd_job_token_enabled, class: 'js-setting-input', value: group.jwt_ci_cd_job_token_enabled? | ||||
|         = render Pajamas::ToggleComponent.new(classes: 'js-setting-toggle', | ||||
|           label: s_('GroupSettings|Enable JWT format for CI/CD job tokens'), | ||||
|           is_checked: group.jwt_ci_cd_job_token_enabled?) do | ||||
|           = s_('GroupSettings|Enable JWT format for the CI_JOB_TOKEN variable. When disabled, it uses the legacy database format.') | ||||
|       .gl-mb-5 | ||||
|       = gitlab_ui_form_for group, url: group_settings_ci_cd_path(group, anchor: 'js-general-pipeline-settings') do |f| | ||||
|         %span.gl-toggle-label.gl-shrink-0= s_('GroupSettings|Enable fine-grained permissions for CI/CD job tokens') | ||||
|         %span= render_if_exists 'shared/experimental_badge_tag' | ||||
|         = f.hidden_field :job_token_policies_enabled, class: 'js-setting-input', value: group.job_token_policies_enabled? | ||||
|         = render Pajamas::ToggleComponent.new(classes: 'js-setting-toggle gl-mt-3', | ||||
|           label: s_('GroupSettings|Enable fine-grained permissions for CI/CD job tokens'), | ||||
|           label_position: :hidden, | ||||
|           is_checked: group.job_token_policies_enabled?) do | ||||
|           = s_('GroupSettings|Enable fine-grained permissions for CI/CD job tokens.') | ||||
|           = link_to _('Learn more.'), help_page_path('ci/jobs/fine_grained_permissions.md'), target: '_blank', rel: 'noopener noreferrer' | ||||
|  |  | |||
|  | @ -1,9 +0,0 @@ | |||
| --- | ||||
| name: label_keep_around_ref_metrics | ||||
| feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383814 | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145988 | ||||
| rollout_issue_url: | ||||
| milestone: '16.10' | ||||
| group: group::gitaly::git | ||||
| type: ops | ||||
| default_enabled: false | ||||
|  | @ -1000,6 +1000,9 @@ Gitlab.ee do | |||
|   Settings.cron_jobs['analytics_dump_ai_user_metrics_database_write_buffer_cron_worker'] ||= {} | ||||
|   Settings.cron_jobs['analytics_dump_ai_user_metrics_database_write_buffer_cron_worker']['cron'] ||= "*/10 * * * *" | ||||
|   Settings.cron_jobs['analytics_dump_ai_user_metrics_database_write_buffer_cron_worker']['job_class'] = 'Analytics::DumpAiUserMetricsWriteBufferCronWorker' | ||||
|   Settings.cron_jobs['delete_expired_vulnerability_exports_worker'] ||= {} | ||||
|   Settings.cron_jobs['delete_expired_vulnerability_exports_worker']['cron'] ||= '0 4 * * *' | ||||
|   Settings.cron_jobs['delete_expired_vulnerability_exports_worker']['job_class'] = 'Vulnerabilities::DeleteExpiredExportsWorker' | ||||
| 
 | ||||
|   Gitlab.com do | ||||
|     Settings.cron_jobs['disable_legacy_open_source_license_for_inactive_projects'] ||= {} | ||||
|  |  | |||
|  | @ -0,0 +1,9 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class AddMergeRequestDashboardListTypeToUserPreferences < Gitlab::Database::Migration[2.2] | ||||
|   milestone '17.11' | ||||
| 
 | ||||
|   def change | ||||
|     add_column :user_preferences, :merge_request_dashboard_list_type, :smallint, default: 0, null: false | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,10 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class AddJobTokenPoliciesEnabledColumnToNamespaceSettings < Gitlab::Database::Migration[2.2] | ||||
|   milestone '17.11' | ||||
|   enable_lock_retries! | ||||
| 
 | ||||
|   def change | ||||
|     add_column :namespace_settings, :job_token_policies_enabled, :boolean, default: false, null: false | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1 @@ | |||
| 821adcb681f8c7618ce7560acb7dfbc36b0004ba6cd9144e76f761536bc574fa | ||||
|  | @ -0,0 +1 @@ | |||
| 968664b5b33cf194f36a9ba5b28490471405e94272c64668264ec0c11d22a672 | ||||
|  | @ -17475,6 +17475,7 @@ CREATE TABLE namespace_settings ( | |||
|     jwt_ci_cd_job_token_enabled boolean DEFAULT false NOT NULL, | ||||
|     jwt_ci_cd_job_token_opted_out boolean DEFAULT false NOT NULL, | ||||
|     require_dpop_for_manage_api_endpoints boolean DEFAULT true NOT NULL, | ||||
|     job_token_policies_enabled boolean DEFAULT false NOT NULL, | ||||
|     CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)), | ||||
|     CONSTRAINT namespace_settings_unique_project_download_limit_alertlist_size CHECK ((cardinality(unique_project_download_limit_alertlist) <= 100)), | ||||
|     CONSTRAINT namespace_settings_unique_project_download_limit_allowlist_size CHECK ((cardinality(unique_project_download_limit_allowlist) <= 100)) | ||||
|  | @ -23498,6 +23499,7 @@ CREATE TABLE user_preferences ( | |||
|     dpop_enabled boolean DEFAULT false NOT NULL, | ||||
|     use_work_items_view boolean DEFAULT false NOT NULL, | ||||
|     text_editor_type smallint DEFAULT 0 NOT NULL, | ||||
|     merge_request_dashboard_list_type smallint DEFAULT 0 NOT NULL, | ||||
|     CONSTRAINT check_1d670edc68 CHECK ((time_display_relative IS NOT NULL)), | ||||
|     CONSTRAINT check_89bf269f41 CHECK ((char_length(diffs_deletion_color) <= 7)), | ||||
|     CONSTRAINT check_b1306f8875 CHECK ((char_length(organization_groups_projects_sort) <= 64)), | ||||
|  |  | |||
|  | @ -59,10 +59,10 @@ The following table lists the GitLab Duo features, and whether they are availabl | |||
| | [Refactor Code](../../user/gitlab_duo_chat/examples.md#refactor-code-in-the-ide)                                                       | {{< icon name="check-circle-filled" >}} Yes    | GitLab 17.9 and later | | ||||
| | [Fix Code](../../user/gitlab_duo_chat/examples.md#fix-code-in-the-ide)                                                                 | {{< icon name="check-circle-filled" >}} Yes    | GitLab 17.9 and later | | ||||
| | [AI Impact Dashboard](../../user/analytics/ai_impact_analytics.md)                                                                     | {{< icon name="check-circle-dashed" >}} Beta | GitLab 17.9 and later | | ||||
| | [Root Cause Analysis](../../user/gitlab_duo_chat/examples.md#troubleshoot-failed-cicd-jobs-with-root-cause-analysis)                   | {{< icon name="check-circle-dashed" >}} Beta      | GitLab 17.10 and later | | ||||
| | [Discussion Summary](../../user/discussions/_index.md#summarize-issue-discussions-with-duo-chat)                                       | {{< icon name="dash-circle" >}} No      | Not applicable | | ||||
| | [GitLab Duo for the CLI](../../editor_extensions/gitlab_cli/_index.md#gitlab-duo-for-the-cli)                                          | {{< icon name="dash-circle" >}} No      | Not applicable | | ||||
| | [Merge Commit Message Generation](../../user/project/merge_requests/duo_in_merge_requests.md#generate-a-merge-commit-message)          | {{< icon name="dash-circle" >}} No      | Not applicable | | ||||
| | [Root Cause Analysis](../../user/gitlab_duo_chat/examples.md#troubleshoot-failed-cicd-jobs-with-root-cause-analysis)                   | {{< icon name="dash-circle" >}} No      | Not applicable | | ||||
| | [Vulnerability Explanation](../../user/application_security/vulnerabilities/_index.md#explaining-a-vulnerability)                      | {{< icon name="dash-circle" >}} No      | Not applicable | | ||||
| | [Vulnerability Resolution](../../user/application_security/vulnerabilities/_index.md#vulnerability-resolution)                         | {{< icon name="dash-circle" >}} No      | Not applicable | | ||||
| 
 | ||||
|  |  | |||
|  | @ -132,7 +132,7 @@ Configure the GitLab Duo feature and sub-feature to send queries to the configur | |||
| 1. Select the **AI-powered features** tab. | ||||
| 1. For the feature and sub-feature you want to configure, from the dropdown list, choose the self-hosted model you want to use. | ||||
| 
 | ||||
|    For example, for the the code generation sub-feature under GitLab Duo Code Suggestions, you can select **claude sonnet on bedrock (Claude 3)**. | ||||
|    For example, for the code generation sub-feature under GitLab Duo Code Suggestions, you can select **claude sonnet on bedrock (Claude 3)**. | ||||
| 
 | ||||
|     | ||||
| 
 | ||||
|  |  | |||
|  | @ -11816,6 +11816,7 @@ Input type: `UserPreferencesUpdateInput` | |||
| | <a id="mutationuserpreferencesupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | ||||
| | <a id="mutationuserpreferencesupdateextensionsmarketplaceoptinstatus"></a>`extensionsMarketplaceOptInStatus` | [`ExtensionsMarketplaceOptInStatus`](#extensionsmarketplaceoptinstatus) | Status of the Web IDE Extension Marketplace opt-in for the user. | | ||||
| | <a id="mutationuserpreferencesupdateissuessort"></a>`issuesSort` | [`IssueSort`](#issuesort) | Sort order for issue lists. | | ||||
| | <a id="mutationuserpreferencesupdatemergerequestdashboardlisttype"></a>`mergeRequestDashboardListType` | [`MergeRequestsDashboardListType`](#mergerequestsdashboardlisttype) | Merge request dashboard list rendering type. | | ||||
| | <a id="mutationuserpreferencesupdatemergerequestssort"></a>`mergeRequestsSort` | [`MergeRequestSort`](#mergerequestsort) | Sort order for issue lists. | | ||||
| | <a id="mutationuserpreferencesupdateorganizationgroupsprojectsdisplay"></a>`organizationGroupsProjectsDisplay` {{< icon name="warning-solid" >}} | [`OrganizationGroupProjectDisplay`](#organizationgroupprojectdisplay) | **Deprecated:** **Status**: Experiment. Introduced in GitLab 17.2. | | ||||
| | <a id="mutationuserpreferencesupdateorganizationgroupsprojectssort"></a>`organizationGroupsProjectsSort` {{< icon name="warning-solid" >}} | [`OrganizationGroupProjectSort`](#organizationgroupprojectsort) | **Deprecated:** **Status**: Experiment. Introduced in GitLab 17.2. | | ||||
|  | @ -39103,6 +39104,7 @@ fields relate to interactions between the two entities. | |||
| | ---- | ---- | ----------- | | ||||
| | <a id="userpreferencesextensionsmarketplaceoptinstatus"></a>`extensionsMarketplaceOptInStatus` | [`ExtensionsMarketplaceOptInStatus!`](#extensionsmarketplaceoptinstatus) | Status of the Web IDE Extension Marketplace opt-in for the user. | | ||||
| | <a id="userpreferencesissuessort"></a>`issuesSort` | [`IssueSort`](#issuesort) | Sort order for issue lists. | | ||||
| | <a id="userpreferencesmergerequestdashboardlisttype"></a>`mergeRequestDashboardListType` | [`MergeRequestsDashboardListType`](#mergerequestsdashboardlisttype) | Merge request dashboard list rendering type. | | ||||
| | <a id="userpreferencesorganizationgroupsprojectsdisplay"></a>`organizationGroupsProjectsDisplay` {{< icon name="warning-solid" >}} | [`OrganizationGroupProjectDisplay!`](#organizationgroupprojectdisplay) | **Introduced** in GitLab 17.2. **Status**: Experiment. Default list view for organization groups and projects. | | ||||
| | <a id="userpreferencesorganizationgroupsprojectssort"></a>`organizationGroupsProjectsSort` {{< icon name="warning-solid" >}} | [`OrganizationGroupProjectSort`](#organizationgroupprojectsort) | **Introduced** in GitLab 17.2. **Status**: Experiment. Sort order for organization groups and projects. | | ||||
| | <a id="userpreferencesprojectssort"></a>`projectsSort` | [`ProjectSort`](#projectsort) | Sort order for projects. | | ||||
|  | @ -43174,6 +43176,15 @@ State of a GitLab merge request. | |||
| | <a id="mergerequeststatemerged"></a>`merged` | Merge request has been merged. | | ||||
| | <a id="mergerequeststateopened"></a>`opened` | Opened merge request. | | ||||
| 
 | ||||
| ### `MergeRequestsDashboardListType` | ||||
| 
 | ||||
| Values for merge request dashboard list type. | ||||
| 
 | ||||
| | Value | Description | | ||||
| | ----- | ----------- | | ||||
| | <a id="mergerequestsdashboardlisttypeaction_based"></a>`ACTION_BASED` | Action based list rendering. | | ||||
| | <a id="mergerequestsdashboardlisttyperole_based"></a>`ROLE_BASED` | Role based list rendering. | | ||||
| 
 | ||||
| ### `MergeStatus` | ||||
| 
 | ||||
| Representation of whether a GitLab merge request can be merged. | ||||
|  |  | |||
|  | @ -29,33 +29,26 @@ Status: Experiment | |||
| 
 | ||||
| {{< /history >}} | ||||
| 
 | ||||
| {{< alert type="flag" >}} | ||||
| 
 | ||||
| The availability of this feature is controlled by a feature flag. | ||||
| For more information, see the history. | ||||
| This feature is available for testing, but not ready for production use. | ||||
| 
 | ||||
| {{< /alert >}} | ||||
| 
 | ||||
| You can use fine-grained permissions to explicitly allow access to a limited set of API endpoints. | ||||
| These permissions are applied to the CI/CD job tokens in a specified project. | ||||
| This feature is an [experiment](../../policy/development_stages_support.md#experiment). | ||||
| 
 | ||||
| ## Enable fine-grained permissions | ||||
| This feature is an [experiment](../../policy/development_stages_support.md#experiment) and subject to change without notice. This feature is not ready for production use. If you want to use this feature, you should test outside of production first. | ||||
| 
 | ||||
| ### On GitLab Self-Managed | ||||
| ## Enable fine-grained permissions for projects | ||||
| 
 | ||||
| 1. Start the GitLab Rails console. For information, see [Enable and disable GitLab features deployed behind feature flags](../../administration/feature_flags.md#enable-or-disable-the-feature) | ||||
| 1. Turn on the [feature flag](../../administration/feature_flags.md): | ||||
| Prerequisites: | ||||
| 
 | ||||
| ```ruby | ||||
| # You must include a specific project ID with this command. | ||||
| Feature.enable(:add_policies_to_ci_job_token, <project_id>) | ||||
| ``` | ||||
| - You must have the Owner role for a group. | ||||
| 
 | ||||
| ### On GitLab.com | ||||
| You must turn on fine-grained permissions at the group level. Then, each project in the group can | ||||
| apply fine-grained permissions for CI/CD job tokens to grant access to individual resources. | ||||
| 
 | ||||
| Add a comment on this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/519575) with your project ID. | ||||
| To enable fine-grained permissions for all projects in a group: | ||||
| 
 | ||||
| 1. On the left sidebar, select **Search or go to** and find your group. | ||||
| 1. On the left sidebar, select **Settings > CI/CD**. | ||||
| 1. Expand **General pipelines**. | ||||
| 1. Turn on the **Enable fine-grained permissions for CI/CD job tokens** toggle. | ||||
| 
 | ||||
| ## Available API endpoints | ||||
| 
 | ||||
|  |  | |||
|  | @ -44,7 +44,7 @@ Please refer to [the documentation](../code_review.md#reviewer-roulette) | |||
| 
 | ||||
| ### Ask for help | ||||
| 
 | ||||
| If contributors have questions or need additional help with Python-specific reviews, direct them to the GitLab #python or #python_maintainers Slack channels for assistance. | ||||
| If contributors have questions or need additional help with Python-specific reviews, direct them to the GitLab `#python` or `#python_maintainers` Slack channels for assistance. | ||||
| 
 | ||||
| ## How to find a project to review | ||||
| 
 | ||||
|  |  | |||
|  | @ -62,7 +62,8 @@ To auto-format this table, use the VS Code Markdown Table formatter: `https://do | |||
| 
 | ||||
| | Command                                                                                         | Issue                  | Merge request          | Epic                   | Action | | ||||
| |:------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------| | ||||
| | `/add_contacts [contact:email1@example.com] [contact:email2@example.com]`                       | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | Add one or more active [CRM contacts](../crm/_index.md) ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413) in GitLab 14.6). | | ||||
| | `/add_child <item>`                       | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | Add `<item>` as a child item. The `<item>` value should be in the format of `#item`, `group/project#item`, or a URL to the item. For issues, you can add tasks and OKRs. Your administrator must have [enabled the new look for issues](../project/issues/issue_work_items.md). For epics, you can add issues, tasks, and OKRs. Multiple work items can be added as child items at the same time. Your administrator must have [enabled the new look for epics](../group/epics/epic_work_items.md). | | ||||
| | `/add_contacts [contact:email1@example.com] [contact:email2@example.com]`                       | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | Add one or more active [CRM contacts](../crm/_index.md). | | ||||
| | `/add_email email1 email2`                                                                      | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | Add up to six [email participants](service_desk/external_participants.md). This action is behind the feature flag `issue_email_participants`. Not supported in [issue templates](description_templates.md). | | ||||
| | `/approve`                                                                                      | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | Approve the merge request. | | ||||
| | `/assign @user1 @user2`                                                                         | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | Assign one or more users. | | ||||
|  | @ -110,6 +111,7 @@ To auto-format this table, use the VS Code Markdown Table formatter: `https://do | |||
| | `/rebase`                                                                                       | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | Rebase source branch on the latest commit of the target branch. For help, see [troubleshooting information](../../topics/git/troubleshooting_git.md). | | ||||
| | `/relabel ~label1 ~label2`                                                                      | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | Replace current labels with those specified. | | ||||
| | `/relate <item1> <item2>`                                                                       | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | Mark items as related. The `<item>` value should be in the format of `#item`, `group/project#item`, or the full URL. | | ||||
| | `/remove_child <item>`                                                                          | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | Remove `<item>` as child. The `<item>` value should be in the format of `#item`, `group/project#item`, or a URL to the item. For issues, your administrator must have [enabled the new look for issues](../project/issues/issue_work_items.md). For epics, your administrator must have [enabled the new look for epics](../group/epics/epic_work_items.md). | | ||||
| | `/remove_child_epic <epic>`                                                                     | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. | | ||||
| | `/remove_contacts [contact:email1@example.com] [contact:email2@example.com]`                    | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | Remove one or more [CRM contacts](../crm/_index.md) | | ||||
| | `/remove_due_date`                                                                              | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | Remove due date. | | ||||
|  | @ -118,6 +120,7 @@ To auto-format this table, use the VS Code Markdown Table formatter: `https://do | |||
| | `/remove_estimate` or `/remove_time_estimate`                                                   | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | Remove time estimate. Alias `/remove_time_estimate` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6. | | ||||
| | `/remove_iteration`                                                                             | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | Remove iteration. | | ||||
| | `/remove_milestone`                                                                             | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | Remove milestone. | | ||||
| | `/remove_parent`                                                                                | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | Remove the parent from item. For issues, your administrator must have [enabled the new look for issues](../project/issues/issue_work_items.md). For epics, your administrator must have [enabled the new look for epics](../group/epics/epic_work_items.md). | | ||||
| | `/remove_parent_epic`                                                                           | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | Remove parent epic from epic. | | ||||
| | `/remove_time_spent`                                                                            | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | Remove time spent. | | ||||
| | `/remove_zoom`                                                                                  | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | Remove Zoom meeting from this issue. | | ||||
|  | @ -169,7 +172,7 @@ To auto-format this table, use the VS Code Markdown Table formatter: `https://do | |||
| |:--------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------| | ||||
| | `/assign @user1 @user2`                                       | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | Assign one or more users. | | ||||
| | `/assign me`                                                  | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | Assign yourself. | | ||||
| | `/add_child <work_item>`                                                                         | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | Add child to `<work_item>`. The `<work_item>` value should be in the format of `#item`, `group/project#item`, or a URL to a work item. Multiple work items can be added as children at the same time. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/420797) in GitLab 16.5. | | ||||
| | `/add_child <work_item>`                                                                         | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | Add child to `<work_item>`. The `<work_item>` value should be in the format of `#item`, `group/project#item`, or a URL to a work item. Multiple work items can be added as child items at the same time. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/420797) in GitLab 16.5. | | ||||
| | `/award :emoji:`                                                                                 | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | Toggle an emoji reaction. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/412275) in GitLab 16.5 | | ||||
| | `/cc @user`                                                   | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes | Mention a user. In GitLab 15.0 and later, this command performs no action. You can instead type `CC @user` or only `@user`. | | ||||
| | `/checkin_reminder <cadence>`                                 | {{< icon name="dotted-circle" >}} No| {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | Schedule [check-in reminders](../okrs.md#schedule-okr-check-in-reminders). Options are `weekly`, `twice-monthly`, `monthly`, or `never` (default). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/422761) in GitLab 16.4 with flags named `okrs_mvc` and `okr_checkin_reminders`.  | | ||||
|  |  | |||
|  | @ -161,6 +161,13 @@ For additional information, see [Approval rules](../../merge_requests/approvals/ | |||
| 
 | ||||
| ### Edit squash commits option | ||||
| 
 | ||||
| {{< details >}} | ||||
| 
 | ||||
| - Tier: Premium, Ultimate | ||||
| - Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated | ||||
| 
 | ||||
| {{< /details >}} | ||||
| 
 | ||||
| {{< history >}} | ||||
| 
 | ||||
| - [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. | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ module Gitlab | |||
|       def execute(shas, source:) | ||||
|         return if disabled? | ||||
| 
 | ||||
|         labels = project_labels.merge(source: source) | ||||
|         labels = { source: source } | ||||
| 
 | ||||
|         shas.uniq.each do |sha| | ||||
|           next unless sha.present? && commit_by(oid: sha) | ||||
|  | @ -61,17 +61,6 @@ module Gitlab | |||
| 
 | ||||
|       private | ||||
| 
 | ||||
|       def project_labels | ||||
|         return { full_path: '' } unless add_project_labels? | ||||
| 
 | ||||
|         { full_path: @repository.full_path } | ||||
|       end | ||||
| 
 | ||||
|       def add_project_labels? | ||||
|         Feature.enabled?(:label_keep_around_ref_metrics, @repository, type: :ops) || | ||||
|           (@repository.project && Feature.enabled?(:label_keep_around_ref_metrics, @repository.project, type: :ops)) | ||||
|       end | ||||
| 
 | ||||
|       def disabled? | ||||
|         Feature.enabled?(:disable_keep_around_refs, @repository, type: :ops) || | ||||
|           (@repository.project && Feature.enabled?(:disable_keep_around_refs, @repository.project, type: :ops)) | ||||
|  |  | |||
|  | @ -63,6 +63,7 @@ module Gitlab | |||
|         provider_arguments.concat arguments | ||||
|         provider_arguments << defaults unless defaults.empty? | ||||
|       when Hash, GitlabSettings::Options | ||||
|         verify_saml_cert_arguments!(provider['name'], arguments) | ||||
|         hash_arguments = merge_hash_defaults_and_args(defaults, arguments) | ||||
|         normalized = normalize_hash_arguments(hash_arguments) | ||||
| 
 | ||||
|  | @ -104,6 +105,33 @@ module Gitlab | |||
|       args | ||||
|     end | ||||
| 
 | ||||
|     def verify_saml_cert_arguments!(provider_name, arguments) | ||||
|       return arguments unless AuthHelper.saml_providers.include?(provider_name.to_sym) | ||||
| 
 | ||||
|       fingerprint = arguments['idp_cert_fingerprint'] | ||||
|       algorithm = arguments['idp_cert_fingerprint_algorithm'] | ||||
| 
 | ||||
|       return arguments unless fingerprint.present? && algorithm.nil? | ||||
| 
 | ||||
|       algorithm = detect_fingerprint_algorithm(fingerprint) | ||||
| 
 | ||||
|       return arguments unless algorithm.present? | ||||
| 
 | ||||
|       arguments['idp_cert_fingerprint_algorithm'] = algorithm | ||||
|       arguments | ||||
|     end | ||||
| 
 | ||||
|     def detect_fingerprint_algorithm(fingerprint) | ||||
|       case fingerprint.scan(/[0-9A-Fa-f]{2}/i).length | ||||
|       when 20 | ||||
|         # v2.x will change to RubySaml::XML::SHA1 | ||||
|         XMLSecurity::Document::SHA1 | ||||
|       when 32 | ||||
|         # v2.x will change to RubySaml::XML::SHA256 | ||||
|         XMLSecurity::Document::SHA256 | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def provider_defaults(provider) | ||||
|       self.class.default_arguments_for(provider['name']) | ||||
|     end | ||||
|  |  | |||
|  | @ -26213,6 +26213,9 @@ msgstr "" | |||
| msgid "Geo|Container repositories synchronization concurrency limit" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Geo|Created: %{timeAgo}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Geo|Data replication lag" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -26282,6 +26285,9 @@ msgstr "" | |||
| msgid "Geo|Go to the primary site" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Geo|GraphQL ID: %{id}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Geo|Groups to synchronize" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -26387,6 +26393,12 @@ msgstr "" | |||
| msgid "Geo|Re-verification interval" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Geo|Registry ID: %{id}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Geo|Registry information" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Geo|Remove %{siteType} site" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -26396,6 +26408,9 @@ msgstr "" | |||
| msgid "Geo|Removing a Geo site stops the synchronization to and from that site. Are you sure?" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Geo|Replicable ID: %{id}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Geo|Replicated data is verified with the secondary site(s) using checksums" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -28816,6 +28831,12 @@ msgstr "" | |||
| msgid "GroupSettings|Enable extension marketplace" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "GroupSettings|Enable fine-grained permissions for CI/CD job tokens" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "GroupSettings|Enable fine-grained permissions for CI/CD job tokens." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "GroupSettings|Enable overview background aggregation for Value Streams Dashboard" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -65328,6 +65349,9 @@ msgstr "" | |||
| msgid "Vulnerability|Scanner:" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Vulnerability|Scanner: %{scannerName}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Vulnerability|Search or filter vulnerabilities..." | ||||
| msgstr "" | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,14 +12,17 @@ class SemgrepResultProcessor | |||
| 
 | ||||
|   ALLOWED_PROJECT_DIRS = %w[/builds/gitlab-org/gitlab].freeze | ||||
|   ALLOWED_API_URLS = %w[https://gitlab.com/api/v4].freeze | ||||
| 
 | ||||
|   UNIQUE_COMMENT_RULES_IDS = %w[builds.sast-custom-rules.appsec-pings.glappsec_ci-job-token builds.sast-custom-rules.secure-coding-guidelines.ruby.glappsec_insecure-regex].freeze | ||||
|   # Remove this when the feature is fully working | ||||
|   APPSEC_HANDLE = "@gitlab-com/gl-security/appsec" | ||||
| 
 | ||||
|   MESSAGE_SCG_PING_APPSEC = "#{APPSEC_HANDLE} please review this finding, which is a potential violation of [GitLab's secure coding guidelines](https://docs.gitlab.com/development/secure_coding_guidelines/).".freeze | ||||
|   MESSAGE_S1_PING_APPSEC = "#{APPSEC_HANDLE} please review this finding. This MR potentially reintroduces code from a past S1 issue.".freeze | ||||
| 
 | ||||
|   MESSAGE_FOOTER = <<~FOOTER | ||||
| 
 | ||||
| 
 | ||||
|     <small> | ||||
|     This AppSec automation is currently under testing. | ||||
|     This automation belongs to AppSec. | ||||
|     Use ~"appsec-sast::helpful" or ~"appsec-sast::unhelpful" for quick feedback. | ||||
|     To stop the bot from further commenting, you can use the ~"appsec-sast::stop" label. | ||||
|     For any detailed feedback, [add a comment here](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/issues/38). | ||||
|  | @ -132,8 +135,18 @@ class SemgrepResultProcessor | |||
|       message_header = "<!-- #{header_information} -->" | ||||
|       new_line = finding[:line] | ||||
|       message = finding[:message] | ||||
|       check_id = finding[:check_id] | ||||
|       uri = URI.parse("#{ENV['CI_API_V4_URL']}/projects/#{ENV['CI_MERGE_REQUEST_PROJECT_ID']}/merge_requests/#{ENV['CI_MERGE_REQUEST_IID']}/discussions") | ||||
|       message_from_bot = "#{message_header}\n#{message}\n#{MESSAGE_FOOTER}" | ||||
|       suffix = if check_id&.start_with?("builds.sast-custom-rules.secure-coding-guidelines") | ||||
|                  "\n#{MESSAGE_SCG_PING_APPSEC}" | ||||
|                elsif check_id&.start_with?("builds.sast-custom-rules.s1") | ||||
|                  "\n#{MESSAGE_S1_PING_APPSEC}" | ||||
|                else | ||||
|                  "" | ||||
|                end | ||||
| 
 | ||||
|       message_from_bot = "#{message_header}\n#{message}#{suffix}\n#{MESSAGE_FOOTER}" | ||||
| 
 | ||||
|       request = Net::HTTP::Post.new(uri) | ||||
|       request["PRIVATE-TOKEN"] = ENV['CUSTOM_SAST_RULES_BOT_PAT'] | ||||
|       request.set_form_data( | ||||
|  |  | |||
|  | @ -543,27 +543,29 @@ function log_disk_usage() { | |||
| 
 | ||||
| # all functions below are for customizing CI job exit code | ||||
| function run_with_custom_exit_code() { | ||||
|   set -o pipefail # Take the exit status of the rightmost command that failed | ||||
|   set +e          # temporarily disable exit on error to prevent premature exit | ||||
|   "$@" | ||||
| 
 | ||||
|   local trace_file="/tmp/stdout_stderr_log.out" | ||||
|   # set -o pipefail # Take the exit status of the rightmost command that failed | ||||
|   # set +e          # temporarily disable exit on error to prevent premature exit | ||||
| 
 | ||||
|   # Run the command and tee output to both the terminal and the file | ||||
|   "$@" 2>&1 | tee "$trace_file" | ||||
|   initial_exit_code=$? | ||||
|   # local trace_file="/tmp/stdout_stderr_log.out" | ||||
| 
 | ||||
|   echo "initial_exit_code: $initial_exit_code" | ||||
|   # # Run the command and tee output to both the terminal and the file | ||||
|   # "$@" 2>&1 | tee "$trace_file" | ||||
|   # initial_exit_code=$? | ||||
| 
 | ||||
|   find_custom_exit_code "$initial_exit_code" "$trace_file" | ||||
|   new_exit_code=$? | ||||
|   # echo "initial_exit_code: $initial_exit_code" | ||||
| 
 | ||||
|   echo "new_exit_code=$new_exit_code" | ||||
|   # find_custom_exit_code "$initial_exit_code" "$trace_file" | ||||
|   # new_exit_code=$? | ||||
| 
 | ||||
|   # Restore shell default behavior | ||||
|   set -e | ||||
|   set +o pipefail | ||||
|   # echo "new_exit_code=$new_exit_code" | ||||
| 
 | ||||
|   exit "$new_exit_code" | ||||
|   # # Restore shell default behavior | ||||
|   # set -e | ||||
|   # set +o pipefail | ||||
| 
 | ||||
|   # exit "$new_exit_code" | ||||
| } | ||||
| 
 | ||||
| function find_custom_exit_code() { | ||||
|  |  | |||
|  | @ -175,6 +175,15 @@ RSpec.describe Groups::Settings::CiCdController, feature_category: :continuous_i | |||
|             .from(false).to(true) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when updating `job_token_policies_enabled`' do | ||||
|         let(:params) { { group_id: group, group: { job_token_policies_enabled: true } } } | ||||
| 
 | ||||
|         it 'can update `job_token_policies_enabled`' do | ||||
|           expect { perform_request }.to change { group.reload.namespace_settings.job_token_policies_enabled } | ||||
|             .from(false).to(true) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when user is a group maintainer' do | ||||
|  | @ -199,6 +208,16 @@ RSpec.describe Groups::Settings::CiCdController, feature_category: :continuous_i | |||
|           expect(response).to have_gitlab_http_status(:not_found) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when updating `job_token_policies_enabled`' do | ||||
|         let(:params) { { group_id: group, group: { job_token_policies_enabled: true } } } | ||||
| 
 | ||||
|         it 'cannot update `job_token_policies_enabled`' do | ||||
|           expect { perform_request }.not_to change { group.reload.namespace_settings.job_token_policies_enabled } | ||||
| 
 | ||||
|           expect(response).to have_gitlab_http_status(:not_found) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when user is an admin' do | ||||
|  | @ -227,6 +246,15 @@ RSpec.describe Groups::Settings::CiCdController, feature_category: :continuous_i | |||
|               .from(false).to(true) | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'when updating `job_token_policies_enabled`' do | ||||
|           let(:params) { { group_id: group, group: { job_token_policies_enabled: true } } } | ||||
| 
 | ||||
|           it 'can update `job_token_policies_enabled`' do | ||||
|             expect { perform_request }.to change { group.reload.namespace_settings.job_token_policies_enabled } | ||||
|               .from(false).to(true) | ||||
|           end | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when admin mode is enabled', :enable_admin_mode do | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ import RulesYaml from './yaml_tests/positive_tests/rules.yml'; | |||
| import RulesNeedsYaml from './yaml_tests/positive_tests/rules_needs.yml'; | ||||
| import RunYaml from './yaml_tests/positive_tests/run.yml'; | ||||
| import ProjectPathYaml from './yaml_tests/positive_tests/project_path.yml'; | ||||
| import SpecInputsYaml from './yaml_tests/positive_tests/spec_inputs.yml'; | ||||
| import VariablesYaml from './yaml_tests/positive_tests/variables.yml'; | ||||
| import JobWhenYaml from './yaml_tests/positive_tests/job_when.yml'; | ||||
| import IdTokensYaml from './yaml_tests/positive_tests/id_tokens.yml'; | ||||
|  | @ -63,6 +64,7 @@ import ProjectPathIncludeTailSlashYaml from './yaml_tests/negative_tests/project | |||
| import RulesNegativeYaml from './yaml_tests/negative_tests/rules.yml'; | ||||
| import RulesNeedsNegativeYaml from './yaml_tests/negative_tests/rules_needs.yml'; | ||||
| import RunNegativeYaml from './yaml_tests/negative_tests/run.yml'; | ||||
| import SpecInputsNegativeYaml from './yaml_tests/negative_tests/spec_inputs.yml'; | ||||
| import TriggerNegativeYaml from './yaml_tests/negative_tests/trigger.yml'; | ||||
| import VariablesInvalidOptionsYaml from './yaml_tests/negative_tests/variables/invalid_options.yml'; | ||||
| import VariablesInvalidSyntaxDescYaml from './yaml_tests/negative_tests/variables/invalid_syntax_desc.yml'; | ||||
|  | @ -113,25 +115,26 @@ describe('positive tests', () => { | |||
|       CacheYaml, | ||||
|       MultipleCachesYaml, | ||||
|       FilterYaml, | ||||
|       HooksYaml, | ||||
|       IdTokensYaml, | ||||
|       IncludeYaml, | ||||
|       JobWhenYaml, | ||||
|       HooksYaml, | ||||
|       NeedsParallelMatrixYaml, | ||||
|       ParallelYaml, | ||||
|       ProjectPathYaml, | ||||
|       RetryYaml, | ||||
|       RulesYaml, | ||||
|       RulesNeedsYaml, | ||||
|       RunYaml, | ||||
|       VariablesYaml, | ||||
|       ProjectPathYaml, | ||||
|       IdTokensYaml, | ||||
|       ServicesYaml, | ||||
|       SecretsYaml, | ||||
|       NeedsParallelMatrixYaml, | ||||
|       ScriptYaml, | ||||
|       SecretsYaml, | ||||
|       ServicesYaml, | ||||
|       SpecInputsYaml, | ||||
|       StagesYaml, | ||||
|       TriggerYaml, | ||||
|       VariablesYaml, | ||||
|       WorkflowRulesAutoCancelOnJobFailureYaml, | ||||
|       WorkflowRulesAutoCancelOnNewCommitYaml, | ||||
|       StagesYaml, | ||||
|       RetryYaml, | ||||
|       ParallelYaml, | ||||
|       TriggerYaml, | ||||
|     }), | ||||
|   )('schema validates %s', (_, input) => { | ||||
|     // We construct a new "JSON" from each main key that is inside a
 | ||||
|  | @ -170,43 +173,44 @@ describe('negative tests', () => { | |||
|     Object.entries({ | ||||
|       // JSON
 | ||||
|       DefaultNoAdditionalPropertiesJson, | ||||
|       JobVariablesMustNotContainObjectsJson, | ||||
|       InheritDefaultNoAdditionalPropertiesJson, | ||||
|       JobVariablesMustNotContainObjectsJson, | ||||
|       ReleaseAssetsLinksJson, | ||||
|       RetryUnknownWhenJson, | ||||
| 
 | ||||
|       // YAML
 | ||||
|       ArtifactsNegativeYaml, | ||||
|       ImageNegativeYaml, | ||||
|       CacheKeyNeative, | ||||
|       MultipleCachesYamlNegative, | ||||
|       HooksNegative, | ||||
|       IdTokensNegativeYaml, | ||||
|       ImageNegativeYaml, | ||||
|       IncludeNegativeYaml, | ||||
|       JobWhenNegativeYaml, | ||||
|       RulesNegativeYaml, | ||||
|       RulesNeedsNegativeYaml, | ||||
|       RunNegativeYaml, | ||||
|       TriggerNegativeYaml, | ||||
|       VariablesInvalidOptionsYaml, | ||||
|       VariablesInvalidSyntaxDescYaml, | ||||
|       VariablesWrongSyntaxUsageExpand, | ||||
|       MultipleCachesYamlNegative, | ||||
|       NeedsParallelMatrixNumericYaml, | ||||
|       NeedsParallelMatrixWrongMatrixValueYaml, | ||||
|       NeedsParallelMatrixWrongParallelValueYaml, | ||||
|       ParallelNegativeYaml, | ||||
|       ProjectPathIncludeEmptyYaml, | ||||
|       ProjectPathIncludeInvalidVariableYaml, | ||||
|       ProjectPathIncludeLeadSlashYaml, | ||||
|       ProjectPathIncludeNoSlashYaml, | ||||
|       ProjectPathIncludeTailSlashYaml, | ||||
|       RetryNegativeYaml, | ||||
|       RulesNeedsNegativeYaml, | ||||
|       RulesNegativeYaml, | ||||
|       RunNegativeYaml, | ||||
|       ScriptNegativeYaml, | ||||
|       SecretsNegativeYaml, | ||||
|       ServicesNegativeYaml, | ||||
|       NeedsParallelMatrixNumericYaml, | ||||
|       NeedsParallelMatrixWrongParallelValueYaml, | ||||
|       NeedsParallelMatrixWrongMatrixValueYaml, | ||||
|       ScriptNegativeYaml, | ||||
|       SpecInputsNegativeYaml, | ||||
|       StagesNegativeYaml, | ||||
|       TriggerNegativeYaml, | ||||
|       VariablesInvalidOptionsYaml, | ||||
|       VariablesInvalidSyntaxDescYaml, | ||||
|       VariablesWrongSyntaxUsageExpand, | ||||
|       WorkflowRulesAutoCancelOnJobFailureNegativeYaml, | ||||
|       WorkflowRulesAutoCancelOnNewCommitNegativeYaml, | ||||
|       StagesNegativeYaml, | ||||
|       RetryNegativeYaml, | ||||
|       ParallelNegativeYaml, | ||||
|     }), | ||||
|   )('schema validates %s', (_, input) => { | ||||
|     // We construct a new "JSON" from each main key that is inside a
 | ||||
|  |  | |||
|  | @ -0,0 +1,63 @@ | |||
| # Type mismatch: string type with number default | ||||
| type_mismatch_string: | ||||
|   spec: | ||||
|     inputs: | ||||
|       string_with_number_default: | ||||
|         type: string | ||||
|         default: 123 | ||||
| 
 | ||||
| # Type mismatch: number type with string default | ||||
| type_mismatch_number: | ||||
|   spec: | ||||
|     inputs: | ||||
|       number_with_string_default: | ||||
|         type: number | ||||
|         default: "not a number" | ||||
| 
 | ||||
| # Type mismatch: boolean type with string default | ||||
| type_mismatch_boolean: | ||||
|   spec: | ||||
|     inputs: | ||||
|       boolean_with_string_default: | ||||
|         type: boolean | ||||
|         default: "not a boolean" | ||||
| 
 | ||||
| # Type mismatch: array type with object default | ||||
| type_mismatch_array: | ||||
|   spec: | ||||
|     inputs: | ||||
|       array_with_object_default: | ||||
|         type: array | ||||
|         default: | ||||
|           key: "value" | ||||
| 
 | ||||
| # Invalid input type | ||||
| invalid_input_type: | ||||
|   spec: | ||||
|     inputs: | ||||
|       invalid_type_input: | ||||
|         type: "object" | ||||
|         default: {} | ||||
| 
 | ||||
| # Invalid property on input | ||||
| invalid_property: | ||||
|   spec: | ||||
|     inputs: | ||||
|       input_with_invalid_property: | ||||
|         type: string | ||||
|         invalid_property: "value" | ||||
| 
 | ||||
| # Include with invalid input name | ||||
| include_invalid_input_name: | ||||
|   include: | ||||
|     - local: "template.yml" | ||||
|       inputs: | ||||
|         invalid/name: "value" | ||||
| 
 | ||||
| # Trigger with invalid input value type | ||||
| trigger_invalid_input: | ||||
|   trigger_job: | ||||
|     trigger: | ||||
|       project: "group/project" | ||||
|       inputs: | ||||
|         number_input: "not a number" | ||||
|  | @ -0,0 +1,51 @@ | |||
| # Global component: spec section with inputs | ||||
| spec: | ||||
|   inputs: | ||||
|     string_input: | ||||
|       type: string | ||||
|       description: "String input" | ||||
|     number_input: | ||||
|       type: number | ||||
|       description: "Number input" | ||||
|     boolean_input: | ||||
|       type: boolean | ||||
|       description: "Boolean input" | ||||
|     array_input: | ||||
|       type: array | ||||
|       description: "Array input" | ||||
| 
 | ||||
| # Global component: include with inputs | ||||
| include: | ||||
|   - local: "template.yml" | ||||
|     inputs: | ||||
|       environment: "production" | ||||
|       version: "v2.0.0" | ||||
|       debug: false | ||||
|       workers: 4 | ||||
| 
 | ||||
| # Job: trigger with downstream inputs | ||||
| trigger_job: | ||||
|   trigger: | ||||
|     project: "group/project" | ||||
|     strategy: depend | ||||
|     inputs: | ||||
|       environment: "production" | ||||
|       version: "v2.1.0" | ||||
|       debug: false | ||||
|       matrix: | ||||
|         - browser: "chrome" | ||||
|           device: "desktop" | ||||
| 
 | ||||
| # Job: trigger with child pipeline inputs | ||||
| child_pipeline_job: | ||||
|   trigger: | ||||
|     include: | ||||
|       - local: "child.yml" | ||||
|         inputs: | ||||
|           environment: "staging" | ||||
|           version: "v1.5.0" | ||||
|       - project: "group/project" | ||||
|         file: "template.yml" | ||||
|         inputs: | ||||
|           debug: true | ||||
|           workers: 8 | ||||
|  | @ -5,11 +5,11 @@ import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | |||
| import axios from '~/lib/utils/axios_utils'; | ||||
| import toast from '~/vue_shared/plugins/global_toast'; | ||||
| 
 | ||||
| import { initJwtCiCdJobTokenEnabledToggle } from '~/group_settings/jwt_ci_cd_job_token_enabled_toggle'; | ||||
| import { initSettingsToggles } from '~/group_settings/settings_toggles'; | ||||
| 
 | ||||
| jest.mock('~/vue_shared/plugins/global_toast'); | ||||
| 
 | ||||
| describe('initJwtCiCdJobTokenEnabledToggle', () => { | ||||
| describe('initSettingsToggles', () => { | ||||
|   let form; | ||||
|   let wrapper; | ||||
|   let requestSubmitMock; | ||||
|  | @ -26,12 +26,12 @@ describe('initJwtCiCdJobTokenEnabledToggle', () => { | |||
|   } = {}) => { | ||||
|     setHTMLFixture(` | ||||
|       <form action="${action}"> | ||||
|         <input class="js-jwt-ci-cd-job-token-enabled-input" value="${hiddenInputValue}" type="hidden" name="group[jwt_ci_cd_job_token_enabled]"/> | ||||
|         <span class="js-jwt-ci-cd-job-token-enabled-toggle" data-disabled="${toggleDisabled}" data-is-checked="${toggleIsChecked}" data-is-loading="false" data-label="${toggleLabel}"></span> | ||||
|         <input class="js-setting-input" value="${hiddenInputValue}" type="hidden" name="group[setting]"/> | ||||
|         <span class="js-setting-toggle" data-disabled="${toggleDisabled}" data-is-checked="${toggleIsChecked}" data-is-loading="false" data-label="${toggleLabel}"></span> | ||||
|       </form> | ||||
|     `);
 | ||||
| 
 | ||||
|     const toggle = initJwtCiCdJobTokenEnabledToggle(); | ||||
|     const toggle = initSettingsToggles()[0]; | ||||
| 
 | ||||
|     form = document.querySelector('form'); | ||||
|     wrapper = createWrapper(toggle); | ||||
|  | @ -44,7 +44,7 @@ describe('initJwtCiCdJobTokenEnabledToggle', () => { | |||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   const findInput = () => form.querySelector('[name="group[jwt_ci_cd_job_token_enabled]"]'); | ||||
|   const findInput = () => form.querySelector('[name="group[setting]"]'); | ||||
|   const findToggle = () => wrapper.findComponent(GlToggle); | ||||
| 
 | ||||
|   afterEach(() => { | ||||
|  | @ -15,6 +15,7 @@ RSpec.describe Types::UserPreferencesType, feature_category: :user_profile do | |||
|       organization_groups_projects_sort | ||||
|       organization_groups_projects_display | ||||
|       timezone | ||||
|       merge_request_dashboard_list_type | ||||
|     ] | ||||
| 
 | ||||
|     expect(described_class).to have_graphql_fields(*expected_fields) | ||||
|  |  | |||
|  | @ -2,13 +2,13 @@ | |||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe Gitlab::Git::KeepAround do | ||||
| RSpec.describe Gitlab::Git::KeepAround, feature_category: :gitaly do | ||||
|   include RepoHelpers | ||||
| 
 | ||||
|   let(:repository) { create(:project, :repository).repository } | ||||
|   let(:service) { described_class.new(repository) } | ||||
|   let(:keep_around_ref_name) { "refs/#{::Repository::REF_KEEP_AROUND}/#{sample_commit.id}" } | ||||
|   let(:metric_labels) { { full_path: repository.full_path, source: 'keeparound_spec' } } | ||||
|   let(:metric_labels) { { source: 'keeparound_spec' } } | ||||
| 
 | ||||
|   def expect_metrics_change(requested, created, &block) | ||||
|     requested_metric = Gitlab::Metrics.registry.get(:gitlab_keeparound_refs_requested_total) | ||||
|  | @ -67,33 +67,4 @@ RSpec.describe Gitlab::Git::KeepAround do | |||
|       expect(service.kept_around?(another_sample_commit.id)).to be_truthy | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when disable_keep_around_refs feature flag is enabled' do | ||||
|     before do | ||||
|       stub_feature_flags(disable_keep_around_refs: true) | ||||
|     end | ||||
| 
 | ||||
|     it 'does not create keep-around refs' do | ||||
|       expect_metrics_change(0, 0) do | ||||
|         service.execute([sample_commit.id], source: 'keeparound_spec') | ||||
|       end | ||||
| 
 | ||||
|       expect(service.kept_around?(sample_commit.id)).to be_truthy | ||||
|       expect(repository.list_refs([keep_around_ref_name])).to be_empty | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when label_keep_around_ref_metrics feature flag is disabled' do | ||||
|     let(:metric_labels) { { full_path: '', source: 'keeparound_spec' } } | ||||
| 
 | ||||
|     before do | ||||
|       stub_feature_flags(label_keep_around_ref_metrics: false) | ||||
|     end | ||||
| 
 | ||||
|     it 'does not label keep-around refs' do | ||||
|       expect_metrics_change(1, 1) do | ||||
|         service.execute([sample_commit.id], source: 'keeparound_spec') | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -281,6 +281,82 @@ RSpec.describe Gitlab::OmniauthInitializer, feature_category: :system_access do | |||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'for SAML certificate settings' do | ||||
|         subject(:init) { initializer.execute(Gitlab.config.omniauth.providers) } | ||||
| 
 | ||||
|         def stub_saml_config_fingerprint(fingerprint) | ||||
|           stub_omniauth_config( | ||||
|             providers: [ | ||||
|               { | ||||
|                 name: 'saml', | ||||
|                 args: { | ||||
|                   idp_cert_fingerprint: fingerprint | ||||
|                 } | ||||
|               } | ||||
|             ] | ||||
|           ) | ||||
|         end | ||||
| 
 | ||||
|         it 'sets SHA1 algorithm for SHA1 fingerprint' do | ||||
|           stub_saml_config_fingerprint("DD:80:B1:FA:A9:A7:8D:9D:41:7E:09:10:D8:6F:7D:0A:7E:58:4C:C4") | ||||
| 
 | ||||
|           expect(devise_config).to receive(:omniauth).with( | ||||
|             :saml, | ||||
|             { | ||||
|               attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements, | ||||
|               idp_cert_fingerprint: "DD:80:B1:FA:A9:A7:8D:9D:41:7E:09:10:D8:6F:7D:0A:7E:58:4C:C4", | ||||
|               idp_cert_fingerprint_algorithm: "http://www.w3.org/2000/09/xmldsig#sha1" | ||||
|             } | ||||
|           ) | ||||
| 
 | ||||
|           init | ||||
|         end | ||||
| 
 | ||||
|         it 'sets SHA256 algoritihm for SHA256 fingerprint' do | ||||
|           stub_saml_config_fingerprint( | ||||
|             "73:2D:28:C2:D2:D0:34:9F:F8:9A:9C:74:23:BF:0A:CB:66:75:78:9B:01:4D:1F:7D:60:8F:AD:47:A2:30:D7:4A" | ||||
|           ) | ||||
| 
 | ||||
|           expect(devise_config).to receive(:omniauth).with( | ||||
|             :saml, | ||||
|             { | ||||
|               attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements, | ||||
|               idp_cert_fingerprint: | ||||
|                 "73:2D:28:C2:D2:D0:34:9F:F8:9A:9C:74:23:BF:0A:CB:66:75:78:9B:01:4D:1F:7D:60:8F:AD:47:A2:30:D7:4A", | ||||
|               idp_cert_fingerprint_algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" | ||||
|             } | ||||
|           ) | ||||
| 
 | ||||
|           init | ||||
|         end | ||||
| 
 | ||||
|         it 'does nothing for explicitly configured algorithm' do | ||||
|           stub_omniauth_config( | ||||
|             providers: [ | ||||
|               { | ||||
|                 name: 'saml', | ||||
|                 args: { | ||||
|                   # Mismatched fingerprint and algo to verify we do not mess with explicitly configured values | ||||
|                   idp_cert_fingerprint: "DD:80:B1:FA:A9:A7:8D:9D:41:7E:09:10:D8:6F:7D:0A:7E:58:4C:C4", | ||||
|                   idp_cert_fingerprint_algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" | ||||
|                 } | ||||
|               } | ||||
|             ] | ||||
|           ) | ||||
| 
 | ||||
|           expect(devise_config).to receive(:omniauth).with( | ||||
|             :saml, | ||||
|             { | ||||
|               attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements, | ||||
|               idp_cert_fingerprint: "DD:80:B1:FA:A9:A7:8D:9D:41:7E:09:10:D8:6F:7D:0A:7E:58:4C:C4", | ||||
|               idp_cert_fingerprint_algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" | ||||
|             } | ||||
|           ) | ||||
| 
 | ||||
|           init | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       it 'configures defaults args for multiple SAML providers' do | ||||
|         stub_omniauth_config( | ||||
|           providers: [ | ||||
|  |  | |||
|  | @ -164,7 +164,7 @@ RSpec.describe Ci::JobToken::Authorization, feature_category: :secrets_managemen | |||
|       context 'when authorization is cross project' do | ||||
|         it 'schedules the log' do | ||||
|           expect(::Ci::JobToken::LogAuthorizationWorker) | ||||
|             .to receive(:perform_in).with(5.minutes, accessed_project.id, origin_project.id) | ||||
|             .to receive(:perform_in).with(5.minutes, accessed_project.id, origin_project.id, []) | ||||
| 
 | ||||
|           log_captures_async | ||||
|         end | ||||
|  |  | |||
|  | @ -9993,4 +9993,28 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr | |||
|       expect(project.container_registry_protection_tag_rules).to have_received(:for_actions_and_access).with(%w[push], :maintainer).once | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#job_token_policies_enabled?' do | ||||
|     let_it_be(:project) { build_stubbed(:project) } | ||||
| 
 | ||||
|     subject { project.job_token_policies_enabled? } | ||||
| 
 | ||||
|     where(:flag_enabled, :setting_enabled, :result) do | ||||
|       true  | true  | true | ||||
|       true  | false | true | ||||
|       false | true  | true | ||||
|       false | false | false | ||||
|     end | ||||
| 
 | ||||
|     before do | ||||
|       project.clear_memoization(:job_token_policies_enabled?) | ||||
|       stub_feature_flags(add_policies_to_ci_job_token: flag_enabled) | ||||
|       allow(project).to receive_message_chain(:namespace, :root_ancestor, :namespace_settings, | ||||
|         :job_token_policies_enabled?).and_return(setting_enabled) | ||||
|     end | ||||
| 
 | ||||
|     with_them do | ||||
|       it { is_expected.to eq(result) } | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -114,6 +114,9 @@ RSpec.describe User, feature_category: :user_profile do | |||
|     it { is_expected.to delegate_method(:use_work_items_view).to(:user_preference) } | ||||
|     it { is_expected.to delegate_method(:use_work_items_view=).to(:user_preference).with_arguments(:args) } | ||||
| 
 | ||||
|     it { is_expected.to delegate_method(:merge_request_dashboard_list_type).to(:user_preference) } | ||||
|     it { is_expected.to delegate_method(:merge_request_dashboard_list_type=).to(:user_preference).with_arguments(:args) } | ||||
| 
 | ||||
|     it { is_expected.to delegate_method(:text_editor).to(:user_preference) } | ||||
|     it { is_expected.to delegate_method(:text_editor=).to(:user_preference).with_arguments(:args) } | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,7 +17,8 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi | |||
|       'organizationGroupsProjectsDisplay' => 'GROUPS', | ||||
|       'organizationGroupsProjectsSort' => 'NAME_DESC', | ||||
|       'visibilityPipelineIdType' => 'IID', | ||||
|       'useWorkItemsView' => true | ||||
|       'useWorkItemsView' => true, | ||||
|       'mergeRequestDashboardListType' => 'ROLE_BASED' | ||||
|     } | ||||
|   end | ||||
| 
 | ||||
|  | @ -36,12 +37,14 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi | |||
|       expect(mutation_response['userPreferences']['organizationGroupsProjectsSort']).to eq('NAME_DESC') | ||||
|       expect(mutation_response['userPreferences']['visibilityPipelineIdType']).to eq('IID') | ||||
|       expect(mutation_response['userPreferences']['useWorkItemsView']).to eq(true) | ||||
|       expect(mutation_response['userPreferences']['mergeRequestDashboardListType']).to eq('ROLE_BASED') | ||||
| 
 | ||||
|       expect(current_user.user_preference.persisted?).to eq(true) | ||||
|       expect(current_user.user_preference.extensions_marketplace_opt_in_status).to eq('enabled') | ||||
|       expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s) | ||||
|       expect(current_user.user_preference.visibility_pipeline_id_type).to eq('iid') | ||||
|       expect(current_user.user_preference.use_work_items_view).to eq(true) | ||||
|       expect(current_user.user_preference.merge_request_dashboard_list_type).to eq('role_based') | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  | @ -54,7 +57,8 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi | |||
|         organization_groups_projects_display: Types::Organizations::GroupsProjectsDisplayEnum.values['GROUPS'].value, | ||||
|         organization_groups_projects_sort: 'NAME_DESC', | ||||
|         visibility_pipeline_id_type: 'id', | ||||
|         use_work_items_view: false | ||||
|         use_work_items_view: false, | ||||
|         merge_request_dashboard_list_type: 'action_based' | ||||
|       } | ||||
|     end | ||||
| 
 | ||||
|  | @ -77,6 +81,7 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi | |||
|       expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s) | ||||
|       expect(current_user.user_preference.visibility_pipeline_id_type).to eq('iid') | ||||
|       expect(current_user.user_preference.use_work_items_view).to eq(true) | ||||
|       expect(current_user.user_preference.merge_request_dashboard_list_type).to eq('role_based') | ||||
|     end | ||||
| 
 | ||||
|     context 'when input has nil attributes' do | ||||
|  |  | |||
|  | @ -309,6 +309,7 @@ RSpec.describe NamespaceSettings::AssignAttributesService, feature_category: :gr | |||
|         :new_user_signups_cap | nil | 100 | ||||
|         :seat_control | 'off' | 'user_cap' | ||||
|         :enabled_git_access_protocol | 'all' | 'ssh' | ||||
|         :job_token_policies_enabled | false | true | ||||
|       end | ||||
| 
 | ||||
|       with_them do | ||||
|  |  | |||
|  | @ -90,7 +90,7 @@ RSpec.describe Projects::ImportExport::PruneExpiredExportJobsService, feature_ca | |||
|         it 'deletes stored upload files' do | ||||
|           old_upload_file_paths = Uploads::Local.new.keys(old_uploads) | ||||
| 
 | ||||
|           expect(DeleteStoredFilesWorker).to receive(:perform_async).with(Uploads::Local, old_upload_file_paths) | ||||
|           expect(DeleteStoredFilesWorker).to receive(:perform_async).with(Uploads::Local.name, old_upload_file_paths) | ||||
| 
 | ||||
|           described_class.execute | ||||
|         end | ||||
|  |  | |||
|  | @ -3,6 +3,15 @@ | |||
| RSpec.shared_examples 'enforcing job token policies' do |policies, expected_success_status: :success, | ||||
|     allow_public_access_for_enabled_project_features: nil| | ||||
| 
 | ||||
|   shared_examples 'capturing job token policies' do | ||||
|     it 'captures the policies' do | ||||
|       expect(::Ci::JobToken::Authorization).to receive(:capture_job_token_policies) | ||||
|         .with(Array(policies)).and_call_original | ||||
| 
 | ||||
|       do_request | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when authenticating with a CI job token from another project' do | ||||
|     let(:source_project) { project } | ||||
|     let(:job_user) { user } | ||||
|  | @ -58,8 +67,12 @@ RSpec.shared_examples 'enforcing job token policies' do |policies, expected_succ | |||
|       end | ||||
|     end | ||||
| 
 | ||||
|     it_behaves_like 'capturing job token policies' | ||||
| 
 | ||||
|     context 'when the policies are not allowed' do | ||||
|       let(:allowed_policies) { [] } | ||||
|       let(:allowed_policies) do | ||||
|         (::Ci::JobToken::Policies::POLICIES - Array(policies)).take(1) | ||||
|       end | ||||
| 
 | ||||
|       it { is_expected.to have_gitlab_http_status(:forbidden) } | ||||
| 
 | ||||
|  | @ -82,6 +95,8 @@ RSpec.shared_examples 'enforcing job token policies' do |policies, expected_succ | |||
|         let(:default_permissions) { true } | ||||
| 
 | ||||
|         it { is_expected.to have_gitlab_http_status(expected_success_status) } | ||||
| 
 | ||||
|         it_behaves_like 'capturing job token policies' | ||||
|       end | ||||
| 
 | ||||
|       context 'when job token policies are disabled' do | ||||
|  | @ -92,6 +107,8 @@ RSpec.shared_examples 'enforcing job token policies' do |policies, expected_succ | |||
|         end | ||||
| 
 | ||||
|         it { is_expected.to have_gitlab_http_status(expected_success_status) } | ||||
| 
 | ||||
|         it_behaves_like 'capturing job token policies' | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ RSpec.shared_examples 'logs inbound authorizations via job token' do |success_st | |||
|         .and_call_original | ||||
| 
 | ||||
|       expect(Ci::JobToken::LogAuthorizationWorker) | ||||
|         .to receive(:perform_in).with(5.minutes, accessed_project.id, origin_project.id) | ||||
|         .to receive(:perform_in).with(5.minutes, accessed_project.id, origin_project.id, anything) | ||||
| 
 | ||||
|       perform_request | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,33 +29,26 @@ Status: Experiment | |||
| 
 | ||||
| {{< /history >}} | ||||
| 
 | ||||
| {{< alert type="flag" >}} | ||||
| 
 | ||||
| The availability of this feature is controlled by a feature flag. | ||||
| For more information, see the history. | ||||
| This feature is available for testing, but not ready for production use. | ||||
| 
 | ||||
| {{< /alert >}} | ||||
| 
 | ||||
| You can use fine-grained permissions to explicitly allow access to a limited set of API endpoints. | ||||
| These permissions are applied to the CI/CD job tokens in a specified project. | ||||
| This feature is an [experiment](../../policy/development_stages_support.md#experiment). | ||||
| 
 | ||||
| ## Enable fine-grained permissions | ||||
| This feature is an [experiment](../../policy/development_stages_support.md#experiment) and subject to change without notice. This feature is not ready for production use. If you want to use this feature, you should test outside of production first. | ||||
| 
 | ||||
| ### On GitLab Self-Managed | ||||
| ## Enable fine-grained permissions for projects | ||||
| 
 | ||||
| 1. Start the GitLab Rails console. For information, see [Enable and disable GitLab features deployed behind feature flags](../../administration/feature_flags.md#enable-or-disable-the-feature) | ||||
| 1. Turn on the [feature flag](../../administration/feature_flags.md): | ||||
| Prerequisites: | ||||
| 
 | ||||
| ```ruby | ||||
| # You must include a specific project ID with this command. | ||||
| Feature.enable(:add_policies_to_ci_job_token, <project_id>) | ||||
| ``` | ||||
| - You must have the Owner role for a group. | ||||
| 
 | ||||
| ### On GitLab.com | ||||
| You must turn on fine-grained permissions at the group level. Then, each project in the group can | ||||
| apply fine-grained permissions for CI/CD job tokens to grant access to individual resources. | ||||
| 
 | ||||
| Add a comment on this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/519575) with your project ID. | ||||
| To enable fine-grained permissions for all projects in a group: | ||||
| 
 | ||||
| 1. On the left sidebar, select **Search or go to** and find your group. | ||||
| 1. On the left sidebar, select **Settings > CI/CD**. | ||||
| 1. Expand **General pipelines**. | ||||
| 1. Turn on the **Enable fine-grained permissions for CI/CD job tokens** toggle. | ||||
| 
 | ||||
| ## Available API endpoints | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ require ( | |||
| 	github.com/sirupsen/logrus v1.9.3 | ||||
| 	github.com/stretchr/testify v1.10.0 | ||||
| 	gitlab.com/gitlab-org/gitaly/v16 v16.11.0-rc1.0.20250313033925-4cbd999aaf5d | ||||
| 	gitlab.com/gitlab-org/labkit v1.23.1 | ||||
| 	gitlab.com/gitlab-org/labkit v1.23.2 | ||||
| 	go.uber.org/goleak v1.3.0 | ||||
| 	gocloud.dev v0.40.1-0.20241107185025-56954848c3aa | ||||
| 	golang.org/x/image v0.20.0 | ||||
|  |  | |||
|  | @ -599,8 +599,8 @@ gitlab.com/gitlab-org/gitaly/v16 v16.11.0-rc1.0.20250313033925-4cbd999aaf5d h1:v | |||
| gitlab.com/gitlab-org/gitaly/v16 v16.11.0-rc1.0.20250313033925-4cbd999aaf5d/go.mod h1:i9SSlTe8PnTQoSTnLlx+FYHurZWg11BosyClhJHhdqI= | ||||
| gitlab.com/gitlab-org/go/reopen v1.0.0 h1:6BujZ0lkkjGIejTUJdNO1w56mN1SI10qcVQyQlOPM+8= | ||||
| gitlab.com/gitlab-org/go/reopen v1.0.0/go.mod h1:D6OID8YJDzEVZNYW02R/Pkj0v8gYFSIhXFTArAsBQw8= | ||||
| gitlab.com/gitlab-org/labkit v1.23.1 h1:KvHn1lAykOjeYA3uO/luBDIO8gLmWQreKPvKrsh2PO4= | ||||
| gitlab.com/gitlab-org/labkit v1.23.1/go.mod h1:pikea0zSNSfV3wuMrFST4REaM3yrSjnccIfMyCmlrkw= | ||||
| gitlab.com/gitlab-org/labkit v1.23.2 h1:7KhXddq6+3pi7ozYEVGSQBpAs2SNFuw8wmh0RSgygO0= | ||||
| gitlab.com/gitlab-org/labkit v1.23.2/go.mod h1:pikea0zSNSfV3wuMrFST4REaM3yrSjnccIfMyCmlrkw= | ||||
| go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= | ||||
| go.etcd.io/etcd/raft/v3 v3.5.18 h1:gueCda+9U76Lvk6rINjNc/mXalUp0u8OK5CVESDZh4I= | ||||
| go.etcd.io/etcd/raft/v3 v3.5.18/go.mod h1:XBaZHTJt3nLnpS8hMDR55Sxrq76cEC4xWYMBYSY3jcs= | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue