From b40f739206a44f7dbd94042fb5e1a37c047ea024 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 8 Jul 2024 15:33:13 +0200 Subject: [PATCH] fix: XML serialize `normalizedString` and `token` properly (#646) fixes #638 --------- Signed-off-by: Jan Kowalleck --- cyclonedx/model/__init__.py | 9 +++++++++ cyclonedx/model/component.py | 10 ++++++++++ cyclonedx/model/contact.py | 4 ++++ cyclonedx/model/issue.py | 4 ++++ cyclonedx/model/license.py | 2 ++ cyclonedx/model/release_note.py | 3 +++ cyclonedx/model/service.py | 4 ++++ cyclonedx/model/vulnerability.py | 6 ++++++ pyproject.toml | 2 +- 9 files changed, 43 insertions(+), 1 deletion(-) diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 7a75f1d8..4edb50e1 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -113,6 +113,7 @@ def flow(self, flow: DataFlow) -> None: @property @serializable.xml_name('.') + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def classification(self) -> str: """ Data classification tags data according to its type, sensitivity, and value if altered, stolen, or destroyed. @@ -182,6 +183,7 @@ def __init__( @property @serializable.xml_attribute() @serializable.xml_name('content-type') + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def content_type(self) -> str: """ Specifies the content type of the text. Defaults to text/plain if not specified. @@ -468,6 +470,7 @@ def alg(self, alg: HashAlgorithm) -> None: @property @serializable.xml_name('.') + @serializable.xml_string(serializable.XmlStringSerializationType.TOKEN) def content(self) -> str: """ Hash value content. @@ -889,6 +892,7 @@ def name(self, name: str) -> None: @property @serializable.xml_name('.') + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def value(self) -> Optional[str]: """ Value of this Property. @@ -1128,6 +1132,7 @@ def __init__( @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def vendor(self) -> Optional[str]: """ The name of the vendor who created the tool. @@ -1143,6 +1148,7 @@ def vendor(self, vendor: Optional[str]) -> None: @property @serializable.xml_sequence(2) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> Optional[str]: """ The name of the tool. @@ -1158,6 +1164,7 @@ def name(self, name: Optional[str]) -> None: @property @serializable.xml_sequence(3) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def version(self) -> Optional[str]: """ The version of the tool. @@ -1268,6 +1275,7 @@ def timestamp(self, timestamp: Optional[datetime]) -> None: self._timestamp = timestamp @property + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> Optional[str]: """ The name of the individual who performed the action. @@ -1282,6 +1290,7 @@ def name(self, name: Optional[str]) -> None: self._name = name @property + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def email(self) -> Optional[str]: """ The email address of the individual who performed the action. diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 68ef7bf5..5287030b 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -94,6 +94,7 @@ def __init__( @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def uid(self) -> Optional[str]: """ A unique identifier of the commit. This may be version control specific. For example, Subversion uses revision @@ -155,6 +156,7 @@ def committer(self, committer: Optional[IdentifiableAction]) -> None: @property @serializable.xml_sequence(5) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def message(self) -> Optional[str]: """ The text description of the contents of the commit. @@ -1151,6 +1153,7 @@ def type(self, type: ComponentType) -> None: self._type = type @property + @serializable.xml_string(serializable.XmlStringSerializationType.TOKEN) def mime_type(self) -> Optional[str]: """ Get any declared mime-type for this Component. @@ -1256,6 +1259,7 @@ def authors(self, authors: Iterable[OrganizationalContact]) -> None: @serializable.view(SchemaVersion1Dot5) @serializable.view(SchemaVersion1Dot6) # todo: this is deprecated in v1.6? @serializable.xml_sequence(4) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def author(self) -> Optional[str]: """ The person(s) or organization(s) that authored the component. @@ -1271,6 +1275,7 @@ def author(self, author: Optional[str]) -> None: @property @serializable.xml_sequence(5) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def publisher(self) -> Optional[str]: """ The person(s) or organization(s) that published the component @@ -1286,6 +1291,7 @@ def publisher(self, publisher: Optional[str]) -> None: @property @serializable.xml_sequence(6) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def group(self) -> Optional[str]: """ The grouping name or identifier. This will often be a shortened, single name of the company or project that @@ -1305,6 +1311,7 @@ def group(self, group: Optional[str]) -> None: @property @serializable.xml_sequence(7) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> str: """ The name of the component. @@ -1328,6 +1335,7 @@ def name(self, name: str) -> None: @serializable.include_none(SchemaVersion1Dot2, '') @serializable.include_none(SchemaVersion1Dot3, '') @serializable.xml_sequence(8) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def version(self) -> Optional[str]: """ The component version. The version should ideally comply with semantic versioning but is not enforced. @@ -1348,6 +1356,7 @@ def version(self, version: Optional[str]) -> None: @property @serializable.xml_sequence(9) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def description(self) -> Optional[str]: """ Get the description of this Component. @@ -1419,6 +1428,7 @@ def licenses(self, licenses: Iterable[License]) -> None: @property @serializable.xml_sequence(13) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def copyright(self) -> Optional[str]: """ An optional copyright notice informing users of the underlying claims to copyright ownership in a published diff --git a/cyclonedx/model/contact.py b/cyclonedx/model/contact.py index 795473b7..dc42b1f7 100644 --- a/cyclonedx/model/contact.py +++ b/cyclonedx/model/contact.py @@ -213,6 +213,7 @@ def __init__( @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> Optional[str]: """ Get the name of the contact. @@ -228,6 +229,7 @@ def name(self, name: Optional[str]) -> None: @property @serializable.xml_sequence(2) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def email(self) -> Optional[str]: """ Get the email of the contact. @@ -243,6 +245,7 @@ def email(self, email: Optional[str]) -> None: @property @serializable.xml_sequence(3) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def phone(self) -> Optional[str]: """ Get the phone of the contact. @@ -305,6 +308,7 @@ def __init__( @property @serializable.xml_sequence(10) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> Optional[str]: """ Get the name of the organization. diff --git a/cyclonedx/model/issue.py b/cyclonedx/model/issue.py index 24a2262a..1378fcc1 100644 --- a/cyclonedx/model/issue.py +++ b/cyclonedx/model/issue.py @@ -60,6 +60,7 @@ def __init__( self.url = url @property + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> Optional[str]: """ The name of the source. For example "National Vulnerability Database", "NVD", and "Apache". @@ -151,6 +152,7 @@ def type(self, type: IssueClassification) -> None: @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def id(self) -> Optional[str]: """ The identifier of the issue assigned by the source of the issue. @@ -166,6 +168,7 @@ def id(self, id: Optional[str]) -> None: @property @serializable.xml_sequence(2) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> Optional[str]: """ The name of the issue. @@ -181,6 +184,7 @@ def name(self, name: Optional[str]) -> None: @property @serializable.xml_sequence(3) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def description(self) -> Optional[str]: """ A description of the issue. diff --git a/cyclonedx/model/license.py b/cyclonedx/model/license.py index f83a1683..1bde1248 100644 --- a/cyclonedx/model/license.py +++ b/cyclonedx/model/license.py @@ -109,6 +109,7 @@ def id(self, id: Optional[str]) -> None: @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> Optional[str]: """ If SPDX does not define the license used, this field may be used to provide the license name. @@ -257,6 +258,7 @@ def __init__( @property @serializable.xml_name('.') + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) @serializable.json_name('expression') def value(self) -> str: """ diff --git a/cyclonedx/model/release_note.py b/cyclonedx/model/release_note.py index 58c14626..f79e16e7 100644 --- a/cyclonedx/model/release_note.py +++ b/cyclonedx/model/release_note.py @@ -61,6 +61,7 @@ def __init__( @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def type(self) -> str: """ The software versioning type. @@ -148,6 +149,7 @@ def timestamp(self, timestamp: Optional[datetime]) -> None: @property @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'alias') + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) @serializable.xml_sequence(7) def aliases(self) -> 'SortedSet[str]': """ @@ -165,6 +167,7 @@ def aliases(self, aliases: Iterable[str]) -> None: @property @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'tag') + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) @serializable.xml_sequence(8) def tags(self) -> 'SortedSet[str]': """ diff --git a/cyclonedx/model/service.py b/cyclonedx/model/service.py index aa330dc0..3e3be565 100644 --- a/cyclonedx/model/service.py +++ b/cyclonedx/model/service.py @@ -119,6 +119,7 @@ def provider(self, provider: Optional[OrganizationalEntity]) -> None: @property @serializable.xml_sequence(2) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def group(self) -> Optional[str]: """ The grouping name, namespace, or identifier. This will often be a shortened, single name of the company or @@ -135,6 +136,7 @@ def group(self, group: Optional[str]) -> None: @property @serializable.xml_sequence(3) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> str: """ The name of the service. This will often be a shortened, single name of the service. @@ -150,6 +152,7 @@ def name(self, name: str) -> None: @property @serializable.xml_sequence(4) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def version(self) -> Optional[str]: """ The service version. @@ -165,6 +168,7 @@ def version(self, version: Optional[str]) -> None: @property @serializable.xml_sequence(5) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def description(self) -> Optional[str]: """ Specifies a description for the service. diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index 76b1dca0..8c9528f4 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -84,6 +84,7 @@ def __init__( @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def version(self) -> Optional[str]: """ A single version of a component or service. @@ -354,6 +355,7 @@ def __init__( @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def title(self) -> Optional[str]: """ The title of this advisory. @@ -422,6 +424,7 @@ def __init__( @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def name(self) -> Optional[str]: """ Name of this Source. @@ -493,6 +496,7 @@ def __init__( @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def id(self) -> Optional[str]: """ The identifier that uniquely identifies the vulnerability in the associated Source. For example: CVE-2021-39182. @@ -803,6 +807,7 @@ def method(self, score_source: Optional[VulnerabilityScoreSource]) -> None: @property @serializable.xml_sequence(5) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def vector(self) -> Optional[str]: """ The textual representation of the metric values used to score the vulnerability - also known as the vector. @@ -994,6 +999,7 @@ def bom_ref(self) -> BomRef: @property @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) def id(self) -> Optional[str]: """ The identifier that uniquely identifies the vulnerability. For example: CVE-2021-39182. diff --git a/pyproject.toml b/pyproject.toml index 1d3834ee..6e6eed96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ keywords = [ [tool.poetry.dependencies] python = "^3.8" packageurl-python = ">=0.11, <2" -py-serializable = ">=1.0.3, <2" +py-serializable = "^1.1.0" sortedcontainers = "^2.4.0" license-expression = "^30" jsonschema = { version = "^4.18", extras=['format'], optional=true }