From 0eb0be8f9707cc8438a77817a7090a2b8d266fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20T=C3=A4nzer?= Date: Tue, 22 Aug 2023 17:52:01 +0200 Subject: [PATCH] [issue-744] implement validation of external license references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Armin Tänzer --- .../license_expression_validator.py | 32 ++++++++++++++- .../test_license_expression_validator.py | 39 ++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/spdx_tools/spdx/validation/license_expression_validator.py b/src/spdx_tools/spdx/validation/license_expression_validator.py index a59aec9fa..e463aa9b6 100644 --- a/src/spdx_tools/spdx/validation/license_expression_validator.py +++ b/src/spdx_tools/spdx/validation/license_expression_validator.py @@ -7,6 +7,7 @@ from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.model import Document, SpdxNoAssertion, SpdxNone +from spdx_tools.spdx.validation.spdx_id_validators import is_external_doc_ref_present_in_document from spdx_tools.spdx.validation.validation_message import SpdxElementType, ValidationContext, ValidationMessage @@ -42,7 +43,36 @@ def validate_license_expression( license_ref_ids: List[str] = [license_ref.license_id for license_ref in document.extracted_licensing_info] for non_spdx_token in spdx_licensing.validate(license_expression).invalid_symbols: - if non_spdx_token not in license_ref_ids: + if ":" in non_spdx_token: + split_token: List[str] = non_spdx_token.split(":") + if len(split_token) != 2: + validation_messages.append( + ValidationMessage( + f"Too many colons in license reference: {non_spdx_token}. " + "A license reference must only contain a single colon to " + "separate an external document reference from the license reference.", + context, + ) + ) + else: + if not split_token[1].startswith("LicenseRef-"): + validation_messages.append( + ValidationMessage( + f'A license reference must start with "LicenseRef-", but is: {split_token[1]} ' + f"in external license reference {non_spdx_token}.", + context, + ) + ) + if not is_external_doc_ref_present_in_document(split_token[0], document): + validation_messages.append( + ValidationMessage( + f'Did not find the external document reference "{split_token[0]}" in the SPDX document. ' + f"From the external license reference {non_spdx_token}.", + context, + ) + ) + + elif non_spdx_token not in license_ref_ids: validation_messages.append( ValidationMessage( f"Unrecognized license reference: {non_spdx_token}. license_expression must only use IDs from the " diff --git a/tests/spdx/validation/test_license_expression_validator.py b/tests/spdx/validation/test_license_expression_validator.py index cb0ef0c66..e965f4803 100644 --- a/tests/spdx/validation/test_license_expression_validator.py +++ b/tests/spdx/validation/test_license_expression_validator.py @@ -15,9 +15,10 @@ validate_license_expressions, ) from spdx_tools.spdx.validation.validation_message import SpdxElementType, ValidationContext, ValidationMessage -from tests.spdx.fixtures import document_fixture, extracted_licensing_info_fixture +from tests.spdx.fixtures import document_fixture, external_document_ref_fixture, extracted_licensing_info_fixture FIXTURE_LICENSE_ID = extracted_licensing_info_fixture().license_id +EXTERNAL_DOCUMENT_ID = external_document_ref_fixture().document_ref_id @pytest.mark.parametrize( @@ -26,6 +27,7 @@ "MIT", FIXTURE_LICENSE_ID, f"GPL-2.0-only with GPL-CC-1.0 and {FIXTURE_LICENSE_ID} with 389-exception or Beerware", + f"{EXTERNAL_DOCUMENT_ID}:LicenseRef-007", ], ) def test_valid_license_expression(expression_string): @@ -136,3 +138,38 @@ def test_invalid_license_expression_with_invalid_exceptions(expression_string, e expected_messages = [ValidationMessage(expected_message, context)] assert validation_messages == expected_messages + + +@pytest.mark.parametrize( + "expression_string, expected_message", + [ + ( + f"{EXTERNAL_DOCUMENT_ID}:LicenseRef-007:4", + f"Too many colons in license reference: {EXTERNAL_DOCUMENT_ID}:LicenseRef-007:4. " + "A license reference must only contain a single colon to " + "separate an external document reference from the license reference.", + ), + ( + f"{EXTERNAL_DOCUMENT_ID}:unknown_license", + 'A license reference must start with "LicenseRef-", but is: unknown_license ' + f"in external license reference {EXTERNAL_DOCUMENT_ID}:unknown_license.", + ), + ( + "DocumentRef-unknown:LicenseRef-1", + 'Did not find the external document reference "DocumentRef-unknown" in the SPDX document. ' + "From the external license reference DocumentRef-unknown:LicenseRef-1.", + ), + ], +) +def test_invalid_license_expression_with_external_reference(expression_string, expected_message): + document: Document = document_fixture() + license_expression: LicenseExpression = spdx_licensing.parse(expression_string) + parent_id = "SPDXRef-File" + context = ValidationContext( + parent_id=parent_id, element_type=SpdxElementType.LICENSE_EXPRESSION, full_element=license_expression + ) + + validation_messages: List[ValidationMessage] = validate_license_expression(license_expression, document, parent_id) + expected_messages = [ValidationMessage(expected_message, context)] + + assert validation_messages == expected_messages