diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index fb82a2d4501c5cd071255ded9769f017d3d0910a..62117ce128629c8fd0150d41a9c1762c539a769d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -1052,6 +1052,11 @@ def gitlab_duo_settings_data(project) :duo_sast_fp_detection_enabled, project, method(:edit_group_path) + ), + duo_sast_vulnerability_resolution_cascading_settings: project_cascading_namespace_settings_tooltip_data( + :duo_sast_vulnerability_resolution_enabled, + project, + method(:edit_group_path) ) } end diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb index 5cb9a202f240d08e84af80e9b0cb2172a8c91a6b..fd5c14abe1f0053df1db63713a617eb2860d5f3b 100644 --- a/app/models/project_setting.rb +++ b/app/models/project_setting.rb @@ -9,7 +9,8 @@ class ProjectSetting < ApplicationRecord include AfterCommitQueue include SafelyChangeColumnDefault - columns_changing_default :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_sast_fp_detection_enabled + columns_changing_default :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_sast_fp_detection_enabled, + :duo_sast_vulnerability_resolution_enabled ALLOWED_TARGET_PLATFORMS = %w[ios osx tvos watchos android].freeze diff --git a/config/application_setting_columns/duo_sast_vulnerability_resolution_enabled.yml b/config/application_setting_columns/duo_sast_vulnerability_resolution_enabled.yml new file mode 100644 index 0000000000000000000000000000000000000000..628a3ef89f85e6c6247de51140920d11da2b24e7 --- /dev/null +++ b/config/application_setting_columns/duo_sast_vulnerability_resolution_enabled.yml @@ -0,0 +1,11 @@ +api_type: +attr: duo_sast_vulnerability_resolution_enabled +clusterwide: false +column: duo_sast_vulnerability_resolution_enabled +db_type: boolean +default: 'false' +description: +encrypted: false +gitlab_com_different_than_default: false +jihu: false +not_null: true diff --git a/config/application_setting_columns/lock_duo_sast_vulnerability_resolution_enabled.yml b/config/application_setting_columns/lock_duo_sast_vulnerability_resolution_enabled.yml new file mode 100644 index 0000000000000000000000000000000000000000..0693395d644cfb4e27a477f125335397ead154de --- /dev/null +++ b/config/application_setting_columns/lock_duo_sast_vulnerability_resolution_enabled.yml @@ -0,0 +1,11 @@ +api_type: +attr: lock_duo_sast_vulnerability_resolution_enabled +clusterwide: false +column: lock_duo_sast_vulnerability_resolution_enabled +db_type: boolean +default: 'false' +description: +encrypted: false +gitlab_com_different_than_default: false +jihu: false +not_null: true diff --git a/db/migrate/20251121205957_add_duo_sast_vulnerability_resolution_enabled_to_project_settings.rb b/db/migrate/20251121205957_add_duo_sast_vulnerability_resolution_enabled_to_project_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..f4de3557f8561e690f88b5874ed79819992e0383 --- /dev/null +++ b/db/migrate/20251121205957_add_duo_sast_vulnerability_resolution_enabled_to_project_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddDuoSastVulnerabilityResolutionEnabledToProjectSettings < Gitlab::Database::Migration[2.3] + milestone '18.8' + + def change + add_column :project_settings, :duo_sast_vulnerability_resolution_enabled, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20251121210038_add_duo_sast_vulnerability_resolution_enabled_cascading_setting.rb b/db/migrate/20251121210038_add_duo_sast_vulnerability_resolution_enabled_cascading_setting.rb new file mode 100644 index 0000000000000000000000000000000000000000..4c14533948edcb478d7c9224b3f5153f6d0e98f8 --- /dev/null +++ b/db/migrate/20251121210038_add_duo_sast_vulnerability_resolution_enabled_cascading_setting.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddDuoSastVulnerabilityResolutionEnabledCascadingSetting < Gitlab::Database::Migration[2.3] + milestone '18.8' + disable_ddl_transaction! + + include Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings + + def up + add_cascading_namespace_setting :duo_sast_vulnerability_resolution_enabled, :boolean, default: false, null: false + end + + def down + remove_cascading_namespace_setting :duo_sast_vulnerability_resolution_enabled + end +end diff --git a/db/schema_migrations/20251121205957 b/db/schema_migrations/20251121205957 new file mode 100644 index 0000000000000000000000000000000000000000..40297fa23ca8afb906c06468772c56ad9c479254 --- /dev/null +++ b/db/schema_migrations/20251121205957 @@ -0,0 +1 @@ +2f91ce6fa3313a1bfa8c4b4df7fd44ad451b118679a2edb160215aa7bcb35ade \ No newline at end of file diff --git a/db/schema_migrations/20251121210038 b/db/schema_migrations/20251121210038 new file mode 100644 index 0000000000000000000000000000000000000000..436b24294f286753c04f12ad9b13d4241270bc2f --- /dev/null +++ b/db/schema_migrations/20251121210038 @@ -0,0 +1 @@ +5031d42f0557dca63b31f944a12b50b31746f92e184e7a52bcb74a8fde3df3e6 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 3ce3ab37724117cca3e096bdc9aa82311a507be5..eaa912abe15f9d3f1981eb498660f39e89595acb 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12281,6 +12281,8 @@ CREATE TABLE application_settings ( iframe_rendering_allowlist text, database_settings jsonb DEFAULT '{}'::jsonb NOT NULL, usage_billing jsonb DEFAULT '{}'::jsonb NOT NULL, + duo_sast_vulnerability_resolution_enabled boolean DEFAULT false NOT NULL, + lock_duo_sast_vulnerability_resolution_enabled boolean DEFAULT false NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), @@ -21492,6 +21494,8 @@ CREATE TABLE namespace_settings ( duo_sast_fp_detection_enabled boolean, lock_duo_sast_fp_detection_enabled boolean DEFAULT false NOT NULL, usage_billing jsonb DEFAULT '{}'::jsonb NOT NULL, + duo_sast_vulnerability_resolution_enabled boolean, + lock_duo_sast_vulnerability_resolution_enabled boolean DEFAULT false NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)), CONSTRAINT check_d9644d516f CHECK ((char_length(step_up_auth_required_oauth_provider) <= 255)), CONSTRAINT check_namespace_settings_security_policies_is_hash CHECK ((jsonb_typeof(security_policies) = 'object'::text)), @@ -25242,6 +25246,7 @@ CREATE TABLE project_settings ( duo_remote_flows_enabled boolean, duo_foundational_flows_enabled boolean, duo_sast_fp_detection_enabled boolean DEFAULT false NOT NULL, + duo_sast_vulnerability_resolution_enabled boolean DEFAULT false NOT NULL, CONSTRAINT check_1a30456322 CHECK ((char_length(pages_unique_domain) <= 63)), CONSTRAINT check_237486989c CHECK ((char_length(merge_request_title_regex_description) <= 255)), CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)), diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml index 6b8098a6220f1c4ab861e8432a8cb0c6afa7739e..c8983b4b219223bf0de230fefdeef9929e4d2a75 100644 --- a/doc/api/openapi/openapi_v2.yaml +++ b/doc/api/openapi/openapi_v2.yaml @@ -32075,6 +32075,11 @@ paths: description: Enable GitLab Duo SAST false positive detection for this project type: boolean required: false + - in: formData + name: override_params[duo_sast_vulnerability_resolution_enabled] + description: Enable GitLab Duo SAST vulnerability resolution for this project + type: boolean + required: false - in: formData name: override_params[spp_repository_pipeline_access] description: Grant read-only access to security policy configurations for @@ -32792,6 +32797,11 @@ paths: description: Enable GitLab Duo SAST false positive detection for this project type: boolean required: false + - in: formData + name: override_params[duo_sast_vulnerability_resolution_enabled] + description: Enable GitLab Duo SAST vulnerability resolution for this project + type: boolean + required: false - in: formData name: override_params[spp_repository_pipeline_access] description: Grant read-only access to security policy configurations for @@ -33588,6 +33598,11 @@ paths: description: Enable GitLab Duo SAST false positive detection for this project type: boolean required: false + - in: formData + name: override_params[duo_sast_vulnerability_resolution_enabled] + description: Enable GitLab Duo SAST vulnerability resolution for this project + type: boolean + required: false - in: formData name: override_params[spp_repository_pipeline_access] description: Grant read-only access to security policy configurations for @@ -48207,7 +48222,10 @@ definitions: description: Enable GitLab foundational Duo flows for this group duo_sast_fp_detection_availability: type: boolean - description: Enable GitLab Duo SAST false position detection for this group + description: Enable GitLab Duo SAST false positive detection for this group + duo_sast_vulnerability_resolution_availability: + type: boolean + description: Enable GitLab Duo SAST vulnerability resolution for this group amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request @@ -48473,7 +48491,10 @@ definitions: description: Enable GitLab foundational Duo flows for this group duo_sast_fp_detection_availability: type: boolean - description: Enable GitLab Duo SAST false position detection for this group + description: Enable GitLab Duo SAST false positive detection for this group + duo_sast_vulnerability_resolution_availability: + type: boolean + description: Enable GitLab Duo SAST vulnerability resolution for this group amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request @@ -49222,6 +49243,8 @@ definitions: type: string duo_sast_fp_detection_enabled: type: string + duo_sast_vulnerability_resolution_enabled: + type: string web_based_commit_signing_enabled: type: string spp_repository_pipeline_access: @@ -64557,6 +64580,9 @@ definitions: duo_sast_fp_detection_enabled: type: boolean description: Enable GitLab Duo SAST false positive detection for this project + duo_sast_vulnerability_resolution_enabled: + type: boolean + description: Enable GitLab Duo SAST vulnerability resolution for this project spp_repository_pipeline_access: type: boolean description: Grant read-only access to security policy configurations for @@ -65003,6 +65029,9 @@ definitions: duo_sast_fp_detection_enabled: type: boolean description: Enable GitLab Duo SAST false positive detection for this project + duo_sast_vulnerability_resolution_enabled: + type: boolean + description: Enable GitLab Duo SAST vulnerability resolution for this project spp_repository_pipeline_access: type: boolean description: Grant read-only access to security policy configurations for @@ -65452,6 +65481,8 @@ definitions: type: string duo_sast_fp_detection_enabled: type: string + duo_sast_vulnerability_resolution_enabled: + type: string web_based_commit_signing_enabled: type: string spp_repository_pipeline_access: @@ -65934,6 +65965,9 @@ definitions: duo_sast_fp_detection_enabled: type: boolean description: Enable GitLab Duo SAST false positive detection for this project + duo_sast_vulnerability_resolution_enabled: + type: boolean + description: Enable GitLab Duo SAST vulnerability resolution for this project spp_repository_pipeline_access: type: boolean description: Grant read-only access to security policy configurations for diff --git a/ee/app/controllers/concerns/ee/groups/params.rb b/ee/app/controllers/concerns/ee/groups/params.rb index a41d50c3817f9169f09fc6827bd4bde07ba2adac..c71b6b80dad342c1a7fef269a7925e2132acec6b 100644 --- a/ee/app/controllers/concerns/ee/groups/params.rb +++ b/ee/app/controllers/concerns/ee/groups/params.rb @@ -71,7 +71,9 @@ def group_params_ee params_ee.push(%i[duo_features_enabled duo_core_features_enabled lock_duo_features_enabled duo_availability duo_remote_flows_availability prompt_cache_enabled duo_remote_flows_enabled lock_duo_remote_flows_enabled duo_foundational_flows_enabled lock_duo_foundational_flows_enabled - duo_sast_fp_detection_enabled lock_duo_sast_fp_detection_enabled enabled_foundational_flows]) + duo_sast_fp_detection_enabled lock_duo_sast_fp_detection_enabled enabled_foundational_flows + duo_sast_fp_detection_enabled lock_duo_sast_fp_detection_enabled enabled_foundational_flows + duo_sast_vulnerability_resolution_enabled lock_duo_sast_vulnerability_resolution_enabled]) end params_ee << :disable_personal_access_tokens if current_group&.disable_personal_access_tokens_available? diff --git a/ee/app/controllers/ee/projects_controller.rb b/ee/app/controllers/ee/projects_controller.rb index 384e36c3da6f208648b67e5ea23c4e7e489750ab..b23aa844663850d3cfcf3c37baf71db82e38ae57 100644 --- a/ee/app/controllers/ee/projects_controller.rb +++ b/ee/app/controllers/ee/projects_controller.rb @@ -104,6 +104,10 @@ def duo_feature_attributes attributes << :duo_sast_fp_detection_enabled end + unless project&.project_setting&.duo_sast_vulnerability_resolution_enabled_locked? + attributes << :duo_sast_vulnerability_resolution_enabled + end + attributes end diff --git a/ee/app/helpers/ee/application_settings_helper.rb b/ee/app/helpers/ee/application_settings_helper.rb index c7d1d261db535d2721ce2f24c0e0f2a41b9aa563..e0d74ab401d89cae4596d86d0fcac7e329d46018 100644 --- a/ee/app/helpers/ee/application_settings_helper.rb +++ b/ee/app/helpers/ee/application_settings_helper.rb @@ -92,6 +92,8 @@ def visible_attributes :duo_foundational_flows_availability, :duo_sast_fp_detection_enabled, :duo_sast_fp_detection_availability, + :duo_sast_vulnerability_resolution_enabled, + :duo_sast_vulnerability_resolution_availability, :enabled_expanded_logging, :foundational_agents_default_enabled, :foundational_agents_statuses, diff --git a/ee/app/helpers/ee/groups/settings_helper.rb b/ee/app/helpers/ee/groups/settings_helper.rb index dd04113b7ee9acd6396288e2c2129f1f3e764674..8e907b7fad432e1b2d52bb6479bfc64195032d6e 100644 --- a/ee/app/helpers/ee/groups/settings_helper.rb +++ b/ee/app/helpers/ee/groups/settings_helper.rb @@ -84,7 +84,8 @@ def duo_cascading_settings_data duo_availability_cascading_settings: cascading_tooltip_data(:duo_features_enabled), duo_remote_flows_cascading_settings: cascading_tooltip_data(:duo_remote_flows_enabled), duo_foundational_flows_cascading_settings: cascading_tooltip_data(:duo_foundational_flows_enabled), - duo_sast_fp_detection_cascading_settings: cascading_tooltip_data(:duo_sast_fp_detection_enabled) + duo_sast_fp_detection_cascading_settings: cascading_tooltip_data(:duo_sast_fp_detection_enabled), + duo_sast_vulnerability_resolution_cascading_settings: cascading_tooltip_data(:duo_sast_vulnerability_resolution_enabled) } end @@ -120,6 +121,7 @@ def foundational_flows_settings_data duo_remote_flows_availability: @group.namespace_settings.duo_remote_flows_availability.to_s, duo_foundational_flows_availability: @group.namespace_settings.duo_foundational_flows_availability.to_s, duo_sast_fp_detection_availability: @group.namespace_settings.duo_sast_fp_detection_availability.to_s, + duo_sast_vulnerability_resolution_availability: @group.namespace_settings.duo_sast_vulnerability_resolution_availability.to_s, available_foundational_flows: available_foundational_flows_json, selected_foundational_flows: selected_foundational_flows_json } diff --git a/ee/app/helpers/ee/projects_helper.rb b/ee/app/helpers/ee/projects_helper.rb index 01d9f227d8110444c01324161ca2c8c9b40cb15a..7b1e73c4426ce5730dd0511672e56645e472570b 100644 --- a/ee/app/helpers/ee/projects_helper.rb +++ b/ee/app/helpers/ee/projects_helper.rb @@ -59,6 +59,7 @@ def gitlab_duo_settings_data(project) initialDuoRemoteFlowsAvailability: project.duo_remote_flows_enabled, initialDuoFoundationalFlowsAvailability: project.duo_foundational_flows_enabled, initialDuoSastFpDetectionEnabled: project.duo_sast_fp_detection_enabled, + initialDuoSastVulnerabilityResolutionEnabled: project.duo_sast_vulnerability_resolution_enabled, experimentFeaturesEnabled: experiment_features_enabled, paidDuoTier: paid_duo_tier_for_project(project) }) diff --git a/ee/app/models/ee/application_setting.rb b/ee/app/models/ee/application_setting.rb index 25b113db46c2f4ca7e0ecfe1be17a39f2508a553..6aefd8d9f7615107131dfa0365ab99f8cac46a04 100644 --- a/ee/app/models/ee/application_setting.rb +++ b/ee/app/models/ee/application_setting.rb @@ -795,6 +795,16 @@ def duo_sast_fp_detection_availability=(value) end end + def duo_sast_vulnerability_resolution_availability + duo_sast_vulnerability_resolution_enabled + end + + def duo_sast_vulnerability_resolution_availability=(value) + self.duo_sast_vulnerability_resolution_enabled = value + + self.lock_duo_sast_vulnerability_resolution_enabled = !value + end + def duo_never_on? duo_availability == :never_on end diff --git a/ee/app/models/ee/group.rb b/ee/app/models/ee/group.rb index ee1ccc80cf84d8017d63f142f3251232775ec578..58668b7673c52ec884f21845efb04ade466e181b 100644 --- a/ee/app/models/ee/group.rb +++ b/ee/app/models/ee/group.rb @@ -117,6 +117,7 @@ module Group delegate :duo_remote_flows_availability, :duo_remote_flows_availability=, to: :namespace_settings delegate :duo_foundational_flows_availability, :duo_foundational_flows_availability=, to: :namespace_settings delegate :duo_sast_fp_detection_availability, :duo_sast_fp_detection_availability=, to: :namespace_settings + delegate :duo_sast_vulnerability_resolution_availability, :duo_sast_vulnerability_resolution_availability=, to: :namespace_settings delegate :experiment_settings_allowed?, :prompt_cache_settings_allowed?, to: :namespace_settings delegate :user_cap_enabled?, to: :namespace_settings diff --git a/ee/app/models/ee/namespace.rb b/ee/app/models/ee/namespace.rb index 14efb9757a01302f3a6ac6ac940109668e6f9ce6..ea87f9f92ce27eccb894626c171edc462153e7f4 100644 --- a/ee/app/models/ee/namespace.rb +++ b/ee/app/models/ee/namespace.rb @@ -181,6 +181,7 @@ module Namespace :duo_remote_flows_enabled, :lock_duo_remote_flows_enabled, :duo_foundational_flows_enabled, :lock_duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled, :lock_duo_sast_fp_detection_enabled, + :duo_sast_vulnerability_resolution_enabled, :lock_duo_sast_vulnerability_resolution_enabled, to: :namespace_settings, allow_nil: true delegate :pipeline_execution_policies_per_configuration_limit, :pipeline_execution_policies_per_configuration_limit=, :scan_execution_policies_per_configuration_limit, diff --git a/ee/app/models/ee/namespace_setting.rb b/ee/app/models/ee/namespace_setting.rb index ea18f6cc544b1547ee78c44f810e29b3bea7f0c6..cf716f538df1c32fc84d1e66f0dd051b747a8c95 100644 --- a/ee/app/models/ee/namespace_setting.rb +++ b/ee/app/models/ee/namespace_setting.rb @@ -9,7 +9,8 @@ module NamespaceSetting DORMANT_REVIEW_PERIOD = 18.hours.ago cascading_attr :duo_features_enabled, :model_prompt_cache_enabled, :auto_duo_code_review_enabled, - :duo_remote_flows_enabled, :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled + :duo_remote_flows_enabled, :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled, + :duo_sast_vulnerability_resolution_enabled scope :requiring_dormant_member_review, ->(limit) do # look for settings that have not been reviewed in more than @@ -209,6 +210,15 @@ def duo_sast_fp_detection_availability=(value) end end + def duo_sast_vulnerability_resolution_availability + duo_sast_vulnerability_resolution_enabled + end + + def duo_sast_vulnerability_resolution_availability=(value) + self.duo_sast_vulnerability_resolution_enabled = value + self.lock_duo_sast_vulnerability_resolution_enabled = !value + end + def duo_availability if duo_features_enabled && !duo_features_enabled_locked?(include_self: true) :default_on @@ -310,6 +320,8 @@ def experiment_features_allowed duo_remote_flows_availability duo_sast_fp_detection_enabled lock_duo_sast_fp_detection_enabled + duo_sast_vulnerability_resolution_enabled + lock_duo_sast_vulnerability_resolution_enabled enterprise_users_extensions_marketplace_opt_in_status allow_enterprise_bypass_placeholder_confirmation web_based_commit_signing_enabled diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb index e4d6dddcf1e1335ad8dad668a9994bcc290b2df9..aa8007aadf3d7aca9e42d74d64cca2a301a0a2c8 100644 --- a/ee/app/models/ee/project.rb +++ b/ee/app/models/ee/project.rb @@ -547,6 +547,8 @@ def lock_for_confirmation!(id) :duo_foundational_flows_enabled=, :duo_sast_fp_detection_enabled, :duo_sast_fp_detection_enabled=, + :duo_sast_vulnerability_resolution_enabled, + :duo_sast_vulnerability_resolution_enabled=, to: :project_setting with_options prefix: :delegated, to: :project_setting do delegate :require_reauthentication_to_approve= diff --git a/ee/app/models/ee/project_setting.rb b/ee/app/models/ee/project_setting.rb index 281e47f0a0b676ea0909504c5d5ced4d08c18f32..9221eff98cf65ea2db0036ba3a6181a0c02ff057 100644 --- a/ee/app/models/ee/project_setting.rb +++ b/ee/app/models/ee/project_setting.rb @@ -8,7 +8,7 @@ module ProjectSetting prepended do cascading_attr :duo_features_enabled, :spp_repository_pipeline_access, :model_prompt_cache_enabled, :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_foundational_flows_enabled, - :duo_sast_fp_detection_enabled + :duo_sast_fp_detection_enabled, :duo_sast_vulnerability_resolution_enabled belongs_to :push_rule diff --git a/ee/app/presenters/admin/ai_configuration_presenter.rb b/ee/app/presenters/admin/ai_configuration_presenter.rb index e92441724169ca6d8d9ec8305020668476747be0..56b692f636919cf22fd59016ec8a23f22ca6b947 100644 --- a/ee/app/presenters/admin/ai_configuration_presenter.rb +++ b/ee/app/presenters/admin/ai_configuration_presenter.rb @@ -11,6 +11,7 @@ class AiConfigurationPresenter :duo_chat_expiration_column, :duo_chat_expiration_days, :duo_sast_fp_detection_availability, + :duo_sast_vulnerability_resolution_availability, :enabled_expanded_logging, :gitlab_dedicated_instance?, :instance_level_ai_beta_features_enabled, @@ -45,6 +46,7 @@ def settings duo_core_features_enabled: duo_core_features_enabled?, duo_pro_visible: active_duo_add_ons_exist?, duo_sast_fp_detection_availability: duo_sast_fp_detection_availability, + duo_sast_vulnerability_resolution_availability: duo_sast_vulnerability_resolution_availability, enabled_expanded_logging: enabled_expanded_logging, experiment_features_enabled: instance_level_ai_beta_features_enabled, on_general_settings_page: false, @@ -96,7 +98,9 @@ def application_settings strong_memoize_attr :application_settings def foundational_agents_statuses + # rubocop:disable Gitlab/AvoidDefaultOrganization -- needs to use default organization ::Organizations::Organization.default_organization&.foundational_agents_statuses + # rubocop:enable Gitlab/AvoidDefaultOrganization end def ai_settings diff --git a/ee/app/services/ai/cascade_duo_settings_service.rb b/ee/app/services/ai/cascade_duo_settings_service.rb index 107f0a11ff28c18fb1f1f9ad6f3f4b37d8e27057..0d19ccb7ce28fc169691e34ec048eec8ae283ad9 100644 --- a/ee/app/services/ai/cascade_duo_settings_service.rb +++ b/ee/app/services/ai/cascade_duo_settings_service.rb @@ -8,6 +8,7 @@ class CascadeDuoSettingsService auto_duo_code_review_enabled duo_foundational_flows_enabled duo_sast_fp_detection_enabled + duo_sast_vulnerability_resolution_enabled enabled_foundational_flows ].freeze diff --git a/ee/app/services/ee/application_settings/update_service.rb b/ee/app/services/ee/application_settings/update_service.rb index b4c7726d083f522a4916a0d4cc81e0f13c7ab1e1..603a25e29f40739dc2ace7f2de8640e9179f37aa 100644 --- a/ee/app/services/ee/application_settings/update_service.rb +++ b/ee/app/services/ee/application_settings/update_service.rb @@ -49,7 +49,9 @@ def execute def handle_organization_settings return unless params.has_key?(:foundational_agents_statuses) + # rubocop:disable Gitlab/AvoidDefaultOrganization -- needs to use default organization default_organization = ::Organizations::Organization.default_organization + # rubocop:enable Gitlab/AvoidDefaultOrganization default_organization.update(foundational_agents_statuses: params.delete(:foundational_agents_statuses)) @@ -111,7 +113,7 @@ def filter_auto_duo_code_review_param def cascade_duo_features_settings previous_changes = application_setting.previous_changes cascading_ai_settings = [:duo_features_enabled, :duo_remote_flows_enabled, :auto_duo_code_review_enabled, - :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled] + :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled, :duo_sast_vulnerability_resolution_enabled] # Collect all changed AI settings and their values changed_ai_settings = cascading_ai_settings.filter_map do |setting| diff --git a/ee/app/services/ee/groups/update_service.rb b/ee/app/services/ee/groups/update_service.rb index 61e896120347ff238989300218db77cf755076f9..89e3dd25287c91c959f45ab690ded24934ad0920 100644 --- a/ee/app/services/ee/groups/update_service.rb +++ b/ee/app/services/ee/groups/update_service.rb @@ -171,7 +171,7 @@ def update_cascading_settings return unless previous_changes.present? cascading_ai_settings = [:duo_features_enabled, :duo_remote_flows_enabled, :auto_duo_code_review_enabled, - :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled] + :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled, :duo_sast_vulnerability_resolution_enabled] # Collect all changed AI settings and their values changed_ai_settings = cascading_ai_settings.filter_map do |setting| if previous_changes.include?(setting) diff --git a/ee/app/services/ee/namespace_settings/assign_attributes_service.rb b/ee/app/services/ee/namespace_settings/assign_attributes_service.rb index 374282654297cd38d411be6e42fd172d7e8a27c3..51ed7c33ba2ebfbf6136e837142f6bc7038d5fff 100644 --- a/ee/app/services/ee/namespace_settings/assign_attributes_service.rb +++ b/ee/app/services/ee/namespace_settings/assign_attributes_service.rb @@ -55,6 +55,14 @@ def execute param_key: :lock_duo_sast_fp_detection_enabled, user_policy: :admin_group ) + validate_settings_param_for_admin( + param_key: :duo_sast_vulnerability_resolution_enabled, + user_policy: :admin_group + ) + validate_settings_param_for_admin( + param_key: :lock_duo_sast_vulnerability_resolution_enabled, + user_policy: :admin_group + ) validate_settings_param_for_root_group( param_key: :disable_invite_members, user_policy: :owner_access diff --git a/ee/lib/ee/api/entities/project.rb b/ee/lib/ee/api/entities/project.rb index 3e36db278d0c077046691218ddec2fed75057b44..61da89fc8370940801cb7643e619d6f8d9f31367 100644 --- a/ee/lib/ee/api/entities/project.rb +++ b/ee/lib/ee/api/entities/project.rb @@ -68,6 +68,7 @@ def preload_relation(projects_relation, options = {}) expose :duo_remote_flows_enabled, if: ->(_, _) { ::Ai::DuoWorkflow.enabled? } expose :duo_foundational_flows_enabled, if: ->(_, _) { ::Ai::DuoWorkflow.enabled? } expose :duo_sast_fp_detection_enabled, if: ->(project, _) { project.licensed_feature_available?(:ai_features) && ::Feature.enabled?(:ai_experiment_sast_fp_detection, project) } + expose :duo_sast_vulnerability_resolution_enabled, if: ->(project, _) { project.licensed_feature_available?(:ai_features) && ::Feature.enabled?(:enable_vulnerability_resolution, project.root_ancestor) } expose :web_based_commit_signing_enabled, if: ->(project, options) do ::Gitlab::Saas.feature_available?(:repositories_web_based_commit_signing) && Ability.allowed?(options[:current_user], :admin_project, project) diff --git a/ee/lib/ee/api/groups.rb b/ee/lib/ee/api/groups.rb index e4bad4723799ec6fddddfc69a49cb3c4619ad385..a6bbebd094b85d055479cfd7c51efc1475bf107c 100644 --- a/ee/lib/ee/api/groups.rb +++ b/ee/lib/ee/api/groups.rb @@ -92,6 +92,11 @@ def remove_unlicensed_params(group) params.delete(:duo_sast_fp_detection_availability) end + unless group.licensed_feature_available?(:ai_features) && ::Feature.enabled?( + :enable_vulnerability_resolution, group.root_ancestor) + params.delete(:duo_sast_vulnerability_resolution_availability) + end + return if group.licensed_feature_available?(:group_level_merge_checks_setting) params.delete(:only_allow_merge_if_pipeline_succeeds) diff --git a/ee/lib/ee/api/helpers/groups_helpers.rb b/ee/lib/ee/api/helpers/groups_helpers.rb index b6cc393bb641dfe6f34f5528c3ea5f7067eebba7..26cb75a6583f5f1604036ec9758912993840ea1f 100644 --- a/ee/lib/ee/api/helpers/groups_helpers.rb +++ b/ee/lib/ee/api/helpers/groups_helpers.rb @@ -32,7 +32,8 @@ module GroupsHelpers optional :duo_availability, type: String, values: %w[default_on default_off never_on], desc: 'Duo availability. One of `default_on`, `default_off` or `never_on`' optional :duo_remote_flows_availability, type: ::Grape::API::Boolean, desc: 'Enable GitLab Duo remote flows for this group' optional :duo_foundational_flows_availability, type: ::Grape::API::Boolean, desc: 'Enable GitLab foundational Duo flows for this group' - optional :duo_sast_fp_detection_availability, type: ::Grape::API::Boolean, desc: 'Enable GitLab Duo SAST false position detection for this group' + optional :duo_sast_fp_detection_availability, type: ::Grape::API::Boolean, desc: 'Enable GitLab Duo SAST false positive detection for this group' + optional :duo_sast_vulnerability_resolution_availability, type: ::Grape::API::Boolean, desc: 'Enable GitLab Duo SAST vulnerability resolution for this group' optional :amazon_q_auto_review_enabled, type: ::Grape::API::Boolean, desc: 'Enable Amazon Q auto review for merge request' optional :experiment_features_enabled, type: ::Grape::API::Boolean, desc: 'Enable experiment features for this group' optional :model_prompt_cache_enabled, type: ::Grape::API::Boolean, desc: 'Enable model prompt cache for this group' diff --git a/ee/lib/ee/api/helpers/projects_helpers.rb b/ee/lib/ee/api/helpers/projects_helpers.rb index 101080226b5ef96ee2fdcf4fc047883d8b07880e..2decbef949467055c7d6af405518d771e9ff3163 100644 --- a/ee/lib/ee/api/helpers/projects_helpers.rb +++ b/ee/lib/ee/api/helpers/projects_helpers.rb @@ -26,6 +26,7 @@ module ProjectsHelpers optional :auto_duo_code_review_enabled, type: Grape::API::Boolean, desc: 'Enable automatic reviews by GitLab Duo on merge requests' optional :duo_remote_flows_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo remote flows for this project' optional :duo_sast_fp_detection_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo SAST false positive detection for this project' + optional :duo_sast_vulnerability_resolution_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo SAST vulnerability resolution for this project' optional :spp_repository_pipeline_access, type: Grape::API::Boolean, desc: 'Grant read-only access to security policy configurations for enforcement in linked CI/CD projects' end @@ -55,6 +56,7 @@ module ProjectsHelpers desc: 'Enable web based commit signing for this project' optional :duo_remote_flows_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo remote flows for this project' optional :duo_sast_fp_detection_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo SAST false positive detection for this project' + optional :duo_sast_vulnerability_resolution_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo SAST vulnerability resolution for this project' optional :spp_repository_pipeline_access, type: Grape::API::Boolean, desc: 'Grant read-only access to security policy configurations for enforcement in linked CI/CD projects' end @@ -76,6 +78,7 @@ def update_params_at_least_one_of :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_sast_fp_detection_enabled, + :duo_sast_vulnerability_resolution_enabled, :allow_pipeline_trigger_approve_deployment, :only_allow_merge_if_all_status_checks_passed, :approvals_before_merge, @@ -101,42 +104,47 @@ def update_params_at_least_one_of def filter_attributes_using_license!(attrs) super - unless ::License.feature_available?(:external_authorization_service_api_management) - attrs.delete(:external_authorization_classification_label) - end + delete_if_license_unavailable(attrs, :external_authorization_classification_label, :external_authorization_service_api_management) + delete_if_license_unavailable(attrs, :allow_pipeline_trigger_approve_deployment, :protected_environments) + delete_if_license_unavailable(attrs, :prevent_merge_without_jira_issue, :jira_issue_association_enforcement) + delete_if_license_unavailable(attrs, :ci_restrict_pipeline_cancellation_role, :ci_pipeline_cancellation_restrictions) + delete_auto_duo_code_review_if_unavailable(attrs) + delete_if_license_unavailable(attrs, :duo_remote_flows_enabled, :ai_workflows) + delete_duo_sast_fp_detection_if_unavailable(attrs) + delete_duo_sast_vulnerability_resolution_if_unavailable(attrs) + delete_if_license_unavailable(attrs, :spp_repository_pipeline_access, :security_orchestration_policies) + end - unless ::License.feature_available?(:protected_environments) - attrs.delete(:allow_pipeline_trigger_approve_deployment) - end + private - unless ::License.feature_available?(:jira_issue_association_enforcement) - attrs.delete(:prevent_merge_without_jira_issue) - end + def delete_if_license_unavailable(attrs, attr_key, license_feature) + attrs.delete(attr_key) unless ::License.feature_available?(license_feature) + end - unless ::License.feature_available?(:ci_pipeline_cancellation_restrictions) - attrs.delete(:ci_restrict_pipeline_cancellation_role) + def delete_duo_sast_fp_detection_if_unavailable(attrs) + if ::License.feature_available?(:ai_features) && ::Feature.enabled?(:ai_experiment_sast_fp_detection, current_user) + return end - if params[:id].present? - # Existing project (update) - check both license and availability - unless ::License.feature_available?(:review_merge_request) && - user_project.auto_duo_code_review_settings_available? - attrs.delete(:auto_duo_code_review_enabled) - end - else - # New project (creation) - only check license - attrs.delete(:auto_duo_code_review_enabled) unless ::License.feature_available?(:review_merge_request) + attrs.delete(:duo_sast_fp_detection_enabled) + end + + def delete_duo_sast_vulnerability_resolution_if_unavailable(attrs) + if ::License.feature_available?(:ai_features) && ::Feature.enabled?(:enable_vulnerability_resolution, user_project.root_ancestor) + return end - attrs.delete(:duo_remote_flows_enabled) unless License.feature_available?(:ai_workflows) + attrs.delete(:duo_sast_vulnerability_resolution_enabled) + end - unless License.feature_available?(:ai_features) && ::Feature.enabled?(:ai_experiment_sast_fp_detection, current_user) - attrs.delete(:duo_sast_fp_detection_enabled) - end + def delete_auto_duo_code_review_if_unavailable(attrs) + return if ::License.feature_available?(:review_merge_request) && user_project_available_for_auto_review? - return if ::License.feature_available?(:security_orchestration_policies) + attrs.delete(:auto_duo_code_review_enabled) + end - attrs.delete(:spp_repository_pipeline_access) + def user_project_available_for_auto_review? + params[:id].present? ? user_project.auto_duo_code_review_settings_available? : true end end end diff --git a/ee/spec/controllers/ee/projects_controller_spec.rb b/ee/spec/controllers/ee/projects_controller_spec.rb index 5ea12e09fdbd69cbd2e0fa62422b29df4c0c3e71..21323078e63ec58e68f2a61db14b9fce703cd701 100644 --- a/ee/spec/controllers/ee/projects_controller_spec.rb +++ b/ee/spec/controllers/ee/projects_controller_spec.rb @@ -962,6 +962,49 @@ end end end + + context 'when duo_sast_vulnerability_resolution_enabled param is specified' do + let(:params) { { project_setting_attributes: { duo_sast_vulnerability_resolution_enabled: true } } } + + let(:request) do + put :update, params: { namespace_id: project.namespace, id: project, project: params } + end + + it 'updates duo_sast_vulnerability_resolution_enabled' do + project.project_setting.duo_sast_vulnerability_resolution_enabled = false + project.project_setting.save! + + request + + expect(project.reload.project_setting.duo_sast_vulnerability_resolution_enabled).to eq(true) + end + + context 'when duo sast vulnerability resolution is locked by the ancestor' do + before do + project.project_setting.duo_sast_vulnerability_resolution_enabled = false + project.project_setting.save! + + project.namespace.namespace_settings.lock_duo_sast_vulnerability_resolution_enabled = true + project.namespace.namespace_settings.duo_sast_vulnerability_resolution_enabled = false + project.namespace.namespace_settings.save! + end + + it 'does not update duo sast vulnerability resolution' do + expect { request }.not_to change { project.reload.project_setting.duo_sast_vulnerability_resolution_enabled }.from(false) + end + + context 'with more params passed' do + let(:params) do + { project_setting_attributes: { duo_sast_vulnerability_resolution_enabled: true }, description: 'Settings test' } + end + + it 'does not update duo sast vulnerability resolution, but updates other attributes' do + expect { request }.not_to change { project.reload.project_setting.duo_sast_vulnerability_resolution_enabled }.from(false) + expect(project.description).to eq('Settings test') + end + end + end + end end describe '#download_export', feature_category: :importers do diff --git a/ee/spec/helpers/ee/groups/settings_helper_spec.rb b/ee/spec/helpers/ee/groups/settings_helper_spec.rb index 2d165688a4fba93aabaa4f0b0ef83e9a594f1dde..1b7f77a06816d380b68d125eaf483d244b7156e8 100644 --- a/ee/spec/helpers/ee/groups/settings_helper_spec.rb +++ b/ee/spec/helpers/ee/groups/settings_helper_spec.rb @@ -111,6 +111,10 @@ duo_sast_fp_detection_cascading_settings: "{\"locked_by_application_setting\":false," \ "\"locked_by_ancestor\":false}", duo_sast_fp_detection_availability: group.namespace_settings.duo_sast_fp_detection_availability.to_s, + duo_sast_vulnerability_resolution_cascading_settings: "{\"locked_by_application_setting\":false," \ + "\"locked_by_ancestor\":false}", + duo_sast_vulnerability_resolution_availability: group.namespace_settings + .duo_sast_vulnerability_resolution_availability.to_s, duo_core_features_enabled: group.namespace_settings.duo_core_features_enabled.to_s, are_duo_settings_locked: group.namespace_settings.duo_features_enabled_locked?.to_s, experiment_features_enabled: group.namespace_settings.experiment_features_enabled.to_s, diff --git a/ee/spec/helpers/projects_helper_spec.rb b/ee/spec/helpers/projects_helper_spec.rb index 57f2ec20ea4c42692bdba476cfccddc48617c065..b1d9460b87548e8458ce61c34f76c0c04ebad120 100644 --- a/ee/spec/helpers/projects_helper_spec.rb +++ b/ee/spec/helpers/projects_helper_spec.rb @@ -704,6 +704,7 @@ initialDuoRemoteFlowsAvailability: true, initialDuoFoundationalFlowsAvailability: true, initialDuoSastFpDetectionEnabled: false, + initialDuoSastVulnerabilityResolutionEnabled: false, experimentFeaturesEnabled: false } end diff --git a/ee/spec/lib/ee/api/entities/project_spec.rb b/ee/spec/lib/ee/api/entities/project_spec.rb index 8679b216b7e34faa7510e570817c42b174d6f255..6d2fefe58e038b4bc622f6f2d820b04965622a0e 100644 --- a/ee/spec/lib/ee/api/entities/project_spec.rb +++ b/ee/spec/lib/ee/api/entities/project_spec.rb @@ -232,6 +232,41 @@ def mock_available end end + describe 'duo_sast_vulnerability_resolution_enabled' do + context 'when project is licensed to use ai_features and feature flag is enabled' do + before do + stub_licensed_features(ai_features: true) + end + + it 'returns a boolean value' do + expect(subject[:duo_sast_vulnerability_resolution_enabled]).to be_in([true, false]) + end + end + + context 'when project is licensed to use ai_features but feature flag is disabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(enable_vulnerability_resolution: false) + end + + it 'returns nil' do + expect(subject[:duo_sast_vulnerability_resolution_enabled]).to be_nil + end + end + + context 'when project is not licensed to use ai_features' do + let(:current_user) { developer } + + before do + stub_licensed_features(ai_features: false) + end + + it 'returns nil' do + expect(subject[:duo_sast_vulnerability_resolution_enabled]).to be_nil + end + end + end + describe 'web_based_commit_signing_enabled' do before do stub_saas_features(repositories_web_based_commit_signing: repositories_web_based_commit_signing) diff --git a/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb b/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb index 1e54928522a3042d2976285e21214039e1682655..6f72ada249e2c73b9b56622f06fb8413a609fb03 100644 --- a/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb +++ b/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb @@ -130,6 +130,7 @@ web_based_commit_signing_enabled allow_enterprise_bypass_placeholder_confirmation enterprise_bypass_expires_at allow_personal_snippets lock_auto_duo_code_review_enabled auto_duo_code_review_enabled lock_duo_remote_flows_enabled duo_remote_flows_enabled lock_duo_foundational_flows_enabled + lock_duo_sast_vulnerability_resolution_enabled duo_sast_vulnerability_resolution_enabled duo_foundational_flows_enabled lock_duo_sast_fp_detection_enabled duo_sast_fp_detection_enabled] columns_to_audit = Namespaces::NamespaceSettingChangesAuditor::EVENT_NAME_PER_COLUMN.keys.map(&:to_s) diff --git a/ee/spec/models/application_setting_spec.rb b/ee/spec/models/application_setting_spec.rb index 1b54705ca9721032d05c068980cd5befe0608f5d..7166c685128281a422e8d42df17a89aa2958ba04 100644 --- a/ee/spec/models/application_setting_spec.rb +++ b/ee/spec/models/application_setting_spec.rb @@ -765,6 +765,29 @@ end end + describe '#duo_sast_vulnerability_resolution_availability=' do + using RSpec::Parameterized::TableSyntax + + where(:duo_sast_vulnerability_resolution_availability, :duo_sast_vulnerability_resolution_enabled_expectation, + :lock_duo_sast_vulnerability_resolution_enabled_expectation) do + true | true | false + false | false | true + end + + with_them do + before do + setting.duo_sast_vulnerability_resolution_availability = duo_sast_vulnerability_resolution_availability + end + + it 'returns the expected response' do + expect(setting.duo_sast_vulnerability_resolution_enabled) + .to be duo_sast_vulnerability_resolution_enabled_expectation + expect(setting.lock_duo_sast_vulnerability_resolution_enabled) + .to be lock_duo_sast_vulnerability_resolution_enabled_expectation + end + end + end + describe '#enabled_expanded_logging' do it "updates ::Ai::Settings.instance.enabled_instance_verbose_ai_logs" do ::Ai::Setting.instance.update!(enabled_instance_verbose_ai_logs: false) diff --git a/ee/spec/models/ee/namespace_spec.rb b/ee/spec/models/ee/namespace_spec.rb index 78e9ac95929c3a5485f633a2bd96d08243534725..0fd90194a10ed64927514d8b1154f64954d9be18 100644 --- a/ee/spec/models/ee/namespace_spec.rb +++ b/ee/spec/models/ee/namespace_spec.rb @@ -57,6 +57,8 @@ it { is_expected.to delegate_method(:lock_duo_remote_flows_enabled).to(:namespace_settings).allow_nil } it { is_expected.to delegate_method(:duo_sast_fp_detection_enabled).to(:namespace_settings).allow_nil } it { is_expected.to delegate_method(:lock_duo_sast_fp_detection_enabled).to(:namespace_settings).allow_nil } + it { is_expected.to delegate_method(:duo_sast_vulnerability_resolution_enabled).to(:namespace_settings).allow_nil } + it { is_expected.to delegate_method(:lock_duo_sast_vulnerability_resolution_enabled).to(:namespace_settings).allow_nil } it { is_expected.to delegate_method(:security_policy_management_project).to(:security_orchestration_policy_configuration) } it { is_expected.to delegate_method(:allow_enterprise_bypass_placeholder_confirmation).to(:namespace_settings).allow_nil } it { is_expected.to delegate_method(:allow_enterprise_bypass_placeholder_confirmation=).to(:namespace_settings).with_arguments(:args) } diff --git a/ee/spec/models/ee/project_setting_spec.rb b/ee/spec/models/ee/project_setting_spec.rb index 711a253a5ad846b0358b5fbbe4a6836e9471abf4..59f7da3a183830dd50ab505c424ee0dfa0509579 100644 --- a/ee/spec/models/ee/project_setting_spec.rb +++ b/ee/spec/models/ee/project_setting_spec.rb @@ -180,6 +180,11 @@ settings_attribute_name: :duo_sast_fp_detection_enabled end + describe '#duo_sast_vulnerability_resolution_enabled' do + it_behaves_like 'a cascading project setting boolean attribute', + settings_attribute_name: :duo_sast_vulnerability_resolution_enabled + end + describe '#enabled_foundational_flows' do let(:setting) { build(:project_setting) } diff --git a/ee/spec/models/namespace_setting_spec.rb b/ee/spec/models/namespace_setting_spec.rb index 04ba22c432d3c6320c1b2667edcf803a2dc9ae3f..d2ee60d17dcebea42980178d32800b432be0d4de 100644 --- a/ee/spec/models/namespace_setting_spec.rb +++ b/ee/spec/models/namespace_setting_spec.rb @@ -652,6 +652,26 @@ end end + describe '#duo_sast_vulnerability_resolution_availability=' do + using RSpec::Parameterized::TableSyntax + + where(:duo_sast_vulnerability_resolution_availability, :duo_sast_vulnerability_resolution_enabled_expectation, :lock_duo_sast_vulnerability_resolution_enabled_expectation) do + true | true | false + false | false | true + end + + with_them do + before do + setting.duo_sast_vulnerability_resolution_availability = duo_sast_vulnerability_resolution_availability + end + + it 'returns the expected response' do + expect(setting.duo_sast_vulnerability_resolution_enabled).to be duo_sast_vulnerability_resolution_enabled_expectation + expect(setting.lock_duo_sast_vulnerability_resolution_enabled).to be lock_duo_sast_vulnerability_resolution_enabled_expectation + end + end + end + describe 'validating new_user_signup_cap' do using RSpec::Parameterized::TableSyntax diff --git a/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb b/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb index d97cb8c34068faf8f18192faaa405024f9668475..586d0d4e04dcca82696ef81c2a6a509462f1b3c1 100644 --- a/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb +++ b/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb @@ -19,6 +19,7 @@ duo_remote_flows_availability: true, duo_foundational_flows_availability: false, duo_sast_fp_detection_availability: true, + duo_sast_vulnerability_resolution_availability: true, duo_chat_expiration_column: 'last_updated_at', duo_chat_expiration_days: '30', enabled_expanded_logging: true, @@ -86,6 +87,7 @@ duo_remote_flows_availability: 'true', duo_foundational_flows_availability: 'false', duo_sast_fp_detection_availability: 'true', + duo_sast_vulnerability_resolution_availability: 'true', duo_chat_expiration_column: 'last_updated_at', duo_chat_expiration_days: '30', duo_core_features_enabled: 'true', diff --git a/ee/spec/requests/api/groups_spec.rb b/ee/spec/requests/api/groups_spec.rb index 6c766272b6345da75993715002f599b6cff9d4ab..6d282bbf0e9517a48d26885b1a4c2965f50306e2 100644 --- a/ee/spec/requests/api/groups_spec.rb +++ b/ee/spec/requests/api/groups_spec.rb @@ -918,6 +918,54 @@ end end + context 'duo_sast_vulnerability_resolution_availability' do + context 'when licence is available and feature flag is enabled' do + before do + stub_licensed_features(ai_features: true) + group.namespace_settings.update!(duo_sast_vulnerability_resolution_availability: true) + end + + it 'updates duo_sast_vulnerability_resolution_enabled field of namespace settings' do + expect do + put api("/groups/#{group.id}", user), params: { duo_sast_vulnerability_resolution_availability: false } + end.to change { group.reload.namespace_settings.duo_sast_vulnerability_resolution_enabled }.from(true).to(false) + .and change { group.reload.namespace_settings.lock_duo_sast_vulnerability_resolution_enabled }.from(false).to(true) + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when licence is available but feature flag is disabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(enable_vulnerability_resolution: false) + end + + it 'does not update duo_sast_vulnerability_resolution_enabled field of namespace settings' do + expect do + put api("/groups/#{group.id}", user), params: { duo_sast_vulnerability_resolution_availability: false } + end.not_to change { group.reload.namespace_settings.duo_sast_vulnerability_resolution_enabled } + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when licence is not available' do + before do + stub_licensed_features(ai_features: false) + stub_feature_flags(enable_vulnerability_resolution: true) + end + + it 'does not update duo_sast_vulnerability_resolution_enabled field of namespace settings' do + expect do + put api("/groups/#{group.id}", user), params: { duo_sast_vulnerability_resolution_availability: false } + end.not_to change { group.reload.namespace_settings.duo_sast_vulnerability_resolution_enabled } + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + context 'auto_duo_code_review_enabled', :saas do using RSpec::Parameterized::TableSyntax diff --git a/ee/spec/requests/api/projects_spec.rb b/ee/spec/requests/api/projects_spec.rb index 9e56d0853da0a69c7936638ba69cb3465ec38af8..aa24ecfa7889776de66e4f4b9e13c1ff8937ee33 100644 --- a/ee/spec/requests/api/projects_spec.rb +++ b/ee/spec/requests/api/projects_spec.rb @@ -2267,6 +2267,46 @@ def decode_cursor(cursor) end end + context 'when setting duo_sast_vulnerability_resolution_enabled' do + let(:project_params) { { duo_sast_vulnerability_resolution_enabled: true } } + + context 'when licence is available and feature flag is enabled' do + before do + stub_licensed_features(ai_features: true) + end + + it 'updates the value' do + expect { subject }.to change { project.reload.duo_sast_vulnerability_resolution_enabled } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['duo_sast_vulnerability_resolution_enabled']).to eq true + end + end + + context 'when licence is available but feature flag is disabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(enable_vulnerability_resolution: false) + end + + it 'does not update the value and does not expose it in response' do + expect { subject }.not_to change { project.reload.duo_sast_vulnerability_resolution_enabled } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['duo_sast_vulnerability_resolution_enabled']).to be_nil + end + end + + context 'when licence is not available' do + it 'does not update the value and does not expose it in response' do + expect { subject }.not_to change { project.reload.duo_sast_vulnerability_resolution_enabled } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['duo_sast_vulnerability_resolution_enabled']).to be_nil + end + end + end + context 'updating web_based_commit_signing_enabled' do using RSpec::Parameterized::TableSyntax diff --git a/ee/spec/services/application_settings/update_service_spec.rb b/ee/spec/services/application_settings/update_service_spec.rb index b15cade53eeda436c8c6b0258d8645a53270c59a..6570cf2f3b7ed5143a445a4e37175186bcb0c779 100644 --- a/ee/spec/services/application_settings/update_service_spec.rb +++ b/ee/spec/services/application_settings/update_service_spec.rb @@ -388,6 +388,14 @@ it_behaves_like 'when updating duo settings', :duo_sast_fp_detection_enabled, false end + context 'when updating duo_sast_vulnerability_resolution_enabled' do + before do + setting.update!(duo_sast_vulnerability_resolution_enabled: true) + end + + it_behaves_like 'when updating duo settings', :duo_sast_vulnerability_resolution_enabled, false + end + context 'when updating duo_agent_platform_enabled' do let(:opts) { { duo_agent_platform_enabled: false } } diff --git a/ee/spec/services/groups/update_service_spec.rb b/ee/spec/services/groups/update_service_spec.rb index 78ee7bb03b9ee3f8a636a2d123ac402d1e6aa600..9c5d9ba773e91df1fa5a7a0f1a3b1faf97fa362c 100644 --- a/ee/spec/services/groups/update_service_spec.rb +++ b/ee/spec/services/groups/update_service_spec.rb @@ -690,6 +690,10 @@ def update_file_template_project_id(id) it_behaves_like 'when updating duo settings', :duo_sast_fp_detection_enabled, false end + context 'when updating duo_sast_vulnerability_resolution_enabled' do + it_behaves_like 'when updating duo settings', :duo_sast_vulnerability_resolution_enabled, false + end + context 'when updating lock_duo_features_enabled' do let_it_be_with_reload(:user) { create(:user) } let_it_be_with_reload(:group) { create(:group, :public) } diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml index d045911061dbfffa39974048a1f5709a4449433c..edef593aeaf189bfb615a84a9e507bec00054641 100644 --- a/spec/requests/api/project_attributes.yml +++ b/spec/requests/api/project_attributes.yml @@ -205,6 +205,7 @@ project_setting: - duo_remote_flows_enabled - duo_foundational_flows_enabled - duo_sast_fp_detection_enabled + - duo_sast_vulnerability_resolution_enabled - enabled_foundational_flows build_service_desk_setting: # service_desk_setting unexposed_attributes: