Skip to content

Commit

Permalink
feat(spec1-5): add support for machine learning
Browse files Browse the repository at this point in the history
Signed-off-by: nscuro <nscuro@protonmail.com>
  • Loading branch information
nscuro committed Jul 29, 2023
1 parent e73d301 commit bcf4b6f
Show file tree
Hide file tree
Showing 10 changed files with 747 additions and 0 deletions.
6 changes: 6 additions & 0 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ func componentConverter(specVersion SpecVersion) func(*Component) {
}
}

if specVersion < SpecVersion1_5 {
c.ModelCard = nil
c.Data = nil
}

if !specVersion.supportsComponentType(c.Type) {
c.Type = ComponentTypeApplication
}
Expand Down Expand Up @@ -382,6 +387,7 @@ func (sv SpecVersion) supportsExternalReferenceType(ert ExternalReferenceType) b
ERTypeDynamicAnalysisReport,
ERTypeExploitabilityStatement,
ERTypeMaturityReport,
ERTypeModelCard,
ERTypePentestReport,
ERTypeQualityMetrics,
ERTypeRiskAssessment,
Expand Down
129 changes: 129 additions & 0 deletions cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,58 @@ type Component struct {
Components *[]Component `json:"components,omitempty" xml:"components>component,omitempty"`
Evidence *Evidence `json:"evidence,omitempty" xml:"evidence,omitempty"`
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
ModelCard *MLModelCard `json:"modelCard,omitempty" xml:"modelCard,omitempty"`
Data *ComponentData `json:"data,omitempty" xml:"data,omitempty"`
}

type ComponentData struct {
BOMRef string `json:"bom-ref,omitempty" xml:"bom-ref,attr,omitempty"`
Type ComponentDataType `json:"type,omitempty" xml:"type,omitempty"`
Name string `json:"name,omitempty" xml:"name,omitempty"`
Contents *ComponentDataContents `json:"contents,omitempty" xml:"contents,omitempty"`
Classification string `json:"classification,omitempty" xml:"classification,omitempty"`
SensitiveData *[]string `json:"sensitiveData,omitempty" xml:"sensitiveData,omitempty"`
Graphics *ComponentDataGraphics `json:"graphics,omitempty" xml:"graphics,omitempty"`
Description string `json:"description,omitempty" xml:"description,omitempty"`
Governance *ComponentDataGovernance `json:"governance,omitempty" xml:"governance,omitempty"`
}

type ComponentDataContents struct {
Attachment *AttachedText `json:"attachment,omitempty" xml:"attachment,omitempty"`
URL string `json:"url,omitempty" xml:"url,omitempty"`
Properties *[]Property `json:"properties,omitempty" xml:"properties,omitempty"`
}

type ComponentDataGovernance struct {
Custodians *[]ComponentDataGovernanceResponsibleParty `json:"custodians,omitempty" xml:"custodians>custodian,omitempty"`
Stewards *[]ComponentDataGovernanceResponsibleParty `json:"stewards,omitempty" xml:"stewards>steward,omitempty"`
Owners *[]ComponentDataGovernanceResponsibleParty `json:"owners,omitempty" xml:"owners>owner,omitempty"`
}

type ComponentDataGovernanceResponsibleParty struct {
Organization *OrganizationalEntity `json:"organization,omitempty" xml:"organization,omitempty"`
Contact *OrganizationalContact `json:"contact,omitempty" xml:"contact,omitempty"`
}

type ComponentDataGraphic struct {
Name string `json:"name,omitempty" xml:"name,omitempty"`
Image *AttachedText `json:"image,omitempty" xml:"image,omitempty"`
}

type ComponentDataGraphics struct {
Description string `json:"description,omitempty" xml:"description,omitempty"`
Collection *[]ComponentDataGraphic `json:"collection,omitempty" xml:"collection>graphic,omitempty"`
}

type ComponentDataType string

const (
ComponentDataTypeConfiguration ComponentDataType = "configuration"
ComponentDataTypeDataset ComponentDataType = "dataset"
ComponentDataTypeOther ComponentDataType = "other"
ComponentDataTypeSourceCode ComponentDataType = "source-code"
)

