spdx_tools.spdx.validation.package_validator

  1# SPDX-FileCopyrightText: 2022 spdx contributors
  2#
  3# SPDX-License-Identifier: Apache-2.0
  4
  5from beartype.typing import List, Optional
  6
  7from spdx_tools.spdx.model import Document, File, Package, Relationship, RelationshipType
  8from spdx_tools.spdx.model.relationship_filters import filter_by_type_and_origin, filter_by_type_and_target
  9from spdx_tools.spdx.spdx_element_utils import get_element_type_from_spdx_id
 10from spdx_tools.spdx.validation.checksum_validator import validate_checksums
 11from spdx_tools.spdx.validation.external_package_ref_validator import validate_external_package_refs
 12from spdx_tools.spdx.validation.license_expression_validator import (
 13    validate_license_expression,
 14    validate_license_expressions,
 15)
 16from spdx_tools.spdx.validation.package_verification_code_validator import validate_verification_code
 17from spdx_tools.spdx.validation.spdx_id_validators import validate_spdx_id
 18from spdx_tools.spdx.validation.uri_validators import validate_download_location, validate_url
 19from spdx_tools.spdx.validation.validation_message import SpdxElementType, ValidationContext, ValidationMessage
 20
 21
 22def validate_packages(
 23    packages: List[Package], spdx_version: str, document: Optional[Document] = None
 24) -> List[ValidationMessage]:
 25    validation_messages: List[ValidationMessage] = []
 26    if document:
 27        for package in packages:
 28            validation_messages.extend(validate_package_within_document(package, spdx_version, document))
 29    else:
 30        for package in packages:
 31            validation_messages.extend(validate_package(package, spdx_version))
 32
 33    return validation_messages
 34
 35
 36def validate_package_within_document(
 37    package: Package, spdx_version: str, document: Document
 38) -> List[ValidationMessage]:
 39    validation_messages: List[ValidationMessage] = []
 40    context = ValidationContext(
 41        spdx_id=package.spdx_id,
 42        parent_id=document.creation_info.spdx_id,
 43        element_type=SpdxElementType.PACKAGE,
 44        full_element=package,
 45    )
 46
 47    for message in validate_spdx_id(package.spdx_id, document):
 48        validation_messages.append(ValidationMessage(message, context))
 49
 50    if not package.files_analyzed:
 51        package_contains_relationships = filter_by_type_and_origin(
 52            document.relationships, RelationshipType.CONTAINS, package.spdx_id
 53        )
 54        package_contains_file_relationships = [
 55            relationship
 56            for relationship in package_contains_relationships
 57            if get_element_type_from_spdx_id(relationship.related_spdx_element_id, document) == File
 58        ]
 59
 60        contained_in_package_relationships = filter_by_type_and_target(
 61            document.relationships, RelationshipType.CONTAINED_BY, package.spdx_id
 62        )
 63        file_contained_in_package_relationships = [
 64            relationship
 65            for relationship in contained_in_package_relationships
 66            if get_element_type_from_spdx_id(relationship.spdx_element_id, document) == File
 67        ]
 68
 69        combined_relationships: List[Relationship] = (
 70            package_contains_file_relationships + file_contained_in_package_relationships
 71        )
 72
 73        if combined_relationships:
 74            validation_messages.append(
 75                ValidationMessage(
 76                    f"package must contain no elements if files_analyzed is False, but found {combined_relationships}",
 77                    context,
 78                )
 79            )
 80
 81    validation_messages.extend(validate_license_expression(package.license_concluded, document, package.spdx_id))
 82
 83    license_info_from_files = package.license_info_from_files
 84    if license_info_from_files:
 85        if not package.files_analyzed:
 86            validation_messages.append(
 87                ValidationMessage(
 88                    f"license_info_from_files must be None if files_analyzed is False, but is: "
 89                    f"{license_info_from_files}",
 90                    context,
 91                )
 92            )
 93        else:
 94            validation_messages.extend(
 95                validate_license_expressions(license_info_from_files, document, package.spdx_id)
 96            )
 97
 98    validation_messages.extend(validate_license_expression(package.license_declared, document, package.spdx_id))
 99
