diff --git a/xray/audit/jas/applicabilitymanager.go b/xray/audit/jas/applicabilitymanager.go index 0dc23ad0e..4c99df9cc 100644 --- a/xray/audit/jas/applicabilitymanager.go +++ b/xray/audit/jas/applicabilitymanager.go @@ -7,9 +7,9 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" - xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/owenrumney/go-sarif/v2/sarif" "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "strings" ) @@ -27,9 +27,9 @@ const ( // map[string]string: A map containing the applicability result of each XRAY CVE. // bool: true if the user is entitled to the applicability scan, false otherwise. // error: An error object (if any). -func getApplicabilityScanResults(xrayResults []services.ScanResponse, dependencyTrees []*xrayUtils.GraphNode, +func getApplicabilityScanResults(xrayResults []services.ScanResponse, directDependencies []string, scannedTechnologies []coreutils.Technology, scanner *AdvancedSecurityScanner) (results map[string]string, err error) { - applicabilityScanManager := newApplicabilityScanManager(xrayResults, dependencyTrees, scanner) + applicabilityScanManager := newApplicabilityScanManager(xrayResults, directDependencies, scanner) if !applicabilityScanManager.shouldRunApplicabilityScan(scannedTechnologies) { log.Debug("The technologies that have been scanned are currently not supported for contextual analysis scanning, or we couldn't find any vulnerable direct dependencies. Skipping....") return @@ -49,8 +49,7 @@ type ApplicabilityScanManager struct { scanner *AdvancedSecurityScanner } -func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, dependencyTrees []*xrayUtils.GraphNode, scanner *AdvancedSecurityScanner) (manager *ApplicabilityScanManager) { - directDependencies := getDirectDependenciesSet(dependencyTrees) +func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, directDependencies []string, scanner *AdvancedSecurityScanner) (manager *ApplicabilityScanManager) { directDependenciesCves := extractDirectDependenciesCvesFromScan(xrayScanResults, directDependencies) return &ApplicabilityScanManager{ applicabilityScanResults: map[string]string{}, @@ -62,20 +61,24 @@ func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, depend // This function gets a list of xray scan responses that contain direct and indirect vulnerabilities and returns only direct // vulnerabilities of the scanned project, ignoring indirect vulnerabilities -func extractDirectDependenciesCvesFromScan(xrayScanResults []services.ScanResponse, directDependencies *datastructures.Set[string]) *datastructures.Set[string] { +func extractDirectDependenciesCvesFromScan(xrayScanResults []services.ScanResponse, directDependencies []string) *datastructures.Set[string] { directsCves := datastructures.MakeSet[string]() for _, scanResult := range xrayScanResults { for _, vulnerability := range scanResult.Vulnerabilities { if isDirectComponents(maps.Keys(vulnerability.Components), directDependencies) { for _, cve := range vulnerability.Cves { - directsCves.Add(cve.Id) + if cve.Id != "" { + directsCves.Add(cve.Id) + } } } } for _, violation := range scanResult.Violations { if isDirectComponents(maps.Keys(violation.Components), directDependencies) { for _, cve := range violation.Cves { - directsCves.Add(cve.Id) + if cve.Id != "" { + directsCves.Add(cve.Id) + } } } } @@ -84,26 +87,15 @@ func extractDirectDependenciesCvesFromScan(xrayScanResults []services.ScanRespon return directsCves } -func isDirectComponents(components []string, directDependencies *datastructures.Set[string]) bool { +func isDirectComponents(components []string, directDependencies []string) bool { for _, component := range components { - if directDependencies.Exists(component) { + if slices.Contains(directDependencies, component) { return true } } return false } -// This function retrieves the dependency trees of the scanned project and extracts a set that contains only the direct dependencies. -func getDirectDependenciesSet(dependencyTrees []*xrayUtils.GraphNode) *datastructures.Set[string] { - directDependencies := datastructures.MakeSet[string]() - for _, tree := range dependencyTrees { - for _, node := range tree.Nodes { - directDependencies.Add(node.Id) - } - } - return directDependencies -} - func (a *ApplicabilityScanManager) Run(wd string) (err error) { if len(a.scanner.workingDirs) > 1 { log.Info("Running applicability scanning in the", wd, "directory...") diff --git a/xray/audit/jas/applicabilitymanager_test.go b/xray/audit/jas/applicabilitymanager_test.go index 7d6caf0d8..3c8ffc5a8 100644 --- a/xray/audit/jas/applicabilitymanager_test.go +++ b/xray/audit/jas/applicabilitymanager_test.go @@ -1,12 +1,10 @@ package jas import ( - "github.com/jfrog/gofrog/datastructures" rtutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/xray/services" - xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/stretchr/testify/assert" "os" "path/filepath" @@ -23,7 +21,7 @@ func TestNewApplicabilityScanManager_InputIsValid(t *testing.T) { assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, fakeBasicDependencyGraph, scanner) + applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, mockDirectDependencies, scanner) // Assert assert.NotEmpty(t, applicabilityManager) @@ -84,7 +82,7 @@ func TestNewApplicabilityScanManager_NoDirectDependenciesInScan(t *testing.T) { assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(noDirectDependenciesResults, fakeBasicDependencyGraph, scanner) + applicabilityManager := newApplicabilityScanManager(noDirectDependenciesResults, mockDirectDependencies, scanner) // Assert assert.NotEmpty(t, applicabilityManager) @@ -97,7 +95,6 @@ func TestNewApplicabilityScanManager_NoDirectDependenciesInScan(t *testing.T) { func TestNewApplicabilityScanManager_MultipleDependencyTrees(t *testing.T) { // Arrange assert.NoError(t, rtutils.DownloadAnalyzerManagerIfNeeded()) - multipleDependencyTrees := []*xrayUtils.GraphNode{multipleFakeBasicDependencyGraph[0], multipleFakeBasicDependencyGraph[1]} // Act scanner, err := NewAdvancedSecurityScanner(nil, &fakeServerDetails) @@ -107,7 +104,7 @@ func TestNewApplicabilityScanManager_MultipleDependencyTrees(t *testing.T) { assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, multipleDependencyTrees, scanner) + applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, mockMultiRootDirectDependencies, scanner) // Assert assert.NotEmpty(t, applicabilityManager) @@ -138,7 +135,7 @@ func TestNewApplicabilityScanManager_ViolationsDontExistInResults(t *testing.T) assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(noViolationScanResponse, fakeBasicDependencyGraph, scanner) + applicabilityManager := newApplicabilityScanManager(noViolationScanResponse, mockDirectDependencies, scanner) // Assert assert.NoError(t, err) @@ -170,7 +167,7 @@ func TestNewApplicabilityScanManager_VulnerabilitiesDontExist(t *testing.T) { assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(noVulnerabilitiesScanResponse, fakeBasicDependencyGraph, scanner) + applicabilityManager := newApplicabilityScanManager(noVulnerabilitiesScanResponse, mockDirectDependencies, scanner) // Assert assert.NotEmpty(t, applicabilityManager) @@ -188,7 +185,7 @@ func TestApplicabilityScanManager_ShouldRun_TechnologiesNotEligibleForScan(t *te assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - results, err := getApplicabilityScanResults(fakeBasicXrayResults, fakeBasicDependencyGraph, + results, err := getApplicabilityScanResults(fakeBasicXrayResults, mockDirectDependencies, []coreutils.Technology{coreutils.Nuget, coreutils.Go}, scanner) // Assert @@ -206,7 +203,7 @@ func TestApplicabilityScanManager_ShouldRun_ScanResultsAreEmpty(t *testing.T) { assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(nil, fakeBasicDependencyGraph, scanner) + applicabilityManager := newApplicabilityScanManager(nil, mockDirectDependencies, scanner) assert.NoError(t, err) // Assert eligible := applicabilityManager.shouldRunApplicabilityScan([]coreutils.Technology{coreutils.Npm}) @@ -240,11 +237,7 @@ func TestExtractXrayDirectViolations(t *testing.T) { } for _, test := range tests { - directDependenciesSet := datastructures.MakeSet[string]() - for _, direct := range test.directDependencies { - directDependenciesSet.Add(direct) - } - cves := extractDirectDependenciesCvesFromScan(xrayResponseForDirectViolationsTest, directDependenciesSet) + cves := extractDirectDependenciesCvesFromScan(xrayResponseForDirectViolationsTest, test.directDependencies) assert.Equal(t, test.cvesCount, cves.Size()) } } @@ -285,48 +278,7 @@ func TestExtractXrayDirectVulnerabilities(t *testing.T) { } for _, test := range tests { - directDependenciesSet := datastructures.MakeSet[string]() - for _, direct := range test.directDependencies { - directDependenciesSet.Add(direct) - } - assert.Equal(t, test.cvesCount, extractDirectDependenciesCvesFromScan(xrayResponseForDirectVulnerabilitiesTest, directDependenciesSet).Size()) - } -} - -func TestGetDirectDependenciesList(t *testing.T) { - tests := []struct { - dependenciesTrees []*xrayUtils.GraphNode - expectedResult []string - }{ - { - dependenciesTrees: nil, - expectedResult: []string{}, - }, - { - dependenciesTrees: []*xrayUtils.GraphNode{ - {Id: "parent_node_id", Nodes: []*xrayUtils.GraphNode{ - {Id: "issueId_1_direct_dependency", Nodes: []*xrayUtils.GraphNode{{Id: "issueId_1_non_direct_dependency"}}}, - {Id: "issueId_2_direct_dependency", Nodes: nil}, - }, - }, - }, - expectedResult: []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, - }, - { - dependenciesTrees: []*xrayUtils.GraphNode{ - {Id: "parent_node_id", Nodes: []*xrayUtils.GraphNode{ - {Id: "issueId_1_direct_dependency", Nodes: nil}, - {Id: "issueId_2_direct_dependency", Nodes: nil}, - }, - }, - }, - expectedResult: []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, - }, - } - - for _, test := range tests { - result := getDirectDependenciesSet(test.dependenciesTrees) - assert.ElementsMatch(t, test.expectedResult, result.ToSlice()) + assert.Equal(t, test.cvesCount, extractDirectDependenciesCvesFromScan(xrayResponseForDirectVulnerabilitiesTest, test.directDependencies).Size()) } } @@ -340,7 +292,7 @@ func TestCreateConfigFile_VerifyFileWasCreated(t *testing.T) { assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, fakeBasicDependencyGraph, scanner) + applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, scanner) currWd, err := coreutils.GetWorkingDirectory() assert.NoError(t, err) @@ -369,7 +321,7 @@ func TestParseResults_EmptyResults_AllCvesShouldGetUnknown(t *testing.T) { assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, fakeBasicDependencyGraph, scanner) + applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, mockDirectDependencies, scanner) applicabilityManager.scanner.resultsFileName = filepath.Join("..", "..", "commands", "testdata", "applicability-scan", "empty-results.sarif") // Act @@ -393,7 +345,7 @@ func TestParseResults_ApplicableCveExist(t *testing.T) { assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, fakeBasicDependencyGraph, scanner) + applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, mockDirectDependencies, scanner) applicabilityManager.scanner.resultsFileName = filepath.Join("..", "..", "commands", "testdata", "applicability-scan", "applicable-cve-results.sarif") // Act @@ -416,7 +368,7 @@ func TestParseResults_AllCvesNotApplicable(t *testing.T) { assert.NoError(t, scanner.scannerDirCleanupFunc()) } }() - applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, fakeBasicDependencyGraph, scanner) + applicabilityManager := newApplicabilityScanManager(fakeBasicXrayResults, mockDirectDependencies, scanner) applicabilityManager.scanner.resultsFileName = filepath.Join("..", "..", "commands", "testdata", "applicability-scan", "no-applicable-cves-results.sarif") // Act @@ -433,7 +385,7 @@ func TestParseResults_AllCvesNotApplicable(t *testing.T) { func TestGetExtendedScanResults_AnalyzerManagerReturnsError(t *testing.T) { assert.NoError(t, rtutils.DownloadAnalyzerManagerIfNeeded()) scanResults := &utils.ExtendedScanResults{XrayResults: fakeBasicXrayResults, ScannedTechnologies: []coreutils.Technology{coreutils.Yarn}} - err := RunScannersAndSetResults(scanResults, fakeBasicDependencyGraph, &fakeServerDetails, nil, nil) + err := RunScannersAndSetResults(scanResults, mockDirectDependencies, &fakeServerDetails, nil, nil) // Expect error: assert.ErrorContains(t, err, "failed to run Applicability scan") diff --git a/xray/audit/jas/jasmanager.go b/xray/audit/jas/jasmanager.go index 083a0a43e..c3304ecdf 100644 --- a/xray/audit/jas/jasmanager.go +++ b/xray/audit/jas/jasmanager.go @@ -8,7 +8,6 @@ import ( "github.com/jfrog/jfrog-client-go/utils/io" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" - xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/owenrumney/go-sarif/v2/sarif" "gopkg.in/yaml.v3" "os" @@ -65,7 +64,7 @@ func (a *AdvancedSecurityScanner) Run(scannerCmd ScannerCmd) (err error) { return } -func RunScannersAndSetResults(scanResults *utils.ExtendedScanResults, dependencyTrees []*xrayUtils.GraphNode, +func RunScannersAndSetResults(scanResults *utils.ExtendedScanResults, directDependencies []string, serverDetails *config.ServerDetails, workingDirs []string, progress io.ProgressMgr) (err error) { if serverDetails == nil || len(serverDetails.Url) == 0 { log.Warn("To include 'Advanced Security' scan as part of the audit output, please run the 'jf c add' command before running this command.") @@ -82,7 +81,7 @@ func RunScannersAndSetResults(scanResults *utils.ExtendedScanResults, dependency if progress != nil { progress.SetHeadlineMsg("Running applicability scanning") } - scanResults.ApplicabilityScanResults, err = getApplicabilityScanResults(scanResults.XrayResults, dependencyTrees, scanResults.ScannedTechnologies, scanner) + scanResults.ApplicabilityScanResults, err = getApplicabilityScanResults(scanResults.XrayResults, directDependencies, scanResults.ScannedTechnologies, scanner) if err != nil { return } diff --git a/xray/audit/jas/jasmanager_test.go b/xray/audit/jas/jasmanager_test.go index e7a36df70..960c1e9d3 100644 --- a/xray/audit/jas/jasmanager_test.go +++ b/xray/audit/jas/jasmanager_test.go @@ -9,7 +9,6 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/xray/services" - xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/stretchr/testify/assert" ) @@ -29,32 +28,8 @@ var fakeBasicXrayResults = []services.ScanResponse{ }, } -var fakeBasicDependencyGraph = []*xrayUtils.GraphNode{ - { - Id: "parent_node_id", - Nodes: []*xrayUtils.GraphNode{ - {Id: "issueId_1_direct_dependency", Nodes: []*xrayUtils.GraphNode{{Id: "issueId_1_non_direct_dependency"}}}, - {Id: "issueId_2_direct_dependency", Nodes: nil}, - }, - }, -} - -var multipleFakeBasicDependencyGraph = []*xrayUtils.GraphNode{ - { - Id: "parent_node_id", - Nodes: []*xrayUtils.GraphNode{ - {Id: "issueId_1_direct_dependency", Nodes: []*xrayUtils.GraphNode{{Id: "issueId_1_non_direct_dependency"}}}, - {Id: "issueId_2_direct_dependency", Nodes: nil}, - }, - }, - { - Id: "parent_node_id", - Nodes: []*xrayUtils.GraphNode{ - {Id: "issueId_3_direct_dependency", Nodes: []*xrayUtils.GraphNode{{Id: "issueId_2_non_direct_dependency"}}}, - {Id: "issueId_4_direct_dependency", Nodes: nil}, - }, - }, -} +var mockDirectDependencies = []string{"issueId_2_direct_dependency", "issueId_1_direct_dependency"} +var mockMultiRootDirectDependencies = []string{"issueId_2_direct_dependency", "issueId_1_direct_dependency", "issueId_3_direct_dependency", "issueId_4_direct_dependency"} var fakeServerDetails = config.ServerDetails{ Url: "platformUrl", @@ -73,13 +48,13 @@ func TestGetExtendedScanResults_AnalyzerManagerDoesntExist(t *testing.T) { assert.NoError(t, os.Unsetenv(coreutils.HomeDir)) }() scanResults := &utils.ExtendedScanResults{XrayResults: fakeBasicXrayResults, ScannedTechnologies: []coreutils.Technology{coreutils.Yarn}} - err = RunScannersAndSetResults(scanResults, fakeBasicDependencyGraph, &fakeServerDetails, nil, nil) + err = RunScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, &fakeServerDetails, nil, nil) // Expect error: assert.Error(t, err) } func TestGetExtendedScanResults_ServerNotValid(t *testing.T) { scanResults := &utils.ExtendedScanResults{XrayResults: fakeBasicXrayResults, ScannedTechnologies: []coreutils.Technology{coreutils.Pip}} - err := RunScannersAndSetResults(scanResults, fakeBasicDependencyGraph, nil, nil, nil) + err := RunScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, nil, nil, nil) assert.NoError(t, err) } diff --git a/xray/commands/audit/generic/auditmanager.go b/xray/commands/audit/generic/auditmanager.go index cd4b256ee..55015a9ce 100644 --- a/xray/commands/audit/generic/auditmanager.go +++ b/xray/commands/audit/generic/auditmanager.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/jfrog/build-info-go/utils/pythonutils" + "github.com/jfrog/gofrog/datastructures" rtutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/audit" @@ -147,7 +148,7 @@ func RunAudit(auditParams *Params) (results *Results, err error) { // Run scanners only if the user is entitled for Advanced Security if results.ExtendedScanResults.EntitledForJas { - results.JasError = jas.RunScannersAndSetResults(results.ExtendedScanResults, auditParams.FullDependenciesTree(), serverDetails, auditParams.workingDirs, auditParams.Progress()) + results.JasError = jas.RunScannersAndSetResults(results.ExtendedScanResults, auditParams.DirectDependencies(), serverDetails, auditParams.workingDirs, auditParams.Progress()) } return } @@ -211,7 +212,7 @@ func runScaScanOnWorkingDir(params *Params, results *Results, workingDir, rootDi if tech == coreutils.Dotnet { continue } - flattenTree, techErr := GetTechDependencyTree(params.GraphBasicParams, tech) + flattenTree, fullDependencyTrees, techErr := GetTechDependencyTree(params.GraphBasicParams, tech) if techErr != nil { err = errors.Join(err, fmt.Errorf("failed while building '%s' dependency tree:\n%s\n", tech, techErr.Error())) continue @@ -232,7 +233,13 @@ func runScaScanOnWorkingDir(params *Params, results *Results, workingDir, rootDi err = errors.Join(err, fmt.Errorf("'%s' Xray dependency tree scan request failed:\n%s\n", tech, techErr.Error())) continue } - techResults = audit.BuildImpactPathsForScanResponse(techResults, params.FullDependenciesTree()) + techResults = audit.BuildImpactPathsForScanResponse(techResults, fullDependencyTrees) + if tech == coreutils.Pip { + params.AppendDirectDependencies(getDirectDependenciesFromTree(flattenTree)) + + } else { + params.AppendDirectDependencies(getDirectDependenciesFromTree(fullDependencyTrees)) + } results.ExtendedScanResults.XrayResults = append(results.ExtendedScanResults.XrayResults, techResults...) if !results.IsMultipleRootProject { results.IsMultipleRootProject = len(flattenTree) > 1 @@ -242,7 +249,18 @@ func runScaScanOnWorkingDir(params *Params, results *Results, workingDir, rootDi return } -func GetTechDependencyTree(params *xrayutils.GraphBasicParams, tech coreutils.Technology) (flatTree []*xrayCmdUtils.GraphNode, err error) { +// This function retrieves the dependency trees of the scanned project and extracts a set that contains only the direct dependencies. +func getDirectDependenciesFromTree(dependencyTrees []*xrayCmdUtils.GraphNode) []string { + directDependencies := datastructures.MakeSet[string]() + for _, tree := range dependencyTrees { + for _, node := range tree.Nodes { + directDependencies.Add(node.Id) + } + } + return directDependencies.ToSlice() +} + +func GetTechDependencyTree(params *xrayutils.GraphBasicParams, tech coreutils.Technology) (flatTree []*xrayCmdUtils.GraphNode, fullDependencyTrees []*xrayCmdUtils.GraphNode, err error) { if params.Progress() != nil { params.Progress().SetHeadlineMsg(fmt.Sprintf("Calculating %v dependencies", tech.ToFormal())) } @@ -250,35 +268,32 @@ func GetTechDependencyTree(params *xrayutils.GraphBasicParams, tech coreutils.Te if err != nil { return } - var dependencyTrees []*xrayCmdUtils.GraphNode switch tech { case coreutils.Maven, coreutils.Gradle: - dependencyTrees, err = getJavaDependencyTree(params, tech) + fullDependencyTrees, err = getJavaDependencyTree(params, tech) case coreutils.Npm: - dependencyTrees, err = npm.BuildDependencyTree(params.Args()) + fullDependencyTrees, err = npm.BuildDependencyTree(params.Args()) case coreutils.Yarn: - dependencyTrees, err = yarn.BuildDependencyTree() + fullDependencyTrees, err = yarn.BuildDependencyTree() case coreutils.Go: - dependencyTrees, err = _go.BuildDependencyTree(serverDetails, params.DepsRepo()) + fullDependencyTrees, err = _go.BuildDependencyTree(serverDetails, params.DepsRepo()) case coreutils.Pipenv, coreutils.Pip, coreutils.Poetry: - dependencyTrees, err = python.BuildDependencyTree(&python.AuditPython{ + fullDependencyTrees, err = python.BuildDependencyTree(&python.AuditPython{ Server: serverDetails, Tool: pythonutils.PythonTool(tech), RemotePypiRepo: params.DepsRepo(), PipRequirementsFile: params.PipRequirementsFile()}) case coreutils.Nuget: - dependencyTrees, err = nuget.BuildDependencyTree() + fullDependencyTrees, err = nuget.BuildDependencyTree() default: err = errorutils.CheckErrorf("%s is currently not supported", string(tech)) } if err != nil { - return nil, err + return nil, nil, err } - // Save the full dependencyTree to build impact paths for vulnerable dependencies - params.SetFullDependenciesTree(dependencyTrees) - // Flatten the graph to speed up the ScanGraph request - return services.FlattenGraph(dependencyTrees) + flatTree, err = services.FlattenGraph(fullDependencyTrees) + return } func getJavaDependencyTree(params *xrayutils.GraphBasicParams, tech coreutils.Technology) ([]*xrayCmdUtils.GraphNode, error) { diff --git a/xray/commands/audit/generic/auditmanager_test.go b/xray/commands/audit/generic/auditmanager_test.go new file mode 100644 index 000000000..3e3d198d7 --- /dev/null +++ b/xray/commands/audit/generic/auditmanager_test.go @@ -0,0 +1,44 @@ +package audit + +import ( + xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGetDirectDependenciesList(t *testing.T) { + tests := []struct { + dependenciesTrees []*xrayUtils.GraphNode + expectedResult []string + }{ + { + dependenciesTrees: nil, + expectedResult: []string{}, + }, + { + dependenciesTrees: []*xrayUtils.GraphNode{ + {Id: "parent_node_id", Nodes: []*xrayUtils.GraphNode{ + {Id: "issueId_1_direct_dependency", Nodes: []*xrayUtils.GraphNode{{Id: "issueId_1_non_direct_dependency"}}}, + {Id: "issueId_2_direct_dependency", Nodes: nil}, + }, + }, + }, + expectedResult: []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, + }, + { + dependenciesTrees: []*xrayUtils.GraphNode{ + {Id: "parent_node_id", Nodes: []*xrayUtils.GraphNode{ + {Id: "issueId_1_direct_dependency", Nodes: nil}, + {Id: "issueId_2_direct_dependency", Nodes: nil}, + }, + }, + }, + expectedResult: []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, + }, + } + + for _, test := range tests { + result := getDirectDependenciesFromTree(test.dependenciesTrees) + assert.ElementsMatch(t, test.expectedResult, result) + } +} diff --git a/xray/commands/curation/audit.go b/xray/commands/curation/audit.go index 956f9c053..9529f59d9 100644 --- a/xray/commands/curation/audit.go +++ b/xray/commands/curation/audit.go @@ -192,12 +192,12 @@ func (ca *CurationAuditCommand) doCurateAudit(results map[string][]*PackageStatu } func (ca *CurationAuditCommand) auditTree(tech coreutils.Technology, results map[string][]*PackageStatus) error { - flattenGraph, err := audit.GetTechDependencyTree(ca.GraphBasicParams, tech) + flattenGraph, fullDependenciesTree, err := audit.GetTechDependencyTree(ca.GraphBasicParams, tech) if err != nil { return err } // Validate the graph isn't empty. - if len(ca.FullDependenciesTree()) == 0 { + if len(fullDependenciesTree) == 0 { return errorutils.CheckErrorf("found no dependencies for the audited project using '%v' as the package manager", tech.ToString()) } if err = ca.SetRepo(tech); err != nil { @@ -216,7 +216,8 @@ func (ca *CurationAuditCommand) auditTree(tech coreutils.Technology, results map if err != nil { return err } - _, projectName, projectScope, projectVersion := getUrlNameAndVersionByTech(tech, ca.FullDependenciesTree()[0].Id, "", "") + rootNode := fullDependenciesTree[0] + _, projectName, projectScope, projectVersion := getUrlNameAndVersionByTech(tech, rootNode.Id, "", "") if ca.Progress() != nil { ca.Progress().SetHeadlineMsg(fmt.Sprintf("Fetch curation status for %s graph with %v nodes project name: %s:%s", tech.ToFormal(), len(flattenGraph[0].Nodes)-1, projectName, projectVersion)) } @@ -238,11 +239,9 @@ func (ca *CurationAuditCommand) auditTree(tech coreutils.Technology, results map parallelRequests: ca.parallelRequests, } packagesStatusMap := sync.Map{} - // Root node id represents the project name and shouldn't be validated with curation - rootNodeId := ca.FullDependenciesTree()[0].Id // Fetch status for each node from a flatten graph which, has no duplicate nodes. - err = analyzer.fetchNodesStatus(flattenGraph[0], &packagesStatusMap, rootNodeId) - analyzer.fillGraphRelations(ca.FullDependenciesTree()[0], &packagesStatusMap, + err = analyzer.fetchNodesStatus(flattenGraph[0], &packagesStatusMap, rootNode.Id) + analyzer.fillGraphRelations(rootNode, &packagesStatusMap, &packagesStatus, "", "", datastructures.MakeSet[string](), true) sort.Slice(packagesStatus, func(i, j int) bool { return packagesStatus[i].ParentName < packagesStatus[j].ParentName diff --git a/xray/utils/models.go b/xray/utils/models.go index dfb6a35bc..553758082 100644 --- a/xray/utils/models.go +++ b/xray/utils/models.go @@ -3,14 +3,13 @@ package utils import ( "github.com/jfrog/jfrog-cli-core/v2/utils/config" ioUtils "github.com/jfrog/jfrog-client-go/utils/io" - xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" ) type GraphBasicParams struct { serverDetails *config.ServerDetails outputFormat OutputFormat progress ioUtils.ProgressMgr - fullDependenciesTree []*xrayUtils.GraphNode + directDependencies []string excludeTestDependencies bool useWrapper bool insecureTls bool @@ -21,12 +20,12 @@ type GraphBasicParams struct { ignoreConfigFile bool } -func (gbp *GraphBasicParams) FullDependenciesTree() []*xrayUtils.GraphNode { - return gbp.fullDependenciesTree +func (gbp *GraphBasicParams) DirectDependencies() []string { + return gbp.directDependencies } -func (gbp *GraphBasicParams) SetFullDependenciesTree(fullDependenciesTree []*xrayUtils.GraphNode) *GraphBasicParams { - gbp.fullDependenciesTree = fullDependenciesTree +func (gbp *GraphBasicParams) AppendDirectDependencies(directDependencies []string) *GraphBasicParams { + gbp.directDependencies = append(gbp.directDependencies, directDependencies...) return gbp }