diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fec4d977..0d80653b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,9 +4,9 @@ on: push: branches: [ 'main' ] workflow_dispatch: - + env: - PYTHON_VERSION_DEFAULT: "3.10" + PYTHON_VERSION_DEFAULT: "3.11" POETRY_VERSION: "1.1.12" jobs: @@ -22,14 +22,14 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - + - name: Setup python # see https://github.com/actions/setup-python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION_DEFAULT }} architecture: 'x64' - + - name: Install and configure Poetry # See https://github.com/marketplace/actions/install-poetry-action uses: snok/install-poetry@v1 @@ -38,17 +38,17 @@ jobs: virtualenvs-create: true virtualenvs-in-project: true installer-parallel: true - + - name: Install dependencies run: poetry install --no-root - name: View poetry version run: poetry --version - + - name: Python Semantic Release # see https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html # see https://github.com/relekang/python-semantic-release - uses: relekang/python-semantic-release@v7.33.1 + uses: relekang/python-semantic-release@v7.33.2 with: github_token: ${{ secrets.GITHUB_TOKEN }} repository_username: __token__ diff --git a/.github/workflows/manual-release-candidate.yml b/.github/workflows/manual-release-candidate.yml index 47376642..c56aaab9 100644 --- a/.github/workflows/manual-release-candidate.yml +++ b/.github/workflows/manual-release-candidate.yml @@ -25,7 +25,7 @@ jobs: python -m pip install poetry --upgrade pip poetry config virtualenvs.create false poetry install - python -m pip install python-semantic-release + python -m pip install python-semantic-release==7.28.1 - name: Apply Pre Release Version run: | RC_VERSION="$(semantic-release --noop --major print-version)-${{ github.event.inputs.release_candidate_suffix }}" diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index ef65f0a8..4269388e 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -4,7 +4,7 @@ name: Python CI on: push: - branches: ["master", "main"] + branches: ["main"] pull_request: branches-ignore: ['dependabot/**'] workflow_dispatch: @@ -15,8 +15,8 @@ on: env: REPORTS_DIR: CI_reports - PYTHON_VERISON_DEFAULT: "3.10" - POETRY_VERSION: "1.1.11" + PYTHON_VERSION_DEFAULT: "3.11" + POETRY_VERSION: "1.1.12" jobs: coding-standards: @@ -27,19 +27,23 @@ jobs: - name: Checkout # see https://github.com/actions/checkout uses: actions/checkout@v3 + - name: Setup Python Environment # see https://github.com/actions/setup-python uses: actions/setup-python@v4 with: - python-version: ${{ env.PYTHON_VERISON_DEFAULT }} + python-version: ${{ env.PYTHON_VERSION_DEFAULT }} architecture: 'x64' + - name: Install poetry # see https://github.com/marketplace/actions/setup-poetry uses: Gr1N/setup-poetry@v8 with: poetry-version: ${{ env.POETRY_VERSION }} + - name: Install dependencies run: poetry install --no-root + - name: Run tox run: poetry run tox -e flake8 -s false @@ -53,36 +57,40 @@ jobs: include: - # test with the locked dependencies os: ubuntu-latest - python-version: '3.10' + python-version: '3.11' toxenv-factor: 'locked' - # test with the lowest dependencies - os: ubuntu-20.04 - python-version: '3.6' + os: ubuntu-latest + python-version: '3.7' toxenv-factor: 'lowest' steps: - name: Checkout # see https://github.com/actions/checkout uses: actions/checkout@v3 + - name: Setup Python Environment # see https://github.com/actions/setup-python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: 'x64' + - name: Install poetry # see https://github.com/marketplace/actions/setup-poetry uses: Gr1N/setup-poetry@v8 with: poetry-version: ${{ env.POETRY_VERSION }} + - name: Install dependencies run: poetry install --no-root + - name: Run tox run: poetry run tox -e mypy-${{ matrix.toxenv-factor }} -s false build-and-test: name: Test (${{ matrix.os }} py${{ matrix.python-version }} ${{ matrix.toxenv-factor }}) runs-on: ${{ matrix.os }} - timeout-minutes: 10 + timeout-minutes: 15 env: REPORTS_ARTIFACT: tests-reports strategy: @@ -90,59 +98,61 @@ jobs: matrix: os: ['ubuntu-latest', 'windows-latest', 'macos-latest'] python-version: - - "3.10" # highest supported + - "3.11" # highest supported + - "3.10" - "3.9" - "3.8" - - "3.7" - - "3.6" # lowest supported + - "3.7" # lowest supported toxenv-factor: ['locked'] include: - - # test with py36 ubuntu20 - os: ubuntu-20.04 - python-version: '3.6' - toxenv-factor: 'locked' - # test with the lowest dependencies - os: ubuntu-20.04 - python-version: '3.6' - toxenv-factor: 'lowest' - exclude: - - # no py36 with latest ubuntu - see https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json os: ubuntu-latest - python-version: '3.6' + python-version: '3.7' + toxenv-factor: 'lowest' steps: - name: Disabled Git auto EOL CRLF transforms run: | git config --global core.autocrlf false git config --global core.eol lf + - name: Checkout # see https://github.com/actions/checkout uses: actions/checkout@v3 + - name: Create reports directory run: mkdir ${{ env.REPORTS_DIR }} + - name: Setup Python Environment # see https://github.com/actions/setup-python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: 'x64' + - name: Validate Python Environment run: echo "import sys; print('Python %s on %s in %s' % (sys.version, sys.platform, sys.getdefaultencoding()))" | python + - name: Install poetry # see https://github.com/marketplace/actions/setup-poetry uses: Gr1N/setup-poetry@v8 with: poetry-version: ${{ env.POETRY_VERSION }} + - name: Install dependencies run: poetry install --no-root + - name: Ensure build successful run: poetry build + - name: Run tox run: poetry run tox -e py-${{ matrix.toxenv-factor }} -s false + - name: Generate coverage reports run: > poetry run coverage report && poetry run coverage xml -o ${{ env.REPORTS_DIR }}/coverage-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.toxenv-factor }}.xml && poetry run coverage html -d ${{ env.REPORTS_DIR }} + - name: Artifact reports if: ${{ ! cancelled() }} # see https://github.com/actions/upload-artifact diff --git a/.isort.cfg b/.isort.cfg index 520c9a1b..05941f76 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -6,7 +6,7 @@ skip_gitignore = false skip_glob = build/*,dist/*,__pycache__,.eggs,*.egg-info*, *_cache,*.cache, - .git/*,.tox/*,.venv/*,venv/* + .git/*,.tox/*,.venv/*,venv/*,.venv*/*,venv*/*, _OLD/*,_TEST/*, docs/* combine_as_imports = true @@ -18,3 +18,4 @@ multi_line_output = 3 src_paths = cyclonedx tests + typings diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b92ff6d9..8a7f3f0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: system name: mypy - entry: poetry run tox -e mypy + entry: poetry run tox -e mypy-locked pass_filenames: false language: system - repo: local @@ -17,7 +17,7 @@ repos: - repo: local hooks: - id: system - name: autopep8 - entry: poetry run autopep8 --in-place -r cyclonedx tests + name: flake8 + entry: poetry run flake8 cyclonedx/ tests/ pass_filenames: false language: system diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a43423ef..13a45baa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ Get it all applied via: ```shell poetry run isort . -poetry run autopep8 --in-place -r cyclonedx tests +poetry run flake8 cyclonedx/ tests/ typings/ ``` ## Documentation diff --git a/README.md b/README.md index 5fc54759..ad13a7e6 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,21 @@ ---- This CycloneDX module for Python can generate valid CycloneDX bill-of-material document containing an aggregate of all -project dependencies. +project dependencies. CycloneDX is a lightweight BOM specification that is easily created, human-readable, and simple +to parse. -This module is not designed for standalone use. +**This module is not designed for standalone use.** -If you're looking for a CycloneDX tool to run to generate (SBOM) software bill-of-materials documents, why not checkout: [CycloneDX Python][cyclonedx-python] +As of version `3.0.0`, the internal data model was adjusted to allow CycloneDX VEX documents to be produced as per +[official examples](https://cyclonedx.org/capabilities/bomlink/#linking-external-vex-to-bom-inventory) linking a VEX +documents to a separate BOM document. -Additionally, the following tool can be used as well (and this library was written to help improve it) [Jake][jake]. +If you're looking for a CycloneDX tool to run to generate (SBOM) software bill-of-materials documents, why not checkout +[CycloneDX Python][cyclonedx-python] or [Jake][jake]. -Additionally, you can use this module yourself in your application to programmatically generate SBOMs. +Alternatively, you can use this module yourself in your application to programmatically generate CycloneDX BOMs. -CycloneDX is a lightweight BOM specification that is easily created, human-readable, and simple to parse. - -View our documentation [here](https://cyclonedx-python-library.readthedocs.io/). +View the documentation [here](https://cyclonedx-python-library.readthedocs.io/). ## Python Support diff --git a/cyclonedx/exception/__init__.py b/cyclonedx/exception/__init__.py index 2329d1d1..dc915bfb 100644 --- a/cyclonedx/exception/__init__.py +++ b/cyclonedx/exception/__init__.py @@ -21,4 +21,7 @@ class CycloneDxException(Exception): + """ + Root exception thrown by this library. + """ pass diff --git a/cyclonedx/exception/factory.py b/cyclonedx/exception/factory.py index 93c6b1da..87540f85 100644 --- a/cyclonedx/exception/factory.py +++ b/cyclonedx/exception/factory.py @@ -30,16 +30,28 @@ class CycloneDxFactoryException(CycloneDxException): class LicenseChoiceFactoryException(CycloneDxFactoryException): + """ + Base exception that covers all LicenseChoiceFactory exceptions. + """ pass class InvalidSpdxLicenseException(LicenseChoiceFactoryException): + """ + Thrown when an invalid SPDX License is provided. + """ pass class LicenseFactoryException(CycloneDxFactoryException): + """ + Base exception that covers all LicenseFactory exceptions. + """ pass class InvalidLicenseExpressionException(LicenseFactoryException): + """ + Thrown when an invalid License expressions is provided. + """ pass diff --git a/cyclonedx/exception/output.py b/cyclonedx/exception/output.py index 58e83976..4dbb7078 100644 --- a/cyclonedx/exception/output.py +++ b/cyclonedx/exception/output.py @@ -22,6 +22,13 @@ from . import CycloneDxException +class BomGenerationErrorException(CycloneDxException): + """ + Raised if there is an unknown error. + """ + pass + + class FormatNotSupportedException(CycloneDxException): """ Exception raised when attempting to output a BOM to a format not supported in the requested version. diff --git a/cyclonedx/factory/__init__.py b/cyclonedx/factory/__init__.py index d526f9b6..4cf62cdc 100644 --- a/cyclonedx/factory/__init__.py +++ b/cyclonedx/factory/__init__.py @@ -14,3 +14,7 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. + +""" +Factories used in this library. +""" diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index a2378bf8..7a801764 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -30,13 +30,12 @@ def make_from_string(self, name_or_spdx: str, *, license_url: Optional[XsUri] = None) -> License: """Make a :class:`cyclonedx.model.License` from a string.""" try: - return self.make_with_id(name_or_spdx, license_text=license_text, license_url=license_url) + return self.make_with_id(name_or_spdx, text=license_text, url=license_url) except InvalidSpdxLicenseException: - return self.make_with_name(name_or_spdx, license_text=license_text, license_url=license_url) + return self.make_with_name(name_or_spdx, text=license_text, url=license_url) - def make_with_id(self, spdx_id: str, *, - license_text: Optional[AttachedText] = None, - license_url: Optional[XsUri] = None) -> License: + def make_with_id(self, spdx_id: str, *, text: Optional[AttachedText] = None, + url: Optional[XsUri] = None) -> License: """Make a :class:`cyclonedx.model.License` from an SPDX-ID. :raises InvalidSpdxLicenseException: if `spdx_id` was not known/supported SPDX-ID @@ -44,13 +43,11 @@ def make_with_id(self, spdx_id: str, *, spdx_license_id = spdx_fixup(spdx_id) if spdx_license_id is None: raise InvalidSpdxLicenseException(spdx_id) - return License(spdx_license_id=spdx_license_id, license_text=license_text, license_url=license_url) + return License(id=spdx_license_id, text=text, url=url) - def make_with_name(self, name: str, *, - license_text: Optional[AttachedText] = None, - license_url: Optional[XsUri] = None) -> License: + def make_with_name(self, name: str, *, text: Optional[AttachedText] = None, url: Optional[XsUri] = None) -> License: """Make a :class:`cyclonedx.model.License` with a name.""" - return License(license_name=name, license_text=license_text, license_url=license_url) + return License(name=name, text=text, url=url) class LicenseChoiceFactory: @@ -74,12 +71,12 @@ def make_with_compound_expression(self, compound_expression: str) -> LicenseChoi :raises InvalidLicenseExpressionException: if `expression` is not known/supported license expression """ if is_spdx_compound_expression(compound_expression): - return LicenseChoice(license_expression=compound_expression) + return LicenseChoice(expression=compound_expression) raise InvalidLicenseExpressionException(compound_expression) def make_with_license(self, name_or_spdx: str, *, license_text: Optional[AttachedText] = None, license_url: Optional[XsUri] = None) -> LicenseChoice: """Make a :class:`cyclonedx.model.LicenseChoice` with a license (name or SPDX-ID).""" - return LicenseChoice(license_=self.license_factory.make_from_string( + return LicenseChoice(license=self.license_factory.make_from_string( name_or_spdx, license_text=license_text, license_url=license_url)) diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 48dc8469..38e5bcf8 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -19,10 +19,11 @@ import re import sys import warnings -from datetime import datetime +from datetime import datetime, timezone from enum import Enum from typing import Any, Iterable, Optional, Tuple, TypeVar +import serializable from sortedcontainers import SortedSet from ..exception.model import ( @@ -32,6 +33,7 @@ NoPropertiesProvidedException, UnknownHashTypeException, ) +from ..schema.schema import SchemaVersion1Dot3, SchemaVersion1Dot4 """ Uniform set of models to represent objects within a CycloneDX software bill-of-materials. @@ -41,6 +43,10 @@ """ +def get_now_utc() -> datetime: + return datetime.now(tz=timezone.utc) + + def sha1sum(filename: str) -> str: """ Generate a SHA1 hash of the provided file. @@ -109,6 +115,7 @@ class DataFlow(str, Enum): UNKNOWN = "unknown" +@serializable.serializable_class class DataClassification: """ This is our internal representation of the `dataClassificationType` complex type within the CycloneDX standard. @@ -122,7 +129,8 @@ def __init__(self, *, flow: DataFlow, classification: str) -> None: self.flow = flow self.classification = classification - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def flow(self) -> DataFlow: """ Specifies the flow direction of the data. @@ -145,7 +153,8 @@ def flow(self) -> DataFlow: def flow(self, flow: DataFlow) -> None: self._flow = flow - @property + @property # type: ignore[misc] + @serializable.xml_name('.') def classification(self) -> str: """ Data classification tags data according to its type, sensitivity, and value if altered, stolen, or destroyed. @@ -181,6 +190,7 @@ class Encoding(str, Enum): BASE_64 = 'base64' +@serializable.serializable_class class AttachedText: """ This is our internal representation of the `attachedTextType` complex type within the CycloneDX standard. @@ -197,7 +207,9 @@ def __init__(self, *, content: str, content_type: str = DEFAULT_CONTENT_TYPE, self.encoding = encoding self.content = content - @property + @property # type: ignore[misc] + @serializable.xml_attribute() + @serializable.xml_name('content-type') def content_type(self) -> str: """ Specifies the content type of the text. Defaults to text/plain if not specified. @@ -211,7 +223,8 @@ def content_type(self) -> str: def content_type(self, content_type: str) -> None: self._content_type = content_type - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def encoding(self) -> Optional[Encoding]: """ Specifies the optional encoding the text is represented in. @@ -225,7 +238,8 @@ def encoding(self) -> Optional[Encoding]: def encoding(self, encoding: Optional[Encoding]) -> None: self._encoding = encoding - @property + @property # type: ignore[misc] + @serializable.xml_name('.') def content(self) -> str: """ The attachment data. @@ -282,6 +296,7 @@ class HashAlgorithm(str, Enum): SHA3_512 = 'SHA3-512' +@serializable.serializable_class class HashType: """ This is our internal representation of the hashType complex type within the CycloneDX standard. @@ -312,27 +327,28 @@ def from_composite_str(composite_hash: str) -> 'HashType': algorithm_prefix = parts[0].lower() if algorithm_prefix == 'md5': return HashType( - algorithm=HashAlgorithm.MD5, - hash_value=parts[1].lower() + alg=HashAlgorithm.MD5, + content=parts[1].lower() ) elif algorithm_prefix[0:3] == 'sha': return HashType( - algorithm=getattr(HashAlgorithm, 'SHA_{}'.format(algorithm_prefix[3:])), - hash_value=parts[1].lower() + alg=getattr(HashAlgorithm, 'SHA_{}'.format(algorithm_prefix[3:])), + content=parts[1].lower() ) elif algorithm_prefix[0:6] == 'blake2': return HashType( - algorithm=getattr(HashAlgorithm, 'BLAKE2b_{}'.format(algorithm_prefix[6:])), - hash_value=parts[1].lower() + alg=getattr(HashAlgorithm, 'BLAKE2b_{}'.format(algorithm_prefix[6:])), + content=parts[1].lower() ) raise UnknownHashTypeException(f"Unable to determine hash type from '{composite_hash}'") - def __init__(self, *, algorithm: HashAlgorithm, hash_value: str) -> None: - self.alg = algorithm - self.content = hash_value + def __init__(self, *, alg: HashAlgorithm, content: str) -> None: + self.alg = alg + self.content = content - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def alg(self) -> HashAlgorithm: """ Specifies the algorithm used to create the hash. @@ -346,7 +362,8 @@ def alg(self) -> HashAlgorithm: def alg(self, alg: HashAlgorithm) -> None: self._alg = alg - @property + @property # type: ignore[misc] + @serializable.xml_name('.') def content(self) -> str: """ Hash value content. @@ -384,6 +401,7 @@ class ExternalReferenceType(str, Enum): .. note:: See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.3/#type_externalReferenceType """ + ADVISORIES = 'advisories' BOM = 'bom' BUILD_META = 'build-meta' @@ -403,7 +421,8 @@ class ExternalReferenceType(str, Enum): WEBSITE = 'website' -class XsUri: +@serializable.serializable_class +class XsUri(serializable.helpers.BaseHelper): """ Helper class that allows us to perform validation on data strings that are defined as xs:anyURI in CycloneDX schema. @@ -423,6 +442,26 @@ def __init__(self, uri: str) -> None: ) self._uri = uri + @property # type: ignore[misc] + @serializable.json_name('.') + @serializable.xml_name('.') + def uri(self) -> str: + return self._uri + + @classmethod + def serialize(cls, o: object) -> str: + if isinstance(o, XsUri): + return str(o) + + raise ValueError(f'Attempt to serialize a non-XsUri: {o.__class__}') + + @classmethod + def deserialize(cls, o: object) -> 'XsUri': + try: + return XsUri(uri=str(o)) + except ValueError: + raise ValueError(f'XsUri string supplied ({o}) does not parse!') + def __eq__(self, other: object) -> bool: if isinstance(other, XsUri): return hash(other) == hash(self) @@ -443,6 +482,7 @@ def __str__(self) -> str: return self._uri +@serializable.serializable_class class ExternalReference: """ This is our internal representation of an ExternalReference complex type that can be used in multiple places within @@ -452,14 +492,15 @@ class ExternalReference: See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.3/#type_externalReference """ - def __init__(self, *, reference_type: ExternalReferenceType, url: XsUri, comment: Optional[str] = None, + def __init__(self, *, type: ExternalReferenceType, url: XsUri, comment: Optional[str] = None, hashes: Optional[Iterable[HashType]] = None) -> None: self.url = url self.comment = comment - self.type = reference_type + self.type = type self.hashes = hashes or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def url(self) -> XsUri: """ The URL to the external reference. @@ -487,7 +528,8 @@ def comment(self) -> Optional[str]: def comment(self, comment: Optional[str]) -> None: self._comment = comment - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def type(self) -> ExternalReferenceType: """ Specifies the type of external reference. @@ -501,10 +543,13 @@ def type(self) -> ExternalReferenceType: return self._type @type.setter - def type(self, type_: ExternalReferenceType) -> None: - self._type = type_ + def type(self, type: ExternalReferenceType) -> None: + self._type = type - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'hash') def hashes(self) -> "SortedSet[HashType]": """ The hashes of the external reference (if applicable). @@ -536,9 +581,10 @@ def __hash__(self) -> int: )) def __repr__(self) -> str: - return f'' + return f'' +@serializable.serializable_class class License: """ This is our internal representation of `licenseType` complex type that can be used in multiple places within @@ -548,22 +594,22 @@ class License: See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_licenseType """ - def __init__(self, *, spdx_license_id: Optional[str] = None, license_name: Optional[str] = None, - license_text: Optional[AttachedText] = None, license_url: Optional[XsUri] = None) -> None: - if not spdx_license_id and not license_name: - raise MutuallyExclusivePropertiesException('Either `spdx_license_id` or `license_name` MUST be supplied') - if spdx_license_id and license_name: + def __init__(self, *, id: Optional[str] = None, name: Optional[str] = None, + text: Optional[AttachedText] = None, url: Optional[XsUri] = None) -> None: + if not id and not name: + raise MutuallyExclusivePropertiesException('Either `id` or `name` MUST be supplied') + if id and name: warnings.warn( - 'Both `spdx_license_id` and `license_name` have been supplied - `license_name` will be ignored!', + 'Both `id` and `name` have been supplied - `name` will be ignored!', RuntimeWarning ) - self.id = spdx_license_id - if not spdx_license_id: - self.name = license_name + self.id = id + if not id: + self.name = name else: self.name = None - self.text = license_text - self.url = license_url + self.text = text + self.url = url @property def id(self) -> Optional[str]: @@ -639,6 +685,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class LicenseChoice: """ This is our internal representation of `licenseChoiceType` complex type that can be used in multiple places within @@ -648,19 +695,19 @@ class LicenseChoice: See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_licenseChoiceType """ - def __init__(self, *, license_: Optional[License] = None, license_expression: Optional[str] = None) -> None: - if not license_ and not license_expression: + def __init__(self, *, license: Optional[License] = None, expression: Optional[str] = None) -> None: + if not license and not expression: raise NoPropertiesProvidedException( - 'One of `license` or `license_expression` must be supplied - neither supplied' + 'One of `license` or `expression` must be supplied - neither supplied' ) - if license_ and license_expression: + if license and expression: warnings.warn( - 'Both `license` and `license_expression` have been supplied - `license` will take precedence', + 'Both `license` and `expression` have been supplied - `license` will take precedence', RuntimeWarning ) - self.license = license_ - if not license_: - self.expression = license_expression + self.license = license + if not license: + self.expression = expression else: self.expression = None @@ -675,8 +722,8 @@ def license(self) -> Optional[License]: return self._license @license.setter - def license(self, license_: Optional[License]) -> None: - self._license = license_ + def license(self, license: Optional[License]) -> None: + self._license = license @property def expression(self) -> Optional[str]: @@ -701,7 +748,8 @@ def __eq__(self, other: object) -> bool: def __lt__(self, other: Any) -> bool: if isinstance(other, LicenseChoice): - return ComparableTuple((self.license, self.expression)) < ComparableTuple((other.license, other.expression)) + return ComparableTuple((self.license, self.expression)) < ComparableTuple( + (other.license, other.expression)) return NotImplemented def __hash__(self) -> int: @@ -711,6 +759,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class Property: """ This is our internal representation of `propertyType` complex type that can be used in multiple places within @@ -726,7 +775,8 @@ def __init__(self, *, name: str, value: str) -> None: self.name = name self.value = value - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def name(self) -> str: """ The name of the property. @@ -742,7 +792,8 @@ def name(self) -> str: def name(self, name: str) -> None: self._name = name - @property + @property # type: ignore[misc] + @serializable.xml_name('.') def value(self) -> str: """ Value of this Property. @@ -773,6 +824,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class NoteText: """ This is our internal representation of the Note.text complex type that can be used in multiple places within @@ -785,12 +837,13 @@ class NoteText: DEFAULT_CONTENT_TYPE: str = 'text/plain' def __init__(self, *, content: str, content_type: Optional[str] = None, - content_encoding: Optional[Encoding] = None) -> None: + encoding: Optional[Encoding] = None) -> None: self.content = content self.content_type = content_type or NoteText.DEFAULT_CONTENT_TYPE - self.encoding = content_encoding + self.encoding = encoding - @property + @property # type: ignore[misc] + @serializable.xml_name('.') def content(self) -> str: """ Get the text content of this Note. @@ -804,7 +857,9 @@ def content(self) -> str: def content(self, content: str) -> None: self._content = content - @property + @property # type: ignore[misc] + @serializable.xml_attribute() + @serializable.xml_name('content-type') def content_type(self) -> Optional[str]: """ Get the content-type of this Note. @@ -820,7 +875,8 @@ def content_type(self) -> Optional[str]: def content_type(self, content_type: str) -> None: self._content_type = content_type - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def encoding(self) -> Optional[Encoding]: """ Get the encoding method used for the note's content. @@ -852,6 +908,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class Note: """ This is our internal representation of the Note complex type that can be used in multiple places within @@ -859,6 +916,8 @@ class Note: .. note:: See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_releaseNotesType + + @todo: Replace ``NoteText`` with ``AttachedText``? """ _LOCALE_TYPE_REGEX = re.compile(r'^[a-z]{2}(?:\-[A-Z]{2})?$') @@ -881,7 +940,8 @@ def text(self) -> NoteText: def text(self, text: NoteText) -> None: self._text = text - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def locale(self) -> Optional[str]: """ Get the ISO locale of this Note. @@ -924,6 +984,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class OrganizationalContact: """ This is our internal representation of the `organizationalContact` complex type that can be used in multiple places @@ -942,7 +1003,8 @@ def __init__(self, *, name: Optional[str] = None, phone: Optional[str] = None, e self.email = email self.phone = phone - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def name(self) -> Optional[str]: """ Get the name of the contact. @@ -956,7 +1018,8 @@ def name(self) -> Optional[str]: def name(self, name: Optional[str]) -> None: self._name = name - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def email(self) -> Optional[str]: """ Get the email of the contact. @@ -970,7 +1033,8 @@ def email(self) -> Optional[str]: def email(self, email: Optional[str]) -> None: self._email = email - @property + @property # type: ignore[misc] + @serializable.xml_sequence(3) def phone(self) -> Optional[str]: """ Get the phone of the contact. @@ -1002,6 +1066,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class OrganizationalEntity: """ This is our internal representation of the `organizationalEntity` complex type that can be used in multiple places @@ -1018,10 +1083,11 @@ def __init__(self, *, name: Optional[str] = None, urls: Optional[Iterable[XsUri] 'One of name, urls or contacts must be supplied for an OrganizationalEntity - none supplied.' ) self.name = name - self.url = urls or [] # type: ignore - self.contact = contacts or [] # type: ignore + self.urls = urls or [] # type: ignore + self.contacts = contacts or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def name(self) -> Optional[str]: """ Get the name of the organization. @@ -1035,33 +1101,39 @@ def name(self) -> Optional[str]: def name(self, name: Optional[str]) -> None: self._name = name - @property - def url(self) -> "SortedSet[XsUri]": + @property # type: ignore[misc] + @serializable.json_name('url') + @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'url') + @serializable.xml_sequence(2) + def urls(self) -> "SortedSet[XsUri]": """ Get a list of URLs of the organization. Multiple URLs are allowed. Returns: Set of `XsUri` """ - return self._url + return self._urls - @url.setter - def url(self, urls: Iterable[XsUri]) -> None: - self._url = SortedSet(urls) + @urls.setter + def urls(self, urls: Iterable[XsUri]) -> None: + self._urls = SortedSet(urls) - @property - def contact(self) -> "SortedSet[OrganizationalContact]": + @property # type: ignore[misc] + @serializable.json_name('contact') + @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'contact') + @serializable.xml_sequence(3) + def contacts(self) -> "SortedSet[OrganizationalContact]": """ Get a list of contact person at the organization. Multiple contacts are allowed. Returns: Set of `OrganizationalContact` """ - return self._contact + return self._contacts - @contact.setter - def contact(self, contacts: Iterable[OrganizationalContact]) -> None: - self._contact = SortedSet(contacts) + @contacts.setter + def contacts(self, contacts: Iterable[OrganizationalContact]) -> None: + self._contacts = SortedSet(contacts) def __eq__(self, other: object) -> bool: if isinstance(other, OrganizationalEntity): @@ -1074,12 +1146,13 @@ def __lt__(self, other: Any) -> bool: return NotImplemented def __hash__(self) -> int: - return hash((self.name, tuple(self.url), tuple(self.contact))) + return hash((self.name, tuple(self.urls), tuple(self.contacts))) def __repr__(self) -> str: return f'' +@serializable.serializable_class class Tool: """ This is our internal representation of the `toolType` complex type within the CycloneDX standard. @@ -1099,7 +1172,8 @@ def __init__(self, *, vendor: Optional[str] = None, name: Optional[str] = None, self.hashes = hashes or [] # type: ignore self.external_references = external_references or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def vendor(self) -> Optional[str]: """ The name of the vendor who created the tool. @@ -1113,7 +1187,8 @@ def vendor(self) -> Optional[str]: def vendor(self, vendor: Optional[str]) -> None: self._vendor = vendor - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def name(self) -> Optional[str]: """ The name of the tool. @@ -1127,7 +1202,8 @@ def name(self) -> Optional[str]: def name(self, name: Optional[str]) -> None: self._name = name - @property + @property # type: ignore[misc] + @serializable.xml_sequence(3) def version(self) -> Optional[str]: """ The version of the tool. @@ -1141,7 +1217,9 @@ def version(self) -> Optional[str]: def version(self, version: Optional[str]) -> None: self._version = version - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'hash') + @serializable.xml_sequence(4) def hashes(self) -> "SortedSet[HashType]": """ The hashes of the tool (if applicable). @@ -1155,7 +1233,10 @@ def hashes(self) -> "SortedSet[HashType]": def hashes(self, hashes: Iterable[HashType]) -> None: self._hashes = SortedSet(hashes) - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference') + @serializable.xml_sequence(5) def external_references(self) -> "SortedSet[ExternalReference]": """ External References provide a way to document systems, sites, and information that may be relevant but which @@ -1188,6 +1269,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class IdentifiableAction: """ This is our internal representation of the `identifiableActionType` complex type. @@ -1207,7 +1289,8 @@ def __init__(self, *, timestamp: Optional[datetime] = None, name: Optional[str] self.name = name self.email = email - @property + @property # type: ignore[misc] + @serializable.type_mapping(serializable.helpers.XsdDateTime) def timestamp(self) -> Optional[datetime]: """ The timestamp in which the action occurred. @@ -1267,6 +1350,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class Copyright: """ This is our internal representation of the `copyrightsType` complex type. @@ -1278,7 +1362,8 @@ class Copyright: def __init__(self, *, text: str) -> None: self.text = text - @property + @property # type: ignore[misc] + @serializable.xml_name('.') def text(self) -> str: """ Copyright statement. @@ -1321,35 +1406,35 @@ def __repr__(self) -> str: ThisTool = Tool(vendor='CycloneDX', name='cyclonedx-python-lib', version=__ThisToolVersion or 'UNKNOWN') ThisTool.external_references.update([ ExternalReference( - reference_type=ExternalReferenceType.BUILD_SYSTEM, + type=ExternalReferenceType.BUILD_SYSTEM, url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/actions') ), ExternalReference( - reference_type=ExternalReferenceType.DISTRIBUTION, + type=ExternalReferenceType.DISTRIBUTION, url=XsUri('https://pypi.org/project/cyclonedx-python-lib/') ), ExternalReference( - reference_type=ExternalReferenceType.DOCUMENTATION, + type=ExternalReferenceType.DOCUMENTATION, url=XsUri('https://cyclonedx.github.io/cyclonedx-python-lib/') ), ExternalReference( - reference_type=ExternalReferenceType.ISSUE_TRACKER, + type=ExternalReferenceType.ISSUE_TRACKER, url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/issues') ), ExternalReference( - reference_type=ExternalReferenceType.LICENSE, + type=ExternalReferenceType.LICENSE, url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE') ), ExternalReference( - reference_type=ExternalReferenceType.RELEASE_NOTES, + type=ExternalReferenceType.RELEASE_NOTES, url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md') ), ExternalReference( - reference_type=ExternalReferenceType.VCS, + type=ExternalReferenceType.VCS, url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib') ), ExternalReference( - reference_type=ExternalReferenceType.WEBSITE, + type=ExternalReferenceType.WEBSITE, url=XsUri('https://cyclonedx.org') ) ]) diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index fa641f2f..a8993b02 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -17,22 +17,45 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. import warnings -from datetime import datetime, timezone +from datetime import datetime from typing import TYPE_CHECKING, Iterable, Optional, Set from uuid import UUID, uuid4 +import serializable from sortedcontainers import SortedSet +from cyclonedx.serialization import UrnUuidHelper + from ..exception.model import UnknownComponentDependencyException from ..parser import BaseParser -from . import ExternalReference, LicenseChoice, OrganizationalContact, OrganizationalEntity, Property, ThisTool, Tool +from ..schema.schema import ( + SchemaVersion1Dot0, + SchemaVersion1Dot1, + SchemaVersion1Dot2, + SchemaVersion1Dot3, + SchemaVersion1Dot4, +) +from . import ( + ExternalReference, + LicenseChoice, + OrganizationalContact, + OrganizationalEntity, + Property, + ThisTool, + Tool, + get_now_utc, +) +from .bom_ref import BomRef from .component import Component +from .dependency import Dependable, Dependency from .service import Service +from .vulnerability import Vulnerability if TYPE_CHECKING: from packageurl import PackageURL # type:ignore[import] +@serializable.serializable_class class BomMetaData: """ This is our internal representation of the metadata complex type within the CycloneDX standard. @@ -46,8 +69,9 @@ def __init__(self, *, tools: Optional[Iterable[Tool]] = None, manufacture: Optional[OrganizationalEntity] = None, supplier: Optional[OrganizationalEntity] = None, licenses: Optional[Iterable[LicenseChoice]] = None, - properties: Optional[Iterable[Property]] = None) -> None: - self.timestamp = datetime.now(tz=timezone.utc) + properties: Optional[Iterable[Property]] = None, + timestamp: Optional[datetime] = None) -> None: + self.timestamp = timestamp or get_now_utc() self.tools = tools or [] # type: ignore self.authors = authors or [] # type: ignore self.component = component @@ -59,7 +83,9 @@ def __init__(self, *, tools: Optional[Iterable[Tool]] = None, if not tools: self.tools.add(ThisTool) - @property + @property # type: ignore[misc] + @serializable.type_mapping(serializable.helpers.XsdDateTime) + @serializable.xml_sequence(1) def timestamp(self) -> datetime: """ The date and time (in UTC) when this BomMetaData was created. @@ -73,7 +99,9 @@ def timestamp(self) -> datetime: def timestamp(self, timestamp: datetime) -> None: self._timestamp = timestamp - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'tool') + @serializable.xml_sequence(2) def tools(self) -> "SortedSet[Tool]": """ Tools used to create this BOM. @@ -87,7 +115,9 @@ def tools(self) -> "SortedSet[Tool]": def tools(self, tools: Iterable[Tool]) -> None: self._tools = SortedSet(tools) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'author') + @serializable.xml_sequence(3) def authors(self) -> "SortedSet[OrganizationalContact]": """ The person(s) who created the BOM. @@ -105,7 +135,8 @@ def authors(self) -> "SortedSet[OrganizationalContact]": def authors(self, authors: Iterable[OrganizationalContact]) -> None: self._authors = SortedSet(authors) - @property + @property # type: ignore[misc] + @serializable.xml_sequence(4) def component(self) -> Optional[Component]: """ The (optional) component that the BOM describes. @@ -129,7 +160,8 @@ def component(self, component: Component) -> None: """ self._component = component - @property + @property # type: ignore[misc] + @serializable.xml_sequence(5) def manufacture(self) -> Optional[OrganizationalEntity]: """ The organization that manufactured the component that the BOM describes. @@ -143,7 +175,8 @@ def manufacture(self) -> Optional[OrganizationalEntity]: def manufacture(self, manufacture: Optional[OrganizationalEntity]) -> None: self._manufacture = manufacture - @property + @property # type: ignore[misc] + @serializable.xml_sequence(6) def supplier(self) -> Optional[OrganizationalEntity]: """ The organization that supplied the component that the BOM describes. @@ -159,7 +192,11 @@ def supplier(self) -> Optional[OrganizationalEntity]: def supplier(self, supplier: Optional[OrganizationalEntity]) -> None: self._supplier = supplier - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'licenses') + @serializable.xml_sequence(7) def licenses(self) -> "SortedSet[LicenseChoice]": """ A optional list of statements about how this BOM is licensed. @@ -173,7 +210,11 @@ def licenses(self) -> "SortedSet[LicenseChoice]": def licenses(self, licenses: Iterable[LicenseChoice]) -> None: self._licenses = SortedSet(licenses) - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property') + @serializable.xml_sequence(8) def properties(self) -> "SortedSet[Property]": """ Provides the ability to document properties in a key/value store. This provides flexibility to include data not @@ -198,13 +239,16 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: return hash(( - self.timestamp, self.tools, self.component + tuple(self.authors), self.component, tuple(self.licenses), self.manufacture, tuple(self.properties), + self.supplier, self.timestamp, tuple(self.tools) )) def __repr__(self) -> str: - return f'' + return f'' +@serializable.serializable_class( + ignore_during_deserialization=['$schema', 'bom_format', 'spec_version']) # type: ignore[misc] class Bom: """ This is our internal representation of a bill-of-materials (BOM). @@ -233,34 +277,52 @@ def from_parser(parser: BaseParser) -> 'Bom': def __init__(self, *, components: Optional[Iterable[Component]] = None, services: Optional[Iterable[Service]] = None, - external_references: Optional[Iterable[ExternalReference]] = None) -> None: + external_references: Optional[Iterable[ExternalReference]] = None, + serial_number: Optional[UUID] = None, version: int = 1, + metadata: Optional[BomMetaData] = None, + dependencies: Optional[Iterable[Dependency]] = None, + vulnerabilities: Optional[Iterable[Vulnerability]] = None) -> None: """ Create a new Bom that you can manually/programmatically add data to later. Returns: New, empty `cyclonedx.model.bom.Bom` instance. """ - self.uuid = uuid4() - self.metadata = BomMetaData() + self.serial_number = serial_number or uuid4() + self.metadata = metadata or BomMetaData() self.components = components or [] # type: ignore self.services = services or [] # type: ignore - self.external_references = external_references or [] # type: ignore - - @property - def uuid(self) -> UUID: + self.external_references = SortedSet(external_references or []) + self.vulnerabilities = SortedSet(vulnerabilities or []) + self.version = version + self.dependencies = SortedSet(dependencies) or SortedSet() + + @property # type: ignore[misc] + @serializable.type_mapping(UrnUuidHelper) + @serializable.view(SchemaVersion1Dot1) + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_attribute() + def serial_number(self) -> UUID: """ Unique UUID for this BOM Returns: `UUID` instance + `UUID` instance """ - return self.__uuid + return self._serial_number - @uuid.setter - def uuid(self, uuid: UUID) -> None: - self.__uuid = uuid + @serial_number.setter + def serial_number(self, serial_number: UUID) -> None: + self._serial_number = serial_number - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_sequence(1) def metadata(self) -> BomMetaData: """ Get our internal metadata object for this Bom. @@ -277,7 +339,11 @@ def metadata(self) -> BomMetaData: def metadata(self, metadata: BomMetaData) -> None: self._metadata = metadata - @property + @property # type: ignore[misc] + @serializable.include_none(SchemaVersion1Dot0) + @serializable.include_none(SchemaVersion1Dot1) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'component') + @serializable.xml_sequence(2) def components(self) -> "SortedSet[Component]": """ Get all the Components currently in this Bom. @@ -316,7 +382,7 @@ def get_urn_uuid(self) -> str: Returns: URN formatted UUID that uniquely identified this Bom instance. """ - return 'urn:uuid:{}'.format(self.__uuid) + return self.serial_number.urn def has_component(self, component: Component) -> bool: """ @@ -331,7 +397,12 @@ def has_component(self, component: Component) -> bool: """ return component in self.components - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'service') + @serializable.xml_sequence(3) def services(self) -> "SortedSet[Service]": """ Get all the Services currently in this Bom. @@ -345,7 +416,13 @@ def services(self) -> "SortedSet[Service]": def services(self, services: Iterable[Service]) -> None: self._services = SortedSet(services) - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot1) + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference') + @serializable.xml_sequence(4) def external_references(self) -> "SortedSet[ExternalReference]": """ Provides the ability to document external references related to the BOM or to the project the BOM describes. @@ -364,20 +441,99 @@ def _get_all_components(self) -> Set[Component]: if self.metadata.component: components.update(self.metadata.component.get_all_nested_components(include_self=True)) + # Add Components and sub Components for c in self.components: components.update(c.get_all_nested_components(include_self=True)) return components + def get_vulnerabilities_for_bom_ref(self, bom_ref: BomRef) -> "SortedSet[Vulnerability]": + """ + Get all known Vulnerabilities that affect the supplied bom_ref. + + Args: + bom_ref: `BomRef` + + Returns: + `SortedSet` of `Vulnerability` + """ + + vulnerabilities: SortedSet[Vulnerability] = SortedSet() + for v in self.vulnerabilities: + for target in v.affects: + if target.ref == bom_ref.value: + vulnerabilities.add(v) + return vulnerabilities + def has_vulnerabilities(self) -> bool: """ Check whether this Bom has any declared vulnerabilities. Returns: - `bool` - `True` if at least one `cyclonedx.model.component.Component` has at least one Vulnerability, - `False` otherwise. + `bool` - `True` if this Bom has at least one Vulnerability, `False` otherwise. + """ + return bool(self.vulnerabilities) + + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'vulnerability') + @serializable.xml_sequence(8) + def vulnerabilities(self) -> "SortedSet[Vulnerability]": """ - return any(c.has_vulnerabilities() for c in self.components) + Get all the Vulnerabilities in this BOM. + + Returns: + Set of `Vulnerability` + """ + return self._vulnerabilities + + @vulnerabilities.setter + def vulnerabilities(self, vulnerabilities: Iterable[Vulnerability]) -> None: + self._vulnerabilities = SortedSet(vulnerabilities) + + @property # type: ignore[misc] + @serializable.xml_attribute() + def version(self) -> int: + return self._version + + @version.setter + def version(self, version: int) -> None: + self._version = version + + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'dependency') + @serializable.xml_sequence(5) + def dependencies(self) -> "SortedSet[Dependency]": + return self._dependencies + + @dependencies.setter + def dependencies(self, dependencies: Iterable[Dependency]) -> None: + self._dependencies = SortedSet(dependencies) + + def register_dependency(self, target: Dependable, depends_on: Optional[Iterable[Dependable]] = None) -> None: + _d = next(filter(lambda _d: _d.ref == target.bom_ref, self.dependencies), None) + + if _d and depends_on: + # Dependency Target already registered - but it might have new dependencies to add + _d.dependencies = _d.dependencies.union( # type: ignore + set(map(lambda _d: Dependency(ref=_d.bom_ref), depends_on)) if depends_on else [] + ) + elif not _d: + # First time we are seeing this target as a Dependency + self._dependencies.add(Dependency( + ref=target.bom_ref, + dependencies=map(lambda _dep: Dependency(ref=_dep.bom_ref), depends_on) if depends_on else [] + )) + + # Ensure dependents are registered with no further dependents in the Dependency Graph as per CDX specification + for _d2 in depends_on if depends_on else []: + self.register_dependency(target=_d2, depends_on=None) + + def urn(self) -> str: + return f'urn:cdx:{self.serial_number}/{self.version}' def validate(self) -> bool: """ @@ -387,12 +543,19 @@ def validate(self) -> bool: Returns: `bool` """ + # 0. Make sure all Dependable have a Dependency entry + if self.metadata.component: + self.register_dependency(target=self.metadata.component) + for _c in self.components: + self.register_dependency(target=_c) + for _s in self.services: + self.register_dependency(target=_s) # 1. Make sure dependencies are all in this Bom. all_bom_refs = set(map(lambda c: c.bom_ref, self._get_all_components())) | set( map(lambda s: s.bom_ref, self.services)) + all_dependency_bom_refs = set().union(*(d.dependencies_as_bom_refs() for d in self.dependencies)) - all_dependency_bom_refs = set().union(*(c.dependencies for c in self.components)) dependency_diff = all_dependency_bom_refs - all_bom_refs if len(dependency_diff) > 0: raise UnknownComponentDependencyException( @@ -400,11 +563,13 @@ def validate(self) -> bool: f'BOM. They are: {dependency_diff}') # 2. Dependencies should exist for the Component this BOM is describing, if one is set - if self.metadata.component and not self.metadata.component.dependencies: + if self.metadata.component and filter( + lambda _d: _d.ref == self.metadata.component.bom_ref, self.dependencies # type: ignore[arg-type] + ): warnings.warn( - f'The Component this BOM is describing (PURL={self.metadata.component.purl}) has no defined ' - f'dependencies which means the Dependency Graph is incomplete - you should add direct dependencies to ' - f'this Component to complete the Dependency Graph data.', + f'The Component this BOM is describing {self.metadata.component.purl} has no defined dependencies ' + f'which means the Dependency Graph is incomplete - you should add direct dependencies to this Component' + f'to complete the Dependency Graph data.', UserWarning ) @@ -417,8 +582,9 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: return hash(( - self.uuid, self.metadata, tuple(self.components), tuple(self.services), tuple(self.external_references) + self.serial_number, self.version, self.metadata, tuple(self.components), tuple(self.services), + tuple(self.external_references), tuple(self.vulnerabilities), tuple(self.dependencies) )) def __repr__(self) -> str: - return f'' + return f'' diff --git a/cyclonedx/model/bom_ref.py b/cyclonedx/model/bom_ref.py index 1d48e6a3..79dad0a3 100644 --- a/cyclonedx/model/bom_ref.py +++ b/cyclonedx/model/bom_ref.py @@ -44,7 +44,7 @@ def value(self, value: str) -> None: def __eq__(self, other: object) -> bool: if isinstance(other, BomRef): - return hash(other) == hash(self) + return other.value == self.value return False def __lt__(self, other: Any) -> bool: diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index f3074f67..c3b8c14c 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -20,13 +20,23 @@ import warnings from enum import Enum from os.path import exists -from typing import Any, Iterable, Optional, Set +from typing import Any, Iterable, Optional, Set, Union +from uuid import uuid4 # See https://github.com/package-url/packageurl-python/issues/65 +import serializable from packageurl import PackageURL # type: ignore from sortedcontainers import SortedSet from ..exception.model import NoPropertiesProvidedException +from ..schema.schema import ( + SchemaVersion1Dot0, + SchemaVersion1Dot1, + SchemaVersion1Dot2, + SchemaVersion1Dot3, + SchemaVersion1Dot4, +) +from ..serialization import BomRefHelper, PackageUrl from . import ( AttachedText, ComparableTuple, @@ -42,11 +52,12 @@ sha1sum, ) from .bom_ref import BomRef +from .dependency import Dependable from .issue import IssueType from .release_note import ReleaseNotes -from .vulnerability import Vulnerability +@serializable.serializable_class class Commit: """ Our internal representation of the `commitType` complex type. @@ -69,7 +80,8 @@ def __init__(self, *, uid: Optional[str] = None, url: Optional[XsUri] = None, self.committer = committer self.message = message - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def uid(self) -> Optional[str]: """ A unique identifier of the commit. This may be version control specific. For example, Subversion uses revision @@ -84,7 +96,8 @@ def uid(self) -> Optional[str]: def uid(self, uid: Optional[str]) -> None: self._uid = uid - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def url(self) -> Optional[XsUri]: """ The URL to the commit. This URL will typically point to a commit in a version control system. @@ -98,7 +111,8 @@ def url(self) -> Optional[XsUri]: def url(self, url: Optional[XsUri]) -> None: self._url = url - @property + @property # type: ignore[misc] + @serializable.xml_sequence(3) def author(self) -> Optional[IdentifiableAction]: """ The author who created the changes in the commit. @@ -112,7 +126,8 @@ def author(self) -> Optional[IdentifiableAction]: def author(self, author: Optional[IdentifiableAction]) -> None: self._author = author - @property + @property # type: ignore[misc] + @serializable.xml_sequence(4) def committer(self) -> Optional[IdentifiableAction]: """ The person who committed or pushed the commit @@ -126,7 +141,8 @@ def committer(self) -> Optional[IdentifiableAction]: def committer(self, committer: Optional[IdentifiableAction]) -> None: self._committer = committer - @property + @property # type: ignore[misc] + @serializable.xml_sequence(5) def message(self) -> Optional[str]: """ The text description of the contents of the commit. @@ -147,8 +163,8 @@ def __eq__(self, other: object) -> bool: def __lt__(self, other: Any) -> bool: if isinstance(other, Commit): - return ComparableTuple((self.uid, self.url, self.author, self.committer, self.message)) < \ - ComparableTuple((other.uid, other.url, other.author, other.committer, other.message)) + return ComparableTuple((self.uid, self.url, self.author, self.committer, self.message)) < ComparableTuple( + (other.uid, other.url, other.author, other.committer, other.message)) return NotImplemented def __hash__(self) -> int: @@ -158,6 +174,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class ComponentEvidence: """ Our internal representation of the `componentEvidenceType` complex type. @@ -169,16 +186,17 @@ class ComponentEvidence: """ def __init__(self, *, licenses: Optional[Iterable[LicenseChoice]] = None, - copyright_: Optional[Iterable[Copyright]] = None) -> None: - if not licenses and not copyright_: + copyright: Optional[Iterable[Copyright]] = None) -> None: + if not licenses and not copyright: raise NoPropertiesProvidedException( - 'At least one of `licenses` or `copyright_` must be supplied for a `ComponentEvidence`.' + 'At least one of `licenses` or `copyright` must be supplied for a `ComponentEvidence`.' ) self.licenses = licenses or [] # type: ignore - self.copyright = copyright_ or [] # type: ignore + self.copyright = copyright or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'license') def licenses(self) -> "SortedSet[LicenseChoice]": """ Optional list of licenses obtained during analysis. @@ -192,7 +210,8 @@ def licenses(self) -> "SortedSet[LicenseChoice]": def licenses(self, licenses: Iterable[LicenseChoice]) -> None: self._licenses = SortedSet(licenses) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'text') def copyright(self) -> "SortedSet[Copyright]": """ Optional list of copyright statements. @@ -203,8 +222,8 @@ def copyright(self) -> "SortedSet[Copyright]": return self._copyright @copyright.setter - def copyright(self, copyright_: Iterable[Copyright]) -> None: - self._copyright = SortedSet(copyright_) + def copyright(self, copyright: Iterable[Copyright]) -> None: + self._copyright = SortedSet(copyright) def __eq__(self, other: object) -> bool: if isinstance(other, ComponentEvidence): @@ -322,6 +341,7 @@ class PatchClassification(str, Enum): UNOFFICIAL = 'unofficial' +@serializable.serializable_class class Patch: """ Our internal representation of the `patchType` complex type. @@ -330,13 +350,14 @@ class Patch: See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_patchType """ - def __init__(self, *, type_: PatchClassification, diff: Optional[Diff] = None, + def __init__(self, *, type: PatchClassification, diff: Optional[Diff] = None, resolves: Optional[Iterable[IssueType]] = None) -> None: - self.type = type_ + self.type = type self.diff = diff self.resolves = resolves or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def type(self) -> PatchClassification: """ Specifies the purpose for the patch including the resolution of defects, security issues, or new behavior or @@ -348,8 +369,8 @@ def type(self) -> PatchClassification: return self._type @type.setter - def type(self, type_: PatchClassification) -> None: - self._type = type_ + def type(self, type: PatchClassification) -> None: + self._type = type @property def diff(self) -> Optional[Diff]: @@ -368,7 +389,8 @@ def diff(self) -> Optional[Diff]: def diff(self, diff: Optional[Diff]) -> None: self._diff = diff - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'issue') def resolves(self) -> "SortedSet[IssueType]": """ Optional list of issues resolved by this patch. @@ -389,8 +411,8 @@ def __eq__(self, other: object) -> bool: def __lt__(self, other: Any) -> bool: if isinstance(other, Patch): - return ComparableTuple((self.type, self.diff, ComparableTuple(self.resolves))) < \ - ComparableTuple((other.type, other.diff, ComparableTuple(other.resolves))) + return ComparableTuple((self.type, self.diff, ComparableTuple(self.resolves))) < ComparableTuple( + (other.type, other.diff, ComparableTuple(other.resolves))) return NotImplemented def __hash__(self) -> int: @@ -400,6 +422,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class Pedigree: """ Our internal representation of the `pedigreeType` complex type. @@ -430,7 +453,9 @@ def __init__(self, *, ancestors: Optional[Iterable['Component']] = None, self.patches = patches or [] # type: ignore self.notes = notes - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'component') + @serializable.xml_sequence(1) def ancestors(self) -> "SortedSet['Component']": """ Describes zero or more components in which a component is derived from. This is commonly used to describe forks @@ -450,7 +475,9 @@ def ancestors(self) -> "SortedSet['Component']": def ancestors(self, ancestors: Iterable['Component']) -> None: self._ancestors = SortedSet(ancestors) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'component') + @serializable.xml_sequence(2) def descendants(self) -> "SortedSet['Component']": """ Descendants are the exact opposite of ancestors. This provides a way to document all forks (and their forks) of @@ -465,7 +492,9 @@ def descendants(self) -> "SortedSet['Component']": def descendants(self, descendants: Iterable['Component']) -> None: self._descendants = SortedSet(descendants) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'component') + @serializable.xml_sequence(3) def variants(self) -> "SortedSet['Component']": """ Variants describe relations where the relationship between the components are not known. For example, if @@ -481,7 +510,9 @@ def variants(self) -> "SortedSet['Component']": def variants(self, variants: Iterable['Component']) -> None: self._variants = SortedSet(variants) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'commit') + @serializable.xml_sequence(4) def commits(self) -> "SortedSet[Commit]": """ A list of zero or more commits which provide a trail describing how the component deviates from an ancestor, @@ -496,7 +527,12 @@ def commits(self) -> "SortedSet[Commit]": def commits(self, commits: Iterable[Commit]) -> None: self._commits = SortedSet(commits) - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'patch') + @serializable.xml_sequence(5) def patches(self) -> "SortedSet[Patch]": """ A list of zero or more patches describing how the component deviates from an ancestor, descendant, or variant. @@ -511,7 +547,8 @@ def patches(self) -> "SortedSet[Patch]": def patches(self, patches: Iterable[Patch]) -> None: self._patches = SortedSet(patches) - @property + @property # type: ignore[misc] + @serializable.xml_sequence(6) def notes(self) -> Optional[str]: """ Notes, observations, and other non-structured commentary describing the components pedigree. @@ -537,9 +574,10 @@ def __hash__(self) -> int: )) def __repr__(self) -> str: - return f'' + return f'' +@serializable.serializable_class class Swid: """ Our internal representation of the `swidType` complex type. @@ -559,7 +597,8 @@ def __init__(self, *, tag_id: str, name: str, version: Optional[str] = None, self.text = text self.url = url - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def tag_id(self) -> str: """ Maps to the tagId of a SoftwareIdentity. @@ -573,7 +612,8 @@ def tag_id(self) -> str: def tag_id(self, tag_id: str) -> None: self._tag_id = tag_id - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def name(self) -> str: """ Maps to the name of a SoftwareIdentity. @@ -587,7 +627,8 @@ def name(self) -> str: def name(self, name: str) -> None: self._name = name - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def version(self) -> Optional[str]: """ Maps to the version of a SoftwareIdentity. @@ -601,7 +642,8 @@ def version(self) -> Optional[str]: def version(self, version: Optional[str]) -> None: self._version = version - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def tag_version(self) -> Optional[int]: """ Maps to the tagVersion of a SoftwareIdentity. @@ -615,7 +657,8 @@ def tag_version(self) -> Optional[int]: def tag_version(self, tag_version: Optional[int]) -> None: self._tag_version = tag_version - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def patch(self) -> Optional[bool]: """ Maps to the patch of a SoftwareIdentity. @@ -669,7 +712,8 @@ def __repr__(self) -> str: return f'' -class Component: +@serializable.serializable_class +class Component(Dependable): """ This is our internal representation of a Component within a Bom. @@ -699,31 +743,35 @@ def for_file(absolute_file_path: str, path_for_bom: Optional[str]) -> 'Component name=path_for_bom if path_for_bom else absolute_file_path, version='0.0.0-{}'.format(sha1_hash[0:12]), hashes=[ - HashType(algorithm=HashAlgorithm.SHA_1, hash_value=sha1_hash) + HashType(alg=HashAlgorithm.SHA_1, content=sha1_hash) ], - component_type=ComponentType.FILE, purl=PackageURL( + type=ComponentType.FILE, purl=PackageURL( type='generic', name=path_for_bom if path_for_bom else absolute_file_path, version='0.0.0-{}'.format(sha1_hash[0:12]) ) ) - def __init__(self, *, name: str, component_type: ComponentType = ComponentType.LIBRARY, - mime_type: Optional[str] = None, bom_ref: Optional[str] = None, + def __init__(self, *, name: str, type: ComponentType = ComponentType.LIBRARY, + mime_type: Optional[str] = None, bom_ref: Optional[Union[str, BomRef]] = None, supplier: Optional[OrganizationalEntity] = None, author: Optional[str] = None, publisher: Optional[str] = None, group: Optional[str] = None, version: Optional[str] = None, description: Optional[str] = None, scope: Optional[ComponentScope] = None, hashes: Optional[Iterable[HashType]] = None, licenses: Optional[Iterable[LicenseChoice]] = None, - copyright_: Optional[str] = None, purl: Optional[PackageURL] = None, + copyright: Optional[str] = None, purl: Optional[PackageURL] = None, external_references: Optional[Iterable[ExternalReference]] = None, properties: Optional[Iterable[Property]] = None, release_notes: Optional[ReleaseNotes] = None, cpe: Optional[str] = None, swid: Optional[Swid] = None, pedigree: Optional[Pedigree] = None, components: Optional[Iterable['Component']] = None, evidence: Optional[ComponentEvidence] = None, + modified: bool = False, # Deprecated parameters kept for backwards compatibility namespace: Optional[str] = None, license_str: Optional[str] = None ) -> None: - self.type = component_type + self.type = type self.mime_type = mime_type - self._bom_ref = BomRef(value=bom_ref) + if isinstance(bom_ref, BomRef): + self._bom_ref = bom_ref + else: + self._bom_ref = BomRef(value=str(bom_ref) if bom_ref else str(uuid4())) self.supplier = supplier self.author = author self.publisher = publisher @@ -734,10 +782,11 @@ def __init__(self, *, name: str, component_type: ComponentType = ComponentType.L self.scope = scope self.hashes = hashes or [] # type: ignore self.licenses = licenses or [] # type: ignore - self.copyright = copyright_ + self.copyright = copyright self.cpe = cpe self.purl = purl self.swid = swid + self.modified = modified self.pedigree = pedigree self.external_references = external_references or [] # type: ignore self.properties = properties or [] # type: ignore @@ -760,12 +809,12 @@ def __init__(self, *, name: str, component_type: ComponentType = ComponentType.L 'standard', DeprecationWarning ) if not licenses: - self.licenses = [LicenseChoice(license_expression=license_str)] # type: ignore + self.licenses = [LicenseChoice(expression=license_str)] # type: ignore self.__dependencies: "SortedSet[BomRef]" = SortedSet() - self.__vulnerabilites: "SortedSet[Vulnerability]" = SortedSet() - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def type(self) -> ComponentType: """ Get the type of this Component. @@ -776,8 +825,8 @@ def type(self) -> ComponentType: return self._type @type.setter - def type(self, component_type: ComponentType) -> None: - self._type = component_type + def type(self, type: ComponentType) -> None: + self._type = type @property def mime_type(self) -> Optional[str]: @@ -797,7 +846,15 @@ def mime_type(self) -> Optional[str]: def mime_type(self, mime_type: Optional[str]) -> None: self._mime_type = mime_type - @property + @property # type: ignore[misc] + @serializable.json_name('bom-ref') + @serializable.type_mapping(BomRefHelper) + @serializable.view(SchemaVersion1Dot1) + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_attribute() + @serializable.xml_name('bom-ref') def bom_ref(self) -> BomRef: """ An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref MUST be @@ -810,7 +867,11 @@ def bom_ref(self) -> BomRef: """ return self._bom_ref - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_sequence(1) def supplier(self) -> Optional[OrganizationalEntity]: """ The organization that supplied the component. The supplier may often be the manufacture, but may also be a @@ -825,7 +886,11 @@ def supplier(self) -> Optional[OrganizationalEntity]: def supplier(self, supplier: Optional[OrganizationalEntity]) -> None: self._supplier = supplier - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_sequence(2) def author(self) -> Optional[str]: """ The person(s) or organization(s) that authored the component. @@ -839,7 +904,8 @@ def author(self) -> Optional[str]: def author(self, author: Optional[str]) -> None: self._author = author - @property + @property # type: ignore[misc] + @serializable.xml_sequence(3) def publisher(self) -> Optional[str]: """ The person(s) or organization(s) that published the component @@ -853,7 +919,8 @@ def publisher(self) -> Optional[str]: def publisher(self, publisher: Optional[str]) -> None: self._publisher = publisher - @property + @property # type: ignore[misc] + @serializable.xml_sequence(4) def group(self) -> Optional[str]: """ The grouping name or identifier. This will often be a shortened, single name of the company or project that @@ -871,7 +938,8 @@ def group(self) -> Optional[str]: def group(self, group: Optional[str]) -> None: self._group = group - @property + @property # type: ignore[misc] + @serializable.xml_sequence(5) def name(self) -> str: """ The name of the component. @@ -889,7 +957,12 @@ def name(self) -> str: def name(self, name: str) -> None: self._name = name - @property + @property # type: ignore[misc] + @serializable.include_none(SchemaVersion1Dot0, "") + @serializable.include_none(SchemaVersion1Dot1, "") + @serializable.include_none(SchemaVersion1Dot2, "") + @serializable.include_none(SchemaVersion1Dot3, "") + @serializable.xml_sequence(6) def version(self) -> Optional[str]: """ The component version. The version should ideally comply with semantic versioning but is not enforced. @@ -906,7 +979,8 @@ def version(self) -> Optional[str]: def version(self, version: Optional[str]) -> None: self._version = version - @property + @property # type: ignore[misc] + @serializable.xml_sequence(7) def description(self) -> Optional[str]: """ Get the description of this Component. @@ -920,7 +994,8 @@ def description(self) -> Optional[str]: def description(self, description: Optional[str]) -> None: self._description = description - @property + @property # type: ignore[misc] + @serializable.xml_sequence(8) def scope(self) -> Optional[ComponentScope]: """ Specifies the scope of the component. @@ -936,7 +1011,9 @@ def scope(self) -> Optional[ComponentScope]: def scope(self, scope: Optional[ComponentScope]) -> None: self._scope = scope - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'hash') + @serializable.xml_sequence(9) def hashes(self) -> "SortedSet[HashType]": """ Optional list of hashes that help specify the integrity of this Component. @@ -950,7 +1027,13 @@ def hashes(self) -> "SortedSet[HashType]": def hashes(self, hashes: Iterable[HashType]) -> None: self._hashes = SortedSet(hashes) - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot1) + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'licenses') + @serializable.xml_sequence(10) def licenses(self) -> "SortedSet[LicenseChoice]": """ A optional list of statements about how this Component is licensed. @@ -964,7 +1047,8 @@ def licenses(self) -> "SortedSet[LicenseChoice]": def licenses(self, licenses: Iterable[LicenseChoice]) -> None: self._licenses = SortedSet(licenses) - @property + @property # type: ignore[misc] + @serializable.xml_sequence(11) def copyright(self) -> Optional[str]: """ An optional copyright notice informing users of the underlying claims to copyright ownership in a published @@ -976,10 +1060,11 @@ def copyright(self) -> Optional[str]: return self._copyright @copyright.setter - def copyright(self, copyright_: Optional[str]) -> None: - self._copyright = copyright_ + def copyright(self, copyright: Optional[str]) -> None: + self._copyright = copyright - @property + @property # type: ignore[misc] + @serializable.xml_sequence(12) def cpe(self) -> Optional[str]: """ Specifies a well-formed CPE name that conforms to the CPE 2.2 or 2.3 specification. @@ -994,7 +1079,9 @@ def cpe(self) -> Optional[str]: def cpe(self, cpe: Optional[str]) -> None: self._cpe = cpe - @property + @property # type: ignore[misc] + @serializable.type_mapping(PackageUrl) + @serializable.xml_sequence(13) def purl(self) -> Optional[PackageURL]: """ Specifies the package-url (PURL). @@ -1011,7 +1098,11 @@ def purl(self) -> Optional[PackageURL]: def purl(self, purl: Optional[PackageURL]) -> None: self._purl = purl - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_sequence(14) def swid(self) -> Optional[Swid]: """ Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags. @@ -1025,7 +1116,22 @@ def swid(self) -> Optional[Swid]: def swid(self, swid: Optional[Swid]) -> None: self._swid = swid - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot0) + @serializable.xml_sequence(18) + def modified(self) -> bool: + return self._modified + + @modified.setter + def modified(self, modified: bool) -> None: + self._modified = modified + + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot1) + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_sequence(16) def pedigree(self) -> Optional[Pedigree]: """ Component pedigree is a way to document complex supply chain scenarios where components are created, @@ -1040,7 +1146,13 @@ def pedigree(self) -> Optional[Pedigree]: def pedigree(self, pedigree: Optional[Pedigree]) -> None: self._pedigree = pedigree - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot1) + @serializable.view(SchemaVersion1Dot2) + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference') + @serializable.xml_sequence(17) def external_references(self) -> "SortedSet[ExternalReference]": """ Provides the ability to document external references related to the component or to the project the component @@ -1055,7 +1167,11 @@ def external_references(self) -> "SortedSet[ExternalReference]": def external_references(self, external_references: Iterable[ExternalReference]) -> None: self._external_references = SortedSet(external_references) - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property') + @serializable.xml_sequence(18) def properties(self) -> "SortedSet[Property]": """ Provides the ability to document properties in a key/value store. This provides flexibility to include data not @@ -1070,7 +1186,9 @@ def properties(self) -> "SortedSet[Property]": def properties(self, properties: Iterable[Property]) -> None: self._properties = SortedSet(properties) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'component') + @serializable.xml_sequence(19) def components(self) -> "SortedSet['Component']": """ A list of software and hardware components included in the parent component. This is not a dependency tree. It @@ -1086,7 +1204,10 @@ def components(self) -> "SortedSet['Component']": def components(self, components: Iterable['Component']) -> None: self._components = SortedSet(components) - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_sequence(20) def evidence(self) -> Optional[ComponentEvidence]: """ Provides the ability to document evidence collected through various forms of extraction or analysis. @@ -1100,7 +1221,9 @@ def evidence(self) -> Optional[ComponentEvidence]: def evidence(self, evidence: Optional[ComponentEvidence]) -> None: self._evidence = evidence - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_sequence(21) def release_notes(self) -> Optional[ReleaseNotes]: """ Specifies optional release notes. @@ -1114,51 +1237,6 @@ def release_notes(self) -> Optional[ReleaseNotes]: def release_notes(self, release_notes: Optional[ReleaseNotes]) -> None: self._release_notes = release_notes - @property - def dependencies(self) -> "SortedSet[BomRef]": - """ - Set of `BomRef` that this Component depends on. - - Returns: - Set of `BomRef` - """ - return self.__dependencies - - @dependencies.setter - def dependencies(self, dependencies: Iterable[BomRef]) -> None: - self.__dependencies = SortedSet(dependencies) - - def add_vulnerability(self, vulnerability: Vulnerability) -> None: - """ - Add a Vulnerability to this Component. - - Args: - vulnerability: - `cyclonedx.model.vulnerability.Vulnerability` instance to add to this Component. - - Returns: - None - """ - self.__vulnerabilites.add(vulnerability) - - def get_vulnerabilities(self) -> "SortedSet[Vulnerability]": - """ - Get all the Vulnerabilities for this Component. - - Returns: - Set of `Vulnerability` - """ - return self.__vulnerabilites - - def has_vulnerabilities(self) -> bool: - """ - Does this Component have any vulnerabilities? - - Returns: - `True` if this Component has 1 or more vulnerabilities, `False` otherwise. - """ - return bool(self.get_vulnerabilities()) - def get_all_nested_components(self, include_self: bool = False) -> Set["Component"]: components = set() if include_self: @@ -1182,8 +1260,8 @@ def __eq__(self, other: object) -> bool: def __lt__(self, other: Any) -> bool: if isinstance(other, Component): - return ComparableTuple((self.type, self.group, self.name, self.version)) < \ - ComparableTuple((other.type, other.group, other.name, other.version)) + return ComparableTuple((self.type, self.group, self.name, self.version)) < ComparableTuple( + (other.type, other.group, other.name, other.version)) return NotImplemented def __hash__(self) -> int: @@ -1191,11 +1269,12 @@ def __hash__(self) -> int: self.type, self.mime_type, self.supplier, self.author, self.publisher, self.group, self.name, self.version, self.description, self.scope, tuple(self.hashes), tuple(self.licenses), self.copyright, self.cpe, self.purl, self.swid, self.pedigree, tuple(self.external_references), tuple(self.properties), - tuple(self.components), self.evidence, self.release_notes + tuple(self.components), self.evidence, self.release_notes, self.modified )) def __repr__(self) -> str: - return f'' + return f'' # Deprecated methods def get_namespace(self) -> Optional[str]: diff --git a/cyclonedx/model/dependency.py b/cyclonedx/model/dependency.py index a1ab9fb2..b8303b55 100644 --- a/cyclonedx/model/dependency.py +++ b/cyclonedx/model/dependency.py @@ -17,33 +17,97 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -from typing import Iterable, Optional +from abc import ABC, abstractmethod +from typing import Any, Iterable, List, Optional, Set +import serializable from sortedcontainers import SortedSet +from cyclonedx.model import ComparableTuple +from cyclonedx.serialization import BomRefHelper + from .bom_ref import BomRef +class DependencyDependencies(serializable.BaseHelper): # type: ignore + + @classmethod + def serialize(cls, o: object) -> List[str]: + if isinstance(o, SortedSet): + return list(map(lambda i: str(i.ref), o)) + + raise ValueError(f'Attempt to serialize a non-Dependency: {o.__class__}') + + @classmethod + def deserialize(cls, o: object) -> Set["Dependency"]: + dependencies: Set["Dependency"] = set() + if isinstance(o, list): + for v in o: + dependencies.add(Dependency(ref=BomRef(value=v))) + return dependencies + + +@serializable.serializable_class class Dependency: """ - This is our internal representation of a Dependency for a Component. + Models a Dependency within a BOM. .. note:: - See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_dependencyType + See https://cyclonedx.org/docs/1.4/xml/#type_dependencyType """ - def __init__(self, *, ref: BomRef, depends_on: Optional[Iterable[BomRef]] = None) -> None: - self._ref = ref - self.depends_on = depends_on or [] # type: ignore + def __init__(self, ref: BomRef, dependencies: Optional[Iterable["Dependency"]] = None) -> None: + self.ref = ref + self.dependencies = SortedSet(dependencies) or SortedSet() - @property + @property # type: ignore[misc] + @serializable.type_mapping(BomRefHelper) + @serializable.xml_attribute() def ref(self) -> BomRef: return self._ref - @property - def depends_on(self) -> "SortedSet[BomRef]": - return self._depends_on + @ref.setter + def ref(self, ref: BomRef) -> None: + self._ref = ref + + @property # type: ignore[misc] + @serializable.json_name('dependsOn') + @serializable.type_mapping(DependencyDependencies) + @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'dependency') + def dependencies(self) -> "SortedSet[Dependency]": + return self._dependencies - @depends_on.setter - def depends_on(self, depends_on: Iterable[BomRef]) -> None: - self._depends_on = SortedSet(depends_on) + @dependencies.setter + def dependencies(self, dependencies: Iterable["Dependency"]) -> None: + self._dependencies = SortedSet(dependencies) + + def dependencies_as_bom_refs(self) -> Set[BomRef]: + return set(map(lambda d: d.ref, self.dependencies)) + + def __eq__(self, other: object) -> bool: + if isinstance(other, Dependency): + return hash(other) == hash(self) + return False + + def __lt__(self, other: Any) -> bool: + if isinstance(other, Dependency): + return ComparableTuple((self.ref, tuple(self.dependencies))) < ComparableTuple( + (other.ref, tuple(other.dependencies))) + return NotImplemented + + def __hash__(self) -> int: + return hash((self.ref, tuple(self.dependencies))) + + def __repr__(self) -> str: + return f'' + + +class Dependable(ABC): + """ + Dependable objects can be part of the Dependency Graph + """ + + @property + @abstractmethod + def bom_ref(self) -> BomRef: + pass diff --git a/cyclonedx/model/impact_analysis.py b/cyclonedx/model/impact_analysis.py index 59eaf246..830912f6 100644 --- a/cyclonedx/model/impact_analysis.py +++ b/cyclonedx/model/impact_analysis.py @@ -19,6 +19,8 @@ from enum import Enum +import serializable + """ This set of classes represents the data about Impact Analysis. @@ -29,6 +31,7 @@ """ +@serializable.serializable_enum class ImpactAnalysisAffectedStatus(str, Enum): """ Enum object that defines the permissible impact analysis affected states. @@ -50,6 +53,7 @@ class ImpactAnalysisAffectedStatus(str, Enum): UNKNOWN = 'unknown' +@serializable.serializable_enum class ImpactAnalysisJustification(str, Enum): """ Enum object that defines the rationale of why the impact analysis state was asserted. @@ -69,6 +73,7 @@ class ImpactAnalysisJustification(str, Enum): REQUIRES_ENVIRONMENT = 'requires_environment' +@serializable.serializable_enum class ImpactAnalysisResponse(str, Enum): """ Enum object that defines the valid rationales as to why the impact analysis state was asserted. @@ -84,6 +89,7 @@ class ImpactAnalysisResponse(str, Enum): WORKAROUND_AVAILABLE = 'workaround_available' +@serializable.serializable_enum class ImpactAnalysisState(str, Enum): """ Enum object that defines the permissible impact analysis states. diff --git a/cyclonedx/model/issue.py b/cyclonedx/model/issue.py index cb965192..554f2772 100644 --- a/cyclonedx/model/issue.py +++ b/cyclonedx/model/issue.py @@ -18,6 +18,7 @@ from enum import Enum from typing import Any, Iterable, Optional +import serializable from sortedcontainers import SortedSet from ..exception.model import NoPropertiesProvidedException @@ -36,6 +37,7 @@ class IssueClassification(str, Enum): SECURITY = 'security' +@serializable.serializable_class class IssueTypeSource: """ This is our internal representation ofa source within the IssueType complex type that can be used in multiple @@ -98,6 +100,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class IssueType: """ This is our internal representation of an IssueType complex type that can be used in multiple places within @@ -107,17 +110,18 @@ class IssueType: See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_issueType """ - def __init__(self, *, classification: IssueClassification, id_: Optional[str] = None, name: Optional[str] = None, + def __init__(self, *, type: IssueClassification, id: Optional[str] = None, name: Optional[str] = None, description: Optional[str] = None, source: Optional[IssueTypeSource] = None, references: Optional[Iterable[XsUri]] = None) -> None: - self.type = classification - self.id = id_ + self.type = type + self.id = id self.name = name self.description = description self.source = source self.references = references or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.xml_attribute() def type(self) -> IssueClassification: """ Specifies the type of issue. @@ -128,10 +132,11 @@ def type(self) -> IssueClassification: return self._type @type.setter - def type(self, classification: IssueClassification) -> None: - self._type = classification + def type(self, type: IssueClassification) -> None: + self._type = type - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def id(self) -> Optional[str]: """ The identifier of the issue assigned by the source of the issue. @@ -142,10 +147,11 @@ def id(self) -> Optional[str]: return self._id @id.setter - def id(self, id_: Optional[str]) -> None: - self._id = id_ + def id(self, id: Optional[str]) -> None: + self._id = id - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def name(self) -> Optional[str]: """ The name of the issue. @@ -159,7 +165,8 @@ def name(self) -> Optional[str]: def name(self, name: Optional[str]) -> None: self._name = name - @property + @property # type: ignore[misc] + @serializable.xml_sequence(3) def description(self) -> Optional[str]: """ A description of the issue. @@ -173,7 +180,8 @@ def description(self) -> Optional[str]: def description(self, description: Optional[str]) -> None: self._description = description - @property + @property # type: ignore[misc] + @serializable.xml_sequence(4) def source(self) -> Optional[IssueTypeSource]: """ The source of this issue. @@ -187,7 +195,9 @@ def source(self) -> Optional[IssueTypeSource]: def source(self, source: Optional[IssueTypeSource]) -> None: self._source = source - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'url') + @serializable.xml_sequence(5) def references(self) -> "SortedSet[XsUri]": """ Any reference URLs related to this issue. diff --git a/cyclonedx/model/release_note.py b/cyclonedx/model/release_note.py index 914fc670..eb942381 100644 --- a/cyclonedx/model/release_note.py +++ b/cyclonedx/model/release_note.py @@ -20,12 +20,14 @@ from datetime import datetime from typing import Iterable, Optional +import serializable from sortedcontainers import SortedSet from ..model import Note, Property, XsUri from ..model.issue import IssueType +@serializable.serializable_class class ReleaseNotes: """ This is our internal representation of a `releaseNotesType` for a Component in a BOM. @@ -34,12 +36,12 @@ class ReleaseNotes: See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/#type_releaseNotesType """ - def __init__(self, *, type_: str, title: Optional[str] = None, featured_image: Optional[XsUri] = None, + def __init__(self, *, type: str, title: Optional[str] = None, featured_image: Optional[XsUri] = None, social_image: Optional[XsUri] = None, description: Optional[str] = None, timestamp: Optional[datetime] = None, aliases: Optional[Iterable[str]] = None, tags: Optional[Iterable[str]] = None, resolves: Optional[Iterable[IssueType]] = None, notes: Optional[Iterable[Note]] = None, properties: Optional[Iterable[Property]] = None) -> None: - self.type = type_ + self.type = type self.title = title self.featured_image = featured_image self.social_image = social_image @@ -51,7 +53,8 @@ def __init__(self, *, type_: str, title: Optional[str] = None, featured_image: O self.notes = notes or [] # type: ignore self.properties = properties or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def type(self) -> str: """ The software versioning type. @@ -73,10 +76,11 @@ def type(self) -> str: return self._type @type.setter - def type(self, type_: str) -> None: - self._type = type_ + def type(self, type: str) -> None: + self._type = type - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def title(self) -> Optional[str]: """ The title of the release. @@ -87,7 +91,8 @@ def title(self) -> Optional[str]: def title(self, title: Optional[str]) -> None: self._title = title - @property + @property # type: ignore[misc] + @serializable.xml_sequence(3) def featured_image(self) -> Optional[XsUri]: """ The URL to an image that may be prominently displayed with the release note. @@ -98,7 +103,8 @@ def featured_image(self) -> Optional[XsUri]: def featured_image(self, featured_image: Optional[XsUri]) -> None: self._featured_image = featured_image - @property + @property # type: ignore[misc] + @serializable.xml_sequence(4) def social_image(self) -> Optional[XsUri]: """ The URL to an image that may be used in messaging on social media platforms. @@ -109,7 +115,8 @@ def social_image(self) -> Optional[XsUri]: def social_image(self, social_image: Optional[XsUri]) -> None: self._social_image = social_image - @property + @property # type: ignore[misc] + @serializable.xml_sequence(5) def description(self) -> Optional[str]: """ A short description of the release. @@ -120,7 +127,9 @@ def description(self) -> Optional[str]: def description(self, description: Optional[str]) -> None: self._description = description - @property + @property # type: ignore[misc] + @serializable.type_mapping(serializable.helpers.XsdDateTime) + @serializable.xml_sequence(6) def timestamp(self) -> Optional[datetime]: """ The date and time (timestamp) when the release note was created. @@ -131,7 +140,9 @@ def timestamp(self) -> Optional[datetime]: def timestamp(self, timestamp: Optional[datetime]) -> None: self._timestamp = timestamp - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'alias') + @serializable.xml_sequence(7) def aliases(self) -> "SortedSet[str]": """ One or more alternate names the release may be referred to. This may include unofficial terms used by @@ -146,7 +157,9 @@ def aliases(self) -> "SortedSet[str]": def aliases(self, aliases: Iterable[str]) -> None: self._aliases = SortedSet(aliases) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'tag') + @serializable.xml_sequence(8) def tags(self) -> "SortedSet[str]": """ One or more tags that may aid in search or retrieval of the release note. @@ -160,7 +173,9 @@ def tags(self) -> "SortedSet[str]": def tags(self, tags: Iterable[str]) -> None: self._tags = SortedSet(tags) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'issue') + @serializable.xml_sequence(9) def resolves(self) -> "SortedSet[IssueType]": """ A collection of issues that have been resolved. @@ -174,7 +189,9 @@ def resolves(self) -> "SortedSet[IssueType]": def resolves(self, resolves: Iterable[IssueType]) -> None: self._resolves = SortedSet(resolves) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'note') + @serializable.xml_sequence(10) def notes(self) -> "SortedSet[Note]": """ Zero or more release notes containing the locale and content. Multiple note elements may be specified to support @@ -189,7 +206,9 @@ def notes(self) -> "SortedSet[Note]": def notes(self, notes: Iterable[Note]) -> None: self._notes = SortedSet(notes) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property') + @serializable.xml_sequence(11) def properties(self) -> "SortedSet[Property]": """ Provides the ability to document properties in a name-value store. This provides flexibility to include data not diff --git a/cyclonedx/model/service.py b/cyclonedx/model/service.py index 7e78b041..e951c31a 100644 --- a/cyclonedx/model/service.py +++ b/cyclonedx/model/service.py @@ -15,10 +15,15 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -from typing import Any, Iterable, Optional +from typing import Any, Iterable, Optional, Union +from uuid import uuid4 +import serializable from sortedcontainers import SortedSet +from cyclonedx.serialization import BomRefHelper + +from ..schema.schema import SchemaVersion1Dot3, SchemaVersion1Dot4 from . import ( ComparableTuple, DataClassification, @@ -29,6 +34,7 @@ XsUri, ) from .bom_ref import BomRef +from .dependency import Dependable from .release_note import ReleaseNotes """ @@ -39,7 +45,8 @@ """ -class Service: +@serializable.serializable_class +class Service(Dependable): """ Class that models the `service` complex type in the CycloneDX schema. @@ -47,7 +54,8 @@ class Service: See the CycloneDX schema: https://cyclonedx.org/docs/1.4/xml/#type_service """ - def __init__(self, *, name: str, bom_ref: Optional[str] = None, provider: Optional[OrganizationalEntity] = None, + def __init__(self, *, name: str, bom_ref: Optional[Union[str, BomRef]] = None, + provider: Optional[OrganizationalEntity] = None, group: Optional[str] = None, version: Optional[str] = None, description: Optional[str] = None, endpoints: Optional[Iterable[XsUri]] = None, authenticated: Optional[bool] = None, x_trust_boundary: Optional[bool] = None, data: Optional[Iterable[DataClassification]] = None, @@ -57,7 +65,10 @@ def __init__(self, *, name: str, bom_ref: Optional[str] = None, provider: Option services: Optional[Iterable['Service']] = None, release_notes: Optional[ReleaseNotes] = None, ) -> None: - self._bom_ref = BomRef(value=bom_ref) + if type(bom_ref) == BomRef: + self._bom_ref = bom_ref + else: + self._bom_ref = BomRef(value=str(bom_ref) if bom_ref else str(uuid4())) self.provider = provider self.group = group self.name = name @@ -73,7 +84,11 @@ def __init__(self, *, name: str, bom_ref: Optional[str] = None, provider: Option self.release_notes = release_notes self.properties = properties or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.json_name('bom-ref') + @serializable.type_mapping(BomRefHelper) + @serializable.xml_attribute() + @serializable.xml_name('bom-ref') def bom_ref(self) -> BomRef: """ An optional identifier which can be used to reference the service elsewhere in the BOM. Uniqueness is enforced @@ -86,7 +101,8 @@ def bom_ref(self) -> BomRef: """ return self._bom_ref - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def provider(self) -> Optional[OrganizationalEntity]: """ Get the The organization that provides the service. @@ -100,7 +116,8 @@ def provider(self) -> Optional[OrganizationalEntity]: def provider(self, provider: Optional[OrganizationalEntity]) -> None: self._provider = provider - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def group(self) -> Optional[str]: """ The grouping name, namespace, or identifier. This will often be a shortened, single name of the company or @@ -115,7 +132,8 @@ def group(self) -> Optional[str]: def group(self, group: Optional[str]) -> None: self._group = group - @property + @property # type: ignore[misc] + @serializable.xml_sequence(3) def name(self) -> str: """ The name of the service. This will often be a shortened, single name of the service. @@ -129,7 +147,8 @@ def name(self) -> str: def name(self, name: str) -> None: self._name = name - @property + @property # type: ignore[misc] + @serializable.xml_sequence(4) def version(self) -> Optional[str]: """ The service version. @@ -143,7 +162,8 @@ def version(self) -> Optional[str]: def version(self, version: Optional[str]) -> None: self._version = version - @property + @property # type: ignore[misc] + @serializable.xml_sequence(5) def description(self) -> Optional[str]: """ Specifies a description for the service. @@ -157,7 +177,9 @@ def description(self) -> Optional[str]: def description(self, description: Optional[str]) -> None: self._description = description - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'endpoint') + @serializable.xml_sequence(6) def endpoints(self) -> "SortedSet[XsUri]": """ A list of endpoints URI's this service provides. @@ -171,7 +193,8 @@ def endpoints(self) -> "SortedSet[XsUri]": def endpoints(self, endpoints: Iterable[XsUri]) -> None: self._endpoints = SortedSet(endpoints) - @property + @property # type: ignore[misc] + @serializable.xml_sequence(7) def authenticated(self) -> Optional[bool]: """ A boolean value indicating if the service requires authentication. A value of true indicates the service @@ -188,7 +211,10 @@ def authenticated(self) -> Optional[bool]: def authenticated(self, authenticated: Optional[bool]) -> None: self._authenticated = authenticated - @property + @property # type: ignore[misc] + @serializable.json_name('x-trust-boundary') + @serializable.xml_name('x-trust-boundary') + @serializable.xml_sequence(8) def x_trust_boundary(self) -> Optional[bool]: """ A boolean value indicating if use of the service crosses a trust zone or boundary. A value of true indicates @@ -205,7 +231,9 @@ def x_trust_boundary(self) -> Optional[bool]: def x_trust_boundary(self, x_trust_boundary: Optional[bool]) -> None: self._x_trust_boundary = x_trust_boundary - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'classification') + @serializable.xml_sequence(9) def data(self) -> "SortedSet[DataClassification]": """ Specifies the data classification. @@ -219,7 +247,9 @@ def data(self) -> "SortedSet[DataClassification]": def data(self, data: Iterable[DataClassification]) -> None: self._data = SortedSet(data) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'licenses') + @serializable.xml_sequence(10) def licenses(self) -> "SortedSet[LicenseChoice]": """ A optional list of statements about how this Service is licensed. @@ -233,7 +263,9 @@ def licenses(self) -> "SortedSet[LicenseChoice]": def licenses(self, licenses: Iterable[LicenseChoice]) -> None: self._licenses = SortedSet(licenses) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference') + @serializable.xml_sequence(11) def external_references(self) -> "SortedSet[ExternalReference]": """ Provides the ability to document external references related to the Service. @@ -247,7 +279,9 @@ def external_references(self) -> "SortedSet[ExternalReference]": def external_references(self, external_references: Iterable[ExternalReference]) -> None: self._external_references = SortedSet(external_references) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'service') + @serializable.xml_sequence(13) def services(self) -> "SortedSet['Service']": """ A list of services included or deployed behind the parent service. @@ -265,7 +299,9 @@ def services(self) -> "SortedSet['Service']": def services(self, services: Iterable['Service']) -> None: self._services = SortedSet(services) - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_sequence(14) def release_notes(self) -> Optional[ReleaseNotes]: """ Specifies optional release notes. @@ -279,7 +315,11 @@ def release_notes(self) -> Optional[ReleaseNotes]: def release_notes(self, release_notes: Optional[ReleaseNotes]) -> None: self._release_notes = release_notes - @property + @property # type: ignore[misc] + @serializable.view(SchemaVersion1Dot3) + @serializable.view(SchemaVersion1Dot4) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property') + @serializable.xml_sequence(12) def properties(self) -> "SortedSet[Property]": """ Provides the ability to document properties in a key/value store. This provides flexibility to include data not @@ -313,4 +353,4 @@ def __hash__(self) -> int: )) def __repr__(self) -> str: - return f'' + return f'' diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index 2947c745..cce31f73 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -23,10 +23,13 @@ from decimal import Decimal from enum import Enum from typing import Any, Iterable, Optional, Tuple, Union +from uuid import uuid4 +import serializable from sortedcontainers import SortedSet from ..exception.model import MutuallyExclusivePropertiesException, NoPropertiesProvidedException +from ..serialization import BomRefHelper from . import ComparableTuple, OrganizationalContact, OrganizationalEntity, Property, Tool, XsUri from .bom_ref import BomRef from .impact_analysis import ( @@ -49,6 +52,7 @@ """ +@serializable.serializable_class class BomTargetVersionRange: """ Class that represents either a version or version range and its affected status. @@ -59,21 +63,22 @@ class BomTargetVersionRange: See the CycloneDX schema: https://cyclonedx.org/docs/1.4/#type_vulnerabilityType """ - def __init__(self, *, version: Optional[str] = None, version_range: Optional[str] = None, + def __init__(self, *, version: Optional[str] = None, range: Optional[str] = None, status: Optional[ImpactAnalysisAffectedStatus] = None) -> None: - if not version and not version_range: + if not version and not range: raise NoPropertiesProvidedException( - 'One of version or version_range must be provided for BomTargetVersionRange - neither provided.' + 'One of version or range must be provided for BomTargetVersionRange - neither provided.' ) - if version and version_range: + if version and range: raise MutuallyExclusivePropertiesException( - 'Either version or version_range should be provided for BomTargetVersionRange - both provided.' + 'Either version or range should be provided for BomTargetVersionRange - both provided.' ) self.version = version - self.range = version_range + self.range = range self.status = status - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def version(self) -> Optional[str]: """ A single version of a component or service. @@ -84,7 +89,8 @@ def version(self) -> Optional[str]: def version(self, version: Optional[str]) -> None: self._version = version - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def range(self) -> Optional[str]: """ A version range specified in Package URL Version Range syntax (vers) which is defined at @@ -97,10 +103,11 @@ def range(self) -> Optional[str]: return self._range @range.setter - def range(self, version_range: Optional[str]) -> None: - self._range = version_range + def range(self, range: Optional[str]) -> None: + self._range = range - @property + @property # type: ignore[misc] + @serializable.xml_sequence(3) def status(self) -> Optional[ImpactAnalysisAffectedStatus]: """ The vulnerability status for the version or range of versions. @@ -129,6 +136,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class BomTarget: """ Class that represents referencing a Component or Service in a BOM. @@ -146,7 +154,8 @@ def __init__(self, *, ref: str, versions: Optional[Iterable[BomTargetVersionRang self.ref = ref self.versions = versions or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def ref(self) -> str: """ Reference to a component or service by the objects `bom-ref`. @@ -157,7 +166,9 @@ def ref(self) -> str: def ref(self, ref: str) -> None: self._ref = ref - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'version') + @serializable.xml_sequence(2) def versions(self) -> "SortedSet[BomTargetVersionRange]": """ Zero or more individual versions or range of versions. @@ -188,6 +199,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class VulnerabilityAnalysis: """ Class that models the `analysis` sub-element of the `vulnerabilityType` complex type. @@ -207,10 +219,11 @@ def __init__(self, *, state: Optional[ImpactAnalysisState] = None, ) self.state = state self.justification = justification - self.response = responses or [] # type: ignore + self.responses = responses or [] # type: ignore self.detail = detail - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def state(self) -> Optional[ImpactAnalysisState]: """ The declared current state of an occurrence of a vulnerability, after automated or manual analysis. @@ -224,7 +237,8 @@ def state(self) -> Optional[ImpactAnalysisState]: def state(self, state: Optional[ImpactAnalysisState]) -> None: self._state = state - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def justification(self) -> Optional[ImpactAnalysisJustification]: """ The rationale of why the impact analysis state was asserted. @@ -238,8 +252,11 @@ def justification(self) -> Optional[ImpactAnalysisJustification]: def justification(self, justification: Optional[ImpactAnalysisJustification]) -> None: self._justification = justification - @property - def response(self) -> "SortedSet[ImpactAnalysisResponse]": + @property # type: ignore[misc] + @serializable.json_name('response') + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'response') + @serializable.xml_sequence(3) + def responses(self) -> "SortedSet[ImpactAnalysisResponse]": """ A list of responses to the vulnerability by the manufacturer, supplier, or project responsible for the affected component or service. More than one response is allowed. Responses are strongly encouraged for @@ -248,13 +265,14 @@ def response(self) -> "SortedSet[ImpactAnalysisResponse]": Returns: Set of `ImpactAnalysisResponse` """ - return self._response + return self._responses - @response.setter - def response(self, responses: Iterable[ImpactAnalysisResponse]) -> None: - self._response = SortedSet(responses) + @responses.setter + def responses(self, responses: Iterable[ImpactAnalysisResponse]) -> None: + self._responses = SortedSet(responses) - @property + @property # type: ignore[misc] + @serializable.xml_sequence(4) def detail(self) -> Optional[str]: """ A detailed description of the impact including methods used during assessment. If a vulnerability is not @@ -276,12 +294,13 @@ def __eq__(self, other: object) -> bool: return False def __hash__(self) -> int: - return hash((self.state, self.justification, tuple(self.response), self.detail)) + return hash((self.state, self.justification, tuple(self.responses), self.detail)) def __repr__(self) -> str: return f'' +@serializable.serializable_class class VulnerabilityAdvisory: """ Class that models the `advisoryType` complex type. @@ -294,7 +313,8 @@ def __init__(self, *, url: XsUri, title: Optional[str] = None) -> None: self.title = title self.url = url - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def title(self) -> Optional[str]: """ The title of this advisory. @@ -305,7 +325,8 @@ def title(self) -> Optional[str]: def title(self, title: Optional[str]) -> None: self._title = title - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def url(self) -> XsUri: """ The url of this advisory. @@ -333,6 +354,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class VulnerabilitySource: """ Class that models the `vulnerabilitySourceType` complex type. @@ -351,7 +373,8 @@ def __init__(self, *, name: Optional[str] = None, url: Optional[XsUri] = None) - self.name = name self.url = url - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def name(self) -> Optional[str]: """ Name of this Source. @@ -362,7 +385,8 @@ def name(self) -> Optional[str]: def name(self, name: Optional[str]) -> None: self._name = name - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def url(self) -> Optional[XsUri]: """ The url of this Source. @@ -390,6 +414,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class VulnerabilityReference: """ Class that models the nested `reference` within the `vulnerabilityType` complex type. @@ -411,7 +436,8 @@ def __init__(self, *, id: Optional[str] = None, source: Optional[VulnerabilitySo self.id = id self.source = source - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def id(self) -> Optional[str]: """ The identifier that uniquely identifies the vulnerability in the associated Source. For example: CVE-2021-39182. @@ -422,7 +448,8 @@ def id(self) -> Optional[str]: def id(self, id: Optional[str]) -> None: self._id = id - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def source(self) -> Optional[VulnerabilitySource]: """ The source that published the vulnerability. @@ -570,6 +597,7 @@ def get_from_cvss_scores(scores: Union[Tuple[float], float, None]) -> 'Vulnerabi return VulnerabilitySeverity.NONE +@serializable.serializable_class class VulnerabilityRating: """ Class that models the `ratingType` complex element CycloneDX core schema. @@ -616,7 +644,8 @@ def __init__(self, *, source: Optional[VulnerabilitySource] = None, score: Optio if vector and method: self.vector = method.get_localised_vector(vector=vector) - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def source(self) -> Optional[VulnerabilitySource]: """ The source that published the vulnerability. @@ -627,7 +656,9 @@ def source(self) -> Optional[VulnerabilitySource]: def source(self, source: Optional[VulnerabilitySource]) -> None: self._source = source - @property + @property # type: ignore[misc] + @serializable.string_format('.1f') + @serializable.xml_sequence(2) def score(self) -> Optional[Decimal]: """ The numerical score of the rating. @@ -638,7 +669,8 @@ def score(self) -> Optional[Decimal]: def score(self, score: Optional[Decimal]) -> None: self._score = score - @property + @property # type: ignore[misc] + @serializable.xml_sequence(3) def severity(self) -> Optional[VulnerabilitySeverity]: """ The textual representation of the severity that corresponds to the numerical score of the rating. @@ -649,7 +681,8 @@ def severity(self) -> Optional[VulnerabilitySeverity]: def severity(self, severity: Optional[VulnerabilitySeverity]) -> None: self._severity = severity - @property + @property # type: ignore[misc] + @serializable.xml_sequence(4) def method(self) -> Optional[VulnerabilityScoreSource]: """ The risk scoring methodology/standard used. @@ -660,7 +693,8 @@ def method(self) -> Optional[VulnerabilityScoreSource]: def method(self, score_source: Optional[VulnerabilityScoreSource]) -> None: self._method = score_source - @property + @property # type: ignore[misc] + @serializable.xml_sequence(5) def vector(self) -> Optional[str]: """ The textual representation of the metric values used to score the vulnerability - also known as the vector. @@ -671,7 +705,8 @@ def vector(self) -> Optional[str]: def vector(self, vector: Optional[str]) -> None: self._vector = vector - @property + @property # type: ignore[misc] + @serializable.xml_sequence(6) def justification(self) -> Optional[str]: """ An optional reason for rating the vulnerability as it was. @@ -690,18 +725,22 @@ def __eq__(self, other: object) -> bool: def __lt__(self, other: Any) -> bool: if isinstance(other, VulnerabilityRating): return ComparableTuple( - (self.severity, self.score, self.source, self.method, self.vector, self.justification)) < \ + (self.severity, self.score, float(f'{self.score or 0:.1f}'), self.method, self.vector, + self.justification)) < \ ComparableTuple( - (other.severity, other.score, other.source, other.method, other.vector, other.justification)) + (other.severity, other.score, float(f'{other.score or 0:.1f}'), other.method, other.vector, + other.justification)) return NotImplemented def __hash__(self) -> int: - return hash((self.source, self.score, self.severity, self.method, self.vector, self.justification)) + return hash( + (self.source, float(f'{self.score or 0:.1f}'), self.severity, self.method, self.vector, self.justification)) def __repr__(self) -> str: return f'' +@serializable.serializable_class class VulnerabilityCredits: """ Class that models the `credits` of `vulnerabilityType` complex type in the CycloneDX schema (version >= 1.4). @@ -722,7 +761,9 @@ def __init__(self, *, organizations: Optional[Iterable[OrganizationalEntity]] = self.organizations = organizations or [] # type: ignore self.individuals = individuals or [] # type: ignore - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'organization') + @serializable.xml_sequence(1) def organizations(self) -> "SortedSet[OrganizationalEntity]": """ The organizations credited with vulnerability discovery. @@ -736,7 +777,9 @@ def organizations(self) -> "SortedSet[OrganizationalEntity]": def organizations(self, organizations: Iterable[OrganizationalEntity]) -> None: self._organizations = SortedSet(organizations) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'individual') + @serializable.xml_sequence(2) def individuals(self) -> "SortedSet[OrganizationalContact]": """ The individuals, not associated with organizations, that are credited with vulnerability discovery. @@ -767,6 +810,7 @@ def __repr__(self) -> str: return f'' +@serializable.serializable_class class Vulnerability: """ Class that models the `vulnerabilityType` complex type in the CycloneDX schema (version >= 1.4). @@ -778,7 +822,7 @@ class Vulnerability: See the CycloneDX schema: https://cyclonedx.org/docs/1.4/#type_vulnerabilityType """ - def __init__(self, *, bom_ref: Optional[str] = None, id: Optional[str] = None, + def __init__(self, *, bom_ref: Optional[Union[str, BomRef]] = None, id: Optional[str] = None, source: Optional[VulnerabilitySource] = None, references: Optional[Iterable[VulnerabilityReference]] = None, ratings: Optional[Iterable[VulnerabilityRating]] = None, cwes: Optional[Iterable[int]] = None, @@ -787,12 +831,15 @@ def __init__(self, *, bom_ref: Optional[str] = None, id: Optional[str] = None, published: Optional[datetime] = None, updated: Optional[datetime] = None, credits: Optional[VulnerabilityCredits] = None, tools: Optional[Iterable[Tool]] = None, analysis: Optional[VulnerabilityAnalysis] = None, - affects_targets: Optional[Iterable[BomTarget]] = None, + affects: Optional[Iterable[BomTarget]] = None, properties: Optional[Iterable[Property]] = None, # Deprecated Parameters kept for backwards compatibility source_name: Optional[str] = None, source_url: Optional[str] = None, recommendations: Optional[Iterable[str]] = None) -> None: - self._bom_ref = BomRef(value=bom_ref) + if type(bom_ref) == BomRef: + self._bom_ref = bom_ref + else: + self._bom_ref = BomRef(value=str(bom_ref) if bom_ref else str(uuid4())) self.id = id self.source = source self.references = references or [] # type: ignore @@ -808,7 +855,7 @@ def __init__(self, *, bom_ref: Optional[str] = None, id: Optional[str] = None, self.credits = credits self.tools = tools or [] # type: ignore self.analysis = analysis - self.affects = affects_targets or [] # type: ignore + self.affects = affects or [] # type: ignore self.properties = properties or [] # type: ignore if source_name or source_url: @@ -821,7 +868,11 @@ def __init__(self, *, bom_ref: Optional[str] = None, id: Optional[str] = None, if not recommendation: self.recommendation = next(iter(recommendations)) - @property + @property # type: ignore[misc] + @serializable.json_name('bom-ref') + @serializable.type_mapping(BomRefHelper) + @serializable.xml_attribute() + @serializable.xml_name('bom-ref') def bom_ref(self) -> BomRef: """ Get the unique reference for this Vulnerability in this BOM. @@ -833,7 +884,8 @@ def bom_ref(self) -> BomRef: """ return self._bom_ref - @property + @property # type: ignore[misc] + @serializable.xml_sequence(1) def id(self) -> Optional[str]: """ The identifier that uniquely identifies the vulnerability. For example: CVE-2021-39182. @@ -847,7 +899,8 @@ def id(self) -> Optional[str]: def id(self, id: Optional[str]) -> None: self._id = id - @property + @property # type: ignore[misc] + @serializable.xml_sequence(2) def source(self) -> Optional[VulnerabilitySource]: """ The source that published the vulnerability. @@ -861,7 +914,9 @@ def source(self) -> Optional[VulnerabilitySource]: def source(self, source: Optional[VulnerabilitySource]) -> None: self._source = source - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference') + @serializable.xml_sequence(3) def references(self) -> "SortedSet[VulnerabilityReference]": """ Zero or more pointers to vulnerabilities that are the equivalent of the vulnerability specified. Often times, @@ -878,7 +933,9 @@ def references(self) -> "SortedSet[VulnerabilityReference]": def references(self, references: Iterable[VulnerabilityReference]) -> None: self._references = SortedSet(references) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'rating') + @serializable.xml_sequence(4) def ratings(self) -> "SortedSet[VulnerabilityRating]": """ List of vulnerability ratings. @@ -892,7 +949,9 @@ def ratings(self) -> "SortedSet[VulnerabilityRating]": def ratings(self, ratings: Iterable[VulnerabilityRating]) -> None: self._ratings = SortedSet(ratings) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'cwe') + @serializable.xml_sequence(5) def cwes(self) -> "SortedSet[int]": """ A list of CWE (Common Weakness Enumeration) identifiers. @@ -909,7 +968,8 @@ def cwes(self) -> "SortedSet[int]": def cwes(self, cwes: Iterable[int]) -> None: self._cwes = SortedSet(cwes) - @property + @property # type: ignore[misc] + @serializable.xml_sequence(6) def description(self) -> Optional[str]: """ A description of the vulnerability as provided by the source. @@ -923,7 +983,8 @@ def description(self) -> Optional[str]: def description(self, description: Optional[str]) -> None: self._description = description - @property + @property # type: ignore[misc] + @serializable.xml_sequence(7) def detail(self) -> Optional[str]: """ If available, an in-depth description of the vulnerability as provided by the source organization. Details @@ -938,7 +999,8 @@ def detail(self) -> Optional[str]: def detail(self, detail: Optional[str]) -> None: self._detail = detail - @property + @property # type: ignore[misc] + @serializable.xml_sequence(8) def recommendation(self) -> Optional[str]: """ Recommendations of how the vulnerability can be remediated or mitigated. @@ -952,7 +1014,9 @@ def recommendation(self) -> Optional[str]: def recommendation(self, recommendation: Optional[str]) -> None: self._recommendation = recommendation - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'advisory') + @serializable.xml_sequence(9) def advisories(self) -> "SortedSet[VulnerabilityAdvisory]": """ Advisories relating to the Vulnerability. @@ -966,7 +1030,9 @@ def advisories(self) -> "SortedSet[VulnerabilityAdvisory]": def advisories(self, advisories: Iterable[VulnerabilityAdvisory]) -> None: self._advisories = SortedSet(advisories) - @property + @property # type: ignore[misc] + @serializable.type_mapping(serializable.helpers.XsdDateTime) + @serializable.xml_sequence(10) def created(self) -> Optional[datetime]: """ The date and time (timestamp) when the vulnerability record was created in the vulnerability database. @@ -980,7 +1046,9 @@ def created(self) -> Optional[datetime]: def created(self, created: Optional[datetime]) -> None: self._created = created - @property + @property # type: ignore[misc] + @serializable.type_mapping(serializable.helpers.XsdDateTime) + @serializable.xml_sequence(11) def published(self) -> Optional[datetime]: """ The date and time (timestamp) when the vulnerability record was first published. @@ -994,7 +1062,9 @@ def published(self) -> Optional[datetime]: def published(self, published: Optional[datetime]) -> None: self._published = published - @property + @property # type: ignore[misc] + @serializable.type_mapping(serializable.helpers.XsdDateTime) + @serializable.xml_sequence(12) def updated(self) -> Optional[datetime]: """ The date and time (timestamp) when the vulnerability record was last updated. @@ -1008,7 +1078,8 @@ def updated(self) -> Optional[datetime]: def updated(self, updated: Optional[datetime]) -> None: self._updated = updated - @property + @property # type: ignore[misc] + @serializable.xml_sequence(13) def credits(self) -> Optional[VulnerabilityCredits]: """ Individuals or organizations credited with the discovery of the vulnerability. @@ -1022,7 +1093,9 @@ def credits(self) -> Optional[VulnerabilityCredits]: def credits(self, credits: Optional[VulnerabilityCredits]) -> None: self._credits = credits - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'tool') + @serializable.xml_sequence(14) def tools(self) -> "SortedSet[Tool]": """ The tool(s) used to identify, confirm, or score the vulnerability. @@ -1036,7 +1109,8 @@ def tools(self) -> "SortedSet[Tool]": def tools(self, tools: Iterable[Tool]) -> None: self._tools = SortedSet(tools) - @property + @property # type: ignore[misc] + @serializable.xml_sequence(15) def analysis(self) -> Optional[VulnerabilityAnalysis]: """ Analysis of the Vulnerability in your context. @@ -1050,7 +1124,9 @@ def analysis(self) -> Optional[VulnerabilityAnalysis]: def analysis(self, analysis: Optional[VulnerabilityAnalysis]) -> None: self._analysis = analysis - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'target') + @serializable.xml_sequence(16) def affects(self) -> "SortedSet[BomTarget]": """ The components or services that are affected by the vulnerability. @@ -1064,7 +1140,9 @@ def affects(self) -> "SortedSet[BomTarget]": def affects(self, affects_targets: Iterable[BomTarget]) -> None: self._affects = SortedSet(affects_targets) - @property + @property # type: ignore[misc] + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property') + @serializable.xml_sequence(17) def properties(self) -> "SortedSet[Property]": """ Provides the ability to document properties in a key/value store. This provides flexibility to include data not @@ -1100,4 +1178,4 @@ def __hash__(self) -> int: )) def __repr__(self) -> str: - return f'' + return f'' diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index 795def70..2f8658fa 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -22,34 +22,11 @@ import importlib import os from abc import ABC, abstractmethod -from enum import Enum from typing import Iterable, Union, cast from ..model.bom import Bom from ..model.component import Component - - -class OutputFormat(str, Enum): - JSON: str = 'Json' - XML: str = 'Xml' - - -class SchemaVersion(str, Enum): - V1_0: str = 'V1Dot0' - V1_1: str = 'V1Dot1' - V1_2: str = 'V1Dot2' - V1_3: str = 'V1Dot3' - V1_4: str = 'V1Dot4' - - def to_version(self) -> str: - """ - Return as a version string - e.g. `1.4` - - Returns: - `str` version - """ - return f'{self.value[1]}.{self.value[5]}' - +from ..schema import OutputFormat, SchemaVersion LATEST_SUPPORTED_SCHEMA_VERSION = SchemaVersion.V1_4 @@ -125,7 +102,7 @@ def get_instance(bom: Bom, output_format: OutputFormat = OutputFormat.XML, try: module = importlib.import_module(f"cyclonedx.output.{output_format.value.lower()}") output_klass = getattr(module, f"{output_format.value}{schema_version.value}") - except (ImportError, AttributeError): - raise ValueError(f"Unknown format {output_format.value.lower()!r}") from None + except (ImportError, AttributeError) as e: + raise ValueError(f"Unknown format {output_format.value.lower()!r}: {e}") from None return cast(BaseOutput, output_klass(bom=bom)) diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index d7bda784..75bc030b 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -19,12 +19,13 @@ import json from abc import abstractmethod -from typing import Any, Dict, List, Optional, Union +from typing import Optional from ..exception.output import FormatNotSupportedException from ..model.bom import Bom -from . import BaseOutput, SchemaVersion -from .schema import ( +from ..schema import SchemaVersion +from ..schema.schema import ( + SCHEMA_VERSIONS, BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, @@ -32,13 +33,7 @@ SchemaVersion1Dot3, SchemaVersion1Dot4, ) -from .serializer.json import CycloneDxJSONEncoder - -ComponentDict = Dict[str, Union[ - str, - List[Dict[str, str]], - List[Dict[str, Dict[str, str]]], - List[Dict[str, Union[str, List[Dict[str, str]]]]]]] +from . import BaseOutput class Json(BaseOutput, BaseSchemaVersion): @@ -52,171 +47,43 @@ def schema_version(self) -> SchemaVersion: return self.schema_version_enum def generate(self, force_regeneration: bool = False) -> None: - if self.generated and not force_regeneration: - return - - bom = self.get_bom() - bom.validate() - + # New Way schema_uri: Optional[str] = self._get_schema_uri() if not schema_uri: raise FormatNotSupportedException( f'JSON is not supported by CycloneDX in schema version {self.schema_version.to_version()}') - extras = {} - if self.bom_supports_dependencies(): - dep_components = self._chained_components(bom) - if bom.metadata.component: - dep_components = [bom.metadata.component, *dep_components] - dependencies = [] - for component in dep_components: - dependencies.append({ - 'ref': str(component.bom_ref), - 'dependsOn': [*map(str, component.dependencies)] - }) - if dependencies: - extras["dependencies"] = dependencies - del dep_components - - if self.bom_supports_vulnerabilities(): - vulnerabilities: List[Dict[Any, Any]] = [] - if bom.components: - for component in bom.components: - for vulnerability in component.get_vulnerabilities(): - vulnerabilities.append( - json.loads(json.dumps(vulnerability, cls=CycloneDxJSONEncoder)) - ) - if vulnerabilities: - extras["vulnerabilities"] = vulnerabilities - - bom_json = json.loads(json.dumps(bom, cls=CycloneDxJSONEncoder)) - bom_json = json.loads(self._specialise_output_for_schema_version(bom_json=bom_json)) - self._json_output = json.dumps({**self._create_bom_element(), **bom_json, **extras}) - - self.generated = True - - def _specialise_output_for_schema_version(self, bom_json: Dict[Any, Any]) -> str: - if 'metadata' in bom_json.keys(): - if not self.bom_supports_metadata(): - del bom_json['metadata'] - else: - if 'tools' in bom_json['metadata'].keys(): - if not self.bom_metadata_supports_tools(): - del bom_json['metadata']['tools'] - else: - if not self.bom_metadata_supports_tools_external_references(): - for _tool in bom_json['metadata']['tools']: - if 'externalReferences' in _tool.keys(): - del _tool['externalReferences'] - del _tool - if 'licenses' in bom_json['metadata'].keys() and not self.bom_metadata_supports_licenses(): - del bom_json['metadata']['licenses'] - if 'properties' in bom_json['metadata'].keys() and not self.bom_metadata_supports_properties(): - del bom_json['metadata']['properties'] - - if self.get_bom().metadata.component: - bom_json['metadata'] = self._recurse_specialise_component(bom_json['metadata'], 'component') - - bom_json = self._recurse_specialise_component(bom_json) - - if 'services' in bom_json.keys(): - for _service in bom_json['services']: - if 'properties' in _service.keys() and not self.services_supports_properties(): - del _service['properties'] - if 'releaseNotes' in _service.keys() and not self.services_supports_release_notes(): - del _service['releaseNotes'] - del _service - - if 'externalReferences' in bom_json.keys(): - if not self.external_references_supports_hashes(): - for _externalReference in bom_json['externalReferences']: - if 'hashes' in _externalReference.keys(): - del _externalReference['hashes'] - del _externalReference - - return json.dumps(bom_json) + _json_core = { + '$schema': schema_uri, + 'bomFormat': 'CycloneDX', + 'specVersion': self.schema_version.to_version() + } + _view = SCHEMA_VERSIONS.get(self.get_schema_version()) + if self.generated and force_regeneration: + self.get_bom().validate() + bom_json = json.loads(self.get_bom().as_json(view_=_view)) # type: ignore + bom_json.update(_json_core) + self._json_output = json.dumps(bom_json) + self.generated = True + return + elif self.generated: + return + else: + self.get_bom().validate() + bom_json = json.loads(self.get_bom().as_json(view_=_view)) # type: ignore + bom_json.update(_json_core) + self._json_output = json.dumps(bom_json) + self.generated = True + return def output_as_string(self) -> str: self.generate() return self._json_output - # Builder Methods - def _create_bom_element(self) -> Dict[str, Union[str, int]]: - return { - "$schema": str(self._get_schema_uri()), - "bomFormat": "CycloneDX", - "specVersion": str(self.get_schema_version()), - "serialNumber": self.get_bom().get_urn_uuid(), - "version": 1 - } - @abstractmethod def _get_schema_uri(self) -> Optional[str]: pass - def _recurse_specialise_component(self, bom_json: Dict[Any, Any], base_key: str = 'components') -> Dict[Any, Any]: - if base_key in bom_json.keys(): - if isinstance(bom_json[base_key], dict): - bom_json[base_key] = self._specialise_component_data(component_json=bom_json[base_key]) - else: - for i in range(len(bom_json[base_key])): - bom_json[base_key][i] = self._specialise_component_data(component_json=bom_json[base_key][i]) - - return bom_json - - def _specialise_component_data(self, component_json: Dict[Any, Any]) -> Dict[Any, Any]: - if not self.component_supports_mime_type_attribute() and 'mime-type' in component_json.keys(): - del component_json['mime-type'] - - if not self.component_supports_supplier() and 'supplier' in component_json.keys(): - del component_json['supplier'] - - if not self.component_supports_author() and 'author' in component_json.keys(): - del component_json['author'] - - if self.component_version_optional() and 'version' in component_json \ - and component_json.get('version', '') == "": - del component_json['version'] - - if not self.component_supports_pedigree() and 'pedigree' in component_json.keys(): - del component_json['pedigree'] - elif 'pedigree' in component_json.keys(): - if 'ancestors' in component_json['pedigree'].keys(): - # recurse into ancestors - component_json['pedigree'] = self._recurse_specialise_component( - bom_json=component_json['pedigree'], base_key='ancestors' - ) - if 'descendants' in component_json['pedigree'].keys(): - # recurse into descendants - component_json['pedigree'] = self._recurse_specialise_component( - bom_json=component_json['pedigree'], base_key='descendants' - ) - if 'variants' in component_json['pedigree'].keys(): - # recurse into variants - component_json['pedigree'] = self._recurse_specialise_component( - bom_json=component_json['pedigree'], base_key='variants' - ) - - if not self.external_references_supports_hashes() and 'externalReferences' \ - in component_json.keys(): - for j in range(len(component_json['externalReferences'])): - del component_json['externalReferences'][j]['hashes'] - - if not self.component_supports_properties() and 'properties' in component_json.keys(): - del component_json['properties'] - - # recurse - if 'components' in component_json.keys(): - component_json = self._recurse_specialise_component(bom_json=component_json) - - if not self.component_supports_evidence() and 'evidence' in component_json.keys(): - del component_json['evidence'] - - if not self.component_supports_release_notes() and 'releaseNotes' in component_json.keys(): - del component_json['releaseNotes'] - - return component_json - class JsonV1Dot0(Json, SchemaVersion1Dot0): diff --git a/cyclonedx/output/schema.py b/cyclonedx/output/schema.py deleted file mode 100644 index b3272d92..00000000 --- a/cyclonedx/output/schema.py +++ /dev/null @@ -1,342 +0,0 @@ -# encoding: utf-8 - -# This file is part of CycloneDX Python Lib -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# Copyright (c) OWASP Foundation. All Rights Reserved. - -from abc import ABC, abstractmethod - -from . import SchemaVersion - - -class BaseSchemaVersion(ABC): - - @property - @abstractmethod - def schema_version_enum(self) -> SchemaVersion: - pass - - def bom_supports_metadata(self) -> bool: - return True - - def bom_metadata_supports_tools(self) -> bool: - return True - - def bom_metadata_supports_tools_external_references(self) -> bool: - return True - - def bom_metadata_supports_licenses(self) -> bool: - return True - - def bom_metadata_supports_properties(self) -> bool: - return True - - def bom_supports_services(self) -> bool: - return True - - def bom_supports_external_references(self) -> bool: - return True - - def bom_supports_dependencies(self) -> bool: - return True - - def services_supports_properties(self) -> bool: - return True - - def services_supports_release_notes(self) -> bool: - return True - - def bom_supports_vulnerabilities(self) -> bool: - return True - - def bom_supports_vulnerabilities_via_extension(self) -> bool: - return False - - def bom_requires_modified(self) -> bool: - return False - - def component_supports_supplier(self) -> bool: - return True - - def component_supports_author(self) -> bool: - return True - - def component_supports_bom_ref_attribute(self) -> bool: - return True - - def component_supports_mime_type_attribute(self) -> bool: - return True - - def license_supports_expression(self) -> bool: - return True - - def component_version_optional(self) -> bool: - return False - - def component_supports_swid(self) -> bool: - return True - - def component_supports_pedigree(self) -> bool: - return True - - def pedigree_supports_patches(self) -> bool: - return True - - def component_supports_external_references(self) -> bool: - return True - - def component_supports_properties(self) -> bool: - return True - - def component_supports_evidence(self) -> bool: - return True - - def component_supports_release_notes(self) -> bool: - return True - - def external_references_supports_hashes(self) -> bool: - return True - - @abstractmethod - def get_schema_version(self) -> str: - raise NotImplementedError - - -class SchemaVersion1Dot4(BaseSchemaVersion): - - @property - def schema_version_enum(self) -> SchemaVersion: - return SchemaVersion.V1_4 - - def get_schema_version(self) -> str: - return '1.4' - - def component_version_optional(self) -> bool: - return True - - -class SchemaVersion1Dot3(BaseSchemaVersion): - - @property - def schema_version_enum(self) -> SchemaVersion: - return SchemaVersion.V1_3 - - def bom_metadata_supports_tools_external_references(self) -> bool: - return False - - def services_supports_release_notes(self) -> bool: - return False - - def bom_supports_vulnerabilities(self) -> bool: - return False - - def bom_supports_vulnerabilities_via_extension(self) -> bool: - return True - - def component_supports_mime_type_attribute(self) -> bool: - return False - - def component_supports_release_notes(self) -> bool: - return False - - def get_schema_version(self) -> str: - return '1.3' - - -class SchemaVersion1Dot2(BaseSchemaVersion): - - @property - def schema_version_enum(self) -> SchemaVersion: - return SchemaVersion.V1_2 - - def bom_metadata_supports_tools_external_references(self) -> bool: - return False - - def bom_metadata_supports_licenses(self) -> bool: - return False - - def bom_metadata_supports_properties(self) -> bool: - return False - - def services_supports_properties(self) -> bool: - return False - - def services_supports_release_notes(self) -> bool: - return False - - def bom_supports_vulnerabilities(self) -> bool: - return False - - def bom_supports_vulnerabilities_via_extension(self) -> bool: - return True - - def component_supports_mime_type_attribute(self) -> bool: - return False - - def component_supports_properties(self) -> bool: - return False - - def component_supports_evidence(self) -> bool: - return False - - def component_supports_release_notes(self) -> bool: - return False - - def external_references_supports_hashes(self) -> bool: - return False - - def get_schema_version(self) -> str: - return '1.2' - - -class SchemaVersion1Dot1(BaseSchemaVersion): - - @property - def schema_version_enum(self) -> SchemaVersion: - return SchemaVersion.V1_1 - - def bom_metadata_supports_tools(self) -> bool: - return False - - def bom_metadata_supports_tools_external_references(self) -> bool: - return False - - def bom_supports_services(self) -> bool: - return False - - def services_supports_properties(self) -> bool: - return False - - def pedigree_supports_patches(self) -> bool: - return False - - def services_supports_release_notes(self) -> bool: - return False - - def bom_supports_dependencies(self) -> bool: - return False - - def bom_supports_vulnerabilities(self) -> bool: - return False - - def bom_supports_vulnerabilities_via_extension(self) -> bool: - return True - - def bom_supports_metadata(self) -> bool: - return False - - def component_supports_mime_type_attribute(self) -> bool: - return False - - def component_supports_supplier(self) -> bool: - return False - - def component_supports_author(self) -> bool: - return False - - def component_supports_swid(self) -> bool: - return False - - def component_supports_properties(self) -> bool: - return False - - def component_supports_evidence(self) -> bool: - return False - - def component_supports_release_notes(self) -> bool: - return False - - def external_references_supports_hashes(self) -> bool: - return False - - def get_schema_version(self) -> str: - return '1.1' - - -class SchemaVersion1Dot0(BaseSchemaVersion): - - @property - def schema_version_enum(self) -> SchemaVersion: - return SchemaVersion.V1_0 - - def bom_metadata_supports_tools(self) -> bool: - return False - - def bom_metadata_supports_tools_external_references(self) -> bool: - return False - - def bom_supports_services(self) -> bool: - return False - - def bom_supports_external_references(self) -> bool: - return False - - def bom_supports_dependencies(self) -> bool: - return False - - def services_supports_properties(self) -> bool: - return False - - def services_supports_release_notes(self) -> bool: - return False - - def bom_supports_vulnerabilities(self) -> bool: - return False - - def bom_supports_metadata(self) -> bool: - return False - - def bom_requires_modified(self) -> bool: - return True - - def component_supports_author(self) -> bool: - return False - - def component_supports_bom_ref_attribute(self) -> bool: - return False - - def license_supports_expression(self) -> bool: - return False - - def component_supports_mime_type_attribute(self) -> bool: - return False - - def component_supports_supplier(self) -> bool: - return False - - def component_supports_swid(self) -> bool: - return False - - def component_supports_pedigree(self) -> bool: - return False - - def component_supports_external_references(self) -> bool: - return False - - def component_supports_properties(self) -> bool: - return False - - def component_supports_evidence(self) -> bool: - return False - - def component_supports_release_notes(self) -> bool: - return False - - def external_references_supports_hashes(self) -> bool: - return False - - def get_schema_version(self) -> str: - return '1.0' diff --git a/cyclonedx/output/serializer/json.py b/cyclonedx/output/serializer/json.py deleted file mode 100644 index f2fc5c64..00000000 --- a/cyclonedx/output/serializer/json.py +++ /dev/null @@ -1,109 +0,0 @@ -# encoding: utf-8 - -# This file is part of CycloneDX Python Lib -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# Copyright (c) OWASP Foundation. All Rights Reserved. - -from datetime import datetime -from decimal import Decimal -from enum import Enum -from json import JSONEncoder -from re import compile -from typing import Any, Dict -from uuid import UUID - -# See https://github.com/package-url/packageurl-python/issues/65 -from packageurl import PackageURL # type: ignore -from sortedcontainers import SortedSet - -from ...model import XsUri -from ...model.bom_ref import BomRef -from ...model.component import Component - -HYPHENATED_ATTRIBUTES = [ - 'bom_ref', 'mime_type', 'x_trust_boundary' -] -PYTHON_TO_JSON_NAME = compile(r'_([a-z])') - - -class CycloneDxJSONEncoder(JSONEncoder): - - def default(self, o: Any) -> Any: - # BomRef - if isinstance(o, BomRef): - return str(o) - - # datetime - if isinstance(o, datetime): - return o.isoformat() - - # Decimal - if isinstance(o, Decimal): - return float(f'{o:.1f}') - - # Enum - if isinstance(o, Enum): - return o.value - - # Set - if isinstance(o, set): - return list(o) - - # SortedSet - if isinstance(o, SortedSet): - return list(o) - - # UUID - if isinstance(o, UUID): - return str(o) - - # XsUri - if isinstance(o, XsUri): - return str(o) - - # Classes - if isinstance(o, object): - d: Dict[Any, Any] = {} - for k, v in o.__dict__.items(): - # Remove leading _ in key names - new_key = k[1:] if k[0] == '_' else k - if new_key.startswith('_') or '__' in new_key: - continue - - # Convert pythonic names to JSON names - # e.g. 'external_references' to 'externalReferences' - # - # Some special cases are hyphenated, not camel case - if new_key in HYPHENATED_ATTRIBUTES: - new_key = new_key.replace('_', '-') - elif '_' in new_key: - new_key = PYTHON_TO_JSON_NAME.sub(lambda x: x.group(1).upper(), new_key) - - # Inject '' for Component.version if it's None - if isinstance(o, Component) and new_key == 'version' and v is None: - d[new_key] = "" - elif v or v is False: - # Skip any None values (exception 'version') - if isinstance(v, PackageURL): - # Special handling of PackageURL instances which JSON would otherwise automatically encode to - # an Array - v = str(v.to_string()) - d[new_key] = v - - return d - - # Fallback to default - super().default(o=o) diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index ca01977f..e85219fa 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -17,31 +17,14 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -import warnings from typing import Optional from xml.etree import ElementTree -from sortedcontainers import SortedSet - -from ..model import ( - AttachedText, - ExternalReference, - HashType, - IdentifiableAction, - LicenseChoice, - OrganizationalContact, - OrganizationalEntity, - Property, - Tool, -) +from ..exception.output import BomGenerationErrorException from ..model.bom import Bom -from ..model.bom_ref import BomRef -from ..model.component import Component, Patch -from ..model.release_note import ReleaseNotes -from ..model.service import Service -from ..model.vulnerability import BomTargetVersionRange, Vulnerability, VulnerabilityRating, VulnerabilitySource -from . import BaseOutput, SchemaVersion -from .schema import ( +from ..schema import SchemaVersion +from ..schema.schema import ( + SCHEMA_VERSIONS, BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, @@ -49,806 +32,50 @@ SchemaVersion1Dot3, SchemaVersion1Dot4, ) +from . import BaseOutput class Xml(BaseOutput, BaseSchemaVersion): - VULNERABILITY_EXTENSION_NAMESPACE: str = 'http://cyclonedx.org/schema/ext/vulnerability/1.0' XML_VERSION_DECLARATION: str = '' def __init__(self, bom: Bom) -> None: super().__init__(bom=bom) - self._root_bom_element: ElementTree.Element = self._create_bom_element() + self._root_bom_element: Optional[ElementTree.Element] = None @property def schema_version(self) -> SchemaVersion: return self.schema_version_enum def generate(self, force_regeneration: bool = False) -> None: + # New way + _view = SCHEMA_VERSIONS.get(self.get_schema_version()) if self.generated and force_regeneration: - self._root_bom_element = self._create_bom_element() + self.get_bom().validate() + self._root_bom_element = self.get_bom().as_xml( # type: ignore + view_=_view, as_string=False, xmlns=self.get_target_namespace() + ) + self.generated = True + return elif self.generated: return - - bom = self.get_bom() - bom.validate() - - if self.bom_supports_metadata(): - self._add_metadata_element() - - has_vulnerabilities: bool = False - - components_element = ElementTree.SubElement(self._root_bom_element, 'components') - if bom.components: - for component in bom.components: - component_element = self._add_component_element(component=component) - components_element.append(component_element) - if self.bom_supports_vulnerabilities_via_extension() and component.has_vulnerabilities(): - # Vulnerabilities are only possible when bom-ref is supported by the main CycloneDX schema version - vulnerabilities = ElementTree.SubElement(component_element, 'v:vulnerabilities') - for vulnerability in component.get_vulnerabilities(): - if component.bom_ref: - vulnerabilities.append( - Xml._get_vulnerability_as_xml_element_pre_1_3(bom_ref=component.bom_ref, - vulnerability=vulnerability) - ) - else: - warnings.warn( - f'Unable to include Vulnerability {str(vulnerability)} in generated BOM as the ' - f'Component it relates to ({str(component)}) but it has no bom-ref.' - ) - elif component.has_vulnerabilities(): - has_vulnerabilities = True - - if self.bom_supports_services() and bom.services: - services_element = ElementTree.SubElement(self._root_bom_element, 'services') - for service in bom.services: - services_element.append(self._add_service_element(service=service)) - - if self.bom_supports_external_references() and bom.external_references: - self._add_external_references_to_element( - ext_refs=bom.external_references, - element=self._root_bom_element + else: + self.get_bom().validate() + self._root_bom_element = self.get_bom().as_xml( # type: ignore + view_=_view, as_string=False, xmlns=self.get_target_namespace() ) - - if self.bom_supports_dependencies() and (bom.metadata.component or bom.components): - dep_components = self._chained_components(bom) - if bom.metadata.component: - dep_components = [bom.metadata.component, *dep_components] - dependencies_element = ElementTree.SubElement(self._root_bom_element, 'dependencies') - for component in dep_components: - dependency_element = ElementTree.SubElement(dependencies_element, 'dependency', { - 'ref': str(component.bom_ref) - }) - for dependency in component.dependencies: - ElementTree.SubElement(dependency_element, 'dependency', { - 'ref': str(dependency) - }) - del dep_components - - if self.bom_supports_vulnerabilities() and has_vulnerabilities: - vulnerabilities_element = ElementTree.SubElement(self._root_bom_element, 'vulnerabilities') - for component in bom.components: - for vulnerability in component.get_vulnerabilities(): - vulnerabilities_element.append( - self._get_vulnerability_as_xml_element_post_1_4(vulnerability=vulnerability) - ) - - self.generated = True + self.generated = True + return def output_as_string(self) -> str: self.generate() - return Xml.XML_VERSION_DECLARATION + ElementTree.tostring(self._root_bom_element, 'unicode') + if self.generated and self._root_bom_element is not None: + return str(Xml.XML_VERSION_DECLARATION + ElementTree.tostring(self._root_bom_element, encoding='unicode')) + + raise BomGenerationErrorException('There was no Root XML Element after BOM generation.') def get_target_namespace(self) -> str: return f'http://cyclonedx.org/schema/bom/{self.get_schema_version()}' - # Builder Methods - def _create_bom_element(self) -> ElementTree.Element: - bom = self.get_bom() - root_attributes = { - 'xmlns': self.get_target_namespace(), - 'version': '1', - 'serialNumber': bom.get_urn_uuid() - } - - if self.bom_supports_vulnerabilities_via_extension() and bom.has_vulnerabilities(): - root_attributes['xmlns:v'] = Xml.VULNERABILITY_EXTENSION_NAMESPACE - ElementTree.register_namespace('v', Xml.VULNERABILITY_EXTENSION_NAMESPACE) - - return ElementTree.Element('bom', root_attributes) - - @staticmethod - def _add_identifiable_action_element(identifiable_action: IdentifiableAction, tag_name: str) -> ElementTree.Element: - ia_element = ElementTree.Element(tag_name) - if identifiable_action.timestamp: - ElementTree.SubElement(ia_element, 'timestamp').text = identifiable_action.timestamp.isoformat() - if identifiable_action.name: - ElementTree.SubElement(ia_element, 'name').text = identifiable_action.name - if identifiable_action.email: - ElementTree.SubElement(ia_element, 'email').text = identifiable_action.email - return ia_element - - def _add_metadata_element(self) -> None: - bom_metadata = self.get_bom().metadata - metadata_e = ElementTree.SubElement(self._root_bom_element, 'metadata') - - ElementTree.SubElement(metadata_e, 'timestamp').text = bom_metadata.timestamp.isoformat() - - if self.bom_metadata_supports_tools() and len(bom_metadata.tools) > 0: - tools_e = ElementTree.SubElement(metadata_e, 'tools') - for tool in bom_metadata.tools: - self._add_tool(parent_element=tools_e, tool=tool) - - if bom_metadata.authors: - authors_e = ElementTree.SubElement(metadata_e, 'authors') - for author in bom_metadata.authors: - Xml._add_organizational_contact( - parent_element=authors_e, contact=author, tag_name='author' - ) - - if bom_metadata.component: - metadata_e.append(self._add_component_element(component=bom_metadata.component)) - - if bom_metadata.manufacture: - Xml._add_organizational_entity( - parent_element=metadata_e, organization=bom_metadata.manufacture, tag_name='manufacture' - ) - - if bom_metadata.supplier: - Xml._add_organizational_entity( - parent_element=metadata_e, organization=bom_metadata.supplier, tag_name='supplier' - ) - - if self.bom_metadata_supports_licenses() and bom_metadata.licenses: - licenses_e = ElementTree.SubElement(metadata_e, 'licenses') - self._add_licenses_to_element(licenses=bom_metadata.licenses, parent_element=licenses_e) - - if self.bom_metadata_supports_properties() and bom_metadata.properties: - Xml._add_properties_element(properties=bom_metadata.properties, parent_element=metadata_e) - - def _add_component_element(self, component: Component) -> ElementTree.Element: - element_attributes = {'type': component.type.value} - if self.component_supports_bom_ref_attribute() and component.bom_ref: - element_attributes['bom-ref'] = str(component.bom_ref) - if self.component_supports_mime_type_attribute() and component.mime_type: - element_attributes['mime-type'] = component.mime_type - - component_element = ElementTree.Element('component', element_attributes) - - # supplier - if self.component_supports_supplier() and component.supplier: - self._add_organizational_entity( - parent_element=component_element, organization=component.supplier, tag_name='supplier' - ) - - # author - if self.component_supports_author() and component.author is not None: - ElementTree.SubElement(component_element, 'author').text = component.author - - # publisher - if component.publisher: - ElementTree.SubElement(component_element, 'publisher').text = component.publisher - - # group - if component.group: - ElementTree.SubElement(component_element, 'group').text = component.group - - # name - ElementTree.SubElement(component_element, 'name').text = component.name - - # version - if self.component_version_optional(): - if component.version: - # 1.4 schema version - ElementTree.SubElement(component_element, 'version').text = component.version - else: - if not component.version: - ElementTree.SubElement(component_element, 'version') - else: - ElementTree.SubElement(component_element, 'version').text = component.version - - # description - if component.description: - ElementTree.SubElement(component_element, 'description').text = component.description - - # scope - if component.scope: - ElementTree.SubElement(component_element, 'scope').text = component.scope.value - - # hashes - if component.hashes: - Xml._add_hashes_to_element(hashes=component.hashes, element=component_element) - - # licenses - if component.licenses: - licenses_e = ElementTree.SubElement(component_element, 'licenses') - license_output: bool = self._add_licenses_to_element(licenses=component.licenses, parent_element=licenses_e) - if not license_output: - component_element.remove(licenses_e) - - # copyright - if component.copyright: - ElementTree.SubElement(component_element, 'copyright').text = component.copyright - - # cpe - if component.cpe: - ElementTree.SubElement(component_element, 'cpe').text = component.cpe - - # purl - if component.purl: - ElementTree.SubElement(component_element, 'purl').text = component.purl.to_string() - - # swid - if self.component_supports_swid() and component.swid: - swid_attrs = { - "tagId": component.swid.tag_id, - "name": component.swid.name - } - if component.swid.version: - swid_attrs['version'] = component.swid.version - if component.swid.tag_version: - swid_attrs['tagVersion'] = str(component.swid.tag_version) - if component.swid.patch is not None: - swid_attrs['patch'] = str(component.swid.patch).lower() - swid_element = ElementTree.SubElement(component_element, 'swid', swid_attrs) - if component.swid.text: - swid_element.append(Xml._add_attached_text(attached_text=component.swid.text)) - if component.swid.url: - ElementTree.SubElement(swid_element, 'url').text = str(component.swid.url) - - # modified - if self.bom_requires_modified(): - ElementTree.SubElement(component_element, 'modified').text = 'false' - - # pedigree - if self.component_supports_pedigree() and component.pedigree: - pedigree_element = ElementTree.SubElement(component_element, 'pedigree') - if component.pedigree.ancestors: - ancestors_element = ElementTree.SubElement(pedigree_element, 'ancestors') - for ancestor in component.pedigree.ancestors: - ancestors_element.append(self._add_component_element(component=ancestor)) - if component.pedigree.descendants: - descendants_element = ElementTree.SubElement(pedigree_element, 'descendants') - for descendant in component.pedigree.descendants: - descendants_element.append(self._add_component_element(component=descendant)) - if component.pedigree.variants: - variants_element = ElementTree.SubElement(pedigree_element, 'variants') - for variant in component.pedigree.variants: - variants_element.append(self._add_component_element(component=variant)) - if component.pedigree.commits: - commits_element = ElementTree.SubElement(pedigree_element, 'commits') - for commit in component.pedigree.commits: - commit_element = ElementTree.SubElement(commits_element, 'commit') - if commit.uid: - ElementTree.SubElement(commit_element, 'uid').text = commit.uid - if commit.url: - ElementTree.SubElement(commit_element, 'url').text = str(commit.url) - if commit.author: - commit_element.append(Xml._add_identifiable_action_element( - identifiable_action=commit.author, tag_name='author' - )) - if commit.committer: - commit_element.append(Xml._add_identifiable_action_element( - identifiable_action=commit.committer, tag_name='committer' - )) - if commit.message: - ElementTree.SubElement(commit_element, 'message').text = commit.message - if self.pedigree_supports_patches() and component.pedigree.patches: - patches_element = ElementTree.SubElement(pedigree_element, 'patches') - for patch in component.pedigree.patches: - patches_element.append(Xml.add_patch_element(patch=patch)) - if component.pedigree.notes: - ElementTree.SubElement(pedigree_element, 'notes').text = component.pedigree.notes - - # externalReferences - if self.component_supports_external_references() and len(component.external_references) > 0: - self._add_external_references_to_element(ext_refs=component.external_references, element=component_element) - - # properties - if self.component_supports_properties() and component.properties: - Xml._add_properties_element(properties=component.properties, parent_element=component_element) - - # components - if component.components: - components_element = ElementTree.SubElement(component_element, 'components') - for nested_component in component.components: - components_element.append(self._add_component_element(component=nested_component)) - - # evidence - if self.component_supports_evidence() and component.evidence: - evidence_element = ElementTree.SubElement(component_element, 'evidence') - if component.evidence.licenses: - evidence_licenses_element = ElementTree.SubElement(evidence_element, 'licenses') - self._add_licenses_to_element( - licenses=component.evidence.licenses, parent_element=evidence_licenses_element - ) - if component.evidence.copyright: - evidence_copyrights_element = ElementTree.SubElement(evidence_element, 'copyright') - for evidence_copyright in component.evidence.copyright: - ElementTree.SubElement(evidence_copyrights_element, 'text').text = evidence_copyright.text - - # releaseNotes - if self.component_supports_release_notes() and component.release_notes: - Xml._add_release_notes_element(release_notes=component.release_notes, parent_element=component_element) - - return component_element - - def _add_licenses_to_element(self, licenses: "SortedSet[LicenseChoice]", - parent_element: ElementTree.Element) -> bool: - license_output = False - for license_ in licenses: - if license_.license: - license_e = ElementTree.SubElement(parent_element, 'license') - if license_.license.id: - ElementTree.SubElement(license_e, 'id').text = license_.license.id - elif license_.license.name: - ElementTree.SubElement(license_e, 'name').text = license_.license.name - if license_.license.text: - license_text_e_attrs = {} - if license_.license.text.content_type: - license_text_e_attrs['content-type'] = license_.license.text.content_type - if license_.license.text.encoding: - license_text_e_attrs['encoding'] = license_.license.text.encoding.value - ElementTree.SubElement(license_e, 'text', - license_text_e_attrs).text = license_.license.text.content - if license_.license.url: - ElementTree.SubElement(license_e, 'url').text = str(license_.license.url) - - license_output = True - else: - if self.license_supports_expression(): - ElementTree.SubElement(parent_element, 'expression').text = license_.expression - license_output = True - return license_output - - @staticmethod - def _add_release_notes_element(release_notes: ReleaseNotes, parent_element: ElementTree.Element) -> None: - release_notes_e = ElementTree.SubElement(parent_element, 'releaseNotes') - - ElementTree.SubElement(release_notes_e, 'type').text = release_notes.type - if release_notes.title: - ElementTree.SubElement(release_notes_e, 'title').text = release_notes.title - if release_notes.featured_image: - ElementTree.SubElement(release_notes_e, - 'featuredImage').text = str(release_notes.featured_image) - if release_notes.social_image: - ElementTree.SubElement(release_notes_e, - 'socialImage').text = str(release_notes.social_image) - if release_notes.description: - ElementTree.SubElement(release_notes_e, - 'description').text = release_notes.description - if release_notes.timestamp: - ElementTree.SubElement(release_notes_e, 'timestamp').text = release_notes.timestamp.isoformat() - if release_notes.aliases: - release_notes_aliases_e = ElementTree.SubElement(release_notes_e, 'aliases') - for alias in release_notes.aliases: - ElementTree.SubElement(release_notes_aliases_e, 'alias').text = alias - if release_notes.tags: - release_notes_tags_e = ElementTree.SubElement(release_notes_e, 'tags') - for tag in release_notes.tags: - ElementTree.SubElement(release_notes_tags_e, 'tag').text = tag - if release_notes.resolves: - release_notes_resolves_e = ElementTree.SubElement(release_notes_e, 'resolves') - for issue in release_notes.resolves: - issue_e = ElementTree.SubElement( - release_notes_resolves_e, 'issue', {'type': issue.type.value} - ) - if issue.id: - ElementTree.SubElement(issue_e, 'id').text = issue.id - if issue.name: - ElementTree.SubElement(issue_e, 'name').text = issue.name - if issue.description: - ElementTree.SubElement(issue_e, 'description').text = issue.description - if issue.source: - issue_source_e = ElementTree.SubElement(issue_e, 'source') - if issue.source.name: - ElementTree.SubElement(issue_source_e, 'name').text = issue.source.name - if issue.source.url: - ElementTree.SubElement(issue_source_e, 'url').text = str(issue.source.url) - if issue.references: - issue_references_e = ElementTree.SubElement(issue_e, 'references') - for reference in issue.references: - ElementTree.SubElement(issue_references_e, 'url').text = str(reference) - if release_notes.notes: - release_notes_notes_e = ElementTree.SubElement(release_notes_e, 'notes') - for note in release_notes.notes: - note_e = ElementTree.SubElement(release_notes_notes_e, 'note') - if note.locale: - ElementTree.SubElement(note_e, 'locale').text = note.locale - text_attrs = {} - if note.text.content_type: - text_attrs['content-type'] = note.text.content_type - if note.text.encoding: - text_attrs['encoding'] = note.text.encoding.value - ElementTree.SubElement(note_e, 'text', text_attrs).text = note.text.content - if release_notes.properties: - Xml._add_properties_element(properties=release_notes.properties, parent_element=release_notes_e) - - @staticmethod - def add_patch_element(patch: Patch) -> ElementTree.Element: - patch_element = ElementTree.Element('patch', {"type": patch.type.value}) - if patch.diff: - diff_element = ElementTree.SubElement(patch_element, 'diff') - if patch.diff.text: - diff_element.append(Xml._add_attached_text(attached_text=patch.diff.text)) - if patch.diff.url: - ElementTree.SubElement(diff_element, 'url').text = str(patch.diff.url) - - return patch_element - - @staticmethod - def _add_properties_element(properties: "SortedSet[Property]", parent_element: ElementTree.Element) -> None: - properties_e = ElementTree.SubElement(parent_element, 'properties') - for property_ in properties: - ElementTree.SubElement( - properties_e, 'property', {'name': property_.name} - ).text = property_.value - - def _add_service_element(self, service: Service) -> ElementTree.Element: - element_attributes = {} - if service.bom_ref: - element_attributes['bom-ref'] = str(service.bom_ref) - - service_element = ElementTree.Element('service', element_attributes) - - # provider - if service.provider: - self._add_organizational_entity( - parent_element=service_element, organization=service.provider, tag_name='provider' - ) - - # group - if service.group: - ElementTree.SubElement(service_element, 'group').text = service.group - - # name - ElementTree.SubElement(service_element, 'name').text = service.name - - # version - if service.version: - ElementTree.SubElement(service_element, 'version').text = service.version - - # description - if service.description: - ElementTree.SubElement(service_element, 'description').text = service.description - - # endpoints - if service.endpoints: - endpoints_e = ElementTree.SubElement(service_element, 'endpoints') - for endpoint in service.endpoints: - ElementTree.SubElement(endpoints_e, 'endpoint').text = str(endpoint) - - # authenticated - if isinstance(service.authenticated, bool): - ElementTree.SubElement(service_element, 'authenticated').text = str(service.authenticated).lower() - - # x-trust-boundary - if isinstance(service.x_trust_boundary, bool): - ElementTree.SubElement(service_element, 'x-trust-boundary').text = str(service.x_trust_boundary).lower() - - # data - if service.data: - data_e = ElementTree.SubElement(service_element, 'data') - for data in service.data: - ElementTree.SubElement(data_e, 'classification', {'flow': data.flow.value}).text = data.classification - - # licenses - if service.licenses: - licenses_e = ElementTree.SubElement(service_element, 'licenses') - license_output: bool = self._add_licenses_to_element(licenses=service.licenses, parent_element=licenses_e) - if not license_output: - service_element.remove(licenses_e) - - # externalReferences - if service.external_references: - self._add_external_references_to_element(ext_refs=service.external_references, element=service_element) - - # properties - if service.properties and self.services_supports_properties(): - Xml._add_properties_element(properties=service.properties, parent_element=service_element) - - # services - if service.services: - services_element = ElementTree.SubElement(service_element, 'services') - for sub_service in service.services: - services_element.append(self._add_service_element(service=sub_service)) - - # releaseNotes - if service.release_notes and self.services_supports_release_notes(): - Xml._add_release_notes_element(release_notes=service.release_notes, parent_element=service_element) - - return service_element - - def _get_vulnerability_as_xml_element_post_1_4(self, vulnerability: Vulnerability) -> ElementTree.Element: - vulnerability_element = ElementTree.Element( - 'vulnerability', - {'bom-ref': str(vulnerability.bom_ref)} if vulnerability.bom_ref else {} - ) - - # id - if vulnerability.id: - ElementTree.SubElement(vulnerability_element, 'id').text = vulnerability.id - - # source - Xml._add_vulnerability_source(parent_element=vulnerability_element, source=vulnerability.source) - - # references - if vulnerability.references: - v_references_element = ElementTree.SubElement(vulnerability_element, 'references') - for reference in vulnerability.references: - v_reference_element = ElementTree.SubElement(v_references_element, 'reference') - if reference.id: - ElementTree.SubElement(v_reference_element, 'id').text = reference.id - Xml._add_vulnerability_source(parent_element=v_reference_element, source=reference.source) - - # ratings - if vulnerability.ratings: - v_ratings_element = ElementTree.SubElement(vulnerability_element, 'ratings') - for rating in vulnerability.ratings: - v_rating_element = ElementTree.SubElement(v_ratings_element, 'rating') - Xml._add_vulnerability_source(parent_element=v_rating_element, source=rating.source) - if rating.score: - ElementTree.SubElement(v_rating_element, 'score').text = f'{rating.score:.1f}' - if rating.severity: - ElementTree.SubElement(v_rating_element, 'severity').text = rating.severity.value - if rating.method: - ElementTree.SubElement(v_rating_element, 'method').text = rating.method.value - if rating.vector: - ElementTree.SubElement(v_rating_element, 'vector').text = rating.vector - if rating.justification: - ElementTree.SubElement(v_rating_element, 'justification').text = rating.justification - - # cwes - if vulnerability.cwes: - v_cwes_element = ElementTree.SubElement(vulnerability_element, 'cwes') - for cwe in vulnerability.cwes: - ElementTree.SubElement(v_cwes_element, 'cwe').text = str(cwe) - - # description - if vulnerability.description: - ElementTree.SubElement(vulnerability_element, 'description').text = vulnerability.description - - # detail - if vulnerability.detail: - ElementTree.SubElement(vulnerability_element, 'detail').text = vulnerability.detail - - # recommendation - if vulnerability.recommendation: - ElementTree.SubElement(vulnerability_element, 'recommendation').text = vulnerability.recommendation - - # advisories - if vulnerability.advisories: - v_advisories_element = ElementTree.SubElement(vulnerability_element, 'advisories') - for advisory in vulnerability.advisories: - v_advisory_element = ElementTree.SubElement(v_advisories_element, 'advisory') - if advisory.title: - ElementTree.SubElement(v_advisory_element, 'title').text = advisory.title - ElementTree.SubElement(v_advisory_element, 'url').text = str(advisory.url) - - # created - if vulnerability.created: - ElementTree.SubElement(vulnerability_element, 'created').text = vulnerability.created.isoformat() - - # published - if vulnerability.published: - ElementTree.SubElement(vulnerability_element, 'published').text = vulnerability.published.isoformat() - - # updated - if vulnerability.updated: - ElementTree.SubElement(vulnerability_element, 'updated').text = vulnerability.updated.isoformat() - - # credits - if vulnerability.credits: - v_credits_element = ElementTree.SubElement(vulnerability_element, 'credits') - if vulnerability.credits.organizations: - v_credits_organizations_element = ElementTree.SubElement(v_credits_element, 'organizations') - for organization in vulnerability.credits.organizations: - Xml._add_organizational_entity( - parent_element=v_credits_organizations_element, organization=organization, - tag_name='organization' - ) - if vulnerability.credits.individuals: - v_credits_individuals_element = ElementTree.SubElement(v_credits_element, 'individuals') - for individual in vulnerability.credits.individuals: - Xml._add_organizational_contact( - parent_element=v_credits_individuals_element, contact=individual, - tag_name='individual' - ) - - # tools - if vulnerability.tools: - v_tools_element = ElementTree.SubElement(vulnerability_element, 'tools') - for tool in vulnerability.tools: - self._add_tool(parent_element=v_tools_element, tool=tool) - - # analysis - if vulnerability.analysis: - v_analysis_element = ElementTree.SubElement(vulnerability_element, 'analysis') - if vulnerability.analysis.state: - ElementTree.SubElement(v_analysis_element, 'state').text = vulnerability.analysis.state.value - if vulnerability.analysis.justification: - ElementTree.SubElement(v_analysis_element, - 'justification').text = vulnerability.analysis.justification.value - if vulnerability.analysis.response: - v_analysis_responses_element = ElementTree.SubElement(v_analysis_element, 'responses') - for response in vulnerability.analysis.response: - ElementTree.SubElement(v_analysis_responses_element, 'response').text = response.value - if vulnerability.analysis.detail: - ElementTree.SubElement(v_analysis_element, 'detail').text = vulnerability.analysis.detail - - # affects - if vulnerability.affects: - v_affects_element = ElementTree.SubElement(vulnerability_element, 'affects') - for target in vulnerability.affects: - v_target_element = ElementTree.SubElement(v_affects_element, 'target') - ElementTree.SubElement(v_target_element, 'ref').text = target.ref - - if target.versions: - v_target_versions_element = ElementTree.SubElement(v_target_element, 'versions') - for version in target.versions: - Xml._add_bom_target_version_range(parent_element=v_target_versions_element, version=version) - - # properties - if vulnerability.properties: - Xml._add_properties_element(properties=vulnerability.properties, parent_element=vulnerability_element) - - return vulnerability_element - - @staticmethod - def _get_vulnerability_as_xml_element_pre_1_3(bom_ref: BomRef, - vulnerability: Vulnerability) -> ElementTree.Element: - vulnerability_element = ElementTree.Element('v:vulnerability', { - 'ref': str(bom_ref) - }) - - # id - ElementTree.SubElement(vulnerability_element, 'v:id').text = vulnerability.id - - # source - if vulnerability.source and vulnerability.source.name: - source_element = ElementTree.SubElement( - vulnerability_element, 'v:source', attrib={'name': str(vulnerability.source.name)} - ) - if vulnerability.source.url: - ElementTree.SubElement(source_element, 'v:url').text = str(vulnerability.source.url) - - # ratings - if vulnerability.ratings: - ratings_element = ElementTree.SubElement(vulnerability_element, 'v:ratings') - rating: VulnerabilityRating - for rating in vulnerability.ratings: - rating_element = ElementTree.SubElement(ratings_element, 'v:rating') - - if rating.score: - score_element = ElementTree.SubElement(rating_element, 'v:score') - ElementTree.SubElement(score_element, 'v:base').text = f'{rating.score:.1f}' - - # rating.severity - if rating.severity: - ElementTree.SubElement(rating_element, 'v:severity').text = str(rating.severity.value).title() - - # rating.severity - if rating.method: - ElementTree.SubElement(rating_element, 'v:method').text = rating.method.get_value_pre_1_4() - - # rating.vector - if rating.vector: - ElementTree.SubElement(rating_element, 'v:vector').text = rating.vector - - # cwes - if vulnerability.cwes: - cwes_element = ElementTree.SubElement(vulnerability_element, 'v:cwes') - for cwe in vulnerability.cwes: - ElementTree.SubElement(cwes_element, 'v:cwe').text = str(cwe) - - # description - if vulnerability.description: - ElementTree.SubElement(vulnerability_element, 'v:description').text = vulnerability.description - - # recommendations - if vulnerability.recommendation: - recommendations_element = ElementTree.SubElement(vulnerability_element, 'v:recommendations') - # for recommendation in vulnerability.get_recommendations(): - ElementTree.SubElement(recommendations_element, 'v:recommendation').text = vulnerability.recommendation - - # advisories - if vulnerability.advisories: - advisories_element = ElementTree.SubElement(vulnerability_element, 'v:advisories') - for advisory in vulnerability.advisories: - ElementTree.SubElement(advisories_element, 'v:advisory').text = str(advisory.url) - - return vulnerability_element - - def _add_external_references_to_element(self, ext_refs: "SortedSet[ExternalReference]", - element: ElementTree.Element) -> None: - ext_refs_element = ElementTree.SubElement(element, 'externalReferences') - for external_reference in ext_refs: - ext_ref_element = ElementTree.SubElement( - ext_refs_element, 'reference', {'type': external_reference.type.value} - ) - ElementTree.SubElement(ext_ref_element, 'url').text = str(external_reference.url) - if external_reference.comment: - ElementTree.SubElement(ext_ref_element, 'comment').text = external_reference.comment - if self.external_references_supports_hashes() and external_reference.hashes: - Xml._add_hashes_to_element(hashes=external_reference.hashes, element=ext_ref_element) - - @staticmethod - def _add_attached_text(attached_text: AttachedText, tag_name: str = 'text') -> ElementTree.Element: - element_attributes = {} - if attached_text.content_type: - element_attributes['content-type'] = attached_text.content_type - if attached_text.encoding: - element_attributes['encoding'] = attached_text.encoding.value - at_element = ElementTree.Element(tag_name, element_attributes) - at_element.text = attached_text.content - return at_element - - @staticmethod - def _add_hashes_to_element(hashes: "SortedSet[HashType]", element: ElementTree.Element) -> None: - hashes_e = ElementTree.SubElement(element, 'hashes') - for h in hashes: - ElementTree.SubElement( - hashes_e, 'hash', {'alg': h.alg.value} - ).text = h.content - - @staticmethod - def _add_bom_target_version_range(parent_element: ElementTree.Element, version: BomTargetVersionRange) -> None: - version_element = ElementTree.SubElement(parent_element, 'version') - if version.version: - ElementTree.SubElement(version_element, 'version').text = version.version - else: - ElementTree.SubElement(version_element, 'range').text = version.range - - if version.status: - ElementTree.SubElement(version_element, 'status').text = version.status.value - - def _add_tool(self, parent_element: ElementTree.Element, tool: Tool, tag_name: str = 'tool') -> None: - tool_element = ElementTree.SubElement(parent_element, tag_name) - if tool.vendor: - ElementTree.SubElement(tool_element, 'vendor').text = tool.vendor - if tool.name: - ElementTree.SubElement(tool_element, 'name').text = tool.name - if tool.version: - ElementTree.SubElement(tool_element, 'version').text = tool.version - if tool.hashes: - Xml._add_hashes_to_element(hashes=tool.hashes, element=tool_element) - if self.bom_metadata_supports_tools_external_references() and tool.external_references: - self._add_external_references_to_element(ext_refs=tool.external_references, element=tool_element) - - @staticmethod - def _add_organizational_contact(parent_element: ElementTree.Element, contact: OrganizationalContact, - tag_name: str) -> None: - oc_element = ElementTree.SubElement(parent_element, tag_name) - if contact.name: - ElementTree.SubElement(oc_element, 'name').text = contact.name - if contact.email: - ElementTree.SubElement(oc_element, 'email').text = contact.email - if contact.phone: - ElementTree.SubElement(oc_element, 'phone').text = contact.phone - - @staticmethod - def _add_organizational_entity(parent_element: ElementTree.Element, organization: OrganizationalEntity, - tag_name: str) -> None: - oe_element = ElementTree.SubElement(parent_element, tag_name) - if organization.name: - ElementTree.SubElement(oe_element, 'name').text = organization.name - if organization.url: - for url in organization.url: - ElementTree.SubElement(oe_element, 'url').text = str(url) - if organization.contact: - for contact in organization.contact: - Xml._add_organizational_contact(parent_element=oe_element, contact=contact, tag_name='contact') - - @staticmethod - def _add_vulnerability_source(parent_element: ElementTree.Element, - source: Optional[VulnerabilitySource] = None) -> None: - if source: - v_source_element = ElementTree.SubElement(parent_element, 'source') - if source.name: - ElementTree.SubElement(v_source_element, 'name').text = source.name - if source.url: - ElementTree.SubElement(v_source_element, 'url').text = str(source.url) - class XmlV1Dot0(Xml, SchemaVersion1Dot0): diff --git a/cyclonedx/parser/__init__.py b/cyclonedx/parser/__init__.py index 4e925af3..1589f3cd 100644 --- a/cyclonedx/parser/__init__.py +++ b/cyclonedx/parser/__init__.py @@ -20,7 +20,6 @@ Use a Parser instead of programmatically creating a Bom as a developer. """ -from abc import ABC from typing import List from ..model.component import Component @@ -41,10 +40,10 @@ def get_warning_message(self) -> str: return self._warning def __repr__(self) -> str: - return ''.format(self._item) + return f'' -class BaseParser(ABC): +class BaseParser: _components: List[Component] = [] _warnings: List[ParserWarning] = [] diff --git a/cyclonedx/output/serializer/__init__.py b/cyclonedx/schema/__init__.py similarity index 56% rename from cyclonedx/output/serializer/__init__.py rename to cyclonedx/schema/__init__.py index 308d97e7..9741a8e8 100644 --- a/cyclonedx/output/serializer/__init__.py +++ b/cyclonedx/schema/__init__.py @@ -1,7 +1,5 @@ # encoding: utf-8 -# This file is part of CycloneDX Python Lib -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -15,4 +13,27 @@ # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 -# Copyright (c) OWASP Foundation. All Rights Reserved. + +from enum import Enum + + +class OutputFormat(str, Enum): + JSON: str = 'Json' + XML: str = 'Xml' + + +class SchemaVersion(str, Enum): + V1_0: str = 'V1Dot0' + V1_1: str = 'V1Dot1' + V1_2: str = 'V1Dot2' + V1_3: str = 'V1Dot3' + V1_4: str = 'V1Dot4' + + def to_version(self) -> str: + """ + Return as a version string - e.g. `1.4` + + Returns: + `str` version + """ + return f'{self.value[1]}.{self.value[5]}' diff --git a/cyclonedx/schema/schema.py b/cyclonedx/schema/schema.py new file mode 100644 index 00000000..248376a5 --- /dev/null +++ b/cyclonedx/schema/schema.py @@ -0,0 +1,79 @@ +# encoding: utf-8 + +# This file is part of CycloneDX Python Lib +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +from abc import ABC, abstractmethod + +from serializable import ViewType + +from ..schema import SchemaVersion + + +class BaseSchemaVersion(ABC, ViewType): + + @property + @abstractmethod + def schema_version_enum(self) -> SchemaVersion: + pass + + def get_schema_version(self) -> str: + return self.schema_version_enum.to_version() + + +class SchemaVersion1Dot4(BaseSchemaVersion): + + @property + def schema_version_enum(self) -> SchemaVersion: + return SchemaVersion.V1_4 + + +class SchemaVersion1Dot3(BaseSchemaVersion): + + @property + def schema_version_enum(self) -> SchemaVersion: + return SchemaVersion.V1_3 + + +class SchemaVersion1Dot2(BaseSchemaVersion): + + @property + def schema_version_enum(self) -> SchemaVersion: + return SchemaVersion.V1_2 + + +class SchemaVersion1Dot1(BaseSchemaVersion): + + @property + def schema_version_enum(self) -> SchemaVersion: + return SchemaVersion.V1_1 + + +class SchemaVersion1Dot0(BaseSchemaVersion): + + @property + def schema_version_enum(self) -> SchemaVersion: + return SchemaVersion.V1_0 + + +SCHEMA_VERSIONS = { + '1.0': SchemaVersion1Dot0, + '1.1': SchemaVersion1Dot1, + '1.2': SchemaVersion1Dot2, + '1.3': SchemaVersion1Dot3, + '1.4': SchemaVersion1Dot4 +} diff --git a/cyclonedx/serialization/__init__.py b/cyclonedx/serialization/__init__.py new file mode 100644 index 00000000..861a6cbc --- /dev/null +++ b/cyclonedx/serialization/__init__.py @@ -0,0 +1,78 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +""" +Set of helper classes for use with ``serializable`` when conducting (de-)serialization. +""" + +from uuid import UUID + +# See https://github.com/package-url/packageurl-python/issues/65 +from packageurl import PackageURL # type: ignore +from serializable.helpers import BaseHelper + +from ..model.bom_ref import BomRef + + +class BomRefHelper(BaseHelper): + + @classmethod + def serialize(cls, o: object) -> str: + if isinstance(o, BomRef): + return o.value + + raise ValueError(f'Attempt to serialize a non-BomRef: {o.__class__}') + + @classmethod + def deserialize(cls, o: object) -> BomRef: + try: + return BomRef(value=str(o)) + except ValueError: + raise ValueError(f'BomRef string supplied ({o}) does not parse!') + + +class PackageUrl(BaseHelper): + + @classmethod + def serialize(cls, o: object) -> str: + if isinstance(o, PackageURL): + return str(o.to_string()) + + raise ValueError(f'Attempt to serialize a non-PackageURL: {o.__class__}') + + @classmethod + def deserialize(cls, o: object) -> PackageURL: + try: + return PackageURL.from_string(purl=str(o)) + except ValueError: + raise ValueError(f'PURL string supplied ({o}) does not parse!') + + +class UrnUuidHelper(BaseHelper): + + @classmethod + def serialize(cls, o: object) -> str: + if isinstance(o, UUID): + return o.urn + + raise ValueError(f'Attempt to serialize a non-UUID: {o.__class__}') + + @classmethod + def deserialize(cls, o: object) -> PackageURL: + try: + return UUID(str(o)) + except ValueError: + raise ValueError(f'UUID string supplied ({o}) does not parse!') diff --git a/docs/architecture.rst b/docs/architecture.rst index ad3d426f..4a8fa7c2 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -21,12 +21,19 @@ This library broadly is separated into three key functional areas: can easily help you build a Model from your project or ecosystem. For Python specific parser implementations see `cyclonedx-python`_ 2. **Model**: Internal models used to unify data from different parsers + + **Note:** As of version 4.0.0 of this library we support deserialization from JSON and XML as well as + serialization to JSON and XML. + 3. **Output**: Choose and configure an output which allows you to define output format as well as the CycloneDX schema version When wishing to generate a BOM, the process is as follows: -1. Generated a Model (either programmatically or from a :py:mod:`cyclonedx.parser` +1. Generate a Model by either: + 1. Programmatically using this library + 2. By deserializing from an existing CycloneDX BOM document + 3. From a :py:mod:`cyclonedx.parser` 2. Output the Model using an :py:mod:`cyclonedx.output` instance that reflects the schema version and format you require .. toctree:: diff --git a/docs/contributing.rst b/docs/contributing.rst index 0dbd0ebc..5a7f88a0 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -22,7 +22,7 @@ This project uses `PEP8`_ Style Guide for Python Code. This project loves sorted .. code-block:: poetry run isort . - poetry run autopep8 --in-place -r cyclonedx tests + poetry run flake8 cyclonedx/ tests/ typings/ Documentation diff --git a/docs/index.rst b/docs/index.rst index 6f2b0201..5d8b3986 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,6 +20,9 @@ CycloneDX is a lightweight BOM specification that is easily created, human-reada This CycloneDX module for Python can generate valid CycloneDX bill-of-material document containing an aggregate of all project dependencies. +As of version ``3.0.0``, the internal data model was adjusted to allow CycloneDX VEX documents to be produced as per +`official examples`_ linking VEX to a separate BOM. + This module is not designed for standalone use (i.e. it is not executable on it’s own). If you’re looking for a CycloneDX tool to run to generate (SBOM) software bill-of-materials documents, why not checkout: @@ -44,4 +47,5 @@ programmatically generate SBOMs. .. _CycloneDX Python: https://pypi.org/project/cyclonedx-bom/ .. _Jake: https://pypi.org/project/jake -.. _CycloneDX Tool Center: https://cyclonedx.org/tool-center/ \ No newline at end of file +.. _CycloneDX Tool Center: https://cyclonedx.org/tool-center/ +.. _official examples: https://cyclonedx.org/capabilities/bomlink/#linking-external-vex-to-bom-inventory \ No newline at end of file diff --git a/docs/modelling.rst b/docs/modelling.rst index 5b563a13..24e9c69d 100644 --- a/docs/modelling.rst +++ b/docs/modelling.rst @@ -23,6 +23,59 @@ Vulnerabilities are supported by the Model as of version 0.3.0. **Note:** Known vulnerabilities associated with Components can be sourced from various data sources, but this library will not source them for you. Perhaps look at `Jake`_ if you're interested in this. +Example BOM created programmatically +------------------------------------ + +.. note:: + + It is recommended that you have a good understanding of the `CycloneDX Schema`_ before attempting to create a BOM + programmatically with this library. + + +For the most up-to-date in-depth examples, look at our `Unit Tests`_. + +Example BOM created from existing CycloneDX BOM +------------------------------------ + +.. note:: + + Supported from version 4.0.0 of this library. + +Deserializing from a CycloneDX JSON BOM +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each model class in this library that is serializable provides a magic ``from_json()`` method. + +See the example below to read and deserialize a JSON CycloneDX document. Note that reading the file and loading as JSON +is the programmers responsibility. + +.. code-block:: python + + import json + from cyclonedx.model.bom import Bom + + with open('/path/to/my/cyclonedx.json') as input_json: + deserialized_bom = Bom.from_json(data=json.loads(input_json.read())) + + +Deserializing from a CycloneDX XML BOM +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each model class in this library that is serializable provides a magic ``from_xml()`` method. + +See the example below to read and deserialize a XML CycloneDX document. Note that reading the file and loading as XML +is the programmers responsibility. Be careful to avoid XML vulnerabilities as documented `here`_. It is recommended that +you use a library such as `defusedxml` instead of the native `xml.etree.ElementTree`. + +.. code-block:: python + + from xml.etree import ElementTree + from cyclonedx.model.bom import Bom + + with open('/path/to/my/cyclonedx.xml') as input_xml: + deserialized_bom = cast(Bom, Bom.from_xml(data=ElementTree.fromstring(input_xml.read()))) + + Example BOM using a Parser -------------------------- @@ -37,19 +90,11 @@ Example BOM using a Parser parser = EnvironmentParser() bom = Bom.from_parser(parser=parser) -Example BOM created programmatically ------------------------------------- - -.. note:: - - It is recommended that you have a good understanding of the `CycloneDX Schema`_ before attempting to create a BOM - programmatically with this library. -For the most up-to-date in-depth examples, look at our `Unit Tests`_. - .. _CycloneDX Python: https://github.com/CycloneDX/cyclonedx-python .. _Jake: https://pypi.org/project/jake .. _CycloneDX Schema: https://cyclonedx.org/docs/latest -.. _Unit Tests: https://github.com/CycloneDX/cyclonedx-python-lib/tree/main/tests \ No newline at end of file +.. _Unit Tests: https://github.com/CycloneDX/cyclonedx-python-lib/tree/main/tests +.. _here: https://docs.python.org/3/library/xml.html#xml-vulnerabilities diff --git a/docs/schema-support.rst b/docs/schema-support.rst index 78553bc2..bbd7e7a3 100644 --- a/docs/schema-support.rst +++ b/docs/schema-support.rst @@ -43,6 +43,7 @@ supported in prior versions of the CycloneDX schema. | ``bom.properties`` | No | See `schema specification bug 130`_ | +----------------------------+---------------+---------------------------------------------------------------------------------------------------+ | ``bom.vulnerabilities`` | Yes | Note: Prior to CycloneDX 1.4, these were present under ``bom.components`` via a schema extension. | +| | | Note: As of ``cyclonedx-python-lib`` ``>3.0.0``, Vulnerability are modelled differently | +----------------------------+---------------+---------------------------------------------------------------------------------------------------+ | ``bom.signature`` | No | | +----------------------------+---------------+---------------------------------------------------------------------------------------------------+ diff --git a/docs/support.rst b/docs/support.rst index 48773249..870a4a48 100644 --- a/docs/support.rst +++ b/docs/support.rst @@ -30,11 +30,5 @@ We endeavour to support all functionality for all `current actively supported Py However, some features may not be possible/present in older Python versions due to their lack of support - which are noted below. -Limitations in Python 3.6.x -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Unit Tests perform schema validation as part of their assertions. The ``jsonschema`` library does not support Python - 3.6 and thus schema validation for JSON tests is skipped when running on Python 3.6.x - .. _GitHub Issue: https://github.com/CycloneDX/cyclonedx-python/issues .. _current actively supported Python versions: https://www.python.org/downloads/ \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index eec6e8f0..e2e11ec3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,44 +1,34 @@ [[package]] name = "attrs" -version = "21.4.0" +version = "22.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] - -[[package]] -name = "autopep8" -version = "1.6.0" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -pycodestyle = ">=2.8.0" -toml = "*" +cov = ["attrs", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs"] +docs = ["furo", "sphinx", "myst-parser", "zope.interface", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["attrs", "zope.interface"] +tests-no-zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] +tests_no_zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] [[package]] name = "colorama" -version = "0.4.5" +version = "0.4.6" description = "Cross-platform colored terminal text." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" [[package]] name = "coverage" -version = "6.2" +version = "7.2.1" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] toml = ["tomli"] @@ -51,9 +41,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "distlib" -version = "0.3.5" +version = "0.3.6" description = "Distribution utilities" category = "dev" optional = false @@ -61,67 +59,68 @@ python-versions = "*" [[package]] name = "filelock" -version = "3.4.1" +version = "3.9.0" description = "A platform independent file lock." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] -testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] +docs = ["furo (>=2022.12.7)", "sphinx-autodoc-typehints (>=1.19.5)", "sphinx (>=5.3)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)", "pytest (>=7.2)"] [[package]] name = "flake8" -version = "4.0.1" +version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.1" [package.dependencies] -importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" +importlib-metadata = {version = ">=1.1.0,<4.3", markers = "python_version < \"3.8\""} +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.9.0,<2.10.0" +pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "flake8-annotations" -version = "2.7.0" +version = "2.9.1" description = "Flake8 Type Annotation Checks" category = "dev" optional = false -python-versions = ">=3.6.2,<4.0.0" +python-versions = ">=3.7,<4.0" [package.dependencies] -flake8 = ">=3.7,<5.0" +attrs = ">=21.4" +flake8 = ">=3.7" typed-ast = {version = ">=1.4,<2.0", markers = "python_version < \"3.8\""} [[package]] name = "flake8-bugbear" -version = "22.7.1" +version = "22.12.6" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] attrs = ">=19.2.0" flake8 = ">=3.0.0" [package.extras] -dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"] +dev = ["tox", "coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"] [[package]] name = "flake8-isort" -version = "4.1.2.post0" +version = "4.2.0" description = "flake8 plugin that integrates isort ." category = "dev" optional = false python-versions = "*" [package.dependencies] -flake8 = ">=3.2.1,<5" +flake8 = ">=3.2.1,<6" isort = ">=4.3.5,<6" [package.extras] @@ -129,7 +128,7 @@ test = ["pytest-cov"] [[package]] name = "importlib-metadata" -version = "4.2.0" +version = "3.10.1" description = "Read metadata from Python packages" category = "main" optional = false @@ -145,36 +144,36 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [[package]] name = "importlib-resources" -version = "5.4.0" +version = "5.12.0" description = "Read resources from Python packages" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] [[package]] name = "isort" -version = "5.10.1" +version = "5.11.5" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6.1,<4.0" +python-versions = ">=3.7.0" [package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib", "pip-shims (>=0.5.2)"] +requirements-deprecated-finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] [[package]] name = "jsonschema" -version = "4.7.2" +version = "4.17.3" description = "An implementation of JSON Schema validation for Python" category = "dev" optional = false @@ -184,6 +183,7 @@ python-versions = ">=3.7" attrs = ">=17.4.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} @@ -193,7 +193,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "lxml" -version = "4.9.1" +version = "4.9.2" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." category = "dev" optional = false @@ -207,73 +207,82 @@ source = ["Cython (>=0.29.7)"] [[package]] name = "mccabe" -version = "0.6.1" +version = "0.7.0" description = "McCabe checker, plugin for flake8" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "mypy" -version = "0.961" +version = "1.1.1" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -mypy-extensions = ">=0.4.3" +mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" [[package]] name = "packageurl-python" -version = "0.10.0" +version = "0.10.4" description = "A purl aka. Package URL parser and builder" category = "main" optional = false python-versions = ">=3.6" [package.extras] -test = ["black", "isort", "pytest"] build = ["wheel"] +test = ["pytest", "isort", "black"] [[package]] name = "packaging" -version = "21.3" +version = "23.0" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +[[package]] +name = "pkgutil-resolve-name" +version = "1.3.10" +description = "Resolve a name to an object." +category = "dev" +optional = false +python-versions = ">=3.6" [[package]] name = "platformdirs" -version = "2.4.0" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "2.6.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} [package.extras] -test = ["pytest-mock (>=3.6)", "pytest-cov (>=2.7)", "pytest (>=6)", "appdirs (==1.4.4)"] -docs = ["sphinx-autodoc-typehints (>=1.12)", "proselint (>=0.10.2)", "furo (>=2021.7.5b38)", "Sphinx (>=4)"] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.5)", "sphinx (>=5.3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"] [[package]] name = "pluggy" @@ -287,8 +296,8 @@ python-versions = ">=3.6" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] -testing = ["pytest-benchmark", "pytest"] -dev = ["tox", "pre-commit"] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "py" @@ -298,36 +307,36 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "py-serializable" +version = "0.11.1" +description = "Library for serializing and deserializing Python Objects to and from JSON and XML." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +defusedxml = ">=0.7.1,<0.8.0" + [[package]] name = "pycodestyle" -version = "2.8.0" +version = "2.9.1" description = "Python style guide checker" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [[package]] name = "pyflakes" -version = "2.4.0" +version = "2.5.0" description = "passive checker of Python programs" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyparsing" -version = "3.0.7" -description = "Python parsing module" -category = "dev" -optional = false python-versions = ">=3.6" -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - [[package]] name = "pyrsistent" -version = "0.18.1" +version = "0.19.3" description = "Persistent/Functional/Immutable data structures" category = "dev" optional = false @@ -349,25 +358,17 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - [[package]] name = "tomli" -version = "1.2.3" +version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "tox" -version = "3.25.1" +version = "3.28.0" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false @@ -381,7 +382,7 @@ packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" six = ">=1.14.0" -toml = ">=0.9.4" +tomli = {version = ">=2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""} virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" [package.extras] @@ -398,27 +399,19 @@ python-versions = ">=3.6" [[package]] name = "types-setuptools" -version = "63.2.2" +version = "67.5.0.0" description = "Typing stubs for setuptools" category = "dev" optional = false python-versions = "*" -[[package]] -name = "types-toml" -version = "0.10.8" -description = "Typing stubs for toml" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.5.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "virtualenv" @@ -432,7 +425,6 @@ python-versions = ">=3.6" distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} platformdirs = ">=2,<3" [package.extras] @@ -441,71 +433,384 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", [[package]] name = "xmldiff" -version = "2.4" +version = "2.5" description = "Creates diffs of XML files" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [package.dependencies] lxml = ">=3.1.0" -six = "*" + +[package.extras] +devenv = ["black", "coverage", "flake8", "zest.releaser"] [[package]] name = "zipp" -version = "3.6.0" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "jaraco.functools", "more-itertools", "big-o", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] [metadata] lock-version = "1.1" -python-versions = "^3.6" -content-hash = "2d54fab7cbd2e7c837e362936f030fe70ce8ed9114529f828a5f865b14f9d1d0" +python-versions = "^3.7" +content-hash = "3b73ac64827c58691b9d20cf28be3c74d31ee9a9ef9b62d7c59834674651f75c" [metadata.files] -attrs = [] -autopep8 = [] -colorama = [] -coverage = [] -ddt = [] -distlib = [] -filelock = [] -flake8 = [] -flake8-annotations = [] -flake8-bugbear = [] -flake8-isort = [] -importlib-metadata = [] -importlib-resources = [] -isort = [] -jsonschema = [] -lxml = [] -mccabe = [] -mypy = [] -mypy-extensions = [] -packageurl-python = [] -packaging = [] -platformdirs = [] -pluggy = [] -py = [] -pycodestyle = [] -pyflakes = [] -pyparsing = [] -pyrsistent = [] -six = [] -sortedcontainers = [] -toml = [] -tomli = [] -tox = [] -typed-ast = [] -types-setuptools = [] -types-toml = [] -typing-extensions = [] -virtualenv = [] -xmldiff = [] -zipp = [] +attrs = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +coverage = [ + {file = "coverage-7.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49567ec91fc5e0b15356da07a2feabb421d62f52a9fff4b1ec40e9e19772f5f8"}, + {file = "coverage-7.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2ef6cae70168815ed91388948b5f4fcc69681480a0061114db737f957719f03"}, + {file = "coverage-7.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3004765bca3acd9e015794e5c2f0c9a05587f5e698127ff95e9cfba0d3f29339"}, + {file = "coverage-7.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cca7c0b7f5881dfe0291ef09ba7bb1582cb92ab0aeffd8afb00c700bf692415a"}, + {file = "coverage-7.2.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2167d116309f564af56f9aa5e75ef710ef871c5f9b313a83050035097b56820"}, + {file = "coverage-7.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cb5f152fb14857cbe7f3e8c9a5d98979c4c66319a33cad6e617f0067c9accdc4"}, + {file = "coverage-7.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:87dc37f16fb5e3a28429e094145bf7c1753e32bb50f662722e378c5851f7fdc6"}, + {file = "coverage-7.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e191a63a05851f8bce77bc875e75457f9b01d42843f8bd7feed2fc26bbe60833"}, + {file = "coverage-7.2.1-cp310-cp310-win32.whl", hash = "sha256:e3ea04b23b114572b98a88c85379e9e9ae031272ba1fb9b532aa934c621626d4"}, + {file = "coverage-7.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:0cf557827be7eca1c38a2480484d706693e7bb1929e129785fe59ec155a59de6"}, + {file = "coverage-7.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:570c21a29493b350f591a4b04c158ce1601e8d18bdcd21db136fbb135d75efa6"}, + {file = "coverage-7.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e872b082b32065ac2834149dc0adc2a2e6d8203080501e1e3c3c77851b466f9"}, + {file = "coverage-7.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac6343bae03b176e9b58104a9810df3cdccd5cfed19f99adfa807ffbf43cf9b"}, + {file = "coverage-7.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abacd0a738e71b20e224861bc87e819ef46fedba2fb01bc1af83dfd122e9c319"}, + {file = "coverage-7.2.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9256d4c60c4bbfec92721b51579c50f9e5062c21c12bec56b55292464873508"}, + {file = "coverage-7.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80559eaf6c15ce3da10edb7977a1548b393db36cbc6cf417633eca05d84dd1ed"}, + {file = "coverage-7.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bd7e628f6c3ec4e7d2d24ec0e50aae4e5ae95ea644e849d92ae4805650b4c4e"}, + {file = "coverage-7.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09643fb0df8e29f7417adc3f40aaf379d071ee8f0350ab290517c7004f05360b"}, + {file = "coverage-7.2.1-cp311-cp311-win32.whl", hash = "sha256:1b7fb13850ecb29b62a447ac3516c777b0e7a09ecb0f4bb6718a8654c87dfc80"}, + {file = "coverage-7.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:617a94ada56bbfe547aa8d1b1a2b8299e2ec1ba14aac1d4b26a9f7d6158e1273"}, + {file = "coverage-7.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8649371570551d2fd7dee22cfbf0b61f1747cdfb2b7587bb551e4beaaa44cb97"}, + {file = "coverage-7.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d2b9b5e70a21474c105a133ba227c61bc95f2ac3b66861143ce39a5ea4b3f84"}, + {file = "coverage-7.2.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae82c988954722fa07ec5045c57b6d55bc1a0890defb57cf4a712ced65b26ddd"}, + {file = "coverage-7.2.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:861cc85dfbf55a7a768443d90a07e0ac5207704a9f97a8eb753292a7fcbdfcfc"}, + {file = "coverage-7.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0339dc3237c0d31c3b574f19c57985fcbe494280153bbcad33f2cdf469f4ac3e"}, + {file = "coverage-7.2.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5928b85416a388dd557ddc006425b0c37e8468bd1c3dc118c1a3de42f59e2a54"}, + {file = "coverage-7.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8d3843ca645f62c426c3d272902b9de90558e9886f15ddf5efe757b12dd376f5"}, + {file = "coverage-7.2.1-cp37-cp37m-win32.whl", hash = "sha256:6a034480e9ebd4e83d1aa0453fd78986414b5d237aea89a8fdc35d330aa13bae"}, + {file = "coverage-7.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6fce673f79a0e017a4dc35e18dc7bb90bf6d307c67a11ad5e61ca8d42b87cbff"}, + {file = "coverage-7.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f099da6958ddfa2ed84bddea7515cb248583292e16bb9231d151cd528eab657"}, + {file = "coverage-7.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:97a3189e019d27e914ecf5c5247ea9f13261d22c3bb0cfcfd2a9b179bb36f8b1"}, + {file = "coverage-7.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a81dbcf6c6c877986083d00b834ac1e84b375220207a059ad45d12f6e518a4e3"}, + {file = "coverage-7.2.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2c3dde4c0b9be4b02067185136b7ee4681978228ad5ec1278fa74f5ca3e99"}, + {file = "coverage-7.2.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a209d512d157379cc9ab697cbdbb4cfd18daa3e7eebaa84c3d20b6af0037384"}, + {file = "coverage-7.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f3d07edb912a978915576a776756069dede66d012baa503022d3a0adba1b6afa"}, + {file = "coverage-7.2.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8dca3c1706670297851bca1acff9618455122246bdae623be31eca744ade05ec"}, + {file = "coverage-7.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b1991a6d64231a3e5bbe3099fb0dd7c9aeaa4275ad0e0aeff4cb9ef885c62ba2"}, + {file = "coverage-7.2.1-cp38-cp38-win32.whl", hash = "sha256:22c308bc508372576ffa3d2dbc4824bb70d28eeb4fcd79d4d1aed663a06630d0"}, + {file = "coverage-7.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:b0c0d46de5dd97f6c2d1b560bf0fcf0215658097b604f1840365296302a9d1fb"}, + {file = "coverage-7.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4dd34a935de268a133e4741827ae951283a28c0125ddcdbcbba41c4b98f2dfef"}, + {file = "coverage-7.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0f8318ed0f3c376cfad8d3520f496946977abde080439d6689d7799791457454"}, + {file = "coverage-7.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:834c2172edff5a08d78e2f53cf5e7164aacabeb66b369f76e7bb367ca4e2d993"}, + {file = "coverage-7.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4d70c853f0546855f027890b77854508bdb4d6a81242a9d804482e667fff6e6"}, + {file = "coverage-7.2.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a6450da4c7afc4534305b2b7d8650131e130610cea448ff240b6ab73d7eab63"}, + {file = "coverage-7.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:99f4dd81b2bb8fc67c3da68b1f5ee1650aca06faa585cbc6818dbf67893c6d58"}, + {file = "coverage-7.2.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bdd3f2f285ddcf2e75174248b2406189261a79e7fedee2ceeadc76219b6faa0e"}, + {file = "coverage-7.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f29351393eb05e6326f044a7b45ed8e38cb4dcc38570d12791f271399dc41431"}, + {file = "coverage-7.2.1-cp39-cp39-win32.whl", hash = "sha256:e2b50ebc2b6121edf352336d503357321b9d8738bb7a72d06fc56153fd3f4cd8"}, + {file = "coverage-7.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd5a12239c0006252244f94863f1c518ac256160cd316ea5c47fb1a11b25889a"}, + {file = "coverage-7.2.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:436313d129db7cf5b4ac355dd2bd3f7c7e5294af077b090b85de75f8458b8616"}, + {file = "coverage-7.2.1.tar.gz", hash = "sha256:c77f2a9093ccf329dd523a9b2b3c854c20d2a3d968b6def3b820272ca6732242"}, +] +ddt = [ + {file = "ddt-1.6.0-py2.py3-none-any.whl", hash = "sha256:e3c93b961a108b4f4d5a6c7f2263513d928baf3bb5b32af8e1c804bfb041141d"}, + {file = "ddt-1.6.0.tar.gz", hash = "sha256:f71b348731b8c78c3100bffbd951a769fbd439088d1fdbb3841eee019af80acd"}, +] +defusedxml = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] +distlib = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] +filelock = [ + {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, + {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, +] +flake8 = [ + {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, + {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, +] +flake8-annotations = [ + {file = "flake8-annotations-2.9.1.tar.gz", hash = "sha256:11f09efb99ae63c8f9d6b492b75fe147fbc323179fddfe00b2e56eefeca42f57"}, + {file = "flake8_annotations-2.9.1-py3-none-any.whl", hash = "sha256:a4385158a7a9fc8af1d8820a2f4c8d03387997006a83f5f8bfe5bc6085bdf88a"}, +] +flake8-bugbear = [ + {file = "flake8-bugbear-22.12.6.tar.gz", hash = "sha256:4cdb2c06e229971104443ae293e75e64c6107798229202fbe4f4091427a30ac0"}, + {file = "flake8_bugbear-22.12.6-py3-none-any.whl", hash = "sha256:b69a510634f8a9c298dfda2b18a8036455e6b19ecac4fe582e4d7a0abfa50a30"}, +] +flake8-isort = [ + {file = "flake8-isort-4.2.0.tar.gz", hash = "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0"}, + {file = "flake8_isort-4.2.0-py3-none-any.whl", hash = "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"}, +] +importlib-metadata = [ + {file = "importlib_metadata-3.10.1-py3-none-any.whl", hash = "sha256:2ec0faae539743ae6aaa84b49a169670a465f7f5d64e6add98388cc29fd1f2f6"}, + {file = "importlib_metadata-3.10.1.tar.gz", hash = "sha256:c9356b657de65c53744046fa8f7358afe0714a1af7d570c00c3835c2d724a7c1"}, +] +importlib-resources = [ + {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, + {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, +] +isort = [ + {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, + {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, +] +jsonschema = [ + {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, + {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, +] +lxml = [ + {file = "lxml-4.9.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2"}, + {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892"}, + {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a"}, + {file = "lxml-4.9.2-cp27-cp27m-win32.whl", hash = "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de"}, + {file = "lxml-4.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3"}, + {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50"}, + {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975"}, + {file = "lxml-4.9.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4"}, + {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7"}, + {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"}, + {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"}, + {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"}, + {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92"}, + {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1"}, + {file = "lxml-4.9.2-cp311-cp311-win32.whl", hash = "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33"}, + {file = "lxml-4.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd"}, + {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"}, + {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"}, + {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"}, + {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"}, + {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e"}, + {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"}, + {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"}, + {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"}, + {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"}, + {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45"}, + {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e"}, + {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b"}, + {file = "lxml-4.9.2-cp37-cp37m-win32.whl", hash = "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe"}, + {file = "lxml-4.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9"}, + {file = "lxml-4.9.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c"}, + {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f"}, + {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457"}, + {file = "lxml-4.9.2-cp38-cp38-win32.whl", hash = "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b"}, + {file = "lxml-4.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7"}, + {file = "lxml-4.9.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5"}, + {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5"}, + {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2"}, + {file = "lxml-4.9.2-cp39-cp39-win32.whl", hash = "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1"}, + {file = "lxml-4.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f"}, + {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c"}, + {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409"}, + {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"}, +] +mccabe = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] +mypy = [ + {file = "mypy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39c7119335be05630611ee798cc982623b9e8f0cff04a0b48dfc26100e0b97af"}, + {file = "mypy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61bf08362e93b6b12fad3eab68c4ea903a077b87c90ac06c11e3d7a09b56b9c1"}, + {file = "mypy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb19c9f662e41e474e0cff502b7064a7edc6764f5262b6cd91d698163196799"}, + {file = "mypy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:315ac73cc1cce4771c27d426b7ea558fb4e2836f89cb0296cbe056894e3a1f78"}, + {file = "mypy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5cb14ff9919b7df3538590fc4d4c49a0f84392237cbf5f7a816b4161c061829e"}, + {file = "mypy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26cdd6a22b9b40b2fd71881a8a4f34b4d7914c679f154f43385ca878a8297389"}, + {file = "mypy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b5f81b40d94c785f288948c16e1f2da37203c6006546c5d947aab6f90aefef2"}, + {file = "mypy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b437be1c02712a605591e1ed1d858aba681757a1e55fe678a15c2244cd68a5"}, + {file = "mypy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d809f88734f44a0d44959d795b1e6f64b2bbe0ea4d9cc4776aa588bb4229fc1c"}, + {file = "mypy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:a380c041db500e1410bb5b16b3c1c35e61e773a5c3517926b81dfdab7582be54"}, + {file = "mypy-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b7c7b708fe9a871a96626d61912e3f4ddd365bf7f39128362bc50cbd74a634d5"}, + {file = "mypy-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c10fa12df1232c936830839e2e935d090fc9ee315744ac33b8a32216b93707"}, + {file = "mypy-1.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0a28a76785bf57655a8ea5eb0540a15b0e781c807b5aa798bd463779988fa1d5"}, + {file = "mypy-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ef6a01e563ec6a4940784c574d33f6ac1943864634517984471642908b30b6f7"}, + {file = "mypy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d64c28e03ce40d5303450f547e07418c64c241669ab20610f273c9e6290b4b0b"}, + {file = "mypy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64cc3afb3e9e71a79d06e3ed24bb508a6d66f782aff7e56f628bf35ba2e0ba51"}, + {file = "mypy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce61663faf7a8e5ec6f456857bfbcec2901fbdb3ad958b778403f63b9e606a1b"}, + {file = "mypy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b0c373d071593deefbcdd87ec8db91ea13bd8f1328d44947e88beae21e8d5e9"}, + {file = "mypy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:2888ce4fe5aae5a673386fa232473014056967f3904f5abfcf6367b5af1f612a"}, + {file = "mypy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:19ba15f9627a5723e522d007fe708007bae52b93faab00f95d72f03e1afa9598"}, + {file = "mypy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:59bbd71e5c58eed2e992ce6523180e03c221dcd92b52f0e792f291d67b15a71c"}, + {file = "mypy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9401e33814cec6aec8c03a9548e9385e0e228fc1b8b0a37b9ea21038e64cdd8a"}, + {file = "mypy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b398d8b1f4fba0e3c6463e02f8ad3346f71956b92287af22c9b12c3ec965a9f"}, + {file = "mypy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:69b35d1dcb5707382810765ed34da9db47e7f95b3528334a3c999b0c90fe523f"}, + {file = "mypy-1.1.1-py3-none-any.whl", hash = "sha256:4e4e8b362cdf99ba00c2b218036002bdcdf1e0de085cdb296a49df03fb31dfc4"}, + {file = "mypy-1.1.1.tar.gz", hash = "sha256:ae9ceae0f5b9059f33dbc62dea087e942c0ccab4b7a003719cb70f9b8abfa32f"}, +] +mypy-extensions = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] +packageurl-python = [ + {file = "packageurl-python-0.10.4.tar.gz", hash = "sha256:5c91334f942cd55d45eb0c67dd339a535ef90e25f05b9ec016ad188ed0ef9048"}, + {file = "packageurl_python-0.10.4-py3-none-any.whl", hash = "sha256:bf8a1ffe755634776f6563904d792fb0aa13b377fc86115c36fe17f69b6e59db"}, +] +packaging = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, +] +pkgutil-resolve-name = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] +platformdirs = [ + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +py-serializable = [ + {file = "py-serializable-0.11.1.tar.gz", hash = "sha256:ba0e1287b9e4f645a5334f1913abd8e647e7250209f84f55dce3909498a6f586"}, + {file = "py_serializable-0.11.1-py3-none-any.whl", hash = "sha256:79e21f0672822e6200b15f45ce9f636e8126466f62dbd7d488c67313c72b5c3e"}, +] +pycodestyle = [ + {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, + {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, +] +pyflakes = [ + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, +] +pyrsistent = [ + {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, + {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, + {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, + {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, + {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, + {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sortedcontainers = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +tox = [ + {file = "tox-3.28.0-py2.py3-none-any.whl", hash = "sha256:57b5ab7e8bb3074edc3c0c0b4b192a4f3799d3723b2c5b76f1fa9f2d40316eea"}, + {file = "tox-3.28.0.tar.gz", hash = "sha256:d0d28f3fe6d6d7195c27f8b054c3e99d5451952b54abdae673b71609a581f640"}, +] +typed-ast = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] +types-setuptools = [ + {file = "types-setuptools-67.5.0.0.tar.gz", hash = "sha256:fa6f231eeb27e86b1d6e8260f73de300e91f99c205b9a5e21debd49f3726a849"}, + {file = "types_setuptools-67.5.0.0-py3-none-any.whl", hash = "sha256:f7f4bf4ab777e88631d3a387bbfdd4d480a2a4693ca896130f8ef738370377b8"}, +] +typing-extensions = [ + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, +] +virtualenv = [ + {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, + {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, +] +xmldiff = [ + {file = "xmldiff-2.5-py3-none-any.whl", hash = "sha256:d2eee7992f3f71b157088b24c7e70f6e77ab50f75b0ece71f6938d9ea2559a7c"}, + {file = "xmldiff-2.5.tar.gz", hash = "sha256:6c5f30bc65ce6e81d9a70a3cb0269f7ba61852fd6caa7a0fbfc0edf33b3cb717"}, +] +zipp = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] diff --git a/pyproject.toml b/pyproject.toml index 7e384f47..057379e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,11 +30,11 @@ classifiers = [ 'Topic :: Software Development', 'Topic :: System :: Software Distribution', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Typing :: Typed', ] keywords = [ @@ -46,30 +46,26 @@ keywords = [ [tool.poetry.dependencies] # ATTENTION: keep `requirements.lowest.txt` file in sync -python = "^3.6" +python = "^3.7" +importlib-metadata = { version = "^3.4.0", python = "<3.8" } packageurl-python = ">= 0.9" -importlib-metadata = { version = ">= 3.4", python = "< 3.8" } -setuptools = ">= 47.0.0" -toml = "^0.10.0" +py-serializable = "^0.11.1" sortedcontainers = "^2.4.0" [tool.poetry.dev-dependencies] -tox = "^3.25.0" ddt = "^1.6.0" -coverage = "^6.2" -mypy = ">= 0.920, <= 0.961" -autopep8 = "^1.6.0" -isort = { version = "^5.10.0", python = ">= 3.6.1" } -flake8 = "^4.0.1" -flake8-annotations = {version = "^2.7.0", python = ">= 3.6.2"} +coverage = "^7.2.1" +flake8 = "^5.0.4" # last version supporting Python 3.7 +flake8-annotations = "^2.9.1" # last version supporting Python 3.7 flake8-bugbear = "^22.7.1" -flake8-isort = { version = "^4.1.2", python = ">= 3.6.1" } -jsonschema = { version = ">= 4.4.0", python = "> 3.6"} +flake8-isort = "^4.1.2" +isort = "^5.11.5" # last version supporting Python 3.7 +jsonschema = ">= 4.4.0" +mypy = "^1.1.1" lxml = ">=4.7.0" +tox = "^3.2.8" # `types-setuptools` need to stay in sync with version of `setuptools` - but 47 was not typed... types-setuptools = ">= 57.0.0" -# `types-toml` need to stay in sync with version of `toml` -types-toml = "^0.10.0" xmldiff = ">=2.4" [build-system] diff --git a/requirements.lowest.txt b/requirements.lowest.txt index 2a00307f..dce1896c 100644 --- a/requirements.lowest.txt +++ b/requirements.lowest.txt @@ -2,8 +2,7 @@ # see pyptoject file for ranges packageurl-python == 0.9.0 +py-serializable == 0.11.1 importlib-metadata == 3.4.0 # ; python_version < '3.8' setuptools == 47.0.0 types-setuptools == 57.0.0 -toml == 0.10.0 -types-toml == 0.10.0 diff --git a/tests/base.py b/tests/base.py index 76dee340..cc86477d 100644 --- a/tests/base.py +++ b/tests/base.py @@ -38,14 +38,18 @@ from jsonschema import ValidationError, validate as json_validate if sys.version_info >= (3, 8): - from importlib.metadata import version + from importlib.metadata import PackageNotFoundError, version else: - from importlib_metadata import version + from importlib_metadata import PackageNotFoundError, version from . import CDX_SCHEMA_DIRECTORY cyclonedx_lib_name: str = 'cyclonedx-python-lib' -cyclonedx_lib_version: str = version(cyclonedx_lib_name) +cyclonedx_lib_version: str = 'DEV' +try: + cyclonedx_lib_version: str = version(cyclonedx_lib_name) +except PackageNotFoundError: + pass single_uuid: str = 'urn:uuid:{}'.format(uuid4()) diff --git a/tests/data.py b/tests/data.py index 32f19c5e..67b62317 100644 --- a/tests/data.py +++ b/tests/data.py @@ -21,8 +21,10 @@ from datetime import datetime, timezone from decimal import Decimal from typing import List, Optional, TypeVar +from uuid import UUID -from packageurl import PackageURL +# See https://github.com/package-url/packageurl-python/issues/65 +from packageurl import PackageURL # type: ignore from cyclonedx.model import ( AttachedText, @@ -55,6 +57,7 @@ Pedigree, Swid, ) +from cyclonedx.model.dependency import Dependency from cyclonedx.model.issue import IssueClassification, IssueType, IssueTypeSource from cyclonedx.model.release_note import ReleaseNotes from cyclonedx.model.service import Service @@ -83,9 +86,15 @@ MOCK_UUID_4 = 'cd3e9c95-9d41-49e7-9924-8cf0465ae789' MOCK_UUID_5 = 'bb5911d6-1a1d-41c9-b6e0-46e848d16655' MOCK_UUID_6 = 'df70b5f1-8f53-47a4-be48-669ae78795e6' +MOCK_UUID_7 = UUID('6f266d1c-760f-4552-ae3b-41a9b74232fa') +MOCK_UUID_8 = UUID('77d15ab9-5602-4cca-8ed2-59ae579aafd3') +MOCK_UUID_9 = UUID('859ff614-35a7-4d37-803b-d89130cb2577') +MOCK_UUID_10 = UUID('0afa65bc-4acd-428b-9e17-8e97b1969745') +MOCK_BOM_UUID_1 = UUID('3e671687-395b-41f5-a30f-a58921a69b79') +MOCK_BOM_UUID_2 = UUID('d0b24ba4-102b-497e-b31f-4fdc3f0a3005') TEST_UUIDS = [ - MOCK_UUID_1, MOCK_UUID_2, MOCK_UUID_3, MOCK_UUID_4, MOCK_UUID_5, MOCK_UUID_6 + UUID(MOCK_UUID_1), UUID(MOCK_UUID_2), UUID(MOCK_UUID_3), UUID(MOCK_UUID_4), UUID(MOCK_UUID_5), UUID(MOCK_UUID_6) ] @@ -111,29 +120,30 @@ def get_bom_with_component_setuptools_with_release_notes() -> Bom: def get_bom_with_dependencies_valid() -> Bom: c1 = get_component_setuptools_simple() - c1.dependencies.update([ - get_component_toml_with_hashes_with_references().bom_ref - ]) - return Bom(components=[ - c1, - get_component_toml_with_hashes_with_references() - ]) + c2 = get_component_toml_with_hashes_with_references() + return Bom( + components=[c1, c2], dependencies=[ + Dependency(ref=c1.bom_ref, dependencies=[ + Dependency(ref=c2.bom_ref) + ]), + Dependency(ref=c2.bom_ref) + ] + ) def get_bom_with_dependencies_invalid() -> Bom: c1 = get_component_setuptools_simple() - c1.dependencies.update([ - get_component_toml_with_hashes_with_references().bom_ref - ]) - return Bom(components=[ - c1 - ]) + return Bom(components=[c1], dependencies=[Dependency(ref=c1.bom_ref)]) def get_bom_with_metadata_component_and_dependencies() -> Bom: bom = Bom(components=[get_component_toml_with_hashes_with_references()]) bom.metadata.component = get_component_setuptools_simple() - bom.metadata.component.dependencies.update([get_component_toml_with_hashes_with_references().bom_ref]) + bom.dependencies.add( + Dependency(ref=bom.metadata.component.bom_ref, dependencies=[ + Dependency(ref=get_component_toml_with_hashes_with_references().bom_ref) + ]) + ) return bom @@ -144,7 +154,8 @@ def get_bom_with_component_setuptools_complete() -> Bom: def get_bom_with_component_setuptools_with_vulnerability() -> Bom: bom = Bom() component = get_component_setuptools_simple() - vulnerability = Vulnerability( + bom.components.add(component) + bom.vulnerabilities.add(Vulnerability( bom_ref='my-vuln-ref-1', id='CVE-2018-7489', source=get_vulnerability_source_nvd(), references=[ VulnerabilityReference(id='SOME-OTHER-ID', source=VulnerabilitySource( @@ -188,18 +199,16 @@ def get_bom_with_component_setuptools_with_vulnerability() -> Bom: state=ImpactAnalysisState.EXPLOITABLE, justification=ImpactAnalysisJustification.REQUIRES_ENVIRONMENT, responses=[ImpactAnalysisResponse.CAN_NOT_FIX], detail='Some extra detail' ), - affects_targets=[ + affects=[ BomTarget( ref=component.purl.to_string() if component.purl else None, versions=[BomTargetVersionRange( - version_range='49.0.0 - 54.0.0', status=ImpactAnalysisAffectedStatus.AFFECTED + range='49.0.0 - 54.0.0', status=ImpactAnalysisAffectedStatus.AFFECTED )] ) ], properties=get_properties_1() - ) - component.add_vulnerability(vulnerability=vulnerability) - bom.components.add(component) + )) return bom @@ -213,10 +222,10 @@ def get_bom_just_complete_metadata() -> Bom: bom.metadata.component = get_component_setuptools_complete() bom.metadata.manufacture = get_org_entity_1() bom.metadata.supplier = get_org_entity_2() - bom.metadata.licenses = [LicenseChoice(license_=License( - spdx_license_id='Apache-2.0', license_text=AttachedText( + bom.metadata.licenses = [LicenseChoice(license=License( + id='Apache-2.0', text=AttachedText( content='VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=', encoding=Encoding.BASE_64 - ), license_url=XsUri('https://www.apache.org/licenses/LICENSE-2.0.txt') + ), url=XsUri('https://www.apache.org/licenses/LICENSE-2.0.txt') ))] bom.metadata.properties = get_properties_1() return bom @@ -235,7 +244,7 @@ def get_bom_with_services_simple() -> Bom: Service(name='my-second-service') ]) bom.metadata.component = Component( - name='cyclonedx-python-lib', version='1.0.0', component_type=ComponentType.LIBRARY + name='cyclonedx-python-lib', version='1.0.0', type=ComponentType.LIBRARY ) return bom @@ -253,7 +262,7 @@ def get_bom_with_services_complex() -> Bom: DataClassification(flow=DataFlow.OUTBOUND, classification='public') ], licenses=[ - LicenseChoice(license_expression='Commercial') + LicenseChoice(expression='Commercial') ], external_references=[ get_external_reference_1() @@ -264,7 +273,7 @@ def get_bom_with_services_complex() -> Bom: Service(name='my-second-service') ]) bom.metadata.component = Component( - name='cyclonedx-python-lib', version='1.0.0', component_type=ComponentType.LIBRARY + name='cyclonedx-python-lib', version='1.0.0', type=ComponentType.LIBRARY ) return bom @@ -282,7 +291,7 @@ def get_bom_with_nested_services() -> Bom: DataClassification(flow=DataFlow.OUTBOUND, classification='public') ], licenses=[ - LicenseChoice(license_expression='Commercial') + LicenseChoice(expression='Commercial') ], external_references=[ get_external_reference_1() @@ -314,7 +323,7 @@ def get_bom_with_nested_services() -> Bom: ) ]) bom.metadata.component = Component( - name='cyclonedx-python-lib', version='1.0.0', component_type=ComponentType.LIBRARY + name='cyclonedx-python-lib', version='1.0.0', type=ComponentType.LIBRARY ) return bom @@ -330,14 +339,16 @@ def get_bom_for_issue_275_components() -> Bom: comp_c = Component(bom_ref=MOCK_UUID_4, name="comp_c", version="1.0.0") comp_b.components.add(comp_c) - comp_b.dependencies.add(comp_c.bom_ref) + # comp_b.dependencies.add(comp_c.bom_ref) libs = [comp_a, comp_b] - app.dependencies.add(comp_a.bom_ref) - app.dependencies.add(comp_b.bom_ref) + # app.dependencies.add(comp_a.bom_ref) + # app.dependencies.add(comp_b.bom_ref) bom = Bom(components=libs) bom.metadata.component = app + bom.register_dependency(target=app, depends_on=[comp_a, comp_b]) + bom.register_dependency(target=comp_b, depends_on=[comp_c]) return bom @@ -363,7 +374,7 @@ def get_bom_for_issue_328_components() -> Bom: see https://github.com/CycloneDX/cyclonedx-python-lib/issues/328 """ - comp_root = Component(component_type=ComponentType.APPLICATION, + comp_root = Component(type=ComponentType.APPLICATION, name='my-project', version='1', bom_ref='my-project') comp_a = Component(name='A', version='0.1', bom_ref='component-A') comp_b = Component(name='B', version='1.0', bom_ref='component-B') @@ -402,7 +413,7 @@ def get_component_setuptools_complete(include_pedigree: bool = True) -> Componen get_component_setuptools_simple(), get_component_toml_with_hashes_with_references() ]) - component.evidence = ComponentEvidence(copyright_=[Copyright(text='Commercial'), Copyright(text='Commercial 2')]) + component.evidence = ComponentEvidence(copyright=[Copyright(text='Commercial'), Copyright(text='Commercial 2')]) component.release_notes = get_release_notes() return component @@ -416,7 +427,7 @@ def get_component_setuptools_simple( purl=PackageURL( type='pypi', name='setuptools', version='50.3.2', qualifiers='extension=tar.gz' ), - licenses=[LicenseChoice(license_expression='MIT License')], + licenses=[LicenseChoice(expression='MIT License')], author='Test Author' ) @@ -427,7 +438,7 @@ def get_component_setuptools_simple_no_version(bom_ref: Optional[str] = None) -> purl=PackageURL( type='pypi', name='setuptools', qualifiers='extension=tar.gz' ), - licenses=[LicenseChoice(license_expression='MIT License')], + licenses=[LicenseChoice(expression='MIT License')], author='Test Author' ) @@ -447,7 +458,7 @@ def get_component_toml_with_hashes_with_references(bom_ref: Optional[str] = None def get_external_reference_1() -> ExternalReference: return ExternalReference( - reference_type=ExternalReferenceType.DISTRIBUTION, + type=ExternalReferenceType.DISTRIBUTION, url=XsUri('https://cyclonedx.org'), comment='No comment', hashes=[ @@ -459,14 +470,14 @@ def get_external_reference_1() -> ExternalReference: def get_external_reference_2() -> ExternalReference: return ExternalReference( - reference_type=ExternalReferenceType.WEBSITE, + type=ExternalReferenceType.WEBSITE, url=XsUri('https://cyclonedx.org') ) def get_issue_1() -> IssueType: return IssueType( - classification=IssueClassification.SECURITY, id_='CVE-2021-44228', name='Apache Log3Shell', + type=IssueClassification.SECURITY, id='CVE-2021-44228', name='Apache Log3Shell', description='Apache Log4j2 2.0-beta9 through 2.12.1 and 2.13.0 through 2.15.0 JNDI features...', source=IssueTypeSource(name='NVD', url=XsUri('https://nvd.nist.gov/vuln/detail/CVE-2021-44228')), references=[ @@ -478,7 +489,7 @@ def get_issue_1() -> IssueType: def get_issue_2() -> IssueType: return IssueType( - classification=IssueClassification.SECURITY, id_='CVE-2021-44229', name='Apache Log4Shell', + type=IssueClassification.SECURITY, id='CVE-2021-44229', name='Apache Log4Shell', description='Apache Log4j2 2.0-beta9 through 2.12.1 and 2.13.0 through 2.15.0 JNDI features...', source=IssueTypeSource(name='NVD', url=XsUri('https://nvd.nist.gov/vuln/detail/CVE-2021-44228')), references=[ @@ -523,7 +534,7 @@ def get_pedigree_1() -> Pedigree: get_component_setuptools_simple(bom_ref='ded1d73e-1fca-4302-b520-f1bc53979958') ], commits=[Commit(uid='a-random-uid', message="A commit message")], - patches=[Patch(type_=PatchClassification.BACKPORT)], + patches=[Patch(type=PatchClassification.BACKPORT)], notes='Some notes here please' ) @@ -541,7 +552,7 @@ def get_release_notes() -> ReleaseNotes: ).decode(encoding='UTF-8') return ReleaseNotes( - type_='major', title="Release Notes Title", + type='major', title="Release Notes Title", featured_image=XsUri('https://cyclonedx.org/theme/assets/images/CycloneDX-Twitter-Card.png'), social_image=XsUri('https://cyclonedx.org/cyclonedx-icon.png'), description="This release is a test release", timestamp=MOCK_TIMESTAMP, @@ -554,13 +565,13 @@ def get_release_notes() -> ReleaseNotes: Note( text=NoteText( content=text_content, content_type='text/plain; charset=UTF-8', - content_encoding=Encoding.BASE_64 + encoding=Encoding.BASE_64 ), locale='en-GB' ), Note( text=NoteText( content=text_content, content_type='text/plain; charset=UTF-8', - content_encoding=Encoding.BASE_64 + encoding=Encoding.BASE_64 ), locale='en-US' ) ], @@ -604,9 +615,9 @@ def get_vulnerability_source_owasp() -> VulnerabilitySource: def reorder(items: List[T], indexes: List[int]) -> List[T]: - ''' + """ Return list of items in the order indicated by indexes. - ''' + """ reordered_items = [] for i in range(len(items)): reordered_items.append(items[indexes[i]]) diff --git a/tests/fixtures/json/1.2/bom_dependencies.json b/tests/fixtures/json/1.2/bom_dependencies.json index 3e7b3a12..8f54fe4a 100644 --- a/tests/fixtures/json/1.2/bom_dependencies.json +++ b/tests/fixtures/json/1.2/bom_dependencies.json @@ -57,8 +57,7 @@ ] }, { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.2/bom_dependencies_component.json b/tests/fixtures/json/1.2/bom_dependencies_component.json index 81361c74..dd6b880e 100644 --- a/tests/fixtures/json/1.2/bom_dependencies_component.json +++ b/tests/fixtures/json/1.2/bom_dependencies_component.json @@ -57,8 +57,7 @@ ] }, { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.2/bom_external_references.json b/tests/fixtures/json/1.2/bom_external_references.json index 3ee319e1..fb370e06 100644 --- a/tests/fixtures/json/1.2/bom_external_references.json +++ b/tests/fixtures/json/1.2/bom_external_references.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2", - "serialNumber": "urn:uuid:b254c902-deb4-4298-9969-027541ee365c", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.467119+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", diff --git a/tests/fixtures/json/1.2/bom_issue_275_components.json b/tests/fixtures/json/1.2/bom_issue_275_components.json index 199a7bcc..8fc7cf7e 100644 --- a/tests/fixtures/json/1.2/bom_issue_275_components.json +++ b/tests/fixtures/json/1.2/bom_issue_275_components.json @@ -51,8 +51,7 @@ ] }, { - "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", - "dependsOn": [] + "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda" }, { "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", @@ -61,8 +60,7 @@ ] }, { - "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789", - "dependsOn": [] + "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789" } ] } diff --git a/tests/fixtures/json/1.2/bom_services_complex.json b/tests/fixtures/json/1.2/bom_services_complex.json index 4b587509..2caa9876 100644 --- a/tests/fixtures/json/1.2/bom_services_complex.json +++ b/tests/fixtures/json/1.2/bom_services_complex.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2", - "serialNumber": "urn:uuid:97251de9-d320-463f-a31d-399d88719b86", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.701426+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -15,7 +15,7 @@ ], "component": { "type": "library", - "bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", + "bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655", "name": "cyclonedx-python-lib", "version": "1.0.0" } @@ -24,20 +24,20 @@ { "bom-ref": "my-specific-bom-ref-for-my-first-service", "provider": { - "name": "CycloneDX", - "url": [ - "https://cyclonedx.org" - ], "contact": [ { - "name": "A N Other", - "email": "someone@somewhere.tld", - "phone": "+44 (0)1234 567890" + "email": "paul.horton@owasp.org", + "name": "Paul Horton" }, { - "name": "Paul Horton", - "email": "paul.horton@owasp.org" + "email": "someone@somewhere.tld", + "name": "A N Other", + "phone": "+44 (0)1234 567890" } + ], + "name": "CycloneDX", + "url": [ + "https://cyclonedx.org" ] }, "group": "a-group", @@ -52,8 +52,8 @@ "x-trust-boundary": true, "data": [ { - "flow": "outbound", - "classification": "public" + "classification": "public", + "flow": "outbound" } ], "licenses": [ @@ -63,15 +63,9 @@ ], "externalReferences": [ { - "url": "https://cyclonedx.org", "comment": "No comment", "type": "distribution", - "hashes": [ - { - "alg": "SHA-256", - "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" - } - ] + "url": "https://cyclonedx.org" } ] }, @@ -82,8 +76,13 @@ ], "dependencies": [ { - "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", - "dependsOn": [] + "ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655" + }, + { + "ref": "my-specific-bom-ref-for-my-first-service" + }, + { + "ref": "be2c6502-7e9a-47db-9a66-e34f729810a3" } ] } diff --git a/tests/fixtures/json/1.2/bom_services_nested.json b/tests/fixtures/json/1.2/bom_services_nested.json index 373cd148..f927f9d7 100644 --- a/tests/fixtures/json/1.2/bom_services_nested.json +++ b/tests/fixtures/json/1.2/bom_services_nested.json @@ -1,110 +1,102 @@ { "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", - "specVersion": "1.2", - "serialNumber": "urn:uuid:3471759e-0fc2-4284-b558-e760e6d23c6c", - "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.729167+00:00", + "component": { + "bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789", + "name": "cyclonedx-python-lib", + "type": "library", + "version": "1.0.0" + }, + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { - "vendor": "CycloneDX", "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", "version": "TESTING" } - ], - "component": { - "type": "library", - "bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789", - "name": "cyclonedx-python-lib", - "version": "1.0.0" - } + ] }, + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "services": [ { + "authenticated": false, "bom-ref": "my-specific-bom-ref-for-my-first-service", - "provider": { - "name": "CycloneDX", - "url": [ - "https://cyclonedx.org" - ], - "contact": [ - { - "name": "A N Other", - "email": "someone@somewhere.tld", - "phone": "+44 (0)1234 567890" - }, - { - "name": "Paul Horton", - "email": "paul.horton@owasp.org" - } - ] - }, - "group": "a-group", - "name": "my-first-service", - "version": "1.2.3", + "data": [ + { + "classification": "public", + "flow": "outbound" + } + ], "description": "Description goes here", "endpoints": [ "/api/thing/1", "/api/thing/2" ], - "authenticated": false, - "x-trust-boundary": true, - "data": [ + "externalReferences": [ { - "flow": "outbound", - "classification": "public" + "comment": "No comment", + "type": "distribution", + "url": "https://cyclonedx.org" } ], + "group": "a-group", "licenses": [ { "expression": "Commercial" } ], - "externalReferences": [ - { - "url": "https://cyclonedx.org", - "comment": "No comment", - "type": "distribution", - "hashes": [ - { - "alg": "SHA-256", - "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" - } - ] - } - ], + "name": "my-first-service", + "provider": { + "contact": [ + { + "email": "paul.horton@owasp.org", + "name": "Paul Horton" + }, + { + "email": "someone@somewhere.tld", + "name": "A N Other", + "phone": "+44 (0)1234 567890" + } + ], + "name": "CycloneDX", + "url": [ + "https://cyclonedx.org" + ] + }, "services": [ { + "bom-ref": "be2c6502-7e9a-47db-9a66-e34f729810a3", + "name": "first-nested-service" + }, + { + "authenticated": true, "bom-ref": "my-specific-bom-ref-for-second-nested-service", + "group": "no-group", + "name": "second-nested-service", "provider": { - "name": "CycloneDX", - "url": [ - "https://cyclonedx.org" - ], "contact": [ { - "name": "A N Other", - "email": "someone@somewhere.tld", - "phone": "+44 (0)1234 567890" + "email": "paul.horton@owasp.org", + "name": "Paul Horton" }, { - "name": "Paul Horton", - "email": "paul.horton@owasp.org" + "email": "someone@somewhere.tld", + "name": "A N Other", + "phone": "+44 (0)1234 567890" } + ], + "name": "CycloneDX", + "url": [ + "https://cyclonedx.org" ] }, - "group": "no-group", - "name": "second-nested-service", "version": "3.2.1", - "authenticated": true, "x-trust-boundary": false - }, - { - "bom-ref": "be2c6502-7e9a-47db-9a66-e34f729810a3", - "name": "first-nested-service" } - ] + ], + "version": "1.2.3", + "x-trust-boundary": true }, { "bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", @@ -112,25 +104,25 @@ "services": [ { "bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", + "group": "what-group", + "name": "yet-another-nested-service", "provider": { - "name": "CycloneDX", - "url": [ - "https://cyclonedx.org" - ], "contact": [ { - "name": "A N Other", - "email": "someone@somewhere.tld", - "phone": "+44 (0)1234 567890" + "email": "paul.horton@owasp.org", + "name": "Paul Horton" }, { - "name": "Paul Horton", - "email": "paul.horton@owasp.org" + "email": "someone@somewhere.tld", + "name": "A N Other", + "phone": "+44 (0)1234 567890" } + ], + "name": "CycloneDX", + "url": [ + "https://cyclonedx.org" ] }, - "group": "what-group", - "name": "yet-another-nested-service", "version": "6.5.4" }, { @@ -140,10 +132,17 @@ ] } ], + "specVersion": "1.2", + "version": 1, "dependencies": [ { - "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789", - "dependsOn": [] + "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857" + }, + { + "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789" + }, + { + "ref": "my-specific-bom-ref-for-my-first-service" } ] } diff --git a/tests/fixtures/json/1.2/bom_services_simple.json b/tests/fixtures/json/1.2/bom_services_simple.json index 47c727ec..198bfadd 100644 --- a/tests/fixtures/json/1.2/bom_services_simple.json +++ b/tests/fixtures/json/1.2/bom_services_simple.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2", - "serialNumber": "urn:uuid:228f339b-f73c-4379-af8e-3ad09dbde1d8", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.753759+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -15,7 +15,7 @@ ], "component": { "type": "library", - "bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", + "bom-ref": "df70b5f1-8f53-47a4-be48-669ae78795e6", "name": "cyclonedx-python-lib", "version": "1.0.0" } @@ -32,8 +32,13 @@ ], "dependencies": [ { - "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", - "dependsOn": [] + "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda" + }, + { + "ref": "be2c6502-7e9a-47db-9a66-e34f729810a3" + }, + { + "ref": "df70b5f1-8f53-47a4-be48-669ae78795e6" } ] } diff --git a/tests/fixtures/json/1.2/bom_setuptools.json b/tests/fixtures/json/1.2/bom_setuptools.json index 4115b7c8..961c69d9 100644 --- a/tests/fixtures/json/1.2/bom_setuptools.json +++ b/tests/fixtures/json/1.2/bom_setuptools.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2", - "serialNumber": "urn:uuid:21a3711c-be49-4007-b4df-c90af6eb8725", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:59.330881+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -31,8 +31,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.2/bom_setuptools_complete.json b/tests/fixtures/json/1.2/bom_setuptools_complete.json index 9319bc44..1bfc41dc 100644 --- a/tests/fixtures/json/1.2/bom_setuptools_complete.json +++ b/tests/fixtures/json/1.2/bom_setuptools_complete.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2", - "serialNumber": "urn:uuid:26201075-4b54-44d2-a6d3-bc90e55c9db7", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.623239+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -24,14 +24,14 @@ "https://cyclonedx.org" ], "contact": [ + { + "name": "Paul Horton", + "email": "paul.horton@owasp.org" + }, { "name": "A N Other", "email": "someone@somewhere.tld", "phone": "+44 (0)1234 567890" - }, - { - "name": "Paul Horton", - "email": "paul.horton@owasp.org" } ] }, @@ -107,55 +107,55 @@ "bom-ref": "555ca729-93c6-48f3-956e-bdaa4a2f0bfa", "name": "toml", "version": "0.10.2", + "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "hashes": [ { "alg": "SHA-256", "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ], - "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", - "comment": "No comment", - "type": "distribution" + "comment": "No comment" } ] } ], "variants": [ - { - "type": "library", - "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", - "author": "Test Author", - "name": "setuptools", - "version": "50.3.2", - "licenses": [ - { - "expression": "MIT License" - } - ], - "purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" - }, { "type": "library", "bom-ref": "e7abdcca-5ba2-4f29-b2cf-b1e1ef788e66", "name": "toml", "version": "0.10.2", + "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "hashes": [ { "alg": "SHA-256", "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ], - "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", - "comment": "No comment", - "type": "distribution" + "comment": "No comment" } ] + }, + { + "type": "library", + "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", + "author": "Test Author", + "name": "setuptools", + "version": "50.3.2", + "licenses": [ + { + "expression": "MIT License" + } + ], + "purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ], "commits": [ @@ -173,9 +173,9 @@ }, "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", - "comment": "No comment", - "type": "distribution" + "comment": "No comment" } ], "components": [ @@ -197,18 +197,18 @@ "bom-ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", "name": "toml", "version": "0.10.2", + "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "hashes": [ { "alg": "SHA-256", "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ], - "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", - "comment": "No comment", - "type": "distribution" + "comment": "No comment" } ] } @@ -217,16 +217,7 @@ ], "dependencies": [ { - "ref": "df70b5f1-8f53-47a4-be48-669ae78795e6", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "df70b5f1-8f53-47a4-be48-669ae78795e6" } ] } diff --git a/tests/fixtures/json/1.2/bom_setuptools_no_version.json b/tests/fixtures/json/1.2/bom_setuptools_no_version.json new file mode 100644 index 00000000..8fb2dd78 --- /dev/null +++ b/tests/fixtures/json/1.2/bom_setuptools_no_version.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.3", + "serialNumber": "urn:uuid:77d15ab9-5602-4cca-8ed2-59ae579aafd3", + "version": 1, + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "vendor": "CycloneDX", + "name": "cyclonedx-python-lib", + "version": "TESTING" + } + ] + }, + "components": [ + { + "type": "library", + "bom-ref": "pkg:pypi/setuptools?extension=tar.gz", + "author": "Test Author", + "name": "setuptools", + "version": "", + "licenses": [ + { + "expression": "MIT License" + } + ], + "purl": "pkg:pypi/setuptools?extension=tar.gz" + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/setuptools?extension=tar.gz" + } + ] +} diff --git a/tests/fixtures/json/1.2/bom_setuptools_with_cpe.json b/tests/fixtures/json/1.2/bom_setuptools_with_cpe.json index d7957112..0d7e9b9e 100644 --- a/tests/fixtures/json/1.2/bom_setuptools_with_cpe.json +++ b/tests/fixtures/json/1.2/bom_setuptools_with_cpe.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2", - "serialNumber": "urn:uuid:65baf289-d8ad-4128-800d-b5292c938c3a", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:59.351895+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -32,8 +32,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.2/bom_toml_1.json b/tests/fixtures/json/1.2/bom_toml_1.json index f5ea324c..4180b39b 100644 --- a/tests/fixtures/json/1.2/bom_toml_1.json +++ b/tests/fixtures/json/1.2/bom_toml_1.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2", - "serialNumber": "urn:uuid:f60062dc-9bb8-4415-be36-78f0c52c5c64", + "serialNumber": "urn:uuid:6f266d1c-760f-4552-ae3b-41a9b74232fa", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.552271+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -38,8 +38,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.2/bom_with_full_metadata.json b/tests/fixtures/json/1.2/bom_with_full_metadata.json index 40c1943c..d24684c6 100644 --- a/tests/fixtures/json/1.2/bom_with_full_metadata.json +++ b/tests/fixtures/json/1.2/bom_with_full_metadata.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2", - "serialNumber": "urn:uuid:47f912c6-4879-4f4b-ae9c-2cf51f15be08", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.775590+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -256,8 +256,7 @@ }, "dependencies": [ { - "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", - "dependsOn": [] + "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857" } ] } diff --git a/tests/fixtures/json/1.3/bom_dependencies.json b/tests/fixtures/json/1.3/bom_dependencies.json index 9e51f5b1..afd36d07 100644 --- a/tests/fixtures/json/1.3/bom_dependencies.json +++ b/tests/fixtures/json/1.3/bom_dependencies.json @@ -63,8 +63,7 @@ ] }, { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.3/bom_dependencies_component.json b/tests/fixtures/json/1.3/bom_dependencies_component.json index 98244502..1820acbd 100644 --- a/tests/fixtures/json/1.3/bom_dependencies_component.json +++ b/tests/fixtures/json/1.3/bom_dependencies_component.json @@ -63,8 +63,7 @@ ] }, { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.3/bom_external_references.json b/tests/fixtures/json/1.3/bom_external_references.json index 3bd5b63a..a4d4d9fd 100644 --- a/tests/fixtures/json/1.3/bom_external_references.json +++ b/tests/fixtures/json/1.3/bom_external_references.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:00236b4e-8837-423e-8789-734863b0f78a", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.491496+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", diff --git a/tests/fixtures/json/1.3/bom_issue_275_components.json b/tests/fixtures/json/1.3/bom_issue_275_components.json index 17b4a3c0..f1f7596d 100644 --- a/tests/fixtures/json/1.3/bom_issue_275_components.json +++ b/tests/fixtures/json/1.3/bom_issue_275_components.json @@ -51,8 +51,7 @@ ] }, { - "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", - "dependsOn": [] + "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda" }, { "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", @@ -61,8 +60,7 @@ ] }, { - "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789", - "dependsOn": [] + "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789" } ] } diff --git a/tests/fixtures/json/1.3/bom_services_complex.json b/tests/fixtures/json/1.3/bom_services_complex.json index 15088185..f222cd50 100644 --- a/tests/fixtures/json/1.3/bom_services_complex.json +++ b/tests/fixtures/json/1.3/bom_services_complex.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:401351ba-6438-4b6d-8f6f-f10f1ae41f8b", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.031955+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -15,7 +15,7 @@ ], "component": { "type": "library", - "bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", + "bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655", "name": "cyclonedx-python-lib", "version": "1.0.0" } @@ -92,8 +92,13 @@ ], "dependencies": [ { - "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", - "dependsOn": [] + "ref": "my-specific-bom-ref-for-my-first-service" + }, + { + "ref": "be2c6502-7e9a-47db-9a66-e34f729810a3" + }, + { + "ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655" } ] } diff --git a/tests/fixtures/json/1.3/bom_services_nested.json b/tests/fixtures/json/1.3/bom_services_nested.json index ba5ae49d..82e9e187 100644 --- a/tests/fixtures/json/1.3/bom_services_nested.json +++ b/tests/fixtures/json/1.3/bom_services_nested.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:6ae24228-acba-422e-bc82-450a230cf04d", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.058446+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -152,8 +152,13 @@ ], "dependencies": [ { - "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789", - "dependsOn": [] + "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789" + }, + { + "ref": "my-specific-bom-ref-for-my-first-service" + }, + { + "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857" } ] } diff --git a/tests/fixtures/json/1.3/bom_services_simple.json b/tests/fixtures/json/1.3/bom_services_simple.json index 1832c30a..a18e31cb 100644 --- a/tests/fixtures/json/1.3/bom_services_simple.json +++ b/tests/fixtures/json/1.3/bom_services_simple.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:e3ad515c-a48a-44d8-a17f-ed11e6a68a1a", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.086656+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -15,7 +15,7 @@ ], "component": { "type": "library", - "bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", + "bom-ref": "df70b5f1-8f53-47a4-be48-669ae78795e6", "name": "cyclonedx-python-lib", "version": "1.0.0" } @@ -32,8 +32,13 @@ ], "dependencies": [ { - "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", - "dependsOn": [] + "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda" + }, + { + "ref": "be2c6502-7e9a-47db-9a66-e34f729810a3" + }, + { + "ref": "df70b5f1-8f53-47a4-be48-669ae78795e6" } ] } diff --git a/tests/fixtures/json/1.3/bom_setuptools.json b/tests/fixtures/json/1.3/bom_setuptools.json index 6e618f73..33d6d399 100644 --- a/tests/fixtures/json/1.3/bom_setuptools.json +++ b/tests/fixtures/json/1.3/bom_setuptools.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:8d1060a2-afd0-4540-b145-9d936fe333e6", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:59.372749+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -31,8 +31,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.3/bom_setuptools_complete.json b/tests/fixtures/json/1.3/bom_setuptools_complete.json index a2e0ff95..d32d0200 100644 --- a/tests/fixtures/json/1.3/bom_setuptools_complete.json +++ b/tests/fixtures/json/1.3/bom_setuptools_complete.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:f62b017a-0fe0-447a-9919-067b3ea8993c", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.930153+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -24,14 +24,14 @@ "https://cyclonedx.org" ], "contact": [ + { + "name": "Paul Horton", + "email": "paul.horton@owasp.org" + }, { "name": "A N Other", "email": "someone@somewhere.tld", "phone": "+44 (0)1234 567890" - }, - { - "name": "Paul Horton", - "email": "paul.horton@owasp.org" } ] }, @@ -107,18 +107,18 @@ "bom-ref": "555ca729-93c6-48f3-956e-bdaa4a2f0bfa", "name": "toml", "version": "0.10.2", + "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "hashes": [ { "alg": "SHA-256", "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ], - "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", "comment": "No comment", - "type": "distribution", "hashes": [ { "alg": "SHA-256", @@ -130,36 +130,23 @@ } ], "variants": [ - { - "type": "library", - "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", - "author": "Test Author", - "name": "setuptools", - "version": "50.3.2", - "licenses": [ - { - "expression": "MIT License" - } - ], - "purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" - }, { "type": "library", "bom-ref": "e7abdcca-5ba2-4f29-b2cf-b1e1ef788e66", "name": "toml", "version": "0.10.2", + "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "hashes": [ { "alg": "SHA-256", "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ], - "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", "comment": "No comment", - "type": "distribution", "hashes": [ { "alg": "SHA-256", @@ -168,6 +155,19 @@ ] } ] + }, + { + "type": "library", + "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", + "author": "Test Author", + "name": "setuptools", + "version": "50.3.2", + "licenses": [ + { + "expression": "MIT License" + } + ], + "purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ], "commits": [ @@ -185,9 +185,9 @@ }, "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", "comment": "No comment", - "type": "distribution", "hashes": [ { "alg": "SHA-256", @@ -196,16 +196,6 @@ ] } ], - "properties": [ - { - "name": "key1", - "value": "val1" - }, - { - "name": "key2", - "value": "val2" - } - ], "components": [ { "type": "library", @@ -225,18 +215,18 @@ "bom-ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", "name": "toml", "version": "0.10.2", + "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "hashes": [ { "alg": "SHA-256", "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ], - "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", "comment": "No comment", - "type": "distribution", "hashes": [ { "alg": "SHA-256", @@ -256,21 +246,22 @@ "text": "Commercial 2" } ] - } + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ] } ], "dependencies": [ { - "ref": "df70b5f1-8f53-47a4-be48-669ae78795e6", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "df70b5f1-8f53-47a4-be48-669ae78795e6" } ] } diff --git a/tests/fixtures/json/1.3/bom_setuptools_no_version.json b/tests/fixtures/json/1.3/bom_setuptools_no_version.json index 6d58ff3a..c59f067e 100644 --- a/tests/fixtures/json/1.3/bom_setuptools_no_version.json +++ b/tests/fixtures/json/1.3/bom_setuptools_no_version.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:3e6b7d74-47af-41c2-b716-aefdc229aa23", + "serialNumber": "urn:uuid:77d15ab9-5602-4cca-8ed2-59ae579aafd3", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.008536+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -31,8 +31,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.3/bom_setuptools_with_cpe.json b/tests/fixtures/json/1.3/bom_setuptools_with_cpe.json index a36c2660..baf80bcc 100644 --- a/tests/fixtures/json/1.3/bom_setuptools_with_cpe.json +++ b/tests/fixtures/json/1.3/bom_setuptools_with_cpe.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:bdec9cb1-c5d2-4c5e-ab73-98ed27724e8b", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:59.396073+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -32,8 +32,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.3/bom_toml_1.json b/tests/fixtures/json/1.3/bom_toml_1.json index 9082d213..2ec92274 100644 --- a/tests/fixtures/json/1.3/bom_toml_1.json +++ b/tests/fixtures/json/1.3/bom_toml_1.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:71dc0b05-ae55-4384-8469-1a7755874d6a", + "serialNumber": "urn:uuid:6f266d1c-760f-4552-ae3b-41a9b74232fa", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.806505+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -44,8 +44,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.3/bom_with_full_metadata.json b/tests/fixtures/json/1.3/bom_with_full_metadata.json index 2cd19999..9cd5271c 100644 --- a/tests/fixtures/json/1.3/bom_with_full_metadata.json +++ b/tests/fixtures/json/1.3/bom_with_full_metadata.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:7e20a198-96d1-4b79-8207-56a34598c9f1", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.110075+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -323,8 +323,7 @@ }, "dependencies": [ { - "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", - "dependsOn": [] + "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda" } ] } diff --git a/tests/fixtures/json/1.4/bom_dependencies.json b/tests/fixtures/json/1.4/bom_dependencies.json index d87f3f8d..02c34960 100644 --- a/tests/fixtures/json/1.4/bom_dependencies.json +++ b/tests/fixtures/json/1.4/bom_dependencies.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:93b888f9-e5a3-4e72-85ac-12dae1fb1ba8", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.745228+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -97,8 +97,7 @@ ] }, { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.4/bom_dependencies_component.json b/tests/fixtures/json/1.4/bom_dependencies_component.json index 00391098..df8cc3b7 100644 --- a/tests/fixtures/json/1.4/bom_dependencies_component.json +++ b/tests/fixtures/json/1.4/bom_dependencies_component.json @@ -97,8 +97,7 @@ ] }, { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.4/bom_external_references.json b/tests/fixtures/json/1.4/bom_external_references.json index a8d27b08..2f961c7b 100644 --- a/tests/fixtures/json/1.4/bom_external_references.json +++ b/tests/fixtures/json/1.4/bom_external_references.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:5430666a-eb1a-484e-a7fa-2fbd8c9e84ef", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:57.516433+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", diff --git a/tests/fixtures/json/1.4/bom_issue_275_components.json b/tests/fixtures/json/1.4/bom_issue_275_components.json index f59a3e76..6826b7a1 100644 --- a/tests/fixtures/json/1.4/bom_issue_275_components.json +++ b/tests/fixtures/json/1.4/bom_issue_275_components.json @@ -85,8 +85,7 @@ ] }, { - "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", - "dependsOn": [] + "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda" }, { "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", @@ -95,8 +94,7 @@ ] }, { - "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789", - "dependsOn": [] + "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789" } ] } diff --git a/tests/fixtures/json/1.4/bom_services_complex.json b/tests/fixtures/json/1.4/bom_services_complex.json index 7db75c3f..d4d80b06 100644 --- a/tests/fixtures/json/1.4/bom_services_complex.json +++ b/tests/fixtures/json/1.4/bom_services_complex.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:8b7d855f-b826-4548-9cba-4c018b9afd7c", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.980752+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -49,7 +49,7 @@ ], "component": { "type": "library", - "bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", + "bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655", "name": "cyclonedx-python-lib", "version": "1.0.0" } @@ -185,8 +185,13 @@ ], "dependencies": [ { - "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda", - "dependsOn": [] + "ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655" + }, + { + "ref": "my-specific-bom-ref-for-my-first-service" + }, + { + "ref": "be2c6502-7e9a-47db-9a66-e34f729810a3" } ] } diff --git a/tests/fixtures/json/1.4/bom_services_nested.json b/tests/fixtures/json/1.4/bom_services_nested.json index dad44b78..3c674682 100644 --- a/tests/fixtures/json/1.4/bom_services_nested.json +++ b/tests/fixtures/json/1.4/bom_services_nested.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:b743da1e-8e6d-464d-bc2e-2295d136c9f4", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:59.022478+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -245,8 +245,13 @@ ], "dependencies": [ { - "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789", - "dependsOn": [] + "ref": "my-specific-bom-ref-for-my-first-service" + }, + { + "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857" + }, + { + "ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789" } ] } diff --git a/tests/fixtures/json/1.4/bom_services_simple.json b/tests/fixtures/json/1.4/bom_services_simple.json index bc78605d..38c71422 100644 --- a/tests/fixtures/json/1.4/bom_services_simple.json +++ b/tests/fixtures/json/1.4/bom_services_simple.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:fece92d9-75d7-4e94-ad5e-5cc18a621b47", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:59.063187+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -49,7 +49,7 @@ ], "component": { "type": "library", - "bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", + "bom-ref": "df70b5f1-8f53-47a4-be48-669ae78795e6", "name": "cyclonedx-python-lib", "version": "1.0.0" } @@ -66,8 +66,13 @@ ], "dependencies": [ { - "ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857", - "dependsOn": [] + "ref": "df70b5f1-8f53-47a4-be48-669ae78795e6" + }, + { + "ref": "be2c6502-7e9a-47db-9a66-e34f729810a3" + }, + { + "ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda" } ] } diff --git a/tests/fixtures/json/1.4/bom_setuptools.json b/tests/fixtures/json/1.4/bom_setuptools.json index 750da385..4fd30cac 100644 --- a/tests/fixtures/json/1.4/bom_setuptools.json +++ b/tests/fixtures/json/1.4/bom_setuptools.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:cf734a42-cebf-41ac-abd8-2a8112771abc", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:59.418893+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -65,8 +65,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.4/bom_setuptools_complete.json b/tests/fixtures/json/1.4/bom_setuptools_complete.json index be0d4769..ceb5903c 100644 --- a/tests/fixtures/json/1.4/bom_setuptools_complete.json +++ b/tests/fixtures/json/1.4/bom_setuptools_complete.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:abd61d3b-997c-47ce-83ae-b814af957b8a", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.823105+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -13,36 +13,36 @@ "version": "TESTING", "externalReferences": [ { - "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions", - "type": "build-system" + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions" }, { - "url": "https://pypi.org/project/cyclonedx-python-lib/", - "type": "distribution" + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-python-lib/" }, { - "url": "https://cyclonedx.github.io/cyclonedx-python-lib/", - "type": "documentation" + "type": "documentation", + "url": "https://cyclonedx.github.io/cyclonedx-python-lib/" }, { - "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues", - "type": "issue-tracker" + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues" }, { - "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE", - "type": "license" + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE" }, { - "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md", - "type": "release-notes" + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md" }, { - "url": "https://github.com/CycloneDX/cyclonedx-python-lib", - "type": "vcs" + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib" }, { - "url": "https://cyclonedx.org", - "type": "website" + "type": "website", + "url": "https://cyclonedx.org" } ] } @@ -58,14 +58,14 @@ "https://cyclonedx.org" ], "contact": [ + { + "name": "Paul Horton", + "email": "paul.horton@owasp.org" + }, { "name": "A N Other", "email": "someone@somewhere.tld", "phone": "+44 (0)1234 567890" - }, - { - "name": "Paul Horton", - "email": "paul.horton@owasp.org" } ] }, @@ -139,18 +139,18 @@ "bom-ref": "555ca729-93c6-48f3-956e-bdaa4a2f0bfa", "name": "toml", "version": "0.10.2", + "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "hashes": [ { "alg": "SHA-256", "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ], - "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", "comment": "No comment", - "type": "distribution", "hashes": [ { "alg": "SHA-256", @@ -162,36 +162,23 @@ } ], "variants": [ - { - "type": "library", - "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", - "author": "Test Author", - "name": "setuptools", - "version": "50.3.2", - "licenses": [ - { - "expression": "MIT License" - } - ], - "purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" - }, { "type": "library", "bom-ref": "e7abdcca-5ba2-4f29-b2cf-b1e1ef788e66", "name": "toml", "version": "0.10.2", + "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "hashes": [ { "alg": "SHA-256", "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ], - "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", "comment": "No comment", - "type": "distribution", "hashes": [ { "alg": "SHA-256", @@ -200,6 +187,19 @@ ] } ] + }, + { + "type": "library", + "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", + "author": "Test Author", + "name": "setuptools", + "version": "50.3.2", + "licenses": [ + { + "expression": "MIT License" + } + ], + "purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ], "commits": [ @@ -217,9 +217,9 @@ }, "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", "comment": "No comment", - "type": "distribution", "hashes": [ { "alg": "SHA-256", @@ -228,16 +228,6 @@ ] } ], - "properties": [ - { - "name": "key1", - "value": "val1" - }, - { - "name": "key2", - "value": "val2" - } - ], "components": [ { "type": "library", @@ -257,18 +247,18 @@ "bom-ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", "name": "toml", "version": "0.10.2", + "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "hashes": [ { "alg": "SHA-256", "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ], - "purl": "pkg:pypi/toml@0.10.2?extension=tar.gz", "externalReferences": [ { + "type": "distribution", "url": "https://cyclonedx.org", "comment": "No comment", - "type": "distribution", "hashes": [ { "alg": "SHA-256", @@ -300,8 +290,8 @@ "First Test Release" ], "tags": [ - "alpha", - "test" + "test", + "alpha" ], "resolves": [ { @@ -314,27 +304,27 @@ "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-44228" }, "references": [ - "https://central.sonatype.org/news/20211213_log4shell_help", - "https://logging.apache.org/log4j/2.x/security.html" + "https://logging.apache.org/log4j/2.x/security.html", + "https://central.sonatype.org/news/20211213_log4shell_help" ] } ], "notes": [ { + "locale": "en-GB", "text": { - "content": "U29tZSBzaW1wbGUgcGxhaW4gdGV4dA==", "contentType": "text/plain; charset=UTF-8", - "encoding": "base64" - }, - "locale": "en-GB" + "encoding": "base64", + "content": "U29tZSBzaW1wbGUgcGxhaW4gdGV4dA==" + } }, { + "locale": "en-US", "text": { - "content": "U29tZSBzaW1wbGUgcGxhaW4gdGV4dA==", "contentType": "text/plain; charset=UTF-8", - "encoding": "base64" - }, - "locale": "en-US" + "encoding": "base64", + "content": "U29tZSBzaW1wbGUgcGxhaW4gdGV4dA==" + } } ], "properties": [ @@ -347,21 +337,22 @@ "value": "val2" } ] - } + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ] } ], "dependencies": [ { - "ref": "df70b5f1-8f53-47a4-be48-669ae78795e6", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "df70b5f1-8f53-47a4-be48-669ae78795e6" } ] } diff --git a/tests/fixtures/json/1.4/bom_setuptools_no_version.json b/tests/fixtures/json/1.4/bom_setuptools_no_version.json index b82e1c4c..ea3bb5d6 100644 --- a/tests/fixtures/json/1.4/bom_setuptools_no_version.json +++ b/tests/fixtures/json/1.4/bom_setuptools_no_version.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:46d26437-5d99-4915-be3f-9fd53fcaa782", + "serialNumber": "urn:uuid:77d15ab9-5602-4cca-8ed2-59ae579aafd3", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.943672+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -64,8 +64,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.4/bom_setuptools_with_cpe.json b/tests/fixtures/json/1.4/bom_setuptools_with_cpe.json index 756388ba..7ad22406 100644 --- a/tests/fixtures/json/1.4/bom_setuptools_with_cpe.json +++ b/tests/fixtures/json/1.4/bom_setuptools_with_cpe.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:71991e19-af7c-4887-9a37-d8d759ecdd8b", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:59.453146+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -66,8 +66,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.4/bom_setuptools_with_release_notes.json b/tests/fixtures/json/1.4/bom_setuptools_with_release_notes.json index 103ac99b..492be373 100644 --- a/tests/fixtures/json/1.4/bom_setuptools_with_release_notes.json +++ b/tests/fixtures/json/1.4/bom_setuptools_with_release_notes.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:039a0707-158e-47b8-991d-e029ae986158", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.665046+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -124,8 +124,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.4/bom_setuptools_with_vulnerabilities.json b/tests/fixtures/json/1.4/bom_setuptools_with_vulnerabilities.json index 2fed6ab3..c7c0ed05 100644 --- a/tests/fixtures/json/1.4/bom_setuptools_with_vulnerabilities.json +++ b/tests/fixtures/json/1.4/bom_setuptools_with_vulnerabilities.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:313807bd-6f7c-4016-95d0-84c2273c3e65", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.703824+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -65,8 +65,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" } ], "vulnerabilities": [ diff --git a/tests/fixtures/json/1.4/bom_toml_1.json b/tests/fixtures/json/1.4/bom_toml_1.json index 985e07d8..80fe0ae3 100644 --- a/tests/fixtures/json/1.4/bom_toml_1.json +++ b/tests/fixtures/json/1.4/bom_toml_1.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:6b822a8f-bb36-47ff-a9e8-321a3a16a46a", + "serialNumber": "urn:uuid:6f266d1c-760f-4552-ae3b-41a9b74232fa", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:58.628816+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -78,8 +78,7 @@ ], "dependencies": [ { - "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz", - "dependsOn": [] + "ref": "pkg:pypi/toml@0.10.2?extension=tar.gz" } ] } diff --git a/tests/fixtures/json/1.4/bom_with_full_metadata.json b/tests/fixtures/json/1.4/bom_with_full_metadata.json index f96488cd..0fab3971 100644 --- a/tests/fixtures/json/1.4/bom_with_full_metadata.json +++ b/tests/fixtures/json/1.4/bom_with_full_metadata.json @@ -2,10 +2,10 @@ "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:cefd0380-8626-476c-ae2d-956079d6d4b2", + "serialNumber": "urn:uuid:d0b24ba4-102b-497e-b31f-4fdc3f0a3005", "version": 1, "metadata": { - "timestamp": "2023-01-07T13:45:59.098229+00:00", + "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ { "vendor": "CycloneDX", @@ -414,8 +414,7 @@ }, "dependencies": [ { - "ref": "be2c6502-7e9a-47db-9a66-e34f729810a3", - "dependsOn": [] + "ref": "be2c6502-7e9a-47db-9a66-e34f729810a3" } ] } diff --git a/tests/fixtures/xml/1.1/bom_dependencies.xml b/tests/fixtures/xml/1.1/bom_dependencies.xml index fc726b67..44cb1a8d 100644 --- a/tests/fixtures/xml/1.1/bom_dependencies.xml +++ b/tests/fixtures/xml/1.1/bom_dependencies.xml @@ -1,6 +1,6 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> setuptools diff --git a/tests/fixtures/xml/1.1/bom_empty.xml b/tests/fixtures/xml/1.1/bom_empty.xml index ac887711..07db9157 100644 --- a/tests/fixtures/xml/1.1/bom_empty.xml +++ b/tests/fixtures/xml/1.1/bom_empty.xml @@ -1,5 +1,5 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> diff --git a/tests/fixtures/xml/1.1/bom_external_references.xml b/tests/fixtures/xml/1.1/bom_external_references.xml index c585e109..1100a3e8 100644 --- a/tests/fixtures/xml/1.1/bom_external_references.xml +++ b/tests/fixtures/xml/1.1/bom_external_references.xml @@ -1,6 +1,6 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> diff --git a/tests/fixtures/xml/1.1/bom_issue_275_components.xml b/tests/fixtures/xml/1.1/bom_issue_275_components.xml index 2e4eda9a..19b2b357 100644 --- a/tests/fixtures/xml/1.1/bom_issue_275_components.xml +++ b/tests/fixtures/xml/1.1/bom_issue_275_components.xml @@ -1,6 +1,6 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> comp_a diff --git a/tests/fixtures/xml/1.1/bom_setuptools.xml b/tests/fixtures/xml/1.1/bom_setuptools.xml index 40768111..74c050f1 100644 --- a/tests/fixtures/xml/1.1/bom_setuptools.xml +++ b/tests/fixtures/xml/1.1/bom_setuptools.xml @@ -1,6 +1,6 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> setuptools diff --git a/tests/fixtures/xml/1.1/bom_setuptools_complete.xml b/tests/fixtures/xml/1.1/bom_setuptools_complete.xml index 6c81ae78..b18d1d36 100644 --- a/tests/fixtures/xml/1.1/bom_setuptools_complete.xml +++ b/tests/fixtures/xml/1.1/bom_setuptools_complete.xml @@ -1,6 +1,6 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> CycloneDX diff --git a/tests/fixtures/xml/1.1/bom_setuptools_no_version.xml b/tests/fixtures/xml/1.1/bom_setuptools_no_version.xml index 318ebf74..2a24f4bc 100644 --- a/tests/fixtures/xml/1.1/bom_setuptools_no_version.xml +++ b/tests/fixtures/xml/1.1/bom_setuptools_no_version.xml @@ -1,6 +1,6 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> setuptools diff --git a/tests/fixtures/xml/1.1/bom_setuptools_with_cpe.xml b/tests/fixtures/xml/1.1/bom_setuptools_with_cpe.xml index 72863e02..b8e9cf15 100644 --- a/tests/fixtures/xml/1.1/bom_setuptools_with_cpe.xml +++ b/tests/fixtures/xml/1.1/bom_setuptools_with_cpe.xml @@ -1,6 +1,6 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> setuptools diff --git a/tests/fixtures/xml/1.1/bom_toml_hashes_and_references.xml b/tests/fixtures/xml/1.1/bom_toml_hashes_and_references.xml index 6acafdba..4020f6fe 100644 --- a/tests/fixtures/xml/1.1/bom_toml_hashes_and_references.xml +++ b/tests/fixtures/xml/1.1/bom_toml_hashes_and_references.xml @@ -1,6 +1,6 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> toml diff --git a/tests/fixtures/xml/1.2/bom_dependencies.xml b/tests/fixtures/xml/1.2/bom_dependencies.xml index 3b6a85a7..a3a52dc7 100644 --- a/tests/fixtures/xml/1.2/bom_dependencies.xml +++ b/tests/fixtures/xml/1.2/bom_dependencies.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.654848+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.2/bom_dependencies_component.xml b/tests/fixtures/xml/1.2/bom_dependencies_component.xml index 6291cdaf..b301d92c 100644 --- a/tests/fixtures/xml/1.2/bom_dependencies_component.xml +++ b/tests/fixtures/xml/1.2/bom_dependencies_component.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.673033+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.2/bom_external_references.xml b/tests/fixtures/xml/1.2/bom_external_references.xml index f3551b6f..3187c7a9 100644 --- a/tests/fixtures/xml/1.2/bom_external_references.xml +++ b/tests/fixtures/xml/1.2/bom_external_references.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.285065+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -11,7 +11,6 @@ - https://cyclonedx.org diff --git a/tests/fixtures/xml/1.2/bom_issue_275_components.xml b/tests/fixtures/xml/1.2/bom_issue_275_components.xml index a38c0e38..2c0584bc 100644 --- a/tests/fixtures/xml/1.2/bom_issue_275_components.xml +++ b/tests/fixtures/xml/1.2/bom_issue_275_components.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.745732+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.2/bom_services_complex.xml b/tests/fixtures/xml/1.2/bom_services_complex.xml index d8833739..fb379dd2 100644 --- a/tests/fixtures/xml/1.2/bom_services_complex.xml +++ b/tests/fixtures/xml/1.2/bom_services_complex.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.798684+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -10,12 +10,11 @@ TESTING - + cyclonedx-python-lib 1.0.0 - @@ -59,6 +58,8 @@ - + + + diff --git a/tests/fixtures/xml/1.2/bom_services_nested.xml b/tests/fixtures/xml/1.2/bom_services_nested.xml index 9a559f1c..b3fce989 100644 --- a/tests/fixtures/xml/1.2/bom_services_nested.xml +++ b/tests/fixtures/xml/1.2/bom_services_nested.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.823724+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -10,12 +10,11 @@ TESTING - + cyclonedx-python-lib 1.0.0 - @@ -107,6 +106,8 @@ - + + + diff --git a/tests/fixtures/xml/1.2/bom_services_simple.xml b/tests/fixtures/xml/1.2/bom_services_simple.xml index 13a5766f..7595971a 100644 --- a/tests/fixtures/xml/1.2/bom_services_simple.xml +++ b/tests/fixtures/xml/1.2/bom_services_simple.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.859799+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -15,7 +15,6 @@ 1.0.0 - my-first-service @@ -26,5 +25,7 @@ + + diff --git a/tests/fixtures/xml/1.2/bom_setuptools.xml b/tests/fixtures/xml/1.2/bom_setuptools.xml index 7ac55d10..ffdfcc42 100644 --- a/tests/fixtures/xml/1.2/bom_setuptools.xml +++ b/tests/fixtures/xml/1.2/bom_setuptools.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.885112+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.2/bom_setuptools_complete.xml b/tests/fixtures/xml/1.2/bom_setuptools_complete.xml index cbeeef0c..4a16c92c 100644 --- a/tests/fixtures/xml/1.2/bom_setuptools_complete.xml +++ b/tests/fixtures/xml/1.2/bom_setuptools_complete.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.698763+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -158,7 +158,5 @@ - - diff --git a/tests/fixtures/xml/1.2/bom_setuptools_no_version.xml b/tests/fixtures/xml/1.2/bom_setuptools_no_version.xml index 3b4b5d7c..c80352ac 100644 --- a/tests/fixtures/xml/1.2/bom_setuptools_no_version.xml +++ b/tests/fixtures/xml/1.2/bom_setuptools_no_version.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.781786+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.2/bom_setuptools_with_cpe.xml b/tests/fixtures/xml/1.2/bom_setuptools_with_cpe.xml index db0d140c..f143d86e 100644 --- a/tests/fixtures/xml/1.2/bom_setuptools_with_cpe.xml +++ b/tests/fixtures/xml/1.2/bom_setuptools_with_cpe.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.914199+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.2/bom_toml_hashes_and_references.xml b/tests/fixtures/xml/1.2/bom_toml_hashes_and_references.xml index f92fe5b7..30cf691b 100644 --- a/tests/fixtures/xml/1.2/bom_toml_hashes_and_references.xml +++ b/tests/fixtures/xml/1.2/bom_toml_hashes_and_references.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.605762+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.2/bom_with_full_metadata.xml b/tests/fixtures/xml/1.2/bom_with_full_metadata.xml index ba2e2fb5..056af173 100644 --- a/tests/fixtures/xml/1.2/bom_with_full_metadata.xml +++ b/tests/fixtures/xml/1.2/bom_with_full_metadata.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.875779+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -187,7 +187,6 @@ - diff --git a/tests/fixtures/xml/1.3/bom_dependencies.xml b/tests/fixtures/xml/1.3/bom_dependencies.xml index ea22a3bc..3685ff84 100644 --- a/tests/fixtures/xml/1.3/bom_dependencies.xml +++ b/tests/fixtures/xml/1.3/bom_dependencies.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.988863+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.3/bom_dependencies_component.xml b/tests/fixtures/xml/1.3/bom_dependencies_component.xml index e131bb71..bbe61960 100644 --- a/tests/fixtures/xml/1.3/bom_dependencies_component.xml +++ b/tests/fixtures/xml/1.3/bom_dependencies_component.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.008960+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.3/bom_external_references.xml b/tests/fixtures/xml/1.3/bom_external_references.xml index 93cf36ef..443d36cc 100644 --- a/tests/fixtures/xml/1.3/bom_external_references.xml +++ b/tests/fixtures/xml/1.3/bom_external_references.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.298511+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -11,7 +11,6 @@ - https://cyclonedx.org diff --git a/tests/fixtures/xml/1.3/bom_issue_275_components.xml b/tests/fixtures/xml/1.3/bom_issue_275_components.xml index 2e49462f..d41ae90b 100644 --- a/tests/fixtures/xml/1.3/bom_issue_275_components.xml +++ b/tests/fixtures/xml/1.3/bom_issue_275_components.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.091353+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.3/bom_services_complex.xml b/tests/fixtures/xml/1.3/bom_services_complex.xml index fa5af8fd..f62058f0 100644 --- a/tests/fixtures/xml/1.3/bom_services_complex.xml +++ b/tests/fixtures/xml/1.3/bom_services_complex.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.146707+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -10,12 +10,11 @@ TESTING - + cyclonedx-python-lib 1.0.0 - @@ -66,6 +65,8 @@ - + + + diff --git a/tests/fixtures/xml/1.3/bom_services_nested.xml b/tests/fixtures/xml/1.3/bom_services_nested.xml index 1239cd7f..ccb0684d 100644 --- a/tests/fixtures/xml/1.3/bom_services_nested.xml +++ b/tests/fixtures/xml/1.3/bom_services_nested.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.173114+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -10,12 +10,11 @@ TESTING - + cyclonedx-python-lib 1.0.0 - @@ -114,6 +113,8 @@ - + + + diff --git a/tests/fixtures/xml/1.3/bom_services_simple.xml b/tests/fixtures/xml/1.3/bom_services_simple.xml index f451f5b3..7eaaaa6a 100644 --- a/tests/fixtures/xml/1.3/bom_services_simple.xml +++ b/tests/fixtures/xml/1.3/bom_services_simple.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.210725+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -15,7 +15,6 @@ 1.0.0 - my-first-service @@ -26,5 +25,7 @@ + + diff --git a/tests/fixtures/xml/1.3/bom_setuptools.xml b/tests/fixtures/xml/1.3/bom_setuptools.xml index 8ce2b636..dbe2a157 100644 --- a/tests/fixtures/xml/1.3/bom_setuptools.xml +++ b/tests/fixtures/xml/1.3/bom_setuptools.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.942098+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.3/bom_setuptools_complete.xml b/tests/fixtures/xml/1.3/bom_setuptools_complete.xml index 392e51b8..f9ac65f0 100644 --- a/tests/fixtures/xml/1.3/bom_setuptools_complete.xml +++ b/tests/fixtures/xml/1.3/bom_setuptools_complete.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.040067+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -180,7 +180,5 @@ - - diff --git a/tests/fixtures/xml/1.3/bom_setuptools_no_version.xml b/tests/fixtures/xml/1.3/bom_setuptools_no_version.xml index 353786fe..9fe15036 100644 --- a/tests/fixtures/xml/1.3/bom_setuptools_no_version.xml +++ b/tests/fixtures/xml/1.3/bom_setuptools_no_version.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.129181+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.3/bom_setuptools_with_cpe.xml b/tests/fixtures/xml/1.3/bom_setuptools_with_cpe.xml index da7166ec..27abd9b2 100644 --- a/tests/fixtures/xml/1.3/bom_setuptools_with_cpe.xml +++ b/tests/fixtures/xml/1.3/bom_setuptools_with_cpe.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.957019+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.3/bom_toml_hashes_and_references.xml b/tests/fixtures/xml/1.3/bom_toml_hashes_and_references.xml index 143a6145..3e3c02bc 100644 --- a/tests/fixtures/xml/1.3/bom_toml_hashes_and_references.xml +++ b/tests/fixtures/xml/1.3/bom_toml_hashes_and_references.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:32.931440+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.3/bom_with_full_metadata.xml b/tests/fixtures/xml/1.3/bom_with_full_metadata.xml index 5ba4a170..434dd66c 100644 --- a/tests/fixtures/xml/1.3/bom_with_full_metadata.xml +++ b/tests/fixtures/xml/1.3/bom_with_full_metadata.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.228062+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -220,7 +220,6 @@ val2 - diff --git a/tests/fixtures/xml/1.4/bom_dependencies.xml b/tests/fixtures/xml/1.4/bom_dependencies.xml index ec440c4c..ecdb150c 100644 --- a/tests/fixtures/xml/1.4/bom_dependencies.xml +++ b/tests/fixtures/xml/1.4/bom_dependencies.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.391540+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_dependencies_component.xml b/tests/fixtures/xml/1.4/bom_dependencies_component.xml index c65b7a7d..0aadc91a 100644 --- a/tests/fixtures/xml/1.4/bom_dependencies_component.xml +++ b/tests/fixtures/xml/1.4/bom_dependencies_component.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.419505+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_external_references.xml b/tests/fixtures/xml/1.4/bom_external_references.xml index 71ac84b7..146497d3 100644 --- a/tests/fixtures/xml/1.4/bom_external_references.xml +++ b/tests/fixtures/xml/1.4/bom_external_references.xml @@ -1,6 +1,5 @@ - + 2023-01-07T13:44:32.312678+00:00 @@ -37,7 +36,6 @@ - https://cyclonedx.org diff --git a/tests/fixtures/xml/1.4/bom_issue_275_components.xml b/tests/fixtures/xml/1.4/bom_issue_275_components.xml index 68bd0708..7d6924a8 100644 --- a/tests/fixtures/xml/1.4/bom_issue_275_components.xml +++ b/tests/fixtures/xml/1.4/bom_issue_275_components.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.518584+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_issue_328_components.xml b/tests/fixtures/xml/1.4/bom_issue_328_components.xml index fed09292..004ce30d 100644 --- a/tests/fixtures/xml/1.4/bom_issue_328_components.xml +++ b/tests/fixtures/xml/1.4/bom_issue_328_components.xml @@ -1,8 +1,7 @@ - + - 2023-01-07T13:52:32.630121+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_services_complex.xml b/tests/fixtures/xml/1.4/bom_services_complex.xml index 3c951436..03d2c460 100644 --- a/tests/fixtures/xml/1.4/bom_services_complex.xml +++ b/tests/fixtures/xml/1.4/bom_services_complex.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.611387+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -36,12 +36,11 @@ - + cyclonedx-python-lib 1.0.0 - @@ -136,6 +135,8 @@ - + + + diff --git a/tests/fixtures/xml/1.4/bom_services_nested.xml b/tests/fixtures/xml/1.4/bom_services_nested.xml index 31b1f256..a0cc89ec 100644 --- a/tests/fixtures/xml/1.4/bom_services_nested.xml +++ b/tests/fixtures/xml/1.4/bom_services_nested.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.660295+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -36,12 +36,11 @@ - + cyclonedx-python-lib 1.0.0 - @@ -184,6 +183,8 @@ - + + + diff --git a/tests/fixtures/xml/1.4/bom_services_simple.xml b/tests/fixtures/xml/1.4/bom_services_simple.xml index 228e1fc0..a8c80c9e 100644 --- a/tests/fixtures/xml/1.4/bom_services_simple.xml +++ b/tests/fixtures/xml/1.4/bom_services_simple.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.720566+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -41,7 +41,6 @@ 1.0.0 - my-first-service @@ -51,6 +50,8 @@ - + + + diff --git a/tests/fixtures/xml/1.4/bom_setuptools.xml b/tests/fixtures/xml/1.4/bom_setuptools.xml index 6f47fbc7..07a313dd 100644 --- a/tests/fixtures/xml/1.4/bom_setuptools.xml +++ b/tests/fixtures/xml/1.4/bom_setuptools.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.971407+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_setuptools_complete.xml b/tests/fixtures/xml/1.4/bom_setuptools_complete.xml index e4de56a2..61ba8689 100644 --- a/tests/fixtures/xml/1.4/bom_setuptools_complete.xml +++ b/tests/fixtures/xml/1.4/bom_setuptools_complete.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.453106+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -248,7 +248,5 @@ - - diff --git a/tests/fixtures/xml/1.4/bom_setuptools_no_version.xml b/tests/fixtures/xml/1.4/bom_setuptools_no_version.xml index aefbc82b..73becdb0 100644 --- a/tests/fixtures/xml/1.4/bom_setuptools_no_version.xml +++ b/tests/fixtures/xml/1.4/bom_setuptools_no_version.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.581759+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_setuptools_with_cpe.xml b/tests/fixtures/xml/1.4/bom_setuptools_with_cpe.xml index 862f0a25..db986987 100644 --- a/tests/fixtures/xml/1.4/bom_setuptools_with_cpe.xml +++ b/tests/fixtures/xml/1.4/bom_setuptools_with_cpe.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.994810+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_setuptools_with_release_notes.xml b/tests/fixtures/xml/1.4/bom_setuptools_with_release_notes.xml index 7ee61b6f..5467da42 100644 --- a/tests/fixtures/xml/1.4/bom_setuptools_with_release_notes.xml +++ b/tests/fixtures/xml/1.4/bom_setuptools_with_release_notes.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.315062+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_setuptools_with_vulnerabilities.xml b/tests/fixtures/xml/1.4/bom_setuptools_with_vulnerabilities.xml index 4569c4a0..1936e286 100644 --- a/tests/fixtures/xml/1.4/bom_setuptools_with_vulnerabilities.xml +++ b/tests/fixtures/xml/1.4/bom_setuptools_with_vulnerabilities.xml @@ -1,8 +1,7 @@ - + - 2023-01-07T13:44:33.347677+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_toml_hashes_and_references.xml b/tests/fixtures/xml/1.4/bom_toml_hashes_and_references.xml index 93bda888..3ead1df9 100644 --- a/tests/fixtures/xml/1.4/bom_toml_hashes_and_references.xml +++ b/tests/fixtures/xml/1.4/bom_toml_hashes_and_references.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.290531+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX diff --git a/tests/fixtures/xml/1.4/bom_with_full_metadata.xml b/tests/fixtures/xml/1.4/bom_with_full_metadata.xml index b63471b8..24ee7aa0 100644 --- a/tests/fixtures/xml/1.4/bom_with_full_metadata.xml +++ b/tests/fixtures/xml/1.4/bom_with_full_metadata.xml @@ -1,8 +1,8 @@ + serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"> - 2023-01-07T13:44:33.750673+00:00 + 2023-01-07T13:44:32.312678+00:00 CycloneDX @@ -288,7 +288,6 @@ val2 - diff --git a/tests/fixtures/xml/1.4/webgoat-6.1.xml b/tests/fixtures/xml/1.4/webgoat-6.1.xml new file mode 100644 index 00000000..c7742062 --- /dev/null +++ b/tests/fixtures/xml/1.4/webgoat-6.1.xml @@ -0,0 +1,1359 @@ + + + + 2022-10-17T17:26:52Z + + + OWASP Foundation + CycloneDX Maven plugin + 2.7.2 + + f4f0e5aa6c0d5f709b304511547647a6 + 13a77f9af6a286c9bfc8c00e5d75ff6e9920d4ce + d565f805cfd5e57af6a345e537e78b42ffd56fad094c74443e2a197a4e097538 + 92ff49fbb954b0341c38daddb30965fd9e8bb909e7e8a66dc65da5d6a958d021c6fe611c0b12314ae02f849de95101efe5b93470050dfb8090a459481ffa736a + e15a47303e035e6612c37f08ebc105674f84cafd779ab52398ce263e67ce93d9d5e4c7c2ddda2f73584b6071a9f1a9da + 8c54133eb43f71bcdc52986159725cd32efef65047f8635a48d160a2d5f274f71c76e61cecbf3d47e3ca7929c3dbe736 + 7be4af15b7a685431c5252752c34b509c0d43a146db6844da9afbed2e4baa908 + 0025064deb5d9eb463a142bc4129c41f3387b18bd7aa54e5c923ed2c2f488616d198a65e952f7f2a1dea371387235f135c29643fb00f95366f7f6007d6327a31 + + + + + WebGoat + WebGoat + 6.0.1 + + pkg:maven/WebGoat/WebGoat@6.0.1?type=war + + + + + com.h2database + h2 + 1.4.187 + optional + + 42f0183908cb80996a5e145d04ddb608 + f6b109788d196430458309ea28ce9a77830b3ed8 + 6204d0c206443681911fb9e04a3af5198b253b5627d16d8d8d79180d13319212 + e7510fa72995ecf93e8927163d6bd864a8e9b4b1dc413f824de97a4e99bd682b278be6c2409e7a20a001fa0e10f5933332e720d003dff4f6395550a14c69d2dc + a8f885316d4ac5c56d159f6673a26102593b3df13acc6245b9067820c625bd8dea622b7af7138d2e0529405b9087148b + c4af674cffc629048338d4aa3abfa19a9323547a0d6078c60e365d3c9ba73d2a56ea8acaae797225818ecfc52211b822 + 48cd3d1053040efd9b1d67e2475cff7229a4ce065ccb1771838721baa78636d8 + 89d198a3f9976bf774d35ea25a284e3849bbbc711dc010ecd29cbef6dfe548f81081a8381fcbc447121e4ca8830ec186d59cb8808c195ae3c6e7cdc546c6818f + + + pkg:maven/com.h2database/h2@1.4.187?type=jar + + + javax.activation + activation + 1.1 + JavaBeans Activation Framework (JAF) is a standard extension to the Java platform that lets you take advantage of standard services to: determine the type of an arbitrary piece of data; encapsulate access to it; discover the operations available on it; and instantiate the appropriate bean to perform the operation(s). + optional + + 8ae38e87cd4f86059c0294a8fe3e0b18 + e6cb541461c2834bdea3eb920f1884d1eb508b50 + 2881c79c9d6ef01c58e62beea13e9d1ac8b8baa16f2fc198ad6e6776defdcdd3 + c0ff5bf3ace7acc1b31fcc109cee48d9eb8f025ae15a31dc91eca760933bdb97c93f05d61e95af1e317859d72e5f179f897f5bf3df0e3810f4212d43bacee4bd + c4ee54d80a2e67e819700051d6cfa01a17631c89f942b8690afb601e491f02d7497fe57bd5c70edfb9b444ae8222b846 + de0777d2d1d7aad105defb12aed17ef38abfe89db2449c5243fa3c69304ea24dd8df0881330351d0733313e8f7252814 + 5fb94d2742cc3d44abad42c5d61b9c7464a2ef33bc58b4b5b121d49799123460 + c5e37fe3d9c420a9035f1160eb1d396e94f01851c01c6e2f19f19a221bfc484e63f9660c7377f58aa65246b95a9eb799ac4e6798c0b20f658edf00a4435e1efa + + + + CDDL-1.0 + + + pkg:maven/javax.activation/activation@1.1?type=jar + https://maven-repository.dev.java.net/nonav/repository/javax.activation/jars/activation-1.1.jar + + + axis + axis + 1.2 + optional + + 8be4d95595f893b44d7b0431ab3976eb + 892c772f7c486b3c09d20f7259fb4219bfff9edf + 0dcb9346e17dafe62c52261641c6969c320b43086bec88039930ece90f16acf4 + 3157807ea0950fb895be4bf32df234b69fef8f1430cb526ac2e70c8823112c474d2be2ddef8811b6052f1c0dea3856d56aa68b4476d344ad48be4f234624ddb2 + 8445a9beb4fe8d441aeb90311c1d52dd880446b630d3e79775dc917d39e077d5d34c0f30b1c01a591a31a7a4ff8f77e8 + 04da3013a180fd53244cfc291b8f7cd6b6ce3c3d00285ddd9830d8b96586ab2de5d8fb4b2bf45a3168d411a37a6f2eda + 8f89b6c49d6022ae6993c63002625b7111a18daf888a4b0838cc5a279fa7fdd6 + 9228a173468173088ef999fa8adb16298649443ec82d0531cfff25ea2c691e33729332b3a3eec2601390c7143c6f38b98ef35e37269e308ace68aa283dcef224 + + + pkg:maven/axis/axis@1.2?type=jar + + + axis + axis-saaj + 1.2 + optional + + 4d89989419d289b2cc7ae4b1e8b9149b + e5681df2b55d3e6f4795e988b632e38985677f5a + 52dedcddcba9d1d48fca6d9a2f95dbb2164d2e8acd15a7af5b895743961ac47a + ee8f4b4359f0dfd4cbd9ffe84aa9cae2fd1fe19afca3534c510e3c8d6acf3b4a7970b5ab25a795841b71c95f91f19a167fcdc354b3cf23a337619f31f3faf95c + 5af565e8292ab0a7a7bdb45149ce60ef41c386e489fb76da2f6a703b8d56b043d7b76d04c61b748e0519f1350efc2e06 + 14c91830a69f95665fbed22edf81e47f2780a7c39e4920f2306f6ce4d7ca9848be14ad3a54760fa3b23cbff263d4eb19 + 2b5156637465246e1911c1455f5344b3694d395ddeb43231c64ffca44a29ef98 + ac1e437c6d7562ad92292ac6e034644fa7f98b3b06c537f5ce77e2ff9c8594031d3738880df8102599d7a9c38329848078bf474d8cf6ec4ed6ddb998bb98587e + + + pkg:maven/axis/axis-saaj@1.2?type=jar + + + axis + axis-jaxrpc + 1.2 + optional + + 9e0ebb5885cc0a0da4423dc67c66bef7 + 56b85e05daa39a9bb4101c8c6e425d5f1db2d724 + 96287de4c6a4d2e72e54e4256778f3afd22ea9a846e872e1c99c6a0b920398f2 + 0192a2028d5d06cb4370106014c1485433a06006741b572c6254b13932c2947dee9cb4a2d680ac8cf528a87e76c753c3fda1754178ba4973cc1eb5ab11d9f407 + e32f55f40fbf27974890039d4f83e4bf8798350ae5b49e2ea3e8f158784bffda0ae75cca4e0d522acc0ef4eef1823a95 + 289ff44e43490f70a82c93e5c868fc71ca2e09d21f35c87ad83b29d6beb13fa55ce7f59b8e2cc950a258acbf13eb40fc + 0bd5c87f74d3e546db0f5029af3f68937ee59eef6b4b1c212adb99608a9c3161 + 23cabe5f1bb301de9417a0f0abd9bc9612c5eeb412ddb9436f7183f08d08b7f242a1dd00134230300bc2806b918680651b590a482c569ba0d0d12792629fa177 + + + pkg:maven/axis/axis-jaxrpc@1.2?type=jar + + + axis + axis-ant + 1.2 + optional + + 03f709e12302cd50ee095a5412d481ed + f07ed3d1d32434426a42043196d7cd7b7a783b1f + db4b8abc94363aef55ec1e1b66106836b0a10ebfb534aed2997310d0439c7351 + b31e077a9aa501cd93e4cd9049cb2b509c552feab3a231e7a36505f92b3701cc4c4c2fe78766b0091f82b1e4d06828d674d1ce914ee7b7428602e7303fdf8be0 + 7dd3910a16900625e0681ba976e80fdf8f2b0af9600c5a8acc80f784ababbfaf6b20c259bf8f6e3804737e34b4580d58 + 5ce58aa2161d0622ff88c9dc5b62f413e400c4242d8415da5590471026aa6b8212503d62a6172f71982db2f73cceb4ff + c80a1366e0c0e70799097fb24d05cfa4fe8cff94fcc9c174dda6a6b1f67ce9c3 + f44a499acdc7e72eb0760a2ea9914b1fc9e754e710d56767c88546c722e5cbd64d646c5fdbd546c249c4e8db8e79e7c9beb35c280ae9d35ad81f91e88f486700 + + + pkg:maven/axis/axis-ant@1.2?type=jar + + + The Apache Software Foundation + org.apache.commons + commons-lang3 + 3.3.2 + Apache Commons Lang, a package of Java utility classes for the + classes that are in java.lang's hierarchy, or are considered to be so + standard as to justify existence in java.lang. + optional + + 3128bf75a2549ebe38663401191bacab + 90a3822c38ec8c996e84c16a3477ef632cbc87a3 + 6b81d10754dadf184d386011486e6509c2cc0c3d33565ced4fb4402b9413d47d + 3e6eb74b61c42d22de48c29e7e52057dd25d9f93857dc7bffc7052a56449f62153c487877d6a019adabbaee90e32085b104acc591ea9731839f8148dc4059e10 + 06bc46540d0b297f595c9625ff0b272a38faf9ebcaf822b687a4fc8bd7d01e4954cad866d54c4ae17cd787d61f852797 + b91600411b010ecde5ba763c1930f1858044904e01aa7088c42e95dcdbce010a73a79baed47284c1d8dc6918fb73571c + d78a4436d103be34ae2bd23c8cb78f504c77960559c57fc09b970d2b48a73e2b + 2716f57d20fb5bcd5ac6b0642575de79055d9e38b0836bdc3780eeee4bb5bb69538acd4f61adf82424f806fd4bd3629104880501314450047a9365b44622c3be + + + + Apache-2.0 + + + pkg:maven/org.apache.commons/commons-lang3@3.3.2?type=jar + http://issues.apache.org/jira/browse/LANGhttp://svn.apache.org/viewvc/commons/proper/lang/trunkhttp://vmbuild.apache.org/continuum/http://mail-archives.apache.org/mod_mbox/commons-user/http://www.apache.org/https://repository.apache.org/service/local/staging/deploy/maven2 + + + Apache Software Foundation + commons-collections + commons-collections + 3.1 + Types that extend and augment the Java Collections Framework. + optional + + d1dcb0fbee884bb855bb327b8190af36 + 40fb048097caeacdb11dbb33b5755854d89efdeb + c1547d185ba6880bcc2da261c5f7533512b6ffdbbc1898db5b793c0cb830fcf0 + 19bcfc52f095adbc8bd6b1b020b8392e8c91e41e05aa0099275f7b391fab51348f32052a1c556c814840a4ed4b6701f26012b05b3d9a243300a2616e8ece1e0a + 76549ee57b9dea4043f239af847fdff70e7251d741c0755a05fec715ed5baec5f757cf128cbafe2f98e1db6c8f52ddaa + f794ea96bf2c1a9a60b8ed4ae1bbb32e2c78db3851efa9ab21675fa625c87c1d35fcd64ee828a49e713ae26bbe3c12a7 + 7f487b838a66f077a703ac812c2d3179180b8202c026698933cabf5c0263a171 + d3008636448f980d8f762922cdf1c14f6adbce9a61437867eb1d3f0df274ba7babb8a818f69921bb33cc888c67bf20b4bf46aa8df364b65d1a1981415694624d + + + pkg:maven/commons-collections/commons-collections@3.1?type=jar + http://www.apache.org + + + commons-digester + commons-digester + 1.4.1 + The Digester package lets you configure an XML->Java object mapping module which triggers certain actions called rules whenever a particular pattern of nested XML elements is recognized. + optional + + 1f2a01f28ec53df6401f7dbcc6fcd884 + 51031e9c43ae47693c99b2f4ffe34ed01ef2ba91 + 3b35d6b867cf9c8d5dd8093c0a4e570261235d6cd9db99f2d75e53ac60fa3dcd + 58b187888e070ebbc47884df1e5f696c05e3bc762dc83d06d413413100cf29f703fee7b6afc5482c4e3e0a49807dd4af81a5cbe672ff8b892b54800463c25b7c + 1f23215470cebd166634e825e0200959968ac1b30777f86e255c71eae802564adf4d01dff541b00828abcb20da1bff7c + b364b4f22aa03279d07136bc118b06e4d06c3f6c4cef5083af7f26796355515bfcee91a222636bf1651938727ce31329 + 25cb177af667daef84bba6826d898e1901b90a9e679f8d35bd7c6b7bb33776d3 + 95d77217f6b00e8940ad75b1d248136a793d6445efebdb93aa88eeef21a3d58d16518eb904eff0d9261ead1c6d8c59786a115bbe3719b4442b522e8e4b6af6b4 + + + pkg:maven/commons-digester/commons-digester@1.4.1?type=jar + + + commons-beanutils + commons-beanutils + 1.6 + Java Bean Utililities + + 31a453fcfed4339bbd08e5dd85116b36 + ed3c2b07d1b16ec11440b6656fdbd4845ea6b8be + 77d8fe257bd9b186cce1261bea2364384ae861b1999815d549121710b0f89407 + 7719a94b20c8bf8a5bbd3c0fb276a0256efbbf610eea989cf1d89ddf7a83ff1e107f64813adea7a051d4f56db289b6b2ef7096cf197d1d81b351c9a8f85899cf + 57247cf1e4fbf01ae090adf23f711c9b507851b5736b53d01fa562cd845f5a7a2d4cf67193581699944e1e1255b4440e + 05d5c63679f741d205cf136200650241bf22323b48f835a458f1254f7ffd98af2d665694d89044591118339136de2ee4 + 0d3d642711035c8e7f19778563e0e923233dddc92f051375c0dba69da0c0bb79 + 468cc219294b38746bf295f8109e5afc965e8891fa12eee75e4c2f144a9c4f44dc1e9b477dfb3780bf365b74e0d575008fa61205867dfbf9980e09f2a59189db + + + pkg:maven/commons-beanutils/commons-beanutils@1.6?type=jar + + + The Apache Software Foundation + commons-logging + commons-logging + 1.1.3 + Commons Logging is a thin adapter allowing configurable bridging to other, + well known logging systems. + optional + + 92eb5aabc1b47287de53d45c086a435c + f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f + 70903f6fc82e9908c8da9f20443f61d90f0870a312642991fe8462a0b9391784 + e5d1fc8ec4544e1fa0f7c4aae8dbcca466c4987bc92fbbc430b054b10d646b745add4a754b1be9d50edd64330c798c53173a97289db57a966312e16f934e9d1f + 00dc41783f4081585e20adcfe83fd4dbb6b5a27236ee633d7e53dedcbca83ba5e5f0ca68d31f95e9c724b365e4c67499 + 6915e597821aa2fed7f4b05f4de3476364074ab5cee7e13b4eb5b94a9d297b58cb391c6b730e6ea0d4e81e323a90d002 + c49c80e2576bf037fc871e70df8708f5e35c0977a907e966c0334e9fac17b297 + b74b6e6d771005014614774d059a4e1ceab929c0678839846d143e56e4724455242ffa3d5d411c6554a41e1f66fe1f94c51472d80c07d44c02a2f9a6715d8c7d + + + + Apache-2.0 + + + pkg:maven/commons-logging/commons-logging@1.1.3?type=jar + http://issues.apache.org/jira/browse/LOGGINGhttp://svn.apache.org/repos/asf/commons/proper/logging/trunkhttp://vmbuild.apache.org/continuum/http://mail-archives.apache.org/mod_mbox/commons-user/http://www.apache.org/https://repository.apache.org/service/local/staging/deploy/maven2 + + + QOS.ch + org.slf4j + jcl-over-slf4j + 1.7.7 + JCL 1.1.1 implemented over SLF4J + optional + + 32ad130f946ef0460af416397b7fc7b7 + 56003dcd0a31deea6391b9e2ef2f2dc90b205a92 + c6472b5950e1c23202e567c6334e4832d1db46fad604b7a0d7af71d4a014bce2 + 6d1185bc6d5165a32331cbd5adff723309a253a15cacf4882e3bbdac60e2b3f41c990af5a561f89b8910af2029ba1fa0924147afb38a02acb0c02dcdbfb9b055 + 666b90ff0bd725f97e0ee12bf1d949ed4f8d56c12be85b8abfa713da60b4c0afeefe797ee464123ff59b57769b2caad6 + b4838a052ccf30848cc31fab117148ec65b7ad4c1808f115f346fe224f1c1f81c244b74dc16272cdd753d5f028c9137e + dac73cfc3b0dc158b94d4ec65b390568a17b261b380f38b45d88b48db8cd2c29 + 5fb7512776fd48ce146a650dbff500152174bd81535ab20c91d146a117217276ca7592e7b952fb41be1ffeb0dcebc1f46f65b19a5e527ac91feb8e3278a3343a + + + + MIT + https://opensource.org/licenses/MIT + + + pkg:maven/org.slf4j/jcl-over-slf4j@1.7.7?type=jar + http://www.qos.chhttps://oss.sonatype.org/service/local/staging/deploy/maven2/https://github.com/ceki/slf4j + + + commons-discovery + commons-discovery + 0.2 + Commons Discovery + optional + + 9a2fb56e6a79169b2fb6c8a0dc79abb0 + 7773ac7a7248f08ed2b8d297c6e2ef28260640ea + ae9995ec412cec2a3489a0787857791b9cc784f153b4c9d0ae93e38c5f2174fa + 897c045cb20f136b57f70aedb081956fb298c672e13d4d32eebcb2f109dc5c164d3516445b71c75a1ddaf4451375a6bfa258b0b3657b06f4123a78ce457859c1 + e892f27bc4a87b63ce2f09e296ddc965a5c0cf51fd680c0feef39f5aa7f1f0e694dfb4d4c5f42007cc1061fefaf9b1ef + 72fca4913fb5881a254f5f01af8ebc01eb58db97c8ae0210661e952921a41490ea8d422053a9ccada0d64ec882cc4ede + 6fa9e7946f657a74f806252fb1de6de82463dd86897cb1dbe8e58585226575a8 + 25897077ec613970071871eb123a315f8c3abcfb836b6e612c7492d9a8f6af455b52446c57d75a3a42a7591c52c34510eb0304993fe9159e93703dcefdb995c0 + + + pkg:maven/commons-discovery/commons-discovery@0.2?type=jar + + + Sun Microsystems, Inc. + javax.mail + mail + 1.4.2 + ${project.name} + optional + + 81e2cd97e84fb814dfd0018bb8782c81 + 6a1d836b6a4c77ec11ac46d2ea8557ca574cd428 + be03dd1caa2f93d7f75d06637ea11e4c1b1ea322a7afd057cbf8b08f87932987 + 56cbe3a80d50b33aec83dc8235386533977fb0b4af7e8c635e0f0e3c3dde9aaa3130ab0a4898a9079ebd6b08d84a5568bcaceaffc29258cff797cc255dd8b6cd + 02ab2f3a6580cdb69da748390ea5f7d631206ae83d5cf1d7f15c1137a7e6eb44dbda43fd75bc6c20ff17499c0c1459d9 + 66a8f804b20295f30e43eb8d1ac61677c34941dcaa7571f7179dfef1aee10731f051e71896751e6ba9790b776a6cef12 + 39ddf43b0929871a28131639780a8b164df83cb471e4f34885195d431c6271d2 + bca1a899dfee569be5d3190975a4d219b6e8a05d15852e58967effe71f5bd94a53f65674db6a28a1359bec55766db304f2557923fff0e2496f097ee45be10ee2 + + + + CDDL + http://www.sun.com/cddl + + + GPL-2.0-with-classpath-exception + + + pkg:maven/javax.mail/mail@1.4.2?type=jar + http://www.sun.com + + + Sun Microsystems, Inc. + javax.mail + mailapi + 1.4.2 + ${project.name} + optional + + 6d316a9c89f0c2a5531a6fb91edc17c7 + f5149f0aaf01daf4bb2f878c2803ce634dd32569 + 9bb2026c96650d113f3917af93da24436145b1ac808ffe0583f525eac5699ade + 1cfe829951fd2e75b2dfb765af647d247b983d707fdfc966e9a57cd461eb89096ba60691c618d2861daa6ea1399846f6553ae1f87a9408fb1c7d75eb879f1795 + 145a86b9f4653530acf3c47d912ecbca058815ae78eb6d5ee792cd61dcb38bd1811162a5f5de9f287b5d42a9984e9150 + 0fda8fa3284e4d444b824f8c61076c4937c25325a8e27a68018c604a68f36c919e805fd0c34d786c308d2ed4d18f56da + 42fdf922751c8a9f1eaddf66dcd0dd504986cc49e5059fa94a4b51896e61eb5f + b296d405e74532e900cf462ee693bb65e30e9afbc996aff128b5db27f29e43f48acb9e2034b073e0914db736f5a2176fdf2154915c2aa014731b5d8dbe5730c7 + + + + CDDL + http://www.sun.com/cddl + + + GPL-2.0-with-classpath-exception + + + pkg:maven/javax.mail/mailapi@1.4.2?type=jar + http://www.sun.com + + + hsqldb + hsqldb + 1.8.0.7 + Lightweight 100% Java SQL Database Engine + optional + + ed0ff8ff5ba782cd6c508d3599039e13 + 20554954120b3cc9f08804524ec90113a73f3015 + c57059259f92c34932a2a8285af239792e0011492f43ac16cdd74b713d86635f + 5e5dfb932081893f26cbfd8e1841c263e464c8e2431c2aad0cb98eb52a5da952cf2ae6a780ed6ce30cf31210d16e1f64751b2f7dd87eff1a0d2559e0098d1db4 + 4193a273b4ca5b4aa4a20224558c793c57d539d2efabb6342c8c27798bc292e4bf9cd6ec97eb6d5a93796362288366e9 + 10f434b8851999e762b960191688b130bd4b7828c122360aa3060b4a279e0c6e7f3fe167bed03dccb59168cf9d6d73bb + 2e295b04b235b5cdcdd70bec559bf5e20ef4c78740cf5b7c0dcd3d53c07a2789 + 72c14214a319791e290008dd8d4f6247191c56300b9a5516bfa4781f0bea91cf15a2555772b4e4a5f9199dc32bc4d35ce6dff77f4f0c1e6e727880e2e71fb377 + + + + HSQLDB License + http://hsqldb.org/web/hsqlLicense.html + + + pkg:maven/hsqldb/hsqldb@1.8.0.7?type=jar + http://sourceforge.net/cvs/?group_id=23316 + + + wsdl4j + wsdl4j + 1.5.1 + Java stub generator for WSDL + optional + + e76bf26b6955a7fa73d85d096be6e1a6 + bd804633b9c2cf06258641febc31a8ff3b0906bc + 7729134d666f27a18ac3c674c16014624376445ec8686bfa60ac648f48b1ece8 + f1301a9282defe37213116b8a9f36d0a5b66a4e99799d2caa37d1ac364a9480c7b0d89e93052691c5defdd69f7fdee3500466fe94ff0770c102f54da236bfc3f + e7fe4daedf0bcec37b32de1d715d2d8a0474b889fa5b66f9f7e89c0c31002a24e7d9f262868a8b8a0f8351f0f291376e + a0cb39f45247e95b106b9866cbcc98d023ff1884dfbeb319a791e33bb675e3e5ba373db55daa6b903636f281b3537fdb + ad5e5ee7371a7ea816a6f89323fd2b3be35a23091136c6c714b07041d19e2c0b + 207a5536c739700fc70513e1aa207516ae2d8a14953856e88b7dbf182f08ddfa687de0c872b3b5005528d8a6a196f824bb56838ef6b6b45ee02c25da6fc3c645 + + + pkg:maven/wsdl4j/wsdl4j@1.5.1?type=jar + + + java2html + j2h + 1.3.1 + The Java2HTML Tool + optional + + 00542e91fb1de915744d45967e5d6a28 + 18d393ad345b03b49c624069e2c7fde336372c3a + 1691231bbdf3d6671bdb0b236619e033e10664be423b2312f93c35dce742f9ad + d8b17879e17a5a772227ff6104492785786a2be4e92cc08d971f3aeaca64e369bd118ce4d2dd7e23ad2e6110ce99766cd81e01b95ad67fedc5fe17075e160880 + 95a3a9cc79d39fefa1707f199dd0265ae8896ef92dd905747ff8a3917ef2b19c5615f1a0d348fe55b8d2ff6d2e525eeb + 4c8a6f8ce03062f08ab155eb0435c89586040b490b257ce1f796e2a2a8024489555d5492df595071c978c4073bb6b765 + f9ffd74b6e2b4659f79f277a608e6b39f8f6a0bc257a83fa3fa7ad896d77255e + 7aa4b321d6710782bc04a9fe0e3b41d3f0f7104854b256072852f929847b110c0f3003ffe6260b4b4084c36988742c4f8c8af9aff396d5a536a58297de516f3e + + + pkg:maven/java2html/j2h@1.3.1?type=jar + + + ecs + ecs + 1.4.2 + optional + + 62d53be190ca9cbfe01bec9fc3396934 + f9bc5fdde56d60876c1785087ce2a301b4e4a676 + b7140e42af61601390a44bd80da35fd847e2363bfeaeccdd568ef9f2cd40f91c + 9082dfb73aee125a456be7ff676ee34767dc6a007a315f9c8c927cd94909fe107bca88d431d3ec61e91d8c1f61f9e46ff2d7b7cb94ed9b0ce1a21cdede612cdb + f554c0abbebbff5e00fd7e1cd8da05fc09c91ea52a8759c820dced8a4256b20335ef56e2de4d5eff2ba0c798481a9c79 + 978c710212174c5411a09728ea636245803e68873b5a86c57cfedaa35044a9d278969158a35534a22ccd0d5d188b2ccf + 033a45b4edf30bc6eb076e3dc4926df1c88617ebd105ea6893b6889f83e269bd + 38b87875d3f4b5682e173f7063cf619da48b51a318bc9d4717874eb702798bec54e125cc94bafcb5e554b2a2f0c3f8a873e77544dbcf35537432767cc9f2df23 + + + pkg:maven/ecs/ecs@1.4.2?type=jar + + + GlassFish Community + javax.transaction + javax.transaction-api + 1.2 + Project GlassFish Java Transaction API + optional + + 2dfee184286530e726ad155816e15b4c + d81aff979d603edd90dcd8db2abc1f4ce6479e3e + 9528449583c34d9d63aa1d8d15069790f925ae1f27b33784773b8099eff4c9ff + 3b99e73bb47a5cc5b6cec3ea40b9215f2cd35d48757e00f8dcf733dd7e1cf3c8f48dd8ee24993535675fe963ecce3f537261a273bbfdaab1be51cc1749ac60da + 766f33dc68259303d6e3b76d7fad84f32b39c81d9ab6d9342bfae614f56c37061ce116fbf4a38a93cabbfb889b0e2326 + 2f0183148a8895b4984b132d0f80fce29ac32a1f3ee638ed3c12ec5180566fc236ce51b346a98006c138aa1d8b5f43df + e7dedf2891cac473afa2cc4f4181b79cc77ec9cac092c9b58e2812e025dc6547 + 44772869812245f52d30faaa139cf033c3012d674061a6b62712ddccaaacee8b85871424935804b1cd78fa16e557fbdec0e4054b762a206b3a48d7f37a0a2f72 + + + pkg:maven/javax.transaction/javax.transaction-api@1.2?type=jar + https://glassfish.java.nethttp://java.net/jira/browse/JTA_SPEChttp://java.net/projects/glassfish/sources/svn/show/tags/javax.transaction-api-1.2https://maven.java.net/service/local/staging/deploy/maven2/ + + + net.sourceforge.jtds + jtds + 1.2.2 + TDS is an open source 100% pure Java (type 4) JDBC 3.0 driver + for Microsoft SQL Server (6.5, 7, 2000 and 2005) and Sybase (10, 11, 12, 15). + jTDS is based on FreeTDS and is currently the fastest production-ready JDBC + driver for SQL Server and Sybase. jTDS is 100% JDBC 3.0 compatible, supporting + forward-only and scrollable/updateable ResultSets, concurrent (completely + independent) Statements and implementing all the DatabaseMetaData and + ResultSetMetaData methods. + optional + + a8f0d9493455a962d0a6413977e8d80a + fcee1795fd535cd7a8a8824df9e099417e8700a4 + 2b0ed4ebc84c8bf777571b33154772ff6bcd1d17a55fc9e099751fbb386fa90c + f9aae7bf14909b6921b5c9ee1830ec1cac66c95ff9afb107011ab19c73ac039772d395b769b6766ad719e566f2ca98fb828fd28c7726e0086d70e3220d8e9103 + 661ae6f40d9aa2daae8741959e3fdb86f9dc115ed513bdb2bb75ce1602bb335ab3d6cb97f15cfe0c3e3ed222b586770a + b5a232f17c72687e26ad80093798151c7feec2b9158baeba0e7b6a0192391e0c6aacef8ff7fc1c7caf54eb06a6f6bbeb + 574b55cfc8c6a9ef2d7b0a8028c83547cb4f6bed49e0365dd35b99422e593fb7 + f791fc8bc57e2fee17f49646cf85fc490d1aefc295705061820e3d3bd8ca55e35ff25f6bf603b72540d22a818d9302634cc0dde79b7ff59f0d65677b19132519 + + + + LGPL + http://www.gnu.org/copyleft/lesser.html + + + pkg:maven/net.sourceforge.jtds/jtds@1.2.2?type=jar + http://cvs.sourceforge.net/viewcvs.py/jtds/ + + + org.apache.tomcat + tomcat-catalina + 7.0.27 + Tomcat Servlet Engine Core Classes and Standard implementations + optional + + e6a1c40d390f6a23e87d9cd82535dd59 + 5636afa26acab10c72bb4e2ec49532a6937a44eb + 596da4a1c7acae65e7048921dfa805f3fa9da38a17c42488a43b8f53e2267f2b + 08685fd3ed529b2ee355c0e3e3e5d75d03ab1ff2d1ebf3df83cf424853b2be5ddbee5ea734687e3a3d90a81e04fce908f35167bda68d54e2856841c0affaf7aa + f4566cce827e30d40d82e66d31063a0f042a1a5ad0a48205887f2d01e459c86d2299f4f81d488362f5abf1e07b085d52 + ef18fd34f824218b6b76f2f90693b4b2a479307700af020f8259c5c3bba47d2616fa39138b0114a1e552de7289eaab8e + 374c1f459c27a90f9670e22cd541e29941e88f8b7698648c747299eb614bf681 + d4553efd65372e0ae50611688578e63ecc0ff241d0effb44ec06464560f831f09023f0f5c295066723b16a3d0618a6d76adafcccc5596e61498b24734032e8b9 + + + pkg:maven/org.apache.tomcat/tomcat-catalina@7.0.27?type=jar + + + org.apache.tomcat + tomcat-servlet-api + 7.0.27 + javax.servlet package + + 3deb28f230117828757dad33489a94b3 + a7159bb6b332fb3b67f8c2c4dfc2a1430892353e + 105230d2be60b437e2106018e5da8a03bad59de0498075266fd9e1caed39c496 + 5443f6fae0a0ad8bf73d0b88601c9fab2d57fcac60a2668083caeadca03cd26694dab35fd9d130653f3f73fa8581beda0a3c251f9653944caae427041605f54e + c3782c3770238666ea71f1487cd6b277a1aa55d097bf5a7bdba54a39479d98f77ecfdfaaa49953429b1b2c3a8c5db2a8 + 9a1450984a1a874977e1e7970551c3427a4e0361466df7319ad562de58c4ec8058377c044e475b6ee8caa76248eb3ac3 + 6fcf4c2704f390ad1e730f524290cd4ae0f924684fb30f1a888db40dba8e2484 + 57dc1852f784cf809b853cf776f84cc6caa7ff4d56838572f6275b23ea715988f2fa81ccb67d6231df2fb56174479b93b1433136f4a3a6b0b3f5eaa5cfb456ea + + + pkg:maven/org.apache.tomcat/tomcat-servlet-api@7.0.27?type=jar + + + org.apache.tomcat + tomcat-juli + 7.0.27 + Tomcat Core Logging Package + + 624c93006fdf73a1401ac52d8f1038c3 + 20ce78c872139424b73ed24d242a84b5bbee7125 + 257e3ab8ce00dd7d1d8490bf1658798d976ed8df119a8b543660b9702e4a5e00 + 3f5a3d9bb04f4e7467654b6e1878098ef64378642ae3385f41437236b6a3ebaf5013a2583666140c397c5f7034ba3b39d4b233b9d1f09d52fe73e1b8715bc2ab + 8ce397109a08bbfc078a8ba23f8b4108fbd60372ba4b72ace701447293958a3908eac3c1c801dcbd1b25d673fc024688 + bf5f49ad35cba7f3017c5e1937afae24515c55029bca4de2636191cf90d5bb3ed5e61226c60382736347f773d5a83b91 + 684059e2aac35c07dd5513e86b258fbb97c4a8859ab874a824049c794a83d2de + 6969a87f4d2dbc20fd95726569887741145b542a845ee2481777ccd22131d6c703ce020af0b92045e25a08e906a69d31b84e692286521022941fd8b3559904e8 + + + pkg:maven/org.apache.tomcat/tomcat-juli@7.0.27?type=jar + + + org.apache.tomcat + tomcat-annotations-api + 7.0.27 + Annotations Package + + b702578fc1b0fa3cc4bc045fde56a0af + 43de2bcd1b031daba05d8aebbaf8cce4bed0d34e + 5dac4014a20e6f17d4bdfeaca346b5ff356abc0b8e8f383045585b2e9dfc7f4e + 6c1287561aac944bea6f5a17aea75946c48a773a27a5b9781121cad1a83711b82dbda0454f0923736bf11f07787430e815e8ecfb0f562408acd1ad67d31a90aa + e0509803b4e55d79782d23aa92a30e735ef6ae4f8c91a90dbe91dc37f39604e77d78eb1cc3212899daf3590ae2de3786 + ad234cd7a56672771b5ba5bcb081ca96b2c73cfa55ea9765bb7d43377f0d7cd9fac8b8a93f252c637a80c3c40a8bb83a + 07191a9cb7b4b46c99b93bf8863ceed6e10289ec02c179c045603d728dc2bc35 + 2b745251ebaed7245c51c894c7fd88e4d234766f43a37472ed6d303532657587d61f05b8101a23c3df0d713075ba26d3b8d09e4ca76ce12f5d32fc6b9abadecd + + + pkg:maven/org.apache.tomcat/tomcat-annotations-api@7.0.27?type=jar + + + org.apache.tomcat + tomcat-api + 7.0.27 + Definition of interfaces shared by Catalina and Jasper + + b608785beed29ece882ef782695e3b69 + a39075408b5725cc8dde84cfd2816099df885711 + 785aaffb22de90cac8af21fc11981d45ed47b2ff6197204b7ba5a6552b63d68e + f82f8aa416913783c625521d77684eb61ec369c9b7c32e77317ff80d4847748dd0be9b16630da2dc047c822dbde0d67831b10d9f2567eb9a3fb86b559b1ff602 + 9d67b395d04f0da94b70de975d267a55ec257c888789dcee84af209dbef9f443d0d5cf251f950f955a82806af029ff0a + dbfa656f886e12fc6f87b7c3f34c848c4a8f7a9e4845215f8a3775ab8b87473719a8ce0d08c8a74a5ab5aa24d0879880 + 0b981ae04f1f8c1c852f39f2a73f1775cd7ee09d1692ed6f178e67970078b870 + e6836cd951b36e950eaf184265e9842087a8190b9d161034f69e9c4d0470608791c9c3f1943ef2cf30a91ce2317a522f1b5de1e5c706b060dd01a2052b88b0f7 + + + pkg:maven/org.apache.tomcat/tomcat-api@7.0.27?type=jar + + + org.apache.tomcat + tomcat-util + 7.0.27 + Common code shared by Catalina and Jasper + + 3bc2ae683f8da61739bf3ade540327e4 + 249a9c7584ba34ede9de3eb7a4ff191990b61f38 + f0e1f5edbab32dd83bf80b472a48efb670664287f7606277afb874a1041af79f + 63bd797d3115a590e9bf2f59716fa4d87d7915249261b4a37664f1143bb166da46284634102ad1cc9fccaffeac647dca3f817a943c42937a378f17deb0e49dc2 + 5e00d38b3e9049cd1e728abf215c3b60698820df3d29e5e6261cd84102916797403eb97c567129cb63b0ac68ef5a9272 + 96ed3fb8c7e52c9a9e4c2612fddd7583407f2272fcd8f827dd0421955374605e0af1f07322b4e8c67305ecfd8b68e0be + b47f35e7513b43f37017470867b3c1646124478274ee03d49d7601a9f0a980a0 + 9aba9d5f4d4d38545fc4da2c3c2980aef582a2a92eb0ab1b8b909c7fc4afd18c80385f07843dbb59f85a7755de6108524e25f34e306d59d4095b0159a6d00ecf + + + pkg:maven/org.apache.tomcat/tomcat-util@7.0.27?type=jar + + + GlassFish Community + javax + javaee-api + 6.0 + Java(TM) EE 6 Specification APIs + optional + + 9322de56b69e3f85f4ea39722c497c08 + d6f416983ea13c334d5c599a9045414ecaf5d66d + a045eac0ff80c5c0fd29b680599f27598abff16b1e6c9df46ce962a4e0b1390e + 3f69f0a8827cd58cb53e9a1e25fb816760b91d441286f0534a0cb2f7a07f83d8ec1a50f8bdf6fe3b489bf66bdb8ac363d9f5c6fecb6fae8ff704f1a42b0aeab6 + 1b0fcc84f84e2bba179ef3e9561948b83c13ad7153b3a0e5f75f93934ccd2c509b27d9c006b88012db5d9f148e86a244 + 40ad101fdd32a6eb83015bc8d93189598e6b01a851a9f1f3be08f48c308e9507c34519a55023c1bac0f0e01e2433e1e8 + 0f4728439e813b9d712713a6433453a2804563e17795b7d66c5f845ef5ca911f + 9364c3781acd0b07aceef20d368ee33525274fb85ddbc47ca621d74324864158f0441442783e7d0a7147122dd28c5fc045940071dabc01b3dafdcf91156463e5 + + + (CDDL-1.0 OR GPL-2.0-with-classpath-exception) + + pkg:maven/javax/javaee-api@6.0?type=jar + https://glassfish.dev.java.nethttps://glassfish.dev.java.net/servlets/ProjectIssueshttps://svn.dev.java.net/svn/glassfish-svn/tags/3.0/javaee-api/javax.javaee + + + SpringSource + org.springframework + spring-core + 3.2.4.RELEASE + Spring Core + optional + + 4ea7fb151a045dcbf89786d85c9176f3 + d36bed1dbca3f725bb5a4405c6115adc8c99d1a9 + f0c462cf982bdc64e0c0b4c885e0581873addde91b0352d18da8522b714b1226 + 48427350e45754dab4f9932551b8e2bf99c91f2854839967abfd48fba1fab2f3586aede7bfac62374f44f2d70f7671a852ad8dba79bed91537b910c97553be60 + 2a94a1c1641a99436f56887c0ef9f3626898a7f45ea19404fd8ee6b06ce779ab283b0594c90ae51bd97f33298510ddee + 7e1178230418d9da483e06fb933e6ec2a1364cc49dca68fae7ebc6729aadfb462b2d2a191f4a1672ac535225825258ca + 4c1f0bf6310b32ccdda8b9e0defd0fa8ada706e3f2438dc4236062550787c338 + 47789728de456b0e5d602f092d88523af9fac08f853acb17f3d369d2426fc67e48dcd8abc73c743fb722cca0743fda60f9639787b2fd70dc8c350bab3536083c + + + + Apache-2.0 + + + pkg:maven/org.springframework/spring-core@3.2.4.RELEASE?type=jar + http://springsource.org/spring-frameworkhttps://github.com/SpringSource/spring-framework + + + fasterxml.com + com.fasterxml.jackson.core + jackson-core + 2.0.4 + Core Jackson abstractions, basic JSON streaming API implementation + optional + + 83342eaf63b2967115f52028a8c158cc + fda2057c7eafd65915deb349c32bbb11a241d6c1 + a56260c670ec41b4db655c8c0ac75cf7693059ec5922821ba583d1ebbf6fc2bb + 9b3f48638b481fe704e2806dc77d9087381d1eced0f2f40c604c76d48a4c35cc5de6e17214dfe9e8f45d7727e9630fb65b805da4ae80a8957a323b5a13adbf82 + e2305dbedddf8a55aa1ac8783568ea3457e786ab8c20536494d3002d06294722d1acd2c1bf27769518683a62711e7909 + f1a3fb45b74d09961964c1844d689b6bb2d20e54fa2917be520600201881c5f21976bc4682784bb371c8687a70768d37 + dd9801113046a5114424ad07d245ab78aa118d79da76f973f7ec7968d50f2be1 + 8e4de277343afaa4acffece3329f83334858b15296e0ba5f4ec3c5ccb16aa96ae779ee7a5015364fc272b2649107c311ae75ab98bc8c637987842e05931ee79a + + + + Apache-2.0 + + + pkg:maven/com.fasterxml.jackson.core/jackson-core@2.0.4?type=jar + http://fasterxml.comhttp://github.com/FasterXML/jackson-corehttps://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + fasterxml.com + com.fasterxml.jackson.core + jackson-databind + 2.0.4 + General data-binding functionality for Jackson: works on core streaming API + optional + + f31b6bf90afbfc33fd7ab3f18485bec6 + cf05e1449bccc5dae87bd64d3e7b7be686a19af8 + 6c84fb848a6b3bbf28cb056a75bae6ca8c6cde277c83dcec4b9a6cae7035595f + 044263f3e15114a1fedfeef0e211e8613e9e8d9210e63d2d2cb358a3389abfab4af6ffade7443cb6962960072e0a4640b955cfa8bae5e3717a481e7a2bc5313f + 6ced5a11cf83a0a74fcb250cdee26e568428f5a77bae92f1623d1c8f06498ca255b24c79b408a73b7e0c298ec5d99b79 + 2937c6b5825843ba2cdc01f14a610a706edb386aa7345c1dbe264d95984268ad1fa994232702d6129bab39229473e190 + e8fafd546e8edc9c41d84bfdce8bb22cd3345e6c360f4dbb77f6dcf30da74508 + 04d5e4b09a142084b7c66910e65fe1e02b67223c036bc8b4d741a22e908a932602bf89436d99cacbab8e27901a542cf73667da4b3070c1644ef6ad3a6da18fe9 + + + + Apache-2.0 + + + Lesser Gnu Public License (LGPL), Version 2.1 + http://www.gnu.org/licenses/lgpl-2.1.html + + + pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.0.4?type=jar + http://fasterxml.comhttp://github.com/FasterXML/jackson-databindhttps://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + FasterXML + com.fasterxml.jackson.core + jackson-annotations + 2.0.4 + Core annotations used for value types, used by Jackson databinding package. + + 80615627d10d83a6904e46cff2c1637f + bf8ae811948b74ada97f643477d209bb336574eb + ae853ba5aaa2e3ead43185e40514ec64de4effa9b2d7e1684f0f70a878965959 + bbe2d74e1bb59e61b8a45d1d6b0172ef718b9ec000ae61e32cdd342def844a33475dd03b102df2e7ad3c56ce982c202c26cba5a5ad415d61fb482949d3794664 + 96cae9f3cad3e7a436f285fcedd93b833b898fcf97476039fa0ff17286e6136074986d8555653e49be7cba8a70033764 + a127cc0b35850fa0c981c6f6ec2b55149b7c8b2f20ba2f19b19c153f3bb698d83c6e558bfbc06a073f09de52546feaa2 + 8148df59effc0b1b1410aa91e76639cf00cae888918a39a05952334f2bbb5b82 + 6d0c94696930896b56872744d8c99d299db78ae89eec6a71f314e7fca5c73dbfeb6dda313cf61d16d3cae272cb35a74e24e9877e41115c4ed29885ce3e82ca3a + + + + Apache-2.0 + + + pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.0.4?type=jar + http://github.com/FasterXML/jackson-annotationshttp://fasterxml.com/https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + SpringSource + org.springframework + spring-webmvc + 3.2.4.RELEASE + Spring Web MVC + optional + + 38173f7b7ca0f7137d5a66b5995fcc65 + 99c0303b36c421b3c43dfc2ae55e46fc81dbaefc + c27719d84c336f5874152aabf5d38d585e9bd2ba3a60e9188f55b4bfee2b9551 + 8626d3fbf345a313d434dc07f8efa5914c0ffc926f1f4883cd91c263ff1fd340f79839c7e46e83129a969cd21aab05f334fee94ca6e182d6de810e15a027882a + af314ece7a80b56222d3fe6f7778da7a2a043d68d37c76b5b212dd25a01b05945f70dc14476415dbe079388cfac08173 + a838b2457c4e6e3ea0246d96453ea5adc87057eca4f21cfceae7bc8f45b6c79cbaa4955ab9a5ba9be51dac5f32ebac29 + 9a62d21ba73c4815fe7859c391edb4c64503a654ad31d5dd6872265e5f7ead41 + ae24281458d7cd8fd3007708bafc3663108b83e5ce2ba47d392ba4fbf211000b2051137e6e3d5c8d7e5c54319a2132e8a27d3613b82c3b1692c7772d7c838b44 + + + + Apache-2.0 + + + pkg:maven/org.springframework/spring-webmvc@3.2.4.RELEASE?type=jar + http://springsource.org/spring-frameworkhttps://github.com/SpringSource/spring-framework + + + SpringSource + org.springframework + spring-beans + 3.2.4.RELEASE + Spring Beans + + 50110a98f45c966e662bd9fbcf38031a + 47b156296a5e9875faa7421b6e25b419a530f52d + 387025a34350812978f39c5cd89484379702d617712f23f567f0dd61952aa573 + aaef84ea406e6b0ecd4cdd501fddb469cfa3216409dc838711842f3f2f995d2cc1eef4502e4f2f569710cd9c1b27051b4d6bf8d798c53bb5ad408a6ba1df39df + 25c51bd252101f18938884520e2ed0d4d1cb31bd11186ccc2a0f2539dc86013feb194b0926fe8cb226966c9b3995aea2 + be27cfc46d30a479c9480587a7e954e13c5c09eff6cea54d0d20717fe093b7bf32a95d8f93bdb3a05583a6348e192260 + 568b9781ff2663aa1e2946fae12dbcd8d9bc86c1c6fd0ab137cd6c4d4d80b4a9 + 6494d7db8cd1f373eb1aae5b91da1978dece34f960fab5f1872110299e426cba3668e672da53a6541e984e9d63d5d5533af1d196299764ed71698a6bc5a5ad9b + + + + Apache-2.0 + + + pkg:maven/org.springframework/spring-beans@3.2.4.RELEASE?type=jar + http://springsource.org/spring-frameworkhttps://github.com/SpringSource/spring-framework + + + SpringSource + org.springframework + spring-context + 3.2.4.RELEASE + Spring Context + + 3b10ff4c60c2646afde5aabf4986402d + 4e6d5edcaeeed6cb332b90fbe3713f003d28188c + 5d51d11f02d2458ee690ef619daa6795f5e72d0e3e83b11962df69e76ff6b837 + 460253e0a50fef316ad9493bc86241601bb68a4da55b401b858aa4fc20e15ad9dfab885af61a172c7b006a75c3ea28ad171ad469b5c3eb5feeddb8fab31e69d4 + 13d90eb3ccd8253e6998fdd9400bc15e293d9e0625a1ffbd45377350e77d257bbb2cd66d286756bbc5041c9de31df97d + 4f4e94cc7179263fa51c37eee3bf93d9c391639305d4025542faa646e65118afcc48104706b4fab11f114c3bf1afa042 + 0a2f99f040c1d3196f7e4e1a4c556b6d72071fe73e1d40c3bf1cd4387d165ed8 + 7b8464ef081fc51ff9da025b6bee63b6097c24c4ae503a5e2f13dfd30627e01a0d268e0cfdff070333f9f69ed41a19571b8c97706ed398a4328435bdaf3d0358 + + + + Apache-2.0 + + + pkg:maven/org.springframework/spring-context@3.2.4.RELEASE?type=jar + http://springsource.org/spring-frameworkhttps://github.com/SpringSource/spring-framework + + + SpringSource + org.springframework + spring-expression + 3.2.4.RELEASE + Spring Expression Language (SpEL) + + 63cc4c17140fc24fb070dad669693ee7 + edde2efe828f5890f57aa153f55c5c994cf04489 + 25cdba8a2e2a9289305e33868a026fcb7b88ff56adb92c0bce1b0d3f2e53d2f4 + a0510cac0c49996e9a16354f0c0d4784010848f9a87548e200b6df698436c26a02a79351d15fd61fe02b15f6f5fe1881a6974b3e78f6b7758480e364cee18a00 + 94bff47eee3d8b044c74146272616ddc2a8ac43e4ec02369d466a7e43bd46edfd2a8cbebf73c95bfa491d5ebed22cb17 + f292c2a58a00fc2361dba4b02f9916e9de10b619ec36bedd068eaa1a7c765dc9244efc8668a3a43a38bff702fce49801 + 9e4f28123532a858579c2d23e2e00a1890a7e8486455a73a76b390daaaa84f15 + 4b7ba617d05340cc2729644ab627c8f5e5d7ef3989fc7d85366bff8b0bc1b0f191bc8622b255a1f8021e8e0a5bee1e105950645cfe4d51cee97a9f76abdff505 + + + + Apache-2.0 + + + pkg:maven/org.springframework/spring-expression@3.2.4.RELEASE?type=jar + http://springsource.org/spring-frameworkhttps://github.com/SpringSource/spring-framework + + + SpringSource + org.springframework + spring-web + 3.2.4.RELEASE + Spring Web + + f7734bda63661ded55e82d6a9c975618 + 8daf0bd1172cb253d93611adadfa0462935d760d + 48da368bc3b457e6e7e08c71c36bddb208f5fcdd028848c85c4867ec4927abf5 + dc398850c77342f5920c38be5277fc3a73545de04fd1fa90eb4987128b29c45a02c34c87ed214d9b23cba66b2c8c39377d0a77f85fe9d04ccae90b71434d9dd3 + 7bb75e593d8dcac837599f488c701d447ccb0759c9f7947e7d1a7849f900555bc5543aa8dc4d4f2e709d0cb60001f80a + c5309a83550a7714317f0c2283f735001c2f5f1a37ecba9dbf73224a7cca68805169145f9a887cf34b8549d38eae236e + 2e767880f56d2f85c303ff93a4f9a4110e9c65486641fb3bcfd75fc1cdb312fa + 888922c2986a039d0661d02a3d645c27f59813293f9b076c1d13056f974ec6602182cc32a6c027058e2a6c8b6c6841e17a316ab040b56406d69d7c8a8c489603 + + + + Apache-2.0 + + + pkg:maven/org.springframework/spring-web@3.2.4.RELEASE?type=jar + http://springsource.org/spring-frameworkhttps://github.com/SpringSource/spring-framework + + + spring.io + org.springframework.security + spring-security-core + 3.2.4.RELEASE + spring-security-core + optional + + bbd11acd0c2699527e3e5a0bc3620105 + 493bc57396a821e8ffa7d99fac77cc79c15e9b7f + 57c2dbbc7599c19e6635dc716dd9795150c0baf53dc24d045e78cce495eccc2e + 6af7b04cff39031c9fac27680467100cc538bc3628b61d8ad5f9624257fc7de0519209c6684c900706f3bd7ae4ccd45f5b2def6df6667ef8a098c5dfdc52662f + 4d6024fab9fb591ea233608061198a79004b7f21ade82d9f495d70ea839171e81c80435c415ac67d0f3bd604dbcc44d9 + 533b444295bc60e3ccb1022602bebaa4dc7108e63b8f6ab6f5002ae760c5792d388c7e536de1ece7d6c889f7bb78484a + 849609a713515f4108b8ccce72819e0f3447c586650fbfbc8012c642bcfad2ac + 01dee4835576886190268effa159727e6350f4810d7b51b001b6657948d96699cefa9eddde900b02e0088ee2a90ccb088fe92e79d6a2f8b5419d12590eab35fd + + + + Apache-2.0 + + + pkg:maven/org.springframework.security/spring-security-core@3.2.4.RELEASE?type=jar + http://spring.io/https://github.com/spring-projects/spring-security + + + aopalliance + aopalliance + 1.0 + AOP Alliance + + 04177054e180d09e3998808efa0401c7 + 0235ba8b489512805ac13a8f9ea77a1ca5ebe3e8 + 0addec670fedcd3f113c5c8091d783280d23f75e3acb841b61a9cdb079376a08 + 3f44a932d8c00cfeee2eb057bcd7c301a2d029063e0a916e1e20b3aec4877d19d67a2fd8aaf58fa2d5a00133d1602128a7f50912ffb6cabc7b0fdc7fbda3f8a1 + 4dddf44338b5aff9580da2532b81c0ac3e1d09e1f28c6db871a55cad442b705dd7791eb07f9d4577d49d0be3673ba783 + 2bd64cbaf769c6e4e85e34f7a6119d89e16fbf55af3fc5d6cbd52eb214c367dec1ac7b9062ee0fb35a2e0acfc7c477e1 + d4a726b2bf8aa58197021a7d8fca674b4b2790d4c48de43a92f728866a91c2f0 + 830bc3f8328be76897990e9b9fc42eef02623115e456af96ad09b20900ad615519c8c8de60155ac04fb332eaa9510110d52edd13911af76271c71d91cbd789cc + + + + Public Domain + + + pkg:maven/aopalliance/aopalliance@1.0?type=jar + + + SpringSource + org.springframework + spring-aop + 3.2.8.RELEASE + Spring AOP + + 72d52ace5d2545edf632944eaeeed333 + 90e7924ef58621a4c07c81279e013d9a11d41789 + 21f22387da593222c20a0bb93a74ee8fb674a1535b868d92342352ed8b596b4e + f503ce8df655f97038258eb008c0435300e25fd4167edc315073d6da5da8297c36e9582ce75df6ae3c885cdfeeff2ce3555c8f21f4dcefde79468c27b9a5f65e + c5d78720e343bcdf97dbb49624d86151e138672284a35f56579d9f1e152c14ac50fc0f324f4f8967a5c7a0bf846316b3 + 9dacc8f39c83b099b6c3f4a2969599d9a1c9c0a5fadc3dfc2070458822f1e4aa7a52a49d1e6bbceb9bcda09adc9cd4b8 + f0e267ec5ad32f2dd6758a7e60cbdb2f28ba5e284e5cdbfd9da611bd3b3625db + 2fb8ab5b5ee13966296fda95bcba8e3481d75ba07381c5cc3a2d806bff44d1269bd19d70655d2c9b0c30d41421005a1e982ed4d1bbdb4b5189d05650bdba1a4c + + + + Apache-2.0 + + + pkg:maven/org.springframework/spring-aop@3.2.8.RELEASE?type=jar + http://springsource.org/spring-frameworkhttps://github.com/SpringSource/spring-framework + + + spring.io + org.springframework.security + spring-security-config + 3.2.4.RELEASE + spring-security-config + optional + + 52b085fea0df311ee1f62e00bb800469 + 952da051fc959b215c8e5882a7ab1ca5137af036 + 076c794187d49218dbc5d1475f70945b68545e10beda12e3b3e1402c71d1d590 + 68833ab3a940878e13eef43f654829de249d0f818378e9b2a928036075c52468001d818487bedcaac8617bb5aaccc3d27fe071d0fe469a8d5bebddff3d521870 + c057aca448348d5a91ffee1508e9fd00765a00bc42ff98e8701453cf52b718771620545d8af3abca844ce344ac50a10a + 0542c953e6b75ac5f826c77cfae6a1d477ff7ce62b705e2766242b3db7d99b9068800d7cb8d74406aaaf225ec3fd69e6 + 05d1c6a1931da3bc5d24ad20768f873b52873b2932eeae6ad39332030e3c9369 + ca78eebb6a50c3d50adb3308e9ccf0787ee2651730d0e520e834c78f8cc4880909b15eac47ba750b7158285a51aca243d959aa6be89cd5a73f0121a0ec6c6c76 + + + + Apache-2.0 + + + pkg:maven/org.springframework.security/spring-security-config@3.2.4.RELEASE?type=jar + http://spring.io/https://github.com/spring-projects/spring-security + + + spring.io + org.springframework.security + spring-security-web + 3.2.4.RELEASE + spring-security-web + optional + + a5d9f8db768872e17b25d0970e574b0e + 197d803ab63dd3523d9df186505fc1cc93044334 + e7cdcb4f3778696e81f26894f08e29823d6da8f151c70db425f8bd71307ed906 + a7f64df946343a0c44d50500743b3cf6db60576cf2ee5ffccdb10caa7882d9046326e9462b65a4871345abc276a306dbc4795fb06bfcce5c00446899a3056afc + 162f74b5e1ebb8662fc913c65cf082b52566089ab31bc3e5f2a18ed38e3b92166761d936a49ce766e68ac85876ba08b4 + 5a8f819078338088978a22cdab0e7d80abd04c152865ecfa137cb5d125ccac0ef0de99a1c85f4b2d8f3fc2e96b8bb8ab + 7a03f627cda522cfaf45f4ad1cb1ca9d599281f0357b4d6e30d1c56c26a07e65 + 5ce88cff601b3329efcf3588d57c14670ef81b0c8c49e8a3377fdfbe7cc97cc6948a7d352dc6cbfb50c4e782e37814f52184650da5ba17c31d8af56463835743 + + + + Apache-2.0 + + + pkg:maven/org.springframework.security/spring-security-web@3.2.4.RELEASE?type=jar + http://spring.io/https://github.com/spring-projects/spring-security + + + The Apache Software Foundation + commons-fileupload + commons-fileupload + 1.3.1 + The Apache Commons FileUpload component provides a simple yet flexible means of adding support for multipart + file upload functionality to servlets and web applications. + optional + + ed8eec445e21ec7e49b86bf3cbcffcbc + c621b54583719ac0310404463d6d99db27e1052c + f4ae31866d62f91054fb3dfd0696efd08705e5e8ccd657b01b460a80044be532 + 712c0b25a9070d8c3a22d2a819c3c855d9f19bbe055663f70818071ed599ae56528a50121b41b9a29e7321ebd84ee37199add723df36da92d71394eaadfa9b24 + 50a70932d8a98771a63e26ce76865ddd41ec4e559dce71a128272f9ab216d6ee24fe556bf9d1f879e18dea929cae48d4 + fc0c4311a4c3b3c5caf9838c6da232d3e8f0dbcec7b0cb9e640d3ba688ac9cc006a499fcc3ed1344b37dd61eb141ced2 + 462a9f5904cd990297675f246b9cefc4d4034bbe3ca4a334bd36c3ac319d8637 + 49e9330b77ee2826ef513d1f10a18d7f61d66bf586d7aae35ad8c83d70901ae170147f40b10a4bdd4ebdc7c2eb482935e30fdc3bbf1f8efd91ef94ff12cf4bee + + + + Apache-2.0 + + + pkg:maven/commons-fileupload/commons-fileupload@1.3.1?type=jar + http://issues.apache.org/jira/browse/FILEUPLOADhttp://svn.apache.org/viewvc/commons/proper/fileupload/trunkhttp://vmbuild.apache.org/continuum/http://mail-archives.apache.org/mod_mbox/commons-user/http://www.apache.org/https://repository.apache.org/service/local/staging/deploy/maven2 + + + The Apache Software Foundation + commons-io + commons-io + 1.3.2 + Commons-IO contains utility classes, stream implementations, file filters, and endian classes. + optional + + 903c04d1fb5d4dc81d95e4be93ff7ecd + b6dde38349ba9bb5e6ea6320531eae969985dae5 + 551c13e49dab32aebdb7a70ec9c2767372e58864ae115ef389582e548cffee38 + 75c9f64c2db652f346ef98a54c5a6e9e93abd579d27e4a2d85d7a1f79ac2a033c4cf1f5d41ca86e0d9fd93fcb4e7a76465e5d4cc2e84cb9e17012b87c322b534 + 71904209b7f8e48756e00f5a535af951cd1abacec9b6b026e0eddb4110379779c5624c62b0ab03e6ad57a1ebb5816a15 + 15e139f6aae4ba4b9db2c7474111eeb890cf5c91c08ce68fa112c31c292e948d43efdbd1897467676ea7bd4689c23673 + cac05241b5bd2eeb3ba3bc64a48026c6537fec3759ee02eab77e8c0d74148b41 + f9b3a7e42aafe5888f34f068d231cf65de0e023b42edc4af5614f922e164b30dfe6d61b69b55247a98543dd3da5a9c0cd1af86c3b8f7e5516c070bfd77f6f74b + + + + Apache-2.0 + + + pkg:maven/commons-io/commons-io@1.3.2?type=jar + http://issues.apache.org/jira/browse/IOhttp://svn.apache.org/viewvc/jakarta/commons/proper/io/trunkhttp://mail-archives.apache.org/mod_mbox/jakarta-commons-devhttp://www.apache.org/ + + + javax.servlet + jstl + 1.2 + optional + + 51e15f798e69358cb893e38c50596b9b + 74aca283cd4f4b4f3e425f5820cda58f44409547 + c6273119354a41522877e663582041012b22f8204fe72bba337ed84c7e649b0a + fb6fe33922631bba21b606cb5d53c4457071e695ec5632205185281e7db3e2d63d95248b43b9c7fb2cb5042d14a267b87d2c5da742c989174c3526dccf23aeec + 750771b79ef083a19d20a7b384c166893c9045ebbb9428b641d0bafd8101615652d50b095e3647d2d849fb79b822b109 + 98a520ca2dfd326e210fee9dac631e6fc21315ae6f8eb93c1a29bf4cd7b7a5fe880aaeafb73d14d5e6638ea72ead222c + 6f412b6fb48e4eafde516fc2803e0f40b09e2b6b090a798094430c0a126da654 + d67e63b347426c3e547e5fa6d6d8285dc5b2f9ba212355d1d909ffae336f1475b18133e30ee0bf84acaac9f8007c52f1c94fb2cda1e1e339ba5db5715f981927 + + + pkg:maven/javax.servlet/jstl@1.2?type=jar + + + taglibs + standard + 1.1.2 + optional + + 65351d0487ad57edda9171bb3b46b98c + a17e8a4d9a1f7fcc5eed606721c9ed6b7f18acf7 + 2c0048ab3ce75a202f692b159d6aa0a68edce3e4e4c5123a3359a38b29faa6b1 + 5accad926fd4337dc2f7c01ddca751d515f03da14947223512b6f0af5314c40b8af6863e44604caa8f24e45c0cc8b00b5ddb446873663d131bcb0ff808ec412c + b8d742ea4048f2493fa2b63ce5009582bc52ae299c743fa22945d628556cda19fd236303790b0412cf8df1e47c3a89ed + f237a616183aaf675f43736ef513247d5e4d46cea0e43447de848b0793ecca6221ce4b4ab9b833e02fe120d634b8e38b + 886ee024747b0b3ad4c7615bf395c0b5f440a4a93b58dc05dfdd21482b732e66 + 007a47aced0cc89df32220819df1923d502c8af0eeb3975a06b6fb24e2debbf6769db96ac1be730efab725691c5ca2b27d40a71f2d8a82e9396e0f32e99be213 + + + pkg:maven/taglibs/standard@1.1.2?type=jar + + + Apache Software Foundation + log4j + log4j + 1.2.17 + Apache Log4j 1.2 + optional + + 04a41f0a068986f0f73485cf507c0f40 + 5af35056b4d257e4b64b9e8069c0746e8b08629f + 1d31696445697720527091754369082a6651bd49781b6005deb94e56753406f9 + 3f12937a69ba60d0f5e86265168d6a0d069ce20d95b99a3ace463987655e7c63053f4d7e36e32f2b53f86992b888ca477bf81253ad04c721896b397f94ee57fc + 80b79d5872dec949dff23d221636b5faf312f08fbea2b5e2a8db84a5f5955e13864aff4a288935cb53d4da3d61defbe1 + 0be025582340080ea191c4e2559395fe9957c8d0aeea040239dea8504ce035bd24150162898d18c863092697ef52501b + f7f31abcf8b57cf6fd18380b5d1a13f999e3d3d1d89bd25a4a3885fc832f4a29 + 0e4a21cb87a6b8626aa8f580b307039d2b060a6abc53d35feac9ecb8ffc2e80b627a12a869b5b5bf30a6e9348cd4c5c3edca937859141614f3c1b14bd8d4ef93 + + + + Apache-2.0 + + + pkg:maven/log4j/log4j@1.2.17?type=jar + http://www.apache.orghttp://vmgump.apache.org/gump/public/logging-log4j-12/logging-log4j-12/index.htmlhttps://issues.apache.org/bugzilla/describecomponents.cgi?product=Log4jhttp://mail-archives.apache.org/mod_mbox/logging-log4j-user/http://svn.apache.org/viewvc/logging/log4j/tags/v1_2_17_rc3 + + + JUnit + junit + junit + 4.8.1 + JUnit is a regression testing framework written by Erich Gamma and Kent Beck. It is used by the developer who implements unit tests in Java. + optional + + fb44723f35c2f1af5c51eb729b502f0d + f2975548f836416306ef1dee748d956f04733915 + efd8cf93b57d01e8f5fbefbe1f17ae39e6e22553615926e00a6eefc307da21e6 + c6bbb069fd60e60b3156bdc8efc012f2b04964114d71036f780b7e6c3177facff63610a7fd617bc5ba93a6fd912ffb06be380b0b9db51ca191b9fc058356801f + 097637a223312afe78d71512768fdd48746f155a3649b97d83d168e831295007017a970b653bf62d499ffb37bd551fe2 + 826817db877217f37b1ca4ead2b8ff16b7da570300519d4cacdd1295ba95ca64099f19269379957012bae82c89a7b1a7 + d2ecb1212b290245f198d4cda14d1645a451af23a38c475ca17c1514e298c869 + d093b73da6422a824391e3edc09363e907bcf07794d4e403ab70cdbf943582a704a8ed7d02d87bba41ccc48497ae8f3e610904598fb152914364f37b66f4ae3a + + + + Common Public License Version 1.0 + http://www.opensource.org/licenses/cpl1.0.txt + + + pkg:maven/junit/junit@4.8.1?type=jar + http://www.junit.orghttp://github.com/KentBeck/junit/tree/master + + + Apache Software Foundation + org.apache.tiles + tiles-core + 2.2.2 + Tiles Core Library, including basic implementation of the APIs. + optional + + bdbd270083adc95c505cc2af5ecbd1c8 + 01db730fbe26c148e3d085edadafb179e9674ece + bf885b7b53f5bca0fb603e66bb374750f3f7d21a4daae1ccef74938a82586814 + 3352991a992c56c84885c4b32be9082937e3fd72e7e18581fd3125d2fc719a58152e8c5468c32d2b3bfdf2894bfbea296bb3f1e5cb51c21f5951f1f941b782ba + d370be34858c67455bad71d75b98f6d98285e4d36f0a2b38e1b0c12901fa3e91651851013b478f4ab2c286ffd80b066b + 09dcd5b61ea3414bd9c61798822cefefde46898aee660f0c630ca2e13d136745ebe4d8e0bcb43e97076384577d0f69f5 + 7e5056f1917b37db91f1457dc582a533fb47cf008e379d2f7b324b3c939791de + 17c4a05123e0d365894e769f9ef8125f83e4bf82f8dd6e895aeffe3c2524ccc490e524d4646179a3550710065b018ed74b6fe1f4a884ac26c5e93aca1265febd + + + + Apache-2.0 + + + pkg:maven/org.apache.tiles/tiles-core@2.2.2?type=jar + http://svn.eu.apache.org/viewvc/tiles/framework/tags/tiles-2.2.2http://www.apache.orghttps://issues.apache.org/jira/browse/TILEShttp://mail-archives.apache.org/mod_mbox/tiles-users/https://repository.apache.org/service/local/staging/deploy/maven2 + + + Apache Software Foundation + org.apache.tiles + tiles-api + 2.2.2 + Tiles APIs, containing interfaces and classes to interact with + Tiles. + + c17a57e8f31177f85087bd519034c0a4 + 7c4a643772ad37664ae053915f073bc5a9a06b45 + c4c73247a4c719bf369382457a0b31dce131816aa175498f05c3dbce3c5a590b + 820b26b62da879bd5673d516e282290262975fad5428a0c09905acc9dc242cd8e69eefff169ff21a6138b722ac633981d056130aca167933f810274424c84efc + a3d72bb7664ce312e634002f4a6aae1349ad886da13af0a2ccccec604af2783c8a05af5360064bdc9886a87580c6b3fb + 81ab8c63b43fa814d468018705f6dbbf284fa717aacbb3af781c672888b721237a6cbd34051101181f597fac5a5240f4 + a7272e1df6513ffae34ddea83345228c774fb5866927c641666dc044030c5fff + 4fe0ad2e9d738742a41ce5ee93163f01e86642d59c36398a04bee681b66d220d97acdab932b9060e5f6acdd2e561e6ee99051533eb2be6a7b33e36bbba0b6f35 + + + + Apache-2.0 + + + pkg:maven/org.apache.tiles/tiles-api@2.2.2?type=jar + http://svn.eu.apache.org/viewvc/tiles/framework/tags/tiles-2.2.2http://www.apache.orghttps://issues.apache.org/jira/browse/TILEShttp://mail-archives.apache.org/mod_mbox/tiles-users/https://repository.apache.org/service/local/staging/deploy/maven2 + + + QOS.ch + org.slf4j + slf4j-api + 1.7.7 + The slf4j API + optional + + ca4280bf93d64367723ae5c8d42dd0b9 + 2b8019b6249bb05d81d3a3094e468753e2b21311 + 69980c038ca1b131926561591617d9c25fabfc7b29828af91597ca8570cf35fe + c47d0d78388022e38bd60fd6e613ab4ceb9cad6b2567a3d36153aefc832864a99de1f6cdcd52959f73231e5b933fd9172353434e5fd83777703f41cb40fc97a7 + 1b59a922a63fa73baff42db7e9eeab1525c71cd8f9d3b7ea208f310792aa7f334fe7d45c8dbbe4bf8bd664dd49ed39d2 + a520c091af8ee6d2613a36f58e35d443476ec4f0e7cec437a6100470863b8e06fd8bc76c23f1d3e49a5aeb3efb610b79 + 2624d4637fa5d3e7b56c66a29393ca61059565d54bbfc255d6494aa2ff7c3f20 + 173bb385f03d4ee926ec9f35fe11ca009ae5f4a5f490d6cdd310b5e4e7553a0607032d32b8ac63f638b3c1b2273252f1a333b22955e8700c50113f06ae0c244d + + + + MIT + https://opensource.org/licenses/MIT + + + pkg:maven/org.slf4j/slf4j-api@1.7.7?type=jar + http://www.qos.chhttps://oss.sonatype.org/service/local/staging/deploy/maven2/https://github.com/ceki/slf4j + + + QOS.ch + org.slf4j + slf4j-log4j12 + 1.7.7 + SLF4J LOG4J-12 Binding + optional + + 4d2be9dc9aa3d08dba9daa09ed917526 + 58f588119ffd1702c77ccab6acb54bfb41bed8bd + dba4c3c10321c86a48d689354fbfd9772a65e51e780d1e5378a5ece2e426a8f8 + f8e4aebe88589d0175079db03cc199543a85ce490949326653e659be9d1daab3b0368a69f93b2c7f0040c02336459253d3b48feda49cd29fc1bcdd090f966f4c + 83edae0c46e22b6d8223ddc68b175d2e9c3b37d5407ce604917d73256db60faf0b7227f3408746c951a54dde8e79ec71 + 04da2428ca3b9451be521b5489e186cacfbbc53725814bd2b0f4a88954f091638884ee8c06dde9678bc54871f8d561a3 + b0a91926b904c397196dc1b9956d4b9df93e53c6d4ef2c223b5230bcc8235475 + e2315316f4e64e78645216d836c1dbf258b5acb40bfc6ed0d5a6e06c6bb3556a332aecf52e447cf3ecb75632581e2c80ba82d95f3f0804b5a269428d88869476 + + + + MIT + https://opensource.org/licenses/MIT + + + pkg:maven/org.slf4j/slf4j-log4j12@1.7.7?type=jar + http://www.qos.chhttps://oss.sonatype.org/service/local/staging/deploy/maven2/https://github.com/ceki/slf4j + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_deserialize_json.py b/tests/test_deserialize_json.py new file mode 100644 index 00000000..8962ee51 --- /dev/null +++ b/tests/test_deserialize_json.py @@ -0,0 +1,432 @@ +# encoding: utf-8 + +# This file is part of CycloneDX Python Lib +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +import json +from datetime import datetime +from os.path import dirname, join +from typing import cast +from unittest.mock import Mock, patch +from uuid import UUID + +from cyclonedx.model.bom import Bom +from cyclonedx.output import LATEST_SUPPORTED_SCHEMA_VERSION, OutputFormat, SchemaVersion, get_instance +from tests.base import BaseJsonTestCase +from tests.data import ( + MOCK_BOM_UUID_1, + MOCK_BOM_UUID_2, + MOCK_UUID_1, + MOCK_UUID_2, + MOCK_UUID_3, + MOCK_UUID_4, + MOCK_UUID_5, + MOCK_UUID_6, + MOCK_UUID_7, + MOCK_UUID_8, + TEST_UUIDS, + get_bom_just_complete_metadata, + get_bom_with_component_setuptools_basic, + get_bom_with_component_setuptools_complete, + get_bom_with_component_setuptools_no_component_version, + get_bom_with_component_setuptools_with_cpe, + get_bom_with_component_setuptools_with_release_notes, + get_bom_with_component_setuptools_with_vulnerability, + get_bom_with_component_toml_1, + get_bom_with_dependencies_valid, + get_bom_with_external_references, + get_bom_with_nested_services, + get_bom_with_services_complex, + get_bom_with_services_simple, +) + + +def fixed_date_time() -> datetime: + return datetime.fromisoformat('2023-01-07 13:44:32.312678+00:00') + + +@patch('cyclonedx.model.ThisTool._version', 'TESTING') +@patch('cyclonedx.model.bom.get_now_utc', fixed_date_time) +class TestOutputJson(BaseJsonTestCase): + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_external_references_v1_4(self, mock_uuid: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_external_references(), schema_version=SchemaVersion.V1_4, + fixture='bom_external_references.json' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_external_references_v1_3(self, mock_uuid: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_external_references(), schema_version=SchemaVersion.V1_3, + fixture='bom_external_references.json' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_external_references_v1_2(self, mock_uuid: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_external_references(), schema_version=SchemaVersion.V1_2, + fixture='bom_external_references.json' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_4(self, mock_uuid: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_basic(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools.json' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_3(self, mock_uuid: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_basic(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools.json' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_2(self, mock_uuid: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_basic(), schema_version=SchemaVersion.V1_2, + fixture='bom_setuptools.json' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_4_with_cpe(self, mock_uuid: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_with_cpe(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_with_cpe.json' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_3_with_cpe(self, mock_uuid: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_with_cpe(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools_with_cpe.json' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_2_with_cpe(self, mock_uuid: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_with_cpe(), schema_version=SchemaVersion.V1_2, + fixture='bom_setuptools_with_cpe.json' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + def test_bom_v1_4_full_component(self, mock1: Mock, mock2: Mock) -> None: + self.maxDiff = None + self._validate_json_bom( + bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_complete.json' + ) + mock1.assert_called() + mock2.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + def test_bom_v1_3_full_component(self, mock1: Mock, mock2: Mock) -> None: + self.maxDiff = None + self._validate_json_bom( + bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools_complete.json' + ) + mock1.assert_called() + mock2.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + def test_bom_v1_2_full_component(self, mock1: Mock, mock2: Mock) -> None: + self.maxDiff = None + self._validate_json_bom( + bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_2, + fixture='bom_setuptools_complete.json' + ) + mock1.assert_called() + mock2.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_UUID_7) + def test_bom_v1_4_component_hashes_external_references(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_toml_1(), schema_version=SchemaVersion.V1_4, + fixture='bom_toml_1.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_UUID_7) + def test_bom_v1_3_component_hashes_external_references(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_toml_1(), schema_version=SchemaVersion.V1_3, + fixture='bom_toml_1.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_UUID_7) + def test_bom_v1_2_component_hashes_external_references(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_toml_1(), schema_version=SchemaVersion.V1_2, + fixture='bom_toml_1.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_UUID_8) + def test_bom_v1_4_no_component_version(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_no_component_version(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_no_version.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_UUID_8) + def test_bom_v1_3_no_component_version(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_no_component_version(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools_no_version.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_UUID_8) + def test_bom_v1_2_no_component_version(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_no_component_version(), schema_version=SchemaVersion.V1_2, + fixture='bom_setuptools_no_version.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_component_with_release_notes(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_with_release_notes(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_with_release_notes.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_3_component_with_release_notes(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_with_release_notes(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_component_with_vulnerability(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_with_vulnerabilities.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_3_component_with_vulnerability(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools.json' + ) + mock_1.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_1)) + def test_bom_v1_4_with_metadata_component(self, mock_1: Mock, mock_2: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_just_complete_metadata(), schema_version=SchemaVersion.V1_4, + fixture='bom_with_full_metadata.json' + ) + mock_1.assert_called() + mock_2.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_2)) + def test_bom_v1_3_with_metadata_component(self, mock_1: Mock, mock_2: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_just_complete_metadata(), schema_version=SchemaVersion.V1_3, + fixture='bom_with_full_metadata.json' + ) + mock_1.assert_called() + mock_2.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + def test_bom_v1_2_with_metadata_component(self, mock_1: Mock, mock_2: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_just_complete_metadata(), schema_version=SchemaVersion.V1_2, + fixture='bom_with_full_metadata.json' + ) + mock_1.assert_called() + mock_2.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_simple(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_4, + fixture='bom_services_simple.json' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_simple(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_3, + fixture='bom_services_simple.json' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_simple(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_2, + fixture='bom_services_simple.json' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_5)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_complex(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_4, + fixture='bom_services_complex.json' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_5)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_complex(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_3, + fixture='bom_services_complex.json' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_5)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_complex(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_2, + fixture='bom_services_complex.json' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_nested(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_4, + fixture='bom_services_nested.json' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_nested(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_3, + fixture='bom_services_nested.json' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_nested(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_json_bom( + bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_2, + fixture='bom_services_nested.json' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_dependencies(self, mock_1: Mock) -> None: + self._validate_json_bom( + bom=get_bom_with_dependencies_valid(), schema_version=SchemaVersion.V1_4, + fixture='bom_dependencies.json' + ) + mock_1.assert_called() + # -- + + # Helper methods + def _validate_json_bom(self, bom: Bom, schema_version: SchemaVersion, fixture: str) -> None: + bom.metadata.timestamp = fixed_date_time() + bom.validate() + + if schema_version != LATEST_SUPPORTED_SCHEMA_VERSION: + # Rewind the BOM to only have data supported by the SchemaVersion in question + outputter = get_instance(bom=bom, output_format=OutputFormat.JSON, schema_version=schema_version) + bom = cast(Bom, Bom.from_json(data=json.loads(outputter.output_as_string()))) + + with open( + join(dirname(__file__), f'fixtures/json/{schema_version.to_version()}/{fixture}')) as input_json: + deserialized_bom = cast(Bom, Bom.from_json(data=json.loads(input_json.read()))) + + self.assertEqual(bom.metadata, deserialized_bom.metadata) + + # This comparison fails for Dependencies despite the SortedSet's being identical + # self.assertEqual(bom.dependencies, deserialized_bom.dependencies) + self.assertSetEqual(set(bom.dependencies), set(deserialized_bom.dependencies)) + + self.assertEqual(bom.vulnerabilities, deserialized_bom.vulnerabilities) + + self.assertEqual(bom, deserialized_bom) diff --git a/tests/test_deserialize_xml.py b/tests/test_deserialize_xml.py new file mode 100644 index 00000000..4cd3e0d6 --- /dev/null +++ b/tests/test_deserialize_xml.py @@ -0,0 +1,709 @@ +# encoding: utf-8 + +# This file is part of CycloneDX Python Lib +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +from datetime import datetime +from os.path import dirname, join +from typing import cast +from unittest.mock import Mock, patch +from uuid import UUID +from xml.etree import ElementTree + +from cyclonedx.model.bom import Bom +from cyclonedx.output import LATEST_SUPPORTED_SCHEMA_VERSION, SchemaVersion, get_instance +from cyclonedx.schema import OutputFormat +from tests.base import BaseXmlTestCase +from tests.data import ( + MOCK_BOM_UUID_1, + MOCK_UUID_2, + MOCK_UUID_3, + MOCK_UUID_4, + MOCK_UUID_5, + MOCK_UUID_6, + TEST_UUIDS, + get_bom_for_issue_275_components, + get_bom_just_complete_metadata, + get_bom_with_component_setuptools_basic, + get_bom_with_component_setuptools_complete, + get_bom_with_component_setuptools_no_component_version, + get_bom_with_component_setuptools_with_cpe, + get_bom_with_component_setuptools_with_release_notes, + get_bom_with_component_setuptools_with_vulnerability, + get_bom_with_component_toml_1, + get_bom_with_dependencies_valid, + get_bom_with_external_references, + get_bom_with_metadata_component_and_dependencies, + get_bom_with_nested_services, + get_bom_with_services_complex, + get_bom_with_services_simple, +) + + +def fixed_date_time() -> datetime: + return datetime.fromisoformat('2023-01-07 13:44:32.312678+00:00') + + +@patch('cyclonedx.model.ThisTool._version', 'TESTING') +@patch('cyclonedx.model.bom.get_now_utc', fixed_date_time) +class TestDeserializeXml(BaseXmlTestCase): + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_external_references_v1_4(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_external_references(), schema_version=SchemaVersion.V1_4, + fixture='bom_external_references.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_external_references_v1_3(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_external_references(), schema_version=SchemaVersion.V1_3, + fixture='bom_external_references.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_external_references_v1_2(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_external_references(), schema_version=SchemaVersion.V1_2, + fixture='bom_external_references.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_external_references_v1_1(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_external_references(), schema_version=SchemaVersion.V1_1, + fixture='bom_external_references.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_external_references_v1_0(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_external_references(), schema_version=SchemaVersion.V1_0, + fixture='bom_empty.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_4(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_basic(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_3(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_basic(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_2(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_basic(), schema_version=SchemaVersion.V1_2, + fixture='bom_setuptools.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_1(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_basic(), schema_version=SchemaVersion.V1_1, + fixture='bom_setuptools.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_0(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_basic(), schema_version=SchemaVersion.V1_0, + fixture='bom_setuptools.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_4_with_cpe(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_cpe(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_with_cpe.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_3_with_cpe(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_cpe(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools_with_cpe.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_2_with_cpe(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_cpe(), schema_version=SchemaVersion.V1_2, + fixture='bom_setuptools_with_cpe.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_1_with_cpe(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_cpe(), schema_version=SchemaVersion.V1_1, + fixture='bom_setuptools_with_cpe.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_simple_bom_v1_0_with_cpe(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_cpe(), schema_version=SchemaVersion.V1_0, + fixture='bom_setuptools_with_cpe.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + def test_bom_v1_4_full_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_complete.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + def test_bom_v1_3_full_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools_complete.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_2)) + def test_bom_v1_2_full_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_2, + fixture='bom_setuptools_complete.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + def test_bom_v1_1_full_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_1, + fixture='bom_setuptools_complete.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + def test_bom_v1_0_full_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_0, + fixture='bom_setuptools_complete.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_component_hashes_external_references(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_toml_1(), schema_version=SchemaVersion.V1_4, + fixture='bom_toml_hashes_and_references.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_3_component_hashes_external_references(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_toml_1(), schema_version=SchemaVersion.V1_3, + fixture='bom_toml_hashes_and_references.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_2_component_hashes_external_references(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_toml_1(), schema_version=SchemaVersion.V1_2, + fixture='bom_toml_hashes_and_references.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_1_component_hashes_external_references(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_toml_1(), schema_version=SchemaVersion.V1_1, + fixture='bom_toml_hashes_and_references.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_0_component_hashes_external_references(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_toml_1(), schema_version=SchemaVersion.V1_0, + fixture='bom_toml_hashes_and_references.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_no_component_version(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_no_component_version(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_no_version.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_3_no_component_version(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_no_component_version(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools_no_version.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_2_no_component_version(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_no_component_version(), schema_version=SchemaVersion.V1_2, + fixture='bom_setuptools_no_version.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_1_no_component_version(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_no_component_version(), schema_version=SchemaVersion.V1_1, + fixture='bom_setuptools_no_version.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_0_no_component_version(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_no_component_version(), schema_version=SchemaVersion.V1_0, + fixture='bom_setuptools_no_version.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_component_with_release_notes(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_release_notes(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_with_release_notes.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_3_component_with_release_notes(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_release_notes(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_component_with_vulnerability(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_with_vulnerabilities.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_3_component_with_vulnerability(self, mock_uuid: Mock) -> None: + with self.assertRaises(ValueError): + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_3, + fixture='bom_setuptools_with_vulnerabilities.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_2_component_with_vulnerability(self, mock_uuid: Mock) -> None: + with self.assertRaises(ValueError): + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_2, + fixture='bom_setuptools_with_vulnerabilities.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_1_component_with_vulnerability(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_1, + fixture='bom_setuptools.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_0_component_with_vulnerability(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_0, + fixture='bom_setuptools.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + def test_bom_v1_4_with_metadata_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_just_complete_metadata(), schema_version=SchemaVersion.V1_4, + fixture='bom_with_full_metadata.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_5)) + def test_bom_v1_3_with_metadata_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_just_complete_metadata(), schema_version=SchemaVersion.V1_3, + fixture='bom_with_full_metadata.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + def test_bom_v1_2_with_metadata_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_just_complete_metadata(), schema_version=SchemaVersion.V1_2, + fixture='bom_with_full_metadata.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + def test_bom_v1_1_with_metadata_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_just_complete_metadata(), schema_version=SchemaVersion.V1_1, + fixture='bom_empty.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + def test_bom_v1_0_with_metadata_component(self, mock_bom_uuid: Mock, mock_bom_ref_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_just_complete_metadata(), schema_version=SchemaVersion.V1_0, + fixture='bom_empty.xml' + ) + mock_bom_uuid.assert_called() + mock_bom_ref_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_simple(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_4, + fixture='bom_services_simple.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_simple(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_3, + fixture='bom_services_simple.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_simple(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_2, + fixture='bom_services_simple.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_1_services_simple(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_1, + fixture='bom_empty.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_0_services_simple(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_0, + fixture='bom_empty.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_complex(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_4, + fixture='bom_services_complex.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_complex(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_3, + fixture='bom_services_complex.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_complex(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_2, + fixture='bom_services_complex.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_1_services_complex(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_1, + fixture='bom_empty.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_nested(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_4, + fixture='bom_services_nested.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_nested(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_3, + fixture='bom_services_nested.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_nested(self, mock_1: Mock, mock_2: Mock, mock_3: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_2, + fixture='bom_services_nested.xml' + ) + mock_1.assert_called() + mock_2.assert_called() + mock_3.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_dependencies(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_dependencies_valid(), schema_version=SchemaVersion.V1_4, + fixture='bom_dependencies.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_3_dependencies(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_dependencies_valid(), schema_version=SchemaVersion.V1_3, + fixture='bom_dependencies.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_2_dependencies(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_dependencies_valid(), schema_version=SchemaVersion.V1_2, + fixture='bom_dependencies.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_1_dependencies(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_dependencies_valid(), schema_version=SchemaVersion.V1_1, + fixture='bom_dependencies.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_dependencies_for_bom_component(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_metadata_component_and_dependencies(), schema_version=SchemaVersion.V1_4, + fixture='bom_dependencies_component.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_3_dependencies_for_bom_component(self, mock_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_metadata_component_and_dependencies(), schema_version=SchemaVersion.V1_3, + fixture='bom_dependencies_component.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_2_dependencies_for_bom_component(self, mock_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_metadata_component_and_dependencies(), schema_version=SchemaVersion.V1_2, + fixture='bom_dependencies_component.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_4_issue_275_components(self, mock_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_4, + fixture='bom_issue_275_components.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_3_issue_275_components(self, mock_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_3, + fixture='bom_issue_275_components.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_2_issue_275_components(self, mock_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_2, + fixture='bom_issue_275_components.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_1_issue_275_components(self, mock_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_1, + fixture='bom_issue_275_components.xml' + ) + mock_uuid.assert_called() + + @patch('cyclonedx.model.bom.uuid4', return_value=MOCK_BOM_UUID_1) + def test_bom_v1_0_issue_275_components(self, mock_uuid: Mock) -> None: + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_0, + fixture='bom_issue_275_components.xml' + ) + mock_uuid.assert_called() + + # Helper methods + def _validate_xml_bom(self, bom: Bom, schema_version: SchemaVersion, fixture: str) -> None: + bom.metadata.timestamp = fixed_date_time() + bom.validate() + + if schema_version != LATEST_SUPPORTED_SCHEMA_VERSION: + # Rewind the BOM to only have data supported by the SchemaVersion in question + outputter = get_instance(bom=bom, output_format=OutputFormat.XML, schema_version=schema_version) + bom = cast(Bom, Bom.from_xml(data=ElementTree.fromstring(outputter.output_as_string()))) + + with open(join(dirname(__file__), f'fixtures/xml/{schema_version.to_version()}/{fixture}')) as input_xml: + xml = input_xml.read() + deserialized_bom = cast(Bom, Bom.from_xml(data=ElementTree.fromstring(xml))) + + self.assertEqual(bom.metadata, deserialized_bom.metadata) + + # This comparison fails for Dependencies despite the SortedSet's being identical + # print(bom.dependencies.difference(deserialized_bom.dependencies)) + # self.assertEqual(bom.dependencies, deserialized_bom.dependencies) + # self.assertSetEqual(set(bom.dependencies), set(deserialized_bom.dependencies)) + + self.assertEqual(bom.vulnerabilities, deserialized_bom.vulnerabilities) + self.assertEqual(bom, deserialized_bom) diff --git a/tests/test_factory_license.py b/tests/test_factory_license.py index e8355104..31edfe55 100644 --- a/tests/test_factory_license.py +++ b/tests/test_factory_license.py @@ -30,57 +30,50 @@ class TestFactoryLicense(unittest.TestCase): def test_make_from_string_with_id(self) -> None: text = unittest.mock.NonCallableMock(spec=AttachedText) url = unittest.mock.NonCallableMock(spec=XsUri) - expected = License(spdx_license_id='bar', license_text=text, license_url=url) - factory = LicenseFactory() + expected = License(id='bar', text=text, url=url) with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value='bar'): - actual = factory.make_from_string('foo', license_text=text, license_url=url) + actual = LicenseFactory().make_from_string(name_or_spdx='foo', license_text=text, license_url=url) self.assertEqual(expected, actual) def test_make_from_string_with_name(self) -> None: text = unittest.mock.NonCallableMock(spec=AttachedText) url = unittest.mock.NonCallableMock(spec=XsUri) - expected = License(license_name='foo', license_text=text, license_url=url) - factory = LicenseFactory() + expected = License(name='foo', text=text, url=url) with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value=None): - actual = factory.make_from_string('foo', license_text=text, license_url=url) + actual = LicenseFactory().make_from_string(name_or_spdx='foo', license_text=text, license_url=url) self.assertEqual(expected, actual) def test_make_with_id(self) -> None: text = unittest.mock.NonCallableMock(spec=AttachedText) url = unittest.mock.NonCallableMock(spec=XsUri) - expected = License(spdx_license_id='bar', license_text=text, license_url=url) - factory = LicenseFactory() + expected = License(id='bar', text=text, url=url) with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value='bar'): - actual = factory.make_with_id('foo', license_text=text, license_url=url) + actual = LicenseFactory().make_with_id(spdx_id='foo', text=text, url=url) self.assertEqual(expected, actual) def test_make_with_id_raises(self) -> None: - factory = LicenseFactory() with self.assertRaises(InvalidSpdxLicenseException, msg='foo'): with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value=None): - factory.make_with_id('foo') + LicenseFactory().make_with_id(spdx_id='foo') def test_make_with_name(self) -> None: text = unittest.mock.NonCallableMock(spec=AttachedText) url = unittest.mock.NonCallableMock(spec=XsUri) - expected = License(license_name='foo', license_text=text, license_url=url) - factory = LicenseFactory() - - actual = factory.make_with_name('foo', license_text=text, license_url=url) - + expected = License(name='foo', text=text, url=url) + actual = LicenseFactory().make_with_name(name='foo', text=text, url=url) self.assertEqual(expected, actual) class TestFactoryLicenseChoice(unittest.TestCase): def test_make_from_string_with_compound_expression(self) -> None: - expected = LicenseChoice(license_expression='foo') + expected = LicenseChoice(expression='foo') factory = LicenseChoiceFactory(license_factory=unittest.mock.MagicMock(spec=LicenseFactory)) with unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=True): @@ -90,7 +83,7 @@ def test_make_from_string_with_compound_expression(self) -> None: def test_make_from_string_with_license(self) -> None: license_ = unittest.mock.NonCallableMock(spec=License) - expected = LicenseChoice(license_=license_) + expected = LicenseChoice(license=license_) license_factory = unittest.mock.MagicMock(spec=LicenseFactory) license_factory.make_from_string.return_value = license_ factory = LicenseChoiceFactory(license_factory=license_factory) @@ -103,7 +96,7 @@ def test_make_from_string_with_license(self) -> None: license_factory.make_from_string.assert_called_once_with('foo', license_text=None, license_url=None) def test_make_with_compound_expression(self) -> None: - expected = LicenseChoice(license_expression='foo') + expected = LicenseChoice(expression='foo') factory = LicenseChoiceFactory(license_factory=unittest.mock.MagicMock(spec=LicenseFactory)) with unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=True): @@ -121,7 +114,7 @@ def test_make_with_license(self) -> None: text = unittest.mock.NonCallableMock(spec=AttachedText) url = unittest.mock.NonCallableMock(spec=XsUri) license_ = unittest.mock.NonCallableMock(spec=License) - expected = LicenseChoice(license_=license_) + expected = LicenseChoice(license=license_) license_factory = unittest.mock.MagicMock(spec=LicenseFactory) license_factory.make_from_string.return_value = license_ factory = LicenseChoiceFactory(license_factory=license_factory) diff --git a/tests/test_model.py b/tests/test_model.py index f6289ef2..1b0558e5 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -158,10 +158,10 @@ def test_sort(self) -> None: # expected sort order: ([id], [name]) expected_order = [1, 0, 3, 2] licenses = [ - License(spdx_license_id='MIT'), - License(spdx_license_id='Apache-2.0'), - License(license_name='MIT'), - License(license_name='Apache-2.0'), + License(id='MIT'), + License(id='Apache-2.0'), + License(name='MIT'), + License(name='Apache-2.0'), ] sorted_licenses = sorted(licenses) expected_licenses = reorder(licenses, expected_order) @@ -171,16 +171,16 @@ def test_sort(self) -> None: class TestModelLicenseChoice(TestCase): def test_sort(self) -> None: - license_a = License(spdx_license_id='Apache-2.0') - license_b = License(spdx_license_id='MIT') + license_a = License(id='Apache-2.0') + license_b = License(id='MIT') # expected sort order: ([license], [expression]) expected_order = [1, 0, 3, 2] licenses = [ - LicenseChoice(license_=license_b), - LicenseChoice(license_=license_a), - LicenseChoice(license_expression='MIT'), - LicenseChoice(license_expression='Apache-2.0'), + LicenseChoice(license=license_b), + LicenseChoice(license=license_a), + LicenseChoice(expression='MIT'), + LicenseChoice(expression='Apache-2.0'), ] sorted_licenses = sorted(licenses) expected_licenses = reorder(licenses, expected_order) @@ -219,7 +219,7 @@ def test_sort(self) -> None: class TestModelExternalReference(TestCase): def test_external_reference_with_xsuri(self) -> None: - e = ExternalReference(reference_type=ExternalReferenceType.VCS, url=XsUri('https://www.google.com')) + e = ExternalReference(type=ExternalReferenceType.VCS, url=XsUri('https://www.google.com')) self.assertEqual(e.type, ExternalReferenceType.VCS) self.assertEqual(e.url, XsUri('https://www.google.com')) self.assertIsNone(e.comment) @@ -227,12 +227,12 @@ def test_external_reference_with_xsuri(self) -> None: def test_same(self) -> None: ref_1 = ExternalReference( - reference_type=ExternalReferenceType.OTHER, + type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org'), comment='No comment' ) ref_2 = ExternalReference( - reference_type=ExternalReferenceType.OTHER, + type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org'), comment='No comment' ) @@ -242,12 +242,12 @@ def test_same(self) -> None: def test_not_same(self) -> None: ref_1 = ExternalReference( - reference_type=ExternalReferenceType.OTHER, + type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org'), comment='No comment' ) ref_2 = ExternalReference( - reference_type=ExternalReferenceType.OTHER, + type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org/'), comment='No comment' ) @@ -259,12 +259,12 @@ def test_sort(self) -> None: # expected sort order: (type, url, [comment]) expected_order = [5, 4, 0, 1, 3, 2] refs = [ - ExternalReference(reference_type=ExternalReferenceType.OTHER, url=XsUri('a'), comment='a'), - ExternalReference(reference_type=ExternalReferenceType.OTHER, url=XsUri('a'), comment='b'), - ExternalReference(reference_type=ExternalReferenceType.OTHER, url=XsUri('b')), - ExternalReference(reference_type=ExternalReferenceType.OTHER, url=XsUri('b'), comment='a'), - ExternalReference(reference_type=ExternalReferenceType.LICENSE, url=XsUri('b'), comment='a'), - ExternalReference(reference_type=ExternalReferenceType.BUILD_SYSTEM, url=XsUri('b'), comment='a'), + ExternalReference(type=ExternalReferenceType.OTHER, url=XsUri('a'), comment='a'), + ExternalReference(type=ExternalReferenceType.OTHER, url=XsUri('a'), comment='b'), + ExternalReference(type=ExternalReferenceType.OTHER, url=XsUri('b')), + ExternalReference(type=ExternalReferenceType.OTHER, url=XsUri('b'), comment='a'), + ExternalReference(type=ExternalReferenceType.LICENSE, url=XsUri('b'), comment='a'), + ExternalReference(type=ExternalReferenceType.BUILD_SYSTEM, url=XsUri('b'), comment='a'), ] sorted_refs = sorted(refs) expected_refs = reorder(refs, expected_order) @@ -291,12 +291,12 @@ def test_sort(self) -> None: # expected sort order: (alg, content) expected_order = [5, 3, 4, 1, 2, 0] hashes = [ - HashType(algorithm=HashAlgorithm.SHA_256, hash_value='c'), - HashType(algorithm=HashAlgorithm.SHA_256, hash_value='a'), - HashType(algorithm=HashAlgorithm.SHA_256, hash_value='b'), - HashType(algorithm=HashAlgorithm.SHA_1, hash_value='a'), - HashType(algorithm=HashAlgorithm.SHA_1, hash_value='b'), - HashType(algorithm=HashAlgorithm.MD5, hash_value='a'), + HashType(alg=HashAlgorithm.SHA_256, content='c'), + HashType(alg=HashAlgorithm.SHA_256, content='a'), + HashType(alg=HashAlgorithm.SHA_256, content='b'), + HashType(alg=HashAlgorithm.SHA_1, content='a'), + HashType(alg=HashAlgorithm.SHA_1, content='b'), + HashType(alg=HashAlgorithm.MD5, content='a'), ] sorted_hashes = sorted(hashes) expected_hashes = reorder(hashes, expected_order) @@ -347,7 +347,7 @@ class TestModelIssueType(TestCase): def test_issue_type(self) -> None: it = IssueType( - classification=IssueClassification.SECURITY, id_='CVE-2021-44228', name='Apache Log3Shell', + type=IssueClassification.SECURITY, id='CVE-2021-44228', name='Apache Log3Shell', description='Apache Log4j2 2.0-beta9 through 2.12.1 and 2.13.0 through 2.15.0 JNDI features used in ' 'configuration, log messages, and parameters do not protect against attacker controlled LDAP ' 'and other JNDI related endpoints. An attacker who can control log messages or log message ' @@ -404,7 +404,7 @@ def test_note_encoded_text_with_locale(self) -> None: n = Note( text=NoteText( - content=text_content, content_type='text/plain; charset=UTF-8', content_encoding=Encoding.BASE_64 + content=text_content, content_type='text/plain; charset=UTF-8', encoding=Encoding.BASE_64 ), locale='en-GB' ) self.assertEqual(n.text.content, text_content) @@ -441,7 +441,7 @@ def test_sort(self) -> None: NoteText(content='b', content_type='a'), NoteText(content='b', content_type='b'), NoteText(content='b', content_type='c'), - NoteText(content='b', content_type='a', content_encoding=Encoding.BASE_64), + NoteText(content='b', content_type='a', encoding=Encoding.BASE_64), ] sorted_notes = sorted(notes) expected_notes = reorder(notes, expected_order) diff --git a/tests/test_model_bom.py b/tests/test_model_bom.py index e182dd5a..6e702dd8 100644 --- a/tests/test_model_bom.py +++ b/tests/test_model_bom.py @@ -18,9 +18,19 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. from unittest import TestCase - -from cyclonedx.model import License, LicenseChoice, OrganizationalContact, OrganizationalEntity, Property -from cyclonedx.model.bom import Bom, BomMetaData, ThisTool, Tool +from uuid import uuid4 + +from cyclonedx.model import ( + License, + LicenseChoice, + OrganizationalContact, + OrganizationalEntity, + Property, + ThisTool, + Tool, +) +from cyclonedx.model.bom import Bom, BomMetaData +from cyclonedx.model.bom_ref import BomRef from cyclonedx.model.component import Component, ComponentType from .data import ( @@ -58,12 +68,12 @@ def test_basic_bom_metadata(self) -> None: manufacturer = OrganizationalEntity(name='test_manufacturer') supplier = OrganizationalEntity(name='test_supplier') licenses = [ - LicenseChoice(license_=License(spdx_license_id='MIT')), - LicenseChoice(license_=License(spdx_license_id='Apache-2.0')), + LicenseChoice(license=License(id='MIT')), + LicenseChoice(license=License(id='Apache-2.0')), ] properties = [ Property(name='property_1', value='value_1'), - Property(name='property_2', value='value_2',) + Property(name='property_2', value='value_2', ) ] metadata = BomMetaData(tools=tools, authors=authors, component=component, @@ -100,28 +110,56 @@ def test_bom_metadata_tool_multiple_tools(self) -> None: bom.metadata.tools.add( Tool(vendor='TestVendor', name='TestTool', version='0.0.0') ) + self.assertEqual(bom.version, 1) self.assertEqual(len(bom.metadata.tools), 2) def test_metadata_component(self) -> None: metadata = Bom().metadata self.assertTrue(metadata.component is None) - hextech = Component(name='Hextech', version='1.0.0', component_type=ComponentType.LIBRARY) + hextech = Component(name='Hextech', version='1.0.0', type=ComponentType.LIBRARY) metadata.component = hextech self.assertFalse(metadata.component is None) self.assertEqual(metadata.component, hextech) def test_empty_bom(self) -> None: bom = Bom() - self.assertIsNotNone(bom.uuid) + self.assertEqual(bom.version, 1) + self.assertIsNotNone(bom.serial_number) self.assertIsNotNone(bom.metadata) self.assertFalse(bom.components) self.assertFalse(bom.services) self.assertFalse(bom.external_references) + def test_empty_bom_defined_serial(self) -> None: + serial_number = uuid4() + bom = Bom(serial_number=serial_number) + self.assertEqual(bom.serial_number, serial_number) + self.assertEqual(bom.get_urn_uuid(), serial_number.urn) + self.assertEqual(bom.version, 1) + self.assertEqual(bom.urn(), f'urn:cdx:{serial_number}/1') + + def test_empty_bom_defined_serial_and_version(self) -> None: + serial_number = uuid4() + bom = Bom(serial_number=serial_number, version=2) + self.assertEqual(bom.serial_number, serial_number) + self.assertEqual(bom.get_urn_uuid(), serial_number.urn) + self.assertEqual(bom.version, 2) + self.assertEqual(bom.urn(), f'urn:cdx:{serial_number}/2') + def test_bom_with_vulnerabilities(self) -> None: bom = get_bom_with_component_setuptools_with_vulnerability() self.assertTrue(bom.has_vulnerabilities()) + def test_bom_get_vulnerabilities_by_bom_ref(self) -> None: + bom = get_bom_with_component_setuptools_with_vulnerability() + vulns = bom.get_vulnerabilities_for_bom_ref(bom_ref=BomRef(value='pkg:pypi/setuptools@50.3.2?extension=tar.gz')) + self.assertEqual(len(vulns), 1) + + def test_bom_get_vulnerabilities_by_bom_ref_negative(self) -> None: + bom = get_bom_with_component_setuptools_with_vulnerability() + vulns = bom.get_vulnerabilities_for_bom_ref(bom_ref=BomRef(value='pkg:pypi/setuptools@50.3.1?extension=tar.gz')) + self.assertEqual(len(vulns), 0) + def test_bom_nested_components_issue_275(self) -> None: """regression test for issue #275 see https://github.com/CycloneDX/cyclonedx-python-lib/issues/275 diff --git a/tests/test_model_component.py b/tests/test_model_component.py index 60ec95c0..eeb2fda6 100644 --- a/tests/test_model_component.py +++ b/tests/test_model_component.py @@ -44,8 +44,8 @@ Pedigree, ) from cyclonedx.model.issue import IssueClassification, IssueType - -from .data import ( +from tests.data import ( + MOCK_UUID_7, get_component_setuptools_simple, get_component_setuptools_simple_no_version, get_component_toml_with_hashes_with_references, @@ -107,11 +107,9 @@ def test_sort(self) -> None: class TestModelComponent(TestCase): - @patch('cyclonedx.model.bom_ref.uuid4', return_value='6f266d1c-760f-4552-ae3b-41a9b74232fa') + @patch('cyclonedx.model.component.uuid4', return_value=MOCK_UUID_7) def test_empty_basic_component(self, mock_uuid: Mock) -> None: - c = Component( - name='test-component', version='1.2.3' - ) + c = Component(name='test-component') mock_uuid.assert_called() self.assertEqual(c.name, 'test-component') self.assertEqual(c.type, ComponentType.LIBRARY) @@ -121,7 +119,7 @@ def test_empty_basic_component(self, mock_uuid: Mock) -> None: self.assertIsNone(c.author) self.assertIsNone(c.publisher) self.assertIsNone(c.group) - self.assertEqual(c.version, '1.2.3') + self.assertIsNone(c.version) self.assertIsNone(c.description) self.assertIsNone(c.scope) self.assertSetEqual(c.hashes, set()) @@ -131,88 +129,68 @@ def test_empty_basic_component(self, mock_uuid: Mock) -> None: self.assertSetEqual(c.external_references, set()) self.assertFalse(c.properties) self.assertIsNone(c.release_notes) - self.assertEqual(0, len(c.components)) - self.assertEqual(0, len(c.get_all_nested_components())) - self.assertEqual(len(c.get_vulnerabilities()), 0) + self.assertEqual(len(c.components), 0) + self.assertEqual(len(c.get_all_nested_components(include_self=True)), 1) - @patch('cyclonedx.model.bom_ref.uuid4', return_value='6f266d1c-760f-4552-ae3b-41a9b74232fa') + @patch('cyclonedx.model.component.uuid4', return_value=MOCK_UUID_7) def test_multiple_basic_components(self, mock_uuid: Mock) -> None: - c1 = Component( - name='test-component', version='1.2.3' - ) + c1 = Component(name='test-component') self.assertEqual(c1.name, 'test-component') - self.assertEqual(c1.version, '1.2.3') + self.assertIsNone(c1.version) self.assertEqual(c1.type, ComponentType.LIBRARY) self.assertEqual(len(c1.external_references), 0) self.assertEqual(len(c1.hashes), 0) - self.assertEqual(len(c1.get_vulnerabilities()), 0) - c2 = Component( - name='test2-component', version='3.2.1' - ) + c2 = Component(name='test2-component') self.assertEqual(c2.name, 'test2-component') - self.assertEqual(c2.version, '3.2.1') + self.assertIsNone(c2.version) self.assertEqual(c2.type, ComponentType.LIBRARY) self.assertEqual(len(c2.external_references), 0) self.assertEqual(len(c2.hashes), 0) - self.assertEqual(len(c2.get_vulnerabilities()), 0) self.assertNotEqual(c1, c2) mock_uuid.assert_called() def test_external_references(self) -> None: - c = Component( - name='test-component', version='1.2.3' - ) + c = Component(name='test-component') c.external_references.add(ExternalReference( - reference_type=ExternalReferenceType.OTHER, + type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org'), comment='No comment' )) self.assertEqual(c.name, 'test-component') - self.assertEqual(c.version, '1.2.3') + self.assertIsNone(c.version) self.assertEqual(c.type, ComponentType.LIBRARY) self.assertEqual(len(c.external_references), 1) self.assertEqual(len(c.hashes), 0) - self.assertEqual(len(c.get_vulnerabilities()), 0) - c2 = Component( - name='test2-component', version='3.2.1' - ) + c2 = Component(name='test2-component') self.assertEqual(c2.name, 'test2-component') - self.assertEqual(c2.version, '3.2.1') + self.assertIsNone(c2.version) self.assertEqual(c2.type, ComponentType.LIBRARY) self.assertEqual(len(c2.external_references), 0) self.assertEqual(len(c2.hashes), 0) - self.assertEqual(len(c2.get_vulnerabilities()), 0) - def test_empty_basic_component_no_version(self) -> None: - c = Component( - name='test-component' - ) + def test_empty_component_with_version(self) -> None: + c = Component(name='test-component', version='1.2.3') self.assertEqual(c.name, 'test-component') - self.assertIsNone(c.version, None) + self.assertEqual(c.version, '1.2.3') self.assertEqual(c.type, ComponentType.LIBRARY) self.assertEqual(len(c.external_references), 0) self.assertEqual(len(c.hashes), 0) - self.assertEqual(len(c.get_vulnerabilities()), 0) def test_component_equal_1(self) -> None: - c = Component( - name='test-component', version='1.2.3' - ) + c = Component(name='test-component') c.external_references.add(ExternalReference( - reference_type=ExternalReferenceType.OTHER, + type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org'), comment='No comment' )) - c2 = Component( - name='test-component', version='1.2.3' - ) + c2 = Component(name='test-component') c2.external_references.add(ExternalReference( - reference_type=ExternalReferenceType.OTHER, + type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org'), comment='No comment' )) @@ -278,63 +256,36 @@ def test_not_same_1(self) -> None: self.assertNotEqual(hash(c1), hash(c2)) self.assertFalse(c1 == c2) - def test_zero_dependencies(self) -> None: - self.assertSetEqual(get_component_setuptools_simple_no_version().dependencies, set()) - - def test_with_dependencies(self) -> None: - c = get_component_setuptools_simple_no_version() - c.dependencies.update([ - get_component_setuptools_simple_no_version().bom_ref, - get_component_toml_with_hashes_with_references().bom_ref - ]) - self.assertEqual(len(c.dependencies), 2) - self.assertTrue(get_component_setuptools_simple_no_version().bom_ref in c.dependencies) - self.assertTrue(get_component_toml_with_hashes_with_references().bom_ref in c.dependencies) - - def test_with_duplicate_dependencies(self) -> None: - c = get_component_setuptools_simple_no_version() - c.dependencies.update([ - get_component_setuptools_simple_no_version().bom_ref, - get_component_toml_with_hashes_with_references().bom_ref, - get_component_setuptools_simple_no_version().bom_ref, - get_component_toml_with_hashes_with_references().bom_ref - ]) - self.assertEqual(len(c.dependencies), 2) - self.assertTrue(get_component_setuptools_simple_no_version().bom_ref in c.dependencies) - self.assertTrue(get_component_toml_with_hashes_with_references().bom_ref in c.dependencies) - def test_sort(self) -> None: # expected sort order: (type, [group], name, [version]) expected_order = [6, 4, 5, 3, 2, 1, 0] components = [ - Component(name='component-c', component_type=ComponentType.LIBRARY), - Component(name='component-a', component_type=ComponentType.LIBRARY), - Component(name='component-b', component_type=ComponentType.LIBRARY, group='group-2'), - Component(name='component-a', component_type=ComponentType.LIBRARY, group='group-2'), - Component(name='component-a', component_type=ComponentType.FILE), - Component(name='component-b', component_type=ComponentType.FILE), - Component(name='component-a', component_type=ComponentType.FILE, version="1.0.0"), + Component(name='component-c', type=ComponentType.LIBRARY), + Component(name='component-a', type=ComponentType.LIBRARY), + Component(name='component-b', type=ComponentType.LIBRARY, group='group-2'), + Component(name='component-a', type=ComponentType.LIBRARY, group='group-2'), + Component(name='component-a', type=ComponentType.FILE), + Component(name='component-b', type=ComponentType.FILE), + Component(name='component-a', type=ComponentType.FILE, version="1.0.0"), ] sorted_components = sorted(components) expected_components = reorder(components, expected_order) self.assertListEqual(sorted_components, expected_components) def test_nested_components_1(self) -> None: - comp_b = Component(name="comp_b", version="1.0.0") - comp_c = Component(name="comp_c", version="1.0.0") + comp_b = Component(name="comp_b") + comp_c = Component(name="comp_c") comp_b.components.add(comp_c) - comp_b.dependencies.add(comp_c.bom_ref) self.assertEqual(1, len(comp_b.components)) self.assertEqual(2, len(comp_b.get_all_nested_components(include_self=True))) self.assertEqual(1, len(comp_b.get_all_nested_components(include_self=False))) def test_nested_components_2(self) -> None: - comp_a = Component(name="comp_a", version="1.2.3") - comp_b = Component(name="comp_b", version="1.0.0") - comp_c = Component(name="comp_c", version="1.0.0") + comp_a = Component(name="comp_a") + comp_b = Component(name="comp_b") + comp_c = Component(name="comp_c") comp_b.components.add(comp_c) - comp_b.dependencies.add(comp_c.bom_ref) comp_b.components.add(comp_a) self.assertEqual(2, len(comp_b.components)) @@ -349,20 +300,20 @@ def test_no_params(self) -> None: ComponentEvidence() def test_same_1(self) -> None: - ce_1 = ComponentEvidence(copyright_=[Copyright(text='Commercial')]) - ce_2 = ComponentEvidence(copyright_=[Copyright(text='Commercial')]) + ce_1 = ComponentEvidence(copyright=[Copyright(text='Commercial')]) + ce_2 = ComponentEvidence(copyright=[Copyright(text='Commercial')]) self.assertEqual(hash(ce_1), hash(ce_2)) self.assertTrue(ce_1 == ce_2) def test_same_2(self) -> None: - ce_1 = ComponentEvidence(copyright_=[Copyright(text='Commercial'), Copyright(text='Commercial 2')]) - ce_2 = ComponentEvidence(copyright_=[Copyright(text='Commercial 2'), Copyright(text='Commercial')]) + ce_1 = ComponentEvidence(copyright=[Copyright(text='Commercial'), Copyright(text='Commercial 2')]) + ce_2 = ComponentEvidence(copyright=[Copyright(text='Commercial 2'), Copyright(text='Commercial')]) self.assertEqual(hash(ce_1), hash(ce_2)) self.assertTrue(ce_1 == ce_2) def test_not_same_1(self) -> None: - ce_1 = ComponentEvidence(copyright_=[Copyright(text='Commercial')]) - ce_2 = ComponentEvidence(copyright_=[Copyright(text='Commercial 2')]) + ce_1 = ComponentEvidence(copyright=[Copyright(text='Commercial')]) + ce_2 = ComponentEvidence(copyright=[Copyright(text='Commercial 2')]) self.assertNotEqual(hash(ce_1), hash(ce_2)) self.assertFalse(ce_1 == ce_2) @@ -427,11 +378,11 @@ class TestModelPatch(TestCase): def test_same_1(self) -> None: p1 = Patch( - type_=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), + type=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), resolves=[get_issue_1(), get_issue_2()] ) p2 = Patch( - type_=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), + type=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), resolves=[get_issue_2(), get_issue_1()] ) self.assertEqual(hash(p1), hash(p2)) @@ -442,11 +393,11 @@ def test_multiple_times_same(self) -> None: i = 0 while i < 1000: p1 = Patch( - type_=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), + type=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), resolves=[get_issue_1(), get_issue_2()] ) p2 = Patch( - type_=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), + type=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), resolves=[get_issue_2(), get_issue_1(), get_issue_1(), get_issue_1(), get_issue_2()] ) self.assertEqual(hash(p1), hash(p2)) @@ -457,11 +408,11 @@ def test_multiple_times_same(self) -> None: def test_not_same_1(self) -> None: p1 = Patch( - type_=PatchClassification.MONKEY, diff=Diff(url=XsUri('https://cyclonedx.org/')), + type=PatchClassification.MONKEY, diff=Diff(url=XsUri('https://cyclonedx.org/')), resolves=[get_issue_1(), get_issue_2()] ) p2 = Patch( - type_=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), + type=PatchClassification.BACKPORT, diff=Diff(url=XsUri('https://cyclonedx.org')), resolves=[get_issue_2(), get_issue_1()] ) self.assertNotEqual(hash(p1), hash(p2)) @@ -473,19 +424,19 @@ def test_sort(self) -> None: diff_b = Diff(text=AttachedText(content='b')) resolves_a = [ - IssueType(classification=IssueClassification.DEFECT), - IssueType(classification=IssueClassification.SECURITY) + IssueType(type=IssueClassification.DEFECT), + IssueType(type=IssueClassification.SECURITY) ] # expected sort order: (type, [diff], sorted(resolves)) expected_order = [5, 4, 2, 3, 1, 0] patches = [ - Patch(type_=PatchClassification.MONKEY), - Patch(type_=PatchClassification.MONKEY, diff=diff_b), - Patch(type_=PatchClassification.MONKEY, diff=diff_a), - Patch(type_=PatchClassification.MONKEY, diff=diff_a, resolves=resolves_a), - Patch(type_=PatchClassification.BACKPORT), - Patch(type_=PatchClassification.BACKPORT, diff=diff_a), + Patch(type=PatchClassification.MONKEY), + Patch(type=PatchClassification.MONKEY, diff=diff_b), + Patch(type=PatchClassification.MONKEY, diff=diff_a), + Patch(type=PatchClassification.MONKEY, diff=diff_a, resolves=resolves_a), + Patch(type=PatchClassification.BACKPORT), + Patch(type=PatchClassification.BACKPORT, diff=diff_a), ] sorted_patches = sorted(patches) expected_patches = reorder(patches, expected_order) diff --git a/tests/test_model_dependency.py b/tests/test_model_dependency.py new file mode 100644 index 00000000..40186a70 --- /dev/null +++ b/tests/test_model_dependency.py @@ -0,0 +1,45 @@ +# encoding: utf-8 + +# This file is part of CycloneDX Python Lib +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +from unittest import TestCase + +from cyclonedx.model.bom_ref import BomRef +from cyclonedx.model.dependency import Dependency +from tests.data import reorder + + +class TestDependency(TestCase): + + def test_sort(self) -> None: + # expected sort order: (value) + expected_order = [3, 2, 0, 1] + deps = [ + Dependency(ref=BomRef(value="be2c6502-7e9a-47db-9a66-e34f729810a3"), dependencies=[ + Dependency(ref=BomRef(value="0b049d09-64c0-4490-a0f5-c84d9aacf857")), + Dependency(ref=BomRef(value="17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda")) + ]), + Dependency(ref=BomRef(value="cd3e9c95-9d41-49e7-9924-8cf0465ae789")), + Dependency(ref=BomRef(value="17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda")), + Dependency(ref=BomRef(value="0b049d09-64c0-4490-a0f5-c84d9aacf857"), dependencies=[ + Dependency(ref=BomRef(value="cd3e9c95-9d41-49e7-9924-8cf0465ae789")) + ]) + ] + sorted_deps = sorted(deps) + expected_deps = reorder(deps, expected_order) + self.assertEqual(sorted_deps, expected_deps) diff --git a/tests/test_model_issue.py b/tests/test_model_issue.py index d6f7a79c..2d65d731 100644 --- a/tests/test_model_issue.py +++ b/tests/test_model_issue.py @@ -49,13 +49,13 @@ def test_sort(self) -> None: # expected sort order: (type/classification, id, name, description, source) expected_order = [6, 5, 0, 1, 2, 3, 4] issues = [ - IssueType(classification=IssueClassification.SECURITY, id_='a', name='a', description='a', source=source_a), - IssueType(classification=IssueClassification.SECURITY, id_='a', name='a', description='a', source=source_b), - IssueType(classification=IssueClassification.SECURITY, id_='a', name='a', description='a'), - IssueType(classification=IssueClassification.SECURITY, id_='a', name='a'), - IssueType(classification=IssueClassification.SECURITY, id_='a'), - IssueType(classification=IssueClassification.DEFECT, id_='a', name='a', description='a', source=source_b), - IssueType(classification=IssueClassification.DEFECT, id_='a', name='a', description='a', source=source_a), + IssueType(type=IssueClassification.SECURITY, id='a', name='a', description='a', source=source_a), + IssueType(type=IssueClassification.SECURITY, id='a', name='a', description='a', source=source_b), + IssueType(type=IssueClassification.SECURITY, id='a', name='a', description='a'), + IssueType(type=IssueClassification.SECURITY, id='a', name='a'), + IssueType(type=IssueClassification.SECURITY, id='a'), + IssueType(type=IssueClassification.DEFECT, id='a', name='a', description='a', source=source_b), + IssueType(type=IssueClassification.DEFECT, id='a', name='a', description='a', source=source_a), ] sorted_issues = sorted(issues) expected_issues = reorder(issues, expected_order) diff --git a/tests/test_model_release_note.py b/tests/test_model_release_note.py index df7dff22..a06025af 100644 --- a/tests/test_model_release_note.py +++ b/tests/test_model_release_note.py @@ -27,7 +27,7 @@ class TestModelReleaseNote(TestCase): def test_simple(self) -> None: - rn = ReleaseNotes(type_='major') + rn = ReleaseNotes(type='major') self.assertEqual(rn.type, 'major') self.assertIsNone(rn.title) self.assertIsNone(rn.featured_image) @@ -43,7 +43,7 @@ def test_simple(self) -> None: def test_complete(self) -> None: timestamp: datetime.datetime = datetime.datetime.utcnow() rn = ReleaseNotes( - type_='major', title="Release Notes Title", + type='major', title="Release Notes Title", featured_image=XsUri('https://cyclonedx.org/theme/assets/images/CycloneDX-Twitter-Card.png'), social_image=XsUri('https://cyclonedx.org/cyclonedx-icon.png'), description="This release is a test release", timestamp=timestamp, diff --git a/tests/test_model_service.py b/tests/test_model_service.py index 9a75cd8f..909aa2b1 100644 --- a/tests/test_model_service.py +++ b/tests/test_model_service.py @@ -21,18 +21,17 @@ from unittest.mock import Mock, patch from cyclonedx.model.service import Service - -from .data import reorder +from tests.data import MOCK_UUID_8, MOCK_UUID_9, reorder class TestModelService(TestCase): - @patch('cyclonedx.model.bom_ref.uuid4', return_value='77d15ab9-5602-4cca-8ed2-59ae579aafd3') + @patch('cyclonedx.model.service.uuid4', return_value=MOCK_UUID_8) def test_minimal_service(self, mock_uuid: Mock) -> None: s = Service(name='my-test-service') mock_uuid.assert_called() self.assertEqual(s.name, 'my-test-service') - self.assertEqual(str(s.bom_ref), '77d15ab9-5602-4cca-8ed2-59ae579aafd3') + self.assertEqual(str(s.bom_ref), str(MOCK_UUID_8)) self.assertIsNone(s.provider) self.assertIsNone(s.group) self.assertIsNone(s.version) @@ -47,7 +46,7 @@ def test_minimal_service(self, mock_uuid: Mock) -> None: self.assertFalse(s.release_notes) self.assertFalse(s.properties) - @patch('cyclonedx.model.bom_ref.uuid4', return_value='859ff614-35a7-4d37-803b-d89130cb2577') + @patch('cyclonedx.model.service.uuid4', return_value=MOCK_UUID_9) def test_service_with_services(self, mock_uuid: Mock) -> None: parent_service = Service(name='parent-service') parent_service.services = [ @@ -56,7 +55,7 @@ def test_service_with_services(self, mock_uuid: Mock) -> None: ] mock_uuid.assert_called() self.assertEqual(parent_service.name, 'parent-service') - self.assertEqual(str(parent_service.bom_ref), '859ff614-35a7-4d37-803b-d89130cb2577') + self.assertEqual(str(parent_service.bom_ref), str(MOCK_UUID_9)) self.assertIsNone(parent_service.provider) self.assertIsNone(parent_service.group) self.assertIsNone(parent_service.version) diff --git a/tests/test_model_vulnerability.py b/tests/test_model_vulnerability.py index 849a8d40..c3e33134 100644 --- a/tests/test_model_vulnerability.py +++ b/tests/test_model_vulnerability.py @@ -17,7 +17,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -import unittest from datetime import datetime, timedelta from decimal import Decimal from unittest import TestCase @@ -36,27 +35,11 @@ VulnerabilitySeverity, VulnerabilitySource, ) - -from .data import reorder +from tests.data import MOCK_UUID_10, reorder class TestModelVulnerability(TestCase): - @unittest.skip('Deprecated in Schema Version 1.4') - def test_v_rating_scores_empty(self) -> None: - vr = VulnerabilityRating() - self.assertFalse(vr.score) - - @unittest.skip('Deprecated in Schema Version 1.4') - def test_v_rating_scores_base_only(self) -> None: - vr = VulnerabilityRating(score_base=1.0) - self.assertTrue(vr.score) - - @unittest.skip('Deprecated in Schema Version 1.4') - def test_v_rating_scores_all(self) -> None: - vr = VulnerabilityRating(score_base=1.0, score_impact=3.5, score_exploitability=5.6) - self.assertTrue(vr.score) - def test_v_severity_from_cvss_scores_single_critical(self) -> None: self.assertEqual( VulnerabilitySeverity.get_from_cvss_scores(9.1), @@ -186,11 +169,11 @@ def test_v_source_get_localised_vector_other_2(self) -> None: 'SOMETHING_OR_OTHER' ) - @patch('cyclonedx.model.bom_ref.uuid4', return_value='0afa65bc-4acd-428b-9e17-8e97b1969745') + @patch('cyclonedx.model.vulnerability.uuid4', return_value=MOCK_UUID_10) def test_empty_vulnerability(self, mock_uuid: Mock) -> None: v = Vulnerability() mock_uuid.assert_called() - self.assertEqual(str(v.bom_ref), '0afa65bc-4acd-428b-9e17-8e97b1969745') + self.assertEqual(str(v.bom_ref), str(MOCK_UUID_10)) self.assertIsNone(v.id) self.assertIsNone(v.source) self.assertFalse(v.references) @@ -338,8 +321,8 @@ def test_sort(self) -> None: BomTargetVersionRange(version='1.0.0'), BomTargetVersionRange(version='2.0.0', status=ImpactAnalysisAffectedStatus.AFFECTED), BomTargetVersionRange(version='1.0.0', status=ImpactAnalysisAffectedStatus.UNAFFECTED), - BomTargetVersionRange(version_range='1.0.0 - 2.0.0', status=ImpactAnalysisAffectedStatus.UNAFFECTED), - BomTargetVersionRange(version_range='2.0.0 - 2.1.0', status=ImpactAnalysisAffectedStatus.AFFECTED), + BomTargetVersionRange(range='1.0.0 - 2.0.0', status=ImpactAnalysisAffectedStatus.UNAFFECTED), + BomTargetVersionRange(range='2.0.0 - 2.1.0', status=ImpactAnalysisAffectedStatus.AFFECTED), ] sorted_ranges = sorted(ranges) expected_ranges = reorder(ranges, expected_order) diff --git a/tests/test_output_generic.py b/tests/test_output_generic.py index f7d7b041..7325cbee 100644 --- a/tests/test_output_generic.py +++ b/tests/test_output_generic.py @@ -21,8 +21,9 @@ from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component -from cyclonedx.output import OutputFormat, SchemaVersion, get_instance +from cyclonedx.output import get_instance from cyclonedx.output.xml import XmlV1Dot3, XmlV1Dot4 +from cyclonedx.schema import OutputFormat, SchemaVersion class TestOutputGeneric(TestCase): diff --git a/tests/test_output_json.py b/tests/test_output_json.py index 1118d7c3..507770c0 100644 --- a/tests/test_output_json.py +++ b/tests/test_output_json.py @@ -16,25 +16,24 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. - -from os.path import join +from os.path import dirname, join from unittest.mock import Mock, patch +from uuid import UUID -from cyclonedx.exception.model import UnknownComponentDependencyException from cyclonedx.exception.output import FormatNotSupportedException from cyclonedx.model.bom import Bom -from cyclonedx.output import OutputFormat, SchemaVersion, get_instance - -from . import FIXTURES_DIRECTORY, RECREATE_SNAPSHOTS -from .base import BaseJsonTestCase -from .data import ( +from cyclonedx.output import get_instance +from cyclonedx.schema import OutputFormat, SchemaVersion +from tests.base import BaseJsonTestCase +from tests.data import ( MOCK_UUID_1, MOCK_UUID_2, MOCK_UUID_3, + MOCK_UUID_4, + MOCK_UUID_5, MOCK_UUID_6, TEST_UUIDS, get_bom_for_issue_275_components, - get_bom_for_issue_328_components, get_bom_just_complete_metadata, get_bom_with_component_setuptools_basic, get_bom_with_component_setuptools_complete, @@ -43,7 +42,6 @@ get_bom_with_component_setuptools_with_release_notes, get_bom_with_component_setuptools_with_vulnerability, get_bom_with_component_toml_1, - get_bom_with_dependencies_invalid, get_bom_with_dependencies_valid, get_bom_with_external_references, get_bom_with_metadata_component_and_dependencies, @@ -120,7 +118,7 @@ def test_simple_bom_v1_2_with_cpe(self) -> None: fixture='bom_setuptools_with_cpe.json' ) - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_6) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) def test_bom_v1_4_full_component(self, mock: Mock) -> None: self.maxDiff = None self._validate_json_bom( @@ -129,7 +127,7 @@ def test_bom_v1_4_full_component(self, mock: Mock) -> None: ) mock.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_6) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) def test_bom_v1_3_full_component(self, mock: Mock) -> None: self._validate_json_bom( bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_3, @@ -137,7 +135,7 @@ def test_bom_v1_3_full_component(self, mock: Mock) -> None: ) mock.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_6) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) def test_bom_v1_2_full_component(self, mock: Mock) -> None: self._validate_json_bom( bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_2, @@ -206,7 +204,7 @@ def test_bom_v1_3_component_with_vulnerability(self) -> None: fixture='bom_setuptools.json' ) - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_1)) def test_bom_v1_4_with_metadata_component(self, mock_uuid: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( @@ -215,7 +213,7 @@ def test_bom_v1_4_with_metadata_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_2)) def test_bom_v1_3_with_metadata_component(self, mock_uuid: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( @@ -224,7 +222,7 @@ def test_bom_v1_3_with_metadata_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_3) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) def test_bom_v1_2_with_metadata_component(self, mock_uuid: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( @@ -233,86 +231,104 @@ def test_bom_v1_2_with_metadata_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_4_services_simple(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_simple(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_4, fixture='bom_services_simple.json' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_3_services_simple(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_simple(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_3, fixture='bom_services_simple.json' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_2_services_simple(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_simple(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_2, fixture='bom_services_simple.json' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_4_services_complex(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_5)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_complex(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_4, fixture='bom_services_complex.json' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_3_services_complex(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_5)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_complex(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_3, fixture='bom_services_complex.json' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_2_services_complex(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_5)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_complex(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_2, fixture='bom_services_complex.json' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_4_services_nested(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_nested(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_4, fixture='bom_services_nested.json' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_3_services_nested(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_nested(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_3, fixture='bom_services_nested.json' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_2_services_nested(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_nested(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_json_bom( bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_2, fixture='bom_services_nested.json' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() def test_bom_v1_4_dependencies(self) -> None: self._validate_json_bom( @@ -350,13 +366,6 @@ def test_bom_v1_2_dependencies_for_bom_component(self) -> None: fixture='bom_dependencies_component.json' ) - def test_bom_v1_4_dependencies_invalid(self) -> None: - with self.assertRaises(UnknownComponentDependencyException): - self._validate_json_bom( - bom=get_bom_with_dependencies_invalid(), schema_version=SchemaVersion.V1_4, - fixture='bom_dependencies.json' - ) - def test_bom_v1_4_issue_275_components(self) -> None: self._validate_json_bom( bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_4, @@ -375,42 +384,16 @@ def test_bom_v1_2_issue_275_components(self) -> None: fixture='bom_issue_275_components.json' ) - def test_bom_v1_4_issue_328_components(self) -> None: - self._validate_json_bom( - bom=get_bom_for_issue_328_components(), schema_version=SchemaVersion.V1_4, - fixture='bom_issue_328_components.json' - ) - - def test_bom_v1_3_issue_328_components(self) -> None: - self._validate_json_bom( - bom=get_bom_for_issue_328_components(), schema_version=SchemaVersion.V1_3, - fixture='bom_issue_328_components.json' - ) - - def test_bom_v1_2_issue_328_components(self) -> None: - self._validate_json_bom( - bom=get_bom_for_issue_328_components(), schema_version=SchemaVersion.V1_2, - fixture='bom_issue_328_components.json' - ) - # region Helper methods def _validate_json_bom(self, bom: Bom, schema_version: SchemaVersion, fixture: str) -> None: outputter = get_instance(bom=bom, output_format=OutputFormat.JSON, schema_version=schema_version) self.assertEqual(outputter.schema_version, schema_version) - output = outputter.output_as_string() - self.assertValidAgainstSchema(bom_json=output, schema_version=schema_version) - fixture_file = join( - FIXTURES_DIRECTORY, - 'json', - schema_version.to_version(), - fixture - ) - if RECREATE_SNAPSHOTS: - with open(fixture_file, 'w') as expected_json: - expected_json.write(output) - with open(fixture_file) as expected_json: - self.assertEqualJsonBom(expected_json.read(), output) + with open( + join(dirname(__file__), f'fixtures/json/{schema_version.to_version()}/{fixture}')) as expected_json: + output_as_string = outputter.output_as_string() + self.assertValidAgainstSchema(bom_json=output_as_string, schema_version=schema_version) + self.assertEqualJsonBom(expected_json.read(), output_as_string) def _validate_json_bom_not_supported(self, bom: Bom, schema_version: SchemaVersion) -> None: with self.assertRaises(FormatNotSupportedException): diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py index bbc02e43..089ae93b 100644 --- a/tests/test_output_xml.py +++ b/tests/test_output_xml.py @@ -16,17 +16,15 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. - -from os.path import join +from os.path import dirname, join from unittest.mock import Mock, patch +from uuid import UUID -from cyclonedx.exception.model import UnknownComponentDependencyException from cyclonedx.model.bom import Bom -from cyclonedx.output import SchemaVersion, get_instance - -from . import FIXTURES_DIRECTORY, RECREATE_SNAPSHOTS -from .base import BaseXmlTestCase -from .data import ( +from cyclonedx.output import get_instance +from cyclonedx.schema import SchemaVersion +from tests.base import BaseXmlTestCase +from tests.data import ( MOCK_UUID_1, MOCK_UUID_2, MOCK_UUID_3, @@ -35,7 +33,6 @@ MOCK_UUID_6, TEST_UUIDS, get_bom_for_issue_275_components, - get_bom_for_issue_328_components, get_bom_just_complete_metadata, get_bom_with_component_setuptools_basic, get_bom_with_component_setuptools_complete, @@ -44,7 +41,6 @@ get_bom_with_component_setuptools_with_release_notes, get_bom_with_component_setuptools_with_vulnerability, get_bom_with_component_toml_1, - get_bom_with_dependencies_invalid, get_bom_with_dependencies_valid, get_bom_with_external_references, get_bom_with_metadata_component_and_dependencies, @@ -147,7 +143,7 @@ def test_simple_bom_v1_0_with_cpe(self) -> None: fixture='bom_setuptools_with_cpe.xml' ) - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_4) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) def test_bom_v1_4_full_component(self, mock_uuid: Mock) -> None: self._validate_xml_bom( bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_4, @@ -155,7 +151,7 @@ def test_bom_v1_4_full_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_3) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) def test_bom_v1_3_full_component(self, mock_uuid: Mock) -> None: self._validate_xml_bom( bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_3, @@ -163,7 +159,7 @@ def test_bom_v1_3_full_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_2) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_2)) def test_bom_v1_2_full_component(self, mock_uuid: Mock) -> None: self._validate_xml_bom( bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_2, @@ -171,7 +167,7 @@ def test_bom_v1_2_full_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_1) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_1)) def test_bom_v1_1_full_component(self, mock_uuid: Mock) -> None: self._validate_xml_bom( bom=get_bom_with_component_setuptools_complete(), schema_version=SchemaVersion.V1_1, @@ -263,31 +259,13 @@ def test_bom_v1_4_component_with_vulnerability(self) -> None: fixture='bom_setuptools_with_vulnerabilities.xml' ) - def test_bom_v1_3_component_with_vulnerability(self) -> None: - self._validate_xml_bom( - bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_3, - fixture='bom_setuptools_with_vulnerabilities.xml' - ) - - def test_bom_v1_2_component_with_vulnerability(self) -> None: - self._validate_xml_bom( - bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_2, - fixture='bom_setuptools_with_vulnerabilities.xml' - ) - - def test_bom_v1_1_component_with_vulnerability(self) -> None: - self._validate_xml_bom( - bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_1, - fixture='bom_setuptools_with_vulnerabilities.xml' - ) - def test_bom_v1_0_component_with_vulnerability(self) -> None: self._validate_xml_bom( bom=get_bom_with_component_setuptools_with_vulnerability(), schema_version=SchemaVersion.V1_0, fixture='bom_setuptools.xml' ) - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_6) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) def test_bom_v1_4_with_metadata_component(self, mock_uuid: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( @@ -296,7 +274,7 @@ def test_bom_v1_4_with_metadata_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_5) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_5)) def test_bom_v1_3_with_metadata_component(self, mock_uuid: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( @@ -305,7 +283,7 @@ def test_bom_v1_3_with_metadata_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_4) + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) def test_bom_v1_2_with_metadata_component(self, mock_uuid: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( @@ -314,7 +292,7 @@ def test_bom_v1_2_with_metadata_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_1) + @patch('cyclonedx.model.bom.uuid4', return_value=UUID(MOCK_UUID_1)) def test_bom_v1_1_with_metadata_component(self, mock_uuid: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( @@ -323,7 +301,7 @@ def test_bom_v1_1_with_metadata_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_1) + @patch('cyclonedx.model.bom.uuid4', return_value=UUID(MOCK_UUID_1)) def test_bom_v1_0_with_metadata_component(self, mock_uuid: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( @@ -332,113 +310,137 @@ def test_bom_v1_0_with_metadata_component(self, mock_uuid: Mock) -> None: ) mock_uuid.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_4_services_simple(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_simple(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_4, fixture='bom_services_simple.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_3_services_simple(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_simple(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_3, fixture='bom_services_simple.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_2_services_simple(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_simple(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_2, fixture='bom_services_simple.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_1_services_simple(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_1_services_simple(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_1, fixture='bom_empty.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_0_services_simple(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_3)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_0_services_simple(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_services_simple(), schema_version=SchemaVersion.V1_0, fixture='bom_empty.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_4_services_complex(self, mock_uuid4: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_complex(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_4, fixture='bom_services_complex.xml' ) - mock_uuid4.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_3_services_complex(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_complex(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_3, fixture='bom_services_complex.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_2_services_complex(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_4)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_complex(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_2, fixture='bom_services_complex.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_1_services_complex(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_2)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_1_services_complex(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_services_complex(), schema_version=SchemaVersion.V1_1, fixture='bom_empty.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_4_services_nested(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_4_services_nested(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_4, fixture='bom_services_nested.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_3_services_nested(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_3_services_nested(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_3, fixture='bom_services_nested.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() - @patch('cyclonedx.model.bom_ref.uuid4', side_effect=TEST_UUIDS) - def test_bom_v1_2_services_nested(self, mock_uuid: Mock) -> None: + @patch('cyclonedx.model.component.uuid4', return_value=UUID(MOCK_UUID_6)) + @patch('cyclonedx.model.service.uuid4', side_effect=TEST_UUIDS) + def test_bom_v1_2_services_nested(self, mock_1: Mock, mock_2: Mock) -> None: with self.assertWarns(UserWarning): self._validate_xml_bom( bom=get_bom_with_nested_services(), schema_version=SchemaVersion.V1_2, fixture='bom_services_nested.xml' ) - mock_uuid.assert_called() + mock_1.assert_called() + mock_2.assert_called() def test_bom_v1_4_dependencies(self) -> None: self._validate_xml_bom( @@ -465,107 +467,72 @@ def test_bom_v1_1_dependencies(self) -> None: ) def test_bom_v1_4_dependencies_for_bom_component(self) -> None: - self._validate_xml_bom( - bom=get_bom_with_metadata_component_and_dependencies(), schema_version=SchemaVersion.V1_4, - fixture='bom_dependencies_component.xml' - ) + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_metadata_component_and_dependencies(), schema_version=SchemaVersion.V1_4, + fixture='bom_dependencies_component.xml' + ) def test_bom_v1_3_dependencies_for_bom_component(self) -> None: - self._validate_xml_bom( - bom=get_bom_with_metadata_component_and_dependencies(), schema_version=SchemaVersion.V1_3, - fixture='bom_dependencies_component.xml' - ) + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_with_metadata_component_and_dependencies(), schema_version=SchemaVersion.V1_3, + fixture='bom_dependencies_component.xml' + ) def test_bom_v1_2_dependencies_for_bom_component(self) -> None: - self._validate_xml_bom( - bom=get_bom_with_metadata_component_and_dependencies(), schema_version=SchemaVersion.V1_2, - fixture='bom_dependencies_component.xml' - ) - - def test_bom_v1_4_dependencies_invalid(self) -> None: - with self.assertRaises(UnknownComponentDependencyException): + with self.assertWarns(UserWarning): self._validate_xml_bom( - bom=get_bom_with_dependencies_invalid(), schema_version=SchemaVersion.V1_4, - fixture='bom_dependencies.xml' + bom=get_bom_with_metadata_component_and_dependencies(), schema_version=SchemaVersion.V1_2, + fixture='bom_dependencies_component.xml' ) def test_bom_v1_4_issue_275_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_4, - fixture='bom_issue_275_components.xml' - ) + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_4, + fixture='bom_issue_275_components.xml' + ) def test_bom_v1_3_issue_275_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_3, - fixture='bom_issue_275_components.xml' - ) + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_3, + fixture='bom_issue_275_components.xml' + ) def test_bom_v1_2_issue_275_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_2, - fixture='bom_issue_275_components.xml' - ) + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_2, + fixture='bom_issue_275_components.xml' + ) def test_bom_v1_1_issue_275_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_1, - fixture='bom_issue_275_components.xml' - ) + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_1, + fixture='bom_issue_275_components.xml' + ) def test_bom_v1_0_issue_275_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_0, - fixture='bom_issue_275_components.xml' - ) - - def test_bom_v1_4_issue_328_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_328_components(), schema_version=SchemaVersion.V1_4, - fixture='bom_issue_328_components.xml' - ) - - def test_bom_v1_3_issue_328_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_328_components(), schema_version=SchemaVersion.V1_3, - fixture='bom_issue_328_components.xml' - ) - - def test_bom_v1_2_issue_328_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_328_components(), schema_version=SchemaVersion.V1_2, - fixture='bom_issue_328_components.xml' - ) - - def test_bom_v1_1_issue_328_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_328_components(), schema_version=SchemaVersion.V1_1, - fixture='bom_issue_328_components.xml' - ) - - def test_bom_v1_0_issue_328_components(self) -> None: - self._validate_xml_bom( - bom=get_bom_for_issue_328_components(), schema_version=SchemaVersion.V1_0, - fixture='bom_issue_328_components.xml' - ) + with self.assertWarns(UserWarning): + self._validate_xml_bom( + bom=get_bom_for_issue_275_components(), schema_version=SchemaVersion.V1_0, + fixture='bom_issue_275_components.xml' + ) # region Helper methods def _validate_xml_bom(self, bom: Bom, schema_version: SchemaVersion, fixture: str) -> None: outputter = get_instance(bom=bom, schema_version=schema_version) self.assertEqual(outputter.schema_version, schema_version) - output = outputter.output_as_string() - self.assertValidAgainstSchema(bom_xml=output, schema_version=schema_version) - fixture_file = join( - FIXTURES_DIRECTORY, - 'xml', - schema_version.to_version(), - fixture - ) - if RECREATE_SNAPSHOTS: - with open(fixture_file, 'w') as expected_xml: - expected_xml.write(output) - with open(fixture_file) as expected_xml: - self.assertEqualXmlBom(expected_xml.read(), output, namespace=outputter.get_target_namespace()) + with open( + join(dirname(__file__), f'fixtures/xml/{schema_version.to_version()}/{fixture}')) as expected_xml: + output_as_string = outputter.output_as_string() + self.assertValidAgainstSchema(bom_xml=output_as_string, schema_version=schema_version) + self.assertEqualXmlBom( + expected_xml.read(), output_as_string, namespace=outputter.get_target_namespace() + ) # endregion Helper methods diff --git a/tests/test_real_world_examples.py b/tests/test_real_world_examples.py new file mode 100644 index 00000000..43aa11a9 --- /dev/null +++ b/tests/test_real_world_examples.py @@ -0,0 +1,45 @@ +# encoding: utf-8 +# This file is part of CycloneDX Python Lib +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. +import unittest +from datetime import datetime +from os.path import dirname, join +from typing import cast +from unittest.mock import patch +from xml.etree import ElementTree + +from cyclonedx.model.bom import Bom +from cyclonedx.schema import SchemaVersion + + +def fixed_date_time() -> datetime: + return datetime.fromisoformat('2023-01-07 13:44:32.312678+00:00') + + +@patch('cyclonedx.model.ThisTool._version', 'TESTING') +@patch('cyclonedx.model.bom.get_now_utc', fixed_date_time) +class TestDeserializeeRealWorldExamples(unittest.TestCase): + + def test_webgoat_6_1(self) -> None: + self._attempt_load_example( + schema_version=SchemaVersion.V1_4, fixture='webgoat-6.1.xml' + ) + + def _attempt_load_example(self, schema_version: SchemaVersion, fixture: str) -> None: + with open(join(dirname(__file__), f'fixtures/xml/{schema_version.to_version()}/{fixture}')) as input_xml: + xml = input_xml.read() + cast(Bom, Bom.from_xml(data=ElementTree.fromstring(xml))) diff --git a/tox.ini b/tox.ini index b70b6ccb..ddf620bc 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ minversion = 3.10 envlist = flake8 mypy-{locked,lowest} - py{310,39,38,37,36}-{locked,lowest} + py{311,310,39,38,37}-{locked,lowest} isolated_build = True skip_missing_interpreters = True usedevelop = False @@ -18,7 +18,6 @@ download = False # settings in this category apply to all other testenv, if not overwritten skip_install = True whitelist_externals = poetry -## deps = poetry ## << this one caused https://github.com/python-poetry/poetry/issues/6288 commands_pre = {envpython} --version poetry install -v @@ -34,7 +33,7 @@ setenv = commands = # mypy config is in own file: `.mypy.ini` !lowest: poetry run mypy - lowest: poetry run mypy --python-version=3.6 + lowest: poetry run mypy --python-version=3.7 [testenv:flake8] commands = @@ -45,7 +44,7 @@ commands = exclude = build,dist,__pycache__,.eggs,*.egg-info*, *_cache,*.cache, - .git,.tox,.venv,venv + .git,.tox,.venv,venv,.venv*,venv*, _OLD,_TEST, docs max-line-length = 120 @@ -53,3 +52,5 @@ ignore = E305 # ignore `self`, `cls` markers of flake8-annotations>=2.0 ANN101,ANN102 + # ignore ANN401 for dynamically typed *args and **kwargs + ANN401