From 6690647824a4f45b15df6b4b5dc4853b0c562919 Mon Sep 17 00:00:00 2001 From: galactica Date: Wed, 5 Feb 2025 16:59:33 +0200 Subject: [PATCH 01/16] Add state id flag --- internal/commands/predicates.go | 63 ++++++++++++++---- internal/commands/predicates_test.go | 70 +++++++++++++++++++- internal/params/flags.go | 1 + internal/wrappers/mock/custom-states-mock.go | 9 ++- internal/wrappers/predicates-http.go | 3 +- internal/wrappers/predicates.go | 14 ++-- 6 files changed, 134 insertions(+), 26 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index 6781ae2b3..c34e625b4 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -12,17 +12,16 @@ import ( "github.com/spf13/cobra" ) -func NewResultsPredicatesCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper ) *cobra.Command { +func NewResultsPredicatesCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper) *cobra.Command { triageCmd := &cobra.Command{ Use: "triage", Short: "Manage results", Long: "The 'triage' command enables the ability to manage results in Checkmarx One.", } triageShowCmd := triageShowSubCommand(resultsPredicatesWrapper) - triageUpdateCmd := triageUpdateSubCommand(resultsPredicatesWrapper, featureFlagsWrapper) + triageUpdateCmd := triageUpdateSubCommand(resultsPredicatesWrapper, featureFlagsWrapper, customStatesWrapper) triageGetStatesCmd := triageGetStatesSubCommand(customStatesWrapper) - addFormatFlagToMultipleCommands( []*cobra.Command{triageShowCmd}, printer.FormatList, printer.FormatTable, printer.FormatJSON, @@ -63,8 +62,6 @@ func runTriageGetStates(customStatesWrapper wrappers.CustomStatesWrapper) func(* } } - - func triageShowSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper) *cobra.Command { triageShowCmd := &cobra.Command{ Use: "show", @@ -90,7 +87,7 @@ func triageShowSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWra return triageShowCmd } -func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) *cobra.Command { +func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper) *cobra.Command { triageUpdateCmd := &cobra.Command{ Use: "update", Short: "Update the state, severity or comment for the given issue", @@ -100,19 +97,21 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW $ cx triage update --similarity-id --project-id - --state + --state > + --custom-state-id --severity --comment --scan-type `, ), - RunE: runTriageUpdate(resultsPredicatesWrapper, featureFlagsWrapper), + RunE: runTriageUpdate(resultsPredicatesWrapper, featureFlagsWrapper, customStatesWrapper), } triageUpdateCmd.PersistentFlags().String(params.SimilarityIDFlag, "", "Similarity ID") triageUpdateCmd.PersistentFlags().String(params.SeverityFlag, "", "Severity") triageUpdateCmd.PersistentFlags().String(params.ProjectIDFlag, "", "Project ID.") triageUpdateCmd.PersistentFlags().String(params.StateFlag, "", "State") + triageUpdateCmd.PersistentFlags().String(params.CustomStateIDFlag, "", "State ID") triageUpdateCmd.PersistentFlags().String(params.CommentFlag, "", "Optional comment.") triageUpdateCmd.PersistentFlags().String(params.ScanTypeFlag, "", "Scan Type") @@ -169,12 +168,13 @@ func runTriageShow(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper) f } } -func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) func(*cobra.Command, []string) error { +func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, _ []string) error { similarityID, _ := cmd.Flags().GetString(params.SimilarityIDFlag) projectID, _ := cmd.Flags().GetString(params.ProjectIDFlag) severity, _ := cmd.Flags().GetString(params.SeverityFlag) state, _ := cmd.Flags().GetString(params.StateFlag) + customStateID, _ := cmd.Flags().GetString(params.CustomStateIDFlag) comment, _ := cmd.Flags().GetString(params.CommentFlag) scanType, _ := cmd.Flags().GetString(params.ScanTypeFlag) // check if the current tenant has critical severity available @@ -183,12 +183,25 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, if !criticalEnabled && strings.EqualFold(severity, "critical") { return errors.Errorf("%s", "Critical severity is not available for your tenant.This severity status will be enabled shortly") } + if isCustomState(state) { + if customStateID == "" { + var err error + customStateID, err = getCustomStateID(customStatesWrapper, state) + if err != nil { + return errors.Wrapf(err, "Failed to get custom state ID for state: %s", state) + } + } + } else { + customStateID = "" + } + predicate := &wrappers.PredicateRequest{ - SimilarityID: similarityID, - ProjectID: projectID, - Severity: severity, - State: state, - Comment: comment, + SimilarityID: similarityID, + ProjectID: projectID, + Severity: severity, + State: state, + CustomStateID: customStateID, + Comment: comment, } _, err := resultsPredicatesWrapper.PredicateSeverityAndState(predicate, scanType) @@ -199,6 +212,28 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, return nil } } +func isCustomState(state string) bool { + systemStates := []string{"TO_VERIFY", "NOT_EXPLOITABLE", "PROPOSED_NOT_EXPLOITABLE", "CONFIRMED", "URGENT"} + for _, customState := range systemStates { + if strings.EqualFold(state, customState) { + return false + } + } + return true +} + +func getCustomStateID(customStatesWrapper wrappers.CustomStatesWrapper, state string) (string, error) { + customStates, err := customStatesWrapper.GetAllCustomStates(false) + if err != nil { + return "", errors.Wrap(err, "Failed to fetch custom states") + } + for _, customState := range customStates { + if customState.Name == state { + return strconv.Itoa(customState.ID), nil + } + } + return "", errors.Errorf("No matching state found for %s", state) +} type predicateView struct { ID string `format:"name:ID"` diff --git a/internal/commands/predicates_test.go b/internal/commands/predicates_test.go index e21952d3d..b4ec895e1 100644 --- a/internal/commands/predicates_test.go +++ b/internal/commands/predicates_test.go @@ -4,6 +4,7 @@ package commands import ( "fmt" + "github.com/checkmarx/ast-cli/internal/wrappers" "github.com/checkmarx/ast-cli/internal/wrappers/mock" "testing" @@ -65,4 +66,71 @@ func TestTriageGetStatesFlag(t *testing.T) { states, err = mockWrapper.GetAllCustomStates(true) assert.NilError(t, err) assert.Equal(t, len(states), 3) -} \ No newline at end of file +} + +func TestGetCustomStateID(t *testing.T) { + tests := []struct { + name string + state string + mockWrapper wrappers.CustomStatesWrapper + expectedStateID string + expectedErrorString string + }{ + { + name: "State found", + state: "demo3", + mockWrapper: &mock.CustomStatesMockWrapper{}, + expectedStateID: "3", + }, + { + name: "State not found", + state: "nonexistent", + mockWrapper: &mock.CustomStatesMockWrapper{}, + expectedStateID: "", + expectedErrorString: "No matching state found for nonexistent", + }, + { + name: "Error fetching states", + state: "nonexistent", + mockWrapper: &mock.CustomStatesMockWrapperWithError{}, + expectedStateID: "", + expectedErrorString: "Failed to fetch custom states", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + stateID, err := getCustomStateID(tt.mockWrapper, tt.state) + if tt.expectedErrorString != "" { + assert.ErrorContains(t, err, tt.expectedErrorString) + } else { + assert.NilError(t, err) + } + assert.Equal(t, stateID, tt.expectedStateID) + }) + } +} + +func TestIsCustomState(t *testing.T) { + tests := []struct { + state string + isCustom bool + }{ + {"TO_VERIFY", false}, + {"to_verify", false}, + {"NOT_EXPLOITABLE", false}, + {"PROPOSED_NOT_EXPLOITABLE", false}, + {"CONFIRMED", false}, + {"URGENT", false}, + {"CUSTOM_STATE_1", true}, + {"CUSTOM_STATE_2", true}, + } + + for _, tt := range tests { + t.Run(tt.state, func(t *testing.T) { + result := isCustomState(tt.state) + assert.Equal(t, result, tt.isCustom) + }) + } +} diff --git a/internal/params/flags.go b/internal/params/flags.go index 02cacddb1..7a0a7a731 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -114,6 +114,7 @@ const ( SimilarityIDFlag = "similarity-id" SeverityFlag = "severity" StateFlag = "state" + CustomStateIDFlag = "state-id" CommentFlag = "comment" LanguageFlag = "language" VulnerabilityTypeFlag = "vulnerability-type" diff --git a/internal/wrappers/mock/custom-states-mock.go b/internal/wrappers/mock/custom-states-mock.go index 6d9260b3d..6955527f6 100644 --- a/internal/wrappers/mock/custom-states-mock.go +++ b/internal/wrappers/mock/custom-states-mock.go @@ -1,6 +1,7 @@ package mock import "github.com/checkmarx/ast-cli/internal/wrappers" +import "github.com/pkg/errors" type CustomStatesMockWrapper struct{} @@ -16,4 +17,10 @@ func (m *CustomStatesMockWrapper) GetAllCustomStates(includeDeleted bool) ([]wra {ID: 2, Name: "demo2", Type: "System"}, {ID: 3, Name: "demo3", Type: "Custom"}, }, nil -} \ No newline at end of file +} + +type CustomStatesMockWrapperWithError struct{} + +func (m *CustomStatesMockWrapperWithError) GetAllCustomStates(includeDeleted bool) ([]wrappers.CustomState, error) { + return nil, errors.New("Failed to fetch custom states") +} diff --git a/internal/wrappers/predicates-http.go b/internal/wrappers/predicates-http.go index d58d7de4b..065a7fe7f 100644 --- a/internal/wrappers/predicates-http.go +++ b/internal/wrappers/predicates-http.go @@ -156,9 +156,8 @@ func responsePredicateParsingFailed(err error) (*PredicatesCollectionResponseMod return nil, nil, errors.Wrapf(err, failedToParsePredicates) } - type CustomStatesHTTPWrapper struct { -path string + path string } func NewCustomStatesHTTPWrapper() CustomStatesWrapper { diff --git a/internal/wrappers/predicates.go b/internal/wrappers/predicates.go index f530a0006..bb71160d6 100644 --- a/internal/wrappers/predicates.go +++ b/internal/wrappers/predicates.go @@ -13,11 +13,12 @@ type BasePredicate struct { } type PredicateRequest struct { - SimilarityID string `json:"similarityId"` - ProjectID string `json:"projectId"` - State string `json:"state"` - Comment string `json:"comment"` - Severity string `json:"severity"` + SimilarityID string `json:"similarityId"` + ProjectID string `json:"projectId"` + State string `json:"state"` + CustomStateID string `json:"customStateId"` + Comment string `json:"comment"` + Severity string `json:"severity"` } type Predicate struct { @@ -45,13 +46,10 @@ type CustomState struct { Type string `json:"type"` } - - type CustomStatesWrapper interface { GetAllCustomStates(includeDeleted bool) ([]CustomState, error) } - type ResultsPredicatesWrapper interface { PredicateSeverityAndState(predicate *PredicateRequest, scanType string) (*WebError, error) GetAllPredicatesForSimilarityID( From 28428337dd10458836d856503e24de8dba697a96 Mon Sep 17 00:00:00 2001 From: galactica Date: Wed, 5 Feb 2025 17:00:47 +0200 Subject: [PATCH 02/16] Add state id flag --- internal/commands/predicates.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index c34e625b4..1da26b15d 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -1,6 +1,7 @@ package commands import ( + "strconv" "strings" "time" From 3f13e2b5285836e3c52b172c8e50eb11c8d89f91 Mon Sep 17 00:00:00 2001 From: galactica Date: Fri, 7 Feb 2025 01:15:25 +0200 Subject: [PATCH 03/16] System state or custom state id, and add tests. --- internal/commands/predicates.go | 46 ++++++-- internal/commands/predicates_test.go | 170 +++++++++++++++++++++++++++ internal/wrappers/feature-flags.go | 5 + 3 files changed, 209 insertions(+), 12 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index 1da26b15d..bec93928e 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -119,7 +119,10 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW markFlagAsRequired(triageUpdateCmd, params.SimilarityIDFlag) markFlagAsRequired(triageUpdateCmd, params.SeverityFlag) markFlagAsRequired(triageUpdateCmd, params.ProjectIDFlag) - markFlagAsRequired(triageUpdateCmd, params.StateFlag) + flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.SastCustomStateEnabled) + if !flagResponse.Status { + markFlagAsRequired(triageUpdateCmd, params.StateFlag) + } markFlagAsRequired(triageUpdateCmd, params.ScanTypeFlag) return triageUpdateCmd @@ -184,16 +187,11 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, if !criticalEnabled && strings.EqualFold(severity, "critical") { return errors.Errorf("%s", "Critical severity is not available for your tenant.This severity status will be enabled shortly") } - if isCustomState(state) { - if customStateID == "" { - var err error - customStateID, err = getCustomStateID(customStatesWrapper, state) - if err != nil { - return errors.Wrapf(err, "Failed to get custom state ID for state: %s", state) - } - } - } else { - customStateID = "" + + var err error + state, customStateID, err = determineSystemOrCustomState(customStatesWrapper, featureFlagsWrapper, state, customStateID) + if err != nil { + return err } predicate := &wrappers.PredicateRequest{ @@ -205,7 +203,7 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, Comment: comment, } - _, err := resultsPredicatesWrapper.PredicateSeverityAndState(predicate, scanType) + _, err = resultsPredicatesWrapper.PredicateSeverityAndState(predicate, scanType) if err != nil { return errors.Wrapf(err, "%s", "Failed updating the predicate") } @@ -213,7 +211,31 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, return nil } } +func determineSystemOrCustomState(customStatesWrapper wrappers.CustomStatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, state, customStateID string) (string, string, error) { + if isCustomState(state) { + flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.SastCustomStateEnabled) + if !flagResponse.Status { + return "", "", errors.Errorf("%s", "Custom state is not available for your tenant.") + } + + if customStateID == "" { + if state == "" { + return "", "", errors.Errorf("state-id is required when state is not provided") + } + var err error + customStateID, err = getCustomStateID(customStatesWrapper, state) + if err != nil { + return "", "", errors.Wrapf(err, "Failed to get custom state ID for state: %s", state) + } + } + return "", customStateID, nil + } + return state, "", nil +} func isCustomState(state string) bool { + if state == "" { + return true + } systemStates := []string{"TO_VERIFY", "NOT_EXPLOITABLE", "PROPOSED_NOT_EXPLOITABLE", "CONFIRMED", "URGENT"} for _, customState := range systemStates { if strings.EqualFold(state, customState) { diff --git a/internal/commands/predicates_test.go b/internal/commands/predicates_test.go index b4ec895e1..a7d1d571e 100644 --- a/internal/commands/predicates_test.go +++ b/internal/commands/predicates_test.go @@ -134,3 +134,173 @@ func TestIsCustomState(t *testing.T) { }) } } +func TestRunTriageUpdateWithNotFoundCustomState(t *testing.T) { + mockResultsPredicatesWrapper := &mock.ResultsPredicatesMockWrapper{} + mockFeatureFlagsWrapper := &mock.FeatureFlagsMockWrapper{} + mockCustomStatesWrapper := &mock.CustomStatesMockWrapper{} + mock.Flag = wrappers.FeatureFlagResponseModel{ + Name: "test-flag", + Status: true, + } + cmd := triageUpdateSubCommand(mockResultsPredicatesWrapper, mockFeatureFlagsWrapper, mockCustomStatesWrapper) + cmd.SetArgs([]string{ + "--similarity-id", "MOCK", + "--project-id", "MOCK", + "--severity", "low", + "--state", "CUSTOM_STATE_1", + "--scan-type", "sast", + }) + + err := cmd.Execute() + assert.ErrorContains(t, err, "Failed to get custom state ID for state: CUSTOM_STATE_1: No matching state found for CUSTOM_STATE_1") +} + +func TestRunTriageUpdateWithCustomState(t *testing.T) { + mockResultsPredicatesWrapper := &mock.ResultsPredicatesMockWrapper{} + mockFeatureFlagsWrapper := &mock.FeatureFlagsMockWrapper{} + mockCustomStatesWrapper := &mock.CustomStatesMockWrapper{} + mock.Flag = wrappers.FeatureFlagResponseModel{ + Name: "test-flag", + Status: true, + } + cmd := triageUpdateSubCommand(mockResultsPredicatesWrapper, mockFeatureFlagsWrapper, mockCustomStatesWrapper) + cmd.SetArgs([]string{ + "--similarity-id", "MOCK", + "--project-id", "MOCK", + "--severity", "low", + "--state", "demo2", + "--scan-type", "sast", + }) + + err := cmd.Execute() + assert.NilError(t, err) +} + +func TestRunTriageUpdateWithSystemState(t *testing.T) { + mockResultsPredicatesWrapper := &mock.ResultsPredicatesMockWrapper{} + mockFeatureFlagsWrapper := &mock.FeatureFlagsMockWrapper{} + mockCustomStatesWrapper := &mock.CustomStatesMockWrapper{} + + cmd := triageUpdateSubCommand(mockResultsPredicatesWrapper, mockFeatureFlagsWrapper, mockCustomStatesWrapper) + cmd.SetArgs([]string{ + "--similarity-id", "MOCK", + "--project-id", "MOCK", + "--severity", "low", + "--state", "TO_VERIFY", + "--scan-type", "sast", + }) + + err := cmd.Execute() + assert.NilError(t, err) +} + +func TestDetermineSystemOrCustomState(t *testing.T) { + tests := []struct { + name string + state string + customStateID string + mockWrapper wrappers.CustomStatesWrapper + mockFeatureFlags wrappers.FeatureFlagsWrapper + flag wrappers.FeatureFlagResponseModel + expectedState string + expectedCustomState string + expectedError string + }{ + { + name: "Custom state with valid state name", + state: "demo2", + customStateID: "", + mockWrapper: &mock.CustomStatesMockWrapper{}, + mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, + flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + expectedState: "", + expectedCustomState: "2", + expectedError: "", + }, + { + name: "Custom state with valid state ID", + state: "", + customStateID: "2", + mockWrapper: &mock.CustomStatesMockWrapper{}, + flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + expectedState: "", + expectedCustomState: "2", + expectedError: "", + }, + { + name: "System state", + state: "TO_VERIFY", + customStateID: "", + mockWrapper: &mock.CustomStatesMockWrapper{}, + flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + expectedState: "TO_VERIFY", + expectedCustomState: "", + expectedError: "", + }, + { + name: "State ID required when state is not provided", + state: "", + customStateID: "", + mockWrapper: &mock.CustomStatesMockWrapper{}, + flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + expectedState: "", + expectedCustomState: "", + expectedError: "state-id is required when state is not provided", + }, + { + name: "Failed to get custom state ID", + state: "INVALID_STATE", + customStateID: "", + mockWrapper: &mock.CustomStatesMockWrapperWithError{}, + flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + expectedState: "", + expectedCustomState: "", + expectedError: "Failed to get custom state ID for state: INVALID_STATE", + }, + { + name: "Both state and state ID provided - valid custom state", + state: "demo2", + customStateID: "2", + mockWrapper: &mock.CustomStatesMockWrapper{}, + flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + expectedState: "", + expectedCustomState: "2", + expectedError: "", + }, + { + name: "Both state and state ID provided - valid system state", + state: "TO_VERIFY", + customStateID: "2", + mockWrapper: &mock.CustomStatesMockWrapper{}, + flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + expectedState: "TO_VERIFY", + expectedCustomState: "", + expectedError: "", + }, + { + name: "Both state and state ID provided - invalid state", + state: "INVALID_STATE", + customStateID: "2", + mockWrapper: &mock.CustomStatesMockWrapperWithError{}, + flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + expectedState: "", + expectedCustomState: "2", + expectedError: "", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + mock.Flag = tt.flag + state, customStateID, err := determineSystemOrCustomState(tt.mockWrapper, tt.mockFeatureFlags, tt.state, tt.customStateID) + if tt.expectedError != "" { + assert.ErrorContains(t, err, tt.expectedError) + } else { + assert.NilError(t, err) + } + assert.Equal(t, state, tt.expectedState) + assert.Equal(t, customStateID, tt.expectedCustomState) + }) + } +} diff --git a/internal/wrappers/feature-flags.go b/internal/wrappers/feature-flags.go index 6be68b174..a171b4244 100644 --- a/internal/wrappers/feature-flags.go +++ b/internal/wrappers/feature-flags.go @@ -10,6 +10,7 @@ const tenantIDClaimKey = "tenant_id" const PackageEnforcementEnabled = "PACKAGE_ENFORCEMENT_ENABLED" const CVSSV3Enabled = "CVSS_V3_ENABLED" const MinioEnabled = "MINIO_ENABLED" +const SastCustomStateEnabled = "SAST_CUSTOM_STATES_ENABLED" const ContainerEngineCLIEnabled = "CONTAINER_ENGINE_CLI_ENABLED" const SCSEngineCLIEnabled = "NEW_2MS_SCORECARD_RESULTS_CLI_ENABLED" const NewScanReportEnabled = "NEW_SAST_SCAN_REPORT_ENABLED" @@ -59,6 +60,10 @@ var FeatureFlagsBaseMap = []CommandFlags{ Name: CVSSV3Enabled, Default: false, }, + { + Name: SastCustomStateEnabled, + Default: false, + }, }, }, } From b76af352efd1b263fb4af2f98a163ecb4532ed63 Mon Sep 17 00:00:00 2001 From: galactica Date: Sun, 9 Feb 2025 12:09:16 +0200 Subject: [PATCH 04/16] Fix comments --- internal/commands/predicates.go | 9 ++++++--- internal/commands/predicates_test.go | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index 166e20001..e6db04445 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -13,6 +13,8 @@ import ( "github.com/spf13/cobra" ) +var systemStates = []string{"TO_VERIFY", "NOT_EXPLOITABLE", "PROPOSED_NOT_EXPLOITABLE", "CONFIRMED", "URGENT"} + func NewResultsPredicatesCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper) *cobra.Command { triageCmd := &cobra.Command{ Use: "triage", @@ -120,7 +122,8 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW markFlagAsRequired(triageUpdateCmd, params.SeverityFlag) markFlagAsRequired(triageUpdateCmd, params.ProjectIDFlag) flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.SastCustomStateEnabled) - if !flagResponse.Status { + sastCustomStateEnabled := flagResponse.Status + if !sastCustomStateEnabled { markFlagAsRequired(triageUpdateCmd, params.StateFlag) } markFlagAsRequired(triageUpdateCmd, params.ScanTypeFlag) @@ -214,7 +217,8 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, func determineSystemOrCustomState(customStatesWrapper wrappers.CustomStatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, state, customStateID string) (string, string, error) { if isCustomState(state) { flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.SastCustomStateEnabled) - if !flagResponse.Status { + sastCustomStateEnabled := flagResponse.Status + if !sastCustomStateEnabled { return "", "", errors.Errorf("%s", "Custom state is not available for your tenant.") } @@ -236,7 +240,6 @@ func isCustomState(state string) bool { if state == "" { return true } - systemStates := []string{"TO_VERIFY", "NOT_EXPLOITABLE", "PROPOSED_NOT_EXPLOITABLE", "CONFIRMED", "URGENT"} for _, customState := range systemStates { if strings.EqualFold(state, customState) { return false diff --git a/internal/commands/predicates_test.go b/internal/commands/predicates_test.go index a7d1d571e..f1fca8d84 100644 --- a/internal/commands/predicates_test.go +++ b/internal/commands/predicates_test.go @@ -278,7 +278,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { expectedError: "", }, { - name: "Both state and state ID provided - invalid state", + name: "Both state and state ID provided - invalid state name", state: "INVALID_STATE", customStateID: "2", mockWrapper: &mock.CustomStatesMockWrapperWithError{}, From daa6c889ba52fac70d25b5c1830d956b9a980250 Mon Sep 17 00:00:00 2001 From: galactica Date: Sun, 9 Feb 2025 13:25:11 +0200 Subject: [PATCH 05/16] Fix test --- internal/commands/predicates_test.go | 30 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/internal/commands/predicates_test.go b/internal/commands/predicates_test.go index f1fca8d84..fab6067ad 100644 --- a/internal/commands/predicates_test.go +++ b/internal/commands/predicates_test.go @@ -139,7 +139,7 @@ func TestRunTriageUpdateWithNotFoundCustomState(t *testing.T) { mockFeatureFlagsWrapper := &mock.FeatureFlagsMockWrapper{} mockCustomStatesWrapper := &mock.CustomStatesMockWrapper{} mock.Flag = wrappers.FeatureFlagResponseModel{ - Name: "test-flag", + Name: wrappers.SastCustomStateEnabled, Status: true, } cmd := triageUpdateSubCommand(mockResultsPredicatesWrapper, mockFeatureFlagsWrapper, mockCustomStatesWrapper) @@ -160,7 +160,7 @@ func TestRunTriageUpdateWithCustomState(t *testing.T) { mockFeatureFlagsWrapper := &mock.FeatureFlagsMockWrapper{} mockCustomStatesWrapper := &mock.CustomStatesMockWrapper{} mock.Flag = wrappers.FeatureFlagResponseModel{ - Name: "test-flag", + Name: wrappers.SastCustomStateEnabled, Status: true, } cmd := triageUpdateSubCommand(mockResultsPredicatesWrapper, mockFeatureFlagsWrapper, mockCustomStatesWrapper) @@ -212,7 +212,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { customStateID: "", mockWrapper: &mock.CustomStatesMockWrapper{}, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, - flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "2", expectedError: "", @@ -222,7 +222,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "", customStateID: "2", mockWrapper: &mock.CustomStatesMockWrapper{}, - flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "2", expectedError: "", @@ -232,7 +232,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "TO_VERIFY", customStateID: "", mockWrapper: &mock.CustomStatesMockWrapper{}, - flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "TO_VERIFY", expectedCustomState: "", expectedError: "", @@ -242,7 +242,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "", customStateID: "", mockWrapper: &mock.CustomStatesMockWrapper{}, - flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "", expectedError: "state-id is required when state is not provided", @@ -252,7 +252,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "INVALID_STATE", customStateID: "", mockWrapper: &mock.CustomStatesMockWrapperWithError{}, - flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "", expectedError: "Failed to get custom state ID for state: INVALID_STATE", @@ -262,7 +262,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "demo2", customStateID: "2", mockWrapper: &mock.CustomStatesMockWrapper{}, - flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "2", expectedError: "", @@ -272,7 +272,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "TO_VERIFY", customStateID: "2", mockWrapper: &mock.CustomStatesMockWrapper{}, - flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "TO_VERIFY", expectedCustomState: "", expectedError: "", @@ -282,11 +282,21 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "INVALID_STATE", customStateID: "2", mockWrapper: &mock.CustomStatesMockWrapperWithError{}, - flag: wrappers.FeatureFlagResponseModel{Name: "test-flag", Status: true}, + flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "2", expectedError: "", }, + { + name: "Custom state not available", + state: "demo2", + customStateID: "", + mockWrapper: &mock.CustomStatesMockWrapper{}, + flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: false}, + expectedState: "", + expectedCustomState: "", + expectedError: "Custom state is not available for your tenant.", + }, } for _, tt := range tests { From 01a4a9025b3cd55cc5a3e5780d048cd61139f8cb Mon Sep 17 00:00:00 2001 From: galactica Date: Sun, 9 Feb 2025 17:09:41 +0200 Subject: [PATCH 06/16] Fix test --- internal/commands/predicates_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/commands/predicates_test.go b/internal/commands/predicates_test.go index fab6067ad..707ad6872 100644 --- a/internal/commands/predicates_test.go +++ b/internal/commands/predicates_test.go @@ -138,6 +138,7 @@ func TestRunTriageUpdateWithNotFoundCustomState(t *testing.T) { mockResultsPredicatesWrapper := &mock.ResultsPredicatesMockWrapper{} mockFeatureFlagsWrapper := &mock.FeatureFlagsMockWrapper{} mockCustomStatesWrapper := &mock.CustomStatesMockWrapper{} + clearFlags() mock.Flag = wrappers.FeatureFlagResponseModel{ Name: wrappers.SastCustomStateEnabled, Status: true, @@ -159,6 +160,7 @@ func TestRunTriageUpdateWithCustomState(t *testing.T) { mockResultsPredicatesWrapper := &mock.ResultsPredicatesMockWrapper{} mockFeatureFlagsWrapper := &mock.FeatureFlagsMockWrapper{} mockCustomStatesWrapper := &mock.CustomStatesMockWrapper{} + clearFlags() mock.Flag = wrappers.FeatureFlagResponseModel{ Name: wrappers.SastCustomStateEnabled, Status: true, @@ -221,6 +223,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { name: "Custom state with valid state ID", state: "", customStateID: "2", + mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, mockWrapper: &mock.CustomStatesMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", @@ -232,6 +235,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "TO_VERIFY", customStateID: "", mockWrapper: &mock.CustomStatesMockWrapper{}, + mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "TO_VERIFY", expectedCustomState: "", @@ -242,6 +246,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "", customStateID: "", mockWrapper: &mock.CustomStatesMockWrapper{}, + mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "", @@ -252,6 +257,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "INVALID_STATE", customStateID: "", mockWrapper: &mock.CustomStatesMockWrapperWithError{}, + mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "", @@ -262,6 +268,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "demo2", customStateID: "2", mockWrapper: &mock.CustomStatesMockWrapper{}, + mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "2", @@ -272,6 +279,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "TO_VERIFY", customStateID: "2", mockWrapper: &mock.CustomStatesMockWrapper{}, + mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "TO_VERIFY", expectedCustomState: "", @@ -282,6 +290,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "INVALID_STATE", customStateID: "2", mockWrapper: &mock.CustomStatesMockWrapperWithError{}, + mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", expectedCustomState: "2", @@ -292,6 +301,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { state: "demo2", customStateID: "", mockWrapper: &mock.CustomStatesMockWrapper{}, + mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: false}, expectedState: "", expectedCustomState: "", @@ -302,6 +312,7 @@ func TestDetermineSystemOrCustomState(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { + clearFlags() mock.Flag = tt.flag state, customStateID, err := determineSystemOrCustomState(tt.mockWrapper, tt.mockFeatureFlags, tt.state, tt.customStateID) if tt.expectedError != "" { From 0443b0481ab1a49c4c3dab58940071857d1d00bb Mon Sep 17 00:00:00 2001 From: galactica Date: Mon, 10 Feb 2025 14:46:24 +0200 Subject: [PATCH 07/16] Fix comment --- internal/commands/predicates.go | 34 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index e6db04445..745967525 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -215,26 +215,28 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, } } func determineSystemOrCustomState(customStatesWrapper wrappers.CustomStatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, state, customStateID string) (string, string, error) { - if isCustomState(state) { - flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.SastCustomStateEnabled) - sastCustomStateEnabled := flagResponse.Status - if !sastCustomStateEnabled { - return "", "", errors.Errorf("%s", "Custom state is not available for your tenant.") + if !isCustomState(state) { + return state, "", nil + } + + flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.SastCustomStateEnabled) + sastCustomStateEnabled := flagResponse.Status + if !sastCustomStateEnabled { + return "", "", errors.Errorf("%s", "Custom state is not available for your tenant.") + } + + if customStateID == "" { + if state == "" { + return "", "", errors.Errorf("state-id is required when state is not provided") } - if customStateID == "" { - if state == "" { - return "", "", errors.Errorf("state-id is required when state is not provided") - } - var err error - customStateID, err = getCustomStateID(customStatesWrapper, state) - if err != nil { - return "", "", errors.Wrapf(err, "Failed to get custom state ID for state: %s", state) - } + var err error + customStateID, err = getCustomStateID(customStatesWrapper, state) + if err != nil { + return "", "", errors.Wrapf(err, "Failed to get custom state ID for state: %s", state) } - return "", customStateID, nil } - return state, "", nil + return "", customStateID, nil } func isCustomState(state string) bool { if state == "" { From d90c3a975b6cb3619468ebfa1636863bd10fc230 Mon Sep 17 00:00:00 2001 From: galactica Date: Mon, 10 Feb 2025 14:53:55 +0200 Subject: [PATCH 08/16] Remove FF from state flag --- internal/commands/predicates.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index 745967525..9208a7b67 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -121,11 +121,6 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW markFlagAsRequired(triageUpdateCmd, params.SimilarityIDFlag) markFlagAsRequired(triageUpdateCmd, params.SeverityFlag) markFlagAsRequired(triageUpdateCmd, params.ProjectIDFlag) - flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.SastCustomStateEnabled) - sastCustomStateEnabled := flagResponse.Status - if !sastCustomStateEnabled { - markFlagAsRequired(triageUpdateCmd, params.StateFlag) - } markFlagAsRequired(triageUpdateCmd, params.ScanTypeFlag) return triageUpdateCmd From bfefd9f641c3f71aeb4e3212ca4a4b7a927921f5 Mon Sep 17 00:00:00 2001 From: galactica Date: Mon, 10 Feb 2025 23:24:01 +0200 Subject: [PATCH 09/16] Fix test --- internal/commands/predicates_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/predicates_test.go b/internal/commands/predicates_test.go index 707ad6872..a14ec8209 100644 --- a/internal/commands/predicates_test.go +++ b/internal/commands/predicates_test.go @@ -48,7 +48,7 @@ func TestRunUpdateTriageCommandWithNoInput(t *testing.T) { fmt.Println(err) assert.Assert( t, - err.Error() == "required flag(s) \"project-id\", \"scan-type\", \"severity\", \"similarity-id\", \"state\" not set") + err.Error() == "required flag(s) \"project-id\", \"scan-type\", \"severity\", \"similarity-id\" not set") } func TestTriageGetStatesFlag(t *testing.T) { From c37c39318da2dc19a4e019f5993dc022a7ff07a9 Mon Sep 17 00:00:00 2001 From: galactica Date: Tue, 11 Feb 2025 13:41:23 +0200 Subject: [PATCH 10/16] Help for command. --- internal/commands/predicates.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index 9208a7b67..f4607c596 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -100,8 +100,8 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW $ cx triage update --similarity-id --project-id - --state > - --custom-state-id + --state + --custom-state-id --severity --comment --scan-type From 8e1e8c2ebe437382ff4aa2caffe59d6298739637 Mon Sep 17 00:00:00 2001 From: galactica Date: Tue, 11 Feb 2025 17:40:05 +0200 Subject: [PATCH 11/16] fix --- internal/commands/predicates.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index f4607c596..3ddd12d19 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -38,7 +38,7 @@ func triageGetStatesSubCommand(customStatesWrapper wrappers.CustomStatesWrapper) triageGetStatesCmd := &cobra.Command{ Use: "get-states", Short: "Show the custom states that have been configured in your tenant", - Long: "The get-states command shows information about each of the custom states that have been configured in your tenant account", + Long: "The get-states command shows information about each of the custom states that have been configured in your tenant account", Example: heredoc.Doc( ` $ cx triage get-states @@ -237,8 +237,8 @@ func isCustomState(state string) bool { if state == "" { return true } - for _, customState := range systemStates { - if strings.EqualFold(state, customState) { + for _, systemState := range systemStates { + if strings.EqualFold(state, systemState) { return false } } From 9e25d15ad0be4914478619f29bf14d5a801e6c95 Mon Sep 17 00:00:00 2001 From: galactica Date: Wed, 12 Feb 2025 14:40:13 +0200 Subject: [PATCH 12/16] Change state id to int, and change the help. --- internal/commands/predicates.go | 56 ++++++++++++++++------------ internal/commands/predicates_test.go | 50 ++++++++++++------------- internal/wrappers/predicates.go | 2 +- 3 files changed, 59 insertions(+), 49 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index 3ddd12d19..30e4a235d 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -1,7 +1,6 @@ package commands import ( - "strconv" "strings" "time" @@ -100,8 +99,8 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW $ cx triage update --similarity-id --project-id - --state - --custom-state-id + --state + --custom-state-id --severity --comment --scan-type @@ -113,8 +112,8 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW triageUpdateCmd.PersistentFlags().String(params.SimilarityIDFlag, "", "Similarity ID") triageUpdateCmd.PersistentFlags().String(params.SeverityFlag, "", "Severity") triageUpdateCmd.PersistentFlags().String(params.ProjectIDFlag, "", "Project ID.") - triageUpdateCmd.PersistentFlags().String(params.StateFlag, "", "State") - triageUpdateCmd.PersistentFlags().String(params.CustomStateIDFlag, "", "State ID") + triageUpdateCmd.PersistentFlags().String(params.StateFlag, "", "Specify the state that you would like to apply. Can be a pre-configured state (e.g., not_exploitable) or a custom state created in your account.") + triageUpdateCmd.PersistentFlags().Int(params.CustomStateIDFlag, -1, "Specify the ID of the states that you would like to apply to this result.") triageUpdateCmd.PersistentFlags().String(params.CommentFlag, "", "Optional comment.") triageUpdateCmd.PersistentFlags().String(params.ScanTypeFlag, "", "Scan Type") @@ -176,7 +175,7 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, projectID, _ := cmd.Flags().GetString(params.ProjectIDFlag) severity, _ := cmd.Flags().GetString(params.SeverityFlag) state, _ := cmd.Flags().GetString(params.StateFlag) - customStateID, _ := cmd.Flags().GetString(params.CustomStateIDFlag) + customStateID, _ := cmd.Flags().GetInt(params.CustomStateIDFlag) comment, _ := cmd.Flags().GetString(params.CommentFlag) scanType, _ := cmd.Flags().GetString(params.ScanTypeFlag) // check if the current tenant has critical severity available @@ -192,13 +191,24 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, return err } - predicate := &wrappers.PredicateRequest{ - SimilarityID: similarityID, - ProjectID: projectID, - Severity: severity, - State: state, - CustomStateID: customStateID, - Comment: comment, + var predicate *wrappers.PredicateRequest + if state != "" { + predicate = &wrappers.PredicateRequest{ + SimilarityID: similarityID, + ProjectID: projectID, + Severity: severity, + State: state, + Comment: comment, + } + } else { + predicate = &wrappers.PredicateRequest{ + SimilarityID: similarityID, + ProjectID: projectID, + Severity: severity, + CustomStateID: customStateID, + Comment: comment, + } + } _, err = resultsPredicatesWrapper.PredicateSeverityAndState(predicate, scanType) @@ -209,26 +219,26 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, return nil } } -func determineSystemOrCustomState(customStatesWrapper wrappers.CustomStatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, state, customStateID string) (string, string, error) { +func determineSystemOrCustomState(customStatesWrapper wrappers.CustomStatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, state string, customStateID int) (string, int, error) { if !isCustomState(state) { - return state, "", nil + return state, -1, nil } flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.SastCustomStateEnabled) sastCustomStateEnabled := flagResponse.Status if !sastCustomStateEnabled { - return "", "", errors.Errorf("%s", "Custom state is not available for your tenant.") + return "", -1, errors.Errorf("%s", "Custom state is not available for your tenant.") } - if customStateID == "" { + if customStateID == -1 { if state == "" { - return "", "", errors.Errorf("state-id is required when state is not provided") + return "", -1, errors.Errorf("state-id is required when state is not provided") } var err error customStateID, err = getCustomStateID(customStatesWrapper, state) if err != nil { - return "", "", errors.Wrapf(err, "Failed to get custom state ID for state: %s", state) + return "", -1, errors.Wrapf(err, "Failed to get custom state ID for state: %s", state) } } return "", customStateID, nil @@ -245,17 +255,17 @@ func isCustomState(state string) bool { return true } -func getCustomStateID(customStatesWrapper wrappers.CustomStatesWrapper, state string) (string, error) { +func getCustomStateID(customStatesWrapper wrappers.CustomStatesWrapper, state string) (int, error) { customStates, err := customStatesWrapper.GetAllCustomStates(false) if err != nil { - return "", errors.Wrap(err, "Failed to fetch custom states") + return -1, errors.Wrap(err, "Failed to fetch custom states") } for _, customState := range customStates { if customState.Name == state { - return strconv.Itoa(customState.ID), nil + return customState.ID, nil } } - return "", errors.Errorf("No matching state found for %s", state) + return -1, errors.Errorf("No matching state found for %s", state) } type predicateView struct { diff --git a/internal/commands/predicates_test.go b/internal/commands/predicates_test.go index a14ec8209..f9da51efa 100644 --- a/internal/commands/predicates_test.go +++ b/internal/commands/predicates_test.go @@ -12,7 +12,7 @@ import ( ) func TestTriageHelp(t *testing.T) { - execCmdNilAssertion(t, "help", "triage") + execCmdNilAssertion(t, "help", "triage", "update") } func TestRunShowTriageCommand(t *testing.T) { @@ -73,27 +73,27 @@ func TestGetCustomStateID(t *testing.T) { name string state string mockWrapper wrappers.CustomStatesWrapper - expectedStateID string + expectedStateID int expectedErrorString string }{ { name: "State found", state: "demo3", mockWrapper: &mock.CustomStatesMockWrapper{}, - expectedStateID: "3", + expectedStateID: 3, }, { name: "State not found", state: "nonexistent", mockWrapper: &mock.CustomStatesMockWrapper{}, - expectedStateID: "", + expectedStateID: -1, expectedErrorString: "No matching state found for nonexistent", }, { name: "Error fetching states", state: "nonexistent", mockWrapper: &mock.CustomStatesMockWrapperWithError{}, - expectedStateID: "", + expectedStateID: -1, expectedErrorString: "Failed to fetch custom states", }, } @@ -200,111 +200,111 @@ func TestDetermineSystemOrCustomState(t *testing.T) { tests := []struct { name string state string - customStateID string + customStateID int mockWrapper wrappers.CustomStatesWrapper mockFeatureFlags wrappers.FeatureFlagsWrapper flag wrappers.FeatureFlagResponseModel expectedState string - expectedCustomState string + expectedCustomState int expectedError string }{ { name: "Custom state with valid state name", state: "demo2", - customStateID: "", + customStateID: -1, mockWrapper: &mock.CustomStatesMockWrapper{}, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", - expectedCustomState: "2", + expectedCustomState: 2, expectedError: "", }, { name: "Custom state with valid state ID", state: "", - customStateID: "2", + customStateID: 2, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, mockWrapper: &mock.CustomStatesMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", - expectedCustomState: "2", + expectedCustomState: 2, expectedError: "", }, { name: "System state", state: "TO_VERIFY", - customStateID: "", + customStateID: -1, mockWrapper: &mock.CustomStatesMockWrapper{}, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "TO_VERIFY", - expectedCustomState: "", + expectedCustomState: -1, expectedError: "", }, { name: "State ID required when state is not provided", state: "", - customStateID: "", + customStateID: -1, mockWrapper: &mock.CustomStatesMockWrapper{}, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", - expectedCustomState: "", + expectedCustomState: -1, expectedError: "state-id is required when state is not provided", }, { name: "Failed to get custom state ID", state: "INVALID_STATE", - customStateID: "", + customStateID: -1, mockWrapper: &mock.CustomStatesMockWrapperWithError{}, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", - expectedCustomState: "", + expectedCustomState: -1, expectedError: "Failed to get custom state ID for state: INVALID_STATE", }, { name: "Both state and state ID provided - valid custom state", state: "demo2", - customStateID: "2", + customStateID: 2, mockWrapper: &mock.CustomStatesMockWrapper{}, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", - expectedCustomState: "2", + expectedCustomState: 2, expectedError: "", }, { name: "Both state and state ID provided - valid system state", state: "TO_VERIFY", - customStateID: "2", + customStateID: 2, mockWrapper: &mock.CustomStatesMockWrapper{}, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "TO_VERIFY", - expectedCustomState: "", + expectedCustomState: -1, expectedError: "", }, { name: "Both state and state ID provided - invalid state name", state: "INVALID_STATE", - customStateID: "2", + customStateID: 2, mockWrapper: &mock.CustomStatesMockWrapperWithError{}, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: true}, expectedState: "", - expectedCustomState: "2", + expectedCustomState: 2, expectedError: "", }, { name: "Custom state not available", state: "demo2", - customStateID: "", + customStateID: -1, mockWrapper: &mock.CustomStatesMockWrapper{}, mockFeatureFlags: &mock.FeatureFlagsMockWrapper{}, flag: wrappers.FeatureFlagResponseModel{Name: wrappers.SastCustomStateEnabled, Status: false}, expectedState: "", - expectedCustomState: "", + expectedCustomState: -1, expectedError: "Custom state is not available for your tenant.", }, } diff --git a/internal/wrappers/predicates.go b/internal/wrappers/predicates.go index bb71160d6..7b2f3f0ff 100644 --- a/internal/wrappers/predicates.go +++ b/internal/wrappers/predicates.go @@ -16,7 +16,7 @@ type PredicateRequest struct { SimilarityID string `json:"similarityId"` ProjectID string `json:"projectId"` State string `json:"state"` - CustomStateID string `json:"customStateId"` + CustomStateID int `json:"customStateId"` Comment string `json:"comment"` Severity string `json:"severity"` } From 3f3612a20813b6b56f11b30d685e63977e3e11f2 Mon Sep 17 00:00:00 2001 From: galactica Date: Wed, 12 Feb 2025 14:44:05 +0200 Subject: [PATCH 13/16] Undo change help test --- internal/commands/predicates_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/predicates_test.go b/internal/commands/predicates_test.go index f9da51efa..5eda102b8 100644 --- a/internal/commands/predicates_test.go +++ b/internal/commands/predicates_test.go @@ -12,7 +12,7 @@ import ( ) func TestTriageHelp(t *testing.T) { - execCmdNilAssertion(t, "help", "triage", "update") + execCmdNilAssertion(t, "help", "triage") } func TestRunShowTriageCommand(t *testing.T) { From 5153152594d947e58a7610ec4eaf54583f652e19 Mon Sep 17 00:00:00 2001 From: galactica Date: Wed, 12 Feb 2025 21:49:40 +0200 Subject: [PATCH 14/16] Custom state ID and state enable nill --- internal/commands/predicates.go | 29 +++++++++++------------------ internal/wrappers/predicates.go | 12 ++++++------ 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index d2c0d4100..cc30d6c44 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -99,8 +99,8 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW $ cx triage update --similarity-id --project-id - --state - --custom-state-id + --state + --state-id --severity --comment --scan-type @@ -191,24 +191,17 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, return err } - var predicate *wrappers.PredicateRequest + predicate := &wrappers.PredicateRequest{ + SimilarityID: similarityID, + ProjectID: projectID, + Severity: severity, + Comment: comment, + } + if state != "" { - predicate = &wrappers.PredicateRequest{ - SimilarityID: similarityID, - ProjectID: projectID, - Severity: severity, - State: state, - Comment: comment, - } + predicate.State = &state } else { - predicate = &wrappers.PredicateRequest{ - SimilarityID: similarityID, - ProjectID: projectID, - Severity: severity, - CustomStateID: customStateID, - Comment: comment, - } - + predicate.CustomStateID = &customStateID } _, err = resultsPredicatesWrapper.PredicateSeverityAndState(predicate, scanType) diff --git a/internal/wrappers/predicates.go b/internal/wrappers/predicates.go index 67da7a867..ed5b8619f 100644 --- a/internal/wrappers/predicates.go +++ b/internal/wrappers/predicates.go @@ -14,12 +14,12 @@ type BasePredicate struct { } type PredicateRequest struct { - SimilarityID string `json:"similarityId"` - ProjectID string `json:"projectId"` - State string `json:"state"` - CustomStateID int `json:"customStateId"` - Comment string `json:"comment"` - Severity string `json:"severity"` + SimilarityID string `json:"similarityId"` + ProjectID string `json:"projectId"` + State *string `json:"state,omitempty"` + CustomStateID *int `json:"customStateId,omitempty"` + Comment string `json:"comment"` + Severity string `json:"severity"` } type Predicate struct { From 485e54abb4634ef2229fbcbc1fc4e52ab61c35e9 Mon Sep 17 00:00:00 2001 From: galactica Date: Wed, 12 Feb 2025 22:10:50 +0200 Subject: [PATCH 15/16] Use in constantsStates --- internal/commands/predicates.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index 80dfec3c2..0872a7c6b 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -12,8 +12,6 @@ import ( "github.com/spf13/cobra" ) -var systemStates = []string{"TO_VERIFY", "NOT_EXPLOITABLE", "PROPOSED_NOT_EXPLOITABLE", "CONFIRMED", "URGENT"} - var constantsStates = []wrappers.CustomState{ {ID: -1, Name: "To Verify", Type: ""}, {ID: -1, Name: "Not Exploitable", Type: ""}, @@ -22,7 +20,7 @@ var constantsStates = []wrappers.CustomState{ {ID: -1, Name: "Urgent", Type: ""}, } -func NewResultsPredicatesCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper ) *cobra.Command { +func NewResultsPredicatesCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper) *cobra.Command { triageCmd := &cobra.Command{ Use: "triage", Short: "Manage results", @@ -253,8 +251,8 @@ func isCustomState(state string) bool { if state == "" { return true } - for _, systemState := range systemStates { - if strings.EqualFold(state, systemState) { + for _, systemState := range constantsStates { + if strings.EqualFold(state, systemState.Name) { return false } } From 926067406cec2e5a1227f9bc06b2dc4b0f6817dc Mon Sep 17 00:00:00 2001 From: Itay Paz Date: Thu, 13 Feb 2025 10:11:42 +0200 Subject: [PATCH 16/16] Itay-change system states to camel case --- internal/commands/predicates.go | 23 +++++++++++------------ internal/commands/predicates_test.go | 10 +++++----- internal/params/flags.go | 7 +++++++ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index 0872a7c6b..403a7fb5e 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -1,23 +1,22 @@ package commands import ( - "strings" - "time" - "github.com/MakeNowJust/heredoc" "github.com/checkmarx/ast-cli/internal/commands/util/printer" "github.com/checkmarx/ast-cli/internal/params" "github.com/checkmarx/ast-cli/internal/wrappers" "github.com/pkg/errors" "github.com/spf13/cobra" + "strings" + "time" ) -var constantsStates = []wrappers.CustomState{ - {ID: -1, Name: "To Verify", Type: ""}, - {ID: -1, Name: "Not Exploitable", Type: ""}, - {ID: -1, Name: "Proposed Not Exploitable", Type: ""}, - {ID: -1, Name: "Confirmed", Type: ""}, - {ID: -1, Name: "Urgent", Type: ""}, +var systemStates = []wrappers.CustomState{ + {ID: -1, Name: params.ToVerify, Type: ""}, + {ID: -1, Name: params.NotExploitable, Type: ""}, + {ID: -1, Name: params.ProposedNotExploitable, Type: ""}, + {ID: -1, Name: params.CONFIRMED, Type: ""}, + {ID: -1, Name: params.URGENT, Type: ""}, } func NewResultsPredicatesCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper) *cobra.Command { @@ -62,14 +61,14 @@ func runTriageGetStates(customStatesWrapper wrappers.CustomStatesWrapper, featur return func(cmd *cobra.Command, _ []string) error { flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.CustomStatesFeatureFlag) if !flagResponse.Status { - return printer.Print(cmd.OutOrStdout(), constantsStates, printer.FormatJSON) + return printer.Print(cmd.OutOrStdout(), systemStates, printer.FormatJSON) } includeDeleted, _ := cmd.Flags().GetBool(params.AllStatesFlag) states, err := customStatesWrapper.GetAllCustomStates(includeDeleted) if err != nil { return errors.Wrap(err, "Failed to fetch custom states") } - states = append(states, constantsStates...) + states = append(states, systemStates...) err = printer.Print(cmd.OutOrStdout(), states, printer.FormatJSON) return err } @@ -251,7 +250,7 @@ func isCustomState(state string) bool { if state == "" { return true } - for _, systemState := range constantsStates { + for _, systemState := range systemStates { if strings.EqualFold(state, systemState.Name) { return false } diff --git a/internal/commands/predicates_test.go b/internal/commands/predicates_test.go index 0f67fad7b..12312f9a5 100644 --- a/internal/commands/predicates_test.go +++ b/internal/commands/predicates_test.go @@ -62,8 +62,8 @@ func TestTriageGetStatesFlag(t *testing.T) { states, err := mockWrapper.GetAllCustomStates(false) assert.NilError(t, err) - expectedStatesCount := len(states) + len(constantsStates) - assert.Equal(t, expectedStatesCount, len(states)+len(constantsStates)) + expectedStatesCount := len(states) + len(systemStates) + assert.Equal(t, expectedStatesCount, len(states)+len(systemStates)) cmd.SetArgs([]string{"--all"}) err = cmd.Execute() @@ -71,15 +71,15 @@ func TestTriageGetStatesFlag(t *testing.T) { states, err = mockWrapper.GetAllCustomStates(true) assert.NilError(t, err) - expectedStatesCount = len(states) + len(constantsStates) - assert.Equal(t, expectedStatesCount, len(states)+len(constantsStates)) + expectedStatesCount = len(states) + len(systemStates) + assert.Equal(t, expectedStatesCount, len(states)+len(systemStates)) mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.CustomStatesFeatureFlag, Status: false} cmd = triageGetStatesSubCommand(mockWrapper, featureFlagsWrapper) cmd.SetArgs([]string{}) err = cmd.Execute() assert.NilError(t, err) - assert.Equal(t, len(constantsStates), len(constantsStates)) + assert.Equal(t, len(systemStates), len(systemStates)) } func TestGetCustomStateID(t *testing.T) { tests := []struct { diff --git a/internal/params/flags.go b/internal/params/flags.go index 7a0a7a731..f9c9f3cdc 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -287,3 +287,10 @@ var ( // Custom states const IncludeDeletedQueryParam = "include-deleted" const True = "true" + +// System States +const ToVerify = "TO_VERIFY" +const NotExploitable = "NOT_EXPLOITABLE" +const ProposedNotExploitable = "PROPOSED_NOT_EXPLOITABLE" +const CONFIRMED = "CONFIRMED" +const URGENT = "URGENT" \ No newline at end of file