mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 18:15:42 +01:00
feat: Add function to find product identification helpers inspecting the tree (#505)
* feat: Add function to find product identification helpers inspecting the tree Signed-off-by: juan131 <jariza@vmware.com> * fix: simplify unit tests Signed-off-by: juan131 <jariza@vmware.com> * fix: also iterate over relationships Signed-off-by: juan131 <jariza@vmware.com> * fix: adapt example to use new library function Signed-off-by: juan131 <jariza@vmware.com> * Separate collecting and visiting of the product id helpers. --------- Signed-off-by: juan131 <jariza@vmware.com> Co-authored-by: Sascha L. Teichmann <sascha.teichmann@intevation.de>
This commit is contained in:
parent
b457dc872f
commit
9073a8a282
3 changed files with 258 additions and 96 deletions
61
csaf/util.go
61
csaf/util.go
|
|
@ -36,3 +36,64 @@ func ExtractProviderURL(r io.Reader, all bool) ([]string, error) {
|
||||||
}
|
}
|
||||||
return urls, nil
|
return urls, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectProductIdentificationHelpers returns a slice of all ProductIdentificationHelper
|
||||||
|
// for a given ProductID.
|
||||||
|
func (pt *ProductTree) CollectProductIdentificationHelpers(id ProductID) []*ProductIdentificationHelper {
|
||||||
|
var helpers []*ProductIdentificationHelper
|
||||||
|
pt.FindProductIdentificationHelpers(
|
||||||
|
id, func(helper *ProductIdentificationHelper) {
|
||||||
|
helpers = append(helpers, helper)
|
||||||
|
})
|
||||||
|
return helpers
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindProductIdentificationHelpers calls visit on all ProductIdentificationHelper
|
||||||
|
// for a given ProductID by iterating over all full product names and branches
|
||||||
|
// recursively available in the ProductTree.
|
||||||
|
func (pt *ProductTree) FindProductIdentificationHelpers(
|
||||||
|
id ProductID,
|
||||||
|
visit func(*ProductIdentificationHelper),
|
||||||
|
) {
|
||||||
|
// Iterate over all full product names
|
||||||
|
if fpns := pt.FullProductNames; fpns != nil {
|
||||||
|
for _, fpn := range *fpns {
|
||||||
|
if fpn != nil &&
|
||||||
|
fpn.ProductID != nil && *fpn.ProductID == id &&
|
||||||
|
fpn.ProductIdentificationHelper != nil {
|
||||||
|
visit(fpn.ProductIdentificationHelper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over branches recursively
|
||||||
|
var recBranch func(b *Branch)
|
||||||
|
recBranch = func(b *Branch) {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fpn := b.Product; fpn != nil &&
|
||||||
|
fpn.ProductID != nil && *fpn.ProductID == id &&
|
||||||
|
fpn.ProductIdentificationHelper != nil {
|
||||||
|
visit(fpn.ProductIdentificationHelper)
|
||||||
|
}
|
||||||
|
for _, c := range b.Branches {
|
||||||
|
recBranch(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, b := range pt.Branches {
|
||||||
|
recBranch(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over relationships
|
||||||
|
if rels := pt.RelationShips; rels != nil {
|
||||||
|
for _, rel := range *rels {
|
||||||
|
if rel != nil {
|
||||||
|
if fpn := rel.FullProductName; fpn != nil && fpn.ProductID != nil &&
|
||||||
|
*fpn.ProductID == id && fpn.ProductIdentificationHelper != nil {
|
||||||
|
visit(fpn.ProductIdentificationHelper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
182
csaf/util_test.go
Normal file
182
csaf/util_test.go
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
// This file is Free Software under the MIT License
|
||||||
|
// without warranty, see README.md and LICENSES/MIT.txt for details.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) <https://www.bsi.bund.de>
|
||||||
|
// Software-Engineering: 2022 Intevation GmbH <https://intevation.de>
|
||||||
|
|
||||||
|
package csaf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProductTree_FindProductIdentificationHelpers(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Branches Branches
|
||||||
|
FullProductNames *FullProductNames
|
||||||
|
RelationShips *Relationships
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
id ProductID
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want []*ProductIdentificationHelper
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty product tree",
|
||||||
|
args: args{
|
||||||
|
id: "CSAFPID-0001",
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product tree with matching full product names",
|
||||||
|
fields: fields{
|
||||||
|
FullProductNames: &FullProductNames{{
|
||||||
|
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
|
||||||
|
ProductIdentificationHelper: &ProductIdentificationHelper{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
id: "CSAFPID-0001",
|
||||||
|
},
|
||||||
|
want: []*ProductIdentificationHelper{{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product tree with no matching full product names",
|
||||||
|
fields: fields{
|
||||||
|
FullProductNames: &FullProductNames{{
|
||||||
|
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
|
||||||
|
ProductIdentificationHelper: &ProductIdentificationHelper{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
id: "CSAFPID-0002",
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product tree with matching branches",
|
||||||
|
fields: fields{
|
||||||
|
Branches: Branches{{
|
||||||
|
Name: &[]string{"beta"}[0],
|
||||||
|
Product: &FullProductName{
|
||||||
|
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
|
||||||
|
ProductIdentificationHelper: &ProductIdentificationHelper{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Branches: Branches{{
|
||||||
|
Name: &[]string{"beta-2"}[0],
|
||||||
|
Product: &FullProductName{
|
||||||
|
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
|
||||||
|
ProductIdentificationHelper: &ProductIdentificationHelper{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta-2:*:*:*:*:*:*"}[0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
id: "CSAFPID-0001",
|
||||||
|
},
|
||||||
|
want: []*ProductIdentificationHelper{{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
|
||||||
|
}, {
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta-2:*:*:*:*:*:*"}[0],
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product tree with no matching branches",
|
||||||
|
fields: fields{
|
||||||
|
Branches: Branches{{
|
||||||
|
Name: &[]string{"beta"}[0],
|
||||||
|
Product: &FullProductName{
|
||||||
|
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
|
||||||
|
ProductIdentificationHelper: &ProductIdentificationHelper{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Branches: Branches{{
|
||||||
|
Name: &[]string{"beta-2"}[0],
|
||||||
|
Product: &FullProductName{
|
||||||
|
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
|
||||||
|
ProductIdentificationHelper: &ProductIdentificationHelper{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta-2:*:*:*:*:*:*"}[0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
id: "CSAFPID-0002",
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product tree with matching relationships",
|
||||||
|
fields: fields{
|
||||||
|
RelationShips: &Relationships{{
|
||||||
|
FullProductName: &FullProductName{
|
||||||
|
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
|
||||||
|
ProductIdentificationHelper: &ProductIdentificationHelper{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
id: "CSAFPID-0001",
|
||||||
|
},
|
||||||
|
want: []*ProductIdentificationHelper{{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product tree with no matching relationships",
|
||||||
|
fields: fields{
|
||||||
|
RelationShips: &Relationships{{
|
||||||
|
FullProductName: &FullProductName{
|
||||||
|
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
|
||||||
|
ProductIdentificationHelper: &ProductIdentificationHelper{
|
||||||
|
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
id: "CSAFPID-0002",
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
for _, testToRun := range tests {
|
||||||
|
test := testToRun
|
||||||
|
t.Run(test.name, func(tt *testing.T) {
|
||||||
|
tt.Parallel()
|
||||||
|
pt := &ProductTree{
|
||||||
|
Branches: test.fields.Branches,
|
||||||
|
FullProductNames: test.fields.FullProductNames,
|
||||||
|
RelationShips: test.fields.RelationShips,
|
||||||
|
}
|
||||||
|
if got := pt.CollectProductIdentificationHelpers(test.args.id); !reflect.DeepEqual(got, test.want) {
|
||||||
|
tt.Errorf("ProductTree.FindProductIdentificationHelpers() = %v, want %v",
|
||||||
|
got, test.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,9 +9,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
|
|
||||||
"github.com/csaf-poc/csaf_distribution/v3/csaf"
|
"github.com/csaf-poc/csaf_distribution/v3/csaf"
|
||||||
|
"github.com/csaf-poc/csaf_distribution/v3/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -35,106 +34,26 @@ func main() {
|
||||||
|
|
||||||
// run prints PURLs belonging to the given Product IDs.
|
// run prints PURLs belonging to the given Product IDs.
|
||||||
func run(files []string, ids string) error {
|
func run(files []string, ids string) error {
|
||||||
|
|
||||||
uf := newURLFinder(strings.Split(ids, ","))
|
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
adv, err := csaf.LoadAdvisory(file)
|
adv, err := csaf.LoadAdvisory(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading %q failed: %w", file, err)
|
return fmt.Errorf("loading %q failed: %w", file, err)
|
||||||
}
|
}
|
||||||
uf.findURLs(adv)
|
|
||||||
uf.dumpURLs()
|
for _, id := range strings.Split(ids, ",") {
|
||||||
uf.clear()
|
already := util.Set[csaf.PURL]{}
|
||||||
|
i := 0
|
||||||
|
adv.ProductTree.FindProductIdentificationHelpers(
|
||||||
|
csaf.ProductID(id),
|
||||||
|
func(h *csaf.ProductIdentificationHelper) {
|
||||||
|
if h.PURL != nil && !already.Contains(*h.PURL) {
|
||||||
|
already.Add(*h.PURL)
|
||||||
|
i++
|
||||||
|
fmt.Printf("%d. %s\n", i, *h.PURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// urlFinder helps to find the URLs of a set of product ids in advisories.
|
|
||||||
type urlFinder struct {
|
|
||||||
ids []csaf.ProductID
|
|
||||||
urls [][]csaf.PURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// newURLFinder creates a new urlFinder for given ids.
|
|
||||||
func newURLFinder(ids []string) *urlFinder {
|
|
||||||
uf := &urlFinder{
|
|
||||||
ids: make([]csaf.ProductID, len(ids)),
|
|
||||||
urls: make([][]csaf.PURL, len(ids)),
|
|
||||||
}
|
|
||||||
for i := range uf.ids {
|
|
||||||
uf.ids[i] = csaf.ProductID(ids[i])
|
|
||||||
}
|
|
||||||
return uf
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear resets the url finder after a run on an advisory.
|
|
||||||
func (uf *urlFinder) clear() {
|
|
||||||
for i := range uf.urls {
|
|
||||||
uf.urls[i] = uf.urls[i][:0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpURLs dumps the found URLs to stdout.
|
|
||||||
func (uf *urlFinder) dumpURLs() {
|
|
||||||
for i, urls := range uf.urls {
|
|
||||||
if len(urls) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Printf("Found URLs for %s:\n", uf.ids[i])
|
|
||||||
for j, url := range urls {
|
|
||||||
fmt.Printf("%d. %s\n", j+1, url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// findURLs find the URLs in an advisory.
|
|
||||||
func (uf *urlFinder) findURLs(adv *csaf.Advisory) {
|
|
||||||
tree := adv.ProductTree
|
|
||||||
if tree == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have found it and we have a valid URL add unique.
|
|
||||||
add := func(idx int, h *csaf.ProductIdentificationHelper) {
|
|
||||||
if idx != -1 && h != nil && h.PURL != nil &&
|
|
||||||
!slices.Contains(uf.urls[idx], *h.PURL) {
|
|
||||||
uf.urls[idx] = append(uf.urls[idx], *h.PURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First iterate over full product names.
|
|
||||||
if names := tree.FullProductNames; names != nil {
|
|
||||||
for _, name := range *names {
|
|
||||||
if name != nil && name.ProductID != nil {
|
|
||||||
add(slices.Index(uf.ids, *name.ProductID), name.ProductIdentificationHelper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second traverse the branches recursively.
|
|
||||||
var recBranch func(*csaf.Branch)
|
|
||||||
recBranch = func(b *csaf.Branch) {
|
|
||||||
if p := b.Product; p != nil && p.ProductID != nil {
|
|
||||||
add(slices.Index(uf.ids, *p.ProductID), p.ProductIdentificationHelper)
|
|
||||||
}
|
|
||||||
for _, c := range b.Branches {
|
|
||||||
recBranch(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, b := range tree.Branches {
|
|
||||||
recBranch(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Third iterate over relationships.
|
|
||||||
if tree.RelationShips != nil {
|
|
||||||
for _, rel := range *tree.RelationShips {
|
|
||||||
if rel != nil {
|
|
||||||
if fpn := rel.FullProductName; fpn != nil && fpn.ProductID != nil {
|
|
||||||
add(slices.Index(uf.ids, *fpn.ProductID), fpn.ProductIdentificationHelper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue