Skip to content

Commit

Permalink
Load/compile dependency schemas for any supported schema
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Rutkowski <[email protected]>
  • Loading branch information
mrutkows committed Nov 18, 2024
1 parent c221d3f commit f0bdde1
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 48 deletions.
106 changes: 75 additions & 31 deletions cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,66 @@ func validationError(document *schema.BOM, valid bool, err error) {
getLogger().Info(message)
}

func LoadSchemaDependencies(depSchemaLoader *gojsonschema.SchemaLoader, schemas []schema.FormatSchemaInstance, schemaNames []string) (err error) {
for _, schemaName := range schemaNames {
formatSchemaInstance, errMatch := schema.FindMatchingFormatSchemaInstance(
schemas, schemaName)
if errMatch != nil {
return fmt.Errorf("schema '%s' match not found in resources: '%s'", schemaName, schema.SCHEMA_FORMAT_COMMON)
}
getLogger().Debugf("Found: '%s': %v", schemaName, formatSchemaInstance)

getLogger().Debugf("Added schema '%s' to loader:...", formatSchemaInstance.File)
err = AddSharedSchemaToLoader(depSchemaLoader, formatSchemaInstance)
if err != nil {
return
}
}
return
}

func AddSharedSchemaToLoader(depSchemaLoader *gojsonschema.SchemaLoader, formatSchemaInstance schema.FormatSchemaInstance) (err error) {
getLogger().Debugf("Reading schema: '%s'...", formatSchemaInstance.File)
bSchema, errRead := resources.BOMSchemaFiles.ReadFile(formatSchemaInstance.File)

if errRead != nil {
return errRead
}
getLogger().Tracef("schema: %s", bSchema)
sharedSchemaLoader := gojsonschema.NewBytesLoader(bSchema)
depSchemaLoader.AddSchema(formatSchemaInstance.Url, sharedSchemaLoader)
return
}

func LoadCompileSchemaDependencies(bomSchemaLoader gojsonschema.JSONLoader, bomSchemaDependencies []string) (jsonBOMSchema *gojsonschema.Schema, err error) {

if len(bomSchemaDependencies) > 0 {
getLogger().Infof("Found schema dependencies: %v", bomSchemaDependencies)
// Create a schema loader we will add all dep. schemas into
depSchemaLoader := gojsonschema.NewSchemaLoader()

// Load common schema from application resources
var commonSchemas schema.FormatSchema
commonSchemas, err = SupportedFormatConfig.FindMatchingFormatSchema(schema.SCHEMA_FORMAT_COMMON)
if err != nil {
return
}
getLogger().Tracef("Found '%s' schemas: %v", schema.SCHEMA_FORMAT_COMMON, commonSchemas)

err = LoadSchemaDependencies(depSchemaLoader, commonSchemas.Schemas, bomSchemaDependencies)
if err != nil {
return
}

// Compile BOM schema (JSON) with the dependency schemas and return it
jsonBOMSchema, err = depSchemaLoader.Compile(bomSchemaLoader)
if err != nil {
return
}
}
return
}

