Skip to content

Commit

Permalink
fix: json validation allow arbitrary $schema value (#613)
Browse files Browse the repository at this point in the history
fixes #612

---------

Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
  • Loading branch information
jkowalleck committed May 6, 2024
1 parent 0d00496 commit 08b7c60
Show file tree
Hide file tree
Showing 11 changed files with 61 additions and 47 deletions.
2 changes: 1 addition & 1 deletion cyclonedx/schema/_res/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ changes:
1. `https?://cyclonedx.org/schema/spdx` was replaced with `spdx.SNAPSHOT.xsd`
2. `spdx.schema.json` was replaced with `spdx.SNAPSHOT.schema.json`
3. `jsf-0.82.schema.json` was replaced with `jsf-0.82.SNAPSHOT.schema.json`
4. `properties.$schema.enum` was fixed to match `$id`
4. `properties.$schema.enum` was removed
5. `required.version` removed, as it is actually optional with default value
5 changes: 1 addition & 4 deletions cyclonedx/schema/_res/bom-1.2-strict.SNAPSHOT.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string",
"enum": [
"http://cyclonedx.org/schema/bom-1.2b.schema.json"
]
"type": "string"
},
"bomFormat": {
"$id": "#/properties/bomFormat",
Expand Down
5 changes: 1 addition & 4 deletions cyclonedx/schema/_res/bom-1.3-strict.SNAPSHOT.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string",
"enum": [
"http://cyclonedx.org/schema/bom-1.3a.schema.json"
]
"type": "string"
},
"bomFormat": {
"$id": "#/properties/bomFormat",
Expand Down
5 changes: 1 addition & 4 deletions cyclonedx/schema/_res/bom-1.4.SNAPSHOT.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string",
"enum": [
"http://cyclonedx.org/schema/bom-1.4.schema.json"
]
"type": "string"
},
"bomFormat": {
"type": "string",
Expand Down
5 changes: 1 addition & 4 deletions cyclonedx/schema/_res/bom-1.5.SNAPSHOT.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string",
"enum": [
"http://cyclonedx.org/schema/bom-1.5.schema.json"
]
"type": "string"
},
"bomFormat": {
"type": "string",
Expand Down
17 changes: 7 additions & 10 deletions tests/_data/own/json/1.2/bom_with_mixed_licenses.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions tests/_data/own/json/1.3/bom_with_mixed_licenses.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion tests/test_deserialize_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ def test(ls: LicenseRepository) -> None:
with open(json_file) as f:
json = json_loads(f.read())
bom: Bom = Bom.from_json(json)
test(bom.metadata.licenses)
if sv is not SchemaVersion.V1_2:
test(bom.metadata.licenses)
test(bom.metadata.component.licenses)
test(list(bom.components)[0].licenses)
test(list(bom.services)[0].licenses)
26 changes: 20 additions & 6 deletions tests/test_validation_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# Copyright (c) OWASP Foundation. All Rights Reserved.

from glob import iglob
from itertools import chain
from os.path import join
from typing import Generator
from unittest import TestCase
Expand All @@ -25,18 +26,25 @@
from cyclonedx.exception import MissingOptionalDependencyException
from cyclonedx.schema import OutputFormat, SchemaVersion
from cyclonedx.validation.json import JsonStrictValidator, JsonValidator
from tests import SCHEMA_TESTDATA_DIRECTORY, DpTuple
from tests import OWN_DATA_DIRECTORY, SCHEMA_TESTDATA_DIRECTORY, DpTuple

UNSUPPORTED_SCHEMA_VERSIONS = {SchemaVersion.V1_0, SchemaVersion.V1_1, }


def _dp(prefix: str) -> Generator:
def _dp_sv_tf(prefix: str) -> Generator:
return (
DpTuple((sv, tf)) for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS
for tf in iglob(join(SCHEMA_TESTDATA_DIRECTORY, sv.to_version(), f'{prefix}-*.json'))
)


def _dp_sv_own() -> Generator:
return (
DpTuple((sv, tf)) for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS
for tf in iglob(join(OWN_DATA_DIRECTORY, 'json', sv.to_version(), '*.json'))
)


@ddt
class TestJsonValidator(TestCase):

Expand All @@ -51,7 +59,10 @@ def test_throws_with_unsupported_schema_version(self, schema_version: SchemaVers
with self.assertRaisesRegex(ValueError, 'Unsupported schema_version'):
JsonValidator(schema_version)

@idata(_dp('valid'))
@idata(chain(
_dp_sv_tf('valid'),
_dp_sv_own()
))
@unpack
def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: str) -> None:
validator = JsonValidator(schema_version)
Expand All @@ -63,7 +74,7 @@ def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: s
self.skipTest('MissingOptionalDependencyException')
self.assertIsNone(validation_error)

@idata(_dp('invalid'))
@idata(_dp_sv_tf('invalid'))
@unpack
def test_validate_expected_error(self, schema_version: SchemaVersion, test_data_file: str) -> None:
validator = JsonValidator(schema_version)
Expand All @@ -85,7 +96,10 @@ def test_throws_with_unsupported_schema_version(self, schema_version: SchemaVers
with self.assertRaisesRegex(ValueError, 'Unsupported schema_version'):
JsonStrictValidator(schema_version)

@idata(_dp('valid'))
@idata(chain(
_dp_sv_tf('valid'),
_dp_sv_own()
))
@unpack
def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: str) -> None:
validator = JsonStrictValidator(schema_version)
Expand All @@ -97,7 +111,7 @@ def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: s
self.skipTest('MissingOptionalDependencyException')
self.assertIsNone(validation_error)

@idata(_dp('invalid'))
@idata(_dp_sv_tf('invalid'))
@unpack
def test_validate_expected_error(self, schema_version: SchemaVersion, test_data_file: str) -> None:
validator = JsonStrictValidator(schema_version)
Expand Down
19 changes: 15 additions & 4 deletions tests/test_validation_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# Copyright (c) OWASP Foundation. All Rights Reserved.

from glob import iglob
from itertools import chain
from os.path import join
from typing import Generator
from unittest import TestCase
Expand All @@ -25,18 +26,25 @@
from cyclonedx.exception import MissingOptionalDependencyException
from cyclonedx.schema import OutputFormat, SchemaVersion
from cyclonedx.validation.xml import XmlValidator
from tests import SCHEMA_TESTDATA_DIRECTORY, DpTuple
from tests import OWN_DATA_DIRECTORY, SCHEMA_TESTDATA_DIRECTORY, DpTuple

UNSUPPORTED_SCHEMA_VERSIONS = set()


def _dp(prefix: str) -> Generator:
def _dp_sv_tf(prefix: str) -> Generator:
return (
DpTuple((sv, tf)) for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS
for tf in iglob(join(SCHEMA_TESTDATA_DIRECTORY, sv.to_version(), f'{prefix}-*.xml'))
)


def _dp_sv_own() -> Generator:
return (
DpTuple((sv, tf)) for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS
for tf in iglob(join(OWN_DATA_DIRECTORY, 'xml', sv.to_version(), '*.xml'))
)


@ddt
class TestXmlValidator(TestCase):

Expand All @@ -51,7 +59,10 @@ def test_throws_with_unsupported_schema_version(self, schema_version: SchemaVers
with self.assertRaisesRegex(ValueError, f'unsupported schema_version: {schema_version}'):
XmlValidator(schema_version)

@idata(_dp('valid'))
@idata(chain(
_dp_sv_tf('valid'),
_dp_sv_own()
))
@unpack
def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: str) -> None:
validator = XmlValidator(schema_version)
Expand All @@ -63,7 +74,7 @@ def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: s
self.skipTest('MissingOptionalDependencyException')
self.assertIsNone(validation_error)

@idata(_dp('invalid'))
@idata(_dp_sv_tf('invalid'))
@unpack
def test_validate_expected_error(self, schema_version: SchemaVersion, test_data_file: str) -> None:
validator = XmlValidator(schema_version)
Expand Down
11 changes: 6 additions & 5 deletions tools/schema-downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
]
}

# "version" is not required but optional with a default value!
# this is wrong in schema<1.5
# "$schema" is not required but optional.
# that enum constraint value there is complicated -> remove it.
# See https://github.com/CycloneDX/specification/issues/402
# See https://github.com/CycloneDX/specification/pull/403
_BOM_SCHEMA_ENUM_RE = re.compile(
r'("\$id": "(http://cyclonedx\.org/schema/bom.+?\.schema\.json)".*"enum": \[\s+")'
r'http://cyclonedx\.org/schema/bom.+?\.schema\.json"',
r',?\s*"enum":\s*\[\s*"http://cyclonedx\.org/schema/.+?\.schema\.json"\s*\]',
re.DOTALL)
_BOM_SCHEMA_ENUM_REPL = r'\1\2"'
_BOM_SCHEMA_ENUM_REPL = r''


# "version" is not required but optional with a default value!
Expand Down

0 comments on commit 08b7c60

Please sign in to comment.