Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPDX-2.2 validation #490

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/spdx/clitools/pyspdxtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
@click.command()
@click.option("--infile", "-i", help="The file containing the document to be validated or converted.")
@click.option("--outfile", "-o", help="The file to write the converted document to (write a dash for output to stdout or omit for no conversion).")
@click.option("--version", help='The SPDX version to be used during parsing and validation (format "SPDX-2.3"). Will be read from the document if not provided.', default=None)
@click.option("--version", help='The SPDX version to be used during parsing and validation ("SPDX-2.2" or "SPDX-2.3"). Will be read from the document if not provided.', default=None)
@click.option("--novalidation", is_flag=True, help="Don't validate the provided document.")
def main(infile: str, outfile: str, version: str, novalidation: bool):
"""
Expand All @@ -45,6 +45,11 @@ def main(infile: str, outfile: str, version: str, novalidation: bool):
if not version:
version = document.creation_info.spdx_version

if not version in ["SPDX-2.2", "SPDX-2.3"]:
print(f"This tool only supports SPDX versions SPDX-2.2 and SPDX-2.3, but got: {version}",
file=sys.stderr)
sys.exit(1)

validation_messages: List[ValidationMessage] = validate_full_spdx_document(document, version)
if validation_messages:
print("The document is invalid. The following issues have been found:", file=sys.stderr)
Expand Down
16 changes: 13 additions & 3 deletions src/spdx/validation/checksum_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,30 @@
}


def validate_checksums(checksums: List[Checksum], parent_id: str) -> List[ValidationMessage]:
def validate_checksums(checksums: List[Checksum], parent_id: str, spdx_version: str) -> List[ValidationMessage]:
validation_messages = []
for checksum in checksums:
validation_messages.extend(validate_checksum(checksum, parent_id))
validation_messages.extend(validate_checksum(checksum, parent_id, spdx_version))

return validation_messages


def validate_checksum(checksum: Checksum, parent_id: str) -> List[ValidationMessage]:
def validate_checksum(checksum: Checksum, parent_id: str, spdx_version: str) -> List[ValidationMessage]:
validation_messages = []
algorithm = checksum.algorithm
context = ValidationContext(parent_id=parent_id, element_type=SpdxElementType.CHECKSUM,
full_element=checksum)

if spdx_version == "SPDX-2.2" and algorithm in [ChecksumAlgorithm.SHA3_512,
ChecksumAlgorithm.SHA3_384,
ChecksumAlgorithm.SHA3_256,
ChecksumAlgorithm.BLAKE3,
ChecksumAlgorithm.BLAKE2B_512,
ChecksumAlgorithm.BLAKE2B_384,
ChecksumAlgorithm.BLAKE2B_256,
ChecksumAlgorithm.ADLER32]:
return [ValidationMessage(f"{checksum.algorithm.name} is not supported in SPDX-2.2", context)]

if not re.match("^[0-9a-f]{" + algorithm_length[algorithm] + "}$", checksum.value):
if algorithm == ChecksumAlgorithm.BLAKE3:
length = "at least 256"
Expand Down
6 changes: 3 additions & 3 deletions src/spdx/validation/creation_info_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from spdx.validation.validation_message import ValidationMessage, ValidationContext, SpdxElementType


def validate_creation_info(creation_info: CreationInfo) -> List[ValidationMessage]:
def validate_creation_info(creation_info: CreationInfo, spdx_version: str) -> List[ValidationMessage]:
validation_messages: List[ValidationMessage] = []

context = ValidationContext(spdx_id=creation_info.spdx_id, element_type=SpdxElementType.DOCUMENT)
Expand Down Expand Up @@ -47,7 +47,7 @@ def validate_creation_info(creation_info: CreationInfo) -> List[ValidationMessag
)

validation_messages.extend(validate_actors(creation_info.creators, creation_info.spdx_id))

validation_messages.extend(validate_external_document_refs(creation_info.external_document_refs, creation_info.spdx_id))
validation_messages.extend(
validate_external_document_refs(creation_info.external_document_refs, creation_info.spdx_id, spdx_version))

return validation_messages
15 changes: 7 additions & 8 deletions src/spdx/validation/document_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
from typing import List

from spdx.model.document import Document
Expand All @@ -34,10 +33,10 @@ def validate_full_spdx_document(document: Document, spdx_version: str = None) ->
if not spdx_version:
spdx_version = document_version

if not re.match(r"^SPDX-\d+.\d+$", document_version):
if document_version not in ["SPDX-2.2", "SPDX-2.3"]:
validation_messages.append(
ValidationMessage(
f'the document\'s spdx_version must be of the form "SPDX-[major].[minor]" but is: {document_version}',
f'only SPDX versions "SPDX-2.2" and "SPDX-2.3" are supported, but the document\'s spdx_version is: {document_version}',
context
)
)
Expand All @@ -53,12 +52,12 @@ def validate_full_spdx_document(document: Document, spdx_version: str = None) ->
"the validation process has been cancelled.", context))
return validation_messages

validation_messages.extend(validate_creation_info(document.creation_info))
validation_messages.extend(validate_packages(document.packages, document))
validation_messages.extend(validate_files(document.files, document))
validation_messages.extend(validate_snippets(document.snippets, document))
validation_messages.extend(validate_creation_info(document.creation_info, spdx_version))
validation_messages.extend(validate_packages(document.packages, spdx_version, document))
validation_messages.extend(validate_files(document.files, spdx_version, document))
validation_messages.extend(validate_snippets(document.snippets, spdx_version, document))
validation_messages.extend(validate_annotations(document.annotations, document))
validation_messages.extend(validate_relationships(document.relationships, document, spdx_version))
validation_messages.extend(validate_relationships(document.relationships, spdx_version, document))
validation_messages.extend(validate_extracted_licensing_infos(document.extracted_licensing_info))

document_id = document.creation_info.spdx_id
Expand Down
11 changes: 6 additions & 5 deletions src/spdx/validation/external_document_ref_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@
from spdx.validation.validation_message import ValidationMessage, ValidationContext, SpdxElementType


def validate_external_document_refs(external_document_refs: List[ExternalDocumentRef], parent_id: str) -> List[
ValidationMessage]:
def validate_external_document_refs(external_document_refs: List[ExternalDocumentRef], parent_id: str,
spdx_version: str) -> List[ValidationMessage]:
validation_messages = []
for external_document_ref in external_document_refs:
validation_messages.extend(validate_external_document_ref(external_document_ref, parent_id))
validation_messages.extend(validate_external_document_ref(external_document_ref, parent_id, spdx_version))

return validation_messages


def validate_external_document_ref(external_document_ref: ExternalDocumentRef, parent_id: str) -> List[ValidationMessage]:
def validate_external_document_ref(external_document_ref: ExternalDocumentRef, parent_id: str, spdx_version: str) -> \
List[ValidationMessage]:
validation_messages = []
context = ValidationContext(parent_id=parent_id, element_type=SpdxElementType.EXTERNAL_DOCUMENT_REF,
full_element=external_document_ref)
Expand All @@ -47,6 +48,6 @@ def validate_external_document_ref(external_document_ref: ExternalDocumentRef, p
)
)

validation_messages.extend(validate_checksum(external_document_ref.checksum, parent_id))
validation_messages.extend(validate_checksum(external_document_ref.checksum, parent_id, spdx_version))

return validation_messages
45 changes: 25 additions & 20 deletions src/spdx/validation/external_package_ref_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import uritools

from spdx.model.package import ExternalPackageRef, ExternalPackageRefCategory, CATEGORY_TO_EXTERNAL_PACKAGE_REF_TYPES
from spdx.validation.uri_validators import validate_url, validate_uri
from spdx.validation.uri_validators import validate_url
from spdx.validation.validation_message import ValidationMessage, ValidationContext, SpdxElementType

CPE22TYPE_REGEX = r'^c[pP][eE]:/[AHOaho]?(:[A-Za-z0-9._\-~%]*){0,6}$'
Expand All @@ -40,16 +40,18 @@
}


def validate_external_package_refs(external_package_refs: List[ExternalPackageRef], parent_id: str) -> List[
ValidationMessage]:
def validate_external_package_refs(external_package_refs: List[ExternalPackageRef], parent_id: str,
spdx_version: str) -> List[ValidationMessage]:
validation_messages = []
for external_package_ref in external_package_refs:
validation_messages.extend(validate_external_package_ref(external_package_ref, parent_id))
validation_messages.extend(validate_external_package_ref(external_package_ref, parent_id, spdx_version))

return validation_messages


def validate_external_package_ref(external_package_ref: ExternalPackageRef, parent_id: str) -> List[ValidationMessage]:
def validate_external_package_ref(external_package_ref: ExternalPackageRef, parent_id: str, spdx_version: str) -> List[
ValidationMessage]:
validation_messages = []
context = ValidationContext(parent_id=parent_id, element_type=SpdxElementType.EXTERNAL_PACKAGE_REF,
full_element=external_package_ref)

Expand All @@ -59,31 +61,34 @@ def validate_external_package_ref(external_package_ref: ExternalPackageRef, pare

if category == ExternalPackageRefCategory.OTHER:
if " " in locator:
return [ValidationMessage(
validation_messages.append(ValidationMessage(
f"externalPackageRef locator in category OTHER must contain no spaces, but is: {locator}",
context)]
return []
context))

if reference_type not in CATEGORY_TO_EXTERNAL_PACKAGE_REF_TYPES[category]:
return [ValidationMessage(
elif spdx_version == "SPDX-2.2" and reference_type in ["advisory", "fix", "url", "swid"]:
return [ValidationMessage(f'externalPackageRef type "{reference_type}" is not supported in SPDX-2.2', context)]

elif reference_type not in CATEGORY_TO_EXTERNAL_PACKAGE_REF_TYPES[category]:
validation_messages.append(ValidationMessage(
f"externalPackageRef type in category {category.name} must be one of {CATEGORY_TO_EXTERNAL_PACKAGE_REF_TYPES[category]}, but is: {reference_type}",
context)]
context))

if reference_type in ["advisory", "fix", "url"]:
elif reference_type in ["advisory", "fix", "url"]:
if validate_url(locator):
return [ValidationMessage(
validation_messages.append(ValidationMessage(
f'externalPackageRef locator of type "{reference_type}" must be a valid URL, but is: {locator}',
context)]
return []
context))

if reference_type == "swid":
elif reference_type == "swid":
if not uritools.isuri(locator) or not locator.startswith("swid"):
return [ValidationMessage(
validation_messages.append(ValidationMessage(
f'externalPackageRef locator of type "swid" must be a valid URI with scheme swid, but is: {locator}',
context)]
return []
context))

return validate_against_regex(locator, reference_type, context)
else:
validation_messages.extend(validate_against_regex(locator, reference_type, context))

return validation_messages


def validate_against_regex(string_to_validate: str, reference_type: str, context: ValidationContext) -> List[
Expand Down
3 changes: 2 additions & 1 deletion src/spdx/validation/extracted_licensing_info_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from spdx.validation.validation_message import ValidationMessage, ValidationContext, SpdxElementType


def validate_extracted_licensing_infos(extracted_licensing_infos: Optional[List[ExtractedLicensingInfo]]) -> List[ValidationMessage]:
def validate_extracted_licensing_infos(extracted_licensing_infos: Optional[List[ExtractedLicensingInfo]]) -> List[
ValidationMessage]:
validation_messages = []
for extracted_licensing_info in extracted_licensing_infos:
validation_messages.extend(validate_extracted_licensing_info(extracted_licensing_info))
Expand Down
27 changes: 20 additions & 7 deletions src/spdx/validation/file_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,34 @@
from spdx.validation.validation_message import ValidationMessage, ValidationContext, SpdxElementType


def validate_files(files: List[File], document: Optional[Document] = None) -> List[ValidationMessage]:
def validate_files(files: List[File], spdx_version: str, document: Optional[Document] = None) -> List[
ValidationMessage]:
validation_messages = []
if document:
for file in files:
validation_messages.extend(validate_file_within_document(file, document))
validation_messages.extend(validate_file_within_document(file, spdx_version, document))
else:
for file in files:
validation_messages.extend(validate_file(file))
validation_messages.extend(validate_file(file, spdx_version))

return validation_messages


def validate_file_within_document(file: File, document: Document) -> List[ValidationMessage]:
def validate_file_within_document(file: File, spdx_version: str, document: Document) -> List[ValidationMessage]:
validation_messages: List[ValidationMessage] = []
context = ValidationContext(spdx_id=file.spdx_id, parent_id=document.creation_info.spdx_id,
element_type=SpdxElementType.FILE, full_element=file)

for message in validate_spdx_id(file.spdx_id, document):
validation_messages.append(ValidationMessage(message, context))

validation_messages.extend(validate_file(file, context))
validation_messages.extend(validate_file(file, spdx_version, context))

return validation_messages


def validate_file(file: File, context: Optional[ValidationContext] = None) -> List[ValidationMessage]:
def validate_file(file: File, spdx_version: str, context: Optional[ValidationContext] = None) -> List[
ValidationMessage]:
validation_messages = []
if not context:
context = ValidationContext(spdx_id=file.spdx_id, element_type=SpdxElementType.FILE, full_element=file)
Expand All @@ -63,10 +65,21 @@ def validate_file(file: File, context: Optional[ValidationContext] = None) -> Li
context)
)

validation_messages.extend(validate_checksums(file.checksums, file.spdx_id))
validation_messages.extend(validate_checksums(file.checksums, file.spdx_id, spdx_version))

validation_messages.extend(validate_license_expression(file.license_concluded))

validation_messages.extend(validate_license_expressions(file.license_info_in_file))

if spdx_version == "SPDX-2.2":
if file.license_concluded is None:
validation_messages.append(
ValidationMessage(f"license_concluded is mandatory in SPDX-2.2", context))
if not file.license_info_in_file:
validation_messages.append(
ValidationMessage(f"license_info_in_file is mandatory in SPDX-2.2", context))
if file.copyright_text is None:
validation_messages.append(
ValidationMessage(f"copyright_text is mandatory in SPDX-2.2", context))

return validation_messages
44 changes: 36 additions & 8 deletions src/spdx/validation/package_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,21 @@
from spdx.validation.validation_message import ValidationMessage, ValidationContext, SpdxElementType


def validate_packages(packages: List[Package], document: Optional[Document] = None) -> List[ValidationMessage]:
def validate_packages(packages: List[Package], spdx_version: str, document: Optional[Document] = None) -> List[
ValidationMessage]:
validation_messages: List[ValidationMessage] = []
if document:
for package in packages:
validation_messages.extend(validate_package_within_document(package, document))
validation_messages.extend(validate_package_within_document(package, spdx_version, document))
else:
for package in packages:
validation_messages.extend(validate_package(package))
validation_messages.extend(validate_package(package, spdx_version))

return validation_messages


def validate_package_within_document(package: Package, document: Document) -> List[ValidationMessage]:
def validate_package_within_document(package: Package, spdx_version: str, document: Document) -> List[
ValidationMessage]:
validation_messages: List[ValidationMessage] = []
context = ValidationContext(spdx_id=package.spdx_id, parent_id=document.creation_info.spdx_id,
element_type=SpdxElementType.PACKAGE, full_element=package)
Expand All @@ -59,12 +61,13 @@ def validate_package_within_document(package: Package, document: Document) -> Li
context)
)

validation_messages.extend(validate_package(package, context))
validation_messages.extend(validate_package(package, spdx_version, context))

return validation_messages


def validate_package(package: Package, context: Optional[ValidationContext] = None) -> List[ValidationMessage]:
def validate_package(package: Package, spdx_version: str, context: Optional[ValidationContext] = None) -> List[
ValidationMessage]:
validation_messages: List[ValidationMessage] = []
if not context:
context = ValidationContext(spdx_id=package.spdx_id, element_type=SpdxElementType.PACKAGE, full_element=package)
Expand All @@ -89,7 +92,7 @@ def validate_package(package: Package, context: Optional[ValidationContext] = No
else:
validation_messages.extend(validate_verification_code(verification_code, package.spdx_id))

validation_messages.extend(validate_checksums(package.checksums, package.spdx_id))
validation_messages.extend(validate_checksums(package.checksums, package.spdx_id, spdx_version))

validation_messages.extend(validate_license_expression(package.license_concluded))

Expand All @@ -106,6 +109,31 @@ def validate_package(package: Package, context: Optional[ValidationContext] = No

validation_messages.extend(validate_license_expression(package.license_declared))

validation_messages.extend(validate_external_package_refs(package.external_references, package.spdx_id))
validation_messages.extend(
validate_external_package_refs(package.external_references, package.spdx_id, spdx_version))

if spdx_version == "SPDX-2.2":
if package.primary_package_purpose is not None:
validation_messages.append(
ValidationMessage(f"primary_package_purpose is not supported in SPDX-2.2", context))
if package.built_date is not None:
validation_messages.append(
ValidationMessage(f"built_date is not supported in SPDX-2.2", context))
if package.release_date is not None:
validation_messages.append(
ValidationMessage(f"release_date is not supported in SPDX-2.2", context))
if package.valid_until_date is not None:
validation_messages.append(
ValidationMessage(f"valid_until_date is not supported in SPDX-2.2", context))

if package.license_concluded is None:
validation_messages.append(
ValidationMessage(f"license_concluded is mandatory in SPDX-2.2", context))
if package.license_declared is None:
validation_messages.append(
ValidationMessage(f"license_declared is mandatory in SPDX-2.2", context))
if package.copyright_text is None:
validation_messages.append(
ValidationMessage(f"copyright_text is mandatory in SPDX-2.2", context))

return validation_messages
Loading