type Composition struct {
BOMRef string `json:"bom-ref,omitempty" xml:"bom-ref,attr,omitempty"`
Aggregate CompositionAggregate `json:"aggregate" xml:"aggregate"`
Expand Down Expand Up @@ -258,6 +308,7 @@ const (
ERTypeLicense ExternalReferenceType = "license"
ERTypeMailingList ExternalReferenceType = "mailing-list"
ERTypeMaturityReport ExternalReferenceType = "maturity-report"
ERTypeModelCard ExternalReferenceType = "model-card"
ERTypeOther ExternalReferenceType = "other"
ERTypePentestReport ExternalReferenceType = "pentest-report"
ERTypeQualityMetrics ExternalReferenceType = "quality-metrics"
Expand Down Expand Up @@ -451,6 +502,84 @@ type Metadata struct {
Properties *[]Property `json:"properties,omitempty" xml:"properties>property,omitempty"`
}

type MLDatasetChoice struct {
Ref string `json:"-" xml:"-"`
ComponentData *ComponentData `json:"-" xml:"-"`
}

type MLInputOutputParameters struct {
Format string `json:"format,omitempty" xml:"format,omitempty"`
}

type MLModelCard struct {
BOMRef string `json:"bom-ref,omitempty" xml:"bom-ref,attr,omitempty"`
ModelParameters *MLModelParameters `json:"modelParameters,omitempty" xml:"modelParameters,omitempty"`
QuantitativeAnalysis *MLQuantitativeAnalysis `json:"quantitativeAnalysis,omitempty" xml:"quantitativeAnalysis,omitempty"`
Considerations *MLModelCardConsiderations `json:"considerations,omitempty" xml:"considerations,omitempty"`
}

type MLModelCardConsiderations struct {
Users *[]string `json:"users,omitempty" xml:"users>user,omitempty"`
UseCases *[]string `json:"useCases,omitempty" xml:"useCases>useCase,omitempty"`
TechnicalLimitations *[]string `json:"technicalLimitations,omitempty" xml:"technicalLimitations>technicalLimitation,omitempty"`
PerformanceTradeoffs *[]string `json:"performanceTradeoffs,omitempty" xml:"performanceTradeoffs>performanceTradeoff,omitempty"`
EthicalConsiderations *[]MLModelCardEthicalConsideration `json:"ethicalConsiderations,omitempty" xml:"ethicalConsiderations>ethicalConsideration,omitempty"`
FairnessAssessments *[]MLModelCardFairnessAssessment `json:"fairnessAssessments,omitempty" xml:"fairnessAssessments>fairnessAssessment,omitempty"`
}

type MLModelCardEthicalConsideration struct {
Name string `json:"name,omitempty" xml:"name,omitempty"`
MitigationStrategy string `json:"mitigationStrategy,omitempty" xml:"mitigationStrategy,omitempty"`
}

type MLModelCardFairnessAssessment struct {
GroupAtRisk string `json:"groupAtRisk,omitempty" xml:"groupAtRisk,omitempty"`
Benefits string `json:"benefits,omitempty" xml:"benefits,omitempty"`
Harms string `json:"harms,omitempty" xml:"harms,omitempty"`
MitigationStrategy string `json:"mitigationStrategy,omitempty" xml:"mitigationStrategy,omitempty"`
}

type MLModelParameters struct {
Approach *MLModelParametersApproach `json:"approach,omitempty" xml:"approach,omitempty"`
Task string `json:"task,omitempty" xml:"task,omitempty"`
ArchitectureFamily string `json:"architectureFamily,omitempty" xml:"architectureFamily,omitempty"`
ModelArchitecture string `json:"modelArchitecture,omitempty" xml:"modelArchitecture,omitempty"`
Datasets *[]MLDatasetChoice `json:"datasets,omitempty" xml:"datasets>dataset,omitempty"`
Inputs *[]MLInputOutputParameters `json:"inputs,omitempty" xml:"inputs>input,omitempty"`
Outputs *[]MLInputOutputParameters `json:"outputs,omitempty" xml:"outputs>output,omitempty"`
}

type MLModelParametersApproach struct {
Type MLModelParametersApproachType `json:"type,omitempty" xml:"type,omitempty"`
}

type MLModelParametersApproachType string

const (
MLModelParametersApproachTypeSupervised MLModelParametersApproachType = "supervised"
MLModelParametersApproachTypeUnsupervised MLModelParametersApproachType = "unsupervised"
MLModelParametersApproachTypeReinforcementLearning MLModelParametersApproachType = "reinforcement-learning"
MLModelParametersApproachTypeSemiSupervised MLModelParametersApproachType = "semi-supervised"
MLModelParametersApproachTypeSelfSupervised MLModelParametersApproachType = "self-supervised"
)

type MLQuantitativeAnalysis struct {
PerformanceMetrics *[]MLPerformanceMetric `json:"performanceMetrics,omitempty" xml:"performanceMetrics>performanceMetric,omitempty"`
Graphics *ComponentDataGraphics `json:"graphics,omitempty" xml:"graphics,omitempty"`
}

type MLPerformanceMetric struct {
Type string `json:"type,omitempty" xml:"type,omitempty"`
Value string `json:"value,omitempty" xml:"value,omitempty"`
Slice string `json:"slice,omitempty" xml:"slice,omitempty"`
ConfidenceInterval *MLPerformanceMetricConfidenceInterval `json:"confidenceInterval,omitempty" xml:"confidenceInterval,omitempty"`
}

type MLPerformanceMetricConfidenceInterval struct {
LowerBound string `json:"lowerBound,omitempty" xml:"lowerBound,omitempty"`
UpperBound string `json:"upperBound,omitempty" xml:"upperBound,omitempty"`
}

type Note struct {
Locale string `json:"locale,omitempty" xml:"locale,omitempty"`
Text AttachedText `json:"text" xml:"text"`
Expand Down
39 changes: 39 additions & 0 deletions cyclonedx_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,45 @@ import (
"encoding/json"
)

type mlDatasetChoiceRefJSON struct {
Ref string `json:"ref" xml:"-"`
}

func (dc MLDatasetChoice) MarshalJSON() ([]byte, error) {
if dc.Ref != "" {
return json.Marshal(mlDatasetChoiceRefJSON{Ref: dc.Ref})
} else if dc.ComponentData != nil {
return json.Marshal(dc.ComponentData)
}

return []byte("{}"), nil
}

func (dc *MLDatasetChoice) UnmarshalJSON(bytes []byte) error {
var refObj mlDatasetChoiceRefJSON
err := json.Unmarshal(bytes, &refObj)
if err != nil {
return err
}

if refObj.Ref != "" {
dc.Ref = refObj.Ref
return nil
}

var componentData ComponentData
err = json.Unmarshal(bytes, &componentData)
if err != nil {
return err
}

if componentData != (ComponentData{}) {
dc.ComponentData = &componentData
}

return nil
}

func (sv SpecVersion) MarshalJSON() ([]byte, error) {
return json.Marshal(sv.String())
}
Expand Down
82 changes: 82 additions & 0 deletions cyclonedx_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,85 @@
// Copyright (c) OWASP Foundation. All Rights Reserved.

package cyclonedx

import (
"encoding/json"
"github.com/stretchr/testify/require"
"testing"
)

func TestMLDatasetChoice_MarshalJSON(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
choice := MLDatasetChoice{}
jsonBytes, err := json.Marshal(choice)
require.NoError(t, err)
require.Equal(t, "{}", string(jsonBytes))
})

t.Run("WithRef", func(t *testing.T) {
choice := MLDatasetChoice{Ref: "foo"}
jsonBytes, err := json.Marshal(choice)
require.NoError(t, err)
require.Equal(t, `{"ref":"foo"}`, string(jsonBytes))
})

t.Run("WithComponentData", func(t *testing.T) {
choice := MLDatasetChoice{
ComponentData: &ComponentData{
BOMRef: "foo",
Name: "bar",
},
}
jsonBytes, err := json.Marshal(choice)
require.NoError(t, err)
require.Equal(t, `{"bom-ref":"foo","name":"bar"}`, string(jsonBytes))
})

t.Run("WithRefAndComponentData", func(t *testing.T) {
choice := MLDatasetChoice{
Ref: "foo",
ComponentData: &ComponentData{
BOMRef: "bar",
Name: "baz",
},
}
jsonBytes, err := json.Marshal(choice)
require.NoError(t, err)
require.Equal(t, `{"ref":"foo"}`, string(jsonBytes))
})
}

func TestMLDatasetChoice_UnmarshalJSON(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
var choice MLDatasetChoice
err := json.Unmarshal([]byte(`{}`), &choice)
require.NoError(t, err)
require.Equal(t, MLDatasetChoice{}, choice)
})

