diff --git a/src/spdx_tools/spdx/spdx_element_utils.py b/src/spdx_tools/spdx/spdx_element_utils.py index c3cb3f7fa..0d6bd8946 100644 --- a/src/spdx_tools/spdx/spdx_element_utils.py +++ b/src/spdx_tools/spdx/spdx_element_utils.py @@ -3,10 +3,11 @@ # SPDX-License-Identifier: Apache-2.0 import hashlib -from beartype.typing import List, Union +from beartype.typing import List, Optional, Type, Union from spdx_tools.spdx.model import ( ChecksumAlgorithm, + Document, ExternalDocumentRef, File, Package, @@ -15,6 +16,18 @@ ) +def get_element_type_from_spdx_id( + spdx_id: str, document: Document +) -> Optional[Union[Type[Package], Type[File], Type[Snippet]]]: + if spdx_id in [package.spdx_id for package in document.packages]: + return Package + if spdx_id in [file.spdx_id for file in document.files]: + return File + if spdx_id in [snippet.spdx_id for snippet in document.snippets]: + return Snippet + return None + + def get_full_element_spdx_id( element: Union[Package, File, Snippet], document_namespace: str, diff --git a/src/spdx_tools/spdx/validation/package_validator.py b/src/spdx_tools/spdx/validation/package_validator.py index 4307fc8ef..25cd6147f 100644 --- a/src/spdx_tools/spdx/validation/package_validator.py +++ b/src/spdx_tools/spdx/validation/package_validator.py @@ -4,8 +4,9 @@ from beartype.typing import List, Optional -from spdx_tools.spdx.model import Document, Package, Relationship, RelationshipType +from spdx_tools.spdx.model import Document, File, Package, Relationship, RelationshipType from spdx_tools.spdx.model.relationship_filters import filter_by_type_and_origin, filter_by_type_and_target +from spdx_tools.spdx.spdx_element_utils import get_element_type_from_spdx_id from spdx_tools.spdx.validation.checksum_validator import validate_checksums from spdx_tools.spdx.validation.external_package_ref_validator import validate_external_package_refs from spdx_tools.spdx.validation.license_expression_validator import ( @@ -50,12 +51,23 @@ def validate_package_within_document( package_contains_relationships = filter_by_type_and_origin( document.relationships, RelationshipType.CONTAINS, package.spdx_id ) + package_contains_file_relationships = [ + relationship + for relationship in package_contains_relationships + if get_element_type_from_spdx_id(relationship.related_spdx_element_id, document) == File + ] + contained_in_package_relationships = filter_by_type_and_target( document.relationships, RelationshipType.CONTAINED_BY, package.spdx_id ) + file_contained_in_package_relationships = [ + relationship + for relationship in contained_in_package_relationships + if get_element_type_from_spdx_id(relationship.spdx_element_id, document) == File + ] combined_relationships: List[Relationship] = ( - package_contains_relationships + contained_in_package_relationships + package_contains_file_relationships + file_contained_in_package_relationships ) if combined_relationships: diff --git a/tests/spdx/validation/test_package_validator.py b/tests/spdx/validation/test_package_validator.py index c2b6640d2..a6ef976ef 100644 --- a/tests/spdx/validation/test_package_validator.py +++ b/tests/spdx/validation/test_package_validator.py @@ -74,10 +74,27 @@ def test_invalid_package(package_input, expected_message): @pytest.mark.parametrize( "relationships", [ - [Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File1")], [Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "DocumentRef-external:SPDXRef-File")], - [Relationship("SPDXRef-File2", RelationshipType.CONTAINED_BY, "SPDXRef-Package")], [Relationship("DocumentRef-external:SPDXRef-File", RelationshipType.CONTAINED_BY, "SPDXRef-Package")], + ], +) +def test_valid_package_with_contains(relationships): + document = document_fixture( + relationships=relationships, + files=[file_fixture(spdx_id="SPDXRef-File1"), file_fixture(spdx_id="SPDXRef-File2")], + ) + package = package_fixture(files_analyzed=False, verification_code=None, license_info_from_files=[]) + + validation_messages: List[ValidationMessage] = validate_package_within_document(package, "SPDX-2.3", document) + + assert validation_messages == [] + + +@pytest.mark.parametrize( + "relationships", + [ + [Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File1")], + [Relationship("SPDXRef-File2", RelationshipType.CONTAINED_BY, "SPDXRef-Package")], [ Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File2"), Relationship("SPDXRef-File1", RelationshipType.CONTAINED_BY, "SPDXRef-Package"),