Skip to content

Commit

Permalink
feat: Improved reporting of supplier information
Browse files Browse the repository at this point in the history
  • Loading branch information
anthonyharrison committed Jan 11, 2023
1 parent e4b3b07 commit 770dee2
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 23 deletions.
4 changes: 2 additions & 2 deletions sbom4python/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2022 Anthony Harrison
# Copyright (C) 2023 Anthony Harrison
# SPDX-License-Identifier: Apache-2.0

import argparse
Expand Down Expand Up @@ -135,7 +135,7 @@ def main(argv=None):
"-",
sbom_scan.get("Name"),
sbom_scan.get("Version"),
sbom_scan.get("Author"),
sbom_scan.get("Author") + " " + sbom_scan.get("Author-email"),
sbom_scan.get("License"),
]
)
Expand Down
27 changes: 20 additions & 7 deletions sbom4python/cyclonedxgenerator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (C) 2022 Anthony Harrison
# Copyright (C) 2023 Anthony Harrison
# SPDX-License-Identifier: Apache-2.0

import re
import uuid
from datetime import datetime

Expand Down Expand Up @@ -149,6 +150,12 @@ def generateComponent(self, id, type, name, supplier, version, licence):
else:
self.generateJSONComponent(id, type, name, supplier, version, licence)

def _format_supplier(self, supplier_info):
# Get names, ignore email addresses
names = re.findall(r"[a-zA-Z\.\]+ [A-Za-z]+ ", supplier_info)
supplier = " ".join(n for n in names)
return re.sub(" +", " ", supplier.strip())

def generateJSONComponent(
self, id, type, name, supplier, version, identified_licence
):
Expand All @@ -157,10 +164,13 @@ def generateJSONComponent(
component["bom-ref"] = id
component["name"] = name
component["version"] = version
if supplier != "UNKNOWN" and len(supplier) > 0:
component["author"] = supplier
if supplier != "UNKNOWN" and len(supplier) > 1:
component_supplier = self._format_supplier(supplier)
component["author"] = component_supplier
# Supplier name mustn't have spaces in. Covert spaces to '_'
component["cpe"] = f"cpe:/a:{supplier.replace(' ', '_').lower()}:{name}:{version}"
component[
"cpe"
] = f"cpe:/a:{component_supplier.replace(' ', '_').lower()}:{name}:{version}"
if identified_licence != "":
license_id = self.license.find_license(identified_licence)
# Only include if valid license
Expand All @@ -183,10 +193,13 @@ def generateXMLComponent(
self.store(f'<component type="{type}" bom-ref="{id}">')
self.store(f"<name>{name}</name>")
self.store(f"<version>{version}</version>")
if supplier != "UNKNOWN" and len(supplier) > 0:
self.store(f"<author>{supplier}</supplier>")
if supplier != "UNKNOWN" and len(supplier) > 1:
component_supplier = self._format_supplier(supplier)
self.store(f"<author>{component_supplier}</supplier>")
# Supplier name mustn't have spaces in. Covert spaces to '_'
self.store(f"<cpe>cpe:/a:{supplier.replace(' ', '_').lower()}:{name}:{version}</cpe>")
self.store(
f"<cpe>cpe:/a:{component_supplier.replace(' ', '_').lower()}:{name}:{version}</cpe>"
)
if identified_licence != "":
license_id = self.license.find_license(identified_licence)
# Only include if valid license
Expand Down
3 changes: 2 additions & 1 deletion sbom4python/license.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2022 Anthony Harrison
# Copyright (C) 2023 Anthony Harrison
# SPDX-License-Identifier: Apache-2.0


Expand All @@ -12,6 +12,7 @@ class LicenseScanner:
"Apache Software License",
"Apache License, Version 2.0",
"Apache 2.0",
"Apache_2.0",
"Apache 2",
]
DEFAULT_LICENSE = "UNKNOWN"
Expand Down
4 changes: 2 additions & 2 deletions sbom4python/scanner.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2022 Anthony Harrison
# Copyright (C) 2023 Anthony Harrison
# SPDX-License-Identifier: Apache-2.0

import subprocess
Expand Down Expand Up @@ -72,7 +72,7 @@ def analyze(self, parent, dependencies):
parent.lower().replace("_", "-"),
self.get("Name").lower().replace("_", "-"),
self.get("Version"),
self.get("Author"),
self.get("Author") + " " + self.get("Author-email"),
self.get("License"),
]
)
Expand Down
30 changes: 22 additions & 8 deletions sbom4python/spdxgenerator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (C) 2022 Anthony Harrison
# Copyright (C) 2023 Anthony Harrison
# SPDX-License-Identifier: Apache-2.0

