11package commands
22
33import (
4+ "fmt"
5+ "sort"
46 "strings"
57 "time"
68
9+ "encoding/json"
10+
711 "github.com/MakeNowJust/heredoc"
812 "github.com/checkmarx/ast-cli/internal/commands/util/printer"
13+ "github.com/checkmarx/ast-cli/internal/logger"
914 "github.com/checkmarx/ast-cli/internal/params"
1015 "github.com/checkmarx/ast-cli/internal/wrappers"
1116 "github.com/pkg/errors"
@@ -92,8 +97,8 @@ func triageShowSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWra
9297 triageShowCmd .PersistentFlags ().String (params .SimilarityIDFlag , "" , "Similarity ID" )
9398 triageShowCmd .PersistentFlags ().String (params .ProjectIDFlag , "" , "Project ID" )
9499 triageShowCmd .PersistentFlags ().String (params .ScanTypeFlag , "" , "Scan Type" )
100+ triageShowCmd .PersistentFlags ().StringSlice (params .VulnerabilitiesFlag , []string {}, "SCA Vulnerabilities details" )
95101
96- markFlagAsRequired (triageShowCmd , params .SimilarityIDFlag )
97102 markFlagAsRequired (triageShowCmd , params .ProjectIDFlag )
98103 markFlagAsRequired (triageShowCmd , params .ScanTypeFlag )
99104
@@ -117,6 +122,7 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW
117122 --scan-type <SAST|IAC-SECURITY>
118123 ` ,
119124 ),
125+
120126 RunE : runTriageUpdate (resultsPredicatesWrapper , featureFlagsWrapper , customStatesWrapper ),
121127 }
122128
@@ -127,9 +133,8 @@ func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesW
127133 triageUpdateCmd .PersistentFlags ().Int (params .CustomStateIDFlag , - 1 , "Specify the ID of the states that you would like to apply to this result" )
128134 triageUpdateCmd .PersistentFlags ().String (params .CommentFlag , "" , "Optional comment" )
129135 triageUpdateCmd .PersistentFlags ().String (params .ScanTypeFlag , "" , "Scan Type" )
136+ triageUpdateCmd .PersistentFlags ().StringSlice (params .VulnerabilitiesFlag , []string {}, "SCA Vulnerabilities details" )
130137
131- markFlagAsRequired (triageUpdateCmd , params .SimilarityIDFlag )
132- markFlagAsRequired (triageUpdateCmd , params .SeverityFlag )
133138 markFlagAsRequired (triageUpdateCmd , params .ProjectIDFlag )
134139 markFlagAsRequired (triageUpdateCmd , params .ScanTypeFlag )
135140
@@ -145,38 +150,48 @@ func runTriageShow(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper) f
145150 similarityID , _ := cmd .Flags ().GetString (params .SimilarityIDFlag )
146151 scanType , _ := cmd .Flags ().GetString (params .ScanTypeFlag )
147152 projectID , _ := cmd .Flags ().GetString (params .ProjectIDFlag )
148-
153+ vulnerabilityDetails , _ := cmd . Flags (). GetStringSlice ( params . VulnerabilitiesFlag )
149154 projectIDs := strings .Split (projectID , "," )
150155 if len (projectIDs ) > 1 {
151156 return errors .Errorf ("%s" , "Multiple project-ids are not allowed." )
152157 }
153158
154- predicatesCollection , errorModel , err = resultsPredicatesWrapper .GetAllPredicatesForSimilarityID (
155- similarityID ,
156- projectID ,
157- scanType ,
158- )
159-
160- if err != nil {
161- return errors .Wrapf (err , "%s" , "Failed showing the predicate" )
162- }
163-
164- // Checking the response
165- if errorModel != nil {
166- return errors .Errorf (
167- "%s: CODE: %d, %s" ,
168- "Failed showing the predicate." ,
169- errorModel .Code ,
170- errorModel .Message ,
171- )
172- } else if predicatesCollection != nil {
173- err = printByFormat (cmd , toPredicatesView (* predicatesCollection ))
159+ if strings .EqualFold (strings .ToLower (strings .TrimSpace (scanType )), params .ScaType ) {
160+ if len (vulnerabilityDetails ) == 0 {
161+ return errors .Errorf ("%s" , "Failed showing the predicate. Vulnerabilities are required for SCA triage" )
162+ }
163+ scaPredicates , err := resultsPredicatesWrapper .GetScaPredicates (vulnerabilityDetails , projectID )
164+ if err != nil {
165+ return errors .Wrapf (err , "%s" , "Failed showing the predicate" )
166+ }
167+ err = printByFormat (cmd , toScaPredicateResultView (scaPredicates ))
174168 if err != nil {
175169 return err
176170 }
171+ return nil
172+ } else {
173+ predicatesCollection , errorModel , err = resultsPredicatesWrapper .GetAllPredicatesForSimilarityID (similarityID , projectID , scanType )
174+ if err != nil {
175+ return errors .Wrapf (err , "%s" , "Failed showing the predicate" )
176+ }
177+ // Checking the response
178+ if errorModel != nil {
179+ return errors .Errorf (
180+ "%s: CODE: %d, %s" ,
181+ "Failed showing the predicate." ,
182+ errorModel .Code ,
183+ errorModel .Message ,
184+ )
185+ } else if predicatesCollection != nil {
186+ err = printByFormat (cmd , toPredicatesView (* predicatesCollection ))
187+ if err != nil {
188+ return err
189+ }
190+ }
191+
192+ return nil
177193 }
178194
179- return nil
180195 }
181196}
182197
@@ -191,38 +206,124 @@ func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper,
191206 scanType , _ := cmd .Flags ().GetString (params .ScanTypeFlag )
192207 // check if the current tenant has critical severity available
193208 flagResponse , _ := wrappers .GetSpecificFeatureFlag (featureFlagsWrapper , wrappers .CVSSV3Enabled )
209+ vulnerabilityDetails , _ := cmd .Flags ().GetStringSlice (params .VulnerabilitiesFlag )
210+
194211 criticalEnabled := flagResponse .Status
195212 if ! criticalEnabled && strings .EqualFold (severity , "critical" ) {
196213 return errors .Errorf ("%s" , "Critical severity is not available for your tenant.This severity status will be enabled shortly" )
197214 }
198-
199215 var err error
200216 state , customStateID , err = determineSystemOrCustomState (customStatesWrapper , featureFlagsWrapper , state , customStateID )
201217 if err != nil {
202- return err
218+ return errors . Wrapf ( err , "%s" , "Failed updating the predicate" )
203219 }
204220
205- predicate := & wrappers.PredicateRequest {
221+ predicate , err := preparePredicateRequest (vulnerabilityDetails , similarityID , projectID , severity , state , customStateID , comment , scanType )
222+ if err != nil {
223+ return errors .Wrapf (err , "%s" , "Failed updating the predicate" )
224+ }
225+ _ , err = resultsPredicatesWrapper .PredicateSeverityAndState (predicate , scanType )
226+ if err != nil {
227+ return errors .Wrapf (err , "%s" , "Failed updating the predicate" )
228+ }
229+ return nil
230+ }
231+ }
232+
233+ func preparePredicateRequest (vulnerabilityDetails []string , similarityID , projectID , severity , state string , customStateID int , comment , scanType string ) (interface {}, error ) {
234+ scanType = strings .ToLower (scanType )
235+ scanType = strings .TrimSpace (scanType )
236+ if strings .EqualFold (scanType , Sca ) {
237+ state = transformState (state )
238+ payload , err := prepareScaTriagePayload (vulnerabilityDetails , comment , state , projectID )
239+ if err != nil {
240+ return nil , err
241+ }
242+ return payload , nil
243+ } else {
244+ payload := & wrappers.PredicateRequest {
206245 SimilarityID : similarityID ,
207246 ProjectID : projectID ,
208247 Severity : severity ,
209248 Comment : comment ,
210249 }
211-
212250 if state != "" {
213- predicate .State = & state
251+ payload .State = & state
214252 } else {
215- predicate .CustomStateID = & customStateID
253+ payload .CustomStateID = & customStateID
216254 }
255+ return payload , nil
256+ }
257+ }
217258
218- _ , err = resultsPredicatesWrapper .PredicateSeverityAndState (predicate , scanType )
259+ func transformState (state string ) string {
260+ state = strings .ToLower (strings .TrimSpace (state ))
261+ switch state {
262+ case strings .ToLower (params .ToVerify ):
263+ return wrappers .ToVerify
264+ case strings .ToLower (params .URGENT ):
265+ return wrappers .Urgent
266+ case strings .ToLower (params .NotExploitable ):
267+ return wrappers .NotExploitable
268+ case strings .ToLower (params .ProposedNotExploitable ):
269+ return wrappers .ProposedNotExploitable
270+ case strings .ToLower (params .CONFIRMED ):
271+ return wrappers .Confirmed
272+ }
273+ return ""
274+ }
275+
276+ func prepareScaTriagePayload (vulnerabilityDetails []string , comment , state , projectID string ) (interface {}, error ) {
277+ if len (vulnerabilityDetails ) == 0 {
278+ return nil , errors .Errorf ("Vulnerabilities details are required." )
279+ }
280+ scaTriageInfo := make (map [string ]interface {})
281+ for _ , vulnerability := range vulnerabilityDetails {
282+ vulnerabilityKeyVal := strings .Split (vulnerability , "=" )
283+ err := validateVulnerabilityDetails (vulnerabilityKeyVal )
219284 if err != nil {
220- return errors . Wrapf ( err , "%s" , "Failed updating the predicate" )
285+ return nil , err
221286 }
287+ scaTriageInfo [strings .TrimSpace (vulnerabilityKeyVal [0 ])] = strings .TrimSpace (vulnerabilityKeyVal [1 ])
288+ }
222289
223- return nil
290+ if scaTriageInfo ["packageName" ] == nil && scaTriageInfo ["packagename" ] == nil {
291+ return nil , errors .Errorf ("Package name is required" )
224292 }
293+ if scaTriageInfo ["packageVersion" ] == nil && scaTriageInfo ["packageversion" ] == nil {
294+ return nil , errors .Errorf ("Package version is required" )
295+ }
296+ if scaTriageInfo ["packageManager" ] == nil && scaTriageInfo ["packagemanager" ] == nil {
297+ return nil , errors .Errorf ("Package manager is required" )
298+ }
299+
300+ scaTriageInfo ["projectIds" ] = []string {projectID }
301+ actionInfo := make (map [string ]interface {})
302+ actionInfo ["actionType" ] = params .ChangeState
303+ actionInfo ["value" ] = state
304+ actionInfo ["comment" ] = comment
305+ scaTriageInfo ["actions" ] = []map [string ]interface {}{actionInfo }
306+ b , err := json .Marshal (scaTriageInfo )
307+ if err != nil {
308+ logger .PrintIfVerbose (fmt .Sprintf ("Failed to serialize vulnerabilities %s" , scaTriageInfo ))
309+ return nil , errors .Errorf ("Failed to prepare SCA triage request" )
310+ }
311+ payload := wrappers.ScaPredicateRequest {}
312+ err = json .Unmarshal (b , & payload )
313+ if err != nil {
314+ logger .PrintIfVerbose (fmt .Sprintf ("Failed to deserialize vulnerabilities %s" , string (b )))
315+ return nil , errors .Errorf ("Failed to prepare SCA triage request" )
316+ }
317+ return payload , nil
318+ }
319+
320+ func validateVulnerabilityDetails (vulnerability []string ) error {
321+ if len (vulnerability ) != params .KeyValuePairSize {
322+ return errors .Errorf ("Invalid vulnerabilities. It should be in a KEY=VALUE format" )
323+ }
324+ return nil
225325}
326+
226327func determineSystemOrCustomState (customStatesWrapper wrappers.CustomStatesWrapper , featureFlagsWrapper wrappers.FeatureFlagsWrapper , state string , customStateID int ) (string , int , error ) {
227328 if ! isCustomState (state ) {
228329 return state , - 1 , nil
@@ -284,6 +385,41 @@ type predicateView struct {
284385 CreatedAt time.Time `format:"name:Created at;time:01-02-06 15:04:05"`
285386}
286387
388+ type scaPredicateResultView struct {
389+ VulnerabilityID string `format:"name:Vulnerability ID"`
390+ PackageName string `format:"name:Package Name"`
391+ PackageVersion string `format:"name:Package Version"`
392+ PackageManager string `format:"name:Package Manager"`
393+ Comment string `format:"name:Comment"`
394+ State string `format:"name:State"`
395+ CreatedBy string `format:"name:Created By"`
396+ CreatedAt time.Time `format:"name:Created at;time:01-02-06 15:04:05"`
397+ }
398+
399+ func toScaPredicateResultView (scaPredicateResult * wrappers.ScaPredicateResult ) []scaPredicateResultView {
400+ view := []scaPredicateResultView {}
401+ if len (scaPredicateResult .Actions ) > 0 {
402+ for _ , action := range scaPredicateResult .Actions {
403+ view = append (view , scaPredicateResultView {
404+ VulnerabilityID : scaPredicateResult .Context .VulnerabilityID ,
405+ PackageName : scaPredicateResult .Context .PackageName ,
406+ PackageVersion : scaPredicateResult .Context .PackageVersion ,
407+ PackageManager : scaPredicateResult .Context .PackageManager ,
408+ Comment : action .Message ,
409+ State : action .ActionValue ,
410+ CreatedBy : action .UserName ,
411+ CreatedAt : action .CreatedAt ,
412+ })
413+ }
414+ }
415+
416+ sort .Slice (view , func (i , j int ) bool {
417+ return view [i ].CreatedAt .After (view [j ].CreatedAt )
418+ })
419+
420+ return view
421+ }
422+
287423func toPredicatesView (predicatesCollection wrappers.PredicatesCollectionResponseModel ) []predicateView {
288424 projectPredicatesCollection := predicatesCollection .PredicateHistoryPerProject
289425
0 commit comments