diff --git a/cmd/guacgql/cmd/ingest.go b/cmd/guacgql/cmd/ingest.go index 1e078e37ce..3a34783485 100644 --- a/cmd/guacgql/cmd/ingest.go +++ b/cmd/guacgql/cmd/ingest.go @@ -186,12 +186,14 @@ func ingestDependency(ctx context.Context, client graphql.Client) { ns := "ubuntu" version := "1.19.0.4" depns := "openssl.org" + opensslVersion := "3.0.3" smartentryNs := "smartentry" ingestDependencies := []struct { - name string - pkg model.PkgInputSpec - depPkg model.PkgInputSpec - dependency model.IsDependencyInputSpec + name string + pkg model.PkgInputSpec + depPkg model.PkgInputSpec + depPkgMatchFlags model.MatchFlags + dependency model.IsDependencyInputSpec }{ { name: "deb: part of SBOM - openssl", @@ -209,6 +211,7 @@ func ingestDependency(ctx context.Context, client graphql.Client) { Namespace: &depns, Name: "openssl", }, + depPkgMatchFlags: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, dependency: model.IsDependencyInputSpec{ VersionRange: "3.0.3", DependencyType: model.DependencyTypeDirect, @@ -228,7 +231,13 @@ func ingestDependency(ctx context.Context, client graphql.Client) { Type: "conan", Namespace: &depns, Name: "openssl", + Version: &opensslVersion, + Qualifiers: []model.PackageQualifierInputSpec{ + {Key: "user", Value: "bincrafters"}, + {Key: "channel", Value: "stable"}, + }, }, + depPkgMatchFlags: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, dependency: model.IsDependencyInputSpec{ VersionRange: "3.0.3", DependencyType: model.DependencyTypeIndirect, @@ -238,7 +247,7 @@ func ingestDependency(ctx context.Context, client graphql.Client) { }, }, { - name: "deb: part of SBOM - openssl (duplicate)", + name: "deb: part of SBOM - openssl (indirect)", pkg: model.PkgInputSpec{ Type: "deb", Namespace: &ns, @@ -252,7 +261,13 @@ func ingestDependency(ctx context.Context, client graphql.Client) { Type: "conan", Namespace: &depns, Name: "openssl", + Version: &opensslVersion, + Qualifiers: []model.PackageQualifierInputSpec{ + {Key: "user", Value: "bincrafters"}, + {Key: "channel", Value: "stable"}, + }, }, + depPkgMatchFlags: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, dependency: model.IsDependencyInputSpec{ VersionRange: "3.0.3", DependencyType: model.DependencyTypeDirect, @@ -269,7 +284,7 @@ func ingestDependency(ctx context.Context, client graphql.Client) { if _, err := model.IngestPackage(ctx, client, ingest.depPkg); err != nil { logger.Errorf("Error in ingesting dependency package: %v\n", err) } - if _, err := model.IsDependency(ctx, client, ingest.pkg, ingest.depPkg, ingest.dependency); err != nil { + if _, err := model.IsDependency(ctx, client, ingest.pkg, ingest.depPkg, ingest.depPkgMatchFlags, ingest.dependency); err != nil { logger.Errorf("Error in ingesting: %v\n", err) } } @@ -2306,6 +2321,7 @@ func ingestReachabilityTestData(ctx context.Context, client graphql.Client) { depPkg model.PkgInputSpec dependency model.IsDependencyInputSpec depPkgWithVersion model.PkgInputSpec + depPkgMatchFlags model.MatchFlags art model.ArtifactInputSpec occurrence model.IsOccurrenceInputSpec source model.SourceInputSpec @@ -2331,6 +2347,7 @@ func ingestReachabilityTestData(ctx context.Context, client graphql.Client) { Namespace: &depns, Name: "openssl", }, + depPkgMatchFlags: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, dependency: model.IsDependencyInputSpec{ VersionRange: "3.0.3", DependencyType: model.DependencyTypeDirect, @@ -2411,7 +2428,7 @@ func ingestReachabilityTestData(ctx context.Context, client graphql.Client) { if _, err := model.IngestVulnerability(ctx, client, *ingest.vuln); err != nil { logger.Errorf("Error in ingesting vuln: %v\n", err) } - if _, err := model.IsDependency(ctx, client, ingest.pkg, ingest.depPkg, ingest.dependency); err != nil { + if _, err := model.IsDependency(ctx, client, ingest.pkg, ingest.depPkg, ingest.depPkgMatchFlags, ingest.dependency); err != nil { logger.Errorf("Error in ingesting: %v\n", err) } if _, err := model.IsOccurrencePkg(ctx, client, ingest.depPkgWithVersion, ingest.art, ingest.occurrence); err != nil { diff --git a/cmd/guacone/cmd/vulnerability.go b/cmd/guacone/cmd/vulnerability.go index 28ac43b8cd..e440ac7841 100644 --- a/cmd/guacone/cmd/vulnerability.go +++ b/cmd/guacone/cmd/vulnerability.go @@ -411,7 +411,7 @@ func searchDependencyPackagesReverse(ctx context.Context, gqlclient graphql.Clie return nil, fmt.Errorf("failed getting package parent:%v", err) } for _, neighbor := range isDependencyNeighborResponses.Neighbors { - if isDependency, ok := neighbor.(*model.NeighborsNeighborsIsDependency); ok { + if isDependency, ok := neighbor.(*model.NeighborsNeighborsIsDependency); ok && now != isDependency.Package.Namespaces[0].Names[0].Versions[0].Id { dfsN, seen := nodeMap[isDependency.Package.Namespaces[0].Names[0].Versions[0].Id] if !seen { dfsN = dfsNode{ @@ -428,6 +428,22 @@ func searchDependencyPackagesReverse(ctx context.Context, gqlclient graphql.Clie } } } + if isDependency, ok := neighbor.(*model.NeighborsNeighborsIsDependency); ok && now != isDependency.Package.Namespaces[0].Names[0].Versions[0].Id { + dfsN, seen := nodeMap[isDependency.Package.Namespaces[0].Names[0].Versions[0].Id] + if !seen { + dfsN = dfsNode{ + parent: now, + isDependency: isDependency, + depth: nowNode.depth + 1, + } + nodeMap[isDependency.Package.Namespaces[0].Names[0].Versions[0].Id] = dfsN + } + if !dfsN.expanded { + queue = append(queue, isDependency.Package.Namespaces[0].Names[0].Versions[0].Id) + collectedIDs = append(collectedIDs, isDependency.Package.Namespaces[0].Names[0].Versions[0].Id) + } + } + } nowNode.expanded = true diff --git a/demo/graphql/path.py b/demo/graphql/path.py index b91bca91c7..8618a3cc6a 100755 --- a/demo/graphql/path.py +++ b/demo/graphql/path.py @@ -41,6 +41,13 @@ def deepID(node): pass return node['id'] +def isPackageVersion(node): + try: + retrieve = (node['namespaces'][0]['names'][0]['versions'][0]) + return True + except: + return False + # guacID returns the ID of a GUAC node. For nouns, the deepest ID is the ID, # for verbs, it is the top level ID. def guacID(node): @@ -71,9 +78,9 @@ def containsID(node, id): # bfs finds the shortest path fron start to target, while applying filter() to # only search in certian ways. def bfs(startID, targetID): - visited = [] + visited = set([]) queue = [] - visited.append(startID) + visited.add(startID) queue.append({'id': startID, 'path': [ startID ], 'pathNodes': [ nodeQuery(startID) ]}) while queue: node = queue.pop(0) @@ -90,7 +97,7 @@ def bfs(startID, targetID): newNodePath.append(n) if nodeID == targetID: return (newPath, newNodePath) - visited.append(nodeID) + visited.add(nodeID) queue.append({'id': nodeID, 'path': newPath, 'pathNodes': newNodePath}) return [], [] @@ -100,6 +107,7 @@ def filter(fromID, fromNode, neighbor): if neighbor['__typename'] == 'Package': # From Package -> Package, only search downwards if fromNode['__typename'] == 'Package': + return isPackageVersion(neighbor) return containsID(neighbor, fromID) # From other node type -> Package is ok. return True diff --git a/demo/graphql/queries.gql b/demo/graphql/queries.gql index d60dd73c9f..6f39d39fe7 100644 --- a/demo/graphql/queries.gql +++ b/demo/graphql/queries.gql @@ -230,6 +230,10 @@ query PkgQ8 { names { id name + versions { + id + version + } } } } diff --git a/internal/testing/e2e/e2e b/internal/testing/e2e/e2e index c4cde4a1b7..2b0b186db5 100755 --- a/internal/testing/e2e/e2e +++ b/internal/testing/e2e/e2e @@ -77,9 +77,9 @@ cat "$queries" | gql-cli http://localhost:8080/query -o CertifyVulnQ1 | jq 'del( diff -u "${SCRIPT_DIR}/expectCertifyVulnQ1.json" "${GUAC_DIR}/gotCertifyVulnQ1.json" id3=$(cat "$queries" | gql-cli http://localhost:8080/query -o PkgQ7 | jq -r ' .packages[0].namespaces[0].names[0].id ') -id4=$(cat "$queries" | gql-cli http://localhost:8080/query -o PkgQ8 | jq -r ' .packages[0].namespaces[0].names[0].id ') +id4=$(cat "$queries" | gql-cli http://localhost:8080/query -o PkgQ8 | jq -r ' .packages[0].namespaces[0].names[0].versions[0].id ') -"${GUAC_DIR}/demo/graphql/path.py" $id3 $id4 | tail -n +2 | jq 'del(.. | .id?) | del(.. | .origin?)' > "${GUAC_DIR}/gotPathPy.json" +"${GUAC_DIR}/demo/graphql/path.py" $id3 $id4 | tail -n +2 | jq 'del(.. | .id?) | del(.. | .origin?) | del(.. | .qualifiers?)' > "${GUAC_DIR}/gotPathPy.json" diff -u "${SCRIPT_DIR}/expectPathPy.json" "${GUAC_DIR}/gotPathPy.json" # Note: graphql_playground is left running, CI will clean it up diff --git a/internal/testing/e2e/expectIsDependencyQ2.json b/internal/testing/e2e/expectIsDependencyQ2.json index ec92dc2e6e..b07975ec70 100644 --- a/internal/testing/e2e/expectIsDependencyQ2.json +++ b/internal/testing/e2e/expectIsDependencyQ2.json @@ -36,7 +36,13 @@ "names": [ { "name": "logrus", - "versions": [] + "versions": [ + { + "version": "v1.4.2", + "qualifiers": [], + "subpath": "" + } + ] } ] } diff --git a/internal/testing/e2e/expectPathPy.json b/internal/testing/e2e/expectPathPy.json index dafc6093fa..314dced93a 100644 --- a/internal/testing/e2e/expectPathPy.json +++ b/internal/testing/e2e/expectPathPy.json @@ -26,12 +26,6 @@ "versions": [ { "version": "sha256:e9ebb760d74df0c28447c2f67050800c3392a3987ed77ce493545c7d3ad24c9d", - "qualifiers": [ - { - "key": "tag", - "value": "latest" - } - ], "subpath": "" } ] @@ -55,12 +49,6 @@ "versions": [ { "version": "sha256:e9ebb760d74df0c28447c2f67050800c3392a3987ed77ce493545c7d3ad24c9d", - "qualifiers": [ - { - "key": "tag", - "value": "latest" - } - ], "subpath": "" } ] @@ -77,7 +65,12 @@ "names": [ { "name": "libsqlite3-dev", - "versions": [] + "versions": [ + { + "version": "3.34.1-3", + "subpath": "" + } + ] } ] } @@ -94,7 +87,12 @@ "names": [ { "name": "libsqlite3-dev", - "versions": [] + "versions": [ + { + "version": "3.34.1-3", + "subpath": "" + } + ] } ] } diff --git a/internal/testing/testdata/exampledata/ingest_predicates.json b/internal/testing/testdata/exampledata/ingest_predicates.json index 6801fa74d5..c0aa62b052 100644 --- a/internal/testing/testdata/exampledata/ingest_predicates.json +++ b/internal/testing/testdata/exampledata/ingest_predicates.json @@ -87,6 +87,9 @@ ], "subpath": "" }, + "depPkgMatchFlag" : { + "pkg": "SPECIFIC_VERSION" + }, "isDependency": { "versionRange": "3.2.0-r22", "dependencyType": "UNKNOWN", @@ -125,6 +128,9 @@ ], "subpath": "" }, + "depPkgMatchFlag" : { + "pkg": "SPECIFIC_VERSION" + }, "isDependency": { "versionRange": "3.2.0-r22", "dependencyType": "UNKNOWN", diff --git a/internal/testing/testdata/testdata.go b/internal/testing/testdata/testdata.go index 96172a0d2e..7fe8883787 100644 --- a/internal/testing/testdata/testdata.go +++ b/internal/testing/testdata/testdata.go @@ -486,8 +486,9 @@ var ( SpdxDeps = []assembler.IsDependencyIngest{ { - Pkg: topLevelPack, - DepPkg: baselayoutPack, + Pkg: topLevelPack, + DepPkg: baselayoutPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "3.2.0-r22", @@ -495,8 +496,9 @@ var ( }, }, { - Pkg: topLevelPack, - DepPkg: baselayoutdataPack, + Pkg: topLevelPack, + DepPkg: baselayoutdataPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "3.2.0-r22", @@ -504,8 +506,9 @@ var ( }, }, { - Pkg: topLevelPack, - DepPkg: keysPack, + Pkg: topLevelPack, + DepPkg: keysPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "2.4-r1", @@ -513,8 +516,9 @@ var ( }, }, { - Pkg: topLevelPack, - DepPkg: worldFilePack, + Pkg: topLevelPack, + DepPkg: worldFilePack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "", @@ -522,8 +526,9 @@ var ( }, }, { - Pkg: topLevelPack, - DepPkg: rootFilePack, + Pkg: topLevelPack, + DepPkg: rootFilePack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "", @@ -531,8 +536,9 @@ var ( }, }, { - Pkg: topLevelPack, - DepPkg: triggersFilePack, + Pkg: topLevelPack, + DepPkg: triggersFilePack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "", @@ -540,8 +546,9 @@ var ( }, }, { - Pkg: topLevelPack, - DepPkg: rsaPubFilePack, + Pkg: topLevelPack, + DepPkg: rsaPubFilePack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "", @@ -549,8 +556,9 @@ var ( }, }, { - Pkg: baselayoutPack, - DepPkg: keysPack, + Pkg: baselayoutPack, + DepPkg: keysPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "2.4-r1", @@ -558,8 +566,9 @@ var ( }, }, { - Pkg: rootFilePack, - DepPkg: rsaPubFilePack, + Pkg: rootFilePack, + DepPkg: rsaPubFilePack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "", @@ -567,8 +576,9 @@ var ( }, }, { - Pkg: baselayoutPack, - DepPkg: rootFilePack, + Pkg: baselayoutPack, + DepPkg: rootFilePack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "", @@ -576,8 +586,9 @@ var ( }, }, { - Pkg: keysPack, - DepPkg: rsaPubFilePack, + Pkg: keysPack, + DepPkg: rsaPubFilePack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "", @@ -638,8 +649,9 @@ var ( CdxDeps = []assembler.IsDependencyIngest{ { - Pkg: cdxTopLevelPack, - DepPkg: cdxBasefilesPack, + Pkg: cdxTopLevelPack, + DepPkg: cdxBasefilesPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "11.1+deb11u5", @@ -647,8 +659,9 @@ var ( }, }, { - Pkg: cdxTopLevelPack, - DepPkg: cdxNetbasePack, + Pkg: cdxTopLevelPack, + DepPkg: cdxNetbasePack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "6.3", @@ -656,8 +669,9 @@ var ( }, }, { - Pkg: cdxTopLevelPack, - DepPkg: cdxTzdataPack, + Pkg: cdxTopLevelPack, + DepPkg: cdxTzdataPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "2021a-1+deb11u6", @@ -691,8 +705,9 @@ var ( CdxQuarkusDeps = []assembler.IsDependencyIngest{ { - Pkg: cdxTopQuarkusPack, - DepPkg: cdxResteasyPack, + Pkg: cdxTopQuarkusPack, + DepPkg: cdxResteasyPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "2.13.4.Final", @@ -700,8 +715,9 @@ var ( }, }, { - Pkg: cdxTopQuarkusPack, - DepPkg: cdxReactiveCommonPack, + Pkg: cdxTopQuarkusPack, + DepPkg: cdxReactiveCommonPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "2.13.4.Final", @@ -709,8 +725,9 @@ var ( }, }, { - Pkg: cdxResteasyPack, - DepPkg: cdxReactiveCommonPack, + Pkg: cdxResteasyPack, + DepPkg: cdxReactiveCommonPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "2.13.4.Final", @@ -777,8 +794,9 @@ var ( CdxNpmDeps = []assembler.IsDependencyIngest{ { - Pkg: cdxWebAppPackage, - DepPkg: cdxBootstrapPackage, + Pkg: cdxWebAppPackage, + DepPkg: cdxBootstrapPackage, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, VersionRange: "4.0.0-beta.2", @@ -1970,8 +1988,9 @@ For the update to take effect, all services linked to the OpenSSL library must b }, IsDependency: []assembler.IsDependencyIngest{ { - Pkg: topLevelPack, - DepPkg: baselayoutPack, + Pkg: topLevelPack, + DepPkg: baselayoutPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &generated.IsDependencyInputSpec{ DependencyType: generated.DependencyTypeUnknown, VersionRange: "3.2.0-r22", @@ -1979,8 +1998,9 @@ For the update to take effect, all services linked to the OpenSSL library must b }, }, { - Pkg: topLevelPack, - DepPkg: baselayoutdataPack, + Pkg: topLevelPack, + DepPkg: baselayoutdataPack, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}, IsDependency: &generated.IsDependencyInputSpec{ DependencyType: generated.DependencyTypeUnknown, VersionRange: "3.2.0-r22", diff --git a/pkg/assembler/assembler.go b/pkg/assembler/assembler.go index 7132ae8c39..44260ecd91 100644 --- a/pkg/assembler/assembler.go +++ b/pkg/assembler/assembler.go @@ -53,9 +53,10 @@ type CertifyScorecardIngest struct { } type IsDependencyIngest struct { - Pkg *generated.PkgInputSpec `json:"pkg,omitempty"` - DepPkg *generated.PkgInputSpec `json:"depPkg,omitempty"` - IsDependency *generated.IsDependencyInputSpec `json:"isDependency,omitempty"` + Pkg *generated.PkgInputSpec `json:"pkg,omitempty"` + DepPkg *generated.PkgInputSpec `json:"depPkg,omitempty"` + DepPkgMatchFlag generated.MatchFlags `json:"depPkgMatchFlag,omitempty"` + IsDependency *generated.IsDependencyInputSpec `json:"isDependency,omitempty"` } type IsOccurrenceIngest struct { diff --git a/pkg/assembler/backends/arangodb/backend.go b/pkg/assembler/backends/arangodb/backend.go index 88b37fd4a3..f5b379fd0a 100644 --- a/pkg/assembler/backends/arangodb/backend.go +++ b/pkg/assembler/backends/arangodb/backend.go @@ -74,9 +74,10 @@ const ( // isDependency collections - isDependencyDepPkgEdgesStr string = "isDependencyDepPkgEdges" - isDependencySubjectPkgEdgesStr string = "isDependencySubjectPkgEdges" - isDependenciesStr string = "isDependencies" + isDependencyDepPkgVersionEdgesStr string = "isDependencyDepPkgVersionEdges" + isDependencyDepPkgNameEdgesStr string = "isDependencyDepPkgNameEdges" + isDependencySubjectPkgEdgesStr string = "isDependencySubjectPkgEdges" + isDependenciesStr string = "isDependencies" //isOccurrences collections @@ -244,16 +245,21 @@ func GetBackend(ctx context.Context, args backends.BackendArgs) (backends.Backen vulnHasVulnerabilityID.To = []string{vulnerabilitiesStr} // setup isDependency collections - var isDependencyDepPkgEdges driver.EdgeDefinition - isDependencyDepPkgEdges.Collection = isDependencyDepPkgEdgesStr - isDependencyDepPkgEdges.From = []string{isDependenciesStr} - isDependencyDepPkgEdges.To = []string{pkgNamesStr} - var isDependencySubjectPkgEdges driver.EdgeDefinition isDependencySubjectPkgEdges.Collection = isDependencySubjectPkgEdgesStr isDependencySubjectPkgEdges.From = []string{pkgVersionsStr} isDependencySubjectPkgEdges.To = []string{isDependenciesStr} + var isDependencyDepPkgVersionEdges driver.EdgeDefinition + isDependencyDepPkgVersionEdges.Collection = isDependencyDepPkgVersionEdgesStr + isDependencyDepPkgVersionEdges.From = []string{isDependenciesStr} + isDependencyDepPkgVersionEdges.To = []string{pkgVersionsStr} + + var isDependencyDepPkgNameEdges driver.EdgeDefinition + isDependencyDepPkgNameEdges.Collection = isDependencyDepPkgNameEdgesStr + isDependencyDepPkgNameEdges.From = []string{isDependenciesStr} + isDependencyDepPkgNameEdges.To = []string{pkgNamesStr} + // setup isOccurrence collections var isOccurrenceArtEdges driver.EdgeDefinition isOccurrenceArtEdges.Collection = isOccurrenceArtEdgesStr @@ -371,7 +377,7 @@ func GetBackend(ctx context.Context, args backends.BackendArgs) (backends.Backen // A graph can contain additional vertex collections, defined in the set of orphan collections var options driver.CreateGraphOptions options.EdgeDefinitions = []driver.EdgeDefinition{pkgHasNamespace, pkgHasName, - pkgHasVersion, srcHasNamespace, srcHasName, vulnHasVulnerabilityID, isDependencyDepPkgEdges, isDependencySubjectPkgEdges, + pkgHasVersion, srcHasNamespace, srcHasName, vulnHasVulnerabilityID, isDependencyDepPkgVersionEdges, isDependencyDepPkgNameEdges, isDependencySubjectPkgEdges, isOccurrenceArtEdges, isOccurrenceSubjectPkgEdges, isOccurrenceSubjectSrcEdges, hasSLSASubjectArtEdges, hasSLSABuiltByEdges, hasSLSABuiltFromEdges, hashEqualArtEdges, hashEqualSubjectArtEdges, hasSBOMPkgEdges, hasSBOMArtEdges, certifyVulnPkgEdges, certifyVulnEdges, certifyScorecardSrcEdges, certifyBadPkgVersionEdges, certifyBadPkgNameEdges, @@ -642,6 +648,14 @@ func (aqb *arangoQueryBuilder) filter(counterName string, fieldName string, cond return newArangoQueryFilter(aqb) } +func (aqb *arangoQueryBuilder) filterLength(counterName string, fieldName string, condition string, value int) *arangoQueryFilter { + aqb.query.WriteString(" ") + + aqb.query.WriteString(fmt.Sprintf("FILTER LENGTH(%s.%s) %s %d", counterName, fieldName, condition, value)) + + return newArangoQueryFilter(aqb) +} + func (aqb *arangoQueryBuilder) string() string { return aqb.query.String() } diff --git a/pkg/assembler/backends/arangodb/certifyBad.go b/pkg/assembler/backends/arangodb/certifyBad.go index d3ad7f5124..e3f52647b1 100644 --- a/pkg/assembler/backends/arangodb/certifyBad.go +++ b/pkg/assembler/backends/arangodb/certifyBad.go @@ -37,7 +37,7 @@ func (c *arangoClient) CertifyBad(ctx context.Context, certifyBadSpec *model.Cer arangoQueryBuilder.forOutBound(certifyBadPkgVersionEdgesStr, "certifyBad", "pVersion") setCertifyBadMatchValues(arangoQueryBuilder, certifyBadSpec, values) - pkgVersionCertifyBads, err := getPkgVersionCertifyBadForQuery(ctx, c, arangoQueryBuilder, values) + pkgVersionCertifyBads, err := getPkgCertifyBadForQuery(ctx, c, arangoQueryBuilder, values, true) if err != nil { return nil, fmt.Errorf("failed to retrieve package version certifyBad with error: %w", err) } @@ -49,7 +49,7 @@ func (c *arangoClient) CertifyBad(ctx context.Context, certifyBadSpec *model.Cer arangoQueryBuilder.forOutBound(certifyBadPkgNameEdgesStr, "certifyBad", "pName") setCertifyBadMatchValues(arangoQueryBuilder, certifyBadSpec, values) - pkgNameCertifyBads, err := getPkgNameCertifyBadForQuery(ctx, c, arangoQueryBuilder, values) + pkgNameCertifyBads, err := getPkgCertifyBadForQuery(ctx, c, arangoQueryBuilder, values, false) if err != nil { return nil, fmt.Errorf("failed to retrieve package name certifyBad with error: %w", err) } @@ -95,7 +95,7 @@ func (c *arangoClient) CertifyBad(ctx context.Context, certifyBadSpec *model.Cer arangoQueryBuilder.forInBound(pkgHasNameStr, "pNs", "pName") arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "pType", "pNs") - pkgVersionCertifyBads, err := getPkgVersionCertifyBadForQuery(ctx, c, arangoQueryBuilder, values) + pkgVersionCertifyBads, err := getPkgCertifyBadForQuery(ctx, c, arangoQueryBuilder, values, true) if err != nil { return nil, fmt.Errorf("failed to retrieve package version certifyBad with error: %w", err) } @@ -108,7 +108,7 @@ func (c *arangoClient) CertifyBad(ctx context.Context, certifyBadSpec *model.Cer arangoQueryBuilder.forInBound(pkgHasNameStr, "pNs", "pName") arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "pType", "pNs") - pkgNameCertifyBads, err := getPkgNameCertifyBadForQuery(ctx, c, arangoQueryBuilder, values) + pkgNameCertifyBads, err := getPkgCertifyBadForQuery(ctx, c, arangoQueryBuilder, values, false) if err != nil { return nil, fmt.Errorf("failed to retrieve package name certifyBad with error: %w", err) } @@ -197,54 +197,44 @@ func getArtCertifyBadForQuery(ctx context.Context, c *arangoClient, arangoQueryB return getArtifactCertifyBad(ctx, cursor) } -func getPkgNameCertifyBadForQuery(ctx context.Context, c *arangoClient, arangoQueryBuilder *arangoQueryBuilder, values map[string]any) ([]*model.CertifyBad, error) { - arangoQueryBuilder.query.WriteString("\n") - arangoQueryBuilder.query.WriteString(`RETURN { - 'pkgName': { - 'type_id': pType._id, - 'type': pType.type, - 'namespace_id': pNs._id, - 'namespace': pNs.namespace, - 'name_id': pName._id, - 'name': pName.name - }, - 'certifyBad_id': certifyBad._id, - 'justification': certifyBad.justification, - 'collector': certifyBad.collector, - 'origin': certifyBad.origin - }`) - - fmt.Println(arangoQueryBuilder.string()) - - cursor, err := executeQueryWithRetry(ctx, c.db, arangoQueryBuilder.string(), values, "CertifyBad") - if err != nil { - return nil, fmt.Errorf("failed to query for CertifyBad: %w", err) +func getPkgCertifyBadForQuery(ctx context.Context, c *arangoClient, arangoQueryBuilder *arangoQueryBuilder, values map[string]any, includeDepPkgVersion bool) ([]*model.CertifyBad, error) { + if includeDepPkgVersion { + arangoQueryBuilder.query.WriteString("\n") + arangoQueryBuilder.query.WriteString(`RETURN { + 'pkgVersion': { + 'type_id': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list + }, + 'certifyBad_id': certifyBad._id, + 'justification': certifyBad.justification, + 'collector': certifyBad.collector, + 'origin': certifyBad.origin + }`) + } else { + arangoQueryBuilder.query.WriteString("\n") + arangoQueryBuilder.query.WriteString(`RETURN { + 'pkgVersion': { + 'type_id': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name + }, + 'certifyBad_id': certifyBad._id, + 'justification': certifyBad.justification, + 'collector': certifyBad.collector, + 'origin': certifyBad.origin + }`) } - defer cursor.Close() - - return getPkgNameCertifyBad(ctx, cursor) -} - -func getPkgVersionCertifyBadForQuery(ctx context.Context, c *arangoClient, arangoQueryBuilder *arangoQueryBuilder, values map[string]any) ([]*model.CertifyBad, error) { - arangoQueryBuilder.query.WriteString("\n") - arangoQueryBuilder.query.WriteString(`RETURN { - 'pkgVersion': { - 'type_id': pType._id, - 'type': pType.type, - 'namespace_id': pNs._id, - 'namespace': pNs.namespace, - 'name_id': pName._id, - 'name': pName.name, - 'version_id': pVersion._id, - 'version': pVersion.version, - 'subpath': pVersion.subpath, - 'qualifier_list': pVersion.qualifier_list - }, - 'certifyBad_id': certifyBad._id, - 'justification': certifyBad.justification, - 'collector': certifyBad.collector, - 'origin': certifyBad.origin - }`) fmt.Println(arangoQueryBuilder.string()) @@ -254,7 +244,7 @@ func getPkgVersionCertifyBadForQuery(ctx context.Context, c *arangoClient, arang } defer cursor.Close() - return getPkgVersionCertifyBad(ctx, cursor) + return getPkgCertifyBad(ctx, cursor) } func setCertifyBadMatchValues(arangoQueryBuilder *arangoQueryBuilder, certifyBadSpec *model.CertifyBadSpec, queryValues map[string]any) { @@ -366,7 +356,7 @@ func (c *arangoClient) IngestCertifyBad(ctx context.Context, subject model.Packa } defer cursor.Close() - certifyBadList, err := getPkgVersionCertifyBad(ctx, cursor) + certifyBadList, err := getPkgCertifyBad(ctx, cursor) if err != nil { return nil, fmt.Errorf("failed to get certifyBads from arango cursor: %w", err) } @@ -409,7 +399,7 @@ func (c *arangoClient) IngestCertifyBad(ctx context.Context, subject model.Packa ) RETURN { - 'pkgName': { + 'pkgVersion': { 'type_id': firstPkg.typeID, 'type': firstPkg.type, 'namespace_id': firstPkg.namespace_id, @@ -429,7 +419,7 @@ func (c *arangoClient) IngestCertifyBad(ctx context.Context, subject model.Packa } defer cursor.Close() - certifyBadList, err := getPkgNameCertifyBad(ctx, cursor) + certifyBadList, err := getPkgCertifyBad(ctx, cursor) if err != nil { return nil, fmt.Errorf("failed to get certifyBads from arango cursor: %w", err) } @@ -654,7 +644,7 @@ func (c *arangoClient) IngestCertifyBads(ctx context.Context, subjects model.Pac } defer cursor.Close() - certifyBadList, err := getPkgVersionCertifyBad(ctx, cursor) + certifyBadList, err := getPkgCertifyBad(ctx, cursor) if err != nil { return nil, fmt.Errorf("failed to get certifyBads from arango cursor: %w", err) } @@ -693,7 +683,7 @@ func (c *arangoClient) IngestCertifyBads(ctx context.Context, subjects model.Pac ) RETURN { - 'pkgName': { + 'pkgVersion': { 'type_id': firstPkg.typeID, 'type': firstPkg.type, 'namespace_id': firstPkg.namespace_id, @@ -715,7 +705,7 @@ func (c *arangoClient) IngestCertifyBads(ctx context.Context, subjects model.Pac } defer cursor.Close() - certifyBadList, err := getPkgNameCertifyBad(ctx, cursor) + certifyBadList, err := getPkgCertifyBad(ctx, cursor) if err != nil { return nil, fmt.Errorf("failed to get certifyBads from arango cursor: %w", err) } @@ -899,48 +889,7 @@ func (c *arangoClient) IngestCertifyBads(ctx context.Context, subjects model.Pac } } -func getPkgNameCertifyBad(ctx context.Context, cursor driver.Cursor) ([]*model.CertifyBad, error) { - type collectedData struct { - PkgName *dbPkgName `json:"pkgName"` - CertifyBadID string `json:"certifyBad_id"` - Justification string `json:"justification"` - Collector string `json:"collector"` - Origin string `json:"origin"` - } - - var createdValues []collectedData - for { - var doc collectedData - _, err := cursor.ReadDocument(ctx, &doc) - if err != nil { - if driver.IsNoMoreDocuments(err) { - break - } else { - return nil, fmt.Errorf("failed to package name certifyBad from cursor: %w", err) - } - } else { - createdValues = append(createdValues, doc) - } - } - - var certifyBadList []*model.CertifyBad - for _, createdValue := range createdValues { - pkg := generateModelPackage(createdValue.PkgName.TypeID, createdValue.PkgName.PkgType, createdValue.PkgName.NamespaceID, createdValue.PkgName.Namespace, createdValue.PkgName.NameID, - createdValue.PkgName.Name, nil, nil, nil, nil) - - certifyBad := &model.CertifyBad{ - ID: createdValue.CertifyBadID, - Subject: pkg, - Justification: createdValue.Justification, - Origin: createdValue.Collector, - Collector: createdValue.Origin, - } - certifyBadList = append(certifyBadList, certifyBad) - } - return certifyBadList, nil -} - -func getPkgVersionCertifyBad(ctx context.Context, cursor driver.Cursor) ([]*model.CertifyBad, error) { +func getPkgCertifyBad(ctx context.Context, cursor driver.Cursor) ([]*model.CertifyBad, error) { type collectedData struct { PkgVersion *dbPkgVersion `json:"pkgVersion"` CertifyBadID string `json:"certifyBad_id"` @@ -957,7 +906,7 @@ func getPkgVersionCertifyBad(ctx context.Context, cursor driver.Cursor) ([]*mode if driver.IsNoMoreDocuments(err) { break } else { - return nil, fmt.Errorf("failed to package version certifyBad from cursor: %w", err) + return nil, fmt.Errorf("failed to package certifyBad from cursor: %w", err) } } else { createdValues = append(createdValues, doc) @@ -967,7 +916,7 @@ func getPkgVersionCertifyBad(ctx context.Context, cursor driver.Cursor) ([]*mode var certifyBadList []*model.CertifyBad for _, createdValue := range createdValues { pkg := generateModelPackage(createdValue.PkgVersion.TypeID, createdValue.PkgVersion.PkgType, createdValue.PkgVersion.NamespaceID, createdValue.PkgVersion.Namespace, createdValue.PkgVersion.NameID, - createdValue.PkgVersion.Name, &createdValue.PkgVersion.VersionID, &createdValue.PkgVersion.Version, &createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) + createdValue.PkgVersion.Name, createdValue.PkgVersion.VersionID, createdValue.PkgVersion.Version, createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) certifyBad := &model.CertifyBad{ ID: createdValue.CertifyBadID, diff --git a/pkg/assembler/backends/arangodb/certifyGood.go b/pkg/assembler/backends/arangodb/certifyGood.go index 97684c2c91..8d5eb0db2d 100644 --- a/pkg/assembler/backends/arangodb/certifyGood.go +++ b/pkg/assembler/backends/arangodb/certifyGood.go @@ -36,7 +36,7 @@ func (c *arangoClient) CertifyGood(ctx context.Context, certifyGoodSpec *model.C arangoQueryBuilder.forOutBound(certifyGoodPkgVersionEdgesStr, "certifyGood", "pVersion") setCertifyGoodMatchValues(arangoQueryBuilder, certifyGoodSpec, values) - pkgVersionCertifyGoods, err := getPkgVersionCertifyGoodForQuery(ctx, c, arangoQueryBuilder, values) + pkgVersionCertifyGoods, err := getPkgCertifyGoodForQuery(ctx, c, arangoQueryBuilder, values, true) if err != nil { return nil, fmt.Errorf("failed to retrieve package version certifyGood with error: %w", err) } @@ -48,7 +48,7 @@ func (c *arangoClient) CertifyGood(ctx context.Context, certifyGoodSpec *model.C arangoQueryBuilder.forOutBound(certifyGoodPkgNameEdgesStr, "certifyGood", "pName") setCertifyGoodMatchValues(arangoQueryBuilder, certifyGoodSpec, values) - pkgNameCertifyGoods, err := getPkgNameCertifyGoodForQuery(ctx, c, arangoQueryBuilder, values) + pkgNameCertifyGoods, err := getPkgCertifyGoodForQuery(ctx, c, arangoQueryBuilder, values, false) if err != nil { return nil, fmt.Errorf("failed to retrieve package name certifyGood with error: %w", err) } @@ -94,7 +94,7 @@ func (c *arangoClient) CertifyGood(ctx context.Context, certifyGoodSpec *model.C arangoQueryBuilder.forInBound(pkgHasNameStr, "pNs", "pName") arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "pType", "pNs") - pkgVersionCertifyGoods, err := getPkgVersionCertifyGoodForQuery(ctx, c, arangoQueryBuilder, values) + pkgVersionCertifyGoods, err := getPkgCertifyGoodForQuery(ctx, c, arangoQueryBuilder, values, true) if err != nil { return nil, fmt.Errorf("failed to retrieve package version certifyGood with error: %w", err) } @@ -107,7 +107,7 @@ func (c *arangoClient) CertifyGood(ctx context.Context, certifyGoodSpec *model.C arangoQueryBuilder.forInBound(pkgHasNameStr, "pNs", "pName") arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "pType", "pNs") - pkgNameCertifyGoods, err := getPkgNameCertifyGoodForQuery(ctx, c, arangoQueryBuilder, values) + pkgNameCertifyGoods, err := getPkgCertifyGoodForQuery(ctx, c, arangoQueryBuilder, values, false) if err != nil { return nil, fmt.Errorf("failed to retrieve package name certifyGood with error: %w", err) } @@ -196,64 +196,55 @@ func getArtCertifyGoodForQuery(ctx context.Context, c *arangoClient, arangoQuery return getArtifactCertifyGood(ctx, cursor) } -func getPkgNameCertifyGoodForQuery(ctx context.Context, c *arangoClient, arangoQueryBuilder *arangoQueryBuilder, values map[string]any) ([]*model.CertifyGood, error) { - arangoQueryBuilder.query.WriteString("\n") - arangoQueryBuilder.query.WriteString(`RETURN { - 'pkgName': { - 'type_id': pType._id, - 'type': pType.type, - 'namespace_id': pNs._id, - 'namespace': pNs.namespace, - 'name_id': pName._id, - 'name': pName.name - }, - 'certifyGood_id': certifyGood._id, - 'justification': certifyGood.justification, - 'collector': certifyGood.collector, - 'origin': certifyGood.origin - }`) - - fmt.Println(arangoQueryBuilder.string()) +func getPkgCertifyGoodForQuery(ctx context.Context, c *arangoClient, arangoQueryBuilder *arangoQueryBuilder, values map[string]any, includeDepPkgVersion bool) ([]*model.CertifyGood, error) { + if includeDepPkgVersion { + arangoQueryBuilder.query.WriteString("\n") + arangoQueryBuilder.query.WriteString(`RETURN { + 'pkgVersion': { + 'type_id': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list + }, + 'certifyGood_id': certifyGood._id, + 'justification': certifyGood.justification, + 'collector': certifyGood.collector, + 'origin': certifyGood.origin + }`) - cursor, err := executeQueryWithRetry(ctx, c.db, arangoQueryBuilder.string(), values, "certifyGood") - if err != nil { - return nil, fmt.Errorf("failed to query for certifyGood: %w", err) + } else { + arangoQueryBuilder.query.WriteString("\n") + arangoQueryBuilder.query.WriteString(`RETURN { + 'pkgVersion': { + 'type_id': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name + }, + 'certifyGood_id': certifyGood._id, + 'justification': certifyGood.justification, + 'collector': certifyGood.collector, + 'origin': certifyGood.origin + }`) } - defer cursor.Close() - - return getPkgNameCertifyGood(ctx, cursor) -} - -func getPkgVersionCertifyGoodForQuery(ctx context.Context, c *arangoClient, arangoQueryBuilder *arangoQueryBuilder, values map[string]any) ([]*model.CertifyGood, error) { - arangoQueryBuilder.query.WriteString("\n") - arangoQueryBuilder.query.WriteString(`RETURN { - 'pkgVersion': { - 'type_id': pType._id, - 'type': pType.type, - 'namespace_id': pNs._id, - 'namespace': pNs.namespace, - 'name_id': pName._id, - 'name': pName.name, - 'version_id': pVersion._id, - 'version': pVersion.version, - 'subpath': pVersion.subpath, - 'qualifier_list': pVersion.qualifier_list - }, - 'certifyGood_id': certifyGood._id, - 'justification': certifyGood.justification, - 'collector': certifyGood.collector, - 'origin': certifyGood.origin - }`) fmt.Println(arangoQueryBuilder.string()) - cursor, err := executeQueryWithRetry(ctx, c.db, arangoQueryBuilder.string(), values, "CertifyGood") + cursor, err := executeQueryWithRetry(ctx, c.db, arangoQueryBuilder.string(), values, "certifyGood") if err != nil { - return nil, fmt.Errorf("failed to query for CertifyGood: %w", err) + return nil, fmt.Errorf("failed to query for certifyGood: %w", err) } defer cursor.Close() - return getPkgVersionCertifyGood(ctx, cursor) + return getPkgCertifyGood(ctx, cursor) } func setCertifyGoodMatchValues(arangoQueryBuilder *arangoQueryBuilder, certifyGoodSpec *model.CertifyGoodSpec, queryValues map[string]any) { @@ -365,7 +356,7 @@ func (c *arangoClient) IngestCertifyGood(ctx context.Context, subject model.Pack } defer cursor.Close() - certifyGoodList, err := getPkgVersionCertifyGood(ctx, cursor) + certifyGoodList, err := getPkgCertifyGood(ctx, cursor) if err != nil { return nil, fmt.Errorf("failed to get certifyGoods from arango cursor: %w", err) } @@ -408,7 +399,7 @@ func (c *arangoClient) IngestCertifyGood(ctx context.Context, subject model.Pack ) RETURN { - 'pkgName': { + 'pkgVersion': { 'type_id': firstPkg.typeID, 'type': firstPkg.type, 'namespace_id': firstPkg.namespace_id, @@ -428,7 +419,7 @@ func (c *arangoClient) IngestCertifyGood(ctx context.Context, subject model.Pack } defer cursor.Close() - certifyGoodList, err := getPkgNameCertifyGood(ctx, cursor) + certifyGoodList, err := getPkgCertifyGood(ctx, cursor) if err != nil { return nil, fmt.Errorf("failed to get certifyGoods from arango cursor: %w", err) } @@ -653,7 +644,7 @@ func (c *arangoClient) IngestCertifyGoods(ctx context.Context, subjects model.Pa } defer cursor.Close() - certifyGoodList, err := getPkgVersionCertifyGood(ctx, cursor) + certifyGoodList, err := getPkgCertifyGood(ctx, cursor) if err != nil { return nil, fmt.Errorf("failed to get certifyGoods from arango cursor: %w", err) } @@ -692,7 +683,7 @@ func (c *arangoClient) IngestCertifyGoods(ctx context.Context, subjects model.Pa ) RETURN { - 'pkgName': { + 'pkgVersion': { 'type_id': firstPkg.typeID, 'type': firstPkg.type, 'namespace_id': firstPkg.namespace_id, @@ -714,7 +705,7 @@ func (c *arangoClient) IngestCertifyGoods(ctx context.Context, subjects model.Pa } defer cursor.Close() - certifyGoodList, err := getPkgNameCertifyGood(ctx, cursor) + certifyGoodList, err := getPkgCertifyGood(ctx, cursor) if err != nil { return nil, fmt.Errorf("failed to get certifyGoods from arango cursor: %w", err) } @@ -898,48 +889,7 @@ func (c *arangoClient) IngestCertifyGoods(ctx context.Context, subjects model.Pa } } -func getPkgNameCertifyGood(ctx context.Context, cursor driver.Cursor) ([]*model.CertifyGood, error) { - type collectedData struct { - PkgName *dbPkgName `json:"pkgName"` - CertifyGoodID string `json:"certifyGood_id"` - Justification string `json:"justification"` - Collector string `json:"collector"` - Origin string `json:"origin"` - } - - var createdValues []collectedData - for { - var doc collectedData - _, err := cursor.ReadDocument(ctx, &doc) - if err != nil { - if driver.IsNoMoreDocuments(err) { - break - } else { - return nil, fmt.Errorf("failed to package name certifyGood from cursor: %w", err) - } - } else { - createdValues = append(createdValues, doc) - } - } - - var certifyGoodList []*model.CertifyGood - for _, createdValue := range createdValues { - pkg := generateModelPackage(createdValue.PkgName.TypeID, createdValue.PkgName.PkgType, createdValue.PkgName.NamespaceID, createdValue.PkgName.Namespace, createdValue.PkgName.NameID, - createdValue.PkgName.Name, nil, nil, nil, nil) - - certifyGood := &model.CertifyGood{ - ID: createdValue.CertifyGoodID, - Subject: pkg, - Justification: createdValue.Justification, - Origin: createdValue.Collector, - Collector: createdValue.Origin, - } - certifyGoodList = append(certifyGoodList, certifyGood) - } - return certifyGoodList, nil -} - -func getPkgVersionCertifyGood(ctx context.Context, cursor driver.Cursor) ([]*model.CertifyGood, error) { +func getPkgCertifyGood(ctx context.Context, cursor driver.Cursor) ([]*model.CertifyGood, error) { type collectedData struct { PkgVersion *dbPkgVersion `json:"pkgVersion"` CertifyGoodID string `json:"certifyGood_id"` @@ -956,7 +906,7 @@ func getPkgVersionCertifyGood(ctx context.Context, cursor driver.Cursor) ([]*mod if driver.IsNoMoreDocuments(err) { break } else { - return nil, fmt.Errorf("failed to package version certifyGood from cursor: %w", err) + return nil, fmt.Errorf("failed to package certifyGood from cursor: %w", err) } } else { createdValues = append(createdValues, doc) @@ -966,7 +916,7 @@ func getPkgVersionCertifyGood(ctx context.Context, cursor driver.Cursor) ([]*mod var certifyGoodList []*model.CertifyGood for _, createdValue := range createdValues { pkg := generateModelPackage(createdValue.PkgVersion.TypeID, createdValue.PkgVersion.PkgType, createdValue.PkgVersion.NamespaceID, createdValue.PkgVersion.Namespace, createdValue.PkgVersion.NameID, - createdValue.PkgVersion.Name, &createdValue.PkgVersion.VersionID, &createdValue.PkgVersion.Version, &createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) + createdValue.PkgVersion.Name, createdValue.PkgVersion.VersionID, createdValue.PkgVersion.Version, createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) certifyGood := &model.CertifyGood{ ID: createdValue.CertifyGoodID, diff --git a/pkg/assembler/backends/arangodb/certifyVuln.go b/pkg/assembler/backends/arangodb/certifyVuln.go index bcdff42612..1ab747a163 100644 --- a/pkg/assembler/backends/arangodb/certifyVuln.go +++ b/pkg/assembler/backends/arangodb/certifyVuln.go @@ -437,7 +437,7 @@ func geCertifyVuln(ctx context.Context, cursor driver.Cursor) ([]*model.CertifyV var certifyVulnList []*model.CertifyVuln for _, createdValue := range createdValues { pkg := generateModelPackage(createdValue.PkgVersion.TypeID, createdValue.PkgVersion.PkgType, createdValue.PkgVersion.NamespaceID, createdValue.PkgVersion.Namespace, createdValue.PkgVersion.NameID, - createdValue.PkgVersion.Name, &createdValue.PkgVersion.VersionID, &createdValue.PkgVersion.Version, &createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) + createdValue.PkgVersion.Name, createdValue.PkgVersion.VersionID, createdValue.PkgVersion.Version, createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) vuln := &model.Vulnerability{ ID: createdValue.Vulnerability.VulnID, diff --git a/pkg/assembler/backends/arangodb/hasSBOM.go b/pkg/assembler/backends/arangodb/hasSBOM.go index eabeb521b7..f2e6ffe957 100644 --- a/pkg/assembler/backends/arangodb/hasSBOM.go +++ b/pkg/assembler/backends/arangodb/hasSBOM.go @@ -548,7 +548,7 @@ func getPkgHasSBOM(ctx context.Context, cursor driver.Cursor) ([]*model.HasSbom, var hasSBOMList []*model.HasSbom for _, createdValue := range createdValues { pkg := generateModelPackage(createdValue.PkgVersion.TypeID, createdValue.PkgVersion.PkgType, createdValue.PkgVersion.NamespaceID, createdValue.PkgVersion.Namespace, createdValue.PkgVersion.NameID, - createdValue.PkgVersion.Name, &createdValue.PkgVersion.VersionID, &createdValue.PkgVersion.Version, &createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) + createdValue.PkgVersion.Name, createdValue.PkgVersion.VersionID, createdValue.PkgVersion.Version, createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) hasSBOM := &model.HasSbom{ ID: createdValue.HasSBOMId, diff --git a/pkg/assembler/backends/arangodb/isDependency.go b/pkg/assembler/backends/arangodb/isDependency.go index 34c4f9a6f0..aeec7e04c1 100644 --- a/pkg/assembler/backends/arangodb/isDependency.go +++ b/pkg/assembler/backends/arangodb/isDependency.go @@ -38,56 +38,138 @@ func (c *arangoClient) IsDependency(ctx context.Context, isDependencySpec *model var arangoQueryBuilder *arangoQueryBuilder if isDependencySpec.Package != nil { + var combinedIsDependency []*model.IsDependency values := map[string]any{} - arangoQueryBuilder := setPkgVersionMatchValues(isDependencySpec.Package, values) + + // dep pkgVersion isDependency + arangoQueryBuilder = setPkgVersionMatchValues(isDependencySpec.Package, values) + arangoQueryBuilder.forOutBound(isDependencySubjectPkgEdgesStr, "isDependency", "pVersion") + setIsDependencyMatchValues(arangoQueryBuilder, isDependencySpec, values, true) + + depPkgVersionIsDependency, err := getDependencyForQuery(ctx, c, arangoQueryBuilder, values, true) + if err != nil { + return nil, fmt.Errorf("failed to retrieve dependent package version isDependency with error: %w", err) + } + + combinedIsDependency = append(combinedIsDependency, depPkgVersionIsDependency...) + + // dep pkgName isDependency + arangoQueryBuilder = setPkgVersionMatchValues(isDependencySpec.Package, values) arangoQueryBuilder.forOutBound(isDependencySubjectPkgEdgesStr, "isDependency", "pVersion") - setIsDependencyMatchValues(arangoQueryBuilder, isDependencySpec, values) + setIsDependencyMatchValues(arangoQueryBuilder, isDependencySpec, values, false) - return getDependencyForQuery(ctx, c, arangoQueryBuilder, values) + depPkgNameIsDependency, err := getDependencyForQuery(ctx, c, arangoQueryBuilder, values, false) + if err != nil { + return nil, fmt.Errorf("failed to retrieve dependent package name isDependency with error: %w", err) + } + + combinedIsDependency = append(combinedIsDependency, depPkgNameIsDependency...) + + return combinedIsDependency, nil } else { + var combinedIsDependency []*model.IsDependency values := map[string]any{} - // get packages + // get pkgVersion isDependency arangoQueryBuilder = newForQuery(isDependenciesStr, "isDependency") - setIsDependencyMatchValues(arangoQueryBuilder, isDependencySpec, values) arangoQueryBuilder.forInBound(isDependencySubjectPkgEdgesStr, "pVersion", "isDependency") arangoQueryBuilder.forInBound(pkgHasVersionStr, "pName", "pVersion") arangoQueryBuilder.forInBound(pkgHasNameStr, "pNs", "pName") arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "pType", "pNs") + setIsDependencyMatchValues(arangoQueryBuilder, isDependencySpec, values, true) + + depPkgVersionIsDependency, err := getDependencyForQuery(ctx, c, arangoQueryBuilder, values, true) + if err != nil { + return nil, fmt.Errorf("failed to retrieve dependent package version isDependency with error: %w", err) + } - return getDependencyForQuery(ctx, c, arangoQueryBuilder, values) + combinedIsDependency = append(combinedIsDependency, depPkgVersionIsDependency...) + + // dep pkgName isDependency + + arangoQueryBuilder = newForQuery(isDependenciesStr, "isDependency") + arangoQueryBuilder.forInBound(isDependencySubjectPkgEdgesStr, "pVersion", "isDependency") + arangoQueryBuilder.forInBound(pkgHasVersionStr, "pName", "pVersion") + arangoQueryBuilder.forInBound(pkgHasNameStr, "pNs", "pName") + arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "pType", "pNs") + setIsDependencyMatchValues(arangoQueryBuilder, isDependencySpec, values, false) + + depPkgNameIsDependency, err := getDependencyForQuery(ctx, c, arangoQueryBuilder, values, false) + if err != nil { + return nil, fmt.Errorf("failed to retrieve dependent package name isDependency with error: %w", err) + } + + combinedIsDependency = append(combinedIsDependency, depPkgNameIsDependency...) + + return combinedIsDependency, nil } } -func getDependencyForQuery(ctx context.Context, c *arangoClient, arangoQueryBuilder *arangoQueryBuilder, values map[string]any) ([]*model.IsDependency, error) { - arangoQueryBuilder.query.WriteString("\n") - arangoQueryBuilder.query.WriteString(`RETURN { - 'pkgVersion': { - 'type_id': pType._id, - 'type': pType.type, - 'namespace_id': pNs._id, - 'namespace': pNs.namespace, - 'name_id': pName._id, - 'name': pName.name, - 'version_id': pVersion._id, - 'version': pVersion.version, - 'subpath': pVersion.subpath, - 'qualifier_list': pVersion.qualifier_list - }, - 'depPkg': { - 'type_id': depType._id, - 'type': depType.type, - 'namespace_id': depNamespace._id, - 'namespace': depNamespace.namespace, - 'name_id': depName._id, - 'name': depName.name - }, - 'isDependency_id': isDependency._id, - 'versionRange': isDependency.versionRange, - 'dependencyType': isDependency.dependencyType, - 'justification': isDependency.justification, - 'collector': isDependency.collector, - 'origin': isDependency.origin - }`) +func getDependencyForQuery(ctx context.Context, c *arangoClient, arangoQueryBuilder *arangoQueryBuilder, values map[string]any, includeDepPkgVersion bool) ([]*model.IsDependency, error) { + if includeDepPkgVersion { + arangoQueryBuilder.query.WriteString("\n") + arangoQueryBuilder.query.WriteString(`RETURN { + 'pkgVersion': { + 'type_id': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list + }, + 'depPkg': { + 'type_id': depType._id, + 'type': depType.type, + 'namespace_id': depNamespace._id, + 'namespace': depNamespace.namespace, + 'name_id': depName._id, + 'name': depName.name, + 'version_id': depVersion._id, + 'version': depVersion.version, + 'subpath': depVersion.subpath, + 'qualifier_list': depVersion.qualifier_list + }, + 'isDependency_id': isDependency._id, + 'versionRange': isDependency.versionRange, + 'dependencyType': isDependency.dependencyType, + 'justification': isDependency.justification, + 'collector': isDependency.collector, + 'origin': isDependency.origin + }`) + } else { + arangoQueryBuilder.query.WriteString("\n") + arangoQueryBuilder.query.WriteString(`RETURN { + 'pkgVersion': { + 'type_id': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list + }, + 'depPkg': { + 'type_id': depType._id, + 'type': depType.type, + 'namespace_id': depNamespace._id, + 'namespace': depNamespace.namespace, + 'name_id': depName._id, + 'name': depName.name + }, + 'isDependency_id': isDependency._id, + 'versionRange': isDependency.versionRange, + 'dependencyType': isDependency.dependencyType, + 'justification': isDependency.justification, + 'collector': isDependency.collector, + 'origin': isDependency.origin + }`) + } fmt.Println(arangoQueryBuilder.string()) @@ -100,7 +182,7 @@ func getDependencyForQuery(ctx context.Context, c *arangoClient, arangoQueryBuil return getIsDependency(ctx, cursor) } -func setIsDependencyMatchValues(arangoQueryBuilder *arangoQueryBuilder, isDependencySpec *model.IsDependencySpec, queryValues map[string]any) { +func setIsDependencyMatchValues(arangoQueryBuilder *arangoQueryBuilder, isDependencySpec *model.IsDependencySpec, queryValues map[string]any, queryDepPkgVersion bool) { if isDependencySpec.ID != nil { arangoQueryBuilder.filter("isDependency", "_id", "==", "@id") queryValues["id"] = *isDependencySpec.ID @@ -122,38 +204,92 @@ func setIsDependencyMatchValues(arangoQueryBuilder *arangoQueryBuilder, isDepend queryValues[collector] = isDependencySpec.Collector } if isDependencySpec.DependentPackage != nil { - arangoQueryBuilder.forOutBound(isDependencyDepPkgEdgesStr, "depName", "isDependency") - if isDependencySpec.DependentPackage.Name != nil { - arangoQueryBuilder.filter("depName", "name", "==", "@depName") - queryValues["depName"] = *isDependencySpec.DependentPackage.Name - } - arangoQueryBuilder.forInBound(pkgHasNameStr, "depNamespace", "depName") - if isDependencySpec.DependentPackage.Namespace != nil { - arangoQueryBuilder.filter("depNamespace", "namespace", "==", "@depNamespace") - queryValues["depNamespace"] = *isDependencySpec.DependentPackage.Namespace - } - arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "depType", "depNamespace") - if isDependencySpec.DependentPackage.Type != nil { - arangoQueryBuilder.filter("depType", "type", "==", "@depType") - queryValues["depType"] = *isDependencySpec.DependentPackage.Type + if !queryDepPkgVersion { + arangoQueryBuilder.forOutBound(isDependencyDepPkgNameEdgesStr, "depName", "isDependency") + if isDependencySpec.DependentPackage.Name != nil { + arangoQueryBuilder.filter("depName", "name", "==", "@depName") + queryValues["depName"] = *isDependencySpec.DependentPackage.Name + } + arangoQueryBuilder.forInBound(pkgHasNameStr, "depNamespace", "depName") + if isDependencySpec.DependentPackage.Namespace != nil { + arangoQueryBuilder.filter("depNamespace", "namespace", "==", "@depNamespace") + queryValues["depNamespace"] = *isDependencySpec.DependentPackage.Namespace + } + arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "depType", "depNamespace") + if isDependencySpec.DependentPackage.Type != nil { + arangoQueryBuilder.filter("depType", "type", "==", "@depType") + queryValues["depType"] = *isDependencySpec.DependentPackage.Type + } + } else { + arangoQueryBuilder.forOutBound(isDependencyDepPkgVersionEdgesStr, "depVersion", "isDependency") + if isDependencySpec.DependentPackage.Version != nil { + arangoQueryBuilder.filter("depVersion", "version", "==", "@depVersionValue") + queryValues["depVersionValue"] = *isDependencySpec.DependentPackage.Version + } + if isDependencySpec.DependentPackage.Subpath != nil { + arangoQueryBuilder.filter("depVersion", "subpath", "==", "@depSubpath") + queryValues["depSubpath"] = *isDependencySpec.DependentPackage.Subpath + } + if isDependencySpec.DependentPackage.MatchOnlyEmptyQualifiers != nil { + if !*isDependencySpec.DependentPackage.MatchOnlyEmptyQualifiers { + if len(isDependencySpec.DependentPackage.Qualifiers) > 0 { + arangoQueryBuilder.filter("depVersion", "qualifier_list", "==", "@depQualifier") + queryValues["depQualifier"] = getQualifiers(isDependencySpec.DependentPackage.Qualifiers) + } + } else { + arangoQueryBuilder.filterLength("depVersion", "qualifier_list", "==", 0) + } + } else { + if len(isDependencySpec.DependentPackage.Qualifiers) > 0 { + arangoQueryBuilder.filter("depVersion", "qualifier_list", "==", "@depQualifier") + queryValues["depQualifier"] = getQualifiers(isDependencySpec.DependentPackage.Qualifiers) + } + } + arangoQueryBuilder.forInBound(pkgHasVersionStr, "depName", "depVersion") + if isDependencySpec.DependentPackage.Name != nil { + arangoQueryBuilder.filter("depName", "name", "==", "@depName") + queryValues["depName"] = *isDependencySpec.DependentPackage.Name + } + arangoQueryBuilder.forInBound(pkgHasNameStr, "depNamespace", "depName") + if isDependencySpec.DependentPackage.Namespace != nil { + arangoQueryBuilder.filter("depNamespace", "namespace", "==", "@depNamespace") + queryValues["depNamespace"] = *isDependencySpec.DependentPackage.Namespace + } + arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "depType", "depNamespace") + if isDependencySpec.DependentPackage.Type != nil { + arangoQueryBuilder.filter("depType", "type", "==", "@depType") + queryValues["depType"] = *isDependencySpec.DependentPackage.Type + } } } else { - arangoQueryBuilder.forOutBound(isDependencyDepPkgEdgesStr, "depName", "isDependency") - arangoQueryBuilder.forInBound(pkgHasNameStr, "depNamespace", "depName") - arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "depType", "depNamespace") + if !queryDepPkgVersion { + arangoQueryBuilder.forOutBound(isDependencyDepPkgNameEdgesStr, "depName", "isDependency") + arangoQueryBuilder.forInBound(pkgHasNameStr, "depNamespace", "depName") + arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "depType", "depNamespace") + } else { + arangoQueryBuilder.forOutBound(isDependencyDepPkgVersionEdgesStr, "depVersion", "isDependency") + arangoQueryBuilder.forInBound(pkgHasVersionStr, "depName", "depVersion") + arangoQueryBuilder.forInBound(pkgHasNameStr, "depNamespace", "depName") + arangoQueryBuilder.forInBound(pkgHasNamespaceStr, "depType", "depNamespace") + } + } } // Ingest IsDependency -func getDependencyQueryValues(pkg *model.PkgInputSpec, depPkg *model.PkgInputSpec, dependency *model.IsDependencyInputSpec) map[string]any { +func getDependencyQueryValues(pkg *model.PkgInputSpec, depPkg *model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency *model.IsDependencyInputSpec) map[string]any { values := map[string]any{} // add guac keys pkgId := guacPkgId(*pkg) depPkgId := guacPkgId(*depPkg) values["pkgVersionGuacKey"] = pkgId.VersionId - values["secondPkgNameGuacKey"] = depPkgId.NameId + if depPkgMatchType.Pkg == model.PkgMatchTypeAllVersions { + values["secondPkgGuacKey"] = depPkgId.NameId + } else { + values["secondPkgGuacKey"] = depPkgId.VersionId + } // isDependency @@ -166,7 +302,8 @@ func getDependencyQueryValues(pkg *model.PkgInputSpec, depPkg *model.PkgInputSpe return values } -func (c *arangoClient) IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) { +func (c *arangoClient) IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) { + // TODO(LUMJJB): handle pkgmatchtype if len(pkgs) != len(depPkgs) { return nil, fmt.Errorf("uneven packages and dependent packages for ingestion") } else if len(pkgs) != len(dependencies) { @@ -176,7 +313,7 @@ func (c *arangoClient) IngestDependencies(ctx context.Context, pkgs []*model.Pkg var listOfValues []map[string]any for i := range pkgs { - listOfValues = append(listOfValues, getDependencyQueryValues(pkgs[i], depPkgs[i], dependencies[i])) + listOfValues = append(listOfValues, getDependencyQueryValues(pkgs[i], depPkgs[i], depPkgMatchType, dependencies[i])) } var documents []string @@ -201,7 +338,9 @@ func (c *arangoClient) IngestDependencies(ctx context.Context, pkgs []*model.Pkg } sb.WriteString("]") - query := ` + if depPkgMatchType.Pkg == model.PkgMatchTypeAllVersions { + + query := ` LET firstPkg = FIRST( FOR pVersion in pkgVersions @@ -230,7 +369,7 @@ func (c *arangoClient) IngestDependencies(ctx context.Context, pkgs []*model.Pkg LET secondPkg = FIRST( FOR pName in pkgNames - FILTER pName.guacKey == doc.secondPkgNameGuacKey + FILTER pName.guacKey == doc.secondPkgGuacKey FOR pNs in pkgNamespaces FILTER pNs._id == pName._parent FOR pType in pkgTypes @@ -255,7 +394,7 @@ func (c *arangoClient) IngestDependencies(ctx context.Context, pkgs []*model.Pkg ) INSERT { _key: CONCAT("isDependencySubjectPkgEdges", firstPkg.versionDoc._key, isDependency._key), _from: firstPkg.version_id, _to: isDependency._id} INTO isDependencySubjectPkgEdges OPTIONS { overwriteMode: "ignore" } - INSERT { _key: CONCAT("isDependencyDepPkgEdges", isDependency._key, secondPkg.nameDoc._key), _from: isDependency._id, _to: secondPkg.name_id} INTO isDependencyDepPkgEdges OPTIONS { overwriteMode: "ignore" } + INSERT { _key: CONCAT("isDependencyDepPkgNameEdges", isDependency._key, secondPkg.nameDoc._key), _from: isDependency._id, _to: secondPkg.name_id} INTO isDependencyDepPkgNameEdges OPTIONS { overwriteMode: "ignore" } RETURN { 'pkgVersion': { @@ -286,8 +425,107 @@ func (c *arangoClient) IngestDependencies(ctx context.Context, pkgs []*model.Pkg 'origin': isDependency.origin }` - sb.WriteString(query) + sb.WriteString(query) + } else { + query := ` + LET firstPkg = FIRST( + FOR pVersion in pkgVersions + FILTER pVersion.guacKey == doc.pkgVersionGuacKey + FOR pName in pkgNames + FILTER pName._id == pVersion._parent + FOR pNs in pkgNamespaces + FILTER pNs._id == pName._parent + FOR pType in pkgTypes + FILTER pType._id == pNs._parent + + RETURN { + 'typeID': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list, + 'versionDoc': pVersion + } + ) + + LET secondPkg = FIRST( + FOR pVersion in pkgVersions + FILTER pVersion.guacKey == doc.secondPkgGuacKey + FOR pName in pkgNames + FILTER pName._id == pVersion._parent + FOR pNs in pkgNamespaces + FILTER pNs._id == pName._parent + FOR pType in pkgTypes + FILTER pType._id == pNs._parent + + RETURN { + 'typeID': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list, + 'versionDoc': pVersion + } + ) + + LET isDependency = FIRST( + UPSERT { packageID:firstPkg.version_id, depPackageID:secondPkg.version_id, versionRange:doc.versionRange, dependencyType:doc.dependencyType, justification:doc.justification, collector:doc.collector, origin:doc.origin } + INSERT { packageID:firstPkg.version_id, depPackageID:secondPkg.version_id, versionRange:doc.versionRange, dependencyType:doc.dependencyType, justification:doc.justification, collector:doc.collector, origin:doc.origin } + UPDATE {} IN isDependencies + RETURN NEW + ) + + INSERT { _key: CONCAT("isDependencySubjectPkgEdges", firstPkg.versionDoc._key, isDependency._key), _from: firstPkg.version_id, _to: isDependency._id} INTO isDependencySubjectPkgEdges OPTIONS { overwriteMode: "ignore" } + INSERT { _key: CONCAT("isDependencyDepPkgVersionEdges", isDependency._key, secondPkg.versionDoc._key), _from: isDependency._id, _to: secondPkg.version_id} INTO isDependencyDepPkgVersionEdges OPTIONS { overwriteMode: "ignore" } + + RETURN { + 'pkgVersion': { + 'type_id': firstPkg.typeID, + 'type': firstPkg.type, + 'namespace_id': firstPkg.namespace_id, + 'namespace': firstPkg.namespace, + 'name_id': firstPkg.name_id, + 'name': firstPkg.name, + 'version_id': firstPkg.version_id, + 'version': firstPkg.version, + 'subpath': firstPkg.subpath, + 'qualifier_list': firstPkg.qualifier_list + }, + 'depPkg': { + 'type_id': secondPkg.typeID, + 'type': secondPkg.type, + 'namespace_id': secondPkg.namespace_id, + 'namespace': secondPkg.namespace, + 'name_id': secondPkg.name_id, + 'name': secondPkg.name, + 'version_id': secondPkg.version_id, + 'version': secondPkg.version, + 'subpath': secondPkg.subpath, + 'qualifier_list': secondPkg.qualifier_list + }, + 'isDependency_id': isDependency._id, + 'versionRange': isDependency.versionRange, + 'dependencyType': isDependency.dependencyType, + 'justification': isDependency.justification, + 'collector': isDependency.collector, + 'origin': isDependency.origin + }` + sb.WriteString(query) + // TODO: add version into return + } + + fmt.Println(sb.String()) cursor, err := executeQueryWithRetry(ctx, c.db, sb.String(), nil, "IngestDependency") if err != nil { return nil, fmt.Errorf("failed to ingest isDependency: %w", err) @@ -297,8 +535,11 @@ func (c *arangoClient) IngestDependencies(ctx context.Context, pkgs []*model.Pkg return getIsDependency(ctx, cursor) } -func (c *arangoClient) IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) { - query := ` +func (c *arangoClient) IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) { + + var query string + if depPkgMatchType.Pkg == model.PkgMatchTypeAllVersions { + query = ` LET firstPkg = FIRST( FOR pVersion in pkgVersions FILTER pVersion.guacKey == @pkgVersionGuacKey @@ -326,7 +567,7 @@ func (c *arangoClient) IngestDependency(ctx context.Context, pkg model.PkgInputS LET secondPkg = FIRST( FOR pName in pkgNames - FILTER pName.guacKey == @secondPkgNameGuacKey + FILTER pName.guacKey == @secondPkgGuacKey FOR pNs in pkgNamespaces FILTER pNs._id == pName._parent FOR pType in pkgTypes @@ -351,7 +592,7 @@ func (c *arangoClient) IngestDependency(ctx context.Context, pkg model.PkgInputS ) INSERT { _key: CONCAT("isDependencySubjectPkgEdges", firstPkg.versionDoc._key, isDependency._key), _from: firstPkg.version_id, _to: isDependency._id} INTO isDependencySubjectPkgEdges OPTIONS { overwriteMode: "ignore" } - INSERT { _key: CONCAT("isDependencyDepPkgEdges", isDependency._key, secondPkg.nameDoc._key), _from: isDependency._id, _to: secondPkg.name_id} INTO isDependencyDepPkgEdges OPTIONS { overwriteMode: "ignore" } + INSERT { _key: CONCAT("isDependencyDepPkgNameEdges", isDependency._key, secondPkg.nameDoc._key), _from: isDependency._id, _to: secondPkg.name_id} INTO isDependencyDepPkgNameEdges OPTIONS { overwriteMode: "ignore" } RETURN { 'pkgVersion': { @@ -381,8 +622,107 @@ func (c *arangoClient) IngestDependency(ctx context.Context, pkg model.PkgInputS 'collector': isDependency.collector, 'origin': isDependency.origin }` + } else { + + // Specific version + query = ` + LET firstPkg = FIRST( + FOR pVersion in pkgVersions + FILTER pVersion.guacKey == @pkgVersionGuacKey + FOR pName in pkgNames + FILTER pName._id == pVersion._parent + FOR pNs in pkgNamespaces + FILTER pNs._id == pName._parent + FOR pType in pkgTypes + FILTER pType._id == pNs._parent + + RETURN { + 'typeID': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list, + 'versionDoc': pVersion + } + ) + + LET secondPkg = FIRST( + FOR pVersion in pkgVersions + FILTER pVersion.guacKey == @secondPkgGuacKey + FOR pName in pkgNames + FILTER pName._id == pVersion._parent + FOR pNs in pkgNamespaces + FILTER pNs._id == pName._parent + FOR pType in pkgTypes + FILTER pType._id == pNs._parent + + + RETURN { + 'typeID': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list, + 'versionDoc': pVersion + } + ) + + + LET isDependency = FIRST( + UPSERT { packageID:firstPkg.version_id, depPackageID:secondPkg.version_id, versionRange:@versionRange, dependencyType:@dependencyType, justification:@justification, collector:@collector, origin:@origin } + INSERT { packageID:firstPkg.version_id, depPackageID:secondPkg.version_id, versionRange:@versionRange, dependencyType:@dependencyType, justification:@justification, collector:@collector, origin:@origin } + UPDATE {} IN isDependencies + RETURN NEW + ) + + INSERT { _key: CONCAT("isDependencySubjectPkgEdges", firstPkg.versionDoc._key, isDependency._key), _from: firstPkg.version_id, _to: isDependency._id} INTO isDependencySubjectPkgEdges OPTIONS { overwriteMode: "ignore" } + INSERT { _key: CONCAT("isDependencyDepPkgVersionEdges", isDependency._key, secondPkg.versionDoc._key), _from: isDependency._id, _to: secondPkg.version_id} INTO isDependencyDepPkgVersionEdges OPTIONS { overwriteMode: "ignore" } + + RETURN { + 'pkgVersion': { + 'type_id': firstPkg.typeID, + 'type': firstPkg.type, + 'namespace_id': firstPkg.namespace_id, + 'namespace': firstPkg.namespace, + 'name_id': firstPkg.name_id, + 'name': firstPkg.name, + 'version_id': firstPkg.version_id, + 'version': firstPkg.version, + 'subpath': firstPkg.subpath, + 'qualifier_list': firstPkg.qualifier_list + }, + 'depPkg': { + 'type_id': secondPkg.typeID, + 'type': secondPkg.type, + 'namespace_id': secondPkg.namespace_id, + 'namespace': secondPkg.namespace, + 'name_id': secondPkg.name_id, + 'name': secondPkg.name, + 'version_id': secondPkg.version_id, + 'version': secondPkg.version, + 'subpath': secondPkg.subpath, + 'qualifier_list': secondPkg.qualifier_list + }, + 'isDependency_id': isDependency._id, + 'versionRange': isDependency.versionRange, + 'dependencyType': isDependency.dependencyType, + 'justification': isDependency.justification, + 'collector': isDependency.collector, + 'origin': isDependency.origin + }` + } - cursor, err := executeQueryWithRetry(ctx, c.db, query, getDependencyQueryValues(&pkg, &depPkg, &dependency), "IngestDependency") + cursor, err := executeQueryWithRetry(ctx, c.db, query, getDependencyQueryValues(&pkg, &depPkg, depPkgMatchType, &dependency), "IngestDependency") if err != nil { return nil, fmt.Errorf("failed to ingest isDependency: %w", err) } @@ -416,7 +756,7 @@ func convertDependencyTypeToEnum(status string) (model.DependencyType, error) { func getIsDependency(ctx context.Context, cursor driver.Cursor) ([]*model.IsDependency, error) { type collectedData struct { PkgVersion *dbPkgVersion `json:"pkgVersion"` - DepPkg *dbPkgName `json:"depPkg"` + DepPkg *dbPkgVersion `json:"depPkg"` IsDependencyID string `json:"isDependency_id"` VersionRange string `json:"versionRange"` DependencyType string `json:"dependencyType"` @@ -443,10 +783,10 @@ func getIsDependency(ctx context.Context, cursor driver.Cursor) ([]*model.IsDepe var isDependencyList []*model.IsDependency for _, createdValue := range createdValues { pkg := generateModelPackage(createdValue.PkgVersion.TypeID, createdValue.PkgVersion.PkgType, createdValue.PkgVersion.NamespaceID, createdValue.PkgVersion.Namespace, createdValue.PkgVersion.NameID, - createdValue.PkgVersion.Name, &createdValue.PkgVersion.VersionID, &createdValue.PkgVersion.Version, &createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) + createdValue.PkgVersion.Name, createdValue.PkgVersion.VersionID, createdValue.PkgVersion.Version, createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) depPkg := generateModelPackage(createdValue.DepPkg.TypeID, createdValue.DepPkg.PkgType, createdValue.DepPkg.NamespaceID, createdValue.DepPkg.Namespace, createdValue.DepPkg.NameID, - createdValue.DepPkg.Name, nil, nil, nil, nil) + createdValue.DepPkg.Name, createdValue.DepPkg.VersionID, createdValue.DepPkg.Version, createdValue.DepPkg.Subpath, createdValue.DepPkg.QualifierList) dependencyTypeEnum, err := convertDependencyTypeToEnum(createdValue.DependencyType) if err != nil { diff --git a/pkg/assembler/backends/arangodb/isOccurrence.go b/pkg/assembler/backends/arangodb/isOccurrence.go index 3513f40e38..9f1210cd9a 100644 --- a/pkg/assembler/backends/arangodb/isOccurrence.go +++ b/pkg/assembler/backends/arangodb/isOccurrence.go @@ -634,7 +634,7 @@ func getPkgIsOccurrence(ctx context.Context, cursor driver.Cursor) ([]*model.IsO var isOccurrenceList []*model.IsOccurrence for _, createdValue := range createdValues { pkg := generateModelPackage(createdValue.PkgVersion.TypeID, createdValue.PkgVersion.PkgType, createdValue.PkgVersion.NamespaceID, createdValue.PkgVersion.Namespace, createdValue.PkgVersion.NameID, - createdValue.PkgVersion.Name, &createdValue.PkgVersion.VersionID, &createdValue.PkgVersion.Version, &createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) + createdValue.PkgVersion.Name, createdValue.PkgVersion.VersionID, createdValue.PkgVersion.Version, createdValue.PkgVersion.Subpath, createdValue.PkgVersion.QualifierList) isOccurrence := &model.IsOccurrence{ ID: createdValue.IsOccurrenceID, diff --git a/pkg/assembler/backends/arangodb/pkg.go b/pkg/assembler/backends/arangodb/pkg.go index b10d7a4de6..9ab02a2866 100644 --- a/pkg/assembler/backends/arangodb/pkg.go +++ b/pkg/assembler/backends/arangodb/pkg.go @@ -33,9 +33,9 @@ type dbPkgVersion struct { Namespace string `json:"namespace"` NameID string `json:"name_id"` Name string `json:"name"` - VersionID string `json:"version_id"` - Version string `json:"version"` - Subpath string `json:"subpath"` + VersionID *string `json:"version_id"` + Version *string `json:"version"` + Subpath *string `json:"subpath"` QualifierList []string `json:"qualifier_list"` } @@ -388,20 +388,21 @@ func setPkgVersionMatchValues(pkgSpec *model.PkgSpec, queryValues map[string]any arangoQueryBuilder.filter("pVersion", "subpath", "==", "@subpath") queryValues["subpath"] = *pkgSpec.Subpath } - if len(pkgSpec.Qualifiers) > 0 { - arangoQueryBuilder.filter("pVersion", "qualifier_list", "==", "@qualifier") - queryValues["qualifier"] = getQualifiers(pkgSpec.Qualifiers) - } - if !*pkgSpec.MatchOnlyEmptyQualifiers { + if pkgSpec.MatchOnlyEmptyQualifiers != nil { + if !*pkgSpec.MatchOnlyEmptyQualifiers { + if len(pkgSpec.Qualifiers) > 0 { + arangoQueryBuilder.filter("pVersion", "qualifier_list", "==", "@qualifier") + queryValues["qualifier"] = getQualifiers(pkgSpec.Qualifiers) + } + } else { + arangoQueryBuilder.filterLength("pVersion", "qualifier_list", "==", 0) + } + } else { if len(pkgSpec.Qualifiers) > 0 { arangoQueryBuilder.filter("pVersion", "qualifier_list", "==", "@qualifier") queryValues["qualifier"] = getQualifiers(pkgSpec.Qualifiers) } - } else { - arangoQueryBuilder.filter("pVersion", "qualifier_list", "==", "@qualifier") - queryValues["objPkgQualifierList"] = []string{} } - } else { arangoQueryBuilder = newForQuery(pkgTypesStr, "pType") arangoQueryBuilder.forOutBound(pkgHasNamespaceStr, "pNs", "pType") @@ -694,9 +695,9 @@ func getPackages(ctx context.Context, cursor driver.Cursor) ([]*model.Package, e typeString := doc.PkgType + "," + doc.TypeID pkgVersion := &model.PackageVersion{ - ID: doc.VersionID, - Version: versionString, - Subpath: subPathString, + ID: *doc.VersionID, + Version: *versionString, + Subpath: *subPathString, Qualifiers: pkgQualifiers, } diff --git a/pkg/assembler/backends/arangodb/search.go b/pkg/assembler/backends/arangodb/search.go index 2e74d113b3..68cbd1ad09 100644 --- a/pkg/assembler/backends/arangodb/search.go +++ b/pkg/assembler/backends/arangodb/search.go @@ -176,7 +176,7 @@ RETURN { if p == nil { return nil, fmt.Errorf("failed to parse result of pkgVersion, got nil when expected non-nil") } - pkg := generateModelPackage(p.TypeID, p.PkgType, p.NamespaceID, p.Namespace, p.NameID, p.Name, &p.VersionID, &p.Version, &p.Subpath, p.QualifierList) + pkg := generateModelPackage(p.TypeID, p.PkgType, p.NamespaceID, p.Namespace, p.NameID, p.Name, p.VersionID, p.Version, p.Subpath, p.QualifierList) results = append(results, pkg) case "pkgName": p := d.PkgName diff --git a/pkg/assembler/backends/backends.go b/pkg/assembler/backends/backends.go index cc53d478b0..4395e1fb44 100644 --- a/pkg/assembler/backends/backends.go +++ b/pkg/assembler/backends/backends.go @@ -68,8 +68,8 @@ type Backend interface { IngestCertifyBads(ctx context.Context, subjects model.PackageSourceOrArtifactInputs, pkgMatchType *model.MatchFlags, certifyBads []*model.CertifyBadInputSpec) ([]*model.CertifyBad, error) IngestCertifyGood(ctx context.Context, subject model.PackageSourceOrArtifactInput, pkgMatchType *model.MatchFlags, certifyGood model.CertifyGoodInputSpec) (*model.CertifyGood, error) IngestCertifyGoods(ctx context.Context, subjects model.PackageSourceOrArtifactInputs, pkgMatchType *model.MatchFlags, certifyGoods []*model.CertifyGoodInputSpec) ([]*model.CertifyGood, error) - IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) - IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) + IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) + IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec) (*model.HasSbom, error) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) ([]*model.HasSbom, error) IngestHasSourceAt(ctx context.Context, pkg model.PkgInputSpec, pkgMatchType model.MatchFlags, source model.SourceInputSpec, hasSourceAt model.HasSourceAtInputSpec) (*model.HasSourceAt, error) diff --git a/pkg/assembler/backends/inmem/isDependency.go b/pkg/assembler/backends/inmem/isDependency.go index 0f0e14833b..af12c10815 100644 --- a/pkg/assembler/backends/inmem/isDependency.go +++ b/pkg/assembler/backends/inmem/isDependency.go @@ -52,7 +52,8 @@ func (n *isDependencyLink) BuildModelNode(c *demoClient) (model.Node, error) { // Ingest IngestDependencies -func (c *demoClient) IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) { +func (c *demoClient) IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) { + // TODO(LUMJJB): match flags if len(pkgs) != len(depPkgs) { return nil, gqlerror.Errorf("uneven packages and dependent packages for ingestion") } @@ -62,7 +63,7 @@ func (c *demoClient) IngestDependencies(ctx context.Context, pkgs []*model.PkgIn var modelIsDependencies []*model.IsDependency for i := range dependencies { - isDependency, err := c.IngestDependency(ctx, *pkgs[i], *depPkgs[i], *dependencies[i]) + isDependency, err := c.IngestDependency(ctx, *pkgs[i], *depPkgs[i], depPkgMatchType, *dependencies[i]) if err != nil { return nil, gqlerror.Errorf("IngestDependency failed with err: %v", err) } @@ -72,11 +73,11 @@ func (c *demoClient) IngestDependencies(ctx context.Context, pkgs []*model.PkgIn } // Ingest IsDependency -func (c *demoClient) IngestDependency(ctx context.Context, packageArg model.PkgInputSpec, dependentPackageArg model.PkgInputSpec, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) { - return c.ingestDependency(ctx, packageArg, dependentPackageArg, dependency, true) +func (c *demoClient) IngestDependency(ctx context.Context, packageArg model.PkgInputSpec, dependentPackageArg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) { + return c.ingestDependency(ctx, packageArg, dependentPackageArg, depPkgMatchType, dependency, true) } -func (c *demoClient) ingestDependency(ctx context.Context, packageArg model.PkgInputSpec, dependentPackageArg model.PkgInputSpec, dependency model.IsDependencyInputSpec, readOnly bool) (*model.IsDependency, error) { +func (c *demoClient) ingestDependency(ctx context.Context, packageArg model.PkgInputSpec, dependentPackageArg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec, readOnly bool) (*model.IsDependency, error) { funcName := "IngestDependency" lock(&c.m, readOnly) defer unlock(&c.m, readOnly) @@ -84,7 +85,6 @@ func (c *demoClient) ingestDependency(ctx context.Context, packageArg model.PkgI // for IsDependency the dependent package will return the ID at the // packageName node. VersionRange will be used to specify the versions are // the attestation relates to - packageID, err := getPackageIDFromInput(c, packageArg, model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion}) if err != nil { return nil, gqlerror.Errorf("%v :: %s", funcName, err) @@ -95,15 +95,15 @@ func (c *demoClient) ingestDependency(ctx context.Context, packageArg model.PkgI } packageDependencies := foundPkgVersion.isDependencyLinks - depPackageID, err := getPackageIDFromInput(c, dependentPackageArg, model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}) + depPackageID, err := getPackageIDFromInput(c, dependentPackageArg, depPkgMatchType) if err != nil { return nil, gqlerror.Errorf("%v :: %s", funcName, err) } - depPkgName, err := byID[*pkgVersionStruct](depPackageID, c) + depPkg, err := byID[pkgNameOrVersion](depPackageID, c) if err != nil { return nil, gqlerror.Errorf("%v :: %s", funcName, err) } - depPackageDependencies := depPkgName.isDependencyLinks + depPackageDependencies := depPkg.getIsDependencyLinks() var searchIDs []uint32 if len(packageDependencies) < len(depPackageDependencies) { @@ -132,7 +132,7 @@ func (c *demoClient) ingestDependency(ctx context.Context, packageArg model.PkgI if !duplicate { if readOnly { c.m.RUnlock() - d, err := c.ingestDependency(ctx, packageArg, dependentPackageArg, dependency, false) + d, err := c.ingestDependency(ctx, packageArg, dependentPackageArg, depPkgMatchType, dependency, false) c.m.RLock() // relock so that defer unlock does not panic return d, err } @@ -151,7 +151,7 @@ func (c *demoClient) ingestDependency(ctx context.Context, packageArg model.PkgI c.isDependencies = append(c.isDependencies, &collectedIsDependencyLink) // set the backlinks foundPkgVersion.setIsDependencyLinks(collectedIsDependencyLink.id) - depPkgName.setIsDependencyLinks(collectedIsDependencyLink.id) + depPkg.setIsDependencyLinks(collectedIsDependencyLink.id) } // build return GraphQL type @@ -199,12 +199,27 @@ func (c *demoClient) IsDependency(ctx context.Context, filter *model.IsDependenc } } if !foundOne && filter != nil && filter.DependentPackage != nil { - exactPackage, err := c.exactPackageName(filter.DependentPackage) - if err != nil { - return nil, gqlerror.Errorf("%v :: %v", funcName, err) + var exactPackageLinks []uint32 + if filter.DependentPackage.Version == nil { + exactPackage, err := c.exactPackageName(filter.DependentPackage) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactPackage != nil { + exactPackageLinks = exactPackage.isDependencyLinks + } + } else { + exactPackage, err := c.exactPackageVersion(filter.Package) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactPackage != nil { + exactPackageLinks = exactPackage.isDependencyLinks + } } - if exactPackage != nil { - search = append(search, exactPackage.isDependencyLinks...) + + if exactPackageLinks != nil { + search = append(search, exactPackageLinks...) foundOne = true } } diff --git a/pkg/assembler/backends/inmem/isDependency_test.go b/pkg/assembler/backends/inmem/isDependency_test.go index 6b103263c5..ebd93e6b0a 100644 --- a/pkg/assembler/backends/inmem/isDependency_test.go +++ b/pkg/assembler/backends/inmem/isDependency_test.go @@ -27,10 +27,16 @@ import ( "golang.org/x/exp/slices" ) +var ( + mAll = model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions} + mSpecific = model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion} +) + func TestIsDependency(t *testing.T) { type call struct { P1 *model.PkgInputSpec P2 *model.PkgInputSpec + MF model.MatchFlags ID *model.IsDependencyInputSpec } tests := []struct { @@ -49,6 +55,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{ Justification: "test justification", }, @@ -72,6 +79,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{ Justification: "test justification", }, @@ -79,6 +87,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{ Justification: "test justification", }, @@ -102,6 +111,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{ Justification: "test justification", }, @@ -109,6 +119,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p3, + MF: mAll, ID: &model.IsDependencyInputSpec{ Justification: "test justification", }, @@ -132,6 +143,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{ Justification: "test justification one", }, @@ -139,6 +151,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{ Justification: "test justification two", }, @@ -162,11 +175,13 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p2, P2: p3, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, @@ -189,16 +204,18 @@ func TestIsDependency(t *testing.T) { { P1: p2, P2: p4, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p2, P2: p1, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, Query: &model.IsDependencySpec{ - DependentPackage: &model.PkgNameSpec{ + DependentPackage: &model.PkgSpec{ Name: ptrfrom.String("openssl"), }, }, @@ -216,11 +233,13 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p3, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, @@ -247,11 +266,13 @@ func TestIsDependency(t *testing.T) { { P1: p2, P2: p1, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p3, P2: p4, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, @@ -259,7 +280,7 @@ func TestIsDependency(t *testing.T) { Package: &model.PkgSpec{ Subpath: ptrfrom.String("saved_model_cli.py"), }, - DependentPackage: &model.PkgNameSpec{ + DependentPackage: &model.PkgSpec{ Name: ptrfrom.String("openssl"), }, }, @@ -277,16 +298,19 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p2, P2: p3, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p1, P2: p3, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, @@ -304,16 +328,19 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p2, P2: p3, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p1, P2: p3, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, @@ -334,6 +361,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p1, + MF: mAll, ID: &model.IsDependencyInputSpec{ VersionRange: "1-3", }, @@ -341,6 +369,7 @@ func TestIsDependency(t *testing.T) { { P1: p2, P2: p1, + MF: mAll, ID: &model.IsDependencyInputSpec{ VersionRange: "4-5", }, @@ -364,6 +393,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p1, + MF: mAll, ID: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeDirect, }, @@ -371,6 +401,7 @@ func TestIsDependency(t *testing.T) { { P1: p2, P2: p1, + MF: mAll, ID: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeIndirect, }, @@ -394,6 +425,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, @@ -406,6 +438,7 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p4, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, @@ -418,16 +451,19 @@ func TestIsDependency(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p2, P2: p3, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, { P1: p1, P2: p3, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, @@ -436,6 +472,91 @@ func TestIsDependency(t *testing.T) { }, ExpQueryErr: true, }, + { + Name: "IsDep from version to version", + InPkg: []*model.PkgInputSpec{p2, p3}, + Calls: []call{ + { + P1: p3, + P2: p2, + MF: mSpecific, + ID: &model.IsDependencyInputSpec{ + Justification: "test justification", + }, + }, + }, + Query: &model.IsDependencySpec{ + Justification: ptrfrom.String("test justification"), + }, + ExpID: []*model.IsDependency{ + { + Package: p3out, + DependentPackage: p2out, + Justification: "test justification", + }, + }, + }, + { + Name: "IsDep from version to name", + InPkg: []*model.PkgInputSpec{p2, p3}, + Calls: []call{ + { + P1: p3, + P2: p2, + MF: mAll, + ID: &model.IsDependencyInputSpec{ + Justification: "test justification", + }, + }, + }, + Query: &model.IsDependencySpec{ + Justification: ptrfrom.String("test justification"), + }, + ExpID: []*model.IsDependency{ + { + Package: p3out, + DependentPackage: p2outName, + Justification: "test justification", + }, + }, + }, + { + Name: "IsDep from version to name and version", + InPkg: []*model.PkgInputSpec{p2, p3}, + Calls: []call{ + { + P1: p3, + P2: p2, + MF: mSpecific, + ID: &model.IsDependencyInputSpec{ + Justification: "test justification", + }, + }, + { + P1: p3, + P2: p2, + MF: mAll, + ID: &model.IsDependencyInputSpec{ + Justification: "test justification", + }, + }, + }, + Query: &model.IsDependencySpec{ + Justification: ptrfrom.String("test justification"), + }, + ExpID: []*model.IsDependency{ + { + Package: p3out, + DependentPackage: p2out, + Justification: "test justification", + }, + { + Package: p3out, + DependentPackage: p2outName, + Justification: "test justification", + }, + }, + }, } ignoreID := cmp.FilterPath(func(p cmp.Path) bool { return strings.Compare(".ID", p[len(p)-1].String()) == 0 @@ -453,7 +574,7 @@ func TestIsDependency(t *testing.T) { } } for _, o := range test.Calls { - _, err := b.IngestDependency(ctx, *o.P1, *o.P2, *o.ID) + _, err := b.IngestDependency(ctx, *o.P1, *o.P2, o.MF, *o.ID) if (err != nil) != test.ExpIngestErr { t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } @@ -486,6 +607,7 @@ func TestIsDependencies(t *testing.T) { type call struct { P1s []*model.PkgInputSpec P2s []*model.PkgInputSpec + MF model.MatchFlags IDs []*model.IsDependencyInputSpec } tests := []struct { @@ -502,6 +624,7 @@ func TestIsDependencies(t *testing.T) { Calls: []call{{ P1s: []*model.PkgInputSpec{p1, p2}, P2s: []*model.PkgInputSpec{p2, p4}, + MF: mAll, IDs: []*model.IsDependencyInputSpec{ { Justification: "test justification", @@ -541,7 +664,7 @@ func TestIsDependencies(t *testing.T) { } } for _, o := range test.Calls { - got, err := b.IngestDependencies(ctx, o.P1s, o.P2s, o.IDs) + got, err := b.IngestDependencies(ctx, o.P1s, o.P2s, o.MF, o.IDs) if (err != nil) != test.ExpIngestErr { t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } @@ -561,6 +684,7 @@ func TestIsDependencyNeighbors(t *testing.T) { type call struct { P1 *model.PkgInputSpec P2 *model.PkgInputSpec + MF model.MatchFlags ID *model.IsDependencyInputSpec } tests := []struct { @@ -576,6 +700,7 @@ func TestIsDependencyNeighbors(t *testing.T) { { P1: p1, P2: p2, + MF: mAll, ID: &model.IsDependencyInputSpec{}, }, }, @@ -593,6 +718,7 @@ func TestIsDependencyNeighbors(t *testing.T) { { P1: p1, P2: p4, + MF: mAll, ID: &model.IsDependencyInputSpec{ Justification: "test justification", }, @@ -600,6 +726,7 @@ func TestIsDependencyNeighbors(t *testing.T) { { P1: p2, P2: p4, + MF: mAll, ID: &model.IsDependencyInputSpec{ Justification: "test justification", }, @@ -628,7 +755,7 @@ func TestIsDependencyNeighbors(t *testing.T) { } } for _, o := range test.Calls { - if _, err := b.IngestDependency(ctx, *o.P1, *o.P2, *o.ID); err != nil { + if _, err := b.IngestDependency(ctx, *o.P1, *o.P2, o.MF, *o.ID); err != nil { t.Fatalf("Could not ingest IsDependency: %v", err) } } diff --git a/pkg/assembler/backends/inmem/pkg.go b/pkg/assembler/backends/inmem/pkg.go index b9a02f7e46..7948189bb7 100644 --- a/pkg/assembler/backends/inmem/pkg.go +++ b/pkg/assembler/backends/inmem/pkg.go @@ -778,7 +778,7 @@ func (c *demoClient) exactPackageVersion(filter *model.PkgSpec) (*pkgVersionNode return nil, nil } -func (c *demoClient) exactPackageName(filter *model.PkgNameSpec) (*pkgVersionStruct, error) { +func (c *demoClient) exactPackageName(filter *model.PkgSpec) (*pkgVersionStruct, error) { if filter == nil { return nil, nil } diff --git a/pkg/assembler/backends/neo4j/isDependency.go b/pkg/assembler/backends/neo4j/isDependency.go index b6ca412e6d..2fdab82255 100644 --- a/pkg/assembler/backends/neo4j/isDependency.go +++ b/pkg/assembler/backends/neo4j/isDependency.go @@ -159,15 +159,16 @@ func setIsDependencyValues(sb *strings.Builder, isDependencySpec *model.IsDepend // Ingest IngestDependencies -func (c *neo4jClient) IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) { +func (c *neo4jClient) IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) { return []*model.IsDependency{}, fmt.Errorf("not implemented: IngestDependencies") } // Ingest IsDependency -func (c *neo4jClient) IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) { +func (c *neo4jClient) IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) { session := c.driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) defer session.Close() + // TODO: handle depPkgMatchType var sb strings.Builder var firstMatch bool = true diff --git a/pkg/assembler/clients/generated/operations.go b/pkg/assembler/clients/generated/operations.go index bdaa427db5..17bf736341 100644 --- a/pkg/assembler/clients/generated/operations.go +++ b/pkg/assembler/clients/generated/operations.go @@ -3105,11 +3105,11 @@ type AllIsDependencyTree struct { Justification string `json:"justification"` // Package that has the dependency Package AllIsDependencyTreePackage `json:"package"` - // Package for the dependency; MUST BE PackageName, not PackageVersion + // Package for the dependency; MUST be PackageName or PackageVersion DependentPackage AllIsDependencyTreeDependentPackage `json:"dependentPackage"` // Type of dependency DependencyType DependencyType `json:"dependencyType"` - // Version range for the dependency link + // Version range for the dependency link, required if depedentPackage points to PackageName VersionRange string `json:"versionRange"` // Document from which this attestation is generated from Origin string `json:"origin"` @@ -10322,6 +10322,7 @@ func (v *IsDependencyIngestDependencyIsDependency) __premarshalJSON() (*__premar // IsDependencyInputSpec is the input to record a new dependency. type IsDependencyInputSpec struct { + // versionRange should be specified for depedentPackages that point to PackageName VersionRange string `json:"versionRange"` DependencyType DependencyType `json:"dependencyType"` Justification string `json:"justification"` @@ -23127,9 +23128,10 @@ func (v *__IngestVulnerabilityInput) GetVuln() VulnerabilityInputSpec { return v // __IsDependenciesInput is used internally by genqlient type __IsDependenciesInput struct { - Pkgs []PkgInputSpec `json:"pkgs"` - DepPkgs []PkgInputSpec `json:"depPkgs"` - Dependencies []IsDependencyInputSpec `json:"dependencies"` + Pkgs []PkgInputSpec `json:"pkgs"` + DepPkgs []PkgInputSpec `json:"depPkgs"` + DepPkgMatchType MatchFlags `json:"depPkgMatchType"` + Dependencies []IsDependencyInputSpec `json:"dependencies"` } // GetPkgs returns __IsDependenciesInput.Pkgs, and is useful for accessing the field via an interface. @@ -23138,14 +23140,18 @@ func (v *__IsDependenciesInput) GetPkgs() []PkgInputSpec { return v.Pkgs } // GetDepPkgs returns __IsDependenciesInput.DepPkgs, and is useful for accessing the field via an interface. func (v *__IsDependenciesInput) GetDepPkgs() []PkgInputSpec { return v.DepPkgs } +// GetDepPkgMatchType returns __IsDependenciesInput.DepPkgMatchType, and is useful for accessing the field via an interface. +func (v *__IsDependenciesInput) GetDepPkgMatchType() MatchFlags { return v.DepPkgMatchType } + // GetDependencies returns __IsDependenciesInput.Dependencies, and is useful for accessing the field via an interface. func (v *__IsDependenciesInput) GetDependencies() []IsDependencyInputSpec { return v.Dependencies } // __IsDependencyInput is used internally by genqlient type __IsDependencyInput struct { - Pkg PkgInputSpec `json:"pkg"` - DepPkg PkgInputSpec `json:"depPkg"` - Dependency IsDependencyInputSpec `json:"dependency"` + Pkg PkgInputSpec `json:"pkg"` + DepPkg PkgInputSpec `json:"depPkg"` + DepPkgMatchType MatchFlags `json:"depPkgMatchType"` + Dependency IsDependencyInputSpec `json:"dependency"` } // GetPkg returns __IsDependencyInput.Pkg, and is useful for accessing the field via an interface. @@ -23154,6 +23160,9 @@ func (v *__IsDependencyInput) GetPkg() PkgInputSpec { return v.Pkg } // GetDepPkg returns __IsDependencyInput.DepPkg, and is useful for accessing the field via an interface. func (v *__IsDependencyInput) GetDepPkg() PkgInputSpec { return v.DepPkg } +// GetDepPkgMatchType returns __IsDependencyInput.DepPkgMatchType, and is useful for accessing the field via an interface. +func (v *__IsDependencyInput) GetDepPkgMatchType() MatchFlags { return v.DepPkgMatchType } + // GetDependency returns __IsDependencyInput.Dependency, and is useful for accessing the field via an interface. func (v *__IsDependencyInput) GetDependency() IsDependencyInputSpec { return v.Dependency } @@ -26573,8 +26582,8 @@ func IngestVulnerability( // The query or mutation executed by IsDependencies. const IsDependencies_Operation = ` -mutation IsDependencies ($pkgs: [PkgInputSpec!]!, $depPkgs: [PkgInputSpec!]!, $dependencies: [IsDependencyInputSpec!]!) { - ingestDependencies(pkgs: $pkgs, depPkgs: $depPkgs, dependencies: $dependencies) { +mutation IsDependencies ($pkgs: [PkgInputSpec!]!, $depPkgs: [PkgInputSpec!]!, $depPkgMatchType: MatchFlags!, $dependencies: [IsDependencyInputSpec!]!) { + ingestDependencies(pkgs: $pkgs, depPkgs: $depPkgs, depPkgMatchType: $depPkgMatchType, dependencies: $dependencies) { ... AllIsDependencyTree } } @@ -26620,15 +26629,17 @@ func IsDependencies( client graphql.Client, pkgs []PkgInputSpec, depPkgs []PkgInputSpec, + depPkgMatchType MatchFlags, dependencies []IsDependencyInputSpec, ) (*IsDependenciesResponse, error) { req := &graphql.Request{ OpName: "IsDependencies", Query: IsDependencies_Operation, Variables: &__IsDependenciesInput{ - Pkgs: pkgs, - DepPkgs: depPkgs, - Dependencies: dependencies, + Pkgs: pkgs, + DepPkgs: depPkgs, + DepPkgMatchType: depPkgMatchType, + Dependencies: dependencies, }, } var err error @@ -26647,8 +26658,8 @@ func IsDependencies( // The query or mutation executed by IsDependency. const IsDependency_Operation = ` -mutation IsDependency ($pkg: PkgInputSpec!, $depPkg: PkgInputSpec!, $dependency: IsDependencyInputSpec!) { - ingestDependency(pkg: $pkg, depPkg: $depPkg, dependency: $dependency) { +mutation IsDependency ($pkg: PkgInputSpec!, $depPkg: PkgInputSpec!, $depPkgMatchType: MatchFlags!, $dependency: IsDependencyInputSpec!) { + ingestDependency(pkg: $pkg, depPkg: $depPkg, depPkgMatchType: $depPkgMatchType, dependency: $dependency) { ... AllIsDependencyTree } } @@ -26694,15 +26705,17 @@ func IsDependency( client graphql.Client, pkg PkgInputSpec, depPkg PkgInputSpec, + depPkgMatchType MatchFlags, dependency IsDependencyInputSpec, ) (*IsDependencyResponse, error) { req := &graphql.Request{ OpName: "IsDependency", Query: IsDependency_Operation, Variables: &__IsDependencyInput{ - Pkg: pkg, - DepPkg: depPkg, - Dependency: dependency, + Pkg: pkg, + DepPkg: depPkg, + DepPkgMatchType: depPkgMatchType, + Dependency: dependency, }, } var err error diff --git a/pkg/assembler/clients/helpers/assembler.go b/pkg/assembler/clients/helpers/assembler.go index e81a5cba9d..f5f89ea712 100644 --- a/pkg/assembler/clients/helpers/assembler.go +++ b/pkg/assembler/clients/helpers/assembler.go @@ -209,7 +209,7 @@ func ingestCertifyScorecard(ctx context.Context, client graphql.Client, v assemb } func ingestIsDependency(ctx context.Context, client graphql.Client, v assembler.IsDependencyIngest) error { - _, err := model.IsDependency(ctx, client, *v.Pkg, *v.DepPkg, *v.IsDependency) + _, err := model.IsDependency(ctx, client, *v.Pkg, *v.DepPkg, v.DepPkgMatchFlag, *v.IsDependency) return err } diff --git a/pkg/assembler/clients/helpers/bulk.go b/pkg/assembler/clients/helpers/bulk.go index 884cc7fbed..7bdf0eb676 100644 --- a/pkg/assembler/clients/helpers/bulk.go +++ b/pkg/assembler/clients/helpers/bulk.go @@ -283,20 +283,42 @@ func ingestCertifyScorecards(ctx context.Context, client graphql.Client, v []ass } func ingestIsDependencies(ctx context.Context, client graphql.Client, v []assembler.IsDependencyIngest) error { - var pkgs []model.PkgInputSpec - var depPkgs []model.PkgInputSpec - var dependencies []model.IsDependencyInputSpec + + var depToVersion, depToName struct { + pkgs []model.PkgInputSpec + depPkgs []model.PkgInputSpec + depPkgMatchFlag model.MatchFlags + dependencies []model.IsDependencyInputSpec + } + + depToVersion.depPkgMatchFlag = model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion} + depToName.depPkgMatchFlag = model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions} + for _, ingest := range v { - pkgs = append(pkgs, *ingest.Pkg) - depPkgs = append(depPkgs, *ingest.DepPkg) - dependencies = append(dependencies, *ingest.IsDependency) + if ingest.DepPkgMatchFlag.Pkg == model.PkgMatchTypeSpecificVersion { + depToVersion.pkgs = append(depToVersion.pkgs, *ingest.Pkg) + depToVersion.depPkgs = append(depToVersion.depPkgs, *ingest.DepPkg) + depToVersion.dependencies = append(depToVersion.dependencies, *ingest.IsDependency) + } else if ingest.DepPkgMatchFlag.Pkg == model.PkgMatchTypeAllVersions { + depToName.pkgs = append(depToName.pkgs, *ingest.Pkg) + depToName.depPkgs = append(depToName.depPkgs, *ingest.DepPkg) + depToName.dependencies = append(depToName.dependencies, *ingest.IsDependency) + } } - if len(v) > 0 { - _, err := model.IsDependencies(ctx, client, pkgs, depPkgs, dependencies) + + if len(depToVersion.pkgs) > 0 { + _, err := model.IsDependencies(ctx, client, depToVersion.pkgs, depToVersion.depPkgs, depToVersion.depPkgMatchFlag, depToVersion.dependencies) if err != nil { return fmt.Errorf("isDependencies failed with error: %w", err) } } + if len(depToName.pkgs) > 0 { + _, err := model.IsDependencies(ctx, client, depToName.pkgs, depToName.depPkgs, depToName.depPkgMatchFlag, depToName.dependencies) + if err != nil { + return fmt.Errorf("isDependencies failed with error: %w", err) + } + } + return nil } diff --git a/pkg/assembler/clients/operations/isDependency.graphql b/pkg/assembler/clients/operations/isDependency.graphql index 394217f0b1..eb76c0ff0b 100644 --- a/pkg/assembler/clients/operations/isDependency.graphql +++ b/pkg/assembler/clients/operations/isDependency.graphql @@ -17,16 +17,16 @@ # Defines the GraphQL operations to ingest dependency information into GUAC -mutation IsDependency($pkg: PkgInputSpec!, $depPkg: PkgInputSpec!, $dependency: IsDependencyInputSpec!) { - ingestDependency(pkg: $pkg, depPkg: $depPkg, dependency: $dependency) { +mutation IsDependency($pkg: PkgInputSpec!, $depPkg: PkgInputSpec!, $depPkgMatchType: MatchFlags!, $dependency: IsDependencyInputSpec!) { + ingestDependency(pkg: $pkg, depPkg: $depPkg, depPkgMatchType: $depPkgMatchType, dependency: $dependency) { ...AllIsDependencyTree } } # Defines the GraphQL operations to bulk ingest dependencies information into GUAC -mutation IsDependencies($pkgs: [PkgInputSpec!]!, $depPkgs: [PkgInputSpec!]!, $dependencies: [IsDependencyInputSpec!]!) { - ingestDependencies(pkgs: $pkgs, depPkgs: $depPkgs, dependencies: $dependencies) { +mutation IsDependencies($pkgs: [PkgInputSpec!]!, $depPkgs: [PkgInputSpec!]!, $depPkgMatchType: MatchFlags!, $dependencies: [IsDependencyInputSpec!]!) { + ingestDependencies(pkgs: $pkgs, depPkgs: $depPkgs, depPkgMatchType: $depPkgMatchType, dependencies: $dependencies) { ...AllIsDependencyTree } } diff --git a/pkg/assembler/graphql/generated/artifact.generated.go b/pkg/assembler/graphql/generated/artifact.generated.go index 2b0ad9e05a..e0e9e4f142 100644 --- a/pkg/assembler/graphql/generated/artifact.generated.go +++ b/pkg/assembler/graphql/generated/artifact.generated.go @@ -40,8 +40,8 @@ type MutationResolver interface { IngestHasSourceAt(ctx context.Context, pkg model.PkgInputSpec, pkgMatchType model.MatchFlags, source model.SourceInputSpec, hasSourceAt model.HasSourceAtInputSpec) (*model.HasSourceAt, error) IngestHashEqual(ctx context.Context, artifact model.ArtifactInputSpec, otherArtifact model.ArtifactInputSpec, hashEqual model.HashEqualInputSpec) (*model.HashEqual, error) IngestHashEquals(ctx context.Context, artifacts []*model.ArtifactInputSpec, otherArtifacts []*model.ArtifactInputSpec, hashEquals []*model.HashEqualInputSpec) ([]*model.HashEqual, error) - IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) - IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) + IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) + IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) IngestOccurrence(ctx context.Context, subject model.PackageOrSourceInput, artifact model.ArtifactInputSpec, occurrence model.IsOccurrenceInputSpec) (*model.IsOccurrence, error) IngestOccurrences(ctx context.Context, subjects model.PackageOrSourceInputs, artifacts []*model.ArtifactInputSpec, occurrences []*model.IsOccurrenceInputSpec) ([]*model.IsOccurrence, error) IngestHasMetadata(ctx context.Context, subject model.PackageSourceOrArtifactInput, pkgMatchType model.MatchFlags, hasMetadata model.HasMetadataInputSpec) (*model.HasMetadata, error) @@ -365,15 +365,24 @@ func (ec *executionContext) field_Mutation_ingestDependencies_args(ctx context.C } } args["depPkgs"] = arg1 - var arg2 []*model.IsDependencyInputSpec + var arg2 model.MatchFlags + if tmp, ok := rawArgs["depPkgMatchType"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("depPkgMatchType")) + arg2, err = ec.unmarshalNMatchFlags2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐMatchFlags(ctx, tmp) + if err != nil { + return nil, err + } + } + args["depPkgMatchType"] = arg2 + var arg3 []*model.IsDependencyInputSpec if tmp, ok := rawArgs["dependencies"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dependencies")) - arg2, err = ec.unmarshalNIsDependencyInputSpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsDependencyInputSpecᚄ(ctx, tmp) + arg3, err = ec.unmarshalNIsDependencyInputSpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsDependencyInputSpecᚄ(ctx, tmp) if err != nil { return nil, err } } - args["dependencies"] = arg2 + args["dependencies"] = arg3 return args, nil } @@ -398,15 +407,24 @@ func (ec *executionContext) field_Mutation_ingestDependency_args(ctx context.Con } } args["depPkg"] = arg1 - var arg2 model.IsDependencyInputSpec + var arg2 model.MatchFlags + if tmp, ok := rawArgs["depPkgMatchType"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("depPkgMatchType")) + arg2, err = ec.unmarshalNMatchFlags2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐMatchFlags(ctx, tmp) + if err != nil { + return nil, err + } + } + args["depPkgMatchType"] = arg2 + var arg3 model.IsDependencyInputSpec if tmp, ok := rawArgs["dependency"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dependency")) - arg2, err = ec.unmarshalNIsDependencyInputSpec2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsDependencyInputSpec(ctx, tmp) + arg3, err = ec.unmarshalNIsDependencyInputSpec2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsDependencyInputSpec(ctx, tmp) if err != nil { return nil, err } } - args["dependency"] = arg2 + args["dependency"] = arg3 return args, nil } @@ -2998,7 +3016,7 @@ func (ec *executionContext) _Mutation_ingestDependency(ctx context.Context, fiel }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().IngestDependency(rctx, fc.Args["pkg"].(model.PkgInputSpec), fc.Args["depPkg"].(model.PkgInputSpec), fc.Args["dependency"].(model.IsDependencyInputSpec)) + return ec.resolvers.Mutation().IngestDependency(rctx, fc.Args["pkg"].(model.PkgInputSpec), fc.Args["depPkg"].(model.PkgInputSpec), fc.Args["depPkgMatchType"].(model.MatchFlags), fc.Args["dependency"].(model.IsDependencyInputSpec)) }) if err != nil { ec.Error(ctx, err) @@ -3071,7 +3089,7 @@ func (ec *executionContext) _Mutation_ingestDependencies(ctx context.Context, fi }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().IngestDependencies(rctx, fc.Args["pkgs"].([]*model.PkgInputSpec), fc.Args["depPkgs"].([]*model.PkgInputSpec), fc.Args["dependencies"].([]*model.IsDependencyInputSpec)) + return ec.resolvers.Mutation().IngestDependencies(rctx, fc.Args["pkgs"].([]*model.PkgInputSpec), fc.Args["depPkgs"].([]*model.PkgInputSpec), fc.Args["depPkgMatchType"].(model.MatchFlags), fc.Args["dependencies"].([]*model.IsDependencyInputSpec)) }) if err != nil { ec.Error(ctx, err) diff --git a/pkg/assembler/graphql/generated/isDependency.generated.go b/pkg/assembler/graphql/generated/isDependency.generated.go index cd4b00a635..5d4a4ea8fc 100644 --- a/pkg/assembler/graphql/generated/isDependency.generated.go +++ b/pkg/assembler/graphql/generated/isDependency.generated.go @@ -502,7 +502,7 @@ func (ec *executionContext) unmarshalInputIsDependencySpec(ctx context.Context, var err error ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dependentPackage")) - data, err := ec.unmarshalOPkgNameSpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPkgNameSpec(ctx, v) + data, err := ec.unmarshalOPkgSpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPkgSpec(ctx, v) if err != nil { return it, err } @@ -558,62 +558,6 @@ func (ec *executionContext) unmarshalInputIsDependencySpec(ctx context.Context, return it, nil } -func (ec *executionContext) unmarshalInputPkgNameSpec(ctx context.Context, obj interface{}) (model.PkgNameSpec, error) { - var it model.PkgNameSpec - asMap := map[string]interface{}{} - for k, v := range obj.(map[string]interface{}) { - asMap[k] = v - } - - fieldsInOrder := [...]string{"id", "type", "namespace", "name"} - for _, k := range fieldsInOrder { - v, ok := asMap[k] - if !ok { - continue - } - switch k { - case "id": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) - data, err := ec.unmarshalOID2ᚖstring(ctx, v) - if err != nil { - return it, err - } - it.ID = data - case "type": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("type")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) - if err != nil { - return it, err - } - it.Type = data - case "namespace": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("namespace")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) - if err != nil { - return it, err - } - it.Namespace = data - case "name": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) - if err != nil { - return it, err - } - it.Name = data - } - } - - return it, nil -} - // endregion **************************** input.gotpl ***************************** // region ************************** interface.gotpl *************************** @@ -816,12 +760,4 @@ func (ec *executionContext) marshalODependencyType2ᚖgithubᚗcomᚋguacsecᚋg return v } -func (ec *executionContext) unmarshalOPkgNameSpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPkgNameSpec(ctx context.Context, v interface{}) (*model.PkgNameSpec, error) { - if v == nil { - return nil, nil - } - res, err := ec.unmarshalInputPkgNameSpec(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - // endregion ***************************** type.gotpl ***************************** diff --git a/pkg/assembler/graphql/generated/root_.generated.go b/pkg/assembler/graphql/generated/root_.generated.go index 1a040a57aa..a48af0f3f0 100644 --- a/pkg/assembler/graphql/generated/root_.generated.go +++ b/pkg/assembler/graphql/generated/root_.generated.go @@ -169,8 +169,8 @@ type ComplexityRoot struct { IngestCertifyGoods func(childComplexity int, subjects model.PackageSourceOrArtifactInputs, pkgMatchType model.MatchFlags, certifyGoods []*model.CertifyGoodInputSpec) int IngestCertifyVuln func(childComplexity int, pkg model.PkgInputSpec, vulnerability model.VulnerabilityInputSpec, certifyVuln model.ScanMetadataInput) int IngestCertifyVulns func(childComplexity int, pkgs []*model.PkgInputSpec, vulnerabilities []*model.VulnerabilityInputSpec, certifyVulns []*model.ScanMetadataInput) int - IngestDependencies func(childComplexity int, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, dependencies []*model.IsDependencyInputSpec) int - IngestDependency func(childComplexity int, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, dependency model.IsDependencyInputSpec) int + IngestDependencies func(childComplexity int, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependencies []*model.IsDependencyInputSpec) int + IngestDependency func(childComplexity int, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec) int IngestHasMetadata func(childComplexity int, subject model.PackageSourceOrArtifactInput, pkgMatchType model.MatchFlags, hasMetadata model.HasMetadataInputSpec) int IngestHasSBOMs func(childComplexity int, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) int IngestHasSbom func(childComplexity int, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec) int @@ -1037,7 +1037,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.IngestDependencies(childComplexity, args["pkgs"].([]*model.PkgInputSpec), args["depPkgs"].([]*model.PkgInputSpec), args["dependencies"].([]*model.IsDependencyInputSpec)), true + return e.complexity.Mutation.IngestDependencies(childComplexity, args["pkgs"].([]*model.PkgInputSpec), args["depPkgs"].([]*model.PkgInputSpec), args["depPkgMatchType"].(model.MatchFlags), args["dependencies"].([]*model.IsDependencyInputSpec)), true case "Mutation.ingestDependency": if e.complexity.Mutation.IngestDependency == nil { @@ -1049,7 +1049,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.IngestDependency(childComplexity, args["pkg"].(model.PkgInputSpec), args["depPkg"].(model.PkgInputSpec), args["dependency"].(model.IsDependencyInputSpec)), true + return e.complexity.Mutation.IngestDependency(childComplexity, args["pkg"].(model.PkgInputSpec), args["depPkg"].(model.PkgInputSpec), args["depPkgMatchType"].(model.MatchFlags), args["dependency"].(model.IsDependencyInputSpec)), true case "Mutation.ingestHasMetadata": if e.complexity.Mutation.IngestHasMetadata == nil { @@ -2187,7 +2187,6 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputPkgEqualInputSpec, ec.unmarshalInputPkgEqualSpec, ec.unmarshalInputPkgInputSpec, - ec.unmarshalInputPkgNameSpec, ec.unmarshalInputPkgSpec, ec.unmarshalInputPointOfContactInputSpec, ec.unmarshalInputPointOfContactSpec, @@ -3494,9 +3493,9 @@ type IsDependency { id: ID! "Package that has the dependency" package: Package! - "Package for the dependency; MUST BE PackageName, not PackageVersion" + "Package for the dependency; MUST be PackageName or PackageVersion" dependentPackage: Package! - "Version range for the dependency link" + "Version range for the dependency link, required if depedentPackage points to PackageName" versionRange: String! "Type of dependency" dependencyType: DependencyType! @@ -3519,7 +3518,7 @@ Dependent packages must be defined at PackageName, not PackageVersion. input IsDependencySpec { id: ID package: PkgSpec - dependentPackage: PkgNameSpec + dependentPackage: PkgSpec versionRange: String dependencyType: DependencyType justification: String @@ -3527,23 +3526,9 @@ input IsDependencySpec { collector: String } -""" -PkgNameSpec is used to query for dependent packages. - -This is different from PkgSpec as the IsDependency attestation should only be -allowed to be made to the packageName node and not the packageVersion node. -Versions will be handled by the version_range in the IsDependency attestation -node. -""" -input PkgNameSpec { - id: ID - type: String - namespace: String - name: String -} - "IsDependencyInputSpec is the input to record a new dependency." input IsDependencyInputSpec { + "versionRange should be specified for depedentPackages that point to PackageName" versionRange: String! dependencyType: DependencyType! justification: String! @@ -3558,9 +3543,9 @@ extend type Query { extend type Mutation { "Adds a dependency between two packages" - ingestDependency(pkg: PkgInputSpec!, depPkg: PkgInputSpec!, dependency: IsDependencyInputSpec!): IsDependency! + ingestDependency(pkg: PkgInputSpec!, depPkg: PkgInputSpec!, depPkgMatchType: MatchFlags!, dependency: IsDependencyInputSpec!): IsDependency! "Bulk adds a dependency between two packages" - ingestDependencies(pkgs: [PkgInputSpec!]!, depPkgs: [PkgInputSpec!]!, dependencies: [IsDependencyInputSpec!]!): [IsDependency!]! + ingestDependencies(pkgs: [PkgInputSpec!]!, depPkgs: [PkgInputSpec!]!, depPkgMatchType: MatchFlags!, dependencies: [IsDependencyInputSpec!]!): [IsDependency!]! } `, BuiltIn: false}, {Name: "../schema/isOccurrence.graphql", Input: `# diff --git a/pkg/assembler/graphql/model/nodes.go b/pkg/assembler/graphql/model/nodes.go index af44f3a79e..8a6144410f 100644 --- a/pkg/assembler/graphql/model/nodes.go +++ b/pkg/assembler/graphql/model/nodes.go @@ -496,9 +496,9 @@ type IsDependency struct { ID string `json:"id"` // Package that has the dependency Package *Package `json:"package"` - // Package for the dependency; MUST BE PackageName, not PackageVersion + // Package for the dependency; MUST be PackageName or PackageVersion DependentPackage *Package `json:"dependentPackage"` - // Version range for the dependency link + // Version range for the dependency link, required if depedentPackage points to PackageName VersionRange string `json:"versionRange"` // Type of dependency DependencyType DependencyType `json:"dependencyType"` @@ -514,6 +514,7 @@ func (IsDependency) IsNode() {} // IsDependencyInputSpec is the input to record a new dependency. type IsDependencyInputSpec struct { + // versionRange should be specified for depedentPackages that point to PackageName VersionRange string `json:"versionRange"` DependencyType DependencyType `json:"dependencyType"` Justification string `json:"justification"` @@ -530,7 +531,7 @@ type IsDependencyInputSpec struct { type IsDependencySpec struct { ID *string `json:"id,omitempty"` Package *PkgSpec `json:"package,omitempty"` - DependentPackage *PkgNameSpec `json:"dependentPackage,omitempty"` + DependentPackage *PkgSpec `json:"dependentPackage,omitempty"` VersionRange *string `json:"versionRange,omitempty"` DependencyType *DependencyType `json:"dependencyType,omitempty"` Justification *string `json:"justification,omitempty"` @@ -820,19 +821,6 @@ type PkgInputSpec struct { Subpath *string `json:"subpath,omitempty"` } -// PkgNameSpec is used to query for dependent packages. -// -// This is different from PkgSpec as the IsDependency attestation should only be -// allowed to be made to the packageName node and not the packageVersion node. -// Versions will be handled by the version_range in the IsDependency attestation -// node. -type PkgNameSpec struct { - ID *string `json:"id,omitempty"` - Type *string `json:"type,omitempty"` - Namespace *string `json:"namespace,omitempty"` - Name *string `json:"name,omitempty"` -} - // PkgSpec allows filtering the list of sources to return in a query. // // Each field matches a qualifier from pURL. Use null to match on all values at diff --git a/pkg/assembler/graphql/resolvers/isDependency.resolvers.go b/pkg/assembler/graphql/resolvers/isDependency.resolvers.go index a01cef0a7a..7fe96ccb09 100644 --- a/pkg/assembler/graphql/resolvers/isDependency.resolvers.go +++ b/pkg/assembler/graphql/resolvers/isDependency.resolvers.go @@ -11,13 +11,13 @@ import ( ) // IngestDependency is the resolver for the ingestDependency field. -func (r *mutationResolver) IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) { - return r.Backend.IngestDependency(ctx, pkg, depPkg, dependency) +func (r *mutationResolver) IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) { + return r.Backend.IngestDependency(ctx, pkg, depPkg, depPkgMatchType, dependency) } // IngestDependencies is the resolver for the ingestDependencies field. -func (r *mutationResolver) IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) { - return r.Backend.IngestDependencies(ctx, pkgs, depPkgs, dependencies) +func (r *mutationResolver) IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) { + return r.Backend.IngestDependencies(ctx, pkgs, depPkgs, depPkgMatchType, dependencies) } // IsDependency is the resolver for the IsDependency field. diff --git a/pkg/assembler/graphql/schema/isDependency.graphql b/pkg/assembler/graphql/schema/isDependency.graphql index 03513680ec..a18413d13e 100644 --- a/pkg/assembler/graphql/schema/isDependency.graphql +++ b/pkg/assembler/graphql/schema/isDependency.graphql @@ -32,9 +32,9 @@ type IsDependency { id: ID! "Package that has the dependency" package: Package! - "Package for the dependency; MUST BE PackageName, not PackageVersion" + "Package for the dependency; MUST be PackageName or PackageVersion" dependentPackage: Package! - "Version range for the dependency link" + "Version range for the dependency link, required if depedentPackage points to PackageName" versionRange: String! "Type of dependency" dependencyType: DependencyType! @@ -57,7 +57,7 @@ Dependent packages must be defined at PackageName, not PackageVersion. input IsDependencySpec { id: ID package: PkgSpec - dependentPackage: PkgNameSpec + dependentPackage: PkgSpec versionRange: String dependencyType: DependencyType justification: String @@ -65,23 +65,9 @@ input IsDependencySpec { collector: String } -""" -PkgNameSpec is used to query for dependent packages. - -This is different from PkgSpec as the IsDependency attestation should only be -allowed to be made to the packageName node and not the packageVersion node. -Versions will be handled by the version_range in the IsDependency attestation -node. -""" -input PkgNameSpec { - id: ID - type: String - namespace: String - name: String -} - "IsDependencyInputSpec is the input to record a new dependency." input IsDependencyInputSpec { + "versionRange should be specified for depedentPackages that point to PackageName" versionRange: String! dependencyType: DependencyType! justification: String! @@ -96,7 +82,7 @@ extend type Query { extend type Mutation { "Adds a dependency between two packages" - ingestDependency(pkg: PkgInputSpec!, depPkg: PkgInputSpec!, dependency: IsDependencyInputSpec!): IsDependency! + ingestDependency(pkg: PkgInputSpec!, depPkg: PkgInputSpec!, depPkgMatchType: MatchFlags!, dependency: IsDependencyInputSpec!): IsDependency! "Bulk adds a dependency between two packages" - ingestDependencies(pkgs: [PkgInputSpec!]!, depPkgs: [PkgInputSpec!]!, dependencies: [IsDependencyInputSpec!]!): [IsDependency!]! + ingestDependencies(pkgs: [PkgInputSpec!]!, depPkgs: [PkgInputSpec!]!, depPkgMatchType: MatchFlags!, dependencies: [IsDependencyInputSpec!]!): [IsDependency!]! } diff --git a/pkg/guacanalytics/patchPlanning.go b/pkg/guacanalytics/patchPlanning.go index bd2e967819..15b2d4cddc 100644 --- a/pkg/guacanalytics/patchPlanning.go +++ b/pkg/guacanalytics/patchPlanning.go @@ -191,6 +191,12 @@ func caseOnPredicates(ctx context.Context, gqlClient graphql.Client, q *queueVal if err != nil { return err } + case *model.NeighborsNeighborsIsDependency: + err := exploreIsDependencyFromDepPkg(ctx, gqlClient, q, *neighbor) + if err != nil { + return err + } + } case SourceName: switch neighbor := neighbor.(type) { @@ -229,15 +235,23 @@ func caseOnPredicates(ctx context.Context, gqlClient graphql.Client, q *queueVal } func exploreIsDependencyFromDepPkg(ctx context.Context, gqlClient graphql.Client, q *queueValues, isDependency model.NeighborsNeighborsIsDependency) error { + // if coming from dependent, ignore + if isDependency.Package.Namespaces[0].Names[0].Versions[0].Id == *q.now { + return nil + } + path = append(path, isDependency.Id) - doesRangeInclude, err := depversion.DoesRangeInclude(q.nowNode.nodeVersions, isDependency.VersionRange) + targetDepPkgVersion := len(isDependency.DependentPackage.Namespaces[0].Names[0].Versions) > 0 - if err != nil { - return err - } + if !targetDepPkgVersion { + doesRangeInclude, err := depversion.DoesRangeInclude(q.nowNode.nodeVersions, isDependency.VersionRange) + if err != nil { + return err + } - if !doesRangeInclude { - return nil + if !doesRangeInclude { + return nil + } } q.addNodeToQueue(PackageVersion, nil, isDependency.Package.Namespaces[0].Names[0].Versions[0].Id) diff --git a/pkg/guacanalytics/patchPlanning_test.go b/pkg/guacanalytics/patchPlanning_test.go index 49d6400648..8d967225a6 100644 --- a/pkg/guacanalytics/patchPlanning_test.go +++ b/pkg/guacanalytics/patchPlanning_test.go @@ -53,6 +53,7 @@ var ( Name: "openssl", Version: ptrfrom.String("3.0.3"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ VersionRange: ">=1.19.0", DependencyType: model.DependencyTypeDirect, @@ -74,6 +75,7 @@ var ( Name: "dpkg", Version: ptrfrom.String("1.19.0"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ VersionRange: ">=1.19.0", DependencyType: model.DependencyTypeDirect, @@ -95,6 +97,7 @@ var ( Name: "bottompkg", Version: ptrfrom.String("1.19.0"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ VersionRange: ">=1.19.0", DependencyType: model.DependencyTypeIndirect, @@ -244,6 +247,7 @@ var ( Name: "pkgName3", Version: ptrfrom.String("1.19.0"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ VersionRange: ">=1.19.0", DependencyType: model.DependencyTypeDirect, @@ -327,6 +331,7 @@ var ( Name: "extraName", Version: ptrfrom.String("1.19.0"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ VersionRange: "=3.0.3", DependencyType: model.DependencyTypeDirect, @@ -423,6 +428,7 @@ var ( Name: "pkgNameB", Version: ptrfrom.String("1.19.0"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ VersionRange: ">=1.19.0", DependencyType: model.DependencyTypeDirect, @@ -554,6 +560,7 @@ var ( Name: "pkgNameE", Version: ptrfrom.String("3.0.3"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ VersionRange: "=>2.0.0", DependencyType: model.DependencyTypeDirect, @@ -654,6 +661,7 @@ var ( Name: "pkgNameI", Version: ptrfrom.String("3.0.3"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ VersionRange: ">=2.0.0", DependencyType: model.DependencyTypeDirect, @@ -714,6 +722,7 @@ var ( Name: "bName", Version: ptrfrom.String("1.19.1"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, Pkg: &model.PkgInputSpec{ Type: "dType", Namespace: ptrfrom.String("dNamespace"), @@ -851,6 +860,7 @@ var ( Name: "pkgNameM", Version: ptrfrom.String("3.0.3"), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ VersionRange: "=>1.0.0", DependencyType: model.DependencyTypeDirect, @@ -944,7 +954,7 @@ func ingestIsDependency(ctx context.Context, client graphql.Client, graph assemb if err != nil { return fmt.Errorf("error in ingesting dependent package: %s\n", err) } - _, err = model.IsDependency(ctx, client, *ingest.Pkg, *ingest.DepPkg, *ingest.IsDependency) + _, err = model.IsDependency(ctx, client, *ingest.Pkg, *ingest.DepPkg, ingest.DepPkgMatchFlag, *ingest.IsDependency) if err != nil { return fmt.Errorf("error in ingesting isDependency: %s\n", err) diff --git a/pkg/ingestor/parser/common/helpers.go b/pkg/ingestor/parser/common/helpers.go index d38dd17a58..0bc366c299 100644 --- a/pkg/ingestor/parser/common/helpers.go +++ b/pkg/ingestor/parser/common/helpers.go @@ -29,10 +29,12 @@ import ( func GetIsDep(foundNode *model.PkgInputSpec, relatedPackNodes []*model.PkgInputSpec, relatedFileNodes []*model.PkgInputSpec, justification string) (*assembler.IsDependencyIngest, error) { if len(relatedFileNodes) > 0 { for _, rfileNode := range relatedFileNodes { + // TODO: Check is this always just expected to be one? return &assembler.IsDependencyIngest{ - Pkg: foundNode, - DepPkg: rfileNode, + Pkg: foundNode, + DepPkg: rfileNode, + DepPkgMatchFlag: getMatchFlagsFromPkgInput(rfileNode), IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, Justification: justification, @@ -43,8 +45,9 @@ func GetIsDep(foundNode *model.PkgInputSpec, relatedPackNodes []*model.PkgInputS } else if len(relatedPackNodes) > 0 { for _, rpackNode := range relatedPackNodes { return &assembler.IsDependencyIngest{ - Pkg: foundNode, - DepPkg: rpackNode, + Pkg: foundNode, + DepPkg: rpackNode, + DepPkgMatchFlag: getMatchFlagsFromPkgInput(rpackNode), IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, Justification: justification, @@ -64,8 +67,9 @@ func CreateTopLevelIsDeps(topLevel *model.PkgInputSpec, packages map[string][]*m for _, packNode := range packNodes { if !reflect.DeepEqual(packNode, topLevel) { p := assembler.IsDependencyIngest{ - Pkg: topLevel, - DepPkg: packNode, + Pkg: topLevel, + DepPkg: packNode, + DepPkgMatchFlag: getMatchFlagsFromPkgInput(packNode), IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, Justification: justification, @@ -80,8 +84,9 @@ func CreateTopLevelIsDeps(topLevel *model.PkgInputSpec, packages map[string][]*m for _, fileNodes := range files { for _, fileNode := range fileNodes { p := assembler.IsDependencyIngest{ - Pkg: topLevel, - DepPkg: fileNode, + Pkg: topLevel, + DepPkg: fileNode, + DepPkgMatchFlag: getMatchFlagsFromPkgInput(fileNode), IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeUnknown, Justification: justification, @@ -108,3 +113,11 @@ func CreateTopLevelHasSBOM(topLevel *model.PkgInputSpec, sbomDoc *processor.Docu }, } } + +func getMatchFlagsFromPkgInput(p *model.PkgInputSpec) model.MatchFlags { + matchFlags := model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions} + if p.Version != nil && *p.Version != "" { + matchFlags = model.MatchFlags{Pkg: model.PkgMatchTypeSpecificVersion} + } + return matchFlags +} diff --git a/pkg/ingestor/parser/deps_dev/deps_dev.go b/pkg/ingestor/parser/deps_dev/deps_dev.go index f11781841e..5c4bf02dd2 100644 --- a/pkg/ingestor/parser/deps_dev/deps_dev.go +++ b/pkg/ingestor/parser/deps_dev/deps_dev.go @@ -67,9 +67,10 @@ func (d *depsDevParser) GetPredicates(ctx context.Context) *assembler.IngestPred for _, isDepComp := range d.packComponent.IsDepPackages { preds.IsDependency = append(preds.IsDependency, assembler.IsDependencyIngest{ - Pkg: isDepComp.CurrentPackageInput, - DepPkg: isDepComp.DepPackageInput, - IsDependency: isDepComp.IsDependency, + Pkg: isDepComp.CurrentPackageInput, + DepPkg: isDepComp.DepPackageInput, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, + IsDependency: isDepComp.IsDependency, }) } diff --git a/pkg/ingestor/parser/deps_dev/deps_dev_test.go b/pkg/ingestor/parser/deps_dev/deps_dev_test.go index bd222fcc57..06679e1d96 100644 --- a/pkg/ingestor/parser/deps_dev/deps_dev_test.go +++ b/pkg/ingestor/parser/deps_dev/deps_dev_test.go @@ -75,6 +75,7 @@ func Test_depsDevParser_Parse(t *testing.T) { Version: ptrfrom.String("1.4.0"), Subpath: ptrfrom.String(""), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, DepPkg: &model.PkgInputSpec{ Type: "npm", Namespace: ptrfrom.String(""), @@ -97,6 +98,7 @@ func Test_depsDevParser_Parse(t *testing.T) { Version: ptrfrom.String("17.0.0"), Subpath: ptrfrom.String(""), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, DepPkg: &model.PkgInputSpec{ Type: "npm", Namespace: ptrfrom.String(""), @@ -126,6 +128,7 @@ func Test_depsDevParser_Parse(t *testing.T) { Version: ptrfrom.String("4.1.1"), Subpath: ptrfrom.String(""), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeDirect, VersionRange: "^4.1.1", @@ -292,6 +295,7 @@ func Test_depsDevParser_Parse(t *testing.T) { Version: ptrfrom.String("0.1.1"), Subpath: ptrfrom.String(""), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, IsDependency: &model.IsDependencyInputSpec{ DependencyType: model.DependencyTypeDirect, VersionRange: "^0.1", @@ -368,6 +372,7 @@ func Test_depsDevParser_Parse(t *testing.T) { Version: ptrfrom.String("4.2.1"), Subpath: ptrfrom.String(""), }, + DepPkgMatchFlag: model.MatchFlags{Pkg: model.PkgMatchTypeAllVersions}, DepPkg: &model.PkgInputSpec{ Type: "npm", Namespace: ptrfrom.String(""), diff --git a/pkg/ingestor/parser/spdx/parse_spdx_test.go b/pkg/ingestor/parser/spdx/parse_spdx_test.go index dedeb6acd6..3918b680fb 100644 --- a/pkg/ingestor/parser/spdx/parse_spdx_test.go +++ b/pkg/ingestor/parser/spdx/parse_spdx_test.go @@ -312,8 +312,9 @@ func Test_spdxParser(t *testing.T) { wantPredicates: &assembler.IngestPredicates{ IsDependency: []assembler.IsDependencyIngest{ { - Pkg: pUrlToPkgDiscardError("pkg:oci/redhat/ubi9-container@sha256:4227a4b5013999a412196237c62e40d778d09cdc751720a66ff3701fbe5a4a9d?repository_url=registry.redhat.io/ubi9&tag=9.1.0-1750"), - DepPkg: pUrlToPkgDiscardError("pkg:rpm/redhat/python3-libcomps@0.1.18-1.el9?arch=x86_64"), + Pkg: pUrlToPkgDiscardError("pkg:oci/redhat/ubi9-container@sha256:4227a4b5013999a412196237c62e40d778d09cdc751720a66ff3701fbe5a4a9d?repository_url=registry.redhat.io/ubi9&tag=9.1.0-1750"), + DepPkg: pUrlToPkgDiscardError("pkg:rpm/redhat/python3-libcomps@0.1.18-1.el9?arch=x86_64"), + DepPkgMatchFlag: generated.MatchFlags{Pkg: generated.PkgMatchTypeSpecificVersion}, IsDependency: &generated.IsDependencyInputSpec{ DependencyType: generated.DependencyTypeUnknown, VersionRange: "0.1.18-1.el9",