t.Run("WithRef", func(t *testing.T) {
var choice MLDatasetChoice
err := json.Unmarshal([]byte(`{"ref":"foo"}`), &choice)
require.NoError(t, err)
require.Equal(t, "foo", choice.Ref)
require.Nil(t, choice.ComponentData)
})

t.Run("WithComponentData", func(t *testing.T) {
var choice MLDatasetChoice
err := json.Unmarshal([]byte(`{"bom-ref":"foo","name":"bar"}`), &choice)
require.NoError(t, err)
require.Empty(t, choice.Ref)
require.NotNil(t, choice.ComponentData)
require.Equal(t, "foo", choice.ComponentData.BOMRef)
require.Equal(t, "bar", choice.ComponentData.Name)
})

t.Run("WithRefAndComponentData", func(t *testing.T) {
var choice MLDatasetChoice
err := json.Unmarshal([]byte(`{"ref":"foo","bom-ref":"bar","name":"baz"}`), &choice)
require.NoError(t, err)
require.Equal(t, "foo", choice.Ref)
require.Nil(t, choice.ComponentData)
})
}
38 changes: 38 additions & 0 deletions cyclonedx_xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,44 @@ func (l *Licenses) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error {
return nil
}

type mlDatasetChoiceRefXML struct {
Ref string `json:"-" xml:"ref"`
}

type mlDatasetChoiceXML struct {
Ref string `json:"-" xml:"ref"`
ComponentData
}

func (dc MLDatasetChoice) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if dc.Ref != "" {
return e.EncodeElement(mlDatasetChoiceRefXML{Ref: dc.Ref}, start)
} else if dc.ComponentData != nil {
return e.EncodeElement(dc.ComponentData, start)
}

return nil
}

func (dc *MLDatasetChoice) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var choice mlDatasetChoiceXML
err := d.DecodeElement(&choice, &start)
if err != nil {
return err
}

if choice.Ref != "" {
dc.Ref = choice.Ref
return nil
}

if choice.ComponentData != (ComponentData{}) {
dc.ComponentData = &choice.ComponentData
}

return nil
}

func (sv SpecVersion) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(sv.String(), start)
}
Expand Down
Loading

0 comments on commit bcf4b6f

Please sign in to comment.