import re
import uuid
from datetime import datetime

Expand Down Expand Up @@ -139,6 +140,21 @@ def license_ident(self, license):
return derived_license
return "NOASSERTION"

def _format_supplier(self, supplier_info):
# Get names
names = re.findall(r"[a-zA-Z\.\]+ [A-Za-z]+ ", supplier_info)
# Get email addresses
# Use RFC-5322 compliant regex (https://regex101.com/library/6EL6YF)
emails = re.findall(
r"((?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\]))",
supplier_info,
)
supplier = " ".join(n for n in names)
if len(emails) > 0:
# Only one email can be specified, so choose last one
supplier = supplier + "(" + emails[-1] + ")"
return re.sub(" +", " ", supplier.strip())

def generateTagPackageDetails(
self, package, id, version, supplier, license, parent_id, relationship
):
Expand All @@ -147,14 +163,12 @@ def generateTagPackageDetails(
package_id = self.package_ident(id)
self.generateTag("SPDXID", package_id)
# Attempt to detect an organization
if len(supplier.split()) > 2:
# Supplier name mustn't have spaces in. Covert spaces to '_'
if len(supplier.split()) > 3:
self.generateTag(
"PackageSupplier: Organization", supplier.replace(" ", "_")
"PackageSupplier: Organization", self._format_supplier(supplier)
)
elif len(supplier) > 0:
# Supplier name mustn't have spaces in. Covert spaces to '_'
self.generateTag("PackageSupplier: Person", supplier.replace(" ", "_"))
elif len(supplier) > 1:
self.generateTag("PackageSupplier: Person", self._format_supplier(supplier))
else:
self.generateTag("PackageSupplier", "NOASSERTION")
self.generateTag("PackageVersion", version)
Expand Down Expand Up @@ -185,7 +199,7 @@ def generateJSONPackageDetails(
if len(supplier.split()) > 2:
# Supplier name mustn't have spaces in. Covert spaces to '_'
component["supplier"] = "Organization: " + supplier.replace(" ", "_")
elif len(supplier) >0:
elif len(supplier) > 0:
# Supplier name mustn't have spaces in. Covert spaces to '_'
component["supplier"] = "Person: " + supplier.replace(" ", "_")
else:
Expand Down
4 changes: 2 additions & 2 deletions sbom4python/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2022 Anthony Harrison
# Copyright (C) 2023 Anthony Harrison
# SPDX-License-Identifier: Apache-2.0

VERSION: str = "0.4.0"
VERSION: str = "0.5.0"
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from setuptools import find_packages, setup

# Copyright (C) 2023 Anthony Harrison
# SPDX-License-Identifier: Apache-2.0

from sbom4python.version import VERSION

with open("README.md", encoding="utf-8") as f:
Expand All @@ -19,7 +22,7 @@
author_email='[email protected]',
maintainer='Anthony Harrison',
maintainer_email='[email protected]',
license='Apache_2.0',
license='Apache-2.0',
keywords=["security", "tools", "SBOM", "DevSecOps", "SPDX", "CycloneDX"],
install_requires=requirements,
classifiers=[
Expand Down

0 comments on commit 770dee2

Please sign in to comment.