Files
depaudit-cli/depaudit/checks/licenses.py

133 lines
3.3 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from depaudit.checks import LicenseInfo
SPDX_LICENSES = {
"MIT": "mit",
"Apache-2.0": "apache-2.0",
"Apache-2": "apache-2.0",
"BSD-2-Clause": "bsd-2-clause",
"BSD-3-Clause": "bsd-3-clause",
"BSD": "bsd",
"ISC": "isc",
"GPL-2.0": "gpl-2.0",
"GPL-3.0": "gpl-3.0",
"GPL-3": "gpl-3.0",
"LGPL-2.1": "lgpl-2.1",
"LGPL-3.0": "lgpl-3.0",
"MPL-2.0": "mpl-2.0",
"MPL-2": "mpl-2.0",
"AGPL-3.0": "agpl-3.0",
"AGPL-3": "agpl-3.0",
"OSL-3.0": "osl-3.0",
"CC0": "cc0-1.0",
"CC-BY-4.0": "cc-by-4.0",
"CC-BY-SA-4.0": "cc-by-sa-4.0",
}
RESTRICTIVE_LICENSES = {
"GPL-1.0",
"GPL-2.0",
"GPL-3.0",
"AGPL-1.0",
"AGPL-3.0",
"SSPL",
"OSL-1.0",
"OSL-2.0",
"OSL-3.0",
"QPL-1.0",
"CPOL-1.02",
}
PERMISSIVE_LICENSES = {
"MIT",
"Apache-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
"MPL-2.0",
"CC0",
"Unlicense",
}
def normalize_license(license_str: str) -> str:
license_str = license_str.strip()
if "OR" in license_str or "AND" in license_str:
licenses = []
for lic in license_str.replace("(", "").replace(")", "").split():
if lic in SPDX_LICENSES:
licenses.append(SPDX_LICENSES[lic])
elif lic.upper() in SPDX_LICENSES:
licenses.append(SPDX_LICENSES[lic.upper()])
return " AND ".join(licenses) if licenses else license_str
if license_str.upper() in SPDX_LICENSES:
return SPDX_LICENSES[license_str.upper()]
for spdx_name, alias in SPDX_LICENSES.items():
if license_str.lower() == spdx_name.lower() or license_str.lower() == alias.lower():
return alias
return license_str
def check_license(
package_name: str,
license_str: str | None,
source: str = "unknown",
allowlist: list[str] | None = None,
blocklist: list[str] | None = None,
) -> LicenseInfo:
allowlist = allowlist or []
blocklist = blocklist or []
normalized = None
if license_str:
normalized = normalize_license(license_str)
is_spdx = normalized in SPDX_LICENSES.values() if normalized else False
license_info = LicenseInfo(
package_name=package_name,
license_type=normalized or license_str,
license_expression=None,
source=source,
is_spdx_compliant=is_spdx,
)
if normalized in RESTRICTIVE_LICENSES:
license_info.license_expression = "restricted"
return license_info
def validate_license_compliance(
license_info: LicenseInfo,
allowlist: list[str],
blocklist: list[str],
) -> tuple[bool, str]:
license_type = license_info.license_type
if license_type is None:
return False, "Unknown license"
normalized = normalize_license(license_type)
normalized_blocklist = {normalize_license(lic) for lic in blocklist}
normalized_allowlist = {normalize_license(lic) for lic in allowlist}
if normalized in normalized_blocklist:
return False, f"License {normalized} is in blocklist"
if normalized in normalized_allowlist:
return True, "License is allowed"
if normalized in SPDX_LICENSES.values():
return True, "SPDX compliant license"
return False, f"License {license_type} is not in allowlist"