diff --git a/pyproject.toml b/pyproject.toml index e773d841c..6ea968565 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ dependencies = [ "pyyaml", "rdflib", "semantic_version", + "spdx-python-model", "uritools", "xmltodict", ] diff --git a/src/spdx_tools/spdx/writer/rdf/writer_utils.py b/src/spdx_tools/spdx/writer/rdf/writer_utils.py index fb27aa18b..528733e08 100644 --- a/src/spdx_tools/spdx/writer/rdf/writer_utils.py +++ b/src/spdx_tools/spdx/writer/rdf/writer_utils.py @@ -10,7 +10,6 @@ from spdx_tools.spdx.datetime_conversions import datetime_to_iso_string from spdx_tools.spdx.model import SpdxNoAssertion, SpdxNone -from spdx_tools.spdx.rdfschema.namespace import SPDX_NAMESPACE from spdx_tools.spdx.validation.spdx_id_validators import is_valid_internal_spdx_id @@ -28,7 +27,7 @@ def add_literal_or_no_assertion_or_none(value: Any, graph: Graph, parent: Node, if value is None: return if isinstance(value, SpdxNone): - graph.add((parent, predicate, SPDX_NAMESPACE.none)) + graph.add((parent, predicate, Literal(str(value)))) return add_literal_or_no_assertion(value, graph, parent, predicate) @@ -37,7 +36,7 @@ def add_literal_or_no_assertion(value: Any, graph: Graph, parent: Node, predicat if value is None: return if isinstance(value, SpdxNoAssertion): - graph.add((parent, predicate, SPDX_NAMESPACE.noassertion)) + graph.add((parent, predicate, Literal(str(value)))) return add_optional_literal(value, graph, parent, predicate) diff --git a/src/spdx_tools/spdx3/binding/helpers.py b/src/spdx_tools/spdx3/binding/helpers.py new file mode 100644 index 000000000..8baec1b06 --- /dev/null +++ b/src/spdx_tools/spdx3/binding/helpers.py @@ -0,0 +1,308 @@ +import importlib +import inspect +import os +from typing import Dict, List, Optional + +from spdx_python_model import v3_0_1 as spdx_3_0 + +from spdx_tools.spdx3.model.element import Element +from spdx_tools.spdx.casing_tools import camel_case_to_snake_case, snake_case_to_camel_case + +FIRST_PART_OF_IRI = "https://spdx.org/rdf" +SPDX_VERSION = "3.0.1" + + +# These should not be instantiated and will only every be referenced as an URI, +# so we don't want to try to map them to a domain class +INDIVIDUAL_ELEMENT_CLASSES = { + "IndividualElement", # concrete class for individuals of Element + "IndividualLicensingInfo", # concrete class for individuals of LicensingInfo + "NoAssertionElement", # the rest are individuals that should only be represented as URIs + "NoneElement", + "SpdxOrganization", + "NoAssertionLicense", + "NoneLicense", +} + + +LOWER_CASE_TO_CORRECT_CASE_PROFILE_NAMES = { + "core": "Core", + "software": "Software", + "security": "Security", + "simplelicensing": "SimpleLicensing", + "expandedlicensing": "ExpandedLicensing", + "dataset": "Dataset", + "ai": "AI", + "build": "Build", + "extension": "Extension", +} + + +def convert_binding_iri_to_domain_class(iri: str) -> Optional[type]: + """ + Converts a binding class IRI () to the corresponding domain class type if one exists. + Returns None if there is no corresponding domain class that would be instantiated, + such as for Individuals such as NoAssertionElement or NoneLicense, who should only be represented + as URI strings in the domain model. Or if the type is abstract. + Raises an exception if the domain class cannot be found and the binding class is not abstract. + """ + + # check if the IRI is valid and if it is for an individual that is abstract + shacl_class = spdx_3_0.SHACLObject.CLASSES.get(iri) + if shacl_class is None: + raise ValueError(f"Could not find SPDX class for IRI: {iri}") + + if shacl_class.IS_ABSTRACT: + return None + + profile = "Core" + relevant_path = iri.replace(FIRST_PART_OF_IRI + "/" + SPDX_VERSION + "/terms/", "") + split_path = relevant_path.split("/") + + if len(split_path) != 2: + raise ValueError("Failed to convert SPDX IRI. Did the IRI format change?") + + profile = split_path[0].lower() + + class_name = split_path[1] + + if class_name in INDIVIDUAL_ELEMENT_CLASSES: + return None + + # check special name cases because these names don't match the spdx class names + if class_name == "ExternalRef": + class_name = "ExternalReference" + if class_name == "ExternalRefType": + class_name = "ExternalReferenceType" + + # this is here because SPDX 3.0.1 DictionaryEntry is represented in + # Python by tuple[str, str] + if class_name == "DictionaryEntry": + return tuple + + # special case for the core profile because it is not in a subdirectory + module_name = "spdx_tools.spdx3.model" + if profile != "core": + module_name += f".{profile}" + try: + target_module = importlib.import_module(module_name) + except ModuleNotFoundError as e: + raise ModuleNotFoundError( + f"Could not find module '{module_name}'. It probably hasn't been implemented yet." + ) from e + + try: + return getattr(target_module, class_name) + except AttributeError as e: + raise AttributeError( + ( + f"Could not find domain class '{class_name}' in module '{module_name}'." + "It probably hasn't been implemented yet or was not added to its module's __init__.py file" + ) + ) from e + + +def get_binding_type_name_from_domain_object(element: Element) -> str: + """ + Returns the SPDX type name for an element, including profile prefix if applicable. + For example: "software_Package" or "CreationInfo" + """ + profile_prefix = get_spdx_profile_prefix(type(element)) + type_name = element.__class__.__name__ + return get_binding_type_name_from_profile_and_domain_class_name(profile_prefix, type_name) + + +def get_binding_type_name_from_profile_and_domain_class_name(profile: str, domain_class_name: str) -> str: + profile_prefix = profile.lower() + type_name = domain_class_name + + # special case for external reference + if type_name == "ExternalReference": + type_name = "ExternalRef" + + return f"{profile_prefix}_{type_name}".strip("_") + + +def get_binding_iri_from_profile_and_domain_class_name(profile: str, domain_class_name: str) -> str: + if profile not in LOWER_CASE_TO_CORRECT_CASE_PROFILE_NAMES and profile != "": + raise ValueError( + ( + f"Unknown SPDX profile name: '{profile}'. Known profiles are: " + f"{list(LOWER_CASE_TO_CORRECT_CASE_PROFILE_NAMES.keys())} or empty string for core profile" + ) + ) + + iri_case_profile_name = LOWER_CASE_TO_CORRECT_CASE_PROFILE_NAMES.get(profile.lower(), "Core") + + class_name = domain_class_name + if class_name == "ExternalReference": + class_name = "ExternalRef" + if class_name == "ExternalReferenceType": + class_name = "ExternalRefType" + + return f"{FIRST_PART_OF_IRI}/{SPDX_VERSION}/terms/{iri_case_profile_name}/{class_name}" + + +def get_binding_attribute_names(binding_class) -> set[str]: + return {py_name for py_name, iri, compact in binding_class().property_keys()} + + +def _get_class_directory_name(obj: object) -> str: + """ + Returns the directory that the passed in class is defined in. + + This is used for finding the name of the directory that an implementation + of an Element is in, which is the same as its profile. + """ + # If we're passed an instance, get its class + if not isinstance(obj, type): + obj = obj.__class__ + + # Get the module of the class + module = inspect.getmodule(obj) + if not module: + return "" + + # Get the file path of the module + file_path = module.__file__ + if not file_path: + return "" + + # Get the directory containing the file + dir_path = os.path.dirname(file_path) + + # Return just the name of the directory (not the full path) + return os.path.basename(dir_path) + + +def get_spdx_profile_prefix(element: type) -> str: + """ + Returns the profile prefix of a domain element type. + """ + directory_name = _get_class_directory_name(element) + + # If the class is in the model directory, then it is part of the Core profile and does not have a profile prefix + if directory_name == "model": + directory_name = "" + + return directory_name + + +def get_attributes_by_domain_class_hierarchy(obj: object) -> Dict[type, List[str]]: + """ + Returns a list of tuples containing each parent class name and the attributes + defined in that class (including the class itself). + + Args: + obj: The object to analyze + + Returns: + List of tuples of (class_name, [attribute_names]) for each class in the inheritance hierarchy + """ + # Get the class of the object if an instance was provided + cls = obj if isinstance(obj, type) else obj.__class__ + + # Get the Method Resolution Order (MRO) - the inheritance hierarchy + class_hierarchy = cls.__mro__ + + result = {} + all_attrs = set() # Track attributes we've already seen + + # Stop at these classes as that is as far down the class hierarchy as we need to go + stop_classes = { + "Element", + "CreationInfo", + "ExternalIdentifier", + "ExternalMap", + "DictionaryEntry", + "ExternalRef", + "PositiveIntegerRange", + "NamespaceMap", + "IntegrityMethod", + "Hash", + "PackageVerificationCode", + } + + # Iterate through each class in the hierarchy + for parent_class in class_hierarchy: + # Get all attributes defined directly in this class (not inherited) + class_attrs = set() + for attr_name in parent_class.__dict__.keys(): + # Skip private/special attributes and methods + if not attr_name.startswith("__") and not attr_name.startswith("_abc_"): + class_attrs.add(attr_name) + + # Remove attributes already found in child classes + unique_attrs = class_attrs - all_attrs + all_attrs.update(unique_attrs) + + # Add to result if there are any attributes + if unique_attrs: + result[parent_class] = sorted(unique_attrs) + if parent_class.__name__ in stop_classes: + break + + return result + + +def get_binding_property_name_from_element_field_name_and_prefix(element_field_name: str, profile_prefix: str) -> str: + # special cases + # the spdx_id field is always mapped to _id in the binding classes + if element_field_name == "spdx_id": + return "_id" + + field_name = element_field_name + if field_name == "from_element": + return "from_" + + if field_name == "external_reference": + return "externalRef" + + if field_name == "external_reference_type": + return "externalRefType" + + if field_name == "imports": + return "import_" + + return f"{profile_prefix}_{snake_case_to_camel_case(field_name)}".strip("_") + + +def get_domain_property_name_from_binding_property_name(binding_property_name: str) -> str: + # Special cases. These do not follow the naming conventions. + if binding_property_name == "_id": + return "spdx_id" + elif binding_property_name == "from_": + return "from_element" + elif binding_property_name == "externalRef": + return "external_reference" + elif binding_property_name == "externalRefType": + return "external_reference_type" + elif binding_property_name == "import_": + return "imports" + + # Typical cases + if "_" in binding_property_name: + # A profile prefix is present because there is a "_" present. + # Remove it because the domain model's property names do not have profile prefixes. + # These binding_property_name(s) are formatted like "software_Package", + # "simplelicensing_LicenseAddition", or "ElementCollection". + prefix, _, suffix = binding_property_name.partition("_") + if len(suffix) == 0: + # this shouldn't happen, but just in case + domain_property_name = prefix + else: + domain_property_name = suffix + else: + # This is a "core" profile property, so it doesn't have a profile prefix + domain_property_name = binding_property_name + + # The (Python) domain properties are Python properties, so they are snake case + return camel_case_to_snake_case(domain_property_name) + + +def is_binding_object_kind_not_iri_and_the_field_is_id(binding_obj, pyname: str) -> bool: + return ( + isinstance(binding_obj, spdx_3_0.SHACLObject) + and binding_obj.NODE_KIND != spdx_3_0.NodeKind.IRI + and pyname == "_id" + ) diff --git a/src/spdx_tools/spdx3/binding/shacl_to_spdx3_converter.py b/src/spdx_tools/spdx3/binding/shacl_to_spdx3_converter.py new file mode 100644 index 000000000..484d9e7e6 --- /dev/null +++ b/src/spdx_tools/spdx3/binding/shacl_to_spdx3_converter.py @@ -0,0 +1,232 @@ +import enum +import inspect +import typing +from types import UnionType +from typing import Any, Dict, Optional, Tuple, Union + +from semantic_version import Version +from spdx_python_model import v3_0_1 as spdx_3_0 + +from spdx_tools.spdx3.binding import helpers +from spdx_tools.spdx3.model import ( + Element, + ExternalIdentifierType, + ExternalReferenceType, + ProfileIdentifierType, + RelationshipType, +) +from spdx_tools.spdx3.model.ai.ai_package import SafetyRiskAssessmentType +from spdx_tools.spdx3.model.ai.energy_unit_type import EnergyUnitType +from spdx_tools.spdx3.model.annotation import AnnotationType +from spdx_tools.spdx3.model.artifact import SupportType +from spdx_tools.spdx3.model.dataset.dataset_package import ( + ConfidentialityLevelType, + DatasetAvailabilityType, + DatasetType, +) +from spdx_tools.spdx3.model.hash import HashAlgorithm +from spdx_tools.spdx3.model.lifecycle_scoped_relationship import LifecycleScopeType +from spdx_tools.spdx3.model.presence_type import PresenceType +from spdx_tools.spdx3.model.relationship import RelationshipCompleteness +from spdx_tools.spdx3.model.security.cvss_severity_type import CvssSeverityType +from spdx_tools.spdx3.model.security.exploit_catalog_vuln_assessment_relationship import ExploitCatalogType +from spdx_tools.spdx3.model.security.ssvc_vuln_assessment_relationship import SsvcDecisionType +from spdx_tools.spdx3.model.security.vex_not_affected_vuln_assessment_relationship import VexJustificationType +from spdx_tools.spdx3.model.software import SoftwarePurpose +from spdx_tools.spdx3.model.software.content_identifier import ContentIdentifierType +from spdx_tools.spdx3.model.software.file import FileKindType +from spdx_tools.spdx3.model.software.sbom import SbomType +from spdx_tools.spdx3.payload import Payload +from spdx_tools.spdx.casing_tools import camel_case_to_snake_case + + +def convert_to_payload(spdx: spdx_3_0.SHACLObjectSet) -> Payload: + converter = SHACLToSpdx3Converter() + + for shacl_object in spdx.foreach(): + converter.read(shacl_object) + + return converter.payload + + +BINDING_NAME_TO_ENUM_MAPPING = { + "ai_autonomyType": PresenceType, + "ai_energyUnitType": EnergyUnitType, + "ai_safetyRiskAssessment": SafetyRiskAssessmentType, + "ai_useSensitivePersonalInformation": PresenceType, + "dataset_confidentialityLevel": ConfidentialityLevelType, + "dataset_datasetAvailability": DatasetAvailabilityType, + "dataset_datasetType": DatasetType, + "dataset_hasSensitivePersonalInformation": PresenceType, + "security_catalogType": ExploitCatalogType, + "security_decisionType": SsvcDecisionType, + "security_justificationType": VexJustificationType, + "security_severity": CvssSeverityType, + "software_additionalPurpose": SoftwarePurpose, + "software_contentIdentifierType": ContentIdentifierType, + "software_fileKind": FileKindType, + "software_primaryPurpose": SoftwarePurpose, + "software_sbomType": SbomType, + "annotationType": AnnotationType, + "externalIdentifierType": ExternalIdentifierType, + "externalRefType": ExternalReferenceType, + "algorithm": HashAlgorithm, + "completeness": RelationshipCompleteness, + "profileConformance": ProfileIdentifierType, + "relationshipType": RelationshipType, + "scope": LifecycleScopeType, + "supportType": SupportType, +} + + +def _parse_compact_iri_to_enum(compact_iri: str | None, binding_value: str) -> enum.Enum | None: + """ + This will attempt to parse a given SPDX 3 compact IRI and value and will either return the parsed enum or a None. + + The compact_iri is the SPDX name of the attribute in the binding class + The binding_value is the URI value of the enum in the binding class + For example, if the compact_iri is "security_severity" and it has a binding_value + of "https://spdx.org/rdf/3.0.1/terms/Security/CvssSeverityType/high", the output should be Severity.HIGH + """ + + if compact_iri is None: + return None + + enum_class = BINDING_NAME_TO_ENUM_MAPPING.get(compact_iri, None) + + if enum_class: + enum_value = camel_case_to_snake_case(binding_value.split("/")[-1]).upper() + return enum_class[enum_value] + + return None + + +class SHACLToSpdx3Converter: + payload: Payload + + def __init__(self): + self.payload = Payload() + + def read(self, obj: spdx_3_0.SHACLObject) -> None: + parsed_element = self._parse_node(obj) + + if isinstance(parsed_element, Element): + self.payload.add_element(parsed_element) + + def _parse_node(self, obj: spdx_3_0.SHACLObject) -> Element | tuple: + target_class = helpers.convert_binding_iri_to_domain_class(obj.get_type()) + + if target_class is None: + raise TypeError( + ( + f"Cannot convert abstract binding class '{obj.__class__.__name__}'" + "to domain class. There is an issue with the SPDX 3 file." + ) + ) + + # Convert properties and create new instance + kwargs = self._convert_properties(obj, target_class) + + # Handle special case of Tuple types because we're mapping the SPDX DictionaryEntry to + # a Tuple[str, str] for later entry into a dict and tuple doesn't accept kwargs + # this method should only return a tuple within a recursive call, and should only return + # an Element to read() + + if issubclass(target_class, tuple): + return tuple(kwargs.values()) + else: + return target_class(**kwargs) + + # This is needed because the SPDX 3 model's types can be optional, like "a: dict[str, str] | None" + # or "b: Optional[dict[str, str]]"" and we need to detect these + def _is_dict(self, param_cls: type | None) -> bool: + param_type = typing.get_origin(param_cls) + is_plain_dict = param_type is dict + is_dict_in_union_type = False + is_union = param_type is UnionType or param_type is Union + if is_union: + # just do one level of checking + if any(typing.get_origin(arg_cls) is dict for arg_cls in typing.get_args(param_cls)): + is_dict_in_union_type = True + + return is_plain_dict or is_dict_in_union_type + + def _parse_single_value(self, binding_value: Any, domain_value_class: Any | None, compact_iri: str | None) -> Any: + if binding_value is None: + return None + + # Handle special cases + # In this case, the binding stores versions as strings, but the domain model uses semantic_version.Version + # and domain_value_class will be Version + if domain_value_class is Version: + return Version(binding_value) + elif isinstance(binding_value, spdx_3_0.SHACLObject): + if binding_value.NODE_KIND == spdx_3_0.NodeKind.BlankNodeOrIRI: + # if a node is a BlankNodeOrIRI (i.e. Non-Element class), then it must be converted in place + return self._parse_node(binding_value) + else: + # otherwise, it is an Element class and we just need to return its ID + return binding_value["@id"] + else: + if isinstance(binding_value, str): + # check if it is an enum + result: enum.Enum | None = _parse_compact_iri_to_enum(compact_iri, binding_value) + if result: + return result + return binding_value + + def _convert_properties(self, binding_obj: spdx_3_0.SHACLObject, target_class: type) -> Dict[str, Any]: + """ + Map properties from binding object to target class. + + Returns a dictionary of parameters to be passed to the target class constructor. + """ + + kwargs: Dict[str, Any] = {} + # Get target class's initialization parameters + domain_class_parameters = inspect.signature(target_class.__init__).parameters + + domain_prop_name_to_type: Dict[str, type] = {} + # make map of domain property name to domain property type + for param_name, param in domain_class_parameters.items(): + param_cls = param.annotation + domain_prop_name_to_type[param_name] = param_cls + + # get binding class's properties + binding_class_properties: list[Tuple[Optional[str], str, Optional[str]]] = list(binding_obj.property_keys()) + for pyname, iri, compact_iri in binding_class_properties: + + binding_value = binding_obj[iri] + + if pyname is None: + raise Exception(f"pyname missing for '{binding_obj}'") + + # pyname is the name of the property in the binding class i.e. comment or security_modifiedTime + # iri is the full URI of the property i.e. https://spdx.org/rdf/3.0.1/terms/Core/comment + # compact_iri is also the name of the property, but in some cases, like the SPDX id, they will differ + + # special case. Do not try to extract the ID if the binding class is + # not an Element because only Elements have IDs + if helpers.is_binding_object_kind_not_iri_and_the_field_is_id(binding_obj, pyname): + continue + + # change the trimmed pyname to snake case so that it will match the domain class + # property name and also handle the special cases + converted_pyname = helpers.get_domain_property_name_from_binding_property_name(pyname) + + param_cls = domain_prop_name_to_type.get(converted_pyname) + + if hasattr(binding_obj, pyname): + # Handle lists and single values + if isinstance(binding_value, spdx_3_0.ListProxy): + if self._is_dict(param_cls): + value = dict(self._parse_single_value(item, param_cls, compact_iri) for item in binding_value) + else: + # Treat this as a list + value = [self._parse_single_value(item, param_cls, compact_iri) for item in binding_value] + else: + value = self._parse_single_value(binding_value, param_cls, compact_iri) + + kwargs[converted_pyname] = value + + return kwargs diff --git a/src/spdx_tools/spdx3/binding/spdx3_to_shacl_converter.py b/src/spdx_tools/spdx3/binding/spdx3_to_shacl_converter.py new file mode 100644 index 000000000..4804ae9e0 --- /dev/null +++ b/src/spdx_tools/spdx3/binding/spdx3_to_shacl_converter.py @@ -0,0 +1,203 @@ +import enum +from datetime import datetime, timezone +from typing import Dict, Optional, Tuple, cast + +from semantic_version import Version +from spdx_python_model import v3_0_1 as spdx_3_0 + +from spdx_tools.spdx3.binding import helpers +from spdx_tools.spdx3.model import CreationInfo, Element +from spdx_tools.spdx3.model.positive_integer_range import PositiveIntegerRange +from spdx_tools.spdx3.payload import Payload +from spdx_tools.spdx.casing_tools import snake_case_to_camel_case + +base_creationinfo_id = "_:CreationInfo" + + +def _does_binding_class_exist(value) -> bool: + binding_name = helpers.get_binding_type_name_from_domain_object(value) + return binding_name in spdx_3_0.SHACLObject.CLASSES + + +def _convert_domain_enum_to_binding_uri(value) -> str: + binding_name = helpers.get_binding_type_name_from_domain_object(value) + + # special case for ExternalRefType because it is called ExternalReferenceType + # in the domain model but ExternalRefType in the SHACL model + if binding_name == "ExternalReferenceType": + shacl_object = spdx_3_0.SHACLObject.CLASSES["ExternalRefType"]() + return shacl_object.NAMED_INDIVIDUALS[snake_case_to_camel_case(value.name)] + else: + shacl_object = spdx_3_0.SHACLObject.CLASSES[binding_name]() + return shacl_object.NAMED_INDIVIDUALS[snake_case_to_camel_case(value.name)] + + +class Spdx3ToSHACLConverter: + creationinfo_str_to_id: Dict[str, str] + + def __init__(self): + self.creationinfo_str_to_id = {} + + def _convert_value_to_positive_int_range(self, value: PositiveIntegerRange) -> spdx_3_0.PositiveIntegerRange: + return spdx_3_0.PositiveIntegerRange( + beginIntegerRange=value.begin_integer_range, + endIntegerRange=value.end_integer_range, + ) + + def _convert_field_to_property_or_return_id_of_id_haver( + self, + value: Element | CreationInfo | list | dict | None, + shacl_set: spdx_3_0.SHACLObjectSet, + binding_obj: spdx_3_0.SHACLObject, + pyname: str, + ) -> spdx_3_0.SHACLObject | str | bool | int | float | datetime | list | dict | None: + """Convert a SPDX domain field value to its SHACL representation. + + SPDX 3 properties either contain the ID of another Element (or CreationInfo), + or the full body of a non-Element, such as an externalIdentifier, + or a URI or date. This accepts a field's value and returns whatever + the correct representation of that value is for that field. + """ + if value is None: + return None + + id_havers = (Element, CreationInfo) + if isinstance(value, id_havers): + # Special case for CreationInfo, since it uniquely has an ID, but is not an Element + if type(value) is CreationInfo: + return self._retrieve_or_create_creation_info(value, shacl_set) + else: + return cast(Element, value).spdx_id + elif isinstance(value, list): + return [ + self._convert_field_to_property_or_return_id_of_id_haver(v, shacl_set, binding_obj, pyname) + for v in value + ] + elif isinstance(value, dict): + converted_dict = [] # Yes, this is supposed to be a list because the SHACL representation of a dictionary + # is a list of DictionaryEntry objects + for k, v in value.items(): + dict_entry: spdx_3_0.DictionaryEntry = spdx_3_0.DictionaryEntry() + dict_entry.key = k + converted_value = self._convert_field_to_property_or_return_id_of_id_haver( + v, shacl_set, binding_obj, pyname + ) + + if not type(converted_value) is str: + raise Exception("Parsing error. Dictionary entry's value should be a string. Dictionary: {value}") + + dict_entry.value = converted_value + converted_dict.append(dict_entry) + return converted_dict + elif isinstance(value, enum.Enum): + return _convert_domain_enum_to_binding_uri(value) + elif isinstance(value, (str, bool, int, float)): + return value + elif isinstance(value, datetime): + return value.astimezone(timezone.utc) + elif isinstance(value, PositiveIntegerRange): + return self._convert_value_to_positive_int_range(value) + elif isinstance(value, Version): + return str(value) + elif _does_binding_class_exist(value): + return self._convert_non_element_with_binding_class_to_shacl(value, shacl_set) + else: + raise NotImplementedError(f"converter not implemented for type: '{type(value)} and value: {value}'") + + def _convert_non_element_with_binding_class_to_shacl( + self, obj, shacl_set: spdx_3_0.SHACLObjectSet + ) -> spdx_3_0.SHACLObject: + binding_name = helpers.get_binding_type_name_from_domain_object(obj) + if not _does_binding_class_exist(obj): + raise TypeError(f"No corresponding SHACL binding class found for {binding_name}") + + shacl_object = spdx_3_0.SHACLObject.CLASSES[binding_name]() + class_names_to_fields: dict[type, list[str]] = helpers.get_attributes_by_domain_class_hierarchy(obj) + + for class_type, attribute_list in class_names_to_fields.items(): + profile_prefix = helpers.get_spdx_profile_prefix(class_type) + + for attribute_name in attribute_list: + full_attribute_name: str = helpers.get_binding_property_name_from_element_field_name_and_prefix( + attribute_name, profile_prefix + ) + if hasattr(obj, attribute_name): + value = getattr(obj, attribute_name) + converted_value = self._convert_field_to_property_or_return_id_of_id_haver( + value, shacl_set, shacl_object, full_attribute_name + ) + if converted_value: + setattr(shacl_object, full_attribute_name, converted_value) + + return shacl_object + + def convert(self, obj: Payload) -> spdx_3_0.SHACLObjectSet: + shacl_set: spdx_3_0.SHACLObjectSet = spdx_3_0.SHACLObjectSet() + + for _, element in obj.get_full_map().items(): + self._convert_element_to_shacl(element, shacl_set) + return shacl_set + + def _retrieve_or_create_creation_info(self, value, shacl_set: spdx_3_0.SHACLObjectSet) -> str: + if str(value) in self.creationinfo_str_to_id: + return self.creationinfo_str_to_id[ + str(value) + ] # return the ID of the creationinfo that was made previously + else: + new_creation_info_id = base_creationinfo_id + str(len(self.creationinfo_str_to_id)) + self.creationinfo_str_to_id[str(value)] = new_creation_info_id + shacl_set.add(self._convert_creation_info_to_shacl(value, new_creation_info_id)) + return new_creation_info_id + + def _convert_creation_info_to_shacl( + self, creation_info: CreationInfo, new_creation_info_id + ) -> spdx_3_0.SHACLObject: + shacl_object = spdx_3_0.CreationInfo() + shacl_object.created = creation_info.created.astimezone(timezone.utc) + for created_by in creation_info.created_by: + shacl_object.createdBy.append(created_by) + shacl_object.specVersion = str(creation_info.spec_version) + shacl_object["@id"] = new_creation_info_id + for creating_using in creation_info.created_using: + shacl_object.createdUsing.append(creating_using) + if creation_info.comment is not None: + shacl_object.comment = creation_info.comment + return shacl_object + + def _convert_element_to_shacl(self, element, shacl_set: spdx_3_0.SHACLObjectSet) -> None: + spdx_type_name = helpers.get_binding_type_name_from_domain_object(element) + + # Dynamically create an instance of the corresponding SHACL class + binding_obj = spdx_3_0.SHACLObject.CLASSES[spdx_type_name]() + + binding_class_properties: list[Tuple[Optional[str], str, Optional[str]]] = list(binding_obj.property_keys()) + for prop in binding_class_properties: + + # pyname is the name of the property in the binding class i.e. comment or security_modifiedTime + pyname = prop[0] + + if pyname is None or pyname.isspace(): + raise Exception(f"pyname missing for '{binding_obj}'") + + # special case. Do not try to extract the ID if the binding class is not an Element + # because only Elements have IDs + if helpers.is_binding_object_kind_not_iri_and_the_field_is_id(binding_obj, pyname): + continue + + # change the trimmed pyname to snake case so that it will match the domain + # class property name and also handle the special cases + converted_pyname = helpers.get_domain_property_name_from_binding_property_name(pyname) + + if hasattr(element, converted_pyname): + value = getattr(element, converted_pyname) + converted_value = self._convert_field_to_property_or_return_id_of_id_haver( + value, shacl_set, binding_obj, pyname + ) + + is_string_with_non_empty_value = type(converted_value) is str and len(converted_value) > 0 + is_string = isinstance(converted_value, str) + + if converted_value is not None and (is_string_with_non_empty_value or not is_string): + setattr(binding_obj, pyname, converted_value) + + shacl_set.add(binding_obj) diff --git a/src/spdx_tools/spdx3/bump_from_spdx2/actor.py b/src/spdx_tools/spdx3/bump_from_spdx2/actor.py index 3283bf11a..62df37c5d 100644 --- a/src/spdx_tools/spdx3/bump_from_spdx2/actor.py +++ b/src/spdx_tools/spdx3/bump_from_spdx2/actor.py @@ -1,8 +1,6 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 -from typing import Optional - from beartype.typing import List from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifier, ExternalIdentifierType, Organization, Person, Tool @@ -12,7 +10,7 @@ def bump_actor( - spdx2_actor: Spdx2_Actor, payload: Payload, document_namespace: str, creation_info: Optional[CreationInfo] = None + spdx2_actor: Spdx2_Actor, payload: Payload, document_namespace: str, creation_info: CreationInfo ) -> str: name: str = spdx2_actor.name email: str = spdx2_actor.email diff --git a/src/spdx_tools/spdx3/bump_from_spdx2/creation_info.py b/src/spdx_tools/spdx3/bump_from_spdx2/creation_info.py index 914d12226..bc6146a46 100644 --- a/src/spdx_tools/spdx3/bump_from_spdx2/creation_info.py +++ b/src/spdx_tools/spdx3/bump_from_spdx2/creation_info.py @@ -7,7 +7,7 @@ from spdx_tools.spdx3.bump_from_spdx2.actor import bump_actor from spdx_tools.spdx3.bump_from_spdx2.external_document_ref import bump_external_document_ref from spdx_tools.spdx3.bump_from_spdx2.message import print_missing_conversion -from spdx_tools.spdx3.model import CreationInfo, ProfileIdentifierType, SpdxDocument +from spdx_tools.spdx3.model import CreationInfo, SpdxDocument from spdx_tools.spdx3.payload import Payload from spdx_tools.spdx.model.actor import ActorType from spdx_tools.spdx.model.document import CreationInfo as Spdx2_CreationInfo @@ -37,11 +37,9 @@ def bump_creation_info(spdx2_creation_info: Spdx2_CreationInfo, payload: Payload "part of licensing profile, " "https://github.com/spdx/spdx-3-model/issues/131", ) creation_info = CreationInfo( - spec_version=Version("3.0.0"), + spec_version=Version("3.0.1"), created=spdx2_creation_info.created, created_by=[], - profile=[ProfileIdentifierType.CORE, ProfileIdentifierType.SOFTWARE, ProfileIdentifierType.LICENSING], - data_license="https://spdx.org/licenses/" + spdx2_creation_info.data_license, ) # due to creators having a creation_info themselves which inherits from the document's one, @@ -75,5 +73,5 @@ def bump_creation_info(spdx2_creation_info: Spdx2_CreationInfo, payload: Payload element=[], root_element=[], imports=imports, - namespaces=namespaces, + namespace_map=namespaces, ) diff --git a/src/spdx_tools/spdx3/bump_from_spdx2/external_document_ref.py b/src/spdx_tools/spdx3/bump_from_spdx2/external_document_ref.py index 41360ffa4..63d299db5 100644 --- a/src/spdx_tools/spdx3/bump_from_spdx2/external_document_ref.py +++ b/src/spdx_tools/spdx3/bump_from_spdx2/external_document_ref.py @@ -12,6 +12,6 @@ def bump_external_document_ref(external_document_ref: ExternalDocumentRef) -> Tu verified_using: List[Hash] = [bump_checksum(external_document_ref.checksum)] return NamespaceMap(external_document_ref.document_ref_id, external_document_ref.document_uri + "#"), ExternalMap( - external_id=f"{external_document_ref.document_ref_id}:SPDXRef-DOCUMENT", + external_spdx_id=f"{external_document_ref.document_ref_id}:SPDXRef-DOCUMENT", verified_using=verified_using, ) diff --git a/src/spdx_tools/spdx3/bump_from_spdx2/file.py b/src/spdx_tools/spdx3/bump_from_spdx2/file.py index 824f95a1f..cbd7bcaee 100644 --- a/src/spdx_tools/spdx3/bump_from_spdx2/file.py +++ b/src/spdx_tools/spdx3/bump_from_spdx2/file.py @@ -6,6 +6,7 @@ from spdx_tools.spdx3.bump_from_spdx2.checksum import bump_checksum from spdx_tools.spdx3.bump_from_spdx2.message import print_missing_conversion from spdx_tools.spdx3.model import ExternalMap +from spdx_tools.spdx3.model.creation_info import CreationInfo from spdx_tools.spdx3.model.software import File from spdx_tools.spdx3.payload import Payload from spdx_tools.spdx.model import ExternalDocumentRef, SpdxNoAssertion @@ -19,13 +20,14 @@ def bump_file( document_namespace: str, external_document_refs: List[ExternalDocumentRef], imports: List[ExternalMap], + creation_info: CreationInfo, ): spdx_id = get_full_element_spdx_id(spdx2_file, document_namespace, external_document_refs) if ":" in spdx2_file.spdx_id: imports.append( ExternalMap( - external_id=spdx2_file.spdx_id, - defining_document=f"{spdx2_file.spdx_id.split(':')[0]}:SPDXRef-DOCUMENT", + external_spdx_id=spdx2_file.spdx_id, + defining_artifact=f"{spdx2_file.spdx_id.split(':')[0]}:SPDXRef-DOCUMENT", ) ) @@ -51,6 +53,7 @@ def bump_file( comment=spdx2_file.comment, verified_using=integrity_methods, copyright_text=copyright_text, - attribution_text=", ".join(spdx2_file.attribution_texts), + attribution_text=[", ".join(spdx2_file.attribution_texts)], + creation_info=creation_info, ) ) diff --git a/src/spdx_tools/spdx3/bump_from_spdx2/license_expression.py b/src/spdx_tools/spdx3/bump_from_spdx2/license_expression.py index de5f006d3..a0500c4d0 100644 --- a/src/spdx_tools/spdx3/bump_from_spdx2/license_expression.py +++ b/src/spdx_tools/spdx3/bump_from_spdx2/license_expression.py @@ -1,89 +1,217 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 -from beartype.typing import List, Union +from typing import List + from license_expression import AND, OR, LicenseExpression, LicenseSymbol, LicenseWithExceptionSymbol from spdx_tools.common.spdx_licensing import spdx_licensing -from spdx_tools.spdx3.model.licensing import ( - AnyLicenseInfo, +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.element import Element +from spdx_tools.spdx3.model.expandedlicensing import ( ConjunctiveLicenseSet, CustomLicense, CustomLicenseAddition, DisjunctiveLicenseSet, License, LicenseAddition, - LicenseField, ListedLicense, ListedLicenseException, - NoAssertionLicense, - NoneLicense, WithAdditionOperator, ) -from spdx_tools.spdx.model import ExtractedLicensingInfo, SpdxNoAssertion, SpdxNone +from spdx_tools.spdx3.model.simplelicensing.any_license_info import AnyLicenseInfo +from spdx_tools.spdx3.payload import Payload +from spdx_tools.spdx.model import ExtractedLicensingInfo + +LICENSE_EXPRESSION_TYPES = { + ConjunctiveLicenseSet, + DisjunctiveLicenseSet, + LicenseWithExceptionSymbol, + WithAdditionOperator, + ListedLicense, + CustomLicense, + ListedLicenseException, + CustomLicenseAddition, +} -def bump_license_expression_or_none_or_no_assertion( - element: Union[LicenseExpression, SpdxNoAssertion, SpdxNone], - extracted_licensing_info: List[ExtractedLicensingInfo], -) -> LicenseField: - if isinstance(element, SpdxNone): - return NoneLicense() - elif isinstance(element, SpdxNoAssertion): - return NoAssertionLicense() - else: - return bump_license_expression(element, extracted_licensing_info) +def _get_spdx_id_for_license_expression( + license_expression_type: type, document_namespace: str, license_element_counts: dict[type, int] +) -> str: + number_of_existing_elements_of_type = license_element_counts.get(license_expression_type, 0) + + return f"{document_namespace}#SPDXRef-{license_expression_type.__name__}-{number_of_existing_elements_of_type}" + + +def _get_count_of_license_expression_types(license_expression_types: set[type], payload: Payload) -> dict[type, int]: + license_element_counts: dict[type, int] = {} + for element_type in payload.get_full_map().values(): + if type(element_type) in license_expression_types: + license_element_counts[type(element_type)] = license_element_counts.get(type(element_type), 0) + 1 + return license_element_counts + + +# Convenience method since this code block is called multiple times in this file +def _add_license(license: Element, payload: Payload, license_element_counts: dict[type, int]) -> None: + license_type = type(license) + payload.add_element(license) + license_element_counts[license_type] = license_element_counts.get(license_type, 0) + 1 + + +# TODO make this raise an exception if the license expression contains NOASSERTION or NONE, +# as they are apparently not supported in SPDX 3.0 def bump_license_expression( - license_expression: LicenseExpression, extracted_licensing_info: List[ExtractedLicensingInfo] + license_expression: LicenseExpression, + extracted_licensing_info: List[ExtractedLicensingInfo], + document_namespace: str, + creation_info: CreationInfo, + payload: Payload, + license_element_counts: ( + dict[type, int] | None + ) = None, # do not use this argument unless already inside a bump_license_expression ) -> AnyLicenseInfo: + + if not license_element_counts: + license_element_counts = _get_count_of_license_expression_types(LICENSE_EXPRESSION_TYPES, payload) + if isinstance(license_expression, AND): - return ConjunctiveLicenseSet( - member=[bump_license_expression(element, extracted_licensing_info) for element in license_expression.args] + license = ConjunctiveLicenseSet( + _get_spdx_id_for_license_expression(ConjunctiveLicenseSet, document_namespace, license_element_counts), + member=[ + bump_license_expression( + element, + extracted_licensing_info, + document_namespace, + creation_info, + payload, + license_element_counts, + ).spdx_id + for element in license_expression.args + ], + creation_info=creation_info, ) + _add_license(license, payload, license_element_counts) + return license if isinstance(license_expression, OR): - return DisjunctiveLicenseSet( - member=[bump_license_expression(element, extracted_licensing_info) for element in license_expression.args] + license = DisjunctiveLicenseSet( + _get_spdx_id_for_license_expression(DisjunctiveLicenseSet, document_namespace, license_element_counts), + creation_info=creation_info, + member=[ + bump_license_expression( + element, + extracted_licensing_info, + document_namespace, + creation_info, + payload, + license_element_counts, + ).spdx_id + for element in license_expression.args + ], ) + _add_license(license, payload, license_element_counts) + return license if isinstance(license_expression, LicenseWithExceptionSymbol): - subject_license = bump_license_expression(license_expression.license_symbol, extracted_licensing_info) + subject_license = bump_license_expression( + license_expression.license_symbol, + extracted_licensing_info, + document_namespace, + creation_info, + payload, + license_element_counts, + ) if not isinstance(subject_license, License): raise ValueError("Subject of LicenseException couldn't be converted to License.") - return WithAdditionOperator( - subject_license=subject_license, - subject_addition=bump_license_exception(license_expression.exception_symbol, extracted_licensing_info), + license = WithAdditionOperator( + _get_spdx_id_for_license_expression(WithAdditionOperator, document_namespace, license_element_counts), + subject_extendable_license=subject_license.spdx_id, + subject_addition=bump_license_exception( + license_expression.exception_symbol, + extracted_licensing_info, + document_namespace, + creation_info, + payload, + license_element_counts, + ).spdx_id, + creation_info=creation_info, ) + _add_license(license, payload, license_element_counts) + return license if isinstance(license_expression, LicenseSymbol): if not spdx_licensing.validate(license_expression).invalid_symbols: - return ListedLicense(license_expression.key, license_expression.obj, "blank") + license = ListedLicense( + _get_spdx_id_for_license_expression(ListedLicense, document_namespace, license_element_counts), + "blank", + creation_info, + name=license_expression.obj, + ) + _add_license(license, payload, license_element_counts) + return license else: for licensing_info in extracted_licensing_info: if licensing_info.license_id == license_expression.key: # the fields are optional in ExtractedLicensingInfo, to prevent type errors we use a type # conversion to str as a quick fix - return CustomLicense( - str(licensing_info.license_id), - str(licensing_info.license_name), + license = CustomLicense( + _get_spdx_id_for_license_expression(CustomLicense, document_namespace, license_element_counts), str(licensing_info.extracted_text), + creation_info, + name=str(licensing_info.license_name), ) + _add_license(license, payload, license_element_counts) + return license - return CustomLicense(license_expression.key, "", "") + license = CustomLicense( + _get_spdx_id_for_license_expression(CustomLicense, document_namespace, license_element_counts), + "blank", + creation_info, + ) + _add_license(license, payload, license_element_counts) + return license + raise ValueError(f"Unsupported license expression type: {type(license_expression)}") def bump_license_exception( - license_exception: LicenseSymbol, extracted_licensing_info: List[ExtractedLicensingInfo] + license_exception: LicenseSymbol, + extracted_licensing_info: List[ExtractedLicensingInfo], + document_namespace: str, + creation_info: CreationInfo, + payload: Payload, + license_element_counts: ( + dict[type, int] | None + ) = None, # do not use this argument unless already inside a bump_license_expression ) -> LicenseAddition: + if not license_element_counts: + license_element_counts = _get_count_of_license_expression_types(LICENSE_EXPRESSION_TYPES, payload) + if not spdx_licensing.validate(license_exception).invalid_symbols: - return ListedLicenseException(license_exception.key, "", "") + license = ListedLicenseException( + _get_spdx_id_for_license_expression(ListedLicenseException, document_namespace, license_element_counts), + license_exception.key, + creation_info, + ) + _add_license(license, payload, license_element_counts) + return license else: for licensing_info in extracted_licensing_info: if licensing_info.license_id == license_exception.key: # the fields are optional in ExtractedLicensingInfo, to prevent type errors we use a type conversion # to str as a quick fix - return CustomLicenseAddition( - str(licensing_info.license_id), - str(licensing_info.license_name), + license = CustomLicenseAddition( + _get_spdx_id_for_license_expression( + CustomLicenseAddition, document_namespace, license_element_counts + ), str(licensing_info.extracted_text), + creation_info, ) - return CustomLicenseAddition(license_exception.key, "", "") + _add_license(license, payload, license_element_counts) + return license + + license = CustomLicenseAddition( + _get_spdx_id_for_license_expression(CustomLicenseAddition, document_namespace, license_element_counts), + license_exception.key, + creation_info, + ) + _add_license(license, payload, license_element_counts) + return license diff --git a/src/spdx_tools/spdx3/bump_from_spdx2/package.py b/src/spdx_tools/spdx3/bump_from_spdx2/package.py index 3d358babd..9341c661d 100644 --- a/src/spdx_tools/spdx3/bump_from_spdx2/package.py +++ b/src/spdx_tools/spdx3/bump_from_spdx2/package.py @@ -14,6 +14,7 @@ ExternalReference, ExternalReferenceType, ) +from spdx_tools.spdx3.model.creation_info import CreationInfo from spdx_tools.spdx3.model.software import Package, SoftwarePurpose from spdx_tools.spdx3.payload import Payload from spdx_tools.spdx.model import Actor as Spdx2_Actor @@ -29,24 +30,24 @@ def bump_package( document_namespace: str, external_document_refs: List[ExternalDocumentRef], imports: List[ExternalMap], + creation_info: CreationInfo, ): spdx_id = get_full_element_spdx_id(spdx2_package, document_namespace, external_document_refs) if ":" in spdx2_package.spdx_id: imports.append( ExternalMap( - external_id=spdx2_package.spdx_id, - defining_document=f"{spdx2_package.spdx_id.split(':')[0]}:SPDXRef-DOCUMENT", + external_spdx_id=spdx2_package.spdx_id, + defining_artifact=f"{spdx2_package.spdx_id.split(':')[0]}:SPDXRef-DOCUMENT", ) ) - download_location = handle_no_assertion_or_none(spdx2_package.download_location, "package.download_location") print_missing_conversion("package2.file_name", 0, "https://github.com/spdx/spdx-3-model/issues/83") if isinstance(spdx2_package.supplier, Spdx2_Actor): - supplied_by_spdx_id = [bump_actor(spdx2_package.supplier, payload, document_namespace)] + supplied_by_spdx_id = bump_actor(spdx2_package.supplier, payload, document_namespace, creation_info) else: supplied_by_spdx_id = None if isinstance(spdx2_package.originator, Spdx2_Actor): - originated_by_spdx_id = [bump_actor(spdx2_package.originator, payload, document_namespace)] + originated_by_spdx_id = [bump_actor(spdx2_package.originator, payload, document_namespace, creation_info)] else: originated_by_spdx_id = None print_missing_conversion("package2.files_analyzed", 0, "https://github.com/spdx/spdx-3-model/issues/84") @@ -106,10 +107,11 @@ def bump_package( package_version=spdx2_package.version, download_location=download_location, package_url=package_url, - homepage=spdx2_package.homepage, + home_page=spdx2_package.homepage, source_info=spdx2_package.source_info, copyright_text=copyright_text, - attribution_text=", ".join(spdx2_package.attribution_texts), + attribution_text=[", ".join(spdx2_package.attribution_texts)], + creation_info=creation_info, ) ) @@ -125,7 +127,7 @@ def bump_package( "npm": None, "nuget": None, "bower": None, - "purl": ExternalIdentifierType.PURL, + "purl": ExternalIdentifierType.PACKAGE_URL, "swh": ExternalIdentifierType.SWHID, "gitoid": ExternalIdentifierType.GITOID, } diff --git a/src/spdx_tools/spdx3/bump_from_spdx2/relationship.py b/src/spdx_tools/spdx3/bump_from_spdx2/relationship.py index dd6909c49..a65528e8f 100644 --- a/src/spdx_tools/spdx3/bump_from_spdx2/relationship.py +++ b/src/spdx_tools/spdx3/bump_from_spdx2/relationship.py @@ -7,12 +7,14 @@ from beartype.typing import Dict, List, Optional, Tuple, Union from spdx_tools.spdx3.bump_from_spdx2.message import print_missing_conversion -from spdx_tools.spdx3.model import LifecycleScopeType, Relationship, RelationshipCompleteness, RelationshipType -from spdx_tools.spdx3.model.software import ( - DependencyConditionalityType, - SoftwareDependencyLinkType, - SoftwareDependencyRelationship, +from spdx_tools.spdx3.model import ( + LifecycleScopedRelationship, + LifecycleScopeType, + Relationship, + RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.creation_info import CreationInfo from spdx_tools.spdx3.payload import Payload from spdx_tools.spdx.model.relationship import Relationship as Spdx2_Relationship from spdx_tools.spdx.model.relationship import RelationshipType as Spdx2_RelationshipType @@ -24,25 +26,24 @@ relationship_mapping: Dict[ Spdx2_RelationshipType, Tuple[ - Union[Relationship, SoftwareDependencyRelationship], - RelationshipType, - Dict[str, Union[bool, LifecycleScopeType, SoftwareDependencyLinkType, DependencyConditionalityType]], + type[Relationship] | type[LifecycleScopeType] | None, + RelationshipType | None, + Dict[str, Union[bool, LifecycleScopeType]], ], ] = { - Spdx2_RelationshipType.AMENDS: (Relationship, RelationshipType.AMENDS, {}), - Spdx2_RelationshipType.ANCESTOR_OF: (Relationship, RelationshipType.ANCESTOR, {}), + Spdx2_RelationshipType.AMENDS: (Relationship, RelationshipType.AMENDED_BY, {"swap": True}), + Spdx2_RelationshipType.ANCESTOR_OF: (Relationship, RelationshipType.ANCESTOR_OF, {}), Spdx2_RelationshipType.BUILD_DEPENDENCY_OF: ( - SoftwareDependencyRelationship, + Relationship, RelationshipType.DEPENDS_ON, { "scope": LifecycleScopeType.BUILD, - "linkage": SoftwareDependencyLinkType.TOOL, }, ), Spdx2_RelationshipType.BUILD_TOOL_OF: ( - SoftwareDependencyRelationship, + LifecycleScopedRelationship, RelationshipType.DEPENDS_ON, - {"scope": LifecycleScopeType.BUILD, "linkage": SoftwareDependencyLinkType.TOOL}, + {"scope": LifecycleScopeType.BUILD}, ), Spdx2_RelationshipType.CONTAINED_BY: (Relationship, RelationshipType.CONTAINS, {"swap": True}), Spdx2_RelationshipType.CONTAINS: ( @@ -50,20 +51,20 @@ RelationshipType.CONTAINS, {}, ), # might be deleted in favor of depends on - Spdx2_RelationshipType.COPY_OF: (Relationship, RelationshipType.COPY, {}), + Spdx2_RelationshipType.COPY_OF: (Relationship, RelationshipType.COPIED_TO, {"swap": True}), Spdx2_RelationshipType.DATA_FILE_OF: (None, None, {}), # not defined, probably input/ output Spdx2_RelationshipType.DEPENDENCY_MANIFEST_OF: ( - SoftwareDependencyRelationship, + LifecycleScopedRelationship, RelationshipType.DEPENDS_ON, - {}, + {"scope": LifecycleScopeType.BUILD}, ), # "expect purpose has been set to manifest" Spdx2_RelationshipType.DEPENDENCY_OF: ( - SoftwareDependencyRelationship, + Relationship, RelationshipType.DEPENDS_ON, {"swap": True}, ), - Spdx2_RelationshipType.DEPENDS_ON: (SoftwareDependencyRelationship, RelationshipType.DEPENDS_ON, {}), - Spdx2_RelationshipType.DESCENDANT_OF: (Relationship, RelationshipType.ANCESTOR, {"swap": True}), + Spdx2_RelationshipType.DEPENDS_ON: (Relationship, RelationshipType.DEPENDS_ON, {}), + Spdx2_RelationshipType.DESCENDANT_OF: (Relationship, RelationshipType.DESCENDANT_OF, {}), Spdx2_RelationshipType.DESCRIBED_BY: (Relationship, RelationshipType.DESCRIBES, {"swap": True}), Spdx2_RelationshipType.DESCRIBES: ( Relationship, @@ -72,80 +73,80 @@ ), # might be deleted in favor of root # property Spdx2_RelationshipType.DEV_DEPENDENCY_OF: ( - SoftwareDependencyRelationship, + LifecycleScopedRelationship, RelationshipType.DEPENDS_ON, {"scope": LifecycleScopeType.DEVELOPMENT}, ), Spdx2_RelationshipType.DEV_TOOL_OF: ( - SoftwareDependencyRelationship, + LifecycleScopedRelationship, RelationshipType.DEPENDS_ON, - {"scope": LifecycleScopeType.DEVELOPMENT, "linkage": SoftwareDependencyLinkType.TOOL}, + {"scope": LifecycleScopeType.DEVELOPMENT}, ), Spdx2_RelationshipType.DISTRIBUTION_ARTIFACT: (None, None, {}), # not defined yet, purpose? - Spdx2_RelationshipType.DOCUMENTATION_OF: (Relationship, RelationshipType.DOCUMENTATION, {}), + Spdx2_RelationshipType.DOCUMENTATION_OF: (Relationship, RelationshipType.HAS_DOCUMENTATION, {"swap": True}), Spdx2_RelationshipType.DYNAMIC_LINK: ( - SoftwareDependencyRelationship, - RelationshipType.DEPENDS_ON, - {"linkage": SoftwareDependencyLinkType.DYNAMIC}, + LifecycleScopedRelationship, + RelationshipType.HAS_DYNAMIC_LINK, + {"swap": True, "scope": LifecycleScopeType.RUNTIME}, ), - Spdx2_RelationshipType.EXAMPLE_OF: (Relationship, RelationshipType.EXAMPLE, {}), - Spdx2_RelationshipType.EXPANDED_FROM_ARCHIVE: (Relationship, RelationshipType.EXPANDED_FROM_ARCHIVE, {}), - Spdx2_RelationshipType.FILE_ADDED: (Relationship, RelationshipType.FILE_ADDED, {}), - Spdx2_RelationshipType.FILE_DELETED: (Relationship, RelationshipType.FILE_DELETED, {}), - Spdx2_RelationshipType.FILE_MODIFIED: (Relationship, RelationshipType.FILE_MODIFIED, {}), + Spdx2_RelationshipType.EXAMPLE_OF: (Relationship, RelationshipType.HAS_EXAMPLE, {"swap": True}), + Spdx2_RelationshipType.EXPANDED_FROM_ARCHIVE: (Relationship, RelationshipType.EXPANDS_TO, {"swap": True}), + Spdx2_RelationshipType.FILE_ADDED: (Relationship, RelationshipType.HAS_ADDED_FILE, {"swap": True}), + Spdx2_RelationshipType.FILE_DELETED: (Relationship, RelationshipType.HAS_DELETED_FILE, {"swap": True}), + Spdx2_RelationshipType.FILE_MODIFIED: (Relationship, RelationshipType.MODIFIED_BY, {"swap": True}), Spdx2_RelationshipType.GENERATED_FROM: (Relationship, RelationshipType.GENERATES, {"swap": True}), Spdx2_RelationshipType.GENERATES: (Relationship, RelationshipType.GENERATES, {}), Spdx2_RelationshipType.HAS_PREREQUISITE: ( - SoftwareDependencyRelationship, - RelationshipType.DEPENDS_ON, - {"conditionality": DependencyConditionalityType.PREREQUISITE}, + LifecycleScopedRelationship, + RelationshipType.HAS_PREREQUISITE, + {}, ), - Spdx2_RelationshipType.METAFILE_OF: (Relationship, RelationshipType.METAFILE, {}), + Spdx2_RelationshipType.METAFILE_OF: (Relationship, RelationshipType.HAS_METADATA, {"swap": True}), Spdx2_RelationshipType.OPTIONAL_COMPONENT_OF: (None, None, {}), # converted to depends on and purpose? not clear Spdx2_RelationshipType.OPTIONAL_DEPENDENCY_OF: ( - SoftwareDependencyRelationship, - RelationshipType.DEPENDS_ON, - {"conditionality": DependencyConditionalityType.OPTIONAL}, + LifecycleScopedRelationship, + RelationshipType.HAS_OPTIONAL_DEPENDENCY, + {"swap": True}, ), Spdx2_RelationshipType.OTHER: (Relationship, RelationshipType.OTHER, {}), - Spdx2_RelationshipType.PACKAGE_OF: (SoftwareDependencyRelationship, RelationshipType.DEPENDS_ON, {}), - Spdx2_RelationshipType.PATCH_APPLIED: (Relationship, RelationshipType.PATCH, {"swap": True}), - Spdx2_RelationshipType.PATCH_FOR: (Relationship, RelationshipType.PATCH, {}), + Spdx2_RelationshipType.PACKAGE_OF: (Relationship, RelationshipType.PACKAGED_BY, {"swap": True}), + Spdx2_RelationshipType.PATCH_APPLIED: (Relationship, RelationshipType.PATCHED_BY, {"swap": True}), + Spdx2_RelationshipType.PATCH_FOR: (Relationship, RelationshipType.PATCHED_BY, {"swap": True}), Spdx2_RelationshipType.PREREQUISITE_FOR: ( - SoftwareDependencyRelationship, - RelationshipType.DEPENDS_ON, - {"conditionality": DependencyConditionalityType.PREREQUISITE}, + LifecycleScopedRelationship, + RelationshipType.HAS_PREREQUISITE, + {"swap": True}, ), Spdx2_RelationshipType.PROVIDED_DEPENDENCY_OF: ( - SoftwareDependencyRelationship, - RelationshipType.DEPENDS_ON, - {"scope": LifecycleScopeType.BUILD, "conditionality": DependencyConditionalityType.PROVIDED}, + LifecycleScopedRelationship, + RelationshipType.HAS_PROVIDED_DEPENDENCY, + {"swap": True}, ), Spdx2_RelationshipType.RUNTIME_DEPENDENCY_OF: ( - SoftwareDependencyRelationship, + LifecycleScopedRelationship, RelationshipType.DEPENDS_ON, - {"scope": LifecycleScopeType.RUNTIME}, + {"scope": LifecycleScopeType.RUNTIME, "swap": True}, ), Spdx2_RelationshipType.STATIC_LINK: ( - SoftwareDependencyRelationship, - RelationshipType.DEPENDS_ON, - {"linkage": SoftwareDependencyLinkType.STATIC}, + Relationship, + RelationshipType.HAS_STATIC_LINK, + {}, ), - Spdx2_RelationshipType.TEST_CASE_OF: (Relationship, RelationshipType.TEST_CASE, {}), + Spdx2_RelationshipType.TEST_CASE_OF: (Relationship, RelationshipType.HAS_TEST_CASE, {}), Spdx2_RelationshipType.TEST_DEPENDENCY_OF: ( - SoftwareDependencyRelationship, + LifecycleScopedRelationship, RelationshipType.DEPENDS_ON, {"scope": LifecycleScopeType.TEST}, ), - Spdx2_RelationshipType.TEST_OF: (Relationship, RelationshipType.TEST, {}), + Spdx2_RelationshipType.TEST_OF: (Relationship, RelationshipType.HAS_TEST, {}), Spdx2_RelationshipType.TEST_TOOL_OF: ( - SoftwareDependencyRelationship, - RelationshipType.DEPENDS_ON, - {"scope": LifecycleScopeType.TEST, "linkage": SoftwareDependencyLinkType.TOOL}, + LifecycleScopeType, + RelationshipType.USES_TOOL, + {"scope": LifecycleScopeType.TEST, "swap": True}, ), - Spdx2_RelationshipType.VARIANT_OF: (Relationship, RelationshipType.VARIANT, {}), - Spdx2_RelationshipType.REQUIREMENT_DESCRIPTION_FOR: (Relationship, RelationshipType.REQUIREMENT_FOR, {}), - Spdx2_RelationshipType.SPECIFICATION_FOR: (Relationship, RelationshipType.SPECIFICATION_FOR, {}), + Spdx2_RelationshipType.VARIANT_OF: (Relationship, RelationshipType.HAS_VARIANT, {"swap": True}), + Spdx2_RelationshipType.REQUIREMENT_DESCRIPTION_FOR: (Relationship, RelationshipType.HAS_REQUIREMENT, {}), + Spdx2_RelationshipType.SPECIFICATION_FOR: (Relationship, RelationshipType.HAS_SPECIFICATION, {"swap": True}), } @@ -153,10 +154,11 @@ def bump_relationships( spdx2_relationships: List[Spdx2_Relationship], payload: Payload, document_namespace: str, + creation_info: CreationInfo, ): generated_relationships: Dict[Tuple[str, str], List[Relationship]] = {} for counter, spdx2_relationship in enumerate(spdx2_relationships): - relationship = bump_relationship(spdx2_relationship, document_namespace, counter) + relationship = bump_relationship(spdx2_relationship, document_namespace, counter, creation_info) if relationship: generated_relationships.setdefault( (relationship.from_element, relationship.relationship_type.name), [] @@ -173,11 +175,12 @@ def bump_relationship( spdx2_relationship: Spdx2_Relationship, document_namespace: str, counter: int, -) -> Optional[Union[Relationship, SoftwareDependencyRelationship]]: + creation_info: CreationInfo, +) -> Optional[Relationship]: completeness, to = determine_completeness_and_to(spdx2_relationship.related_spdx_element_id) spdx_id = "#".join([document_namespace, f"SPDXRef-Relationship-{counter}"]) relationship_class, relationship_type, parameters = relationship_mapping[spdx2_relationship.relationship_type] - if relationship_class is None: + if relationship_class is None or relationship_type is None: print_missing_conversion(spdx2_relationship.relationship_type.name, 0) return @@ -186,31 +189,44 @@ def bump_relationship( if swap_direction: if not to: print_missing_conversion("Swapped Relationship to NoAssertion/None", 0) - return + from_element = ( + "NoneElement" + if isinstance(spdx2_relationship.related_spdx_element_id, SpdxNone) + else "NoAssertionElement" + ) + return Relationship( + spdx_id, + from_element, + relationship_type, + creation_info, + [f"{document_namespace}#{spdx2_relationship.spdx_element_id}"], + comment=spdx2_relationship.comment, + completeness=completeness, + ) from_element = to[0] to = [spdx2_relationship.spdx_element_id] else: from_element = spdx2_relationship.spdx_element_id - if relationship_class == SoftwareDependencyRelationship: - from_element = spdx2_relationship.spdx_element_id - - return SoftwareDependencyRelationship( + if relationship_class == LifecycleScopedRelationship: + # the value for the scope key will always be a LifecycleScopeType + scope: LifecycleScopeType = parameters.get("scope") # type: ignore + return LifecycleScopedRelationship( spdx_id, f"{document_namespace}#{from_element}", relationship_type, + creation_info, [f"{document_namespace}#{t}" for t in to], comment=spdx2_relationship.comment, completeness=completeness, - scope=parameters.get("scope"), - software_linkage=parameters.get("linkage"), - conditionality=parameters.get("conditionality"), + scope=scope, ) return Relationship( spdx_id, f"{document_namespace}#{from_element}", relationship_type, + creation_info, [f"{document_namespace}#{t}" for t in to], comment=spdx2_relationship.comment, completeness=completeness, @@ -221,7 +237,7 @@ def determine_completeness_and_to( related_spdx_element_id: Union[str, SpdxNone, SpdxNoAssertion], ) -> Tuple[Optional[RelationshipCompleteness], List[str]]: if isinstance(related_spdx_element_id, SpdxNoAssertion): - completeness = RelationshipCompleteness.NOASSERTION + completeness = RelationshipCompleteness.NO_ASSERTION to = [] elif isinstance(related_spdx_element_id, SpdxNone): completeness = RelationshipCompleteness.COMPLETE diff --git a/src/spdx_tools/spdx3/bump_from_spdx2/snippet.py b/src/spdx_tools/spdx3/bump_from_spdx2/snippet.py index b052511c1..2151d3c55 100644 --- a/src/spdx_tools/spdx3/bump_from_spdx2/snippet.py +++ b/src/spdx_tools/spdx3/bump_from_spdx2/snippet.py @@ -5,10 +5,11 @@ from spdx_tools.spdx3.bump_from_spdx2.message import print_missing_conversion from spdx_tools.spdx3.model import ExternalMap +from spdx_tools.spdx3.model.creation_info import CreationInfo from spdx_tools.spdx3.model.positive_integer_range import PositiveIntegerRange from spdx_tools.spdx3.model.software import Snippet from spdx_tools.spdx3.payload import Payload -from spdx_tools.spdx.model import ExternalDocumentRef, SpdxNoAssertion +from spdx_tools.spdx.model import ExternalDocumentRef, File, SpdxNoAssertion from spdx_tools.spdx.model.snippet import Snippet as Spdx2_Snippet from spdx_tools.spdx.spdx_element_utils import get_full_element_spdx_id @@ -19,17 +20,20 @@ def bump_integer_range(spdx2_range: Optional[Tuple[int, int]]) -> PositiveIntege def bump_snippet( spdx2_snippet: Spdx2_Snippet, + spdx2_file: File, payload: Payload, document_namespace: str, external_document_refs: List[ExternalDocumentRef], imports: List[ExternalMap], + creation_info: CreationInfo, ): spdx_id = get_full_element_spdx_id(spdx2_snippet, document_namespace, external_document_refs) + file_spdx_id = get_full_element_spdx_id(spdx2_file, document_namespace, external_document_refs) if ":" in spdx2_snippet.spdx_id: imports.append( ExternalMap( - external_id=spdx2_snippet.spdx_id, - defining_document=f"{spdx2_snippet.spdx_id.split(':')[0]}:SPDXRef-DOCUMENT", + external_spdx_id=spdx2_snippet.spdx_id, + defining_artifact=f"{spdx2_snippet.spdx_id.split(':')[0]}:SPDXRef-DOCUMENT", ) ) @@ -48,11 +52,13 @@ def bump_snippet( payload.add_element( Snippet( spdx_id=spdx_id, + snippet_from_file=file_spdx_id, name=spdx2_snippet.name, comment=spdx2_snippet.comment, byte_range=bump_integer_range(spdx2_snippet.byte_range), line_range=bump_integer_range(spdx2_snippet.line_range), copyright_text=copyright_text, - attribution_text=", ".join(spdx2_snippet.attribution_texts), + attribution_text=[", ".join(spdx2_snippet.attribution_texts)], + creation_info=creation_info, ) ) diff --git a/src/spdx_tools/spdx3/bump_from_spdx2/spdx_document.py b/src/spdx_tools/spdx3/bump_from_spdx2/spdx_document.py index 0257c403f..d080af211 100644 --- a/src/spdx_tools/spdx3/bump_from_spdx2/spdx_document.py +++ b/src/spdx_tools/spdx3/bump_from_spdx2/spdx_document.py @@ -40,8 +40,11 @@ def bump_spdx_document(document: Spdx2_Document) -> Payload: document_namespace, document.creation_info.external_document_refs, spdx_document.imports, + creation_info, ) + file_id_map = {} + for spdx2_file in document.files: bump_file( spdx2_file, @@ -49,18 +52,24 @@ def bump_spdx_document(document: Spdx2_Document) -> Payload: document_namespace, document.creation_info.external_document_refs, spdx_document.imports, + creation_info, ) + file_id_map[spdx2_file.spdx_id] = spdx2_file for spdx2_snippet in document.snippets: bump_snippet( spdx2_snippet, + file_id_map[spdx2_snippet.file_spdx_id], payload, document_namespace, document.creation_info.external_document_refs, spdx_document.imports, + creation_info, ) - bump_relationships(document.relationships, payload, document_namespace) + # TODO add bump licenses + + bump_relationships(document.relationships, payload, document_namespace, creation_info) for counter, spdx2_annotation in enumerate(document.annotations): bump_annotation(spdx2_annotation, payload, creation_info, document_namespace, counter) diff --git a/src/spdx_tools/spdx3/clitools/pyspdxtools3.py b/src/spdx_tools/spdx3/clitools/pyspdxtools3.py index 9dd21a5d1..d637cb1b3 100644 --- a/src/spdx_tools/spdx3/clitools/pyspdxtools3.py +++ b/src/spdx_tools/spdx3/clitools/pyspdxtools3.py @@ -24,7 +24,7 @@ "--outfile", "-o", help="The file to write the converted document to (write a dash for output to stdout or omit for no conversion)." - "For now only a prototype serialization to json-ld is available. The provided file will therefore be extended " + "For now only serialization to json-ld is available. The provided file will therefore be extended " "with `.jsonld`.", ) @click.option( @@ -35,8 +35,7 @@ @click.option("--novalidation", is_flag=True, help="Don't validate the provided document.") def main(infile: str, outfile: str, version: str, novalidation: bool): """ - CLI-tool to parse and validate a SPDX 2.x document and migrate it into the prototype of SPDX 3.0. As there is no - definition for a serialization yet output can only be written to stdout. + CLI-tool to parse and validate a SPDX 2.x document and migrate it into SPDX 3.0.1. To use, run: 'pyspdxtools3 --infile -o -' """ try: diff --git a/src/spdx_tools/spdx3/model/__init__.py b/src/spdx_tools/spdx3/model/__init__.py index 8fab45e9e..13008e8a9 100644 --- a/src/spdx_tools/spdx3/model/__init__.py +++ b/src/spdx_tools/spdx3/model/__init__.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 -from spdx_tools.spdx3.model.profile_identifier import ProfileIdentifierType +from spdx_tools.spdx3.model.profile_identifier_type import ProfileIdentifierType from spdx_tools.spdx3.model.creation_info import CreationInfo from spdx_tools.spdx3.model.integrity_method import IntegrityMethod from spdx_tools.spdx3.model.hash import Hash, HashAlgorithm @@ -15,11 +15,15 @@ from spdx_tools.spdx3.model.organization import Organization from spdx_tools.spdx3.model.software_agent import SoftwareAgent from spdx_tools.spdx3.model.tool import Tool -from spdx_tools.spdx3.model.spdx_collection import ElementCollection +from spdx_tools.spdx3.model.element_collection import ElementCollection from spdx_tools.spdx3.model.bundle import Bundle from spdx_tools.spdx3.model.bom import Bom from spdx_tools.spdx3.model.spdx_document import SpdxDocument from spdx_tools.spdx3.model.annotation import Annotation, AnnotationType from spdx_tools.spdx3.model.relationship import Relationship, RelationshipType, RelationshipCompleteness from spdx_tools.spdx3.model.lifecycle_scoped_relationship import LifecycleScopedRelationship, LifecycleScopeType -from spdx_tools.spdx3.model.artifact import Artifact +from spdx_tools.spdx3.model.artifact import Artifact, SupportType +from spdx_tools.spdx3.model.presence_type import PresenceType +from spdx_tools.spdx3.model.package_verification_code import PackageVerificationCode +from spdx_tools.spdx3.model.positive_integer_range import PositiveIntegerRange +from spdx_tools.spdx3.model.individual_element import IndividualElement diff --git a/src/spdx_tools/spdx3/model/agent.py b/src/spdx_tools/spdx3/model/agent.py index 14db2a52e..38f48243e 100644 --- a/src/spdx_tools/spdx3/model/agent.py +++ b/src/spdx_tools/spdx3/model/agent.py @@ -6,6 +6,7 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, Element, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties @@ -13,7 +14,7 @@ class Agent(Element): def __init__( self, spdx_id: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -21,9 +22,10 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/ai/__init__.py b/src/spdx_tools/spdx3/model/ai/__init__.py index 1f711abf6..b40ff2360 100644 --- a/src/spdx_tools/spdx3/model/ai/__init__.py +++ b/src/spdx_tools/spdx3/model/ai/__init__.py @@ -1,4 +1,7 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 -from spdx_tools.spdx3.model.ai.ai_package import AIPackage +from spdx_tools.spdx3.model.ai.ai_package import AIPackage, SafetyRiskAssessmentType +from spdx_tools.spdx3.model.ai.energy_consumption import EnergyConsumption +from spdx_tools.spdx3.model.ai.energy_consumption_description import EnergyConsumptionDescription +from spdx_tools.spdx3.model.ai.energy_unit_type import EnergyUnitType diff --git a/src/spdx_tools/spdx3/model/ai/ai_package.py b/src/spdx_tools/spdx3/model/ai/ai_package.py index f08907352..8ae559b4f 100644 --- a/src/spdx_tools/spdx3/model/ai/ai_package.py +++ b/src/spdx_tools/spdx3/model/ai/ai_package.py @@ -10,8 +10,12 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod -from spdx_tools.spdx3.model.licensing import LicenseField +from spdx_tools.spdx3.model.ai.energy_consumption import EnergyConsumption +from spdx_tools.spdx3.model.artifact import SupportType +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.presence_type import PresenceType from spdx_tools.spdx3.model.software import Package, SoftwarePurpose +from spdx_tools.spdx3.model.software.content_identifier import ContentIdentifier class SafetyRiskAssessmentType(Enum): @@ -23,7 +27,7 @@ class SafetyRiskAssessmentType(Enum): @dataclass_with_properties class AIPackage(Package): - energy_consumption: Optional[str] = None + energy_consumption: Optional[EnergyConsumption] = None standard_compliance: List[str] = field(default_factory=list) limitation: Optional[str] = None type_of_model: List[str] = field(default_factory=list) @@ -32,44 +36,42 @@ class AIPackage(Package): hyperparameter: Dict[str, Optional[str]] = field(default_factory=dict) model_data_preprocessing: List[str] = field(default_factory=list) model_explainability: List[str] = field(default_factory=list) - sensitive_personal_information: Optional[bool] = None + use_sensitive_personal_information: Optional[PresenceType] = None metric_decision_threshold: Dict[str, Optional[str]] = field(default_factory=dict) metric: Dict[str, Optional[str]] = field(default_factory=dict) domain: List[str] = field(default_factory=list) - autonomy_type: Optional[bool] = None + autonomy_type: Optional[PresenceType] = None safety_risk_assessment: Optional[SafetyRiskAssessmentType] = None def __init__( self, spdx_id: str, name: str, - supplied_by: List[str], + supplied_by: Optional[str], download_location: str, package_version: str, primary_purpose: SoftwarePurpose, release_time: datetime, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, summary: Optional[str] = None, description: Optional[str] = None, comment: Optional[str] = None, verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, originated_by: List[str] | None = None, built_time: Optional[datetime] = None, valid_until_time: Optional[datetime] = None, standard: List[str] | None = None, - content_identifier: Optional[str] = None, + content_identifier: List[ContentIdentifier] | None = None, additional_purpose: List[SoftwarePurpose] | None = None, - concluded_license: Optional[LicenseField] = None, - declared_license: Optional[LicenseField] = None, copyright_text: Optional[str] = None, - attribution_text: Optional[str] = None, + attribution_text: List[str] | None = None, package_url: Optional[str] = None, - homepage: Optional[str] = None, + home_page: Optional[str] = None, source_info: Optional[str] = None, - energy_consumption: Optional[str] = None, + energy_consumption: Optional[EnergyConsumption] = None, standard_compliance: List[str] | None = None, limitation: Optional[str] = None, type_of_model: List[str] | None = None, @@ -78,19 +80,24 @@ def __init__( hyperparameter: Dict[str, Optional[str]] | None = None, model_data_preprocessing: List[str] | None = None, model_explainability: List[str] | None = None, - sensitive_personal_information: Optional[bool] = None, + use_sensitive_personal_information: Optional[PresenceType] = None, metric_decision_threshold: Dict[str, Optional[str]] | None = None, metric: Dict[str, Optional[str]] | None = None, domain: List[str] | None = None, - autonomy_type: Optional[bool] = None, + autonomy_type: Optional[PresenceType] = None, safety_risk_assessment: Optional[SafetyRiskAssessmentType] = None, + standard_name: List[str] | None = None, + support_level: List[SupportType] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension originated_by = [] if originated_by is None else originated_by additional_purpose = [] if additional_purpose is None else additional_purpose + attribution_text = [] if attribution_text is None else attribution_text standard = [] if standard is None else standard + content_identifier = [] if content_identifier is None else content_identifier standard_compliance = [] if standard_compliance is None else standard_compliance type_of_model = [] if type_of_model is None else type_of_model hyperparameter = {} if hyperparameter is None else hyperparameter @@ -99,4 +106,6 @@ def __init__( metric_decision_threshold = {} if metric_decision_threshold is None else metric_decision_threshold metric = {} if metric is None else metric domain = [] if domain is None else domain + standard_name = [] if standard_name is None else standard_name + support_level = [] if support_level is None else support_level check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/ai/energy_consumption.py b/src/spdx_tools/spdx3/model/ai/energy_consumption.py new file mode 100644 index 000000000..e7c5af5be --- /dev/null +++ b/src/spdx_tools/spdx3/model/ai/energy_consumption.py @@ -0,0 +1,25 @@ +from dataclasses import field + +from beartype.typing import List + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.ai.energy_consumption_description import EnergyConsumptionDescription + + +@dataclass_with_properties +class EnergyConsumption: + finetuning_energy_consumption: List[EnergyConsumptionDescription] = field(default_factory=list) + inference_energy_consumption: List[EnergyConsumptionDescription] = field(default_factory=list) + training_energy_consumption: List[EnergyConsumptionDescription] = field(default_factory=list) + + def __init__( + self, + finetuning_energy_consumption: List[EnergyConsumptionDescription] | None = None, + inference_energy_consumption: List[EnergyConsumptionDescription] | None = None, + training_energy_consumption: List[EnergyConsumptionDescription] | None = None, + ): + finetuning_energy_consumption = [] if finetuning_energy_consumption is None else finetuning_energy_consumption + inference_energy_consumption = [] if inference_energy_consumption is None else inference_energy_consumption + training_energy_consumption = [] if training_energy_consumption is None else training_energy_consumption + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/ai/energy_consumption_description.py b/src/spdx_tools/spdx3/model/ai/energy_consumption_description.py new file mode 100644 index 000000000..340e1fb6d --- /dev/null +++ b/src/spdx_tools/spdx3/model/ai/energy_consumption_description.py @@ -0,0 +1,12 @@ +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.ai.energy_unit_type import EnergyUnitType + + +@dataclass_with_properties +class EnergyConsumptionDescription: + energy_quantity: float | int = None # Decimal value + energy_unit: EnergyUnitType = None + + def __init__(self, energy_quantity: float | int, energy_unit: EnergyUnitType): + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/ai/energy_unit_type.py b/src/spdx_tools/spdx3/model/ai/energy_unit_type.py new file mode 100644 index 000000000..38c568e0a --- /dev/null +++ b/src/spdx_tools/spdx3/model/ai/energy_unit_type.py @@ -0,0 +1,7 @@ +from enum import Enum, auto + + +class EnergyUnitType(Enum): + KILOWATT_HOUR = auto() + MEGAJOULE = auto() + OTHER = auto() diff --git a/src/spdx_tools/spdx3/model/annotation.py b/src/spdx_tools/spdx3/model/annotation.py index e9fa6a7a0..f11d11bea 100644 --- a/src/spdx_tools/spdx3/model/annotation.py +++ b/src/spdx_tools/spdx3/model/annotation.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 -from dataclasses import field from enum import Enum, auto from beartype.typing import List, Optional @@ -9,6 +8,7 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, Element, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.extension.extension import Extension class AnnotationType(Enum): @@ -20,7 +20,7 @@ class AnnotationType(Enum): class Annotation(Element): annotation_type: AnnotationType = None subject: str = None - content_type: List[str] = field(default_factory=list) # placeholder for MediaType + content_type: Optional[str] = None statement: Optional[str] = None def __init__( @@ -28,7 +28,7 @@ def __init__( spdx_id: str, annotation_type: AnnotationType, subject: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -36,12 +36,12 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, - content_type: List[str] | None = None, + extension: List[Extension] | None = None, + content_type: Optional[str] = None, statement: Optional[str] = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier - content_type = [] if content_type is None else content_type + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/artifact.py b/src/spdx_tools/spdx3/model/artifact.py index 0fccec89c..113b21653 100644 --- a/src/spdx_tools/spdx3/model/artifact.py +++ b/src/spdx_tools/spdx3/model/artifact.py @@ -4,6 +4,7 @@ from abc import abstractmethod from dataclasses import field from datetime import datetime +from enum import Enum, auto from beartype.typing import List, Optional @@ -11,14 +12,25 @@ from spdx_tools.spdx3.model import Element +class SupportType(Enum): + DEPLOYED = auto() + DEVELOPMENT = auto() + END_OF_SUPPORT = auto() + LIMITED_SUPPORT = auto() + NO_ASSERTION = auto() + NO_SUPPORT = auto() + SUPPORT = auto() + + @dataclass_with_properties class Artifact(Element): originated_by: List[str] = field(default_factory=list) # SPDXID of the Agent/Tool - supplied_by: List[str] = field(default_factory=list) # SPDXID of the Agent/Tool + supplied_by: Optional[str] = None # SPDXID of the Agent/Tool built_time: Optional[datetime] = None release_time: Optional[datetime] = None valid_until_time: Optional[datetime] = None - standard: List[str] = field(default_factory=list) + standard_name: List[str] = field(default_factory=list) + support_level: List[SupportType] = field(default_factory=list) @abstractmethod def __init__(self): diff --git a/src/spdx_tools/spdx3/model/bom.py b/src/spdx_tools/spdx3/model/bom.py index bf9b874a1..5191b6e0d 100644 --- a/src/spdx_tools/spdx3/model/bom.py +++ b/src/spdx_tools/spdx3/model/bom.py @@ -9,11 +9,11 @@ Bundle, CreationInfo, ExternalIdentifier, - ExternalMap, ExternalReference, IntegrityMethod, - NamespaceMap, ) +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.profile_identifier_type import ProfileIdentifierType @dataclass_with_properties @@ -26,7 +26,7 @@ def __init__( spdx_id: str, element: List[str], root_element: List[str], - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -34,14 +34,13 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, - namespaces: List[NamespaceMap] | None = None, - imports: List[ExternalMap] | None = None, + extension: List[Extension] | None = None, context: Optional[str] = None, + profile_conformance: List[ProfileIdentifierType] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier - namespaces = [] if namespaces is None else namespaces - imports = [] if imports is None else imports + extension = [] if extension is None else extension + profile_conformance = [] if profile_conformance is None else profile_conformance check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/build/build.py b/src/spdx_tools/spdx3/model/build/build.py index 37583371e..80b9024b3 100644 --- a/src/spdx_tools/spdx3/model/build/build.py +++ b/src/spdx_tools/spdx3/model/build/build.py @@ -9,6 +9,7 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, Element, ExternalIdentifier, ExternalReference, Hash, IntegrityMethod +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties @@ -18,7 +19,7 @@ class Build(Element): config_source_entrypoint: List[str] = field(default_factory=list) config_source_uri: List[str] = field(default_factory=list) config_source_digest: List[Hash] = field(default_factory=list) - parameters: Dict[str, str] = field(default_factory=dict) + parameter: Dict[str, str] = field(default_factory=dict) build_start_time: Optional[datetime] = None build_end_time: Optional[datetime] = None environment: Dict[str, str] = field(default_factory=dict) @@ -27,7 +28,7 @@ def __init__( self, spdx_id: str, build_type: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -35,12 +36,12 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, build_id: Optional[str] = None, config_source_entrypoint: List[str] | None = None, config_source_uri: List[str] | None = None, config_source_digest: List[Hash] | None = None, - parameters: Dict[str, str] | None = None, + parameter: Dict[str, str] | None = None, build_start_time: Optional[datetime] = None, build_end_time: Optional[datetime] = None, environment: Dict[str, str] | None = None, @@ -48,10 +49,11 @@ def __init__( verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension config_source_entrypoint = [] if config_source_entrypoint is None else config_source_entrypoint config_source_uri = [] if config_source_uri is None else config_source_uri config_source_digest = [] if config_source_digest is None else config_source_digest - parameters = {} if parameters is None else parameters + parameter = {} if parameter is None else parameter environment = {} if environment is None else environment check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/bundle.py b/src/spdx_tools/spdx3/model/bundle.py index 88079a19c..faa5e0a4a 100644 --- a/src/spdx_tools/spdx3/model/bundle.py +++ b/src/spdx_tools/spdx3/model/bundle.py @@ -9,11 +9,11 @@ CreationInfo, ElementCollection, ExternalIdentifier, - ExternalMap, ExternalReference, IntegrityMethod, - NamespaceMap, + ProfileIdentifierType, ) +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties @@ -25,7 +25,7 @@ def __init__( spdx_id: str, element: List[str], root_element: List[str], - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -33,14 +33,13 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, - namespaces: List[NamespaceMap] | None = None, - imports: List[ExternalMap] | None = None, + extension: List[Extension] | None = None, + profile_conformance: List[ProfileIdentifierType] | None = None, context: Optional[str] = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier - namespaces = [] if namespaces is None else namespaces - imports = [] if imports is None else imports + profile_conformance = [] if profile_conformance is None else profile_conformance + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/creation_info.py b/src/spdx_tools/spdx3/model/creation_info.py index cbb75d0bc..d819d4bff 100644 --- a/src/spdx_tools/spdx3/model/creation_info.py +++ b/src/spdx_tools/spdx3/model/creation_info.py @@ -9,7 +9,6 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model import ProfileIdentifierType @dataclass_with_properties @@ -17,8 +16,6 @@ class CreationInfo: spec_version: Version created: datetime created_by: List[str] # SPDXID of Agents - profile: List[ProfileIdentifierType] - data_license: Optional[str] = "CC0-1.0" created_using: List[str] = field(default_factory=list) # SPDXID of Tools comment: Optional[str] = None @@ -27,8 +24,6 @@ def __init__( spec_version: Version, created: datetime, created_by: List[str], - profile: List[ProfileIdentifierType], - data_license: Optional[str] = "CC0-1.0", created_using: List[str] | None = None, comment: Optional[str] = None, ): diff --git a/src/spdx_tools/spdx3/model/dataset/__init__.py b/src/spdx_tools/spdx3/model/dataset/__init__.py index 5e2b4e153..6c948a097 100644 --- a/src/spdx_tools/spdx3/model/dataset/__init__.py +++ b/src/spdx_tools/spdx3/model/dataset/__init__.py @@ -1,4 +1,9 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 -from spdx_tools.spdx3.model.dataset.dataset import Dataset, DatasetAvailabilityType, ConfidentialityLevelType +from spdx_tools.spdx3.model.dataset.dataset_package import ( + DatasetPackage, + DatasetAvailabilityType, + ConfidentialityLevelType, + DatasetType, +) diff --git a/src/spdx_tools/spdx3/model/dataset/dataset.py b/src/spdx_tools/spdx3/model/dataset/dataset_package.py similarity index 76% rename from src/spdx_tools/spdx3/model/dataset/dataset.py rename to src/spdx_tools/spdx3/model/dataset/dataset_package.py index c5fff28cd..cc2c3a36e 100644 --- a/src/spdx_tools/spdx3/model/dataset/dataset.py +++ b/src/spdx_tools/spdx3/model/dataset/dataset_package.py @@ -10,8 +10,11 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod -from spdx_tools.spdx3.model.licensing import LicenseField +from spdx_tools.spdx3.model.artifact import SupportType +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.presence_type import PresenceType from spdx_tools.spdx3.model.software import Package, SoftwarePurpose +from spdx_tools.spdx3.model.software.content_identifier import ContentIdentifier class DatasetType(Enum): @@ -47,8 +50,8 @@ class DatasetAvailabilityType(Enum): @dataclass_with_properties -class Dataset(Package): - dataset_type: List[DatasetType] = None +class DatasetPackage(Package): + dataset_type: List[DatasetType] = field(default_factory=list) data_collection_process: Optional[str] = None intended_use: Optional[str] = None dataset_size: Optional[int] = None @@ -56,7 +59,7 @@ class Dataset(Package): data_preprocessing: List[str] = field(default_factory=list) sensor: Dict[str, Optional[str]] = field(default_factory=dict) known_bias: List[str] = field(default_factory=list) - sensitive_personal_information: Optional[bool] = None + has_sensitive_personal_information: Optional[PresenceType] = None anonymization_method_used: List[str] = field(default_factory=list) confidentiality_level: Optional[ConfidentialityLevelType] = None dataset_update_mechanism: Optional[str] = None @@ -72,26 +75,24 @@ def __init__( built_time: datetime, release_time: datetime, dataset_type: List[DatasetType], - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, summary: Optional[str] = None, description: Optional[str] = None, comment: Optional[str] = None, verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, - supplied_by: List[str] | None = None, + extension: List[Extension] | None = None, + supplied_by: Optional[str] = None, valid_until_time: Optional[datetime] = None, standard: List[str] | None = None, - content_identifier: Optional[str] = None, + content_identifier: List[ContentIdentifier] | None = None, additional_purpose: List[SoftwarePurpose] | None = None, - concluded_license: Optional[LicenseField] = None, - declared_license: Optional[LicenseField] = None, copyright_text: Optional[str] = None, - attribution_text: Optional[str] = None, + attribution_text: List[str] | None = None, package_version: Optional[str] = None, package_url: Optional[str] = None, - homepage: Optional[str] = None, + home_page: Optional[str] = None, source_info: Optional[str] = None, data_collection_process: Optional[str] = None, intended_use: Optional[str] = None, @@ -100,21 +101,27 @@ def __init__( data_preprocessing: List[str] | None = None, sensor: Dict[str, Optional[str]] | None = None, known_bias: List[str] | None = None, - sensitive_personal_information: Optional[bool] = None, + has_sensitive_personal_information: Optional[PresenceType] = None, anonymization_method_used: List[str] | None = None, confidentiality_level: Optional[ConfidentialityLevelType] = None, dataset_update_mechanism: Optional[str] = None, dataset_availability: Optional[DatasetAvailabilityType] = None, + standard_name: List[str] | None = None, + support_level: List[SupportType] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension originated_by = [] if originated_by is None else originated_by additional_purpose = [] if additional_purpose is None else additional_purpose - supplied_by = [] if supplied_by is None else supplied_by + attribution_text = [] if attribution_text is None else attribution_text standard = [] if standard is None else standard + content_identifier = [] if content_identifier is None else content_identifier data_preprocessing = [] if data_preprocessing is None else data_preprocessing sensor = {} if sensor is None else sensor known_bias = [] if known_bias is None else known_bias anonymization_method_used = [] if anonymization_method_used is None else anonymization_method_used + standard_name = [] if standard_name is None else standard_name + support_level = [] if support_level is None else support_level check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/element.py b/src/spdx_tools/spdx3/model/element.py index 08f2d7b85..43783ef59 100644 --- a/src/spdx_tools/spdx3/model/element.py +++ b/src/spdx_tools/spdx3/model/element.py @@ -8,12 +8,13 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties class Element(ABC): spdx_id: str # IRI - creation_info: Optional[CreationInfo] = None + creation_info: CreationInfo name: Optional[str] = None summary: Optional[str] = None description: Optional[str] = None @@ -21,7 +22,7 @@ class Element(ABC): verified_using: List[IntegrityMethod] = field(default_factory=list) external_reference: List[ExternalReference] = field(default_factory=list) external_identifier: List[ExternalIdentifier] = field(default_factory=list) - extension: Optional[str] = None # placeholder for extension + extension: List[Extension] = field(default_factory=list) @abstractmethod def __init__(self): diff --git a/src/spdx_tools/spdx3/model/spdx_collection.py b/src/spdx_tools/spdx3/model/element_collection.py similarity index 77% rename from src/spdx_tools/spdx3/model/spdx_collection.py rename to src/spdx_tools/spdx3/model/element_collection.py index 65c28951a..addba70cd 100644 --- a/src/spdx_tools/spdx3/model/spdx_collection.py +++ b/src/spdx_tools/spdx3/model/element_collection.py @@ -7,7 +7,7 @@ from beartype.typing import List from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.spdx3.model import Element, ExternalMap, NamespaceMap +from spdx_tools.spdx3.model import Element, ProfileIdentifierType @dataclass_with_properties @@ -16,8 +16,7 @@ class ElementCollection(Element): # the __init__ method still raises an error if required fields are not set element: List[str] = field(default_factory=list) root_element: List[str] = field(default_factory=list) - namespaces: List[NamespaceMap] = field(default_factory=list) - imports: List[ExternalMap] = field(default_factory=list) + profile_conformance: List[ProfileIdentifierType] = field(default_factory=list) @abstractmethod def __init__(self): diff --git a/src/spdx_tools/spdx3/model/licensing/__init__.py b/src/spdx_tools/spdx3/model/expandedlicensing/__init__.py similarity index 77% rename from src/spdx_tools/spdx3/model/licensing/__init__.py rename to src/spdx_tools/spdx3/model/expandedlicensing/__init__.py index 8d9b9c6af..263fc1aa7 100644 --- a/src/spdx_tools/spdx3/model/licensing/__init__.py +++ b/src/spdx_tools/spdx3/model/expandedlicensing/__init__.py @@ -1,17 +1,13 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 -from .any_license_info import AnyLicenseInfo from .conjunctive_license_set import ConjunctiveLicenseSet from .custom_license import CustomLicense from .custom_license_addition import CustomLicenseAddition from .disjunctive_license_set import DisjunctiveLicenseSet from .license import License from .license_addition import LicenseAddition -from .license_field import LicenseField from .listed_license import ListedLicense from .listed_license_exception import ListedLicenseException -from .no_assertion_license import NoAssertionLicense -from .none_license import NoneLicense from .or_later_operator import OrLaterOperator from .with_addition_operator import WithAdditionOperator diff --git a/src/spdx_tools/spdx3/model/expandedlicensing/conjunctive_license_set.py b/src/spdx_tools/spdx3/model/expandedlicensing/conjunctive_license_set.py new file mode 100644 index 000000000..c18d12659 --- /dev/null +++ b/src/spdx_tools/spdx3/model/expandedlicensing/conjunctive_license_set.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from dataclasses import field +from typing import Optional + +from beartype.typing import List + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod +from spdx_tools.spdx3.model.simplelicensing.any_license_info import AnyLicenseInfo + + +@dataclass_with_properties +class ConjunctiveLicenseSet(AnyLicenseInfo): + member: List[str] = field(default_factory=list) + + def __init__( + self, + spdx_id: str, + member: List[str], + creation_info: CreationInfo, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + member = [] if member is None else member + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/expandedlicensing/custom_license.py b/src/spdx_tools/spdx3/model/expandedlicensing/custom_license.py new file mode 100644 index 000000000..e02837fa1 --- /dev/null +++ b/src/spdx_tools/spdx3/model/expandedlicensing/custom_license.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from beartype.typing import List, Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.expandedlicensing.license import License +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod + + +@dataclass_with_properties +class CustomLicense(License): + def __init__( + self, + spdx_id: str, + license_text: str, + creation_info: CreationInfo, + see_also: List[str] | None = None, + is_osi_approved: Optional[bool] = None, + is_fsf_libre: Optional[bool] = None, + standard_license_header: Optional[str] = None, + standard_license_template: Optional[str] = None, + is_deprecated_license_id: Optional[bool] = None, + obsoleted_by: Optional[str] = None, + license_xml: Optional[str] = None, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + see_also = [] if see_also is None else see_also + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/expandedlicensing/custom_license_addition.py b/src/spdx_tools/spdx3/model/expandedlicensing/custom_license_addition.py new file mode 100644 index 000000000..0440244f2 --- /dev/null +++ b/src/spdx_tools/spdx3/model/expandedlicensing/custom_license_addition.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from beartype.typing import List, Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.expandedlicensing.license_addition import LicenseAddition +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod + + +@dataclass_with_properties +class CustomLicenseAddition(LicenseAddition): + def __init__( + self, + spdx_id: str, + addition_text: str, + creation_info: CreationInfo, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + is_deprecated_addition_id: Optional[bool] = None, + license_xml: Optional[str] = None, + obsoleted_by: Optional[str] = None, + see_also: List[str] | None = None, + standard_addition_template: Optional[str] = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + see_also = [] if see_also is None else see_also + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/expandedlicensing/disjunctive_license_set.py b/src/spdx_tools/spdx3/model/expandedlicensing/disjunctive_license_set.py new file mode 100644 index 000000000..cff634a97 --- /dev/null +++ b/src/spdx_tools/spdx3/model/expandedlicensing/disjunctive_license_set.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from dataclasses import field +from typing import Optional + +from beartype.typing import List + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod +from spdx_tools.spdx3.model.simplelicensing.any_license_info import AnyLicenseInfo + + +@dataclass_with_properties +class DisjunctiveLicenseSet(AnyLicenseInfo): + member: List[str] = field(default_factory=list) + + def __init__( + self, + spdx_id: str, + member: List[str], + creation_info: CreationInfo, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + member = [] if member is None else member + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/expandedlicensing/extendable_license.py b/src/spdx_tools/spdx3/model/expandedlicensing/extendable_license.py new file mode 100644 index 000000000..ec68292bb --- /dev/null +++ b/src/spdx_tools/spdx3/model/expandedlicensing/extendable_license.py @@ -0,0 +1,11 @@ +from abc import abstractmethod + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.spdx3.model.simplelicensing.any_license_info import AnyLicenseInfo + + +@dataclass_with_properties +class ExtendableLicense(AnyLicenseInfo): + @abstractmethod + def __init__(self): + pass diff --git a/src/spdx_tools/spdx3/model/licensing/license.py b/src/spdx_tools/spdx3/model/expandedlicensing/license.py similarity index 76% rename from src/spdx_tools/spdx3/model/licensing/license.py rename to src/spdx_tools/spdx3/model/expandedlicensing/license.py index e2fd625ff..bbf7a75fd 100644 --- a/src/spdx_tools/spdx3/model/licensing/license.py +++ b/src/spdx_tools/spdx3/model/expandedlicensing/license.py @@ -7,15 +7,12 @@ from beartype.typing import List, Optional from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.spdx3.model.licensing.any_license_info import AnyLicenseInfo +from spdx_tools.spdx3.model.expandedlicensing.extendable_license import ExtendableLicense @dataclass_with_properties -class License(AnyLicenseInfo): - license_id: str - license_name: str - license_text: str - license_comment: Optional[str] = None +class License(ExtendableLicense): + license_text: str = None see_also: List[str] = field(default_factory=list) is_osi_approved: Optional[bool] = None is_fsf_libre: Optional[bool] = None @@ -23,6 +20,7 @@ class License(AnyLicenseInfo): standard_license_template: Optional[str] = None is_deprecated_license_id: Optional[bool] = None obsoleted_by: Optional[str] = None + license_xml: Optional[str] = None @abstractmethod def __init__(self): diff --git a/src/spdx_tools/spdx3/model/licensing/license_addition.py b/src/spdx_tools/spdx3/model/expandedlicensing/license_addition.py similarity index 75% rename from src/spdx_tools/spdx3/model/licensing/license_addition.py rename to src/spdx_tools/spdx3/model/expandedlicensing/license_addition.py index e3669b5cb..672934d36 100644 --- a/src/spdx_tools/spdx3/model/licensing/license_addition.py +++ b/src/spdx_tools/spdx3/model/expandedlicensing/license_addition.py @@ -1,24 +1,23 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 -from abc import ABC, abstractmethod +from abc import abstractmethod from dataclasses import field from beartype.typing import List, Optional from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.spdx3.model import Element @dataclass_with_properties -class LicenseAddition(ABC): - addition_id: str - addition_name: str - addition_text: str - addition_comment: Optional[str] = None - see_also: List[str] = field(default_factory=list) - standard_addition_template: Optional[str] = None +class LicenseAddition(Element): + addition_text: str = None is_deprecated_addition_id: Optional[bool] = None + license_xml: Optional[str] = None obsoleted_by: Optional[str] = None + see_also: List[str] = field(default_factory=list) + standard_addition_template: Optional[str] = None @abstractmethod def __init__(self): diff --git a/src/spdx_tools/spdx3/model/expandedlicensing/listed_license.py b/src/spdx_tools/spdx3/model/expandedlicensing/listed_license.py new file mode 100644 index 000000000..666beb061 --- /dev/null +++ b/src/spdx_tools/spdx3/model/expandedlicensing/listed_license.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from beartype.typing import List, Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.expandedlicensing.license import License +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod + + +@dataclass_with_properties +class ListedLicense(License): + list_version_added: Optional[str] = None + deprecated_version: Optional[str] = None + + def __init__( + self, + spdx_id: str, + license_text: str, + creation_info: CreationInfo, + list_version_added: Optional[str] = None, + deprecated_version: Optional[str] = None, + see_also: List[str] | None = None, + is_osi_approved: Optional[bool] = None, + is_fsf_libre: Optional[bool] = None, + standard_license_header: Optional[str] = None, + standard_license_template: Optional[str] = None, + is_deprecated_license_id: Optional[bool] = None, + obsoleted_by: Optional[str] = None, + license_xml: Optional[str] = None, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + see_also = [] if see_also is None else see_also + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/expandedlicensing/listed_license_exception.py b/src/spdx_tools/spdx3/model/expandedlicensing/listed_license_exception.py new file mode 100644 index 000000000..ee54ceeb8 --- /dev/null +++ b/src/spdx_tools/spdx3/model/expandedlicensing/listed_license_exception.py @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from beartype.typing import List, Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.expandedlicensing.license_addition import LicenseAddition +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod + + +@dataclass_with_properties +class ListedLicenseException(LicenseAddition): + list_version_added: Optional[str] = None + deprecated_version: Optional[str] = None + + def __init__( + self, + spdx_id: str, + addition_text: str, + creation_info: CreationInfo, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + is_deprecated_addition_id: Optional[bool] = None, + license_xml: Optional[str] = None, + obsoleted_by: Optional[str] = None, + see_also: List[str] | None = None, + standard_addition_template: Optional[str] = None, + list_version_added: Optional[str] = None, + deprecated_version: Optional[str] = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + see_also = [] if see_also is None else see_also + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/expandedlicensing/or_later_operator.py b/src/spdx_tools/spdx3/model/expandedlicensing/or_later_operator.py new file mode 100644 index 000000000..8f2977cd8 --- /dev/null +++ b/src/spdx_tools/spdx3/model/expandedlicensing/or_later_operator.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from typing import List, Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model import CreationInfo +from spdx_tools.spdx3.model.expandedlicensing.extendable_license import ExtendableLicense +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod + + +@dataclass_with_properties +class OrLaterOperator(ExtendableLicense): + subject_license: str = None + + def __init__( + self, + spdx_id: str, + subject_license: str, + creation_info: CreationInfo, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/expandedlicensing/with_addition_operator.py b/src/spdx_tools/spdx3/model/expandedlicensing/with_addition_operator.py new file mode 100644 index 000000000..053bd24d2 --- /dev/null +++ b/src/spdx_tools/spdx3/model/expandedlicensing/with_addition_operator.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from typing import List, Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod +from spdx_tools.spdx3.model.simplelicensing.any_license_info import AnyLicenseInfo + + +@dataclass_with_properties +class WithAdditionOperator(AnyLicenseInfo): + subject_extendable_license: str = None + subject_addition: str = None + + def __init__( + self, + spdx_id: str, + subject_extendable_license: str, + subject_addition: str, + creation_info: CreationInfo, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/extension/__init__.py b/src/spdx_tools/spdx3/model/extension/__init__.py new file mode 100644 index 000000000..745eaa833 --- /dev/null +++ b/src/spdx_tools/spdx3/model/extension/__init__.py @@ -0,0 +1,3 @@ +from .cdx_properties_extension import CdxPropertiesExtension +from .cdx_property_entry import CdxPropertyEntry +from .extension import Extension diff --git a/src/spdx_tools/spdx3/model/extension/cdx_properties_extension.py b/src/spdx_tools/spdx3/model/extension/cdx_properties_extension.py new file mode 100644 index 000000000..958974349 --- /dev/null +++ b/src/spdx_tools/spdx3/model/extension/cdx_properties_extension.py @@ -0,0 +1,16 @@ +from dataclasses import field +from typing import List + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.extension.cdx_property_entry import CdxPropertyEntry +from spdx_tools.spdx3.model.extension.extension import Extension + + +@dataclass_with_properties +class CdxPropertiesExtension(Extension): + cdx_property: List[CdxPropertyEntry] = field(default_factory=list) + + def __init__(self, cdx_property: List[CdxPropertyEntry] | None = None): + cdx_property = [] if cdx_property is None else cdx_property + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/extension/cdx_property_entry.py b/src/spdx_tools/spdx3/model/extension/cdx_property_entry.py new file mode 100644 index 000000000..9e82bba40 --- /dev/null +++ b/src/spdx_tools/spdx3/model/extension/cdx_property_entry.py @@ -0,0 +1,13 @@ +from typing import Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values + + +@dataclass_with_properties +class CdxPropertyEntry: + cdx_prop_name: str = None + cdx_prop_value: Optional[str] = None + + def __init__(self, cdx_prop_name: str, cdx_prop_value: Optional[str] = None): + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/extension/extension.py b/src/spdx_tools/spdx3/model/extension/extension.py new file mode 100644 index 000000000..250759a34 --- /dev/null +++ b/src/spdx_tools/spdx3/model/extension/extension.py @@ -0,0 +1,11 @@ +from abc import abstractmethod + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties + + +@dataclass_with_properties +class Extension: + + @abstractmethod + def __init__(self): + pass diff --git a/src/spdx_tools/spdx3/model/external_identifier.py b/src/spdx_tools/spdx3/model/external_identifier.py index 772c1df0a..b38ac58dd 100644 --- a/src/spdx_tools/spdx3/model/external_identifier.py +++ b/src/spdx_tools/spdx3/model/external_identifier.py @@ -16,7 +16,7 @@ class ExternalIdentifierType(Enum): CVE = auto() EMAIL = auto() GITOID = auto() - PURL = auto() + PACKAGE_URL = auto() SECURITY_OTHER = auto() SWHID = auto() SWID = auto() diff --git a/src/spdx_tools/spdx3/model/external_map.py b/src/spdx_tools/spdx3/model/external_map.py index 01fcd7ebc..389889121 100644 --- a/src/spdx_tools/spdx3/model/external_map.py +++ b/src/spdx_tools/spdx3/model/external_map.py @@ -12,17 +12,17 @@ @dataclass_with_properties class ExternalMap: - external_id: str # anyURI + external_spdx_id: str # anyURI verified_using: List[IntegrityMethod] = field(default_factory=list) location_hint: Optional[str] = None # anyURI - defining_document: Optional[str] = None + defining_artifact: Optional[str] = None def __init__( self, - external_id: str, + external_spdx_id: str, verified_using: List[IntegrityMethod] | None = None, location_hint: Optional[str] = None, - defining_document: Optional[str] = None, + defining_artifact: Optional[str] = None, ): verified_using = [] if verified_using is None else verified_using check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/external_reference.py b/src/spdx_tools/spdx3/model/external_reference.py index 64e5bdb97..f4ea814eb 100644 --- a/src/spdx_tools/spdx3/model/external_reference.py +++ b/src/spdx_tools/spdx3/model/external_reference.py @@ -14,20 +14,27 @@ class ExternalReferenceType(Enum): ALT_DOWNLOAD_LOCATION = auto() ALT_WEB_PAGE = auto() BINARY_ARTIFACT = auto() + BOWER = auto() BUILD_META = auto() BUILD_SYSTEM = auto() CERTIFICATION_REPORT = auto() CHAT = auto() COMPONENT_ANALYSIS_REPORT = auto() + CWE = auto() DOCUMENTATION = auto() DYNAMIC_ANALYSIS_REPORT = auto() EOL_NOTICE = auto() + EXPORT_CONTROL_ASSESSMENT = auto() FUNDING = auto() ISSUE_TRACKER = auto() LICENSE = auto() MAILING_LIST = auto() + MAVEN_CENTRAL = auto() METRICS = auto() + NPM = auto() + NUGET = auto() OTHER = auto() + PRIVACY_ASSESSMENT = auto() PRODUCT_METADATA = auto() QUALITY_ASSESSMENT_REPORT = auto() RELEASE_HISTORY = auto() diff --git a/src/spdx_tools/spdx3/model/hash.py b/src/spdx_tools/spdx3/model/hash.py index 42ef4ff4d..f035fba80 100644 --- a/src/spdx_tools/spdx3/model/hash.py +++ b/src/spdx_tools/spdx3/model/hash.py @@ -11,12 +11,13 @@ class HashAlgorithm(Enum): + ADLER32 = auto() BLAKE2B256 = auto() BLAKE2B384 = auto() BLAKE2B512 = auto() BLAKE3 = auto() - CRYSTALS_KYBER = auto() CRYSTALS_DILITHIUM = auto() + CRYSTALS_KYBER = auto() FALCON = auto() MD2 = auto() MD4 = auto() @@ -26,15 +27,12 @@ class HashAlgorithm(Enum): SHA1 = auto() SHA224 = auto() SHA256 = auto() + SHA384 = auto() SHA3_224 = auto() SHA3_256 = auto() SHA3_384 = auto() SHA3_512 = auto() - SHA384 = auto() SHA512 = auto() - SPDXPVCSHA1 = auto() - SPDXPVCSHA256 = auto() - SPHINCS_PLUS = auto() @dataclass_with_properties diff --git a/src/spdx_tools/spdx3/model/individual_element.py b/src/spdx_tools/spdx3/model/individual_element.py new file mode 100644 index 000000000..273065182 --- /dev/null +++ b/src/spdx_tools/spdx3/model/individual_element.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from beartype.typing import List, Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.element import Element +from spdx_tools.spdx3.model.extension.extension import Extension + + +@dataclass_with_properties +class IndividualElement(Element): + def __init__( + self, + spdx_id: str, + creation_info: CreationInfo, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/individual_elements_ids.py b/src/spdx_tools/spdx3/model/individual_elements_ids.py new file mode 100644 index 000000000..9f1dcc252 --- /dev/null +++ b/src/spdx_tools/spdx3/model/individual_elements_ids.py @@ -0,0 +1,14 @@ +""" +Rather than representing these individual elements as full classes in the model, +we are using constants to represent them. +This is because they do not have any properties beyond their identifier, so a full class is unneeded. +""" + +# Core profile individual elements +NO_ASSERTION_ELEMENT_ID: str = "https://spdx.org/rdf/3.0.1/terms/Core/NoAssertionElement" +NONE_ELEMENT_ID: str = "https://spdx.org/rdf/3.0.1/terms/Core/NoneElement" +SPDX_ORGANIZATION_ID: str = "https://spdx.org/rdf/3.0.1/terms/Core/SpdxOrganization" + +# Expanded Licensing profile individual elements +NO_ASSERTION_LICENSE_ID: str = "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/NoAssertionLicense" +NONE_LICENSE_ID: str = "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/NoneLicense" diff --git a/src/spdx_tools/spdx3/model/licensing/any_license_info.py b/src/spdx_tools/spdx3/model/licensing/any_license_info.py deleted file mode 100644 index 1f1402427..000000000 --- a/src/spdx_tools/spdx3/model/licensing/any_license_info.py +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from abc import abstractmethod - -from spdx_tools.spdx3.model.licensing.license_field import LicenseField - - -class AnyLicenseInfo(LicenseField): - @abstractmethod - def __init__(self): - pass diff --git a/src/spdx_tools/spdx3/model/licensing/conjunctive_license_set.py b/src/spdx_tools/spdx3/model/licensing/conjunctive_license_set.py deleted file mode 100644 index fe5605761..000000000 --- a/src/spdx_tools/spdx3/model/licensing/conjunctive_license_set.py +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from beartype.typing import List - -from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model.licensing.any_license_info import AnyLicenseInfo - - -@dataclass_with_properties -class ConjunctiveLicenseSet(AnyLicenseInfo): - member: List[AnyLicenseInfo] - - def __init__(self, member: List[AnyLicenseInfo]): - check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/licensing/custom_license.py b/src/spdx_tools/spdx3/model/licensing/custom_license.py deleted file mode 100644 index 608f7e69a..000000000 --- a/src/spdx_tools/spdx3/model/licensing/custom_license.py +++ /dev/null @@ -1,28 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from beartype.typing import List, Optional - -from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model.licensing.license import License - - -@dataclass_with_properties -class CustomLicense(License): - def __init__( - self, - license_id: str, - license_name: str, - license_text: str, - license_comment: Optional[str] = None, - see_also: List[str] | None = None, - is_osi_approved: Optional[bool] = None, - is_fsf_libre: Optional[bool] = None, - standard_license_header: Optional[str] = None, - standard_license_template: Optional[str] = None, - is_deprecated_license_id: Optional[bool] = None, - obsoleted_by: Optional[str] = None, - ): - see_also = [] if see_also is None else see_also - check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/licensing/custom_license_addition.py b/src/spdx_tools/spdx3/model/licensing/custom_license_addition.py deleted file mode 100644 index 5d4ee8929..000000000 --- a/src/spdx_tools/spdx3/model/licensing/custom_license_addition.py +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from beartype.typing import List, Optional - -from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model.licensing.license_addition import LicenseAddition - - -@dataclass_with_properties -class CustomLicenseAddition(LicenseAddition): - def __init__( - self, - addition_id: str, - addition_name: str, - addition_text: str, - addition_comment: Optional[str] = None, - see_also: List[str] | None = None, - standard_addition_template: Optional[str] = None, - is_deprecated_addition_id: Optional[bool] = None, - obsoleted_by: Optional[str] = None, - ): - see_also = [] if see_also is None else see_also - check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/licensing/disjunctive_license_set.py b/src/spdx_tools/spdx3/model/licensing/disjunctive_license_set.py deleted file mode 100644 index a5ac3bdc8..000000000 --- a/src/spdx_tools/spdx3/model/licensing/disjunctive_license_set.py +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from beartype.typing import List - -from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model.licensing.any_license_info import AnyLicenseInfo - - -@dataclass_with_properties -class DisjunctiveLicenseSet(AnyLicenseInfo): - member: List[AnyLicenseInfo] - - def __init__(self, member: List[AnyLicenseInfo]): - check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/licensing/license_field.py b/src/spdx_tools/spdx3/model/licensing/license_field.py deleted file mode 100644 index babe141f2..000000000 --- a/src/spdx_tools/spdx3/model/licensing/license_field.py +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from abc import ABC, abstractmethod - - -class LicenseField(ABC): - @abstractmethod - def __init__(self): - pass diff --git a/src/spdx_tools/spdx3/model/licensing/listed_license.py b/src/spdx_tools/spdx3/model/licensing/listed_license.py deleted file mode 100644 index 104396dbc..000000000 --- a/src/spdx_tools/spdx3/model/licensing/listed_license.py +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from beartype.typing import List, Optional - -from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model.licensing.license import License - - -@dataclass_with_properties -class ListedLicense(License): - list_version_added: Optional[str] = None - deprecated_version: Optional[str] = None - - def __init__( - self, - license_id: str, - license_name: str, - license_text: str, - license_comment: Optional[str] = None, - see_also: List[str] | None = None, - is_osi_approved: Optional[bool] = None, - is_fsf_libre: Optional[bool] = None, - standard_license_header: Optional[str] = None, - standard_license_template: Optional[str] = None, - is_deprecated_license_id: Optional[bool] = None, - obsoleted_by: Optional[str] = None, - list_version_added: Optional[str] = None, - deprecated_version: Optional[str] = None, - ): - see_also = [] if see_also is None else see_also - check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/licensing/listed_license_exception.py b/src/spdx_tools/spdx3/model/licensing/listed_license_exception.py deleted file mode 100644 index 2264f474f..000000000 --- a/src/spdx_tools/spdx3/model/licensing/listed_license_exception.py +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from beartype.typing import List, Optional - -from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model.licensing.license_addition import LicenseAddition - - -@dataclass_with_properties -class ListedLicenseException(LicenseAddition): - list_version_added: Optional[str] = None - deprecated_version: Optional[str] = None - - def __init__( - self, - addition_id: str, - addition_name: str, - addition_text: str, - addition_comment: Optional[str] = None, - see_also: List[str] | None = None, - standard_addition_template: Optional[str] = None, - is_deprecated_addition_id: Optional[bool] = None, - obsoleted_by: Optional[str] = None, - list_version_added: Optional[str] = None, - deprecated_version: Optional[str] = None, - ): - see_also = [] if see_also is None else see_also - check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/licensing/no_assertion_license.py b/src/spdx_tools/spdx3/model/licensing/no_assertion_license.py deleted file mode 100644 index 66a00b261..000000000 --- a/src/spdx_tools/spdx3/model/licensing/no_assertion_license.py +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from spdx_tools.spdx3.model.licensing.license_field import LicenseField - - -class NoAssertionLicense(LicenseField): - def __init__(self): - pass diff --git a/src/spdx_tools/spdx3/model/licensing/none_license.py b/src/spdx_tools/spdx3/model/licensing/none_license.py deleted file mode 100644 index e34253608..000000000 --- a/src/spdx_tools/spdx3/model/licensing/none_license.py +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from spdx_tools.spdx3.model.licensing.license_field import LicenseField - - -class NoneLicense(LicenseField): - def __init__(self): - pass diff --git a/src/spdx_tools/spdx3/model/licensing/or_later_operator.py b/src/spdx_tools/spdx3/model/licensing/or_later_operator.py deleted file mode 100644 index 2aa204b98..000000000 --- a/src/spdx_tools/spdx3/model/licensing/or_later_operator.py +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model.licensing.any_license_info import AnyLicenseInfo -from spdx_tools.spdx3.model.licensing.license import License - - -@dataclass_with_properties -class OrLaterOperator(AnyLicenseInfo): - subject_license: License - - def __init__(self, subject_license: License): - check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/licensing/with_addition_operator.py b/src/spdx_tools/spdx3/model/licensing/with_addition_operator.py deleted file mode 100644 index 9e79f8d98..000000000 --- a/src/spdx_tools/spdx3/model/licensing/with_addition_operator.py +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties -from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model.licensing.any_license_info import AnyLicenseInfo -from spdx_tools.spdx3.model.licensing.license import License -from spdx_tools.spdx3.model.licensing.license_addition import LicenseAddition - - -@dataclass_with_properties -class WithAdditionOperator(AnyLicenseInfo): - subject_license: License - subject_addition: LicenseAddition - - def __init__(self, subject_license: License, subject_addition: LicenseAddition): - check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/lifecycle_scoped_relationship.py b/src/spdx_tools/spdx3/model/lifecycle_scoped_relationship.py index 660e1217e..e6ae7a6c2 100644 --- a/src/spdx_tools/spdx3/model/lifecycle_scoped_relationship.py +++ b/src/spdx_tools/spdx3/model/lifecycle_scoped_relationship.py @@ -17,6 +17,7 @@ RelationshipCompleteness, RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension class LifecycleScopeType(Enum): @@ -37,8 +38,8 @@ def __init__( spdx_id: str, from_element: str, relationship_type: RelationshipType, + creation_info: CreationInfo, to: List[str] | None = None, - creation_info: Optional[CreationInfo] = None, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -46,7 +47,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -56,4 +57,5 @@ def __init__( verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/organization.py b/src/spdx_tools/spdx3/model/organization.py index fdc71e594..34b965afc 100644 --- a/src/spdx_tools/spdx3/model/organization.py +++ b/src/spdx_tools/spdx3/model/organization.py @@ -6,6 +6,7 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import Agent, CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties @@ -13,7 +14,7 @@ class Organization(Agent): def __init__( self, spdx_id: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -21,9 +22,10 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/package_verification_code.py b/src/spdx_tools/spdx3/model/package_verification_code.py new file mode 100644 index 000000000..0c906165b --- /dev/null +++ b/src/spdx_tools/spdx3/model/package_verification_code.py @@ -0,0 +1,26 @@ +from dataclasses import field +from typing import List + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.hash import HashAlgorithm +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod + + +@dataclass_with_properties +class PackageVerificationCode(IntegrityMethod): + algorithm: HashAlgorithm = None + hash_value: str = None + package_verification_code_excluded_file: List[str] = field(default_factory=list) + + def __init__( + self, + algorithm: HashAlgorithm, + hash_value: str, + package_verification_code_excluded_file: List[str] | None = None, + comment: str | None = None, + ): + package_verification_code_excluded_file = ( + [] if package_verification_code_excluded_file is None else package_verification_code_excluded_file + ) + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/person.py b/src/spdx_tools/spdx3/model/person.py index dc9bd6c42..db96fc4ba 100644 --- a/src/spdx_tools/spdx3/model/person.py +++ b/src/spdx_tools/spdx3/model/person.py @@ -6,6 +6,7 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import Agent, CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties @@ -13,7 +14,7 @@ class Person(Agent): def __init__( self, spdx_id: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -21,9 +22,10 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/positive_integer_range.py b/src/spdx_tools/spdx3/model/positive_integer_range.py index cbe1c6322..328696930 100644 --- a/src/spdx_tools/spdx3/model/positive_integer_range.py +++ b/src/spdx_tools/spdx3/model/positive_integer_range.py @@ -7,12 +7,12 @@ @dataclass_with_properties class PositiveIntegerRange: - begin: int - end: int + begin_integer_range: int + end_integer_range: int def __init__( self, - begin: int, - end: int, + begin_integer_range: int, + end_integer_range: int, ): check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/presence_type.py b/src/spdx_tools/spdx3/model/presence_type.py new file mode 100644 index 000000000..ed47794d9 --- /dev/null +++ b/src/spdx_tools/spdx3/model/presence_type.py @@ -0,0 +1,7 @@ +from enum import Enum, auto + + +class PresenceType(Enum): + NO = auto() + NO_ASSERTION = auto() + YES = auto() diff --git a/src/spdx_tools/spdx3/model/profile_identifier.py b/src/spdx_tools/spdx3/model/profile_identifier_type.py similarity index 78% rename from src/spdx_tools/spdx3/model/profile_identifier.py rename to src/spdx_tools/spdx3/model/profile_identifier_type.py index 40fe7ac41..341500636 100644 --- a/src/spdx_tools/spdx3/model/profile_identifier.py +++ b/src/spdx_tools/spdx3/model/profile_identifier_type.py @@ -7,10 +7,11 @@ class ProfileIdentifierType(Enum): CORE = auto() SOFTWARE = auto() - LICENSING = auto() + EXPANDED_LICENSING = auto() SECURITY = auto() BUILD = auto() AI = auto() DATASET = auto() - USAGE = auto() EXTENSION = auto() + LITE = auto() + SIMPLE_LICENSING = auto() diff --git a/src/spdx_tools/spdx3/model/relationship.py b/src/spdx_tools/spdx3/model/relationship.py index 28ba527ce..d1d9e9e73 100644 --- a/src/spdx_tools/spdx3/model/relationship.py +++ b/src/spdx_tools/spdx3/model/relationship.py @@ -10,77 +10,75 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, Element, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.extension.extension import Extension class RelationshipType(Enum): AFFECTS = auto() - AMENDS = auto() - ANCESTOR = auto() + AMENDED_BY = auto() + ANCESTOR_OF = auto() AVAILABLE_FROM = auto() - BUILD_DEPENDENCY = auto() - BUILD_TOOL = auto() - COORDINATED_BY = auto() + CONFIGURES = auto() CONTAINS = auto() - CONFIG_OF = auto() - COPY = auto() - DATA_FILE = auto() - DEPENDENCY_MANIFEST = auto() + COORDINATED_BY = auto() + COPIED_TO = auto() + DELEGATED_TO = auto() DEPENDS_ON = auto() - DESCENDANT = auto() + DESCENDANT_OF = auto() DESCRIBES = auto() - DEV_DEPENDENCY = auto() - DEV_TOOL = auto() - DISTRIBUTION_ARTIFACT = auto() - DOCUMENTATION = auto() DOES_NOT_AFFECT = auto() - DYNAMIC_LINK = auto() - EXAMPLE = auto() - EVIDENCE_FOR = auto() - EXPANDED_FROM_ARCHIVE = auto() + EXPANDS_TO = auto() EXPLOIT_CREATED_BY = auto() - FILE_ADDED = auto() - FILE_DELETED = auto() - FILE_MODIFIED = auto() FIXED_BY = auto() FIXED_IN = auto() FOUND_BY = auto() GENERATES = auto() + HAS_ADDED_FILE = auto() HAS_ASSESSMENT_FOR = auto() HAS_ASSOCIATED_VULNERABILITY = auto() - HOST_OF = auto() - INPUT_OF = auto() + HAS_CONCLUDED_LICENSE = auto() + HAS_DATA_FILE = auto() + HAS_DECLARED_LICENSE = auto() + HAS_DELETED_FILE = auto() + HAS_DEPENDENCY_MANIFEST = auto() + HAS_DISTRIBUTION_ARTIFACT = auto() + HAS_DOCUMENTATION = auto() + HAS_DYNAMIC_LINK = auto() + HAS_EVIDENCE = auto() + HAS_EXAMPLE = auto() + HAS_HOST = auto() + HAS_INPUT = auto() + HAS_METADATA = auto() + HAS_OPTIONAL_COMPONENT = auto() + HAS_OPTIONAL_DEPENDENCY = auto() + HAS_OUTPUT = auto() + HAS_PREREQUISITE = auto() + HAS_PROVIDED_DEPENDENCY = auto() + HAS_REQUIREMENT = auto() + HAS_SPECIFICATION = auto() + HAS_STATIC_LINK = auto() + HAS_TEST = auto() + HAS_TEST_CASE = auto() + HAS_VARIANT = auto() INVOKED_BY = auto() - METAFILE = auto() - ON_BEHALF_OF = auto() - OPTIONAL_COMPONENT = auto() - OPTIONAL_DEPENDENCY = auto() + MODIFIED_BY = auto() OTHER = auto() - OUTPUT_OF = auto() - PACKAGES = auto() - PATCH = auto() - PREREQUISITE = auto() - PROVIDED_DEPENDENCY = auto() + PACKAGED_BY = auto() + PATCHED_BY = auto() PUBLISHED_BY = auto() REPORTED_BY = auto() REPUBLISHED_BY = auto() - REQUIREMENT_FOR = auto() - RUNTIME_DEPENDENCY = auto() - SPECIFICATION_FOR = auto() - STATIC_LINK = auto() - TEST = auto() - TEST_CASE = auto() - TEST_DEPENDENCY = auto() - TEST_TOOL = auto() + SERIALIZED_IN_ARTIFACT = auto() TESTED_ON = auto() TRAINED_ON = auto() UNDER_INVESTIGATION_FOR = auto() - VARIANT = auto() + USES_TOOL = auto() class RelationshipCompleteness(Enum): INCOMPLETE = auto() COMPLETE = auto() - NOASSERTION = auto() + NO_ASSERTION = auto() @dataclass_with_properties @@ -99,8 +97,8 @@ def __init__( spdx_id: str, from_element: str, relationship_type: RelationshipType, + creation_info: CreationInfo, to: List[str] | None = None, - creation_info: Optional[CreationInfo] = None, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -108,7 +106,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -117,4 +115,5 @@ def __init__( verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/__init__.py b/src/spdx_tools/spdx3/model/security/__init__.py index 3407981c8..08f062ba9 100644 --- a/src/spdx_tools/spdx3/model/security/__init__.py +++ b/src/spdx_tools/spdx3/model/security/__init__.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from .cvss_v2_vuln_assessment_relationship import CvssV2VulnAssessmentRelationship from .cvss_v3_vuln_assessment_relationship import CvssV3VulnAssessmentRelationship +from .cvss_v4_vuln_assessment_relationship import CvssV4VulnAssessmentRelationship from .epss_vuln_assessment_relationship import EpssVulnAssessmentRelationship from .exploit_catalog_vuln_assessment_relationship import ExploitCatalogVulnAssessmentRelationship, ExploitCatalogType from .ssvc_vuln_assessment_relationship import SsvcVulnAssessmentRelationship, SsvcDecisionType @@ -16,3 +17,4 @@ from .vex_vuln_assessment_relationship import VexVulnAssessmentRelationship from .vuln_assessment_relationship import VulnAssessmentRelationship from .vulnerability import Vulnerability +from .cvss_severity_type import CvssSeverityType diff --git a/src/spdx_tools/spdx3/model/security/cvss_severity_type.py b/src/spdx_tools/spdx3/model/security/cvss_severity_type.py new file mode 100644 index 000000000..02ad50f75 --- /dev/null +++ b/src/spdx_tools/spdx3/model/security/cvss_severity_type.py @@ -0,0 +1,9 @@ +from enum import Enum, auto + + +class CvssSeverityType(Enum): + CRITICAL = auto() + HIGH = auto() + MEDIUM = auto() + LOW = auto() + NONE = auto() diff --git a/src/spdx_tools/spdx3/model/security/cvss_v2_vuln_assessment_relationship.py b/src/spdx_tools/spdx3/model/security/cvss_v2_vuln_assessment_relationship.py index 13366cb63..5e08cc158 100644 --- a/src/spdx_tools/spdx3/model/security/cvss_v2_vuln_assessment_relationship.py +++ b/src/spdx_tools/spdx3/model/security/cvss_v2_vuln_assessment_relationship.py @@ -13,16 +13,16 @@ ExternalReference, IntegrityMethod, RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.security.vuln_assessment_relationship import VulnAssessmentRelationship -from spdx_tools.spdx.model import RelationshipType @dataclass_with_properties class CvssV2VulnAssessmentRelationship(VulnAssessmentRelationship): - score: str = None - severity: Optional[str] = None - vector: Optional[str] = None + score: float | int = None + vector_string: str = None def __init__( self, @@ -30,8 +30,9 @@ def __init__( from_element: str, relationship_type: RelationshipType, to: List[str], - score: str, - creation_info: Optional[CreationInfo] = None, + score: float | int, + vector_string: str, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -39,7 +40,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -48,10 +49,9 @@ def __init__( supplied_by: Optional[str] = None, modified_time: Optional[datetime] = None, withdrawn_time: Optional[datetime] = None, - severity: Optional[str] = None, - vector: Optional[str] = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/cvss_v3_vuln_assessment_relationship.py b/src/spdx_tools/spdx3/model/security/cvss_v3_vuln_assessment_relationship.py index 62a50805e..15cbe3195 100644 --- a/src/spdx_tools/spdx3/model/security/cvss_v3_vuln_assessment_relationship.py +++ b/src/spdx_tools/spdx3/model/security/cvss_v3_vuln_assessment_relationship.py @@ -13,16 +13,18 @@ ExternalReference, IntegrityMethod, RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.security.cvss_severity_type import CvssSeverityType from spdx_tools.spdx3.model.security.vuln_assessment_relationship import VulnAssessmentRelationship -from spdx_tools.spdx.model import RelationshipType @dataclass_with_properties class CvssV3VulnAssessmentRelationship(VulnAssessmentRelationship): - score: str = None - severity: Optional[str] = None - vector: Optional[str] = None + score: float | int = None + severity: CvssSeverityType = None + vector_string: str = None def __init__( self, @@ -30,8 +32,10 @@ def __init__( from_element: str, to: List[str], relationship_type: RelationshipType, - score: str, - creation_info: Optional[CreationInfo] = None, + score: float | int, + severity: CvssSeverityType, + vector_string: str, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -39,7 +43,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -48,10 +52,9 @@ def __init__( supplied_by: Optional[str] = None, modified_time: Optional[datetime] = None, withdrawn_time: Optional[datetime] = None, - severity: Optional[str] = None, - vector: Optional[str] = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/software/software_dependency_relationship.py b/src/spdx_tools/spdx3/model/security/cvss_v4_vuln_assessment_relationship.py similarity index 61% rename from src/spdx_tools/spdx3/model/software/software_dependency_relationship.py rename to src/spdx_tools/spdx3/model/security/cvss_v4_vuln_assessment_relationship.py index 37c48a610..0dfddb4ee 100644 --- a/src/spdx_tools/spdx3/model/software/software_dependency_relationship.py +++ b/src/spdx_tools/spdx3/model/security/cvss_v4_vuln_assessment_relationship.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: Apache-2.0 from datetime import datetime -from enum import Enum, auto from beartype.typing import List, Optional @@ -13,40 +12,30 @@ ExternalIdentifier, ExternalReference, IntegrityMethod, - LifecycleScopedRelationship, - LifecycleScopeType, RelationshipCompleteness, RelationshipType, ) - - -class SoftwareDependencyLinkType(Enum): - STATIC = auto() - DYNAMIC = auto() - TOOL = auto() - OTHER = auto() - - -class DependencyConditionalityType(Enum): - OPTIONAL = auto() - REQUIRED = auto() - PROVIDED = auto() - PREREQUISITE = auto() - OTHER = auto() +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.security.cvss_severity_type import CvssSeverityType +from spdx_tools.spdx3.model.security.vuln_assessment_relationship import VulnAssessmentRelationship @dataclass_with_properties -class SoftwareDependencyRelationship(LifecycleScopedRelationship): - software_linkage: Optional[SoftwareDependencyLinkType] = None - conditionality: Optional[DependencyConditionalityType] = None +class CvssV4VulnAssessmentRelationship(VulnAssessmentRelationship): + score: float | int = None + severity: CvssSeverityType = None + vector_string: str = None def __init__( self, spdx_id: str, from_element: str, + to: List[str], relationship_type: RelationshipType, - to: List[str] | None = None, - creation_info: Optional[CreationInfo] = None, + score: float | int, + severity: CvssSeverityType, + vector_string: str, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -54,16 +43,18 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, - scope: Optional[LifecycleScopeType] = None, - software_linkage: Optional[SoftwareDependencyLinkType] = None, - conditionality: Optional[DependencyConditionalityType] = None, + assessed_element: Optional[str] = None, + published_time: Optional[datetime] = None, + supplied_by: Optional[str] = None, + modified_time: Optional[datetime] = None, + withdrawn_time: Optional[datetime] = None, ): - to = [] if to is None else to verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/epss_vuln_assessment_relationship.py b/src/spdx_tools/spdx3/model/security/epss_vuln_assessment_relationship.py index 261601456..0f0d54b9f 100644 --- a/src/spdx_tools/spdx3/model/security/epss_vuln_assessment_relationship.py +++ b/src/spdx_tools/spdx3/model/security/epss_vuln_assessment_relationship.py @@ -13,15 +13,16 @@ ExternalReference, IntegrityMethod, RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.security.vuln_assessment_relationship import VulnAssessmentRelationship -from spdx_tools.spdx.model import RelationshipType @dataclass_with_properties class EpssVulnAssessmentRelationship(VulnAssessmentRelationship): - probability: int = None - severity: Optional[str] = None + percentile: float | int = None # A decimal number between 0 and 1 inclusive + probability: float | int = None # A decimal number between 0 and 1 inclusive def __init__( self, @@ -29,8 +30,9 @@ def __init__( from_element: str, relationship_type: RelationshipType, to: List[str], - probability: int, - creation_info: Optional[CreationInfo] = None, + percentile: float | int, + probability: float | int, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -38,7 +40,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -47,9 +49,9 @@ def __init__( supplied_by: Optional[str] = None, modified_time: Optional[datetime] = None, withdrawn_time: Optional[datetime] = None, - severity: Optional[str] = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/exploit_catalog_vuln_assessment_relationship.py b/src/spdx_tools/spdx3/model/security/exploit_catalog_vuln_assessment_relationship.py index 27608ad24..4c71c81f7 100644 --- a/src/spdx_tools/spdx3/model/security/exploit_catalog_vuln_assessment_relationship.py +++ b/src/spdx_tools/spdx3/model/security/exploit_catalog_vuln_assessment_relationship.py @@ -14,9 +14,10 @@ ExternalReference, IntegrityMethod, RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.security.vuln_assessment_relationship import VulnAssessmentRelationship -from spdx_tools.spdx.model import RelationshipType class ExploitCatalogType(Enum): @@ -39,7 +40,7 @@ def __init__( catalog_type: ExploitCatalogType, exploited: bool, locator: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -47,7 +48,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -60,4 +61,5 @@ def __init__( verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/ssvc_vuln_assessment_relationship.py b/src/spdx_tools/spdx3/model/security/ssvc_vuln_assessment_relationship.py index d2f52f69e..72caa2045 100644 --- a/src/spdx_tools/spdx3/model/security/ssvc_vuln_assessment_relationship.py +++ b/src/spdx_tools/spdx3/model/security/ssvc_vuln_assessment_relationship.py @@ -14,9 +14,10 @@ ExternalReference, IntegrityMethod, RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.security.vuln_assessment_relationship import VulnAssessmentRelationship -from spdx_tools.spdx.model import RelationshipType class SsvcDecisionType(Enum): @@ -37,7 +38,7 @@ def __init__( relationship_type: RelationshipType, to: List[str], decision_type: SsvcDecisionType, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -45,7 +46,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -58,4 +59,5 @@ def __init__( verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/vex_affected_vuln_assessment_relationship.py b/src/spdx_tools/spdx3/model/security/vex_affected_vuln_assessment_relationship.py index f42da97ae..22ab7669a 100644 --- a/src/spdx_tools/spdx3/model/security/vex_affected_vuln_assessment_relationship.py +++ b/src/spdx_tools/spdx3/model/security/vex_affected_vuln_assessment_relationship.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 -from dataclasses import field from datetime import datetime from beartype.typing import List, Optional @@ -14,15 +13,16 @@ ExternalReference, IntegrityMethod, RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.security.vex_vuln_assessment_relationship import VexVulnAssessmentRelationship -from spdx_tools.spdx.model import RelationshipType @dataclass_with_properties class VexAffectedVulnAssessmentRelationship(VexVulnAssessmentRelationship): action_statement: Optional[str] = None - action_statement_time: List[datetime] = field(default_factory=list) + action_statement_time: Optional[datetime] = None def __init__( self, @@ -30,7 +30,8 @@ def __init__( from_element: str, relationship_type: RelationshipType, to: List[str], - creation_info: Optional[CreationInfo] = None, + action_statement: str, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -38,7 +39,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -49,11 +50,10 @@ def __init__( withdrawn_time: Optional[datetime] = None, vex_version: Optional[str] = None, status_notes: Optional[str] = None, - action_statement: Optional[str] = None, - action_statement_time: List[datetime] | None = None, + action_statement_time: Optional[datetime] = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier - action_statement_time = [] if action_statement_time is None else action_statement_time + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/vex_fixed_vuln_assessment_relationship.py b/src/spdx_tools/spdx3/model/security/vex_fixed_vuln_assessment_relationship.py index 976c81bbc..f6e4d4b44 100644 --- a/src/spdx_tools/spdx3/model/security/vex_fixed_vuln_assessment_relationship.py +++ b/src/spdx_tools/spdx3/model/security/vex_fixed_vuln_assessment_relationship.py @@ -13,9 +13,10 @@ ExternalReference, IntegrityMethod, RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.security.vex_vuln_assessment_relationship import VexVulnAssessmentRelationship -from spdx_tools.spdx.model import RelationshipType @dataclass_with_properties @@ -26,7 +27,7 @@ def __init__( from_element: str, relationship_type: RelationshipType, to: List[str], - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -34,7 +35,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -49,4 +50,5 @@ def __init__( verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/vex_not_affected_vuln_assessment_relationship.py b/src/spdx_tools/spdx3/model/security/vex_not_affected_vuln_assessment_relationship.py index 5fa1e05a8..e0663a91b 100644 --- a/src/spdx_tools/spdx3/model/security/vex_not_affected_vuln_assessment_relationship.py +++ b/src/spdx_tools/spdx3/model/security/vex_not_affected_vuln_assessment_relationship.py @@ -14,9 +14,10 @@ ExternalReference, IntegrityMethod, RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.security.vex_vuln_assessment_relationship import VexVulnAssessmentRelationship -from spdx_tools.spdx.model import RelationshipType class VexJustificationType(Enum): @@ -39,7 +40,7 @@ def __init__( from_element: str, relationship_type: RelationshipType, to: List[str], - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -47,7 +48,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -65,4 +66,5 @@ def __init__( verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/vex_under_investigation_vuln_assessment_relationship.py b/src/spdx_tools/spdx3/model/security/vex_under_investigation_vuln_assessment_relationship.py index 66a5f2fe1..a3bf908bf 100644 --- a/src/spdx_tools/spdx3/model/security/vex_under_investigation_vuln_assessment_relationship.py +++ b/src/spdx_tools/spdx3/model/security/vex_under_investigation_vuln_assessment_relationship.py @@ -13,9 +13,10 @@ ExternalReference, IntegrityMethod, RelationshipCompleteness, + RelationshipType, ) +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.security.vex_vuln_assessment_relationship import VexVulnAssessmentRelationship -from spdx_tools.spdx.model import RelationshipType @dataclass_with_properties @@ -26,7 +27,7 @@ def __init__( from_element: str, relationship_type: RelationshipType, to: List[str], - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -34,7 +35,7 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, completeness: Optional[RelationshipCompleteness] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, @@ -49,4 +50,5 @@ def __init__( verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/security/vulnerability.py b/src/spdx_tools/spdx3/model/security/vulnerability.py index 6f5db8522..440e161ed 100644 --- a/src/spdx_tools/spdx3/model/security/vulnerability.py +++ b/src/spdx_tools/spdx3/model/security/vulnerability.py @@ -7,11 +7,13 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values -from spdx_tools.spdx3.model import CreationInfo, Element, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.artifact import Artifact, SupportType +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties -class Vulnerability(Element): +class Vulnerability(Artifact): published_time: Optional[datetime] = None modified_time: Optional[datetime] = None withdrawn_time: Optional[datetime] = None @@ -19,7 +21,7 @@ class Vulnerability(Element): def __init__( self, spdx_id: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -27,12 +29,23 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, published_time: Optional[datetime] = None, modified_time: Optional[datetime] = None, withdrawn_time: Optional[datetime] = None, + originated_by: List[str] | None = None, + supplied_by: Optional[str] = None, + built_time: Optional[datetime] = None, + release_time: Optional[datetime] = None, + valid_until_time: Optional[datetime] = None, + standard_name: List[str] | None = None, + support_level: List[SupportType] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + originated_by = [] if originated_by is None else originated_by + standard_name = [] if standard_name is None else standard_name + support_level = [] if support_level is None else support_level check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/simplelicensing/__init__.py b/src/spdx_tools/spdx3/model/simplelicensing/__init__.py new file mode 100644 index 000000000..3dec1dec1 --- /dev/null +++ b/src/spdx_tools/spdx3/model/simplelicensing/__init__.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from .any_license_info import AnyLicenseInfo +from .license_expression import LicenseExpression +from .simple_licensing_text import SimpleLicensingText diff --git a/src/spdx_tools/spdx3/model/simplelicensing/any_license_info.py b/src/spdx_tools/spdx3/model/simplelicensing/any_license_info.py new file mode 100644 index 000000000..3d8861595 --- /dev/null +++ b/src/spdx_tools/spdx3/model/simplelicensing/any_license_info.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from abc import abstractmethod + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.spdx3.model.element import Element + + +@dataclass_with_properties +class AnyLicenseInfo(Element): + @abstractmethod + def __init__(self): + pass diff --git a/src/spdx_tools/spdx3/model/simplelicensing/license_expression.py b/src/spdx_tools/spdx3/model/simplelicensing/license_expression.py new file mode 100644 index 000000000..e2da947a7 --- /dev/null +++ b/src/spdx_tools/spdx3/model/simplelicensing/license_expression.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +from dataclasses import field +from typing import Dict, List, Optional + +from semantic_version import Version + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod +from spdx_tools.spdx3.model.simplelicensing.any_license_info import AnyLicenseInfo + + +@dataclass_with_properties +class LicenseExpression(AnyLicenseInfo): + license_expression: str = None + license_list_version: Optional[Version] = None + custom_id_to_uri: Dict[str, str] = field(default_factory=dict) + + def __init__( + self, + spdx_id: str, + creation_info: CreationInfo, + license_expression: str, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + custom_id_to_uri: Dict[str, str] | None = None, + license_list_version: Version | None = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + custom_id_to_uri = {} if custom_id_to_uri is None else custom_id_to_uri + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/simplelicensing/simple_licensing_text.py b/src/spdx_tools/spdx3/model/simplelicensing/simple_licensing_text.py new file mode 100644 index 000000000..227c4f1ca --- /dev/null +++ b/src/spdx_tools/spdx3/model/simplelicensing/simple_licensing_text.py @@ -0,0 +1,35 @@ +from typing import List, Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.element import Element +from spdx_tools.spdx3.model.extension.extension import Extension +from spdx_tools.spdx3.model.external_identifier import ExternalIdentifier +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.integrity_method import IntegrityMethod + + +@dataclass_with_properties +class SimpleLicensingText(Element): + license_text: str = None + + def __init__( + self, + spdx_id: str, + license_text: str, + creation_info: CreationInfo, + name: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + comment: Optional[str] = None, + verified_using: List[IntegrityMethod] | None = None, + external_reference: List[ExternalReference] | None = None, + external_identifier: List[ExternalIdentifier] | None = None, + extension: List[Extension] | None = None, + ): + verified_using = [] if verified_using is None else verified_using + external_reference = [] if external_reference is None else external_reference + external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/software/__init__.py b/src/spdx_tools/spdx3/model/software/__init__.py index f3b157024..ae78ec7f3 100644 --- a/src/spdx_tools/spdx3/model/software/__init__.py +++ b/src/spdx_tools/spdx3/model/software/__init__.py @@ -1,13 +1,11 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 +from spdx_tools.spdx3.model.software.content_identifier import ContentIdentifier, ContentIdentifierType from spdx_tools.spdx3.model.software.software_purpose import SoftwarePurpose -from spdx_tools.spdx3.model.software.file import File +from spdx_tools.spdx3.model.software.file import File, FileKindType from spdx_tools.spdx3.model.software.package import Package from spdx_tools.spdx3.model.software.snippet import Snippet -from spdx_tools.spdx3.model.software.sbom import Sbom, SBOMType -from spdx_tools.spdx3.model.software.software_dependency_relationship import ( - SoftwareDependencyRelationship, - SoftwareDependencyLinkType, - DependencyConditionalityType, -) +from spdx_tools.spdx3.model.software.sbom import Sbom, SbomType +from spdx_tools.spdx3.model.software.software_artifact import SoftwareArtifact +from spdx_tools.spdx3.model.software.software_purpose import SoftwarePurpose diff --git a/src/spdx_tools/spdx3/model/software/content_identifier.py b/src/spdx_tools/spdx3/model/software/content_identifier.py new file mode 100644 index 000000000..abb59e0fd --- /dev/null +++ b/src/spdx_tools/spdx3/model/software/content_identifier.py @@ -0,0 +1,26 @@ +from enum import Enum, auto + +from beartype.typing import Optional + +from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties +from spdx_tools.common.typing.type_checks import check_types_and_set_values +from spdx_tools.spdx3.model import IntegrityMethod + + +class ContentIdentifierType(Enum): + GITOID = auto() + SWHID = auto() + + +@dataclass_with_properties +class ContentIdentifier(IntegrityMethod): + content_identifier_type: ContentIdentifierType = None + content_identifier_value: str = None + + def __init__( + self, + content_identifier_type: ContentIdentifierType, + content_identifier_value: str, + comment: Optional[str] = None, + ): + check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/software/file.py b/src/spdx_tools/spdx3/model/software/file.py index f4b170345..054dc25d9 100644 --- a/src/spdx_tools/spdx3/model/software/file.py +++ b/src/spdx_tools/spdx3/model/software/file.py @@ -2,53 +2,67 @@ # # SPDX-License-Identifier: Apache-2.0 from datetime import datetime +from enum import Enum, auto from beartype.typing import List, Optional from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod -from spdx_tools.spdx3.model.licensing import LicenseField +from spdx_tools.spdx3.model.artifact import SupportType +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.software import SoftwarePurpose +from spdx_tools.spdx3.model.software.content_identifier import ContentIdentifier from spdx_tools.spdx3.model.software.software_artifact import SoftwareArtifact +class FileKindType(Enum): + DIRECTORY = auto() + FILE = auto() + + @dataclass_with_properties class File(SoftwareArtifact): content_type: Optional[str] = None # placeholder for MediaType + file_kind: Optional[FileKindType] = None def __init__( self, spdx_id: str, name: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, summary: Optional[str] = None, description: Optional[str] = None, comment: Optional[str] = None, verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, originated_by: List[str] | None = None, - supplied_by: List[str] | None = None, + supplied_by: Optional[str] = None, built_time: Optional[datetime] = None, release_time: Optional[datetime] = None, valid_until_time: Optional[datetime] = None, standard: List[str] | None = None, - content_identifier: Optional[str] = None, + content_identifier: List[ContentIdentifier] | None = None, primary_purpose: Optional[SoftwarePurpose] = None, additional_purpose: List[SoftwarePurpose] | None = None, - concluded_license: Optional[LicenseField] = None, - declared_license: Optional[LicenseField] = None, copyright_text: Optional[str] = None, - attribution_text: Optional[str] = None, + attribution_text: List[str] | None = None, content_type: Optional[str] = None, + standard_name: List[str] | None = None, + support_level: List[SupportType] | None = None, + file_kind: Optional[FileKindType] = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier originated_by = [] if originated_by is None else originated_by - supplied_by = [] if supplied_by is None else supplied_by standard = [] if standard is None else standard + content_identifier = [] if content_identifier is None else content_identifier additional_purpose = [] if additional_purpose is None else additional_purpose + extension = [] if extension is None else extension + attribution_text = [] if attribution_text is None else attribution_text + standard_name = [] if standard_name is None else standard_name + support_level = [] if support_level is None else support_level check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/software/package.py b/src/spdx_tools/spdx3/model/software/package.py index 12e3eefa0..6a2050756 100644 --- a/src/spdx_tools/spdx3/model/software/package.py +++ b/src/spdx_tools/spdx3/model/software/package.py @@ -8,8 +8,10 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod -from spdx_tools.spdx3.model.licensing import LicenseField +from spdx_tools.spdx3.model.artifact import SupportType +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.software import SoftwarePurpose +from spdx_tools.spdx3.model.software.content_identifier import ContentIdentifier from spdx_tools.spdx3.model.software.software_artifact import SoftwareArtifact @@ -18,45 +20,47 @@ class Package(SoftwareArtifact): package_version: Optional[str] = None download_location: Optional[str] = None # anyURI package_url: Optional[str] = None # anyURI - homepage: Optional[str] = None # anyURI + home_page: Optional[str] = None # anyURI source_info: Optional[str] = None def __init__( self, spdx_id: str, name: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, summary: Optional[str] = None, description: Optional[str] = None, comment: Optional[str] = None, verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, originated_by: List[str] | None = None, - supplied_by: List[str] | None = None, + supplied_by: Optional[str] = None, built_time: Optional[datetime] = None, release_time: Optional[datetime] = None, valid_until_time: Optional[datetime] = None, - standard: List[str] | None = None, - content_identifier: Optional[str] = None, + content_identifier: List[ContentIdentifier] | None = None, primary_purpose: Optional[SoftwarePurpose] = None, additional_purpose: List[SoftwarePurpose] | None = None, - concluded_license: Optional[LicenseField] = None, - declared_license: Optional[LicenseField] = None, copyright_text: Optional[str] = None, - attribution_text: Optional[str] = None, + attribution_text: List[str] | None = None, package_version: Optional[str] = None, download_location: Optional[str] = None, package_url: Optional[str] = None, - homepage: Optional[str] = None, + home_page: Optional[str] = None, source_info: Optional[str] = None, + standard_name: List[str] | None = None, + support_level: List[SupportType] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier originated_by = [] if originated_by is None else originated_by - supplied_by = [] if supplied_by is None else supplied_by - standard = [] if standard is None else standard + content_identifier = [] if content_identifier is None else content_identifier additional_purpose = [] if additional_purpose is None else additional_purpose + attribution_text = [] if attribution_text is None else attribution_text + extension = [] if extension is None else extension + standard_name = [] if standard_name is None else standard_name + support_level = [] if support_level is None else support_level check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/software/sbom.py b/src/spdx_tools/spdx3/model/software/sbom.py index 951322e91..4cb05a0e3 100644 --- a/src/spdx_tools/spdx3/model/software/sbom.py +++ b/src/spdx_tools/spdx3/model/software/sbom.py @@ -12,14 +12,14 @@ Bom, CreationInfo, ExternalIdentifier, - ExternalMap, ExternalReference, IntegrityMethod, - NamespaceMap, + ProfileIdentifierType, ) +from spdx_tools.spdx3.model.extension.extension import Extension -class SBOMType(Enum): +class SbomType(Enum): DESIGN = auto() SOURCE = auto() BUILD = auto() @@ -30,7 +30,7 @@ class SBOMType(Enum): @dataclass_with_properties class Sbom(Bom): - sbom_type: List[SBOMType] = field(default_factory=list) + sbom_type: List[SbomType] = field(default_factory=list) # We overwrite the super-__init__ as check_types_and_set_values() # takes care of all fields (including inherited ones). @@ -39,7 +39,7 @@ def __init__( spdx_id: str, element: List[str], root_element: List[str], - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -47,16 +47,15 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, - namespaces: List[NamespaceMap] | None = None, - imports: List[ExternalMap] | None = None, + extension: List[Extension] | None = None, context: Optional[str] = None, - sbom_type: List[SBOMType] | None = None, + sbom_type: List[SbomType] | None = None, + profile_conformance: List[ProfileIdentifierType] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier - namespaces = [] if namespaces is None else namespaces - imports = [] if imports is None else imports sbom_type = [] if sbom_type is None else sbom_type + profile_conformance = [] if profile_conformance is None else profile_conformance + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/software/snippet.py b/src/spdx_tools/spdx3/model/software/snippet.py index 8cb7a771d..8efdf879a 100644 --- a/src/spdx_tools/spdx3/model/software/snippet.py +++ b/src/spdx_tools/spdx3/model/software/snippet.py @@ -8,9 +8,11 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod -from spdx_tools.spdx3.model.licensing import LicenseField +from spdx_tools.spdx3.model.artifact import SupportType +from spdx_tools.spdx3.model.extension.extension import Extension from spdx_tools.spdx3.model.positive_integer_range import PositiveIntegerRange from spdx_tools.spdx3.model.software import SoftwarePurpose +from spdx_tools.spdx3.model.software.content_identifier import ContentIdentifier from spdx_tools.spdx3.model.software.software_artifact import SoftwareArtifact @@ -18,11 +20,13 @@ class Snippet(SoftwareArtifact): byte_range: Optional[PositiveIntegerRange] = None line_range: Optional[PositiveIntegerRange] = None + snippet_from_file: str = None # SPDXID of file from which snippet is taken def __init__( self, spdx_id: str, - creation_info: Optional[CreationInfo] = None, + snippet_from_file: str, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -30,28 +34,32 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, originated_by: List[str] | None = None, - supplied_by: List[str] | None = None, + supplied_by: Optional[str] = None, built_time: Optional[datetime] = None, release_time: Optional[datetime] = None, valid_until_time: Optional[datetime] = None, standard: List[str] | None = None, - content_identifier: Optional[str] = None, + content_identifier: List[ContentIdentifier] | None = None, primary_purpose: Optional[SoftwarePurpose] = None, additional_purpose: List[SoftwarePurpose] | None = None, - concluded_license: Optional[LicenseField] = None, - declared_license: Optional[LicenseField] = None, copyright_text: Optional[str] = None, - attribution_text: Optional[str] = None, + attribution_text: List[str] | None = None, byte_range: Optional[PositiveIntegerRange] = None, line_range: Optional[PositiveIntegerRange] = None, + standard_name: List[str] | None = None, + support_level: List[SupportType] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier originated_by = [] if originated_by is None else originated_by - supplied_by = [] if supplied_by is None else supplied_by standard = [] if standard is None else standard + content_identifier = [] if content_identifier is None else content_identifier additional_purpose = [] if additional_purpose is None else additional_purpose + extension = [] if extension is None else extension + attribution_text = [] if attribution_text is None else attribution_text + standard_name = [] if standard_name is None else standard_name + support_level = [] if support_level is None else support_level check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/software/software_artifact.py b/src/spdx_tools/spdx3/model/software/software_artifact.py index afc2b7ff3..e0a5f458c 100644 --- a/src/spdx_tools/spdx3/model/software/software_artifact.py +++ b/src/spdx_tools/spdx3/model/software/software_artifact.py @@ -8,19 +8,17 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.spdx3.model import Artifact -from spdx_tools.spdx3.model.licensing import LicenseField from spdx_tools.spdx3.model.software import SoftwarePurpose +from spdx_tools.spdx3.model.software.content_identifier import ContentIdentifier @dataclass_with_properties class SoftwareArtifact(Artifact): - content_identifier: Optional[str] = None + content_identifier: List[ContentIdentifier] = field(default_factory=list) primary_purpose: Optional[SoftwarePurpose] = None additional_purpose: List[SoftwarePurpose] = field(default_factory=list) - concluded_license: Optional[LicenseField] = None - declared_license: Optional[LicenseField] = None copyright_text: Optional[str] = None - attribution_text: Optional[str] = None + attribution_text: List[str] = field(default_factory=list) @abstractmethod def __init__(self): diff --git a/src/spdx_tools/spdx3/model/software_agent.py b/src/spdx_tools/spdx3/model/software_agent.py index 158928926..05728ba48 100644 --- a/src/spdx_tools/spdx3/model/software_agent.py +++ b/src/spdx_tools/spdx3/model/software_agent.py @@ -6,6 +6,7 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import Agent, CreationInfo, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties @@ -13,7 +14,7 @@ class SoftwareAgent(Agent): def __init__( self, spdx_id: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -21,9 +22,10 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/spdx_document.py b/src/spdx_tools/spdx3/model/spdx_document.py index 95aee5a40..26438da11 100644 --- a/src/spdx_tools/spdx3/model/spdx_document.py +++ b/src/spdx_tools/spdx3/model/spdx_document.py @@ -1,23 +1,31 @@ # SPDX-FileCopyrightText: 2023 spdx contributors # # SPDX-License-Identifier: Apache-2.0 +from dataclasses import field + from beartype.typing import List, Optional from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import ( - Bundle, CreationInfo, ExternalIdentifier, ExternalMap, ExternalReference, IntegrityMethod, NamespaceMap, + ProfileIdentifierType, ) +from spdx_tools.spdx3.model.element_collection import ElementCollection +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties -class SpdxDocument(Bundle): +class SpdxDocument(ElementCollection): + namespace_map: List[NamespaceMap] = field(default_factory=list) + imports: List[ExternalMap] = field(default_factory=list) + data_license: Optional[str] = None + # The inherited field "name" is required for a SpdxDocument, no longer optional. # We overwrite the super-__init__ as check_types_and_set_values() # takes care of all fields (including inherited ones). @@ -27,21 +35,24 @@ def __init__( name: str, element: List[str], root_element: List[str], - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, summary: Optional[str] = None, description: Optional[str] = None, comment: Optional[str] = None, verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, - namespaces: List[NamespaceMap] | None = None, + extension: List[Extension] | None = None, + namespace_map: List[NamespaceMap] | None = None, + profile_conformance: List[ProfileIdentifierType] | None = None, imports: List[ExternalMap] | None = None, - context: Optional[str] = None, + data_license: Optional[str] = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier - namespaces = [] if namespaces is None else namespaces + extension = [] if extension is None else extension + namespace_map = [] if namespace_map is None else namespace_map + profile_conformance = [] if profile_conformance is None else profile_conformance imports = [] if imports is None else imports check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/model/tool.py b/src/spdx_tools/spdx3/model/tool.py index 8742c393a..b9559d3d9 100644 --- a/src/spdx_tools/spdx3/model/tool.py +++ b/src/spdx_tools/spdx3/model/tool.py @@ -6,6 +6,7 @@ from spdx_tools.common.typing.dataclass_with_properties import dataclass_with_properties from spdx_tools.common.typing.type_checks import check_types_and_set_values from spdx_tools.spdx3.model import CreationInfo, Element, ExternalIdentifier, ExternalReference, IntegrityMethod +from spdx_tools.spdx3.model.extension.extension import Extension @dataclass_with_properties @@ -13,7 +14,7 @@ class Tool(Element): def __init__( self, spdx_id: str, - creation_info: Optional[CreationInfo] = None, + creation_info: CreationInfo, name: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -21,9 +22,10 @@ def __init__( verified_using: List[IntegrityMethod] | None = None, external_reference: List[ExternalReference] | None = None, external_identifier: List[ExternalIdentifier] | None = None, - extension: Optional[str] = None, + extension: List[Extension] | None = None, ): verified_using = [] if verified_using is None else verified_using external_reference = [] if external_reference is None else external_reference external_identifier = [] if external_identifier is None else external_identifier + extension = [] if extension is None else extension check_types_and_set_values(self, locals()) diff --git a/src/spdx_tools/spdx3/parser/jsonld/jsonld_parser.py b/src/spdx_tools/spdx3/parser/jsonld/jsonld_parser.py new file mode 100644 index 000000000..a2f73cf6e --- /dev/null +++ b/src/spdx_tools/spdx3/parser/jsonld/jsonld_parser.py @@ -0,0 +1,26 @@ +import json +from typing import Dict + +from spdx_python_model import v3_0_1 as spdx_3_0 + +from spdx_tools.spdx3.binding.shacl_to_spdx3_converter import convert_to_payload +from spdx_tools.spdx3.payload import Payload + + +def parse_from_file(file_name: str) -> Payload: + object_set = spdx_3_0.SHACLObjectSet() + + with open(file_name, "r") as fd: + sbom: Dict = json.load(fd) + spdx_3_0.JSONLDDeserializer().deserialize_data(sbom, object_set) + + return convert_to_payload(object_set) + + +def parse_from_string(json_string: str) -> Payload: + object_set = spdx_3_0.SHACLObjectSet() + + sbom: Dict = json.loads(json_string) + spdx_3_0.JSONLDDeserializer().deserialize_data(sbom, object_set) + + return convert_to_payload(object_set) diff --git a/src/spdx_tools/spdx3/payload.py b/src/spdx_tools/spdx3/payload.py index d1108cf25..205d841d4 100644 --- a/src/spdx_tools/spdx3/payload.py +++ b/src/spdx_tools/spdx3/payload.py @@ -20,3 +20,8 @@ def get_element(self, spdx_id: str) -> Element: def get_full_map(self) -> Dict[str, Element]: return self._spdx_id_map + + def get_elements_of_type(self, element_type: type) -> Dict[str, Element]: + return { + spdx_id: element for spdx_id, element in self._spdx_id_map.items() if isinstance(element, element_type) + } diff --git a/src/spdx_tools/spdx3/writer/console/bundle_writer.py b/src/spdx_tools/spdx3/writer/console/bundle_writer.py index 5930db5ee..8c7428d1f 100644 --- a/src/spdx_tools/spdx3/writer/console/bundle_writer.py +++ b/src/spdx_tools/spdx3/writer/console/bundle_writer.py @@ -4,7 +4,6 @@ from beartype.typing import TextIO from spdx_tools.spdx3.model import Bundle -from spdx_tools.spdx3.writer.console.console import write_value from spdx_tools.spdx3.writer.console.spdx_collection_writer import write_collection @@ -12,4 +11,3 @@ def write_bundle(bundle: Bundle, text_output: TextIO, heading: bool = True): if heading: text_output.write("## Bundle\n") write_collection(bundle, text_output) - write_value("context", bundle.context, text_output) diff --git a/src/spdx_tools/spdx3/writer/console/creation_info_writer.py b/src/spdx_tools/spdx3/writer/console/creation_info_writer.py index c91e6781d..f19613aa7 100644 --- a/src/spdx_tools/spdx3/writer/console/creation_info_writer.py +++ b/src/spdx_tools/spdx3/writer/console/creation_info_writer.py @@ -16,6 +16,4 @@ def write_creation_info(creation_info: CreationInfo, text_output: TextIO, indent write_value("created by", created_by, text_output, indent) for created_using in creation_info.created_using: write_value("created using", created_using, text_output, indent) - write_value("profile", [profile.name for profile in creation_info.profile], text_output, indent) - write_value("data license", creation_info.data_license, text_output, indent) write_value("comment", creation_info.comment, text_output, indent) diff --git a/src/spdx_tools/spdx3/writer/console/dataset/dataset_writer.py b/src/spdx_tools/spdx3/writer/console/dataset/dataset_writer.py index 91131240a..0c07a615b 100644 --- a/src/spdx_tools/spdx3/writer/console/dataset/dataset_writer.py +++ b/src/spdx_tools/spdx3/writer/console/dataset/dataset_writer.py @@ -3,14 +3,14 @@ # SPDX-License-Identifier: Apache-2.0 from beartype.typing import TextIO -from spdx_tools.spdx3.model.dataset import Dataset +from spdx_tools.spdx3.model.dataset import DatasetPackage from spdx_tools.spdx3.writer.console.console import write_value from spdx_tools.spdx3.writer.console.software.package_writer import write_package -def write_dataset(dataset: Dataset, text_output: TextIO): - text_output.write("## Dataset\n") +def write_dataset(dataset: DatasetPackage, text_output: TextIO): + text_output.write("## DatasetPackage\n") write_package(dataset, text_output, False) - for property_name in Dataset.__annotations__.keys(): + for property_name in DatasetPackage.__annotations__.keys(): write_value(property_name, getattr(dataset, property_name), text_output) diff --git a/src/spdx_tools/spdx3/writer/console/external_map_writer.py b/src/spdx_tools/spdx3/writer/console/external_map_writer.py index 41f59dc5d..493e7c49d 100644 --- a/src/spdx_tools/spdx3/writer/console/external_map_writer.py +++ b/src/spdx_tools/spdx3/writer/console/external_map_writer.py @@ -10,7 +10,7 @@ def write_external_map(external_map: ExternalMap, text_output: TextIO): - write_value("external_id", external_map.external_id, text_output) + write_value("external_id", external_map.external_spdx_id, text_output) write_optional_heading(external_map.verified_using, "verified using\n", text_output) for integrity_method in external_map.verified_using: # for now Hash is the only child class of the abstract class IntegrityMethod, diff --git a/src/spdx_tools/spdx3/writer/console/payload_writer.py b/src/spdx_tools/spdx3/writer/console/payload_writer.py index 34532f364..ce466dd21 100644 --- a/src/spdx_tools/spdx3/writer/console/payload_writer.py +++ b/src/spdx_tools/spdx3/writer/console/payload_writer.py @@ -16,8 +16,9 @@ ) from spdx_tools.spdx3.model.ai import AIPackage from spdx_tools.spdx3.model.build import Build -from spdx_tools.spdx3.model.dataset import Dataset -from spdx_tools.spdx3.model.software import File, Package, Sbom, Snippet, SoftwareDependencyRelationship +from spdx_tools.spdx3.model.dataset import DatasetPackage +from spdx_tools.spdx3.model.simplelicensing.license_expression import LicenseExpression +from spdx_tools.spdx3.model.software import File, Package, Sbom, Snippet from spdx_tools.spdx3.payload import Payload from spdx_tools.spdx3.writer.console.agent_writer import write_agent from spdx_tools.spdx3.writer.console.ai.ai_package_writer import write_ai_package @@ -27,20 +28,17 @@ from spdx_tools.spdx3.writer.console.bundle_writer import write_bundle from spdx_tools.spdx3.writer.console.dataset.dataset_writer import write_dataset from spdx_tools.spdx3.writer.console.relationship_writer import write_relationship +from spdx_tools.spdx3.writer.console.simplelicensing.license_expression_writer import write_license_expression from spdx_tools.spdx3.writer.console.software.file_writer import write_file from spdx_tools.spdx3.writer.console.software.package_writer import write_package from spdx_tools.spdx3.writer.console.software.sbom_writer import write_sbom from spdx_tools.spdx3.writer.console.software.snippet_writer import write_snippet -from spdx_tools.spdx3.writer.console.software.software_dependency_relationship_writer import ( - write_software_dependency_relationship, -) from spdx_tools.spdx3.writer.console.spdx_document_writer import write_spdx_document from spdx_tools.spdx3.writer.console.tool_writer import write_tool MAP_CLASS_TO_WRITE_METHOD = { Annotation: write_annotation, Relationship: write_relationship, - SoftwareDependencyRelationship: write_software_dependency_relationship, Bundle: write_bundle, SpdxDocument: write_spdx_document, Bom: write_bom, @@ -53,8 +51,9 @@ SoftwareAgent: write_agent, Tool: write_tool, AIPackage: write_ai_package, - Dataset: write_dataset, + DatasetPackage: write_dataset, Build: write_build, + LicenseExpression: write_license_expression, } diff --git a/src/spdx_tools/spdx3/writer/console/relationship_writer.py b/src/spdx_tools/spdx3/writer/console/relationship_writer.py index 9f61e5214..1a8b16a3e 100644 --- a/src/spdx_tools/spdx3/writer/console/relationship_writer.py +++ b/src/spdx_tools/spdx3/writer/console/relationship_writer.py @@ -12,5 +12,5 @@ def write_relationship(relationship: Relationship, text_output: TextIO, heading: if heading: text_output.write("## Relationship\n") write_element_properties(relationship, text_output) - for property_name in Relationship.__annotations__.keys(): + for property_name in relationship.__annotations__.keys(): write_value(property_name, getattr(relationship, property_name), text_output) diff --git a/src/spdx_tools/spdx3/writer/console/simplelicensing/__init__.py b/src/spdx_tools/spdx3/writer/console/simplelicensing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/spdx_tools/spdx3/writer/console/simplelicensing/any_license_info_writer.py b/src/spdx_tools/spdx3/writer/console/simplelicensing/any_license_info_writer.py new file mode 100644 index 000000000..472e2edf0 --- /dev/null +++ b/src/spdx_tools/spdx3/writer/console/simplelicensing/any_license_info_writer.py @@ -0,0 +1,10 @@ +from beartype.typing import TextIO + +from spdx_tools.spdx3.model.simplelicensing.license_expression import AnyLicenseInfo +from spdx_tools.spdx3.writer.console.element_writer import write_element_properties + + +def write_any_license_info(any_license_info: AnyLicenseInfo, text_output: TextIO, heading: bool = True): + if heading: + text_output.write("## AnyLicenseInfo\n") + write_element_properties(any_license_info, text_output) diff --git a/src/spdx_tools/spdx3/writer/console/simplelicensing/license_expression_writer.py b/src/spdx_tools/spdx3/writer/console/simplelicensing/license_expression_writer.py new file mode 100644 index 000000000..40216b0de --- /dev/null +++ b/src/spdx_tools/spdx3/writer/console/simplelicensing/license_expression_writer.py @@ -0,0 +1,13 @@ +from beartype.typing import TextIO + +from spdx_tools.spdx3.model.simplelicensing.license_expression import LicenseExpression +from spdx_tools.spdx3.writer.console.console import write_value +from spdx_tools.spdx3.writer.console.simplelicensing.any_license_info_writer import write_any_license_info + + +def write_license_expression(license_expression: LicenseExpression, text_output: TextIO): + text_output.write("## LicenseExpression\n") + write_any_license_info(license_expression, text_output, False) + + for property_name in LicenseExpression.__annotations__.keys(): + write_value(property_name, getattr(license_expression, property_name), text_output) diff --git a/src/spdx_tools/spdx3/writer/console/software/software_dependency_relationship_writer.py b/src/spdx_tools/spdx3/writer/console/software/software_dependency_relationship_writer.py deleted file mode 100644 index 8064c76a1..000000000 --- a/src/spdx_tools/spdx3/writer/console/software/software_dependency_relationship_writer.py +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-FileCopyrightText: 2023 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 - -from beartype.typing import TextIO - -from spdx_tools.spdx3.model.software import SoftwareDependencyRelationship -from spdx_tools.spdx3.writer.console.console import write_value -from spdx_tools.spdx3.writer.console.lifecycle_scoped_relationship_writer import write_lifecycle_scoped_relationship - - -def write_software_dependency_relationship( - relationship: SoftwareDependencyRelationship, text_output: TextIO, heading: bool = True -): - if heading: - text_output.write("## SoftwareDependencyRelationship\n") - write_lifecycle_scoped_relationship(relationship, text_output, heading=False) - - for property_name in SoftwareDependencyRelationship.__annotations__.keys(): - write_value(property_name, getattr(relationship, property_name), text_output) diff --git a/src/spdx_tools/spdx3/writer/console/spdx_collection_writer.py b/src/spdx_tools/spdx3/writer/console/spdx_collection_writer.py index 7654329b2..66324ee4f 100644 --- a/src/spdx_tools/spdx3/writer/console/spdx_collection_writer.py +++ b/src/spdx_tools/spdx3/writer/console/spdx_collection_writer.py @@ -5,17 +5,10 @@ from spdx_tools.spdx3.model import ElementCollection from spdx_tools.spdx3.writer.console.element_writer import write_element_properties -from spdx_tools.spdx3.writer.console.external_map_writer import write_external_map -from spdx_tools.spdx3.writer.console.namespace_map_writer import write_namespace_map -from spdx_tools.spdx.writer.tagvalue.tagvalue_writer_helper_functions import write_optional_heading def write_collection(collection: ElementCollection, text_output: TextIO): write_element_properties(collection, text_output) text_output.write(f"elements: {', '.join(collection.element)}\n") - write_optional_heading(collection.namespaces, "# Namespaces\n", text_output) - for namespace_map in collection.namespaces: - write_namespace_map(namespace_map, text_output) - write_optional_heading(collection.imports, "# Imports\n", text_output) - for external_map in collection.imports: - write_external_map(external_map, text_output) + text_output.write(f"root elements: {', '.join(collection.root_element)}\n") + text_output.write(f"profile conformance: {', '.join(collection.root_element)}\n") diff --git a/src/spdx_tools/spdx3/writer/console/spdx_document_writer.py b/src/spdx_tools/spdx3/writer/console/spdx_document_writer.py index 8c2cdf649..f705ae2ef 100644 --- a/src/spdx_tools/spdx3/writer/console/spdx_document_writer.py +++ b/src/spdx_tools/spdx3/writer/console/spdx_document_writer.py @@ -5,8 +5,13 @@ from spdx_tools.spdx3.model import SpdxDocument from spdx_tools.spdx3.writer.console.bundle_writer import write_bundle +from spdx_tools.spdx3.writer.console.namespace_map_writer import write_namespace_map +from spdx_tools.spdx.writer.tagvalue.tagvalue_writer_helper_functions import write_optional_heading def write_spdx_document(spdx_document: SpdxDocument, text_output: TextIO): text_output.write("## SPDX Document\n") + write_optional_heading(spdx_document.namespace_map, "# Namespace Map\n", text_output) + for namespace_map in spdx_document.namespace_map: + write_namespace_map(namespace_map, text_output) write_bundle(spdx_document, text_output, False) diff --git a/src/spdx_tools/spdx3/writer/json_ld/json_ld_converter.py b/src/spdx_tools/spdx3/writer/json_ld/json_ld_converter.py index 865053b71..705e39b2d 100644 --- a/src/spdx_tools/spdx3/writer/json_ld/json_ld_converter.py +++ b/src/spdx_tools/spdx3/writer/json_ld/json_ld_converter.py @@ -54,7 +54,7 @@ def _convert_to_json_ld_dict(element: Any, alt_creation_info=False, alt_hash=Fal # else: # element_dict = {} # typing of non-Element classes should be handled by the @context, I think - element_dict = {"@type": element.__class__.__name__} + element_dict = {"type": element.__class__.__name__} for attribute_name in vars(element): attribute_value = getattr(element, attribute_name) @@ -68,7 +68,7 @@ def _convert_to_json_ld_dict(element: Any, alt_creation_info=False, alt_hash=Fal elif attribute_value: if attribute_name == "_spdx_id": - attribute_name = "@id" + attribute_name = "spdxId" elif attribute_name == "_from_element": attribute_name = "from" else: diff --git a/src/spdx_tools/spdx3/writer/json_ld/json_ld_writer.py b/src/spdx_tools/spdx3/writer/json_ld/json_ld_writer.py index 3c3e0819a..7d7df00ff 100644 --- a/src/spdx_tools/spdx3/writer/json_ld/json_ld_writer.py +++ b/src/spdx_tools/spdx3/writer/json_ld/json_ld_writer.py @@ -2,24 +2,18 @@ # # SPDX-License-Identifier: Apache-2.0 import json -from importlib import resources +from spdx_python_model.bindings.v3_0_1 import JSONLDSerializer + +from spdx_tools.spdx3.binding.spdx3_to_shacl_converter import Spdx3ToSHACLConverter from spdx_tools.spdx3.payload import Payload -from spdx_tools.spdx3.writer.json_ld.json_ld_converter import ( - convert_payload_to_json_ld_list_of_elements, -) def write_payload(payload: Payload, file_name: str): - element_list = convert_payload_to_json_ld_list_of_elements(payload) - - # this will be obsolete as soon as the context is publicly available under some URI - # Note: 3.0.1 context is now available at - # https://spdx.org/rdf/3.0.1/spdx-context.jsonld - with resources.files("spdx_tools.spdx3.writer.json_ld").joinpath("context.json").open("r") as infile: - context = json.load(infile) - - complete_dict = {"@context": context, "@graph": element_list} + converter = Spdx3ToSHACLConverter() + shacl_payload = converter.convert(payload) + serializer = JSONLDSerializer() + json_str = serializer.serialize_data(shacl_payload) - with open(file_name + ".jsonld", "w", encoding="utf-8") as out: - json.dump(complete_dict, out, indent=2) + with open(file_name + ".jsonld", "w") as out: + json.dump(json_str, out, indent=2) diff --git a/tests/spdx/writer/rdf/test_package_writer.py b/tests/spdx/writer/rdf/test_package_writer.py index 2c0d5abbd..1680f58d9 100644 --- a/tests/spdx/writer/rdf/test_package_writer.py +++ b/tests/spdx/writer/rdf/test_package_writer.py @@ -6,6 +6,8 @@ from spdx_tools.spdx.datetime_conversions import datetime_to_iso_string from spdx_tools.spdx.model import ExternalPackageRefCategory +from spdx_tools.spdx.model.spdx_no_assertion import SpdxNoAssertion +from spdx_tools.spdx.model.spdx_none import SpdxNone from spdx_tools.spdx.rdfschema.namespace import LICENSE_NAMESPACE, SPDX_NAMESPACE from spdx_tools.spdx.writer.rdf.package_writer import ( add_external_package_ref_to_graph, @@ -96,3 +98,19 @@ def test_external_package_ref_to_graph(external_reference, ref_type, category): assert (None, SPDX_NAMESPACE.referenceType, ref_type) in graph assert (None, SPDX_NAMESPACE.referenceLocator, Literal(external_reference.locator)) in graph assert (None, RDFS.comment, Literal(external_reference.comment)) in graph + + +@pytest.mark.parametrize( + "download_location_value", + [ + "https://download.com", + SpdxNone(), + SpdxNoAssertion(), + ], +) +def test_download_location_values(download_location_value): + graph = Graph() + package = package_fixture() + package.download_location = download_location_value + add_package_to_graph(package, graph, "docNamespace", {}) + assert (None, SPDX_NAMESPACE.downloadLocation, Literal(package.download_location)) in graph diff --git a/tests/spdx3/binding/__init__.py b/tests/spdx3/binding/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/spdx3/binding/test_domain_model.py b/tests/spdx3/binding/test_domain_model.py new file mode 100644 index 000000000..e008d05d7 --- /dev/null +++ b/tests/spdx3/binding/test_domain_model.py @@ -0,0 +1,138 @@ +import enum +import importlib +import pkgutil + +from spdx_python_model import v3_0_1 as spdx_3_0 + +import spdx_tools.spdx3.model as model_package +from spdx_tools.spdx3.binding import helpers +from tests.spdx3.helpers import domain_enum_to_binding_name + + +def test_that_all_binding_properties_and_classes_have_a_domain_mapping(): + """ + This test ensures that (almost) all properties in the binding classes + have a corresponding property in the domain classes. + It does this by iterating over all binding classes and their properties, + converting the property names to their domain equivalents, and checking + that the domain class has an attribute with that name. + It does not do type checking of the attributes, just that they exist. + """ + # get all binding classes with a URI + for binding_uri, binding_class in filter( + lambda item: item[0].startswith("https"), spdx_3_0.SHACLObject.CLASSES.items() + ): + domain_class = helpers.convert_binding_iri_to_domain_class(binding_uri) + + # Skip classes that are abstract, since we don't need to worry about instances of them + if domain_class is None: + continue + + # DictionaryEntry is a special case that does not have a corresponding domain class because the + # domain handles it as an item in a dict[str, str] instead of a list of DictionaryEntry objects, + # so skip this one + if binding_class.__name__ == "DictionaryEntry": + continue + + # Check that all binding attributes have a corresponding domain attribute + binding_attributes = helpers.get_binding_attribute_names(binding_class) + for binding_attribute in binding_attributes: + # skip spdx_id if the domain class is not Element or a subclass of Element because + # only Element and its subclasses have spdx_id + if binding_attribute == "_id" and binding_class.NODE_KIND == spdx_3_0.NodeKind.BlankNodeOrIRI: + continue + + domain_attribute = helpers.get_domain_property_name_from_binding_property_name(binding_attribute) + assert hasattr(domain_class, domain_attribute), ( + f"SPDX property '{binding_attribute}' in class '{binding_class.__name__}'" + f"maps to domain '{domain_class.__name__}::{domain_attribute}', which does not exist" + ) + + +def get_all_domain_classes() -> list: + domain_classes = [] + for _, modname, _ in pkgutil.walk_packages(model_package.__path__, model_package.__name__ + "."): + try: + module = importlib.import_module(modname) + for name in dir(module): + obj = getattr(module, name) + if isinstance(obj, type) and obj.__module__ == modname: + domain_classes.append(obj) + except ImportError: + continue + + return domain_classes + + +def test_that_all_domain_properties_and_classes_have_a_binding_mapping(): + # Test each domain class + for domain_class in get_all_domain_classes(): + print(f"Testing domain class: {domain_class.__name__}") + # Skip private classes and imports + if domain_class.__name__.startswith("_"): + continue + # skip ignored cases and individual values + if domain_class.__name__ in helpers.INDIVIDUAL_ELEMENT_CLASSES: + print(f"Skipping {domain_class.__name__}") + continue + + attributes = [attr for attr in dir(domain_class) if not attr.startswith("_")] + + spdx_iri = helpers.get_binding_iri_from_profile_and_domain_class_name( + helpers.get_spdx_profile_prefix(domain_class), domain_class.__name__ + ) + + if issubclass(domain_class, enum.Enum): + # check if the values match the enum values in the binding + shacl_class = spdx_3_0.SHACLObject.CLASSES.get(spdx_iri) + if shacl_class is None: + raise ValueError(f"Could not find SPDX class for IRI: {spdx_iri}") + + for attribute in attributes: + print(f" Checking enum value: {attribute}") + converted_enum_value = domain_enum_to_binding_name(attribute) + assert converted_enum_value in shacl_class.NAMED_INDIVIDUALS, ( + f"Domain enum value '{attribute}' in domain enum '{domain_class.__name__}'" + f"does not have a corresponding enum value in binding class '{shacl_class.__name__}'" + ) + + else: + # check if the IRI exists in the binding and get the class associated with it + shacl_class = spdx_3_0.SHACLObject.CLASSES.get(spdx_iri) + if shacl_class is None: + raise ValueError(f"Could not find SPDX class for IRI: {spdx_iri}") + + # get all properties in the domain class and check that each property maps to + # a property in the binding class + + # We have to get the properties via the _OBJ_PY_PROPS because we can't use property_keys() + # as we are not instanciating objects + binding_properties = shacl_class._OBJ_PY_PROPS.keys() # pyright: ignore[reportAttributeAccessIssue] + renamed_properties = { + helpers.get_domain_property_name_from_binding_property_name(prop) for prop in binding_properties + } + + if shacl_class.IS_ABSTRACT: + print( + ( + f"Skipping properties comparison for {spdx_iri} as it" + "is abstract and we cannot extract its fields for comparison" + ) + ) + continue + + for attribute in attributes: + if attribute == "spdx_id" and shacl_class.NODE_KIND == spdx_3_0.NodeKind.BlankNodeOrIRI: + print(f"skipping spdx_id as {spdx_iri} is a BlankNodeOrIRI and does not have an _id") + continue + + assert attribute in renamed_properties, ( + f"Domain property '{attribute}' in domain class '{domain_class.__name__}'" + f"does not have a corresponding property in binding class '{spdx_iri}'" + ) + print( + ( + f" Property '{attribute}' in class '{domain_class.__name__}'" + f"maps to binding property in class '{shacl_class.__name__}'" + ) + ) diff --git a/tests/spdx3/binding/test_helpers.py b/tests/spdx3/binding/test_helpers.py new file mode 100644 index 000000000..cec55a3c9 --- /dev/null +++ b/tests/spdx3/binding/test_helpers.py @@ -0,0 +1,66 @@ +from spdx_python_model import v3_0_1 as spdx_3_0 + +from spdx_tools.spdx3.binding import helpers +from spdx_tools.spdx3.model import Element +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.external_reference import ExternalReference +from spdx_tools.spdx3.model.software import Package +from spdx_tools.spdx3.model.software.software_artifact import SoftwareArtifact +from spdx_tools.spdx3.model.spdx_document import SpdxDocument +from tests.spdx3.helpers import domain_enum_to_binding_name + + +def test_get_binding_attribute_names(): + expected_names = { + "_id", + "creationInfo", + "name", + "summary", + "description", + "comment", + "verifiedUsing", + "externalRef", + "externalIdentifier", + "extension", + "element", + "rootElement", + "import_", + "profileConformance", + "namespaceMap", + "dataLicense", + } + + actual_names = helpers.get_binding_attribute_names(spdx_3_0.SpdxDocument) + assert actual_names == expected_names + + +def test_convert_binding_iri_to_domain_class(): + assert helpers.convert_binding_iri_to_domain_class(spdx_3_0.SpdxDocument().get_type()) is SpdxDocument + assert helpers.convert_binding_iri_to_domain_class(spdx_3_0.CreationInfo().get_type()) is CreationInfo + assert helpers.convert_binding_iri_to_domain_class(spdx_3_0.software_Package().get_type()) is Package + assert helpers.convert_binding_iri_to_domain_class(spdx_3_0.ExternalRef().get_type()) is ExternalReference + + +def test_get_attributes_by_domain_class_hierarchy(): + result = helpers.get_attributes_by_domain_class_hierarchy(Package) + + # Check that the result contains expected classes and attributes + assert Package in result + assert "package_version" in result[Package] + assert "home_page" in result[Package] + assert "spdx_id" not in result[Package] # Inherited attribute should not be here + + assert SoftwareArtifact in result + assert "primary_purpose" in result[SoftwareArtifact] + assert "additional_purpose" in result[SoftwareArtifact] + + assert Element in result + assert "spdx_id" in result[Element] + assert "name" in result[Element] + + +def test_domain_enum_to_binding_name(): + assert domain_enum_to_binding_name("BLAKE2B512") == "blake2b512" + assert domain_enum_to_binding_name("CRYSTALS_DILITHIUM") == "crystalsDilithium" + assert domain_enum_to_binding_name("ALT_DOWNLOAD_LOCATION") == "altDownloadLocation" + assert domain_enum_to_binding_name("SHA3_512") == "sha3_512" diff --git a/tests/spdx3/binding/test_shacl.py b/tests/spdx3/binding/test_shacl.py new file mode 100644 index 000000000..ab4dff1ba --- /dev/null +++ b/tests/spdx3/binding/test_shacl.py @@ -0,0 +1,203 @@ +import json +import os +from datetime import datetime, timezone + +from spdx_python_model import v3_0_1 as spdx_3_0 + + +def test_single_creation_info_serialization(): + agent_id = "urn:agentid.com/asd" + starting_creation_info_id = "_:creationinfo" + expected_creation_info_id = "_:CreationInfo0" # The SHACL bindings will rename the ID to this format + + creation_info1 = spdx_3_0.CreationInfo() + creation_info1.comment = "test" + creation_info1.specVersion = "3.0.1" + creation_info1.created = datetime.now().astimezone(timezone.utc) + creation_info1["@id"] = starting_creation_info_id + + agent = spdx_3_0.Agent() + agent["@id"] = agent_id + agent.creationInfo = creation_info1 + creation_info1.createdBy.append(agent) + + document = spdx_3_0.SpdxDocument() + document.creationInfo = creation_info1 + document["@id"] = "urn:documentid.com/1" + + shacl_set = spdx_3_0.SHACLObjectSet() + shacl_set.add(creation_info1) + shacl_set.add(agent) + shacl_set.add(document) + + serializer = spdx_3_0.JSONLDSerializer() + serialized_data = serializer.serialize_data(shacl_set) + + # Extract the graph array from serialized data + graph = serialized_data.get("@graph", []) + + # Find CreationInfo objects by ID + graph = serialized_data.get("@graph", []) + creation_infos = {item.get("@id"): item for item in graph if item.get("type") == "CreationInfo"} + + # Assert both creation infos exist + assert len(creation_infos) == 1, f"Expected 1 CreationInfo objects, found {len(creation_infos)}" + assert expected_creation_info_id in creation_infos, f"CreationInfo with ID '{expected_creation_info_id}' not found" + + # Assert that the SpdxDocument's reference to CreationInfo is correct + spdx_document = next((item for item in graph if item.get("type") == "SpdxDocument"), None) + assert spdx_document is not None, "SpdxDocument not found in serialized data" + assert ( + spdx_document.get("creationInfo") == expected_creation_info_id + ), f"SpdxDocument's creationInfo does not reference '{expected_creation_info_id}'" + + +def test_multiple_creation_info_serialization_no_library(): + agent_id = "urn:agentid.com/asd" + creation_info_id_1 = "_:CreationInfo1" + creation_info_id_2 = "_:CreationInfo0" + + creation_info1 = spdx_3_0.CreationInfo() + creation_info1.comment = "test" + creation_info1.specVersion = "3.0.1" + creation_info1.created = datetime.now().astimezone(timezone.utc) + creation_info1["@id"] = creation_info_id_1 + + creation_info2 = spdx_3_0.CreationInfo() + creation_info2.comment = "test2" + creation_info2.specVersion = "3.0.1" + creation_info2.created = datetime.now().astimezone(timezone.utc) + creation_info1.createdBy.append(agent_id) + creation_info2["@id"] = creation_info_id_2 + + agent = spdx_3_0.Agent() + agent["@id"] = agent_id + agent.creationInfo = creation_info1 + creation_info1.createdBy.append(agent_id) + creation_info2.createdBy.append(agent_id) + + document = spdx_3_0.SpdxDocument() + document.creationInfo = creation_info2 + document["@id"] = "urn:documentid.com/1" + + shacl_set = spdx_3_0.SHACLObjectSet() + shacl_set.add(creation_info1) + shacl_set.add(creation_info2) + shacl_set.add(agent) + shacl_set.add(document) + + serializer = spdx_3_0.JSONLDSerializer() + serialized_data = serializer.serialize_data(shacl_set) + + # Extract the graph array from serialized data + graph = serialized_data.get("@graph", []) + + # Find CreationInfo objects by ID + graph = serialized_data.get("@graph", []) + creation_infos = {item.get("@id"): item for item in graph if item.get("type") == "CreationInfo"} + + # Assert both creation infos exist + assert len(creation_infos) == 2, f"Expected 2 CreationInfo objects, found {len(creation_infos)}" + assert creation_info_id_1 in creation_infos, f"CreationInfo with ID '{creation_info_id_1}' not found" + assert creation_info_id_2 in creation_infos, f"CreationInfo with ID '{creation_info_id_2}' not found" + + # The creation infos will have different IDs and be in an arbitrary order, so we need to assign them by comment + creation_info1_serialized = next((ci for ci in creation_infos.values() if ci.get("comment") == "test"), None) + creation_info2_serialized = next((ci for ci in creation_infos.values() if ci.get("comment") == "test2"), None) + + # Assert both creation infos exist in serialized data + assert ( + creation_info1_serialized is not None + ), f"CreationInfo with ID '{creation_info_id_1}' not found in serialized data" + assert ( + creation_info2_serialized is not None + ), f"CreationInfo with ID '{creation_info_id_2}' not found in serialized data" + + # Assert their properties are correctly serialized + assert creation_info1_serialized.get("comment") == "test" + assert creation_info1_serialized.get("specVersion") == "3.0.1" + + assert creation_info2_serialized.get("comment") == "test2" + assert creation_info2_serialized.get("specVersion") == "3.0.1" + + # Assert that both have the agent in their createdBy field + assert agent_id in creation_info1_serialized.get("createdBy", []) + assert agent_id in creation_info2_serialized.get("createdBy", []) + + +def test_that_spdx_python_tools_is_importing_creation_info_ids_wrong(): + file_path = os.path.join(os.path.dirname(__file__), "../data/spdxV3-example.json") + object_set = spdx_3_0.SHACLObjectSet() + deserializer = spdx_3_0.JSONLDDeserializer() + + with open(file_path, "r") as fd: + sbom = json.load(fd) + deserializer.deserialize_data(sbom, object_set) + + serializer = spdx_3_0.JSONLDSerializer() + serialized_data = serializer.serialize_data(object_set) + + creation_infos = [] + for item in serialized_data["@graph"]: + if item.get("type") == "CreationInfo": + creation_infos.append(item) + + assert len(creation_infos) == 1 + creation_info = creation_infos[0] + assert ( + creation_info["@id"] != "_:creationinfo" + ) # This is the ID that was in the original file, but the SHACL bindings rename it to the following format instead + assert creation_info["@id"] == "_:CreationInfo0" + + +def test_that_individual_element_uri_conversion_works(): + spdx = """ + { + "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld", + "@graph": [ + { + "type": "CreationInfo", + "@id": "_:CreationInfo0", + "comment": "test", + "created": "2025-10-15T01:02:01Z", + "createdBy": [ + "urn:agentid.com/asd" + ], + "specVersion": "3.0.1" + }, + { + "type": "Agent", + "spdxId": "urn:agentid.com/asd", + "creationInfo": "_:CreationInfo0" + }, + { + "type": "SpdxDocument", + "spdxId": "urn:documentid.com/1", + "creationInfo": "_:CreationInfo0" + }, + { + "type": "Relationship", + "spdxId": "urn:relationshipid.com/1", + "creationInfo": "_:CreationInfo0", + "from": "https://spdx.org/rdf/3.0.1/terms/Core/NoneElement", + "relationshipType": "hasConcludedLicense", + "to": [ + "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/NoAssertionLicense" + ] + } + ] + } + """ + object_set = spdx_3_0.SHACLObjectSet() + deserializer = spdx_3_0.JSONLDDeserializer() + + deserializer.deserialize_data(json.loads(spdx), object_set) + + serializer = spdx_3_0.JSONLDSerializer() + serialized_data = serializer.serialize_data(object_set) + + graph = serialized_data.get("@graph", []) + relationship = next((item for item in graph if item.get("type") == "Relationship"), None) + assert relationship is not None + assert relationship.get("from") == "NoneElement" + assert "expandedlicensing_NoAssertionLicense" in relationship.get("to") diff --git a/tests/spdx3/binding/test_spdx3_to_shacl_converter.py b/tests/spdx3/binding/test_spdx3_to_shacl_converter.py new file mode 100644 index 000000000..7f351ff68 --- /dev/null +++ b/tests/spdx3/binding/test_spdx3_to_shacl_converter.py @@ -0,0 +1,387 @@ +import json +import os +from datetime import datetime, timezone + +from semantic_version import Version +from spdx_python_model import v3_0_1 as spdx_3_0 + +from spdx_tools.spdx3.binding.shacl_to_spdx3_converter import convert_to_payload +from spdx_tools.spdx3.binding.spdx3_to_shacl_converter import Spdx3ToSHACLConverter +from spdx_tools.spdx3.model.agent import Agent +from spdx_tools.spdx3.model.creation_info import CreationInfo +from spdx_tools.spdx3.model.element import Element +from spdx_tools.spdx3.model.individual_elements_ids import NO_ASSERTION_LICENSE_ID +from spdx_tools.spdx3.model.relationship import Relationship, RelationshipType +from spdx_tools.spdx3.model.security.cvss_severity_type import CvssSeverityType +from spdx_tools.spdx3.model.security.cvss_v3_vuln_assessment_relationship import CvssV3VulnAssessmentRelationship +from spdx_tools.spdx3.model.security.exploit_catalog_vuln_assessment_relationship import ( + ExploitCatalogType, + ExploitCatalogVulnAssessmentRelationship, +) +from spdx_tools.spdx3.model.security.vulnerability import Vulnerability +from spdx_tools.spdx3.model.software.package import Package +from spdx_tools.spdx3.model.spdx_document import SpdxDocument +from spdx_tools.spdx3.payload import Payload +from tests.spdx3.binding.test_domain_model import get_all_domain_classes +from tests.spdx3.fixtures import fixture_factory + + +def test_serializing_and_compare_it_to_deserialized_spdx(): + file_path = os.path.join(os.path.dirname(__file__), "../data/spdxV3-example.json") + object_set = spdx_3_0.SHACLObjectSet() + deserializer = spdx_3_0.JSONLDDeserializer() + + with open(file_path, "r") as fd: + sbom = json.load(fd) + deserializer.deserialize_data(sbom, object_set) + + payload = convert_to_payload(object_set) + + # convert back to SHACL and serialize, then compare to original file + shacl_object_set = convert_to_shacl(payload) + + serializer = spdx_3_0.JSONLDSerializer() + serialized_data = serializer.serialize_data(shacl_object_set) + + with open(file_path, "r") as fd: + expected_data = json.load(fd) + + # The creationInfo objects will have different IDs after serialization, + # so we need to remove them and references to them before comparison + for item in serialized_data["@graph"]: + if item.get("type") == "CreationInfo": + item.pop("@id", None) + + for item in expected_data["@graph"]: + if item.get("type") == "CreationInfo": + item.pop("@id", None) + + cleaned_serialized_data = normalize_and_exclude_creation_info(serialized_data) + cleaned_expected_data = normalize_and_exclude_creation_info(expected_data) + + assert cleaned_serialized_data == cleaned_expected_data + + +def test_serializing_and_compare_it_to_deserialized_spdx_2(): + file_path = os.path.join(os.path.dirname(__file__), "../data/example6-bin.json") + object_set = spdx_3_0.SHACLObjectSet() + deserializer = spdx_3_0.JSONLDDeserializer() + + with open(file_path, "r") as fd: + sbom = json.load(fd) + deserializer.deserialize_data(sbom, object_set) + + payload = convert_to_payload(object_set) + + # verify creation info is correct + creation_info = payload.get_element( + "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/document0" + ).creation_info + assert creation_info is not None + + # convert back to SHACL and serialize, then compare to original file + shacl_object_set = convert_to_shacl(payload) + + serializer = spdx_3_0.JSONLDSerializer() + serialized_data = serializer.serialize_data(shacl_object_set) + + with open(file_path, "r") as fd: + expected_data = json.load(fd) + + # The creationInfo objects will have different IDs after serialization, + # so we need to remove them and references to them before comparison + for item in serialized_data["@graph"]: + if item.get("type") == "CreationInfo": + item.pop("@id", None) + + for item in expected_data["@graph"]: + if item.get("type") == "CreationInfo": + item.pop("@id", None) + + cleaned_serialized_data = normalize_and_exclude_creation_info(serialized_data) + cleaned_expected_data = normalize_and_exclude_creation_info(expected_data) + + assert cleaned_serialized_data == cleaned_expected_data + + +def remove_creation_info_from_json(obj): + """Recursively remove 'creationInfo' fields from JSON objects.""" + if isinstance(obj, dict): + # Create a new dict without 'creationInfo' field + return {k: remove_creation_info_from_json(v) for k, v in obj.items() if k != "creationInfo"} + elif isinstance(obj, list): + return [remove_creation_info_from_json(item) for item in obj] + else: + return obj + + +def normalize_and_exclude_creation_info(obj): + """Normalize JSON and exclude creationInfo fields.""" + # First remove creationInfo fields + cleaned_obj = remove_creation_info_from_json(obj) + # Then normalize for comparison + return normalize_json(cleaned_obj) + + +def normalize_json(obj): + """Recursively normalize JSON object for comparison.""" + if isinstance(obj, dict): + return {k: normalize_json(v) for k, v in sorted(obj.items())} + elif isinstance(obj, list): + # Sort lists if items are comparable + try: + return sorted([normalize_json(item) for item in obj], key=str) + except TypeError: + # If items can't be sorted, just normalize them + return [normalize_json(item) for item in obj] + else: + return obj + + +def test_multiple_creation_info_serialization_with_library(): + document_id = "urn:documentid.com/1" + agent_id = "urn:agentid.com/asd" + creation_info_id_1 = "_:CreationInfo0" + creation_info_id_2 = "_:CreationInfo1" + + creation_info1 = CreationInfo( + Version("3.0.1"), datetime.now().astimezone(timezone.utc), [agent_id], comment="test1" + ) + creation_info2 = CreationInfo( + Version("3.0.1"), datetime.now().astimezone(timezone.utc), [agent_id], comment="test2" + ) + + agent = Agent(agent_id, creation_info1) + + document = SpdxDocument(document_id, "Document Name", [], [], creation_info=creation_info2) + + payload = Payload() + payload.add_element(agent) + payload.add_element(document) + + converter = Spdx3ToSHACLConverter() + shacl_set = converter.convert(payload) + + serializer = spdx_3_0.JSONLDSerializer() + serialized_data = serializer.serialize_data(shacl_set) + + # Extract the graph array from serialized data + graph = serialized_data.get("@graph", []) + + # Find CreationInfo objects by ID + graph = serialized_data.get("@graph", []) + creation_infos = {item.get("@id"): item for item in graph if item.get("type") == "CreationInfo"} + + # Assert both creation infos exist + assert len(creation_infos) == 2, f"Expected 2 CreationInfo objects, found {len(creation_infos)}" + assert creation_info_id_1 in creation_infos, f"CreationInfo with ID '{creation_info_id_1}' not found" + assert creation_info_id_2 in creation_infos, f"CreationInfo with ID '{creation_info_id_2}' not found" + + # The creation infos may have different IDs and be in an arbitrary order, so we need to assign them by comment + creation_info1_serialized = next((ci for ci in creation_infos.values() if ci.get("comment") == "test1"), None) + creation_info2_serialized = next((ci for ci in creation_infos.values() if ci.get("comment") == "test2"), None) + + # Assert both creation infos exist in serialized data + assert ( + creation_info1_serialized is not None + ), f"CreationInfo with ID '{creation_info_id_1}' not found in serialized data" + assert ( + creation_info2_serialized is not None + ), f"CreationInfo with ID '{creation_info_id_2}' not found in serialized data" + + # Assert their properties are correctly serialized + assert creation_info1_serialized.get("comment") == "test1" + assert creation_info1_serialized.get("specVersion") == "3.0.1" + + assert creation_info2_serialized.get("comment") == "test2" + assert creation_info2_serialized.get("specVersion") == "3.0.1" + + # Assert that both have the agent in their createdBy field + assert agent_id in creation_info1_serialized.get("createdBy") + assert agent_id in creation_info2_serialized.get("createdBy") + + # Assert that the Agent's reference to CreationInfo is correct + agent = next((item for item in graph if item.get("type") == "Agent"), None) + assert agent is not None, "Agent not found in serialized data" + assert agent.get("creationInfo") == creation_info_id_1 + + # Assert that the SpdxDocument's reference to CreationInfo is correct + spdx_document = next((item for item in graph if item.get("type") == "SpdxDocument"), None) + assert spdx_document is not None, "SpdxDocument not found in serialized data" + assert spdx_document.get("creationInfo") == creation_info_id_2 + + +def convert_to_shacl(payload: Payload) -> spdx_3_0.SHACLObjectSet: + converter = Spdx3ToSHACLConverter() + return converter.convert(payload) + + +def test_serializing_and_compare_it_to_deserialized_spdx_3(): + file_path = os.path.join(os.path.dirname(__file__), "../data/appbomination.spdx.json") + object_set = spdx_3_0.SHACLObjectSet() + deserializer = spdx_3_0.JSONLDDeserializer() + + with open(file_path, "r") as fd: + sbom = json.load(fd) + deserializer.deserialize_data(sbom, object_set) + + payload = convert_to_payload(object_set) + + # verify that the creation infos are linked correctly + spdx_document = payload.get_element( + "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/document0" + ) + creation_info_1 = spdx_document.creation_info + assert creation_info_1 is not None + + package = payload.get_element( + ( + "http://www.sourceauditor.com/spdxdocs/appbomination-src/" + "e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd94" + ) + ) + creation_info_0 = package.creation_info + assert creation_info_0 is not None + + assert creation_info_0 != creation_info_1 + + software_file = payload.get_element( + ( + "http://www.sourceauditor.com/spdxdocs/appbomination-src/" + "e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd91" + ) + ) + assert creation_info_0 == software_file.creation_info + + tool = payload.get_element( + ( + "http://www.sourceauditor.com/spdxdocs/appbomination-src/" + "e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/additionalToolSPDXRef-gnrtd72" + ) + ) + assert creation_info_1 == tool.creation_info + + # convert back to SHACL and serialize, then compare to original file + shacl_object_set = convert_to_shacl(payload) + + serializer = spdx_3_0.JSONLDSerializer() + serialized_data = serializer.serialize_data(shacl_object_set) + + with open(file_path, "r") as fd: + expected_data = json.load(fd) + + # The creationInfo objects will have different IDs after serialization, + # so we need to remove them and references to them before comparison + for item in serialized_data["@graph"]: + if item.get("type") == "CreationInfo": + item.pop("@id", None) + + # Also, this SPDX file being read has a blank "software_sourceInfo" field, which the SHACL bindings deserialize + # as a None value, so when serializing it back to JSON-LD it becomes absent. + # To handle this we remove that field from the expected data as well. + for item in expected_data["@graph"]: + if item.get("type") == "CreationInfo": + item.pop("@id", None) + if item.get("type") == "software_Package" and "software_sourceInfo" in item: + item.pop("software_sourceInfo", None) + + cleaned_serialized_data = normalize_and_exclude_creation_info(serialized_data) + cleaned_expected_data = normalize_and_exclude_creation_info(expected_data) + + assert cleaned_expected_data == cleaned_serialized_data + + +def test_individual_element_conversion(): + document_id = "urn:documentid.com/1" + agent_id = "urn:agentid.com/asd" + package_id = "urn:packageid.com/1" + relationship_id = "urn:relationshipid.com/1" + vuln_id = "urn:vulnerabilityid.com/1" + cvss_relationship_id = "urn:vulnerabilityrelationship.com/1" + exploit_relationship_id = "urn:exploitrelationship.com/1" + + creation_info1 = CreationInfo( + Version("3.0.1"), datetime.now().astimezone(timezone.utc), [agent_id], comment="test1" + ) + + agent = Agent(agent_id, creation_info1) + + document = SpdxDocument(document_id, "Document Name", [], [], creation_info=creation_info1) + + package = Package(package_id, "Package Name", creation_info1, built_time=datetime.now().astimezone(timezone.utc)) + + relationship = Relationship( + relationship_id, + package_id, + RelationshipType.HAS_CONCLUDED_LICENSE, + creation_info1, + to=[NO_ASSERTION_LICENSE_ID], + ) + + vulnerability = Vulnerability(vuln_id, creation_info=creation_info1, name="Vulnerability Name") + + vuln_relationship = CvssV3VulnAssessmentRelationship( + cvss_relationship_id, + vuln_id, + list(package_id), + RelationshipType.AFFECTS, + 7.5, + CvssSeverityType.HIGH, + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H/E:P/RL:U/RC:R", + creation_info1, + ) + + vuln_exploit_catalog_relationship = ExploitCatalogVulnAssessmentRelationship( + exploit_relationship_id, + vuln_id, + RelationshipType.HAS_ASSESSMENT_FOR, + list(package_id), + ExploitCatalogType.KEV, + False, + "https://www.cisa.gov", + creation_info1, + ) + + payload = Payload() + payload.add_element(agent) + payload.add_element(document) + payload.add_element(package) + payload.add_element(relationship) + payload.add_element(vulnerability) + payload.add_element(vuln_relationship) + payload.add_element(vuln_exploit_catalog_relationship) + + converter = Spdx3ToSHACLConverter() + shacl_set = converter.convert(payload) + + serializer = spdx_3_0.JSONLDSerializer() + serialized_data = serializer.serialize_data(shacl_set) + + serialized_relationship = next( + (item for item in serialized_data.get("@graph", []) if item.get("type") == "Relationship"), None + ) + + assert serialized_relationship is not None, "Relationship not found in serialized data" + assert serialized_relationship.get("from") == package_id + assert serialized_relationship.get("to") == ["expandedlicensing_NoAssertionLicense"] + + +def test_fixture_serialization(): + payload = Payload() + + for domain_class in get_all_domain_classes(): + try: + fixture = fixture_factory(domain_class) + except ValueError: + if isinstance(domain_class, Element) or isinstance(domain_class, CreationInfo): + raise RuntimeError(f"Domain class: {type(domain_class)} is missing its fixture.") + else: + # not in the fixture factory, but isn't a stand alone class, e.g. an enum, so it's okay to skip + continue + payload.add_element(fixture) + + converter = Spdx3ToSHACLConverter() + shacl_set = converter.convert(payload) + + serializer = spdx_3_0.JSONLDSerializer() + serializer.serialize_data(shacl_set) diff --git a/tests/spdx3/bump/test_actor_bump.py b/tests/spdx3/bump/test_actor_bump.py index e6606134e..4e6c4e36b 100644 --- a/tests/spdx3/bump/test_actor_bump.py +++ b/tests/spdx3/bump/test_actor_bump.py @@ -13,7 +13,6 @@ ExternalIdentifierType, Organization, Person, - ProfileIdentifierType, Tool, ) from spdx_tools.spdx3.payload import Payload @@ -37,7 +36,7 @@ def test_bump_actor(actor_type, actor_name, actor_mail, element_type, new_spdx_id): payload = Payload() document_namespace = "https://doc.namespace" - creation_info = CreationInfo(Version("3.0.0"), datetime(2022, 1, 1), ["Creator"], [ProfileIdentifierType.CORE]) + creation_info = CreationInfo(Version("3.0.0"), datetime(2022, 1, 1), ["Creator"]) actor = Actor(actor_type, actor_name, actor_mail) agent_or_tool_id = bump_actor(actor, payload, document_namespace, creation_info) @@ -54,8 +53,8 @@ def test_bump_actor(actor_type, actor_name, actor_mail, element_type, new_spdx_i def test_bump_actor_that_already_exists(): - creation_info_old = CreationInfo(Version("3.0.0"), datetime(2022, 1, 1), ["Creator"], [ProfileIdentifierType.CORE]) - creation_info_new = CreationInfo(Version("3.0.0"), datetime(2023, 2, 2), ["Creator"], [ProfileIdentifierType.CORE]) + creation_info_old = CreationInfo(Version("3.0.0"), datetime(2022, 1, 1), ["Creator"]) + creation_info_new = CreationInfo(Version("3.0.0"), datetime(2023, 2, 2), ["Creator"]) name = "some name" document_namespace = "https://doc.namespace" diff --git a/tests/spdx3/bump/test_external_document_ref_bump.py b/tests/spdx3/bump/test_external_document_ref_bump.py index 296a080c6..a882dd906 100644 --- a/tests/spdx3/bump/test_external_document_ref_bump.py +++ b/tests/spdx3/bump/test_external_document_ref_bump.py @@ -18,7 +18,7 @@ def test_bump_external_document_ref(): assert namespace_map.prefix == "DocumentRef-external" assert namespace_map.namespace == "https://external.uri#" - assert imports.external_id == "DocumentRef-external:SPDXRef-DOCUMENT" + assert imports.external_spdx_id == "DocumentRef-external:SPDXRef-DOCUMENT" assert imports.verified_using == [bump_checksum(checksum)] @@ -33,4 +33,4 @@ def test_bump_multiple_external_document_refs(): spdx_document = bump_creation_info(creation_info, payload) assert len(spdx_document.imports) == 2 - assert len(spdx_document.namespaces) == 2 + assert len(spdx_document.namespace_map) == 2 diff --git a/tests/spdx3/bump/test_external_element_bump.py b/tests/spdx3/bump/test_external_element_bump.py index 887fa5f44..f88475fa7 100644 --- a/tests/spdx3/bump/test_external_element_bump.py +++ b/tests/spdx3/bump/test_external_element_bump.py @@ -36,15 +36,15 @@ def test_bump_external_elements(): ), packages=[package_fixture(spdx_id=package_id)], files=[file_fixture(spdx_id=file_id)], - snippets=[snippet_fixture(spdx_id=snippet_id)], + snippets=[snippet_fixture(spdx_id=snippet_id, file_spdx_id=file_id)], ) payload: Payload = bump_spdx_document(spdx2_document) expected_imports = [ - ExternalMap(external_id=package_id, defining_document=full_external_doc_id), - ExternalMap(external_id=file_id, defining_document=full_external_doc_id), - ExternalMap(external_id=snippet_id, defining_document=full_external_doc_id), - ExternalMap(external_id=full_external_doc_id, verified_using=[bump_checksum(checksum_fixture())]), + ExternalMap(external_spdx_id=package_id, defining_artifact=full_external_doc_id), + ExternalMap(external_spdx_id=file_id, defining_artifact=full_external_doc_id), + ExternalMap(external_spdx_id=snippet_id, defining_artifact=full_external_doc_id), + ExternalMap(external_spdx_id=full_external_doc_id, verified_using=[bump_checksum(checksum_fixture())]), ] spdx_document = payload.get_element(f"{document_namespace}#SPDXRef-DOCUMENT") diff --git a/tests/spdx3/bump/test_file_bump.py b/tests/spdx3/bump/test_file_bump.py index 06e93db42..a1e7c8a36 100644 --- a/tests/spdx3/bump/test_file_bump.py +++ b/tests/spdx3/bump/test_file_bump.py @@ -7,6 +7,7 @@ from spdx_tools.spdx3.model.software import File from spdx_tools.spdx3.payload import Payload from spdx_tools.spdx.model.file import File as Spdx2_File +from tests.spdx3.fixtures import creation_info_fixture from tests.spdx.fixtures import file_fixture @@ -14,14 +15,15 @@ def test_bump_file(): payload = Payload() document_namespace = "https://doc.namespace" spdx2_file: Spdx2_File = file_fixture() + creation_info = creation_info_fixture() integrity_method: Hash = Hash(HashAlgorithm.SHA1, "71c4025dd9897b364f3ebbb42c484ff43d00791c") expected_new_file_id = f"{document_namespace}#{spdx2_file.spdx_id}" - bump_file(spdx2_file, payload, document_namespace, [], []) + bump_file(spdx2_file, payload, document_namespace, [], [], creation_info) file = payload.get_element(expected_new_file_id) assert isinstance(file, File) assert file.spdx_id == expected_new_file_id assert file.verified_using == [integrity_method] assert file.copyright_text == spdx2_file.copyright_text - assert file.attribution_text == spdx2_file.attribution_texts[0] + assert file.attribution_text == spdx2_file.attribution_texts diff --git a/tests/spdx3/bump/test_license_expression_bump.py b/tests/spdx3/bump/test_license_expression_bump.py index 0f63299cf..97873f7c5 100644 --- a/tests/spdx3/bump/test_license_expression_bump.py +++ b/tests/spdx3/bump/test_license_expression_bump.py @@ -7,70 +7,77 @@ from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx3.bump_from_spdx2.license_expression import ( bump_license_expression, - bump_license_expression_or_none_or_no_assertion, ) -from spdx_tools.spdx3.model.licensing import ( +from spdx_tools.spdx3.model.expandedlicensing import ( ConjunctiveLicenseSet, CustomLicense, - CustomLicenseAddition, DisjunctiveLicenseSet, ListedLicense, - ListedLicenseException, - NoAssertionLicense, - NoneLicense, WithAdditionOperator, ) -from spdx_tools.spdx.model import SpdxNoAssertion, SpdxNone +from spdx_tools.spdx3.payload import Payload +from tests.spdx3.fixtures import creation_info_fixture from tests.spdx.fixtures import extracted_licensing_info_fixture +creation_info = creation_info_fixture() -@pytest.mark.parametrize( - "element, expected_class", - [ - (SpdxNoAssertion(), NoAssertionLicense), - (SpdxNone(), NoneLicense), - (spdx_licensing.parse("MIT"), ListedLicense), - ], -) -def test_license_expression_or_none_or_no_assertion(element, expected_class): - license_info = bump_license_expression_or_none_or_no_assertion(element, []) - - assert isinstance(license_info, expected_class) +namespace = "https://doc.namespace" @pytest.mark.parametrize( "license_expression, extracted_licensing_info, expected_element", [ - (spdx_licensing.parse("MIT"), [], ListedLicense("MIT", "MIT", "blank")), - (spdx_licensing.parse("LGPL-2.0"), [], ListedLicense("LGPL-2.0-only", "LGPL-2.0-only", "blank")), + ( + spdx_licensing.parse("MIT"), + [], + ListedLicense("https://doc.namespace#SPDXRef-ListedLicense-1", "blank", creation_info, name="MIT"), + ), + ( + spdx_licensing.parse("LGPL-2.0"), + [], + ListedLicense( + "https://doc.namespace#SPDXRef-ListedLicense-1", "blank", creation_info, name="LGPL-2.0-only" + ), + ), ( spdx_licensing.parse("LicenseRef-1"), [extracted_licensing_info_fixture()], - CustomLicense("LicenseRef-1", "licenseName", "extractedText"), + CustomLicense( + "https://doc.namespace#SPDXRef-CustomLicense-1", "extractedText", creation_info, name="licenseName" + ), ), ( spdx_licensing.parse("MIT AND LGPL-2.0"), [], ConjunctiveLicenseSet( - [ListedLicense("MIT", "MIT", "blank"), ListedLicense("LGPL-2.0-only", "LGPL-2.0-only", "blank")] + "https://doc.namespace#SPDXRef-ConjunctiveLicenseSet-0", + [ + "https://doc.namespace#SPDXRef-ListedLicense-1", + "https://doc.namespace#SPDXRef-ListedLicense-2", + ], + creation_info, ), ), ( spdx_licensing.parse("LicenseRef-1 OR LGPL-2.0"), [extracted_licensing_info_fixture()], DisjunctiveLicenseSet( + "https://doc.namespace#SPDXRef-DisjunctiveLicenseSet-1", [ - CustomLicense("LicenseRef-1", "licenseName", "extractedText"), - ListedLicense("LGPL-2.0-only", "LGPL-2.0-only", "blank"), - ] + "https://doc.namespace#SPDXRef-CustomLicense-1", + "https://doc.namespace#SPDXRef-ListedLicense-1", + ], + creation_info, ), ), ( spdx_licensing.parse("LGPL-2.0 WITH 389-exception"), [], WithAdditionOperator( - ListedLicense("LGPL-2.0-only", "LGPL-2.0-only", "blank"), - ListedLicenseException("389-exception", "", ""), + "https://doc.namespace#SPDXRef-WithAdditionOperator-0", + "https://doc.namespace#SPDXRef-ListedLicense-1", + "https://doc.namespace#SPDXRef-ListedLicenseException-0", + creation_info, ), ), ( @@ -80,8 +87,10 @@ def test_license_expression_or_none_or_no_assertion(element, expected_class): extracted_licensing_info_fixture("custom-exception", "This is a custom exception", "exceptionName"), ], WithAdditionOperator( - CustomLicense("LicenseRef-1", "licenseName", "extractedText"), - CustomLicenseAddition("custom-exception", "exceptionName", "This is a custom exception"), + "https://doc.namespace#SPDXRef-WithAdditionOperator-0", + "https://doc.namespace#SPDXRef-CustomLicense-1", + "https://doc.namespace#SPDXRef-CustomLicenseAddition-0", + creation_info, ), ), ( @@ -91,18 +100,37 @@ def test_license_expression_or_none_or_no_assertion(element, expected_class): extracted_licensing_info_fixture("custom-exception", "This is a custom exception", "exceptionName"), ], ConjunctiveLicenseSet( + "https://doc.namespace#SPDXRef-ConjunctiveLicenseSet-0", [ - ListedLicense("MIT", "MIT", "blank"), - WithAdditionOperator( - CustomLicense("LicenseRef-1", "licenseName", "extractedText"), - CustomLicenseAddition("custom-exception", "exceptionName", "This is a custom exception"), - ), - ] + "https://doc.namespace#SPDXRef-ListedLicense-1", + "https://doc.namespace#SPDXRef-WithAdditionOperator-0", + ], + creation_info, ), ), ], ) def test_license_expression_bump(license_expression: LicenseExpression, extracted_licensing_info, expected_element): - license_info = bump_license_expression(license_expression, extracted_licensing_info) + # populating the payload to test that additional SPDX IDs belonging to elements of these types increment correctly + payload = Payload() + payload.add_element( + CustomLicense("https://doc.namespace#SPDXRef-CustomLicense-0", "license text", creation_info_fixture()) + ) + payload.add_element( + ListedLicense("https://doc.namespace#SPDXRef-ListedLicense-0", "license text", creation_info_fixture()) + ) + payload.add_element( + DisjunctiveLicenseSet( + "https://doc.namespace#SPDXRef-DisjunctiveLicenseSet-0", + [ + "https://doc.namespace#SPDXRef-CustomLicense-0", + "https://doc.namespace#SPDXRef-ListedLicense-0", + ], + creation_info, + ) + ) + license_info = bump_license_expression( + license_expression, extracted_licensing_info, namespace, creation_info, payload + ) assert license_info == expected_element diff --git a/tests/spdx3/bump/test_package_bump.py b/tests/spdx3/bump/test_package_bump.py index efef7933d..864562c3f 100644 --- a/tests/spdx3/bump/test_package_bump.py +++ b/tests/spdx3/bump/test_package_bump.py @@ -12,6 +12,7 @@ from spdx_tools.spdx.model import SpdxNoAssertion from spdx_tools.spdx.model.package import ExternalPackageRef, ExternalPackageRefCategory from spdx_tools.spdx.model.package import Package as Spdx2_Package +from tests.spdx3.fixtures import creation_info_fixture from tests.spdx.fixtures import actor_fixture, package_fixture @@ -22,10 +23,10 @@ actor_fixture(name="originatorName"), ["https://doc.namespace#SPDXRef-Actor-originatorName-some@mail.com"], actor_fixture(name="supplierName"), - ["https://doc.namespace#SPDXRef-Actor-supplierName-some@mail.com"], + "https://doc.namespace#SPDXRef-Actor-supplierName-some@mail.com", ), - (None, [], None, []), - (SpdxNoAssertion(), [], SpdxNoAssertion(), []), + (None, [], None, None), + (SpdxNoAssertion(), [], SpdxNoAssertion(), None), ], ) def test_bump_package(originator, expected_originator, supplier, expected_supplier): @@ -41,9 +42,10 @@ def test_bump_package(originator, expected_originator, supplier, expected_suppli ExternalPackageRef(ExternalPackageRefCategory.PERSISTENT_ID, "swh", "swh_locator", "swh_comment"), ], ) + creation_info = creation_info_fixture() expected_new_package_id = f"{document_namespace}#{spdx2_package.spdx_id}" - bump_package(spdx2_package, payload, document_namespace, [], []) + bump_package(spdx2_package, payload, document_namespace, [], [], creation_info) package = payload.get_element(expected_new_package_id) assert isinstance(package, Package) @@ -59,13 +61,16 @@ def test_bump_package(originator, expected_originator, supplier, expected_suppli assert package.package_version == spdx2_package.version assert package.originated_by == expected_originator assert package.supplied_by == expected_supplier - assert package.homepage == spdx2_package.homepage + assert package.home_page == spdx2_package.homepage assert package.source_info == spdx2_package.source_info assert package.built_time == spdx2_package.built_date assert package.release_time == spdx2_package.release_date assert package.valid_until_time == spdx2_package.valid_until_date assert package.copyright_text == spdx2_package.copyright_text - assert package.attribution_text == spdx2_package.attribution_texts[0] + assert package.attribution_text == spdx2_package.attribution_texts + assert package.creation_info == creation_info + + # check for license relationships as those not part of the package directly def test_bump_of_single_purl_without_comment(): @@ -76,9 +81,10 @@ def test_bump_of_single_purl_without_comment(): ExternalPackageRef(ExternalPackageRefCategory.PACKAGE_MANAGER, "purl", "purl_locator", None), ] ) + creation_info = creation_info_fixture() expected_new_package_id = f"{document_namespace}#{spdx2_package.spdx_id}" - bump_package(spdx2_package, payload, document_namespace, [], []) + bump_package(spdx2_package, payload, document_namespace, [], [], creation_info) package = payload.get_element(expected_new_package_id) assert package.package_url == "purl_locator" @@ -94,15 +100,16 @@ def test_bump_of_single_purl_with_comment(): ExternalPackageRef(ExternalPackageRefCategory.PACKAGE_MANAGER, "purl", "purl_locator", "purl_comment"), ] ) + creation_info = creation_info_fixture() expected_new_package_id = f"{document_namespace}#{spdx2_package.spdx_id}" - bump_package(spdx2_package, payload, document_namespace, [], []) + bump_package(spdx2_package, payload, document_namespace, [], [], creation_info) package = payload.get_element(expected_new_package_id) assert package.package_url is None assert package.external_reference == [] assert package.external_identifier == [ - ExternalIdentifier(ExternalIdentifierType.PURL, "purl_locator", "purl_comment") + ExternalIdentifier(ExternalIdentifierType.PACKAGE_URL, "purl_locator", "purl_comment") ] @@ -115,9 +122,10 @@ def test_bump_of_multiple_purls(): ExternalPackageRef(ExternalPackageRefCategory.PACKAGE_MANAGER, "purl", "purl_locator2", None), ] ) + creation_info = creation_info_fixture() expected_new_package_id = f"{document_namespace}#{spdx2_package.spdx_id}" - bump_package(spdx2_package, payload, document_namespace, [], []) + bump_package(spdx2_package, payload, document_namespace, [], [], creation_info) package = payload.get_element(expected_new_package_id) assert package.package_url is None @@ -125,7 +133,7 @@ def test_bump_of_multiple_purls(): TestCase().assertCountEqual( package.external_identifier, [ - ExternalIdentifier(ExternalIdentifierType.PURL, "purl_locator", "comment"), - ExternalIdentifier(ExternalIdentifierType.PURL, "purl_locator2", None), + ExternalIdentifier(ExternalIdentifierType.PACKAGE_URL, "purl_locator", "comment"), + ExternalIdentifier(ExternalIdentifierType.PACKAGE_URL, "purl_locator2", None), ], ) diff --git a/tests/spdx3/bump/test_relationship_bump.py b/tests/spdx3/bump/test_relationship_bump.py index cd2910268..d94be52c7 100644 --- a/tests/spdx3/bump/test_relationship_bump.py +++ b/tests/spdx3/bump/test_relationship_bump.py @@ -6,18 +6,21 @@ from spdx_tools.spdx3.payload import Payload from spdx_tools.spdx.model import RelationshipType as Spdx2_RelationshipType from spdx_tools.spdx.model import SpdxNoAssertion, SpdxNone +from tests.spdx3.fixtures import creation_info_fixture from tests.spdx.fixtures import relationship_fixture def test_relationship_bump(): spdx2_relationship = relationship_fixture() document_namespace = "https://doc.namespace" - relationship = bump_relationship(spdx2_relationship, document_namespace, 1) + creation_info = creation_info_fixture() + relationship = bump_relationship(spdx2_relationship, document_namespace, 1, creation_info) assert relationship == Relationship( f"{document_namespace}#SPDXRef-Relationship-1", f"{document_namespace}#{spdx2_relationship.spdx_element_id}", RelationshipType.DESCRIBES, + creation_info, [f"{document_namespace}#{spdx2_relationship.related_spdx_element_id}"], comment=spdx2_relationship.comment, ) @@ -30,12 +33,14 @@ def test_relationships_bump(): ] payload = Payload() document_namespace = "https://doc.namespace" - bump_relationships(relationships, payload, document_namespace) + creation_info = creation_info_fixture() + bump_relationships(relationships, payload, document_namespace, creation_info) assert payload.get_element(f"{document_namespace}#SPDXRef-Relationship-1") == Relationship( f"{document_namespace}#SPDXRef-Relationship-1", f"{document_namespace}#{relationships[0].spdx_element_id}", RelationshipType.DESCRIBES, + creation_info, [ f"{document_namespace}#{relationships[0].related_spdx_element_id}", f"{document_namespace}#{relationships[1].related_spdx_element_id}", @@ -55,28 +60,31 @@ def test_relationships_bump_with_setting_completeness(): ] payload = Payload() document_namespace = "https://doc.namespace" - bump_relationships(relationships, payload, document_namespace) + creation_info = creation_info_fixture() + bump_relationships(relationships, payload, document_namespace, creation_info) assert payload.get_element(f"{document_namespace}#SPDXRef-Relationship-0") == Relationship( f"{document_namespace}#SPDXRef-Relationship-0", f"{document_namespace}#{relationships[0].spdx_element_id}", RelationshipType.DESCRIBES, - [], + creation_info, comment=relationships[0].comment, - completeness=RelationshipCompleteness.NOASSERTION, + completeness=RelationshipCompleteness.NO_ASSERTION, ) assert payload.get_element(f"{document_namespace}#SPDXRef-Relationship-1") == Relationship( f"{document_namespace}#SPDXRef-Relationship-1", f"{document_namespace}#{relationships[1].spdx_element_id}", RelationshipType.DESCRIBES, + creation_info, [f"{document_namespace}#{relationships[1].related_spdx_element_id}"], comment=relationships[1].comment, ) assert payload.get_element(f"{document_namespace}#SPDXRef-Relationship-2") == Relationship( f"{document_namespace}#SPDXRef-Relationship-2", - f"{document_namespace}#{relationships[2].spdx_element_id}", - RelationshipType.SPECIFICATION_FOR, - [], + "NoneElement", + RelationshipType.HAS_SPECIFICATION, + creation_info, + [f"{document_namespace}#{relationships[2].spdx_element_id}"], completeness=RelationshipCompleteness.COMPLETE, ) @@ -90,7 +98,8 @@ def test_undefined_relationship_bump(capsys): ] payload = Payload() document_namespace = "https://doc.namespace" - bump_relationships(relationships, payload, document_namespace) + creation_info = creation_info_fixture() + bump_relationships(relationships, payload, document_namespace, creation_info) captured = capsys.readouterr() assert ( diff --git a/tests/spdx3/bump/test_snippet_bump.py b/tests/spdx3/bump/test_snippet_bump.py index 1ded094b6..6cc323c54 100644 --- a/tests/spdx3/bump/test_snippet_bump.py +++ b/tests/spdx3/bump/test_snippet_bump.py @@ -3,22 +3,29 @@ # SPDX-License-Identifier: Apache-2.0 from spdx_tools.spdx3.bump_from_spdx2.snippet import bump_snippet +from spdx_tools.spdx3.model.creation_info import CreationInfo from spdx_tools.spdx3.model.software import Snippet from spdx_tools.spdx3.payload import Payload +from spdx_tools.spdx.model import File from spdx_tools.spdx.model.snippet import Snippet as Spdx2_Snippet -from tests.spdx.fixtures import snippet_fixture +from tests.spdx3.fixtures import creation_info_fixture +from tests.spdx.fixtures import file_fixture, snippet_fixture def test_bump_snippet(): payload = Payload() document_namespace = "https://doc.namespace" spdx2_snippet: Spdx2_Snippet = snippet_fixture() + spdx2_file: File = file_fixture() expected_new_snippet_id = f"{document_namespace}#{spdx2_snippet.spdx_id}" + expected_new_snippet_file_id = f"{document_namespace}#{spdx2_file.spdx_id}" + creation_info: CreationInfo = creation_info_fixture() - bump_snippet(spdx2_snippet, payload, document_namespace, [], []) + bump_snippet(spdx2_snippet, spdx2_file, payload, document_namespace, [], [], creation_info) snippet = payload.get_element(expected_new_snippet_id) assert isinstance(snippet, Snippet) assert snippet.spdx_id == expected_new_snippet_id + assert snippet.snippet_from_file == expected_new_snippet_file_id assert snippet.copyright_text == spdx2_snippet.copyright_text - assert snippet.attribution_text == spdx2_snippet.attribution_texts[0] + assert snippet.attribution_text == spdx2_snippet.attribution_texts diff --git a/tests/spdx3/data/appbomination.spdx.json b/tests/spdx3/data/appbomination.spdx.json new file mode 100644 index 000000000..8994365d1 --- /dev/null +++ b/tests/spdx3/data/appbomination.spdx.json @@ -0,0 +1,852 @@ +{ + "@context" : "https://spdx.org/rdf/3.0.1/spdx-context.jsonld", + "@graph" : [ { + "@id" : "_:creationInfo_0", + "type" : "CreationInfo", + "specVersion" : "3.0.1", + "createdBy" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd0" ], + "createdUsing" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/additionalToolSPDXRef-gnrtd1" ], + "created" : "2021-09-02T13:46:32Z" + }, { + "@id" : "_:creationInfo_1", + "type" : "CreationInfo", + "specVersion" : "3.0.1", + "createdBy" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd71" ], + "createdUsing" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/additionalToolSPDXRef-gnrtd72" ], + "created" : "2021-09-02T13:46:32Z" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd2", + "type" : "Relationship", + "relationshipType" : "describes", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/document0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd4", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd5" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd6", + "type" : "Relationship", + "relationshipType" : "contains", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd5" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd7", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd5", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd9", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd5", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd11", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd12" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd13", + "type" : "Relationship", + "relationshipType" : "contains", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd12" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd14", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd12", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd15", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd12", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd16", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd17" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd18", + "type" : "Relationship", + "relationshipType" : "contains", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd17" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd19", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd17", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd20", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd17", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd21", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd22" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd23", + "type" : "Relationship", + "relationshipType" : "contains", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd22" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd24", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd22", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd25", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd22", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd26", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd27" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd28", + "type" : "Relationship", + "relationshipType" : "contains", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd27" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd29", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd30", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd31" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd29", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd32", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd31" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd29", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd33", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd31" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd27", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd34", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd27", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd35", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd36" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd37", + "type" : "Relationship", + "relationshipType" : "contains", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd36" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd38", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd39", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd38", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd40", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd38", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd41", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd36", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd42", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd36", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd43", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd44" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd45", + "type" : "Relationship", + "relationshipType" : "contains", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd44" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd38", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd46", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd44", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd47", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd44", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd48", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd49" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd50", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd51" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd49", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd52", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd49", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd53", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd54" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd55", + "type" : "Relationship", + "relationshipType" : "hasMetadata", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd54" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd56", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd57" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd54", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd58", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd54", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd59", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd60" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd61", + "type" : "Relationship", + "relationshipType" : "contains", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd60" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd62", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd60", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd63", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd60", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd64", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd65" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd68", + "type" : "Relationship", + "relationshipType" : "hasDistributionArtifact", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd67" ], + "completeness" : "complete", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd70", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd75", + "type" : "Relationship", + "relationshipType" : "dependsOn", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd74" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd76", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd78", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd79" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd76", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd81", + "type" : "Relationship", + "relationshipType" : "hasDistributionArtifact", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd80" ], + "completeness" : "complete", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd76", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd82", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd79" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd76", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd83", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd84" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd74", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd86", + "type" : "Relationship", + "relationshipType" : "hasDistributionArtifact", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd85" ], + "completeness" : "complete", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd74", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd87", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd74", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd89", + "type" : "Relationship", + "relationshipType" : "dependsOn", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd88" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd90", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd88", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd92", + "type" : "Relationship", + "relationshipType" : "hasDistributionArtifact", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd91" ], + "completeness" : "complete", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd88", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd93", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd88", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd95", + "type" : "Relationship", + "relationshipType" : "dependsOn", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd94" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd96", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd94", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd98", + "type" : "Relationship", + "relationshipType" : "hasDistributionArtifact", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd97" ], + "completeness" : "complete", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd94", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd99", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10" ], + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd94", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/document0", + "type" : "SpdxDocument", + "dataLicense" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd73", + "rootElement" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3" ], + "name" : "SpdxDoc for App-BOM-ination", + "creationInfo" : "_:creationInfo_1" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/additionalToolSPDXRef-gnrtd1", + "type" : "Tool", + "name" : "Source Auditor Open Source Console", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/additionalToolSPDXRef-gnrtd72", + "type" : "Tool", + "name" : "Source Auditor Open Source Console", + "creationInfo" : "_:creationInfo_1" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd8", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "Apache-2.0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd10", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "NOASSERTION", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd31", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "LicenseRef-1", + "simplelicensing_customIdToUri" : [ { + "type" : "DictionaryEntry", + "value" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/LicenseRef-1", + "key" : "LicenseRef-1" + } ], + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd51", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "(Apache-2.0 AND LicenseRef-1)", + "simplelicensing_customIdToUri" : [ { + "type" : "DictionaryEntry", + "value" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/LicenseRef-1", + "key" : "LicenseRef-1" + } ], + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd57", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "(LGPL-2.0-or-later OR WTFPL)", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd65", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "(Apache-2.0 AND LicenseRef-1)", + "simplelicensing_customIdToUri" : [ { + "type" : "DictionaryEntry", + "value" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/LicenseRef-1", + "key" : "LicenseRef-1" + } ], + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd73", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "CC0-1.0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd79", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "EPL-1.0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd84", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "BSD-3-Clause", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd77", + "type" : "LifecycleScopedRelationship", + "relationshipType" : "dependsOn", + "scope" : "test", + "to" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd76" ], + "completeness" : "noAssertion", + "from" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd69", + "type" : "Organization", + "name" : "ACT Project", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd0", + "type" : "Person", + "name" : "Gary O'Neall", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd66", + "type" : "Person", + "name" : "Yevester", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd71", + "type" : "Person", + "name" : "Gary O'Neall", + "creationInfo" : "_:creationInfo_1" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd5", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "92170cdc034b2ff819323ff670d3b7266c8bffcd" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/LICENSE", + "software_primaryPurpose" : "other", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd12", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "1458a5c5fb1189d2cc8212052975f39ae710d622" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/settings.gradle", + "software_primaryPurpose" : "other", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd17", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "340e8b696bc50d76cf50df943dbaf46591da9ef4" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/logo.png", + "contentType" : "application/octet-stream", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd22", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "2b7b936a3f185a53528724e4f4141030906963c2" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/src/main/java/com/github/appbomination/Main.java", + "software_primaryPurpose" : "source", + "comment" : "Seen licenses generated by Source Auditor Scanner. Results should be manually verified.", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd27", + "type" : "software_File", + "software_copyrightText" : "Copyright Faust Inc. All, and I mean ALL, rights are reserved", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "fd668bc0096794e4d8125a29f9a746c0ab1edc57" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/src/main/java/com/github/appbomination/InsufficientKarmaException.java", + "software_primaryPurpose" : "source", + "comment" : "BOMNOTE:File|Matched Notice='Faust Proprietary Notice'|", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd36", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "d841ffc9855dcc642901e8abf28dee20b0485864" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/gradlew.bat", + "software_primaryPurpose" : "source", + "comment" : "BOMNOTE:File|", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd44", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "b86a8c3bab5a3ed0441b3fe3b1f6b31ec1ead901" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/gradlew", + "software_primaryPurpose" : "other", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd49", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "67174de726d5caae455cd22e9c4450e9c490ac6b" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/gradle/wrapper/gradle-wrapper.properties", + "software_primaryPurpose" : "other", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd54", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "9c55f0e3bd70363a02377f729d139a5d91325d40" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/build.gradle", + "software_primaryPurpose" : "other", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd60", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "15399fcbbe6f3ff84c82039d446d820ecbbc3ac6" + } ], + "software_attributionText" : [ "NOASSERTION" ], + "name" : "./App-BOM-ination-1.0/README.md", + "software_primaryPurpose" : "other", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd67", + "type" : "software_File", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "498cebd51a4483d6e68c2fc62d27008252fa4f7b" + } ], + "name" : "App-BOM-ination-1.0.zip", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd80", + "type" : "software_File", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "4e6be5af63e3373b4f0cbc4c151c13e059151e00" + } ], + "name" : "junit-4.12.jar", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd85", + "type" : "software_File", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "42a25dc3219429f0e5d060061f71acb49bf010a0" + } ], + "name" : "hamcrest-core-1.3.jar", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd91", + "type" : "software_File", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "5fe28b9518e58819180a43a850fbc0dd24b7c050" + } ], + "name" : "commons-lang3-3.4.jar", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd97", + "type" : "software_File", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "466308a5554594190da5fe2c86b4a8e5037c37cc" + } ], + "name" : "slf4j-api-1.7.21.jar", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd3", + "type" : "software_Package", + "software_copyrightText" : "Copyright (c) 2016 Faust, Inc.", + "suppliedBy" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd69", + "verifiedUsing" : [ { + "type" : "PackageVerificationCode", + "algorithm" : "sha1", + "hashValue" : "be2fb65c6b22b81f1b273442d70479a41a3093e7" + } ], + "name" : "App-BOM-ination", + "software_sourceInfo" : "", + "comment" : "Faust proprietary notice was found in one or more source files. LGPL-2.0-or-later OR WTFPL was in a build configuration file and does not apply to the concluded license.", + "software_downloadLocation" : "https://github.com/act-project/App-BOM-ination/archive/refs/tags/1.0.zip", + "summary" : "A uniquely useless project with a cataclysmic software supply chain, to serve a test case for BOM solutions.", + "software_packageVersion" : "1.0", + "software_homePage" : "https://github.com/act-project/App-BOM-ination", + "description" : "A uniquely useless project with a cataclysmic software supply chain, to serve a test case for BOM solutions.", + "originatedBy" : [ "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd66" ], + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd29", + "type" : "software_Package", + "software_copyrightText" : "NOASSERTION", + "software_downloadLocation" : "NOASSERTION", + "summary" : "Files containing a Faust Proprietary Notice", + "software_homePage" : "https://github.com/act-project/App-BOM-ination/blob/master/src/main/java/com/github/appbomination/InsufficientKarmaException.java", + "description" : "Files containing a Faust Proprietary Notice", + "name" : "Faust Proprietary File", + "comment" : "Package info generated from Source Auditor package database", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd38", + "type" : "software_Package", + "software_copyrightText" : "NOASSERTION", + "software_downloadLocation" : "NOASSERTION", + "software_homePage" : "https://gradle.org/", + "name" : "Gradle", + "comment" : "Package info generated from Source Auditor package database", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd74", + "type" : "software_Package", + "software_copyrightText" : "NOASSERTION", + "software_downloadLocation" : "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar", + "software_packageVersion" : "1.3", + "name" : "hamcrest-core", + "comment" : "Package info from Maven Central POM file", + "software_packageUrl" : "pkg:maven/org.hamcrest/hamcrest-core@1.3", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd76", + "type" : "software_Package", + "software_copyrightText" : "NOASSERTION", + "software_downloadLocation" : "https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar", + "software_packageVersion" : "4.12", + "software_homePage" : "http://junit.org", + "name" : "junit", + "comment" : "Package info from Maven Central POM file", + "software_packageUrl" : "pkg:maven/junit/junit@4.12", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd88", + "type" : "software_Package", + "software_copyrightText" : "NOASSERTION", + "software_downloadLocation" : "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.4/commons-lang3-3.4.jar", + "software_packageVersion" : "3.4", + "software_homePage" : "http://commons.apache.org/proper/commons-lang/", + "name" : "commons-lang3", + "comment" : "Package info from Maven Central POM file", + "software_packageUrl" : "pkg:maven/org.apache.commons/commons-lang3@3.4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://www.sourceauditor.com/spdxdocs/appbomination-src/e3b71037-57de-44c9-8b7f-4e8a62f45311-specv3/SPDXRef-gnrtd94", + "type" : "software_Package", + "software_copyrightText" : "NOASSERTION", + "software_downloadLocation" : "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar", + "software_packageVersion" : "1.7.21", + "software_homePage" : "http://www.slf4j.org", + "name" : "slf4j-api", + "comment" : "Package info from Maven Central POM file", + "software_packageUrl" : "pkg:maven/org.slf4j/slf4j-api@1.7.21", + "creationInfo" : "_:creationInfo_0" + } ] +} \ No newline at end of file diff --git a/tests/spdx3/data/example6-bin.json b/tests/spdx3/data/example6-bin.json new file mode 100644 index 000000000..c45255e1e --- /dev/null +++ b/tests/spdx3/data/example6-bin.json @@ -0,0 +1,268 @@ +{ + "@context" : "https://spdx.org/rdf/3.0.1/spdx-context.jsonld", + "@graph" : [ { + "@id" : "_:creationInfo_0", + "type" : "CreationInfo", + "specVersion" : "3.0.1", + "createdBy" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd0" ], + "createdUsing" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/additionalToolSPDXRef-gnrtd1", "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/additionalToolSPDXRef-gnrtd2" ], + "created" : "2021-08-26T01:56:00Z" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd3", + "type" : "Relationship", + "relationshipType" : "describes", + "completeness" : "noAssertion", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4" ], + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/document0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd5", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd6" ], + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd7", + "type" : "Relationship", + "relationshipType" : "generates", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd6" ], + "completeness" : "noAssertion", + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-src-v2#SPDXRef-hello-go-src", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd8", + "type" : "Relationship", + "relationshipType" : "generates", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd6" ], + "completeness" : "noAssertion", + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-src-v2#SPDXRef-Makefile", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd9", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd10" ], + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd6", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd11", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd12" ], + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd6", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd13", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd14" ], + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd15", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd12" ], + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd151", + "type" : "LifecycleScopedRelationship", + "scope" : "build", + "relationshipType" : "usesTool", + "to" : [ "https://swinslow.net/spdx-examples/example6/go-lib-v2#SPDXRef-Package-go-compiler" ], + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd152", + "type" : "LifecycleScopedRelationship", + "relationshipType" : "dependsOn", + "scope" : "runtime", + "to" : [ "https://swinslow.net/spdx-examples/example6/go-lib-v2#SPDXRef-Package-go.fmt" ], + "from" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd153", + "type" : "Relationship", + "relationshipType" : "hasStaticLink", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4" ], + "from" : "https://swinslow.net/spdx-examples/example6/go-lib-v2#SPDXRef-Package-go.fmt", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd154", + "type" : "Relationship", + "relationshipType" : "hasStaticLink", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4" ], + "from" : "https://swinslow.net/spdx-examples/example6/go-lib-v2#SPDXRef-Package-go.reflect", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd155", + "type" : "Relationship", + "relationshipType" : "hasStaticLink", + "to" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4" ], + "from" : "https://swinslow.net/spdx-examples/example6/go-lib-v2#SPDXRef-Package-go.strconv", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/document0", + "type" : "SpdxDocument", + "dataLicense" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd16", + "rootElement" : [ "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4" ], + "import" : [ { + "type" : "ExternalMap", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "58e4a6d5745f032b9788142e49edee1b508c7ac5" + } ], + "externalSpdxId" : "https://swinslow.net/spdx-examples/example6/go-lib-v2#SPDXRef-Package-go-compiler", + "locationHint" : "https://swinslow.net/spdx-examples/example6/go-lib-v2" + }, { + "type" : "ExternalMap", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "58e4a6d5745f032b9788142e49edee1b508c7ac5" + } ], + "externalSpdxId" : "https://swinslow.net/spdx-examples/example6/go-lib-v2#SPDXRef-Package-go.fmt", + "locationHint" : "https://swinslow.net/spdx-examples/example6/go-lib-v2" + }, { + "type" : "ExternalMap", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "58e4a6d5745f032b9788142e49edee1b508c7ac5" + } ], + "externalSpdxId" : "https://swinslow.net/spdx-examples/example6/go-lib-v2#SPDXRef-Package-go.reflect", + "locationHint" : "https://swinslow.net/spdx-examples/example6/go-lib-v2" + }, { + "type" : "ExternalMap", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "58e4a6d5745f032b9788142e49edee1b508c7ac5" + } ], + "externalSpdxId" : "https://swinslow.net/spdx-examples/example6/go-lib-v2#SPDXRef-Package-go.strconv", + "locationHint" : "https://swinslow.net/spdx-examples/example6/go-lib-v2" + }, { + "type" : "ExternalMap", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "b3018ddb18802a56b60ad839c98d279687b60bd6" + } ], + "externalSpdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-src-v2#SPDXRef-hello-go-src", + "locationHint" : "https://swinslow.net/spdx-examples/example6/hello-go-src-v2" + }, { + "type" : "ExternalMap", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "b3018ddb18802a56b60ad839c98d279687b60bd6" + } ], + "externalSpdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-src-v2#SPDXRef-Makefile", + "locationHint" : "https://swinslow.net/spdx-examples/example6/hello-go-src-v2" + } ], + "name" : "hello-go-bin", + "namespaceMap" : [ { + "type" : "NamespaceMap", + "prefix" : "DocumentRef-go-lib", + "namespace" : "https://swinslow.net/spdx-examples/example6/go-lib-v2#" + }, { + "type" : "NamespaceMap", + "prefix" : "DocumentRef-hello-go-src", + "namespace" : "https://swinslow.net/spdx-examples/example6/hello-go-src-v2#" + } ], + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/additionalToolSPDXRef-gnrtd1", + "type" : "Tool", + "name" : "github.com/spdx/tools-golang/builder", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/additionalToolSPDXRef-gnrtd2", + "type" : "Tool", + "name" : "github.com/spdx/tools-golang/idsearcher", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd10", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "(GPL-3.0-or-later AND LicenseRef-Golang-BSD-plus-Patents)", + "simplelicensing_customIdToUri" : [ { + "type" : "DictionaryEntry", + "value" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/LicenseRef-Golang-BSD-plus-Patents", + "key" : "LicenseRef-Golang-BSD-plus-Patents" + } ], + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd12", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "NOASSERTION", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd14", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "(GPL-3.0-or-later AND LicenseRef-Golang-BSD-plus-Patents)", + "simplelicensing_customIdToUri" : [ { + "type" : "DictionaryEntry", + "value" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/LicenseRef-Golang-BSD-plus-Patents", + "key" : "LicenseRef-Golang-BSD-plus-Patents" + } ], + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd16", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "CC0-1.0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd0", + "type" : "Person", + "externalIdentifier" : [ { + "type" : "ExternalIdentifier", + "identifier" : "steve@swinslow.net", + "externalIdentifierType" : "email" + } ], + "name" : "Steve Winslow", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd17", + "type" : "expandedlicensing_CustomLicense", + "expandedlicensing_seeAlso" : [ "https://github.com/golang/go/blob/master/LICENSE", "https://github.com/golang/go/blob/master/PATENTS" ], + "simplelicensing_licenseText" : "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nAdditional IP Rights Grant (Patents)\n\n\"This implementation\" means the copyrightable works distributed by\nGoogle as part of the Go project.\n\nGoogle hereby grants to You a perpetual, worldwide, non-exclusive,\nno-charge, royalty-free, irrevocable (except as stated in this section)\npatent license to make, have made, use, offer to sell, sell, import,\ntransfer and otherwise run, modify and propagate the contents of this\nimplementation of Go, where such license applies only to those patent\nclaims, both currently owned or controlled by Google and acquired in\nthe future, licensable by Google that are necessarily infringed by this\nimplementation of Go. This grant does not include claims that would be\ninfringed only as a consequence of further modification of this\nimplementation. If you or your agent or exclusive licensee institute or\norder or agree to the institution of patent litigation against any\nentity (including a cross-claim or counterclaim in a lawsuit) alleging\nthat this implementation of Go or any code incorporated within this\nimplementation of Go constitutes direct or contributory patent\ninfringement, or inducement of patent infringement, then any patent\nrights granted to you under this License for this implementation of Go\nshall terminate as of the date such litigation is filed.", + "name" : "Golang BSD-plus-PATENTS", + "comment" : "The Golang license text is split across two files, with the BSD-3-Clause content in LICENSE and the Additional IP Rights Grant in PATENTS.", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd6", + "type" : "software_File", + "software_copyrightText" : "NOASSERTION", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "md5", + "hashValue" : "9ec63d68bdceb2922548e3faa377e7d0" + }, { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "78ed46e8e6f86f19d3a6782979029be5f918235f" + }, { + "type" : "Hash", + "algorithm" : "sha256", + "hashValue" : "3d51cb6c9a38d437e8ee20a1902a15875ea1d3771a215622e14739532be14949" + } ], + "name" : "./hello", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4", + "type" : "software_Package", + "software_copyrightText" : "NOASSERTION", + "software_downloadLocation" : "git+https://github.com/swinslow/spdx-examples.git#example6/content/build", + "verifiedUsing" : [ { + "type" : "PackageVerificationCode", + "algorithm" : "sha1", + "hashValue" : "41acac4b846ee388cb6c1234f04489ccd5daa5a5" + } ], + "name" : "hello-go-bin", + "creationInfo" : "_:creationInfo_0" + } ] +} \ No newline at end of file diff --git a/tests/spdx3/data/spdxV3-example.json b/tests/spdx3/data/spdxV3-example.json new file mode 100644 index 000000000..95fdb6a26 --- /dev/null +++ b/tests/spdx3/data/spdxV3-example.json @@ -0,0 +1,226 @@ +{ + "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld", + "@graph": [ + { + "type": "Person", + "spdxId": "urn:jane-doe-1@acme.com-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "name": "Application Owner Jane Doe", + "externalIdentifier": [ + { + "type": "ExternalIdentifier", + "externalIdentifierType": "email", + "identifier": "jane-doe-1@acme.com" + } + ] + }, + { + "type": "Organization", + "spdxId": "urn:acme.com-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "name": "Acme Company" + }, + { + "type": "Person", + "spdxId": "urn:github.com-indutny-c4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "name": "Fedor Indutny", + "externalIdentifier": [ + { + "type": "ExternalIdentifier", + "externalIdentifierType": "other", + "identifier": "https://github.com/indutny" + } + ] + }, + { + "type": "Organization", + "spdxId": "urn:github.com-alpinelinux-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "name": "Alpine Linux" + }, + { + "type": "CreationInfo", + "@id": "_:creationinfo", + "specVersion": "3.0.1", + "createdBy": [ + "urn:jane-doe-1@acme.com-4fe40e24-20e3-11ee-be56-0242ac120002", + "urn:acme.com-4fe40e24-20e3-11ee-be56-0242ac120002" + ], + "created": "2024-05-02T00:00:00Z" + }, + { + "type": "SpdxDocument", + "spdxId": "http://spdx.example.com/Document1", + "creationInfo": "_:creationinfo", + "profileConformance": [ + "core", + "software" + ], + "rootElement": [ + "urn:example13-sbom.com-4fe40e24-20e3-11ee-be56-0242ac120002" + ] + }, + { + "type": "software_Sbom", + "spdxId": "urn:example13-sbom.com-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "profileConformance": [ + "core", + "software" + ], + "rootElement": [ + "urn:product-acme-application-1.3-4fe40e24-20e3-11ee-be56-0242ac120002" + ] + }, + { + "type": "software_Package", + "spdxId": "urn:product-acme-application-1.3-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "name": "Acme Application", + "software_packageVersion": "1.3", + "suppliedBy": "urn:acme.com-4fe40e24-20e3-11ee-be56-0242ac120002", + "software_primaryPurpose": "application", + "externalIdentifier": [ + { + "type": "ExternalIdentifier", + "externalIdentifierType": "cpe23", + "identifier": "cpe:2.3:a:*:acme-application:1.3:*:*:*:*:*:*:*" + }, + { + "type": "ExternalIdentifier", + "externalIdentifierType": "packageUrl", + "identifier": "pkg:generic/acme-application@1.3" + } + ], + "externalRef": [ + { + "type": "ExternalRef", + "comment": "comment1", + "locator": [ + "locator1" + ] + }, + { + "type": "ExternalRef", + "comment": "comment2", + "locator": [ + "locator2" + ] + }, + { + "type": "ExternalRef", + "comment": "comment3", + "locator": [ + "locator3" + ] + } + ], + "extension": [ + { + "type": "extension_CdxPropertiesExtension", + "extension_cdxProperty": [ + { + "type": "extension_CdxPropertyEntry", + "extension_cdxPropName": "example name", + "extension_cdxPropValue": "example value" + } + ] + } + ] + }, + { + "type": "software_Package", + "spdxId": "urn:npm-elliptic-6.5.2-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "name": "npm-elliptic", + "software_packageVersion": "6.5.2", + "suppliedBy": "urn:github.com-indutny-c4fe40e24-20e3-11ee-be56-0242ac120002", + "software_primaryPurpose": "library", + "externalIdentifier": [ + { + "type": "ExternalIdentifier", + "externalIdentifierType": "other", + "identifier": "https://github.com/indutny/elliptic/releases/tag/v6.5.2" + } + ] + }, + { + "type": "software_Package", + "spdxId": "urn:container-alpine-latest-sha256:69665d02cb32192e52e07644d76bc6f25abeb5410edc1c7a81a10ba3f0efb90a-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "name": "alpine:latest", + "software_packageVersion": "69665d02cb32192e52e07644d76bc6f25abeb5410edc1c7a81a10ba3f0efb90a", + "suppliedBy": "urn:github.com-alpinelinux-4fe40e24-20e3-11ee-be56-0242ac120002", + "software_primaryPurpose": "container" + }, + { + "type": "software_Package", + "spdxId": "urn:openssl-3.0.4-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "name": "openssl", + "software_packageVersion": "3.0.4", + "software_primaryPurpose": "library" + }, + { + "type": "Relationship", + "spdxId": "urn:acme-relationship-1-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "from": "urn:product-acme-application-1.3-4fe40e24-20e3-11ee-be56-0242ac120002", + "to": [ + "urn:jane-doe-1@acme.com-4fe40e24-20e3-11ee-be56-0242ac120002" + ], + "relationshipType": "availableFrom" + }, + { + "type": "Relationship", + "spdxId": "urn:acme-relationship-2-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "from": "urn:product-acme-application-1.3-4fe40e24-20e3-11ee-be56-0242ac120002", + "to": [ + "urn:npm-elliptic-6.5.2-4fe40e24-20e3-11ee-be56-0242ac120002" + ], + "relationshipType": "contains" + }, + { + "type": "Relationship", + "spdxId": "urn:acme-relationship-3-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "from": "urn:product-acme-application-1.3-4fe40e24-20e3-11ee-be56-0242ac120002", + "to": [ + "urn:container-alpine-latest-sha256:69665d02cb32192e52e07644d76bc6f25abeb5410edc1c7a81a10ba3f0efb90a-4fe40e24-20e3-11ee-be56-0242ac120002" + ], + "relationshipType": "dependsOn" + }, + { + "type": "Relationship", + "spdxId": "urn:acme-relationship-4-4fe40e24-20e3-11ee-be56-0242ac120002", + "creationInfo": "_:creationinfo", + "from": "urn:container-alpine-latest-sha256:69665d02cb32192e52e07644d76bc6f25abeb5410edc1c7a81a10ba3f0efb90a-4fe40e24-20e3-11ee-be56-0242ac120002", + "to": [ + "urn:openssl-3.0.4-4fe40e24-20e3-11ee-be56-0242ac120002" + ], + "relationshipType": "contains" + }, + { + "type": "dataset_DatasetPackage", + "spdxId": "urn:example13-sbom.com-5c49bc40-6c26-486f-aa11-f462bbfd1b0e", + "creationInfo": "_:creationinfo", + "name": "Example Dataset Package", + "builtTime": "2026-01-01T13:44:44Z", + "originatedBy": [ + "urn:acme.com-4fe40e24-20e3-11ee-be56-0242ac120002" + ], + "releaseTime": "2026-01-01T13:44:44Z", + "software_primaryPurpose": "data", + "software_downloadLocation": "https://github.com/indutny", + "dataset_datasetType": [ + "structured", + "timestamp" + ], + "dataset_datasetUpdateMechanism": "Publish updated data as it becomes available. Most are on an annual basis.", + "dataset_hasSensitivePersonalInformation": "no", + "dataset_datasetSize": 1024 + } + ] +} \ No newline at end of file diff --git a/tests/spdx3/fixtures.py b/tests/spdx3/fixtures.py index 9d7e5fd24..4aa6a3b86 100644 --- a/tests/spdx3/fixtures.py +++ b/tests/spdx3/fixtures.py @@ -20,6 +20,7 @@ ExternalReferenceType, Hash, HashAlgorithm, + IntegrityMethod, LifecycleScopedRelationship, LifecycleScopeType, NamespaceMap, @@ -34,23 +35,35 @@ Tool, ) from spdx_tools.spdx3.model.ai.ai_package import AIPackage, SafetyRiskAssessmentType +from spdx_tools.spdx3.model.ai.energy_consumption import EnergyConsumption +from spdx_tools.spdx3.model.ai.energy_consumption_description import EnergyConsumptionDescription +from spdx_tools.spdx3.model.ai.energy_unit_type import EnergyUnitType +from spdx_tools.spdx3.model.artifact import SupportType from spdx_tools.spdx3.model.build import Build -from spdx_tools.spdx3.model.dataset.dataset import ( +from spdx_tools.spdx3.model.dataset.dataset_package import ( ConfidentialityLevelType, - Dataset, DatasetAvailabilityType, + DatasetPackage, DatasetType, ) -from spdx_tools.spdx3.model.licensing import ( +from spdx_tools.spdx3.model.expandedlicensing import ( CustomLicense, CustomLicenseAddition, ListedLicense, ListedLicenseException, ) +from spdx_tools.spdx3.model.expandedlicensing.conjunctive_license_set import ConjunctiveLicenseSet +from spdx_tools.spdx3.model.expandedlicensing.disjunctive_license_set import DisjunctiveLicenseSet +from spdx_tools.spdx3.model.expandedlicensing.or_later_operator import OrLaterOperator +from spdx_tools.spdx3.model.expandedlicensing.with_addition_operator import WithAdditionOperator +from spdx_tools.spdx3.model.extension.cdx_properties_extension import CdxPropertiesExtension +from spdx_tools.spdx3.model.extension.cdx_property_entry import CdxPropertyEntry from spdx_tools.spdx3.model.positive_integer_range import PositiveIntegerRange +from spdx_tools.spdx3.model.presence_type import PresenceType from spdx_tools.spdx3.model.security import ( CvssV2VulnAssessmentRelationship, CvssV3VulnAssessmentRelationship, + CvssV4VulnAssessmentRelationship, EpssVulnAssessmentRelationship, ExploitCatalogType, ExploitCatalogVulnAssessmentRelationship, @@ -63,46 +76,39 @@ VexUnderInvestigationVulnAssessmentRelationship, Vulnerability, ) +from spdx_tools.spdx3.model.security.cvss_severity_type import CvssSeverityType +from spdx_tools.spdx3.model.simplelicensing.license_expression import LicenseExpression +from spdx_tools.spdx3.model.simplelicensing.simple_licensing_text import SimpleLicensingText from spdx_tools.spdx3.model.software import ( - DependencyConditionalityType, File, Package, Sbom, - SBOMType, + SbomType, Snippet, - SoftwareDependencyLinkType, - SoftwareDependencyRelationship, SoftwarePurpose, ) +from spdx_tools.spdx3.model.software.content_identifier import ContentIdentifier, ContentIdentifierType +from spdx_tools.spdx3.model.software.file import FileKindType """Utility methods to create data model instances. All properties have valid defaults, so they don't need to be specified unless relevant for the test.""" def creation_info_fixture( - spec_version=Version("3.0.0"), + spec_version=Version("3.0.1"), created=datetime(2022, 12, 1), created_by=None, created_using=None, - profile=None, - data_license="CC0-1.0", comment="creationInfoComment", ) -> CreationInfo: created_by = ["https://spdx.test/tools-python/creation_info_created_by"] if created_by is None else created_by created_using = ( ["https://spdx.test/tools-python/creation_info_created_using"] if created_using is None else created_using ) - profile = ( - [ProfileIdentifierType.CORE, ProfileIdentifierType.SOFTWARE, ProfileIdentifierType.LICENSING] - if profile is None - else profile - ) return CreationInfo( spec_version=spec_version, created=created, created_by=created_by, - profile=profile, - data_license=data_license, created_using=created_using, comment=comment, ) @@ -132,7 +138,7 @@ def external_identifier_fixture( def external_reference_fixture( external_reference_type=ExternalReferenceType.OTHER, locator=None, - content_type="externalReferenceContentType", + content_type="text/plain", comment="externalReferenceComment", ) -> ExternalReference: locator = ["org.apache.tomcat:tomcat:9.0.0.M4"] if locator is None else locator @@ -151,16 +157,16 @@ def hash_fixture( def external_map_fixture( external_id="https://spdx.test/tools-python/external_map_external_id", - verified_using=None, + verified_using: list[IntegrityMethod] | None = None, location_hint="https://spdx.test/tools-python/external_map_location_hint", - defining_document="https://spdx.test/tools-python/defining_document", + defining_artifact="https://spdx.test/tools-python/defining_artifact", ) -> ExternalMap: verified_using = [hash_fixture()] if verified_using is None else verified_using return ExternalMap( - external_id=external_id, + external_spdx_id=external_id, verified_using=verified_using, location_hint=location_hint, - defining_document=defining_document, + defining_artifact=defining_artifact, ) @@ -170,36 +176,25 @@ def namespace_map_fixture( return NamespaceMap(prefix=prefix, namespace=namespace) -def listed_license_fixture( - license_id="https://spdx.test/tools-python/license_id", - license_name="license name", - license_text="license text", - license_comment="license comment", - see_also=None, - is_osi_approved=True, - is_fsf_libre=True, - standard_license_header="license header", - standard_license_template="license template", - is_deprecated_license_id=True, - obsoleted_by="https://spdx.test/tools-python/obsoleted_by_license_id", - list_version_added="2.1", - deprecated_version="2.2", -): - see_also = ["https://see.also/license"] if see_also is None else see_also - return ListedLicense( - license_id=license_id, - license_name=license_name, - license_text=license_text, - license_comment=license_comment, - see_also=see_also, - is_osi_approved=is_osi_approved, - is_fsf_libre=is_fsf_libre, - standard_license_header=standard_license_header, - standard_license_template=standard_license_template, - is_deprecated_license_id=is_deprecated_license_id, - obsoleted_by=obsoleted_by, - list_version_added=list_version_added, - deprecated_version=deprecated_version, +def energy_consumption_description_fixture( + energy_quantity=100.0, + energy_unit=EnergyUnitType.MEGAJOULE, +) -> EnergyConsumptionDescription: + return EnergyConsumptionDescription( + energy_quantity=energy_quantity, + energy_unit=energy_unit, + ) + + +def energy_consumption_fixture( + finetuning_energy_consumption=[energy_consumption_description_fixture()], + inference_energy_consumption=[energy_consumption_description_fixture()], + training_energy_consumption=[energy_consumption_description_fixture()], +) -> EnergyConsumption: + return EnergyConsumption( + finetuning_energy_consumption=finetuning_energy_consumption, + inference_energy_consumption=inference_energy_consumption, + training_energy_consumption=training_energy_consumption, ) @@ -213,7 +208,7 @@ def listed_license_fixture( "verified_using": [hash_fixture()], "external_reference": [external_reference_fixture()], "external_identifier": [external_identifier_fixture()], - "extension": "extensionPlaceholder", + "extension": [CdxPropertiesExtension(cdx_property=[CdxPropertyEntry("exampleName", "exampleValue")])], } RELATIONSHIP_DICT = { @@ -230,15 +225,14 @@ def listed_license_fixture( ANNOTATION_DICT = { "annotation_type": AnnotationType.OTHER, "subject": "https://spdx.test/tools-python/annotation_subject", - "content_type": ["annotationContent"], + "content_type": "text/plain", "statement": "annotationStatement", } ELEMENT_COLLECTION_DICT = { "element": ["https://spdx.test/tools-python/collection_element"], "root_element": ["https://spdx.test/tools-python/collection_root_element"], - "namespaces": [namespace_map_fixture()], - "imports": [external_map_fixture()], + "profile_conformance": [ProfileIdentifierType.CORE], } BUNDLE_DICT = { @@ -246,14 +240,11 @@ def listed_license_fixture( } SBOM_DICT = { - "sbom_type": [SBOMType.BUILD], + "sbom_type": [SbomType.BUILD], } LICENSE_DICT = { - "license_id": "https://spdx.test/tools-python/license_id", - "license_name": "license name", "license_text": "license text", - "license_comment": "license comment", "see_also": ["https://see.also/license"], "is_osi_approved": True, "is_fsf_libre": True, @@ -261,6 +252,7 @@ def listed_license_fixture( "standard_license_template": "license template", "is_deprecated_license_id": True, "obsoleted_by": "https://spdx.test/tools-python/obsoleted_by_license_id", + "license_xml": "license xml", } LISTED_LICENSE_DICT = { @@ -269,14 +261,12 @@ def listed_license_fixture( } LICENSE_ADDITION_DICT = { - "addition_id": "https://spdx.test/tools-python/addition_id", - "addition_name": "addition name", "addition_text": "addition text", - "addition_comment": "addition comment", - "see_also": ["https://see.also/addition"], - "standard_addition_template": "addition template", "is_deprecated_addition_id": True, + "license_xml": "license xml", "obsoleted_by": "https://spdx.test/tools-python/obsoleted_by_addition_id", + "see_also": ["https://see.also/addition"], + "standard_addition_template": "addition template", } LISTED_LICENSE_EXCEPTION_DICT = { @@ -284,6 +274,12 @@ def listed_license_fixture( "deprecated_version": "2.2", } +LICENSE_EXPRESSION_DICT = { + "custom_id_to_uri": {"CustomLicenseRef-1": "https://custom.license/1"}, + "license_expression": "(MIT AND CustomLicenseRef-1)", + "license_list_version": Version("3.19.0"), +} + VULNERABILITY_DICT = { "published_time": datetime(2010, 1, 1), "modified_time": datetime(2011, 1, 1), @@ -299,27 +295,28 @@ def listed_license_fixture( } CVSS_V2_VULN_ASSESSMENT_RELATIONSHIP_DICT = { - "score": "4.3", - "severity": "low", - "vector": "(AV:N/AC:M/Au:N/C:P/I:N/A:N)", + "score": 4.3, + "vector_string": "(AV:N/AC:M/Au:N/C:P/I:N/A:N)", "relationship_type": RelationshipType.HAS_ASSESSMENT_FOR, } CVSS_V3_VULN_ASSESSMENT_RELATIONSHIP_DICT = { - "score": "6.8", - "severity": "medium", - "vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N", + "score": 6.8, + "severity": CvssSeverityType.MEDIUM, + "vector_string": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N", "relationship_type": RelationshipType.HAS_ASSESSMENT_FOR, } -EPSS_VULN_ASSESSMENT_RELATIONSHIP_DICT = { - "probability": 80, - "severity": "high", +CVSS_V4_VULN_ASSESSMENT_RELATIONSHIP_DICT = { + "score": 6.8, + "severity": CvssSeverityType.MEDIUM, + "vector_string": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N", "relationship_type": RelationshipType.HAS_ASSESSMENT_FOR, } -SSVC_VULN_ASSESSMENT_RELATIONSHIP_DICT = { - "decision_type": SsvcDecisionType.ACT, +EPSS_VULN_ASSESSMENT_RELATIONSHIP_DICT = { + "percentile": 0.9, + "probability": 0.8, "relationship_type": RelationshipType.HAS_ASSESSMENT_FOR, } @@ -330,6 +327,11 @@ def listed_license_fixture( "relationship_type": RelationshipType.HAS_ASSESSMENT_FOR, } +SSVC_VULN_ASSESSMENT_RELATIONSHIP_DICT = { + "decision_type": SsvcDecisionType.ACT, + "relationship_type": RelationshipType.HAS_ASSESSMENT_FOR, +} + VEX_VULN_ASSESSMENT_RELATIONSHIP_DICT = { "vex_version": "v4.2", "status_notes": "some status notes", @@ -337,10 +339,14 @@ def listed_license_fixture( VEX_AFFECTED_VULN_ASSESSMENT_RELATIONSHIP_DICT = { "action_statement": "Upgrade to version 1.4 of ACME application.", - "action_statement_time": [datetime(2015, 10, 15)], + "action_statement_time": datetime(2015, 10, 15), "relationship_type": RelationshipType.AFFECTS, } +VEX_FIXED_VULN_ASSESSMENT_RELATIONSHIP_DICT = { + "relationship_type": RelationshipType.FIXED_IN, +} + VEX_NOT_AFFECTED_VULN_ASSESSMENT_RELATIONSHIP_DICT = { "justification_type": VexJustificationType.COMPONENT_NOT_PRESENT, "impact_statement": "Not using this vulnerable part of this library.", @@ -352,12 +358,19 @@ def listed_license_fixture( "relationship_type": RelationshipType.UNDER_INVESTIGATION_FOR, } -VEX_FIXED_VULN_ASSESSMENT_RELATIONSHIP_DICT = { - "relationship_type": RelationshipType.FIXED_IN, +ENERGY_CONSUMPTION_DESCRIPTION_DICT = { + "energy_quantity": 100.0, + "energy_unit": EnergyUnitType.MEGAJOULE, +} + +ENERGY_CONSUMPTION_DICT = { + "finetuning_energy_consumption": [energy_consumption_description_fixture()], + "inference_energy_consumption": [energy_consumption_description_fixture()], + "training_energy_consumption": [energy_consumption_description_fixture()], } AIPACKAGE_DICT = { - "energy_consumption": "energyConsumption", + "energy_consumption": energy_consumption_fixture(), "standard_compliance": ["standardCompliance"], "limitation": "aIPackageLimitation", "type_of_model": ["typeOfModel"], @@ -366,48 +379,51 @@ def listed_license_fixture( "hyperparameter": {"aIPackageHypParaKey": "aIPackageHypParaValue"}, "model_data_preprocessing": ["aImodelDataPreprocessing"], "model_explainability": ["aImodelExplainability"], - "sensitive_personal_information": True, + "use_sensitive_personal_information": PresenceType.YES, "metric_decision_threshold": {"metricDecisionThresholdKey": "metricDecisionThresholdValue"}, "metric": {"aIMetricKey": "aIMetricValue"}, "domain": ["aIDomain"], - "autonomy_type": True, + "autonomy_type": PresenceType.YES, "safety_risk_assessment": SafetyRiskAssessmentType.LOW, } ARTIFACT_DICT = { "originated_by": ["https://spdx.test/tools-python/originatedBy"], - "supplied_by": ["https://spdx.test/tools-python/suppliedBy"], + "supplied_by": "https://spdx.test/tools-python/suppliedBy", "built_time": datetime(2004, 1, 1), "release_time": datetime(2005, 1, 1), "valid_until_time": datetime(2006, 1, 1), - "standard": ["https://spdx.test/tools-python/standard"], + "standard_name": ["aStandard"], + "support_level": [SupportType.SUPPORT], } SOFTWARE_ARTIFACT_DICT = { - "content_identifier": "https://spdx.test/tools-python/contentIdentifier", + "content_identifier": [ + ContentIdentifier(ContentIdentifierType.GITOID, "gitoid:blob:sha1:261eeb9e9f8b2b4b0d119366dda99c6fd7d35c65") + ], "primary_purpose": SoftwarePurpose.SOURCE, "additional_purpose": [SoftwarePurpose.OTHER], - "concluded_license": listed_license_fixture(), - "declared_license": listed_license_fixture(), "copyright_text": "copyrightText", - "attribution_text": "attributionText", + "attribution_text": ["attributionText"], } -FILE_DICT = {"content_type": "fileContentType"} +FILE_DICT = { + "content_type": "text/spdx", + "file_kind": FileKindType.FILE, +} PACKAGE_DICT = { "package_version": "packageVersion", "download_location": "https://spdx.test/tools-python/downloadPackage", "package_url": "https://spdx.test/tools-python/package", - "homepage": "https://spdx.test/tools-python/homepage", + "home_page": "https://spdx.test/tools-python/homepage", "source_info": "sourceInfo", } -SNIPPET_DICT = {"byte_range": PositiveIntegerRange(1024, 2048), "line_range": PositiveIntegerRange(1, 4)} - -SOFTWARE_DEPENDENCY_RELATIONSHIP_DICT = { - "software_linkage": SoftwareDependencyLinkType.OTHER, - "conditionality": DependencyConditionalityType.OTHER, +SNIPPET_DICT = { + "byte_range": PositiveIntegerRange(1024, 2048), + "line_range": PositiveIntegerRange(1, 4), + "snippet_from_file": "https://spdx.test/tools-python/snippetFromFile", } DATASET_DICT = { @@ -419,7 +435,7 @@ def listed_license_fixture( "data_preprocessing": ["DataPreprocessing"], "sensor": {"SensorKey": "SensorValue"}, "known_bias": ["DatasetKnownBias"], - "sensitive_personal_information": True, + "has_sensitive_personal_information": PresenceType.NO, "anonymization_method_used": ["DatasetAnonymizationMethodUsed"], "confidentiality_level": ConfidentialityLevelType.CLEAR, "dataset_update_mechanism": "DatasetUpdateMechanism", @@ -432,12 +448,60 @@ def listed_license_fixture( "config_source_entrypoint": ["ConfigSourceEntrypoint"], "config_source_uri": ["ConfigSourceURI"], "config_source_digest": [hash_fixture()], - "parameters": {"parameter": "value"}, + "parameter": {"parameter": "value"}, "build_start_time": datetime(2015, 4, 4), "build_end_time": datetime(2015, 4, 5), "environment": {"environment_param": "environment_value"}, } +SPDX_DOCUMENT_DICT = { + "namespace_map": [namespace_map_fixture()], + "imports": [external_map_fixture()], + "data_license": "https://spdx.test/tools-python/license_id", +} + +INTEGRITY_METHOD_DICT = { + "comment": "integrityMethodComment", +} + +CONTENT_IDENTIFIER_DICT = { + "content_identifier_type": ContentIdentifierType.GITOID, + "content_identifier_value": "gitoid:blob:sha1:261eeb9e9f8b2b4b0d119366dda99c6fd7d35c65", +} + +PACKAGE_VERIFICATION_CODE_DICT = { + "algorithm": HashAlgorithm.SHA1, + "hash_value": "71c4025dd9897b364f3ebbb42c484ff43d00791c", + "package_verification_code_excluded_file": ["excludedFile1", "excludedFile2"], +} + +SIMPLE_LICENSING_TEXT_DICT = { + "license_text": "This is a simple license text.", +} + +CONJUNCTIVE_LICENSE_SET_DICT = { + "member": [ + "https://spdx.test/tools-python/license_id_1", + "https://spdx.test/tools-python/license_id_2", + ] +} + +DISJUNCTIVE_LICENSE_SET_DICT = { + "member": [ + "https://spdx.test/tools-python/license_id_1", + "https://spdx.test/tools-python/license_id_2", + ] +} + +OR_LATER_OPERATOR_DICT = { + "subject_license": "https://spdx.test/tools-python/license_id_1", +} + +WITH_ADDITION_OPERATOR_DICT = { + "subject_addition": "https://spdx.test/tools-python/license_id_1", + "subject_extendable_license": "https://spdx.test/tools-python/license_id_2", +} + FIXTURE_DICTS = { Agent: [ELEMENT_DICT], Person: [ELEMENT_DICT], @@ -448,13 +512,18 @@ def listed_license_fixture( LifecycleScopedRelationship: [ELEMENT_DICT, RELATIONSHIP_DICT, LIFECYCLE_SCOPED_RELATIONSHIP_DICT], Annotation: [ELEMENT_DICT, ANNOTATION_DICT], Bundle: [ELEMENT_DICT, ELEMENT_COLLECTION_DICT, BUNDLE_DICT], - SpdxDocument: [ELEMENT_DICT, ELEMENT_COLLECTION_DICT, BUNDLE_DICT], + SpdxDocument: [ELEMENT_DICT, ELEMENT_COLLECTION_DICT, SPDX_DOCUMENT_DICT], Bom: [ELEMENT_DICT, ELEMENT_COLLECTION_DICT, BUNDLE_DICT], Sbom: [ELEMENT_DICT, ELEMENT_COLLECTION_DICT, BUNDLE_DICT, SBOM_DICT], - ListedLicense: [LICENSE_DICT, LISTED_LICENSE_DICT], - CustomLicense: [LICENSE_DICT], - ListedLicenseException: [LICENSE_ADDITION_DICT, LISTED_LICENSE_EXCEPTION_DICT], - CustomLicenseAddition: [LICENSE_ADDITION_DICT], + ListedLicense: [ELEMENT_DICT, LICENSE_DICT, LISTED_LICENSE_DICT], + CustomLicense: [ELEMENT_DICT, LICENSE_DICT], + ListedLicenseException: [ELEMENT_DICT, LICENSE_ADDITION_DICT, LISTED_LICENSE_EXCEPTION_DICT], + CustomLicenseAddition: [ELEMENT_DICT, LICENSE_ADDITION_DICT], + LicenseExpression: [ELEMENT_DICT, LICENSE_EXPRESSION_DICT], + ConjunctiveLicenseSet: [ELEMENT_DICT, CONJUNCTIVE_LICENSE_SET_DICT], + DisjunctiveLicenseSet: [ELEMENT_DICT, DISJUNCTIVE_LICENSE_SET_DICT], + OrLaterOperator: [ELEMENT_DICT, OR_LATER_OPERATOR_DICT], + WithAdditionOperator: [ELEMENT_DICT, WITH_ADDITION_OPERATOR_DICT], CvssV2VulnAssessmentRelationship: [ ELEMENT_DICT, RELATIONSHIP_DICT, @@ -467,6 +536,12 @@ def listed_license_fixture( VULN_ASSESSMENT_RELATIONSHIP_DICT, CVSS_V3_VULN_ASSESSMENT_RELATIONSHIP_DICT, ], + CvssV4VulnAssessmentRelationship: [ + ELEMENT_DICT, + RELATIONSHIP_DICT, + VULN_ASSESSMENT_RELATIONSHIP_DICT, + CVSS_V4_VULN_ASSESSMENT_RELATIONSHIP_DICT, + ], EpssVulnAssessmentRelationship: [ ELEMENT_DICT, RELATIONSHIP_DICT, @@ -513,19 +588,14 @@ def listed_license_fixture( VEX_VULN_ASSESSMENT_RELATIONSHIP_DICT, VEX_UNDER_INVESTIGATION_VULN_ASSESSMENT_RELATIONSHIP_DICT, ], - Vulnerability: [ELEMENT_DICT, VULNERABILITY_DICT], + Vulnerability: [ELEMENT_DICT, ARTIFACT_DICT, VULNERABILITY_DICT], AIPackage: [AIPACKAGE_DICT, PACKAGE_DICT, ELEMENT_DICT, ARTIFACT_DICT, SOFTWARE_ARTIFACT_DICT], File: [ELEMENT_DICT, ARTIFACT_DICT, SOFTWARE_ARTIFACT_DICT, FILE_DICT], Package: [ELEMENT_DICT, ARTIFACT_DICT, SOFTWARE_ARTIFACT_DICT, PACKAGE_DICT], Snippet: [ELEMENT_DICT, ARTIFACT_DICT, SOFTWARE_ARTIFACT_DICT, SNIPPET_DICT], - SoftwareDependencyRelationship: [ - ELEMENT_DICT, - RELATIONSHIP_DICT, - LIFECYCLE_SCOPED_RELATIONSHIP_DICT, - SOFTWARE_DEPENDENCY_RELATIONSHIP_DICT, - ], - Dataset: [ELEMENT_DICT, ARTIFACT_DICT, SOFTWARE_ARTIFACT_DICT, PACKAGE_DICT, DATASET_DICT], + DatasetPackage: [ELEMENT_DICT, ARTIFACT_DICT, SOFTWARE_ARTIFACT_DICT, PACKAGE_DICT, DATASET_DICT], Build: [ELEMENT_DICT, BUILD_DICT], + SimpleLicensingText: [ELEMENT_DICT, SIMPLE_LICENSING_TEXT_DICT], } diff --git a/tests/spdx3/helpers.py b/tests/spdx3/helpers.py new file mode 100644 index 000000000..bf384082b --- /dev/null +++ b/tests/spdx3/helpers.py @@ -0,0 +1,5 @@ +from re import sub + + +def domain_enum_to_binding_name(enum_name: str) -> str: + return sub(r"_+([a-z])", lambda m: m.group(1).upper(), enum_name.strip("_").lower()) diff --git a/tests/spdx3/model/licensing/test_conjunctive_license_set.py b/tests/spdx3/model/licensing/test_conjunctive_license_set.py index 8a676041c..e98f73fef 100644 --- a/tests/spdx3/model/licensing/test_conjunctive_license_set.py +++ b/tests/spdx3/model/licensing/test_conjunctive_license_set.py @@ -3,21 +3,23 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -from spdx_tools.spdx3.model.licensing import ConjunctiveLicenseSet, ListedLicense -from tests.spdx3.fixtures import fixture_factory +from spdx_tools.spdx3.model.expandedlicensing import ConjunctiveLicenseSet, ListedLicense +from tests.spdx3.fixtures import creation_info_fixture, fixture_factory def test_valid_initialization(): lic = fixture_factory(ListedLicense) - conjunctive_license_set = ConjunctiveLicenseSet([lic, lic]) + conjunctive_license_set = ConjunctiveLicenseSet( + "https://spdx.test/tools-python/conjunctive-license-set", [lic.spdx_id, lic.spdx_id], creation_info_fixture() + ) - assert conjunctive_license_set.member == [lic, lic] + assert conjunctive_license_set.member == [lic.spdx_id, lic.spdx_id] def test_invalid_initialization(): lic = fixture_factory(ListedLicense) with pytest.raises(TypeError) as err: - ConjunctiveLicenseSet(lic) + ConjunctiveLicenseSet(123, lic, "creationInfo") # type: ignore # intentionally incorrect - assert len(err.value.args[0]) == 1 + assert len(err.value.args[0]) == 3 assert err.value.args[0][0].startswith("SetterError ConjunctiveLicenseSet:") diff --git a/tests/spdx3/model/licensing/test_disjunctive_license_set.py b/tests/spdx3/model/licensing/test_disjunctive_license_set.py index f18bd6424..8c1ca572a 100644 --- a/tests/spdx3/model/licensing/test_disjunctive_license_set.py +++ b/tests/spdx3/model/licensing/test_disjunctive_license_set.py @@ -3,21 +3,23 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -from spdx_tools.spdx3.model.licensing import DisjunctiveLicenseSet, ListedLicense -from tests.spdx3.fixtures import fixture_factory +from spdx_tools.spdx3.model.expandedlicensing import DisjunctiveLicenseSet, ListedLicense +from tests.spdx3.fixtures import creation_info_fixture, fixture_factory def test_valid_initialization(): lic = fixture_factory(ListedLicense) - disjunctive_license_set = DisjunctiveLicenseSet([lic, lic]) + disjunctive_license_set = DisjunctiveLicenseSet( + "https://spdx.test/tools-python/disjunctive-license-set", [lic.spdx_id, lic.spdx_id], creation_info_fixture() + ) - assert disjunctive_license_set.member == [lic, lic] + assert disjunctive_license_set.member == [lic.spdx_id, lic.spdx_id] def test_invalid_initialization(): lic = fixture_factory(ListedLicense) with pytest.raises(TypeError) as err: - DisjunctiveLicenseSet(lic) + DisjunctiveLicenseSet(123, lic, "creationInfo") # type: ignore # intentionally incorrect - assert len(err.value.args[0]) == 1 + assert len(err.value.args[0]) == 3 assert err.value.args[0][0].startswith("SetterError DisjunctiveLicenseSet:") diff --git a/tests/spdx3/model/licensing/test_or_later_operator.py b/tests/spdx3/model/licensing/test_or_later_operator.py index 219307fa0..e7401cbf1 100644 --- a/tests/spdx3/model/licensing/test_or_later_operator.py +++ b/tests/spdx3/model/licensing/test_or_later_operator.py @@ -3,20 +3,22 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -from spdx_tools.spdx3.model.licensing import ListedLicense, OrLaterOperator -from tests.spdx3.fixtures import fixture_factory +from spdx_tools.spdx3.model.expandedlicensing import ListedLicense, OrLaterOperator +from tests.spdx3.fixtures import creation_info_fixture, fixture_factory def test_valid_initialization(): lic = fixture_factory(ListedLicense) - or_later_operator = OrLaterOperator(lic) + or_later_operator = OrLaterOperator( + "https://spdx.test/tools-python/with_addition_operation", lic.spdx_id, creation_info_fixture() + ) - assert or_later_operator.subject_license == lic + assert or_later_operator.subject_license == lic.spdx_id def test_invalid_initialization(): with pytest.raises(TypeError) as err: - OrLaterOperator("License") + OrLaterOperator(123, "License", "creationInfo") # type: ignore # intentionally incorrect - assert len(err.value.args[0]) == 1 + assert len(err.value.args[0]) == 2 assert err.value.args[0][0].startswith("SetterError OrLaterOperator:") diff --git a/tests/spdx3/model/licensing/test_with_addition_operator.py b/tests/spdx3/model/licensing/test_with_addition_operator.py index 388aca27f..9c232f837 100644 --- a/tests/spdx3/model/licensing/test_with_addition_operator.py +++ b/tests/spdx3/model/licensing/test_with_addition_operator.py @@ -3,21 +3,28 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -from spdx_tools.spdx3.model.licensing import ListedLicense, ListedLicenseException, WithAdditionOperator -from tests.spdx3.fixtures import fixture_factory +from spdx_tools.spdx3.model.expandedlicensing import ListedLicense, ListedLicenseException, WithAdditionOperator +from tests.spdx3.fixtures import creation_info_fixture, fixture_factory def test_valid_initialization(): lic = fixture_factory(ListedLicense) lic_addition = fixture_factory(ListedLicenseException) - with_addition_operator = WithAdditionOperator(lic, lic_addition) + with_addition_operator = WithAdditionOperator( + "https://spdx.test/tools-python/with_addition_operation", + lic.spdx_id, + lic_addition.spdx_id, + creation_info_fixture(), + ) - assert with_addition_operator.subject_license == lic + assert with_addition_operator.subject_extendable_license == lic.spdx_id + assert with_addition_operator.subject_addition == lic_addition.spdx_id def test_invalid_initialization(): with pytest.raises(TypeError) as err: - WithAdditionOperator("License", "LicenseAddition") + # intentionally incorrect + WithAdditionOperator(123, "License", "LicenseAddition", "creationInfo") # type: ignore assert len(err.value.args[0]) == 2 for arg in err.value.args[0]: diff --git a/tests/spdx3/model/test_creation_info.py b/tests/spdx3/model/test_creation_info.py index 03b2fad58..9d1ff863e 100644 --- a/tests/spdx3/model/test_creation_info.py +++ b/tests/spdx3/model/test_creation_info.py @@ -6,7 +6,7 @@ import pytest from semantic_version import Version -from spdx_tools.spdx3.model import CreationInfo, ProfileIdentifierType +from spdx_tools.spdx3.model import CreationInfo from tests.spdx3.fixtures import creation_info_fixture from tests.spdx3.model.model_test_utils import get_property_names @@ -17,33 +17,27 @@ def test_correct_initialization(): for property_name in get_property_names(CreationInfo): assert getattr(creation_info, property_name) is not None - assert creation_info.spec_version == Version("3.0.0") + assert creation_info.spec_version == Version("3.0.1") assert creation_info.created == datetime(2022, 12, 1) assert creation_info.created_by == ["https://spdx.test/tools-python/creation_info_created_by"] assert creation_info.created_using == ["https://spdx.test/tools-python/creation_info_created_using"] - assert creation_info.profile == [ - ProfileIdentifierType.CORE, - ProfileIdentifierType.SOFTWARE, - ProfileIdentifierType.LICENSING, - ] - assert creation_info.data_license == "CC0-1.0" assert creation_info.comment == "creationInfoComment" def test_invalid_initialization(): with pytest.raises(TypeError) as err: - CreationInfo("2.3", "2012-01-01", [], "core", 3, [], []) + CreationInfo("2.3", "2012-01-01", [], "core", []) # type: ignore # intentionally incorrect - assert len(err.value.args[0]) == 5 + assert len(err.value.args[0]) == 4 for error in err.value.args[0]: assert error.startswith("SetterError CreationInfo:") def test_incomplete_initialization(): with pytest.raises(TypeError) as err: - CreationInfo("2.3") - + CreationInfo(Version("3.0.1")) # type: ignore # intentionally incorrect + print(err.value.args[0]) assert ( - "__init__() missing 3 required positional arguments: 'created', 'created_by', and 'profile'" + "CreationInfo.__init__() missing 2 required positional arguments: 'created' and 'created_by'" in err.value.args[0] ) diff --git a/tests/spdx3/model/test_element_and_licensing_subclasses.py b/tests/spdx3/model/test_element_and_licensing_subclasses.py index 0df9aa69f..5b2968857 100644 --- a/tests/spdx3/model/test_element_and_licensing_subclasses.py +++ b/tests/spdx3/model/test_element_and_licensing_subclasses.py @@ -17,13 +17,17 @@ ) from spdx_tools.spdx3.model.ai import AIPackage from spdx_tools.spdx3.model.build import Build -from spdx_tools.spdx3.model.dataset import Dataset -from spdx_tools.spdx3.model.licensing import ( +from spdx_tools.spdx3.model.dataset import DatasetPackage +from spdx_tools.spdx3.model.expandedlicensing import ( CustomLicense, CustomLicenseAddition, ListedLicense, ListedLicenseException, ) +from spdx_tools.spdx3.model.expandedlicensing.conjunctive_license_set import ConjunctiveLicenseSet +from spdx_tools.spdx3.model.expandedlicensing.disjunctive_license_set import DisjunctiveLicenseSet +from spdx_tools.spdx3.model.expandedlicensing.or_later_operator import OrLaterOperator +from spdx_tools.spdx3.model.expandedlicensing.with_addition_operator import WithAdditionOperator from spdx_tools.spdx3.model.security import ( CvssV2VulnAssessmentRelationship, CvssV3VulnAssessmentRelationship, @@ -36,7 +40,11 @@ VexUnderInvestigationVulnAssessmentRelationship, Vulnerability, ) -from spdx_tools.spdx3.model.software import File, Package, Sbom, Snippet, SoftwareDependencyRelationship +from spdx_tools.spdx3.model.security.cvss_v4_vuln_assessment_relationship import CvssV4VulnAssessmentRelationship +from spdx_tools.spdx3.model.simplelicensing.license_expression import LicenseExpression +from spdx_tools.spdx3.model.simplelicensing.simple_licensing_text import SimpleLicensingText +from spdx_tools.spdx3.model.software import File, Package, Sbom, Snippet +from spdx_tools.spdx3.model.tool import Tool from tests.spdx3.fixtures import fixture_factory, get_fixture_dict from tests.spdx3.model.model_test_utils import InvalidTypeClass, get_property_names @@ -55,8 +63,14 @@ CustomLicense, ListedLicenseException, CustomLicenseAddition, + LicenseExpression, + ConjunctiveLicenseSet, + DisjunctiveLicenseSet, + OrLaterOperator, + WithAdditionOperator, CvssV2VulnAssessmentRelationship, CvssV3VulnAssessmentRelationship, + CvssV4VulnAssessmentRelationship, EpssVulnAssessmentRelationship, ExploitCatalogVulnAssessmentRelationship, SsvcVulnAssessmentRelationship, @@ -69,10 +83,11 @@ Package, Snippet, Sbom, - SoftwareDependencyRelationship, - Dataset, + DatasetPackage, AIPackage, Build, + SimpleLicensingText, + Tool, ] diff --git a/tests/spdx3/model/test_external_identifier.py b/tests/spdx3/model/test_external_identifier.py index c006dfda5..fc2170df4 100644 --- a/tests/spdx3/model/test_external_identifier.py +++ b/tests/spdx3/model/test_external_identifier.py @@ -27,7 +27,8 @@ def test_correct_initialization(): def test_invalid_initialization(): with pytest.raises(TypeError) as err: - ExternalIdentifier("CPE22", ["identifier", "another_identifier"], 34, "locator", True) + # intentionally incorrect + ExternalIdentifier("CPE22", ["identifier", "another_identifier"], 34, "locator", True) # type: ignore assert len(err.value.args[0]) == 5 for error in err.value.args[0]: diff --git a/tests/spdx3/model/test_external_map.py b/tests/spdx3/model/test_external_map.py index b883b4598..9ba10b8eb 100644 --- a/tests/spdx3/model/test_external_map.py +++ b/tests/spdx3/model/test_external_map.py @@ -15,15 +15,15 @@ def test_correct_initialization(): for property_name in get_property_names(ExternalMap): assert getattr(external_map, property_name) is not None - assert external_map.external_id == "https://spdx.test/tools-python/external_map_external_id" + assert external_map.external_spdx_id == "https://spdx.test/tools-python/external_map_external_id" assert external_map.verified_using == [hash_fixture()] assert external_map.location_hint == "https://spdx.test/tools-python/external_map_location_hint" - assert external_map.defining_document == "https://spdx.test/tools-python/defining_document" + assert external_map.defining_artifact == "https://spdx.test/tools-python/defining_artifact" def test_invalid_initialization(): with pytest.raises(TypeError) as err: - ExternalMap(234, None, ["location hints"]) + ExternalMap(234, None, ["location hints"]) # type: ignore # intentionally incorrect assert len(err.value.args[0]) == 2 for error in err.value.args[0]: diff --git a/tests/spdx3/model/test_external_reference.py b/tests/spdx3/model/test_external_reference.py index 91dac9fb8..b342c99c8 100644 --- a/tests/spdx3/model/test_external_reference.py +++ b/tests/spdx3/model/test_external_reference.py @@ -16,13 +16,13 @@ def test_correct_initialization(): assert external_reference.external_reference_type == ExternalReferenceType.OTHER assert external_reference.locator == ["org.apache.tomcat:tomcat:9.0.0.M4"] - assert external_reference.content_type == "externalReferenceContentType" + assert external_reference.content_type == "text/plain" assert external_reference.comment == "externalReferenceComment" def test_invalid_initialization(): with pytest.raises(TypeError) as err: - ExternalReference("OTHER", "a URI", 34, True) + ExternalReference("OTHER", "a URI", 34, True) # type: ignore # intentionally incorrect assert len(err.value.args[0]) == 4 for error in err.value.args[0]: diff --git a/tests/spdx3/model/test_hash.py b/tests/spdx3/model/test_hash.py index 8994e1a7c..4e69c1ea3 100644 --- a/tests/spdx3/model/test_hash.py +++ b/tests/spdx3/model/test_hash.py @@ -21,7 +21,7 @@ def test_correct_initialization(): def test_invalid_initialization(): with pytest.raises(TypeError) as err: - Hash("SHA1", 345) + Hash("SHA1", 345) # type: ignore # intentionally incorrect assert len(err.value.args[0]) == 2 for error in err.value.args[0]: diff --git a/tests/spdx3/model/test_namespace_map.py b/tests/spdx3/model/test_namespace_map.py index a0e430162..6172c1f22 100644 --- a/tests/spdx3/model/test_namespace_map.py +++ b/tests/spdx3/model/test_namespace_map.py @@ -20,7 +20,7 @@ def test_correct_initialization(): def test_invalid_initialization(): with pytest.raises(TypeError) as err: - NamespaceMap(34, ["list of namespaces"]) + NamespaceMap(34, ["list of namespaces"]) # type: ignore # intentionally incorrect assert len(err.value.args[0]) == 2 for error in err.value.args[0]: diff --git a/tests/spdx3/parser/json_ld/test_jsonld_parser.py b/tests/spdx3/parser/json_ld/test_jsonld_parser.py new file mode 100644 index 000000000..b6a0f3428 --- /dev/null +++ b/tests/spdx3/parser/json_ld/test_jsonld_parser.py @@ -0,0 +1,197 @@ +import os +import typing +from datetime import datetime + +from semantic_version import Version + +from spdx_tools.spdx3.model import CreationInfo, ExternalIdentifierType, Person, ProfileIdentifierType +from spdx_tools.spdx3.model.extension.cdx_properties_extension import CdxPropertiesExtension +from spdx_tools.spdx3.model.simplelicensing.license_expression import LicenseExpression +from spdx_tools.spdx3.model.software import Sbom +from spdx_tools.spdx3.model.spdx_document import SpdxDocument +from spdx_tools.spdx3.parser.jsonld.jsonld_parser import parse_from_file + + +def test_parse_from_file(): + file_path = os.path.join(os.path.dirname(__file__), "../../data/spdxV3-example.json") + payload = parse_from_file(file_path) + + # Test SpdxDocument + spdx_document = payload.get_element("http://spdx.example.com/Document1") + assert spdx_document is not None + assert isinstance(spdx_document, SpdxDocument) + assert spdx_document.spdx_id == "http://spdx.example.com/Document1" + assert spdx_document.profile_conformance == [ProfileIdentifierType.CORE, ProfileIdentifierType.SOFTWARE] + assert spdx_document.root_element == ["urn:example13-sbom.com-4fe40e24-20e3-11ee-be56-0242ac120002"] + + assert spdx_document.creation_info is not None + assert isinstance(spdx_document.creation_info, CreationInfo) + assert spdx_document.creation_info.spec_version == Version("3.0.1") + assert spdx_document.creation_info.created == datetime.fromisoformat("2024-05-02T00:00:00+00:00") + + # Test sbom + sbom = payload.get_element("urn:example13-sbom.com-4fe40e24-20e3-11ee-be56-0242ac120002") + assert sbom is not None + assert isinstance(sbom, Sbom) + assert sbom.spdx_id == "urn:example13-sbom.com-4fe40e24-20e3-11ee-be56-0242ac120002" + assert sbom.profile_conformance == [ProfileIdentifierType.CORE, ProfileIdentifierType.SOFTWARE] + assert sbom.root_element == ["urn:product-acme-application-1.3-4fe40e24-20e3-11ee-be56-0242ac120002"] + + # Test Person - Jane Doe + jane_doe = payload.get_element("urn:jane-doe-1@acme.com-4fe40e24-20e3-11ee-be56-0242ac120002") + assert jane_doe is not None + assert isinstance(jane_doe, Person) + assert jane_doe.spdx_id == "urn:jane-doe-1@acme.com-4fe40e24-20e3-11ee-be56-0242ac120002" + assert jane_doe.name == "Application Owner Jane Doe" + assert len(jane_doe.external_identifier) == 1 + assert jane_doe.external_identifier[0].external_identifier_type == ExternalIdentifierType.EMAIL + assert jane_doe.external_identifier[0].identifier == "jane-doe-1@acme.com" + + # Test Organization - Acme Company + acme_company = payload.get_element("urn:acme.com-4fe40e24-20e3-11ee-be56-0242ac120002") + assert acme_company is not None + # Import Organization class to use in isinstance check + from spdx_tools.spdx3.model import Organization + + assert isinstance(acme_company, Organization) + assert acme_company.spdx_id == "urn:acme.com-4fe40e24-20e3-11ee-be56-0242ac120002" + assert acme_company.name == "Acme Company" + + # Test Person - Fedor Indutny + fedor_indutny = payload.get_element("urn:github.com-indutny-c4fe40e24-20e3-11ee-be56-0242ac120002") + assert fedor_indutny is not None + assert isinstance(fedor_indutny, Person) + assert fedor_indutny.spdx_id == "urn:github.com-indutny-c4fe40e24-20e3-11ee-be56-0242ac120002" + assert fedor_indutny.name == "Fedor Indutny" + assert len(fedor_indutny.external_identifier) == 1 + assert fedor_indutny.external_identifier[0].external_identifier_type == ExternalIdentifierType.OTHER + assert fedor_indutny.external_identifier[0].identifier == "https://github.com/indutny" + + # Test Organization - Alpine Linux + alpine_linux = payload.get_element("urn:github.com-alpinelinux-4fe40e24-20e3-11ee-be56-0242ac120002") + assert alpine_linux is not None + assert isinstance(alpine_linux, Organization) + assert alpine_linux.spdx_id == "urn:github.com-alpinelinux-4fe40e24-20e3-11ee-be56-0242ac120002" + assert alpine_linux.name == "Alpine Linux" + + # Test Package - Acme Application + acme_app = payload.get_element("urn:product-acme-application-1.3-4fe40e24-20e3-11ee-be56-0242ac120002") + assert acme_app is not None + from spdx_tools.spdx3.model.software import Package + + assert isinstance(acme_app, Package) + assert acme_app.spdx_id == "urn:product-acme-application-1.3-4fe40e24-20e3-11ee-be56-0242ac120002" + assert acme_app.name == "Acme Application" + assert acme_app.package_version == "1.3" + assert acme_app.supplied_by == "urn:acme.com-4fe40e24-20e3-11ee-be56-0242ac120002" + assert len(acme_app.extension) == 1 + extension = acme_app.extension[0] + assert type(extension) is CdxPropertiesExtension + assert len(extension.cdx_property) == 1 + assert extension.cdx_property[0].cdx_prop_name == "example name" + assert extension.cdx_property[0].cdx_prop_value == "example value" + + # Test external identifiers + assert len(acme_app.external_identifier) == 2 + cpe_identifier = next( + (ei for ei in acme_app.external_identifier if ei.external_identifier_type == ExternalIdentifierType.CPE23), + None, + ) + assert cpe_identifier is not None + assert cpe_identifier.identifier == "cpe:2.3:a:*:acme-application:1.3:*:*:*:*:*:*:*" + purl_identifier = next( + ( + ei + for ei in acme_app.external_identifier + if ei.external_identifier_type == ExternalIdentifierType.PACKAGE_URL + ), + None, + ) + assert purl_identifier is not None + assert purl_identifier.identifier == "pkg:generic/acme-application@1.3" + # Test external references + assert len(acme_app.external_reference) == 3 + external_reference = next((er for er in acme_app.external_reference if er.comment == "comment1"), None) + assert external_reference is not None + assert external_reference.locator == ["locator1"] + + # Test Package - npm-elliptic + elliptic_package = payload.get_element("urn:npm-elliptic-6.5.2-4fe40e24-20e3-11ee-be56-0242ac120002") + assert elliptic_package is not None + assert isinstance(elliptic_package, Package) + assert elliptic_package.spdx_id == "urn:npm-elliptic-6.5.2-4fe40e24-20e3-11ee-be56-0242ac120002" + assert elliptic_package.name == "npm-elliptic" + assert elliptic_package.package_version == "6.5.2" + assert elliptic_package.supplied_by == "urn:github.com-indutny-c4fe40e24-20e3-11ee-be56-0242ac120002" + assert len(elliptic_package.external_identifier) == 1 + assert elliptic_package.external_identifier[0].external_identifier_type == ExternalIdentifierType.OTHER + assert ( + elliptic_package.external_identifier[0].identifier == "https://github.com/indutny/elliptic/releases/tag/v6.5.2" + ) + + # Test Package - Alpine Container + alpine_container = payload.get_element( + ( + "urn:container-alpine-latest-sha256:" + "69665d02cb32192e52e07644d76bc6f25abeb5410edc1c7a81a10ba3f0efb90a-4fe40e24-20e3-11ee-be56-0242ac120002" + ) + ) + assert alpine_container is not None + assert isinstance(alpine_container, Package) + assert alpine_container.spdx_id == ( + "urn:container-alpine-latest-sha256:" + "69665d02cb32192e52e07644d76bc6f25abeb5410edc1c7a81a10ba3f0efb90a-4fe40e24-20e3-11ee-be56-0242ac120002" + ) + assert alpine_container.name == "alpine:latest" + assert alpine_container.package_version == "69665d02cb32192e52e07644d76bc6f25abeb5410edc1c7a81a10ba3f0efb90a" + assert alpine_container.supplied_by == "urn:github.com-alpinelinux-4fe40e24-20e3-11ee-be56-0242ac120002" + + # Test Package - OpenSSL + openssl_package = payload.get_element("urn:openssl-3.0.4-4fe40e24-20e3-11ee-be56-0242ac120002") + assert openssl_package is not None + assert isinstance(openssl_package, Package) + assert openssl_package.spdx_id == "urn:openssl-3.0.4-4fe40e24-20e3-11ee-be56-0242ac120002" + assert openssl_package.name == "openssl" + assert openssl_package.package_version == "3.0.4" + + +def test_parse_from_file_2(): + file_path = os.path.join(os.path.dirname(__file__), "../../data/example6-bin.json") + payload = parse_from_file(file_path) + + # Test SpdxDocument + spdx_document = payload.get_element("https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/document0") + assert spdx_document is not None + assert isinstance(spdx_document, SpdxDocument) + assert spdx_document.spdx_id == "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/document0" + assert spdx_document.root_element == [ + "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd4" + ] + assert ( + spdx_document.data_license + == "https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd16" + ) + + assert spdx_document.creation_info is not None + assert isinstance(spdx_document.creation_info, CreationInfo) + assert spdx_document.creation_info.spec_version == Version("3.0.1") + assert spdx_document.creation_info.created == datetime.fromisoformat("2021-08-26T01:56:00+00:00") + + license_expression: LicenseExpression = typing.cast( + LicenseExpression, + payload.get_element("https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd16"), + ) + assert license_expression is not None + assert license_expression.license_expression == "CC0-1.0" + + person: Person = typing.cast( + Person, + payload.get_element("https://swinslow.net/spdx-examples/example6/hello-go-bin-v2-specv3/SPDXRef-gnrtd0"), + ) + assert person is not None + assert isinstance(person, Person) + assert person.name == "Steve Winslow" + assert len(person.external_identifier) == 1 + person_external_id = person.external_identifier[0] + assert person_external_id.external_identifier_type == ExternalIdentifierType.EMAIL + assert person_external_id.identifier == "steve@swinslow.net" diff --git a/tests/spdx3/writer/tag_value/test_write_document.py b/tests/spdx3/writer/tag_value/test_write_document.py index 10d5d54c6..bdd03ad53 100644 --- a/tests/spdx3/writer/tag_value/test_write_document.py +++ b/tests/spdx3/writer/tag_value/test_write_document.py @@ -6,7 +6,7 @@ from semantic_version import Version -from spdx_tools.spdx3.model import CreationInfo, ProfileIdentifierType, SpdxDocument +from spdx_tools.spdx3.model import CreationInfo, SpdxDocument from spdx_tools.spdx3.writer.console.spdx_document_writer import write_spdx_document @@ -17,7 +17,6 @@ def test_render_creation_info(): spec_version=spec_version, created=fake_datetime, created_by=[], - profile=[ProfileIdentifierType.SOFTWARE], ) spdx_document = SpdxDocument( spdx_id="SPDXRef-FOO", @@ -36,7 +35,7 @@ def test_render_creation_info(): # Creation Information specVersion: 3.0.0 created: 2024-01-01T00:00:00Z - profile: SOFTWARE - data license: CC0-1.0 elements: +root elements: +profile conformance: """ # noqa: W291 # elements: are printed with a space