100    validation_messages.extend(validate_package(package, spdx_version, context))
101
102    return validation_messages
103
104
105def validate_package(
106    package: Package, spdx_version: str, context: Optional[ValidationContext] = None
107) -> List[ValidationMessage]:
108    validation_messages: List[ValidationMessage] = []
109    if not context:
110        context = ValidationContext(
111            spdx_id=package.spdx_id, element_type=SpdxElementType.PACKAGE, full_element=package
112        )
113
114    download_location = package.download_location
115    if isinstance(download_location, str):
116        for message in validate_download_location(download_location):
117            validation_messages.append(ValidationMessage("package download_location " + message, context))
118
119    homepage = package.homepage
120    if isinstance(homepage, str):
121        for message in validate_url(homepage):
122            validation_messages.append(ValidationMessage("homepage " + message, context))
123
124    verification_code = package.verification_code
125    if verification_code:
126        if not package.files_analyzed:
127            validation_messages.append(
128                ValidationMessage(
129                    f"verification_code must be None if files_analyzed is False, but is: {verification_code}", context
130                )
131            )
132        else:
133            validation_messages.extend(validate_verification_code(verification_code, package.spdx_id))
134
135    validation_messages.extend(validate_checksums(package.checksums, package.spdx_id, spdx_version))
136
137    validation_messages.extend(
138        validate_external_package_refs(package.external_references, package.spdx_id, spdx_version)
139    )
140
141    if spdx_version == "SPDX-2.2":
142        if package.primary_package_purpose is not None:
143            validation_messages.append(
144                ValidationMessage("primary_package_purpose is not supported in SPDX-2.2", context)
145            )
146        if package.built_date is not None:
147            validation_messages.append(ValidationMessage("built_date is not supported in SPDX-2.2", context))
148        if package.release_date is not None:
149            validation_messages.append(ValidationMessage("release_date is not supported in SPDX-2.2", context))
150        if package.valid_until_date is not None:
151            validation_messages.append(ValidationMessage("valid_until_date is not supported in SPDX-2.2", context))
152
153        if package.license_concluded is None:
154            validation_messages.append(ValidationMessage("license_concluded is mandatory in SPDX-2.2", context))
155        if package.license_declared is None:
156            validation_messages.append(ValidationMessage("license_declared is mandatory in SPDX-2.2", context))
157        if package.copyright_text is None:
158            validation_messages.append(ValidationMessage("copyright_text is mandatory in SPDX-2.2", context))
159
160    return validation_messages
def validate_packages( packages: list[spdx_tools.spdx.model.package.Package], spdx_version: str, document: Optional[spdx_tools.spdx.model.document.Document] = None) -> list[spdx_tools.spdx.validation.validation_message.ValidationMessage]:
23def validate_packages(
24    packages: List[Package], spdx_version: str, document: Optional[Document] = None
25) -> List[ValidationMessage]:
26    validation_messages: List[ValidationMessage] = []
27    if document:
28        for package in packages:
29            validation_messages.extend(validate_package_within_document(package, spdx_version, document))
30    else:
31        for package in packages:
32            validation_messages.extend(validate_package(package, spdx_version))
33
34    return validation_messages
def validate_package_within_document( package: spdx_tools.spdx.model.package.Package, spdx_version: str, document: spdx_tools.spdx.model.document.Document) -> list[spdx_tools.spdx.validation.validation_message.ValidationMessage]:
 37def validate_package_within_document(
 38    package: Package, spdx_version: str, document: Document
 39) -> List[ValidationMessage]:
 40    validation_messages: List[ValidationMessage] = []
 41    context = ValidationContext(
 42        spdx_id=package.spdx_id,
 43        parent_id=document.creation_info.spdx_id,
 44        element_type=SpdxElementType.PACKAGE,
 45        full_element=package,
 46    )
 47
 48    for message in validate_spdx_id(package.spdx_id, document):
 49        validation_messages.append(ValidationMessage(message, context))
 50
 51    if not package.files_analyzed:
 52        package_contains_relationships = filter_by_type_and_origin(
 53            document.relationships, RelationshipType.CONTAINS, package.spdx_id
 54        )
 55        package_contains_file_relationships = [
 56            relationship
 57            for relationship in package_contains_relationships
 58            if get_element_type_from_spdx_id(relationship.related_spdx_element_id, document) == File
 59        ]
 60
 61        contained_in_package_relationships = filter_by_type_and_target(
 62            document.relationships, RelationshipType.CONTAINED_BY, package.spdx_id
 63        )
 64        file_contained_in_package_relationships = [
 65            relationship
 66            for relationship in contained_in_package_relationships
 67            if get_element_type_from_spdx_id(relationship.spdx_element_id, document) == File
 68        ]
 69
 70        combined_relationships: List[Relationship] = (
 71            package_contains_file_relationships + file_contained_in_package_relationships
 72        )
 73
 74        if combined_relationships:
 75            validation_messages.append(
 76                ValidationMessage(
 77                    f"package must contain no elements if files_analyzed is False, but found {combined_relationships}",
 78                    context,
 79                )
 80            )
 81
 82    validation_messages.extend(validate_license_expression(package.license_concluded, document, package.spdx_id))
 83
 84    license_info_from_files = package.license_info_from_files
 85    if license_info_from_files:
 86        if not package.files_analyzed:
 87            validation_messages.append(
 88                ValidationMessage(
 89                    f"license_info_from_files must be None if files_analyzed is False, but is: "
 90                    f"{license_info_from_files}",
 91                    context,
 92                )
 93            )
 94        else:
 95            validation_messages.extend(
 96                validate_license_expressions(license_info_from_files, document, package.spdx_id)
 97            )
 98
 99    validation_messages.extend(validate_license_expression(package.license_declared, document, package.spdx_id))
