Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add merging functionality #12

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
incorporate bom link; add test files for components merge
Signed-off-by: nscuro <[email protected]>
nscuro committed Feb 25, 2022

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
commit a2657d9777cef49699352ef02c87b7b3d918934c
69 changes: 69 additions & 0 deletions cyclonedx.go
Original file line number Diff line number Diff line change
@@ -23,6 +23,9 @@ import (
"errors"
"fmt"
"io"

"github.com/google/uuid"
"github.com/gowebpki/jcs"
)

const (
@@ -164,6 +167,37 @@ type Component struct {
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
}

func (c Component) bomReference() string {
return c.BOMRef
}

func (c *Component) setBOMReference(ref string) {
c.BOMRef = ref
}

// TODO: Can we solve this more elegantly?
type componentRefSeed Component

func (c componentRefSeed) MarshalJSON() ([]byte, error) {
c.BOMRef = ""

componentJSON, err := json.Marshal(Component(c))
if err != nil {
return nil, err
}

return jcs.Transform(componentJSON)
}

func (c Component) generateBOMReference() (string, error) {
componentJSON, err := json.Marshal(componentRefSeed(c))
if err != nil {
return "", err
}

return uuid.NewSHA1(uuid.MustParse("369fac08-d4a0-452e-b4b1-de87a0f376c6"), componentJSON).String(), nil
}

type Composition struct {
Aggregate CompositionAggregate `json:"aggregate" xml:"aggregate"`
Assemblies *[]BOMReference `json:"assemblies,omitempty" xml:"assemblies>assembly,omitempty"`
@@ -520,6 +554,17 @@ type Property struct {
Value string `json:"value" xml:",innerxml"`
}

// referrer is an internal utility interface that is used
// to address bom elements that have a BOM reference.
type referrer interface {
bomReference() string
setBOMReference(ref string)

// generateBOMReference returns a new value intended to be used as BOM reference.
// Given the same state of the referrer, generateBOMReference must return the same result.
generateBOMReference() (string, error)
}

type ReleaseNotes struct {
Type string `json:"type" xml:"type"`
Title string `json:"title,omitempty" xml:"title,omitempty"`
@@ -570,6 +615,18 @@ type Service struct {
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
}

func (s Service) bomReference() string {
return s.BOMRef
}

func (s *Service) setBOMReference(ref string) {
s.BOMRef = ref
}

func (s Service) generateBOMReference() (string, error) {
return "", nil
}

type Severity string

const (
@@ -625,6 +682,18 @@ type Vulnerability struct {
Affects *[]Affects `json:"affects,omitempty" xml:"affects>target,omitempty"`
}

func (v Vulnerability) bomReference() string {
return v.BOMRef
}

func (v *Vulnerability) setBOMReference(ref string) {
v.BOMRef = ref
}

func (v Vulnerability) generateBOMReference() (string, error) {
return "", nil
}

type VulnerabilityAnalysis struct {
State ImpactAnalysisState `json:"state,omitempty" xml:"state,omitempty"`
Justification ImpactAnalysisJustification `json:"justification,omitempty" xml:"justification,omitempty"`
21 changes: 21 additions & 0 deletions cyclonedx_test.go
Original file line number Diff line number Diff line change
@@ -20,6 +20,8 @@ package cyclonedx
import (
"encoding/json"
"encoding/xml"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
@@ -217,3 +219,22 @@ func TestLicenses_UnmarshalXML(t *testing.T) {
err = xml.Unmarshal([]byte("<Licenses><somethingElse>expressionValue</somethingElse></Licenses>"), licenses)
assert.Error(t, err)
}

func readTestBOM(t *testing.T, filePath string) *BOM {
format := BOMFileFormatJSON
if filepath.Ext(filePath) == ".xml" {
format = BOMFileFormatXML
}

file, err := os.Open(filePath)
require.NoError(t, err)
defer func() {
_ = file.Close()
}()

var bom BOM
err = NewBOMDecoder(file, format).Decode(&bom)
require.NoError(t, err)

return &bom
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -4,5 +4,9 @@ go 1.15

require (
github.com/bradleyjkemp/cupaloy/v2 v2.7.0
github.com/google/go-cmp v0.5.7
github.com/google/uuid v1.3.0
github.com/gowebpki/jcs v1.0.0
github.com/mitchellh/copystructure v1.2.0
github.com/stretchr/testify v1.7.0
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -3,13 +3,25 @@ github.com/bradleyjkemp/cupaloy/v2 v2.7.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1l
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gowebpki/jcs v1.0.0 h1:0pZtOgGetfH/L7yXb4KWcJqIyZNA43WXFyMd7ftZACw=
github.com/gowebpki/jcs v1.0.0/go.mod h1:CID1cNZ+sHp1CCpAR8mPf6QRtagFBgPJE0FCUQ6+BrI=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
99 changes: 99 additions & 0 deletions link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package cyclonedx

import (
"fmt"
"net/url"
"regexp"
"strconv"

"github.com/google/uuid"
)

var bomLinkRegex = regexp.MustCompile(`^urn:cdx:(?P<serialNumber>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/(?P<version>[0-9]+)(?:#(?P<bomRef>[0-9a-zA-Z\-._~%!$&'()*+,;=:@\/?]+))?$`)

// IsBOMLink TODO
func IsBOMLink(s string) bool {
return bomLinkRegex.MatchString(s)
}

// BOMLink TODO
type BOMLink struct {
SerialNumber uuid.UUID // Serial number of the linked BOM
Version int // Version of the linked BOM
Reference string // Reference of the linked element
}

// NewBOMLink TODO
func NewBOMLink(bom *BOM, elem referrer) (*BOMLink, error) {
if bom == nil {
return nil, fmt.Errorf("bom is nil")
}
if bom.SerialNumber == "" {
return nil, fmt.Errorf("missing serial number")
}
if bom.Version < 1 {
return nil, fmt.Errorf("versions below 1 are not allowed")
}

serial, err := uuid.Parse(bom.SerialNumber)
if err != nil {
return nil, fmt.Errorf("invalid serial number: %w", err)
}

if elem == nil {
return &BOMLink{
SerialNumber: serial,
Version: bom.Version,
}, nil
}

return &BOMLink{
SerialNumber: serial,
Version: bom.Version,
Reference: elem.bomReference(),
}, nil
}

// String TODO
func (b BOMLink) String() string {
if b.Reference == "" {
return fmt.Sprintf("urn:cdx:%s/%d", b.SerialNumber, b.Version)
}

return fmt.Sprintf("urn:cdx:%s/%d#%s", b.SerialNumber, b.Version, url.QueryEscape(b.Reference))
}

// ParseBOMLink TODO
func ParseBOMLink(s string) (*BOMLink, error) {
matches := bomLinkRegex.FindStringSubmatch(s)
if len(matches) < 3 || len(matches) > 4 {
return nil, fmt.Errorf("")
}

serial, err := uuid.Parse(matches[1])
if err != nil {
return nil, fmt.Errorf("invalid serial number: %w", err)
}
version, err := strconv.Atoi(matches[2])
if err != nil {
return nil, fmt.Errorf("invalid version: %w", err)
}

if len(matches) == 4 {
bomRef, err := url.QueryUnescape(matches[3])
if err != nil {
return nil, fmt.Errorf("invalid reference: %w", err)
}

return &BOMLink{
SerialNumber: serial,
Version: version,
Reference: bomRef,
}, nil
}

return &BOMLink{
SerialNumber: serial,
Version: version,
}, nil
}
15 changes: 15 additions & 0 deletions link_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package cyclonedx

import "testing"

func TestIsBOMLink(t *testing.T) {
// TODO
}

func TestNewBOMLink(t *testing.T) {
// TODO
}

func TestParseBOMLink(t *testing.T) {
// TODO
}
Loading