func Validate(writer io.Writer, persistentFlags utils.PersistentCommandFlags, validateFlags utils.ValidateCommandFlags) (valid bool, bom *schema.BOM, schemaErrors []gojsonschema.ResultError, err error) {
getLogger().Enter()
defer getLogger().Exit()
Expand Down Expand Up @@ -213,8 +273,8 @@ func Validate(writer io.Writer, persistentFlags utils.PersistentCommandFlags, va

// Create a loader for the BOM (JSON) document
var documentLoader gojsonschema.JSONLoader
var schemaLoader gojsonschema.JSONLoader
var errRead error
var jsonBOMSchemaLoader gojsonschema.JSONLoader
var errRead, errLoadCompile error
var bSchema, bDocument []byte

if bDocument = bom.GetRawBytes(); len(bDocument) > 0 {
Expand All @@ -240,10 +300,8 @@ func Validate(writer io.Writer, persistentFlags utils.PersistentCommandFlags, va

// If caller "forced" a specific schema file (version), load it instead of
// any SchemaInfo found in config.json
// TODO: support remote schema load (via URL) with a flag (default should always be local file for security)
forcedSchemaFile := validateFlags.ForcedJsonSchemaFile
if forcedSchemaFile != "" {

if !isValidURIPrefix(forcedSchemaFile) {
// attempt to load as a local file
forcedSchemaFile = "file://" + forcedSchemaFile
Expand All @@ -257,7 +315,7 @@ func Validate(writer io.Writer, persistentFlags utils.PersistentCommandFlags, va
}

getLogger().Infof("Loading schema from '--force' flag: '%s'...", forcedSchemaFile)
schemaLoader = gojsonschema.NewReferenceLoader(forcedSchemaFile)
jsonBOMSchemaLoader = gojsonschema.NewReferenceLoader(forcedSchemaFile)
getLogger().Infof("Validating document using forced schema (i.e., '--force %s')", forcedSchemaFile)
} else {
// Load the matching JSON schema (format, version and variant) from embedded resources
Expand All @@ -271,33 +329,19 @@ func Validate(writer io.Writer, persistentFlags utils.PersistentCommandFlags, va
return INVALID, bom, schemaErrors, errRead
}

sl := gojsonschema.NewSchemaLoader()

schemaLoader = gojsonschema.NewBytesLoader(bSchema)

// NEW NEW NEW
JSF := "schema/cyclonedx/common/jsf-0.82.schema.json"
getLogger().Infof("Loading schema '%s'...", JSF)
bJsfSchema, _ := resources.BOMSchemaFiles.ReadFile(JSF)
getLogger().Tracef("schema: %s", bJsfSchema)
jsfSchemaLoader := gojsonschema.NewBytesLoader(bJsfSchema)
sl.AddSchema("http://cyclonedx.org/schema/jsf-0.82.schema.json", jsfSchemaLoader)

SPDXID := "schema/cyclonedx/common/spdx.schema.json"
getLogger().Infof("Loading schema '%s'...", SPDXID)
bSpdxIdSchema, _ := resources.BOMSchemaFiles.ReadFile(SPDXID)
getLogger().Tracef("schema: %s", bSpdxIdSchema)
spdxSchemaLoader := gojsonschema.NewBytesLoader(bSpdxIdSchema)
sl.AddSchema("http://cyclonedx.org/schema/spdx.schema.json", spdxSchemaLoader)

var errCompile error
jsonBOMSchema, errCompile = sl.Compile(schemaLoader)
if errCompile != nil {
getLogger().Error(errCompile)
// Create a schema loader for the primary BOM schema file
jsonBOMSchemaLoader = gojsonschema.NewBytesLoader(bSchema)

// If the BOM schema has $refs to other schemas, attempt to load and compile
// them from those included as built-in resources
jsonBOMSchema, errLoadCompile = LoadCompileSchemaDependencies(jsonBOMSchemaLoader, bom.SchemaInfo.Dependencies)
if err != nil {
return INVALID, bom, schemaErrors, errLoadCompile
}
}

if schemaLoader == nil {
// At this point we should have a BOM schema loader
if jsonBOMSchemaLoader == nil {
// we force result to INVALID as any errors from the library means
// we could NOT actually confirm the input documents validity
return INVALID, bom, schemaErrors, fmt.Errorf("unable to read schema: '%s'", schemaName)
Expand All @@ -314,7 +358,7 @@ func Validate(writer io.Writer, persistentFlags utils.PersistentCommandFlags, va
// externally referenced schemas over network)... attempt fixed retry...
if jsonBOMSchema == nil {
for i := 0; i < RETRY; i++ {
jsonBOMSchema, errLoad = gojsonschema.NewSchema(schemaLoader)
jsonBOMSchema, errLoad = gojsonschema.NewSchema(jsonBOMSchemaLoader)

if errLoad == nil {
break
Expand All @@ -327,7 +371,7 @@ func Validate(writer io.Writer, persistentFlags utils.PersistentCommandFlags, va
}
}

getLogger().Infof("Schema '%s' loaded.", schemaName)
getLogger().Infof("Schema '%s' loaded", schemaName)

// Validate against the schema and save result determination
getLogger().Infof("Validating '%s'...", bom.GetFilenameInterpolated())
Expand Down
22 changes: 13 additions & 9 deletions resources/config/config.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"formats": [
{
"canonicalName": "http://cyclonedx.org/schema/",
"propertyKeyFormat": "http://cyclonedx.org/schema/",
"propertyKeyVersion": "latest",
"canonicalName": "common",
"propertyKeyFormat": "",
"propertyKeyVersion": "version",
"propertyValueFormat": "latest",
"schemas": [
{
"version": "",
"variant": "",
"name": "jsf-0.82.schema.json",
"file": "resources/schema/cyclonedx/common/jsf-0.82.schema.json",
"file": "schema/cyclonedx/common/jsf-0.82.schema.json",
"development": "",
"url": "http://cyclonedx.org/schema/jsf-0.82.schema.json",
"default": false
Expand All @@ -19,7 +19,7 @@
"version": "",
"variant": "",
"name": "spdx.schema.json",
"file": "resources/schema/cyclonedx/common/spdx.schema.json",
"file": "schema/cyclonedx/common/spdx.schema.json",
"development": "",
"url": "http://cyclonedx.org/schema/spdx.schema.json",
"default": false
Expand Down Expand Up @@ -119,7 +119,8 @@
"file": "schema/cyclonedx/1.4/bom-1.4.schema.json",
"development": "https://github.com/CycloneDX/specification/blob/master/schema/bom-1.4.schema.json",
"url": "https://raw.githubusercontent.com/CycloneDX/specification/master/schema/bom-1.4.schema.json",
"default": false
"default": false,
"dependencies": ["jsf-0.82.schema.json", "spdx.schema.json"]
},
{
"version": "1.5",
Expand All @@ -128,7 +129,8 @@
"file": "schema/cyclonedx/1.5/bom-1.5.schema.json",
"development": "https://github.com/CycloneDX/specification/blob/master/schema/bom-1.5.schema.json",
"url": "https://raw.githubusercontent.com/CycloneDX/specification/master/schema/bom-1.5.schema.json",
"default": false
"default": false,
"dependencies": ["jsf-0.82.schema.json", "spdx.schema.json"]
},
{
"version": "1.6",
Expand All @@ -137,7 +139,8 @@
"file": "schema/cyclonedx/1.6/bom-1.6.schema.json",
"development": "https://github.com/CycloneDX/specification/blob/master/schema/bom-1.6.schema.json",
"url": "https://raw.githubusercontent.com/CycloneDX/specification/master/schema/bom-1.6.schema.json",
"default": true
"default": true,
"dependencies": ["jsf-0.82.schema.json", "spdx.schema.json"]
},
{
"version": "1.3",
Expand All @@ -155,7 +158,8 @@
"file": "schema/test/bom-1.4-custom.schema.json",
"development":"https://github.com/CycloneDX/sbom-utility/blob/main/resources/schema/test/bom-1.4-custom.schema.json",
"url": "",
"default": false
"default": false,
"dependencies": ["jsf-0.82.schema.json", "spdx.schema.json"]
}
]
}
Expand Down
50 changes: 42 additions & 8 deletions schema/schema_formats.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
const (
SCHEMA_FORMAT_SPDX = "SPDX"
SCHEMA_FORMAT_CYCLONEDX = "CycloneDX"
SCHEMA_FORMAT_COMMON = "common"
)

const (
Expand Down Expand Up @@ -93,14 +94,15 @@ type FormatSchema struct {
// e.g., key string
// where key: SchemaKey{ID_CYCLONEDX, VERSION_CYCLONEDX_1_3, false},
type FormatSchemaInstance struct {
Name string `json:"name"`
Version string `json:"version"`
Development string `json:"development"`
File string `json:"file"`
Url string `json:"url"`
Default bool `json:"default"`
Variant string `json:"variant"`
Format string `json:"format"` // value set from parent FormatSchema's `CanonicalName`
Name string `json:"name"`
Version string `json:"version"`
Development string `json:"development"`
File string `json:"file"`
Url string `json:"url"`
Default bool `json:"default"`
Variant string `json:"variant"`
Format string `json:"format"` // value set from parent FormatSchema's `CanonicalName`
Dependencies []string `json:"dependencies"`
}

func (config *BOMFormatAndSchemaConfig) Reset() {
Expand Down Expand Up @@ -201,6 +203,38 @@ func (schemaConfig *BOMFormatAndSchemaConfig) FindFormatAndSchema(bom *BOM) (err
return
}

func (schemaConfig *BOMFormatAndSchemaConfig) FindMatchingFormatSchema(name string) (formatSchema FormatSchema, err error) {
getLogger().Enter()
defer getLogger().Exit()

// Iterate over configured schema formats to find matching name
for _, formatSchema = range schemaConfig.Formats {
if name == formatSchema.CanonicalName {
return
}
}

// Inform user we could not find a matching schema
err = NewUnsupportedSchemaError(MSG_CONFIG_SCHEMA_VERSION_NOT_FOUND, name, "", "")
return
}

func FindMatchingFormatSchemaInstance(formatSchemas []FormatSchemaInstance, schemaName string) (formatSchemaInstance FormatSchemaInstance, err error) {
getLogger().Enter()
defer getLogger().Exit()

// Iterate over known formats to see if SBOM document contains a known value
for _, formatSchemaInstance = range formatSchemas {
if schemaName == formatSchemaInstance.Name {
return
}
}

// if we reach here, we did not find the format in our configuration (list)
err = NewUnsupportedSchemaError(MSG_CONFIG_SCHEMA_VERSION_NOT_FOUND, schemaName, "", "")
return
}

// There are multiple variants possible within a given version
func (bom *BOM) findSchemaVersionWithVariant(format FormatSchema, version string, variant string) (err error) {
getLogger().Enter()
Expand Down

0 comments on commit f0bdde1

Please sign in to comment.