100
101    validation_messages.extend(validate_package(package, spdx_version, context))
102
103    return validation_messages
106def validate_package(
107    package: Package, spdx_version: str, context: Optional[ValidationContext] = None
108) -> List[ValidationMessage]:
109    validation_messages: List[ValidationMessage] = []
110    if not context:
111        context = ValidationContext(
112            spdx_id=package.spdx_id, element_type=SpdxElementType.PACKAGE, full_element=package
113        )
114
115    download_location = package.download_location
116    if isinstance(download_location, str):
117        for message in validate_download_location(download_location):
118            validation_messages.append(ValidationMessage("package download_location " + message, context))
119
120    homepage = package.homepage
121    if isinstance(homepage, str):
122        for message in validate_url(homepage):
123            validation_messages.append(ValidationMessage("homepage " + message, context))
124
125    verification_code = package.verification_code
126    if verification_code:
127        if not package.files_analyzed:
128            validation_messages.append(
129                ValidationMessage(
130                    f"verification_code must be None if files_analyzed is False, but is: {verification_code}", context
131                )
132            )
133        else:
134            validation_messages.extend(validate_verification_code(verification_code, package.spdx_id))
135
136    validation_messages.extend(validate_checksums(package.checksums, package.spdx_id, spdx_version))
137
138    validation_messages.extend(
139        validate_external_package_refs(package.external_references, package.spdx_id, spdx_version)
140    )
141
142    if spdx_version == "SPDX-2.2":
143        if package.primary_package_purpose is not None:
144            validation_messages.append(
145                ValidationMessage("primary_package_purpose is not supported in SPDX-2.2", context)
146            )
147        if package.built_date is not None:
148            validation_messages.append(ValidationMessage("built_date is not supported in SPDX-2.2", context))
149        if package.release_date is not None:
150            validation_messages.append(ValidationMessage("release_date is not supported in SPDX-2.2", context))
151        if package.valid_until_date is not None:
152            validation_messages.append(ValidationMessage("valid_until_date is not supported in SPDX-2.2", context))
153
154        if package.license_concluded is None:
155            validation_messages.append(ValidationMessage("license_concluded is mandatory in SPDX-2.2", context))
156        if package.license_declared is None:
157            validation_messages.append(ValidationMessage("license_declared is mandatory in SPDX-2.2", context))
158        if package.copyright_text is None:
159            validation_messages.append(ValidationMessage("copyright_text is mandatory in SPDX-2.2", context))
160
161    return validation_messages