diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e253037aaa09cd3bcc8f85873968ad053bca7143..64139bc2615aa467b46867ce3e9c8a2411b8a96a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -1040,6 +1040,11 @@ def gitlab_duo_settings_data(project) :duo_foundational_flows_enabled, project, method(:edit_group_path) + ), + duo_sast_fp_detection_cascading_settings: project_cascading_namespace_settings_tooltip_data( + :duo_sast_fp_detection_enabled, + project, + method(:edit_group_path) ) } end diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb index e8cceb53102d0cbbe4b2c0da787e0f26bfb41c72..5cb9a202f240d08e84af80e9b0cb2172a8c91a6b 100644 --- a/app/models/project_setting.rb +++ b/app/models/project_setting.rb @@ -9,7 +9,7 @@ class ProjectSetting < ApplicationRecord include AfterCommitQueue include SafelyChangeColumnDefault - columns_changing_default :auto_duo_code_review_enabled, :duo_remote_flows_enabled + columns_changing_default :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_sast_fp_detection_enabled ALLOWED_TARGET_PLATFORMS = %w[ios osx tvos watchos android].freeze diff --git a/config/application_setting_columns/duo_sast_fp_detection_enabled.yml b/config/application_setting_columns/duo_sast_fp_detection_enabled.yml new file mode 100644 index 0000000000000000000000000000000000000000..d106459f2385f04e7824a0427563a0bc91cd83db --- /dev/null +++ b/config/application_setting_columns/duo_sast_fp_detection_enabled.yml @@ -0,0 +1,11 @@ +api_type: +attr: duo_sast_fp_detection_enabled +clusterwide: false +column: duo_sast_fp_detection_enabled +db_type: boolean +default: 'true' +description: +encrypted: false +gitlab_com_different_than_default: false +jihu: false +not_null: true diff --git a/config/application_setting_columns/lock_duo_sast_fp_detection_enabled.yml b/config/application_setting_columns/lock_duo_sast_fp_detection_enabled.yml new file mode 100644 index 0000000000000000000000000000000000000000..27cddfdac274dbb5eb91b985afe5baa3541ca897 --- /dev/null +++ b/config/application_setting_columns/lock_duo_sast_fp_detection_enabled.yml @@ -0,0 +1,11 @@ +api_type: +attr: lock_duo_sast_fp_detection_enabled +clusterwide: false +column: lock_duo_sast_fp_detection_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/20251113120025_add_duo_sast_fp_detection_enabled_to_project_settings.rb b/db/migrate/20251113120025_add_duo_sast_fp_detection_enabled_to_project_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..026a305ce538ab8b699527cafb2a43dfcb54082a --- /dev/null +++ b/db/migrate/20251113120025_add_duo_sast_fp_detection_enabled_to_project_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddDuoSastFpDetectionEnabledToProjectSettings < Gitlab::Database::Migration[2.3] + milestone '18.7' + + def change + add_column :project_settings, :duo_sast_fp_detection_enabled, :boolean, default: true, null: false + end +end diff --git a/db/migrate/20251113120026_add_duo_sast_fp_detection_enabled_cascading_setting.rb b/db/migrate/20251113120026_add_duo_sast_fp_detection_enabled_cascading_setting.rb new file mode 100644 index 0000000000000000000000000000000000000000..545611dee4f627bf7f142088979a87fab9b79fc7 --- /dev/null +++ b/db/migrate/20251113120026_add_duo_sast_fp_detection_enabled_cascading_setting.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddDuoSastFpDetectionEnabledCascadingSetting < Gitlab::Database::Migration[2.3] + milestone '18.7' + disable_ddl_transaction! + + include Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings + + def up + add_cascading_namespace_setting :duo_sast_fp_detection_enabled, :boolean, default: true, null: false + end + + def down + remove_cascading_namespace_setting :duo_sast_fp_detection_enabled + end +end diff --git a/db/schema_migrations/20251113120025 b/db/schema_migrations/20251113120025 new file mode 100644 index 0000000000000000000000000000000000000000..8c24755455583dbdcdeea105b0352d3a8d58a74a --- /dev/null +++ b/db/schema_migrations/20251113120025 @@ -0,0 +1 @@ +cf9a366901fc6eb7da309b96ed04ffa2fffcd4fb7e179f899d042ab14009d8af \ No newline at end of file diff --git a/db/schema_migrations/20251113120026 b/db/schema_migrations/20251113120026 new file mode 100644 index 0000000000000000000000000000000000000000..aaef99cd82bb910f2dbee3b6cb7b7ec2a7d8e3ab --- /dev/null +++ b/db/schema_migrations/20251113120026 @@ -0,0 +1 @@ +6a350c11fdcf482427712def37ce71aba2f4564a3ac1688700cfaec3c8484546 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 56ed9c50550cc8ffbd7ed4e1f8af62a39295ace6..b087fe71547f70cc3bbc1b0dc9f970fc6e58698a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12132,6 +12132,8 @@ CREATE TABLE application_settings ( namespace_deletion_settings jsonb DEFAULT '{}'::jsonb NOT NULL, duo_foundational_flows_enabled boolean DEFAULT false NOT NULL, lock_duo_foundational_flows_enabled boolean DEFAULT false NOT NULL, + duo_sast_fp_detection_enabled boolean DEFAULT true NOT NULL, + lock_duo_sast_fp_detection_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)), @@ -21279,6 +21281,8 @@ CREATE TABLE namespace_settings ( disable_ssh_keys boolean DEFAULT false NOT NULL, duo_foundational_flows_enabled boolean, lock_duo_foundational_flows_enabled boolean DEFAULT false NOT NULL, + duo_sast_fp_detection_enabled boolean, + lock_duo_sast_fp_detection_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)), @@ -25011,6 +25015,7 @@ CREATE TABLE project_settings ( merge_request_title_regex_description text, duo_remote_flows_enabled boolean, duo_foundational_flows_enabled boolean, + duo_sast_fp_detection_enabled boolean DEFAULT true 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 bf152cc9500b731263c3e26dfb165fdefe127d8d..ffe7c60a0ba8573c29c3581cf8f53100930dc27d 100644 --- a/doc/api/openapi/openapi_v2.yaml +++ b/doc/api/openapi/openapi_v2.yaml @@ -31990,6 +31990,11 @@ paths: description: Enable GitLab Duo remote flows for this project type: boolean required: false + - in: formData + name: override_params[duo_sast_fp_detection_enabled] + description: Enable GitLab Duo SAST false positive detection 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 @@ -32687,6 +32692,11 @@ paths: description: Enable GitLab Duo remote flows for this project type: boolean required: false + - in: formData + name: override_params[duo_sast_fp_detection_enabled] + description: Enable GitLab Duo SAST false positive detection 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 @@ -33463,6 +33473,11 @@ paths: description: Enable GitLab Duo remote flows for this project type: boolean required: false + - in: formData + name: override_params[duo_sast_fp_detection_enabled] + description: Enable GitLab Duo SAST false positive detection 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 @@ -48058,6 +48073,9 @@ definitions: duo_foundational_flows_availability: type: boolean 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 amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request @@ -48264,6 +48282,9 @@ definitions: duo_foundational_flows_availability: type: boolean 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 amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request @@ -48947,6 +48968,8 @@ definitions: type: string duo_foundational_flows_enabled: type: string + duo_sast_fp_detection_enabled: + type: string web_based_commit_signing_enabled: type: string spp_repository_pipeline_access: @@ -64419,6 +64442,9 @@ definitions: duo_remote_flows_enabled: type: boolean description: Enable GitLab Duo remote flows for this project + duo_sast_fp_detection_enabled: + type: boolean + description: Enable GitLab Duo SAST false positive detection for this project spp_repository_pipeline_access: type: boolean description: Grant read-only access to security policy configurations for @@ -64862,6 +64888,9 @@ definitions: duo_remote_flows_enabled: type: boolean description: Enable GitLab Duo remote flows for this project + duo_sast_fp_detection_enabled: + type: boolean + description: Enable GitLab Duo SAST false positive detection for this project spp_repository_pipeline_access: type: boolean description: Grant read-only access to security policy configurations for @@ -65309,6 +65338,8 @@ definitions: type: string duo_foundational_flows_enabled: type: string + duo_sast_fp_detection_enabled: + type: string web_based_commit_signing_enabled: type: string spp_repository_pipeline_access: @@ -65788,6 +65819,9 @@ definitions: duo_remote_flows_enabled: type: boolean description: Enable GitLab Duo remote flows for this project + duo_sast_fp_detection_enabled: + type: boolean + description: Enable GitLab Duo SAST false positive detection 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 822deab1c4eea04414af966bbf42e99f0395712c..48b571c26abee5e1f3607c00b35596b6cab5457e 100644 --- a/ee/app/controllers/concerns/ee/groups/params.rb +++ b/ee/app/controllers/concerns/ee/groups/params.rb @@ -70,7 +70,8 @@ def group_params_ee if licensed_ai_features_available? 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]) + 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]) 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 17086285e59e45e8c07b66a9e1645cad7e1b9f39..b84adf8d540d33ab3e36696292b99164de8e601b 100644 --- a/ee/app/controllers/ee/projects_controller.rb +++ b/ee/app/controllers/ee/projects_controller.rb @@ -51,7 +51,15 @@ def active_new_project_tab override :project_setting_attributes def project_setting_attributes - attributes = %i[ + attributes = base_project_setting_attributes + attributes += licensed_feature_attributes + attributes += duo_feature_attributes + + super + attributes + end + + def base_project_setting_attributes + %i[ prevent_merge_without_jira_issue cve_id_request_enabled product_analytics_data_collector_host @@ -59,15 +67,15 @@ def project_setting_attributes cube_api_key product_analytics_configurator_connection_string ] + end + + def licensed_feature_attributes + attributes = [] if project&.licensed_feature_available?(:external_status_checks) attributes << :only_allow_merge_if_all_status_checks_passed end - if project&.licensed_ai_features_available? && ::Feature.enabled?(:use_duo_context_exclusion, project) - attributes << { duo_context_exclusion_settings: { exclusion_rules: [] } } - end - if project&.licensed_feature_available?(:security_orchestration_policies) attributes << :spp_repository_pipeline_access end @@ -77,12 +85,29 @@ def project_setting_attributes end add_duo_workflow_attributes(attributes) + attributes + end + + def duo_feature_attributes + attributes = [] + + if duo_context_exclusion_available? + attributes << { duo_context_exclusion_settings: { exclusion_rules: [] } } + end unless project&.project_setting&.duo_features_enabled_locked? attributes << :duo_features_enabled end - super + attributes + unless project&.project_setting&.duo_sast_fp_detection_enabled_locked? + attributes << :duo_sast_fp_detection_enabled + end + + attributes + end + + def duo_context_exclusion_available? + project&.licensed_ai_features_available? && ::Feature.enabled?(:use_duo_context_exclusion, project) end def add_duo_workflow_attributes(attributes) diff --git a/ee/app/helpers/ee/application_settings_helper.rb b/ee/app/helpers/ee/application_settings_helper.rb index 5c3d1dfe4b86cffc278bd69cb7f2c65258e3ab9c..f54ff51be3c5c20f644b18c80969c0bce8298a16 100644 --- a/ee/app/helpers/ee/application_settings_helper.rb +++ b/ee/app/helpers/ee/application_settings_helper.rb @@ -87,6 +87,8 @@ def visible_attributes :duo_remote_flows_availability, :duo_foundational_flows_enabled, :duo_foundational_flows_availability, + :duo_sast_fp_detection_enabled, + :duo_sast_fp_detection_availability, :enabled_expanded_logging, :foundational_agents_default_enabled, # Add all Zoekt settings automatically diff --git a/ee/app/helpers/ee/groups/settings_helper.rb b/ee/app/helpers/ee/groups/settings_helper.rb index cb4821d31a13ced776f1791f4d407bd58ef4ebe1..c7c3962553fd7eb02602bbbd639ca8841374cb52 100644 --- a/ee/app/helpers/ee/groups/settings_helper.rb +++ b/ee/app/helpers/ee/groups/settings_helper.rb @@ -56,14 +56,41 @@ def group_ai_configuration_settings_helper_data end def group_ai_settings_helper_data - duo_availability_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_features_enabled, @group, method(:edit_group_path))[:tooltip_data] - duo_remote_flows_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_remote_flows_enabled, @group, method(:edit_group_path))[:tooltip_data] - duo_foundational_flows_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_foundational_flows_enabled, @group, method(:edit_group_path))[:tooltip_data] + duo_cascading_settings_data.merge(duo_feature_settings_data) + end + + def group_amazon_q_settings_view_model_data + { + group_id: @group.id.to_s, + init_availability: @group.namespace_settings.duo_availability.to_s, + init_auto_review_enabled: @group.amazon_q_integration&.auto_review_enabled.present?, + are_duo_settings_locked: @group.namespace_settings.duo_features_enabled_locked?, + duo_availability_cascading_settings: cascading_namespace_settings_tooltip_raw_data(:duo_features_enabled, @group, method(:edit_group_path)) + } + end + def group_amazon_q_settings_view_model_json + ::Gitlab::Json.generate(group_amazon_q_settings_view_model_data.deep_transform_keys { |k| k.to_s.camelize(:lower) }) + end + + def seat_control_disabled_help_text + _("Restricted access and user cap cannot be turned on. The group or one of its subgroups or projects is shared externally.") + end + + private + + 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) + } + end + + def duo_feature_settings_data { - duo_availability_cascading_settings: duo_availability_cascading_settings, duo_availability: @group.namespace_settings.duo_availability.to_s, - duo_remote_flows_cascading_settings: duo_remote_flows_cascading_settings, are_duo_settings_locked: @group.namespace_settings.duo_features_enabled_locked?.to_s, experiment_features_enabled: @group.namespace_settings.experiment_features_enabled.to_s, duo_core_features_enabled: @group.namespace_settings.duo_core_features_enabled.to_s, @@ -76,34 +103,18 @@ def group_ai_settings_helper_data duo_workflow_available: (@group.root? && current_user.can?(:admin_duo_workflow, @group)).to_s, duo_workflow_mcp_enabled: @group.duo_workflow_mcp_enabled.to_s, duo_remote_flows_availability: @group.namespace_settings.duo_remote_flows_availability.to_s, + duo_sast_fp_detection_availability: @group.namespace_settings.duo_sast_fp_detection_availability.to_s, foundational_agents_default_enabled: @group.foundational_agents_default_enabled.to_s, - duo_foundational_flows_cascading_settings: duo_foundational_flows_cascading_settings, duo_foundational_flows_availability: @group.namespace_settings.duo_foundational_flows_availability.to_s, is_saas: saas?.to_s, show_foundational_agents_availability: show_foundational_agents_availability?.to_s } end - def group_amazon_q_settings_view_model_data - { - group_id: @group.id.to_s, - init_availability: @group.namespace_settings.duo_availability.to_s, - init_auto_review_enabled: @group.amazon_q_integration&.auto_review_enabled.present?, - are_duo_settings_locked: @group.namespace_settings.duo_features_enabled_locked?, - duo_availability_cascading_settings: cascading_namespace_settings_tooltip_raw_data(:duo_features_enabled, @group, method(:edit_group_path)) - } + def cascading_tooltip_data(setting_key) + cascading_namespace_settings_tooltip_data(setting_key, @group, method(:edit_group_path))[:tooltip_data] end - def group_amazon_q_settings_view_model_json - ::Gitlab::Json.generate(group_amazon_q_settings_view_model_data.deep_transform_keys { |k| k.to_s.camelize(:lower) }) - end - - def seat_control_disabled_help_text - _("Restricted access and user cap cannot be turned on. The group or one of its subgroups or projects is shared externally.") - end - - private - def show_foundational_agents_availability? ::Feature.enabled?(:duo_foundational_agents_availability, @group) && saas? && @group.root? end diff --git a/ee/app/helpers/ee/projects_helper.rb b/ee/app/helpers/ee/projects_helper.rb index 456fe83055a097d932ddf2bde9e97d4bae760fe0..222c351859f245aa9e8ab8d3e649bdd7c3a18fcd 100644 --- a/ee/app/helpers/ee/projects_helper.rb +++ b/ee/app/helpers/ee/projects_helper.rb @@ -58,6 +58,7 @@ def gitlab_duo_settings_data(project) duoContextExclusionSettings: project.project_setting.duo_context_exclusion_settings || {}, initialDuoRemoteFlowsAvailability: project.duo_remote_flows_enabled, initialDuoFoundationalFlowsAvailability: project.duo_foundational_flows_enabled, + initialDuoSastFpDetectionEnabled: project.duo_sast_fp_detection_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 dc361dfd1d0d971c1cb130d6d18347a8cdd556a9..fd4e1fe5face0fc220a018067e142637cffc97e6 100644 --- a/ee/app/models/ee/application_setting.rb +++ b/ee/app/models/ee/application_setting.rb @@ -371,7 +371,8 @@ module ApplicationSetting allow_nil: false, inclusion: { in: [true, false], message: N_('must be a boolean value') } - validates :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_foundational_flows_enabled, + validates :auto_duo_code_review_enabled, :duo_remote_flows_enabled, + :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled, inclusion: { in: [true, false] } after_commit :update_personal_access_tokens_lifetime, if: :saved_change_to_max_personal_access_token_lifetime? @@ -745,6 +746,20 @@ def duo_foundational_flows_availability=(value) end end + def duo_sast_fp_detection_availability + duo_sast_fp_detection_enabled + end + + def duo_sast_fp_detection_availability=(value) + self.duo_sast_fp_detection_enabled = value + + self.lock_duo_sast_fp_detection_enabled = if value + false + else + true + end + 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 8e982ecf10176796b9cccab997c6894aa8be2321..c2ebbe7de7e64cdcd44fba50de11f7c1c2d2d033 100644 --- a/ee/app/models/ee/group.rb +++ b/ee/app/models/ee/group.rb @@ -115,6 +115,7 @@ module Group delegate :duo_availability, :duo_availability=, to: :namespace_settings 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 :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 0112035c48ff9eab0149b16b6615f532ee6d6a7a..e8e52273176b464b551f2ea63c414e812f3cc6d2 100644 --- a/ee/app/models/ee/namespace.rb +++ b/ee/app/models/ee/namespace.rb @@ -196,6 +196,7 @@ module Namespace :auto_duo_code_review_enabled, :auto_duo_code_review_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, to: :namespace_settings, allow_nil: true delegate :duo_agent_platform_request_count, :duo_default_on?, :duo_default_off?, :experiment_features_enabled?, to: :namespace_settings diff --git a/ee/app/models/ee/namespace_setting.rb b/ee/app/models/ee/namespace_setting.rb index 9ef3484d47037a4d332f99952d1ad56227b2c817..555210033310a33bf786f0175d85b2375c6291bd 100644 --- a/ee/app/models/ee/namespace_setting.rb +++ b/ee/app/models/ee/namespace_setting.rb @@ -9,7 +9,7 @@ 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_remote_flows_enabled, :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled scope :requiring_dormant_member_review, ->(limit) do # look for settings that have not been reviewed in more than @@ -184,6 +184,19 @@ def duo_foundational_flows_availability=(value) end end + def duo_sast_fp_detection_availability + duo_sast_fp_detection_enabled + end + + def duo_sast_fp_detection_availability=(value) + self.duo_sast_fp_detection_enabled = value + self.lock_duo_sast_fp_detection_enabled = if value + false + else + true + end + end + def duo_availability if duo_features_enabled && !duo_features_enabled_locked?(include_self: true) :default_on @@ -295,6 +308,8 @@ def experiment_features_allowed auto_duo_code_review_enabled duo_remote_flows_enabled duo_remote_flows_availability + duo_sast_fp_detection_enabled + lock_duo_sast_fp_detection_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 8b0ec1cf61966572ca9a6d57b09f595d3e4eeff2..214e773e90b2d7ccd267c05571b88087345f884b 100644 --- a/ee/app/models/ee/project.rb +++ b/ee/app/models/ee/project.rb @@ -550,6 +550,8 @@ def lock_for_confirmation!(id) :duo_remote_flows_enabled=, :duo_foundational_flows_enabled, :duo_foundational_flows_enabled=, + :duo_sast_fp_detection_enabled, + :duo_sast_fp_detection_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 85cc4ce3c88200f66df23147e25bc8b4c3cedcf5..b8e3c67465e812d84dca36a2c54dc7c0aafcebc2 100644 --- a/ee/app/models/ee/project_setting.rb +++ b/ee/app/models/ee/project_setting.rb @@ -7,7 +7,8 @@ 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 + :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_foundational_flows_enabled, + :duo_sast_fp_detection_enabled belongs_to :push_rule diff --git a/ee/app/models/ee/vulnerability.rb b/ee/app/models/ee/vulnerability.rb index d038afd3ddfcc783ecdf49ad3a58389437e34dd9..c9efff8d162ea762c40b4621437e6297f934433a 100644 --- a/ee/app/models/ee/vulnerability.rb +++ b/ee/app/models/ee/vulnerability.rb @@ -381,6 +381,7 @@ def self.es_type def trigger_false_positive_detection return unless ::Feature.enabled?(:enable_vulnerability_fp_detection, group) return unless sast? + return unless project.duo_sast_fp_detection_enabled run_after_commit_or_now do ::Vulnerabilities::TriggerFalsePositiveDetectionWorkflowWorker.perform_async(id) diff --git a/ee/app/presenters/admin/ai_configuration_presenter.rb b/ee/app/presenters/admin/ai_configuration_presenter.rb index b417c67086fe15efae62f9dacb89ee64c92fc832..f03e02c3b7137f38d15b023f6eacf093a9365204 100644 --- a/ee/app/presenters/admin/ai_configuration_presenter.rb +++ b/ee/app/presenters/admin/ai_configuration_presenter.rb @@ -10,6 +10,7 @@ class AiConfigurationPresenter :duo_foundational_flows_availability, :duo_chat_expiration_column, :duo_chat_expiration_days, + :duo_sast_fp_detection_availability, :enabled_expanded_logging, :gitlab_dedicated_instance?, :instance_level_ai_beta_features_enabled, @@ -39,6 +40,7 @@ def settings duo_chat_expiration_days: duo_chat_expiration_days, 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, enabled_expanded_logging: enabled_expanded_logging, experiment_features_enabled: instance_level_ai_beta_features_enabled, on_general_settings_page: false, diff --git a/ee/app/services/ai/cascade_duo_settings_service.rb b/ee/app/services/ai/cascade_duo_settings_service.rb index 1c90909fd5d0cdb605966cfcc66a9b7ed9048d22..940f380af470aa3df218b94f5d65e1f3b6273719 100644 --- a/ee/app/services/ai/cascade_duo_settings_service.rb +++ b/ee/app/services/ai/cascade_duo_settings_service.rb @@ -3,7 +3,7 @@ module Ai class CascadeDuoSettingsService DUO_SETTINGS = %w[duo_features_enabled duo_remote_flows_enabled auto_duo_code_review_enabled - duo_foundational_flows_enabled].freeze + duo_foundational_flows_enabled duo_sast_fp_detection_enabled].freeze def initialize(setting_attributes) @setting_attributes = setting_attributes.stringify_keys diff --git a/ee/app/services/ee/application_settings/update_service.rb b/ee/app/services/ee/application_settings/update_service.rb index a736f7a7dfe5110511799452edf62d6b69d242da..42ea9a844d67a9f3302f0a3581a0819c2f43e113 100644 --- a/ee/app/services/ee/application_settings/update_service.rb +++ b/ee/app/services/ee/application_settings/update_service.rb @@ -97,7 +97,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_foundational_flows_enabled, :duo_sast_fp_detection_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 ec7af544e91097bc5b3245450aaca10f89fa9699..f9b009e63686fb5592eb514b4869bca2c5c56fd8 100644 --- a/ee/app/services/ee/groups/update_service.rb +++ b/ee/app/services/ee/groups/update_service.rb @@ -167,7 +167,7 @@ def update_cascading_settings previous_changes = group.namespace_settings.previous_changes cascading_ai_settings = [:duo_features_enabled, :duo_remote_flows_enabled, :auto_duo_code_review_enabled, - :duo_foundational_flows_enabled] + :duo_foundational_flows_enabled, :duo_sast_fp_detection_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 ebbca91b36a8566661385c55d4af324a11a14d74..374282654297cd38d411be6e42fd172d7e8a27c3 100644 --- a/ee/app/services/ee/namespace_settings/assign_attributes_service.rb +++ b/ee/app/services/ee/namespace_settings/assign_attributes_service.rb @@ -47,6 +47,14 @@ def execute param_key: :lock_duo_foundational_flows_enabled, user_policy: :admin_group ) + validate_settings_param_for_admin( + param_key: :duo_sast_fp_detection_enabled, + user_policy: :admin_group + ) + validate_settings_param_for_admin( + param_key: :lock_duo_sast_fp_detection_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 8d0cbebb8c724862c252c6358b82a40e7d899fb6..2f5c0c78a8ca33ade2efb633875cf3e2a709d23c 100644 --- a/ee/lib/ee/api/entities/project.rb +++ b/ee/lib/ee/api/entities/project.rb @@ -67,6 +67,7 @@ def preload_relation(projects_relation, options = {}) expose :auto_duo_code_review_enabled, if: ->(project, _) { project.namespace.has_active_add_on_purchase?(:duo_enterprise) } 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 :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 8dc7b705f8b903361a0726d55b66ebd38b5a28cb..9dbbead74cb680d94767a8bf2d3f31f3c9c15984 100644 --- a/ee/lib/ee/api/groups.rb +++ b/ee/lib/ee/api/groups.rb @@ -63,6 +63,11 @@ def update_group(group) params.delete(:service_access_tokens_expiration_enforced) unless group.root? && can?(current_user, :admin_service_accounts, group) + unless group.licensed_feature_available?(:ai_features) && ::Feature.enabled?( + :ai_experiment_sast_fp_detection, group) + params.delete(:duo_sast_fp_detection_availability) + end + unless group.unique_project_download_limit_enabled? %i[ unique_project_download_limit diff --git a/ee/lib/ee/api/helpers/groups_helpers.rb b/ee/lib/ee/api/helpers/groups_helpers.rb index 67dbb4d49633266a03d3a2b6559a1b8da5a1f182..8023f9e34c75f828327c62738a67b322632ae96c 100644 --- a/ee/lib/ee/api/helpers/groups_helpers.rb +++ b/ee/lib/ee/api/helpers/groups_helpers.rb @@ -17,6 +17,7 @@ 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 :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 338355b225e38a6a00f375893bab3149e9397d0a..d1dcc00b5f4d1691478e481f873ad6832516eebe 100644 --- a/ee/lib/ee/api/helpers/projects_helpers.rb +++ b/ee/lib/ee/api/helpers/projects_helpers.rb @@ -25,6 +25,7 @@ module ProjectsHelpers optional :prevent_merge_without_jira_issue, type: Grape::API::Boolean, desc: 'Require an associated issue from Jira' 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 :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 @@ -53,6 +54,7 @@ module ProjectsHelpers type: ::Grape::API::Boolean, 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 :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 @@ -73,6 +75,7 @@ def update_params_at_least_one_of super.concat [ :auto_duo_code_review_enabled, :duo_remote_flows_enabled, + :duo_sast_fp_detection_enabled, :allow_pipeline_trigger_approve_deployment, :only_allow_merge_if_all_status_checks_passed, :approvals_before_merge, @@ -117,6 +120,10 @@ def filter_attributes_using_license!(attrs) attrs.delete(:auto_duo_code_review_enabled) unless ::License.feature_available?(:review_merge_request) attrs.delete(:duo_remote_flows_enabled) unless License.feature_available?(:ai_workflows) + unless License.feature_available?(:ai_features) && ::Feature.enabled?(:ai_experiment_sast_fp_detection, current_user) + attrs.delete(:duo_sast_fp_detection_enabled) + end + return if ::License.feature_available?(:security_orchestration_policies) attrs.delete(:spp_repository_pipeline_access) diff --git a/ee/spec/controllers/ee/projects_controller_spec.rb b/ee/spec/controllers/ee/projects_controller_spec.rb index 879f3a77e3b75c59a52374e35ef4ce45831fee49..5ea12e09fdbd69cbd2e0fa62422b29df4c0c3e71 100644 --- a/ee/spec/controllers/ee/projects_controller_spec.rb +++ b/ee/spec/controllers/ee/projects_controller_spec.rb @@ -914,7 +914,50 @@ it 'updates duo_foundational_flows_enabled' do expect { request } .to change { project.reload.project_setting.duo_foundational_flows_enabled } - .from(false).to(true) + .from(false).to(true) + end + end + end + end + + context 'when duo_sast_fp_detection_enabled param is specified' do + let(:params) { { project_setting_attributes: { duo_sast_fp_detection_enabled: true } } } + + let(:request) do + put :update, params: { namespace_id: project.namespace, id: project, project: params } + end + + it 'updates duo_sast_fp_detection_enabled' do + project.project_setting.duo_sast_fp_detection_enabled = false + project.project_setting.save! + + request + + expect(project.reload.project_setting.duo_sast_fp_detection_enabled).to eq(true) + end + + context 'when duo sast fp detection is locked by the ancestor' do + before do + project.project_setting.duo_sast_fp_detection_enabled = false + project.project_setting.save! + + project.namespace.namespace_settings.lock_duo_sast_fp_detection_enabled = true + project.namespace.namespace_settings.duo_sast_fp_detection_enabled = false + project.namespace.namespace_settings.save! + end + + it 'does not update duo sast fp detection' do + expect { request }.not_to change { project.reload.project_setting.duo_sast_fp_detection_enabled }.from(false) + end + + context 'with more params passed' do + let(:params) do + { project_setting_attributes: { duo_sast_fp_detection_enabled: true }, description: 'Settings test' } + end + + it 'does not update duo sast fp detection, but updates other attributes' do + expect { request }.not_to change { project.reload.project_setting.duo_sast_fp_detection_enabled }.from(false) + expect(project.description).to eq('Settings test') end end end diff --git a/ee/spec/helpers/ee/groups/settings_helper_spec.rb b/ee/spec/helpers/ee/groups/settings_helper_spec.rb index c863cb5d0f6284cc07b48c84b232f1381f7ff300..d2033a5ac47141b001451c49022d44914070829d 100644 --- a/ee/spec/helpers/ee/groups/settings_helper_spec.rb +++ b/ee/spec/helpers/ee/groups/settings_helper_spec.rb @@ -93,6 +93,9 @@ duo_foundational_flows_cascading_settings: "{\"locked_by_application_setting\":false,\"locked_by_ancestor\":false}", duo_foundational_flows_availability: group.namespace_settings.duo_foundational_flows_availability.to_s, + 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_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 c2de79ece5a94471fdfbf2d043814df0b35b67d2..a45193451d62bd755ebd968918c4abb9bfbdad45 100644 --- a/ee/spec/helpers/projects_helper_spec.rb +++ b/ee/spec/helpers/projects_helper_spec.rb @@ -702,6 +702,7 @@ duoFeaturesLocked: false, initialDuoRemoteFlowsAvailability: true, initialDuoFoundationalFlowsAvailability: false, + initialDuoSastFpDetectionEnabled: true, 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 6fce576fad0085c2bd9297fe377c6b95ba6cc375..644d4f0c97d99a3984b0cdcbb543050dfd07f0f9 100644 --- a/ee/spec/lib/ee/api/entities/project_spec.rb +++ b/ee/spec/lib/ee/api/entities/project_spec.rb @@ -204,6 +204,43 @@ def mock_available end end + describe 'duo_sast_fp_detection_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) + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'returns a boolean value' do + expect(subject[:duo_sast_fp_detection_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(ai_experiment_sast_fp_detection: false) + end + + it 'returns nil' do + expect(subject[:duo_sast_fp_detection_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) + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'returns nil' do + expect(subject[:duo_sast_fp_detection_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 1202dd649f52a906c77c0c448e21677197f22577..60c8b412588a80e1b3f4465eb564436df9999b6b 100644 --- a/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb +++ b/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb @@ -130,7 +130,8 @@ 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 duo_agent_platform_request_count - lock_duo_foundational_flows_enabled duo_foundational_flows_enabled] + lock_duo_foundational_flows_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 fd2035c5b6d00cc9d9354390ee43b44878a56eef..166f3259384c809ca279108617fabd22a3afa87e 100644 --- a/ee/spec/models/application_setting_spec.rb +++ b/ee/spec/models/application_setting_spec.rb @@ -718,6 +718,27 @@ end end + describe '#duo_sast_fp_detection_availability=' do + using RSpec::Parameterized::TableSyntax + + where(:duo_sast_fp_detection_availability, :duo_sast_fp_detection_enabled_expectation, + :lock_duo_sast_fp_detection_enabled_expectation) do + true | true | false + false | false | true + end + + with_them do + before do + setting.duo_sast_fp_detection_availability = duo_sast_fp_detection_availability + end + + it 'returns the expected response' do + expect(setting.duo_sast_fp_detection_enabled).to be duo_sast_fp_detection_enabled_expectation + expect(setting.lock_duo_sast_fp_detection_enabled).to be lock_duo_sast_fp_detection_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 f7d858e5fc9a39323137d53a772562216a939a8c..ac50834a0a60b8c66f21c93f8545649962dd6c23 100644 --- a/ee/spec/models/ee/namespace_spec.rb +++ b/ee/spec/models/ee/namespace_spec.rb @@ -50,6 +50,12 @@ it { is_expected.to delegate_method(:duo_features_enabled).to(:namespace_settings) } it { is_expected.to delegate_method(:lock_duo_features_enabled).to(:namespace_settings) } it { is_expected.to delegate_method(:duo_availability).to(:namespace_settings) } + it { is_expected.to delegate_method(:auto_duo_code_review_enabled).to(:namespace_settings).allow_nil } + it { is_expected.to delegate_method(:auto_duo_code_review_enabled=).to(:namespace_settings).with_arguments(:args).allow_nil } + it { is_expected.to delegate_method(:duo_remote_flows_enabled).to(:namespace_settings).allow_nil } + 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_default_on?).to(:namespace_settings) } it { is_expected.to delegate_method(:duo_default_off?).to(:namespace_settings) } it { is_expected.to delegate_method(:experiment_features_enabled?).to(:namespace_settings) } diff --git a/ee/spec/models/ee/project_setting_spec.rb b/ee/spec/models/ee/project_setting_spec.rb index 62db86f32e97d3da77a10eea8c621a3aac1256e1..6fe35fe1fc2e045c9a25fdad3865c17e1304193c 100644 --- a/ee/spec/models/ee/project_setting_spec.rb +++ b/ee/spec/models/ee/project_setting_spec.rb @@ -174,4 +174,9 @@ it_behaves_like 'a cascading project setting boolean attribute', settings_attribute_name: :duo_foundational_flows_enabled end + + describe '#duo_sast_fp_detection_enabled' do + it_behaves_like 'a cascading project setting boolean attribute', + settings_attribute_name: :duo_sast_fp_detection_enabled + end end diff --git a/ee/spec/models/ee/vulnerability_spec.rb b/ee/spec/models/ee/vulnerability_spec.rb index aba17cd48ecc060402c482d6ed24977b03729ea3..5667df5b1c98554185366a013f14a68665e0e06c 100644 --- a/ee/spec/models/ee/vulnerability_spec.rb +++ b/ee/spec/models/ee/vulnerability_spec.rb @@ -1539,15 +1539,34 @@ context 'when feature flag is enabled' do context 'when vulnerability is of type sast' do - it 'triggers false positive detection workflow' do - expect_next_instance_of(::Vulnerability) do |instance| - expect(instance).to receive(:run_after_commit_or_now).and_yield + context 'when duo_sast_fp_detection_enabled is true' do + before do + project.update!(duo_sast_fp_detection_enabled: true) end - expect(::Vulnerabilities::TriggerFalsePositiveDetectionWorkflowWorker) - .to receive(:perform_async) - .with(anything) - create(:vulnerability, :sast, author: user, project: project) + it 'triggers false positive detection workflow' do + expect_next_instance_of(::Vulnerability) do |instance| + expect(instance).to receive(:run_after_commit_or_now).and_yield + end + expect(::Vulnerabilities::TriggerFalsePositiveDetectionWorkflowWorker) + .to receive(:perform_async) + .with(anything) + + create(:vulnerability, :sast, author: user, project: project) + end + end + + context 'when duo_sast_fp_detection_enabled is false' do + before do + project.update!(duo_sast_fp_detection_enabled: false) + end + + it 'does not trigger false positive detection workflow' do + expect(::Vulnerabilities::TriggerFalsePositiveDetectionWorkflowWorker) + .not_to receive(:perform_async) + + create(:vulnerability, :sast, author: user, project: project) + end end end diff --git a/ee/spec/models/namespace_setting_spec.rb b/ee/spec/models/namespace_setting_spec.rb index 15c0a7274cc145ec43549247fe6d41739ad92544..412634795d80888c125cd1a3bc6fa5eca6edb584 100644 --- a/ee/spec/models/namespace_setting_spec.rb +++ b/ee/spec/models/namespace_setting_spec.rb @@ -654,6 +654,26 @@ end end + describe '#duo_sast_fp_detection_availability=' do + using RSpec::Parameterized::TableSyntax + + where(:duo_sast_fp_detection_availability, :duo_sast_fp_detection_enabled_expectation, :lock_duo_sast_fp_detection_enabled_expectation) do + true | true | false + false | false | true + end + + with_them do + before do + setting.duo_sast_fp_detection_availability = duo_sast_fp_detection_availability + end + + it 'returns the expected response' do + expect(setting.duo_sast_fp_detection_enabled).to be duo_sast_fp_detection_enabled_expectation + expect(setting.lock_duo_sast_fp_detection_enabled).to be lock_duo_sast_fp_detection_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 1da955bbb05751fa48a3e30e1394088b4eea9cec..85418502eb8324baaf47a2a9c160a891af3bdefe 100644 --- a/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb +++ b/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb @@ -12,6 +12,7 @@ duo_availability: 'default_off', duo_remote_flows_availability: true, duo_foundational_flows_availability: false, + duo_sast_fp_detection_availability: true, duo_chat_expiration_column: 'last_updated_at', duo_chat_expiration_days: '30', enabled_expanded_logging: true, @@ -73,6 +74,7 @@ duo_availability: 'default_off', duo_remote_flows_availability: 'true', duo_foundational_flows_availability: 'false', + duo_sast_fp_detection_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 76f6bc32b0946abb8992712244cf40c3ef21041f..4f2941f00614eda5d4086672af620dcba768e2d7 100644 --- a/ee/spec/requests/api/groups_spec.rb +++ b/ee/spec/requests/api/groups_spec.rb @@ -846,6 +846,54 @@ end end + context 'duo_sast_fp_detection_availability' do + context 'when licence is available and feature flag is enabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'updates duo_sast_fp_detection_enabled field of namespace settings' do + expect do + put api("/groups/#{group.id}", user), params: { duo_sast_fp_detection_availability: false } + end.to change { group.reload.namespace_settings.duo_sast_fp_detection_enabled }.from(true).to(false) + .and change { group.reload.namespace_settings.lock_duo_sast_fp_detection_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(ai_experiment_sast_fp_detection: false) + end + + it 'does not update duo_sast_fp_detection_enabled field of namespace settings' do + expect do + put api("/groups/#{group.id}", user), params: { duo_sast_fp_detection_availability: false } + end.not_to change { group.reload.namespace_settings.duo_sast_fp_detection_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(ai_experiment_sast_fp_detection: true) + end + + it 'does not update duo_sast_fp_detection_enabled field of namespace settings' do + expect do + put api("/groups/#{group.id}", user), params: { duo_sast_fp_detection_availability: false } + end.not_to change { group.reload.namespace_settings.duo_sast_fp_detection_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 f7ccee13f0dd2d57a75003062c00405167c2a712..980739efdff0b499aa12be4a42939d71953f25d4 100644 --- a/ee/spec/requests/api/projects_spec.rb +++ b/ee/spec/requests/api/projects_spec.rb @@ -2186,6 +2186,51 @@ def decode_cursor(cursor) end end + context 'when setting duo_sast_fp_detection_enabled' do + let(:project_params) { { duo_sast_fp_detection_enabled: false } } + + context 'when licence is available and feature flag is enabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'updates the value' do + expect { subject }.to change { project.reload.duo_sast_fp_detection_enabled } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['duo_sast_fp_detection_enabled']).to eq false + end + end + + context 'when licence is available but feature flag is disabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(ai_experiment_sast_fp_detection: 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_fp_detection_enabled } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['duo_sast_fp_detection_enabled']).to be_nil + end + end + + context 'when licence is not available' do + before do + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'does not update the value and does not expose it in response' do + expect { subject }.not_to change { project.reload.duo_sast_fp_detection_enabled } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['duo_sast_fp_detection_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 774a11a9d84d867d6537096c8d76a69ad399d939..17758e0660cfba2af98e82ec03dbec67f1133ad0 100644 --- a/ee/spec/services/application_settings/update_service_spec.rb +++ b/ee/spec/services/application_settings/update_service_spec.rb @@ -375,6 +375,14 @@ it_behaves_like 'when updating duo settings', :duo_foundational_flows_enabled, true end + context 'when updating duo_sast_fp_detection_enabled' do + before do + setting.update!(duo_sast_fp_detection_enabled: true) + end + + it_behaves_like 'when updating duo settings', :duo_sast_fp_detection_enabled, false + end + context 'when updating auto_duo_code_review_enabled' do let(:params) { { auto_duo_code_review_enabled: true } } let(:service) { described_class.new(setting, user, params) } diff --git a/ee/spec/services/groups/update_service_spec.rb b/ee/spec/services/groups/update_service_spec.rb index 679eebe7a972ef5f0d975d72e834d7fb888d3717..f68991ef556b5223435ec1890a8fe9f8e5f42cd0 100644 --- a/ee/spec/services/groups/update_service_spec.rb +++ b/ee/spec/services/groups/update_service_spec.rb @@ -637,6 +637,10 @@ def update_file_template_project_id(id) it_behaves_like 'when updating duo settings', :duo_foundational_flows_enabled, false end + context 'when updating duo_sast_fp_detection_enabled' do + it_behaves_like 'when updating duo settings', :duo_sast_fp_detection_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 0422ce34b1cfca473d1570ac251a38005667dbaf..1f7c04118489d13c8a2841a37d8fabf63c1cf473 100644 --- a/spec/requests/api/project_attributes.yml +++ b/spec/requests/api/project_attributes.yml @@ -120,7 +120,6 @@ ci_cd_settings: computed_attributes: - restrict_user_defined_variables - build_import_state: # import_state unexposed_attributes: - id @@ -205,7 +204,7 @@ project_setting: - duo_context_exclusion_settings - duo_remote_flows_enabled - duo_foundational_flows_enabled - + - duo_sast_fp_detection_enabled build_service_desk_setting: # service_desk_setting unexposed_attributes: - project_id