feat: detect when full license text has been provided and preserve as separate field (#3450)

* feat: add full text field to syft license struct
---------
Signed-off-by: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
Christopher Angelo Phillips 2025-05-01 15:00:46 -04:00 committed by GitHub
parent 4999de4114
commit 94e63eb367
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 3282 additions and 426 deletions

View File

@ -3,5 +3,5 @@ package internal
const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.0.26"
JSONSchemaVersion = "16.0.27"
)

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.26/document",
"$id": "anchore.io/schema/syft/json/16.0.27/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@ -1391,6 +1391,9 @@
"value": {
"type": "string"
},
"fullText": {
"type": "string"
},
"spdxExpression": {
"type": "string"
},
@ -1416,6 +1419,7 @@
"type": "object",
"required": [
"value",
"fullText",
"spdxExpression",
"type",
"urls",

View File

@ -759,13 +759,18 @@ func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
for _, id := range ids {
license := licenses[id]
value := license.Value
fullText := license.FullText
// handle cases where LicenseRef needs to be included in hasExtractedLicensingInfos
if license.Value == "" {
value, _ = strings.CutPrefix(license.ID, "LicenseRef-")
}
other := &spdx.OtherLicense{
LicenseIdentifier: license.ID,
ExtractedText: value,
}
if fullText != "" {
other.ExtractedText = fullText
} else {
other.ExtractedText = value
}
customPrefix := spdxlicense.LicenseRefPrefix + helpers.SanitizeElementID(internallicenses.UnknownLicensePrefix)
if strings.HasPrefix(license.ID, customPrefix) {

View File

@ -5,7 +5,6 @@ import (
"fmt"
"strings"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/spdxlicense"
"github.com/anchore/syft/syft/license"
"github.com/anchore/syft/syft/pkg"
@ -59,39 +58,14 @@ func joinLicenses(licenses []SPDXLicense) string {
}
type SPDXLicense struct {
ID string
Value string
ID string
Value string
FullText string
}
func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense) {
for _, l := range raw {
if l.Value == "" {
continue
}
candidate := SPDXLicense{}
if l.SPDXExpression != "" && !strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
candidate.ID = l.SPDXExpression
} else {
candidate.Value = l.Value
// we did not find a valid SPDX license ID so treat as separate license
if strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
candidate.ID = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.SPDXExpression)
if len(l.Contents) > 0 {
candidate.Value = l.Contents
}
} else {
if len(l.Value) <= 64 {
// if the license text is less than the size of the hash,
// just use it directly so the id is more readable
candidate.ID = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.Value)
} else {
hash := sha256.Sum256([]byte(l.Value))
candidate.ID = fmt.Sprintf("%s%x", spdxlicense.LicenseRefPrefix, hash)
}
}
}
candidate := createSPDXLicense(l)
switch l.Type {
case license.Concluded:
concluded = append(concluded, candidate)
@ -102,3 +76,33 @@ func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense) {
return concluded, declared
}
func createSPDXLicense(l pkg.License) SPDXLicense {
candidate := SPDXLicense{
ID: generateLicenseID(l),
FullText: l.FullText,
}
if l.SPDXExpression == "" {
candidate.Value = l.Value
}
return candidate
}
func generateLicenseID(l pkg.License) string {
if l.SPDXExpression != "" {
return l.SPDXExpression
}
if l.Value != "" {
return licenseSum(l.Value)
}
return licenseSum(l.FullText)
}
func licenseSum(s string) string {
if len(s) <= 64 {
return spdxlicense.LicenseRefPrefix + SanitizeElementID(s)
}
hash := sha256.Sum256([]byte(s))
return fmt.Sprintf("%s%x", spdxlicense.LicenseRefPrefix, hash)
}

View File

@ -0,0 +1,130 @@
{
"artifacts": [
{
"id": "25f6913140cb5286",
"name": "package-1",
"version": "1.0.1",
"type": "python",
"foundBy": "the-cataloger-1",
"locations": [
{
"path": "/somefile-1.txt",
"layerID": "sha256:100d5a55f9032faead28b7427fa3e650e4f0158f86ea89d06e1489df00cb8c6f",
"accessPath": "/somefile-1.txt"
}
],
"licenses": [
{
"value": "MIT",
"fullText": "",
"spdxExpression": "MIT",
"type": "declared",
"urls": [],
"locations": []
}
],
"language": "python",
"cpes": [
{
"cpe": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
"source": "syft-generated"
}
],
"purl": "a-purl-1",
"metadataType": "python-package",
"metadata": {
"name": "package-1",
"version": "1.0.1",
"author": "",
"authorEmail": "",
"platform": "",
"sitePackagesRootPath": ""
}
},
{
"id": "4b756c6f6fb127a3",
"name": "package-2",
"version": "2.0.1",
"type": "deb",
"foundBy": "the-cataloger-2",
"locations": [
{
"path": "/somefile-2.txt",
"layerID": "sha256:000fb9200890d3a19138478b20023023c0dce1c54352007c2863716780f049eb",
"accessPath": "/somefile-2.txt"
}
],
"licenses": [],
"language": "",
"cpes": [
{
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
"source": "nvd-cpe-dictionary"
}
],
"purl": "pkg:deb/debian/package-2@2.0.1",
"metadataType": "dpkg-db-entry",
"metadata": {
"package": "package-2",
"source": "",
"version": "2.0.1",
"sourceVersion": "",
"architecture": "",
"maintainer": "",
"installedSize": 0,
"files": null
}
}
],
"artifactRelationships": [],
"source": {
"id": "34d40fdc6ca13e9a3fa18415db216b50bff047716fae7d95a225c09732fe83fb",
"name": "user-image-input",
"version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
"type": "image",
"metadata": {
"userInput": "user-image-input",
"imageID": "sha256:bf783ea304a3f02b5c7d2ece521800f5e2182e65ed5bb5116f578e17d6e82be4",
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"tags": [
"stereoscope-fixture-image-simple:85066c51088bdd274f7a89e99e00490f666c49e72ffc955707cd6e18f0e22c5b"
],
"imageSize": 38,
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"digest": "sha256:100d5a55f9032faead28b7427fa3e650e4f0158f86ea89d06e1489df00cb8c6f",
"size": 22
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"digest": "sha256:000fb9200890d3a19138478b20023023c0dce1c54352007c2863716780f049eb",
"size": 16
}
],
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzIsImRpZ2VzdCI6InNoYTI1NjpiZjc4M2VhMzA0YTNmMDJiNWM3ZDJlY2U1MjE4MDBmNWUyMTgyZTY1ZWQ1YmI1MTE2ZjU3OGUxN2Q2ZTgyYmU0In0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjoxMDBkNWE1NWY5MDMyZmFlYWQyOGI3NDI3ZmEzZTY1MGU0ZjAxNThmODZlYTg5ZDA2ZTE0ODlkZjAwY2I4YzZmIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjAwMGZiOTIwMDg5MGQzYTE5MTM4NDc4YjIwMDIzMDIzYzBkY2UxYzU0MzUyMDA3YzI4NjM3MTY3ODBmMDQ5ZWIifV19",
"config": "eyJhcmNoaXRlY3R1cmUiOiJhcm02NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjMtMDktMjhUMTI6MjM6MzUuNDAwNjcyODg1WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIzLTA5LTI4VDEyOjIzOjM1LjM5Mzk4NjUxWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMy0wOS0yOFQxMjoyMzozNS40MDA2NzI4ODVaIiwiY3JlYXRlZF9ieSI6IkFERCBmaWxlLTIudHh0IC9zb21lZmlsZS0yLnR4dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifV0sIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1NjoxMDBkNWE1NWY5MDMyZmFlYWQyOGI3NDI3ZmEzZTY1MGU0ZjAxNThmODZlYTg5ZDA2ZTE0ODlkZjAwY2I4YzZmIiwic2hhMjU2OjAwMGZiOTIwMDg5MGQzYTE5MTM4NDc4YjIwMDIzMDIzYzBkY2UxYzU0MzUyMDA3YzI4NjM3MTY3ODBmMDQ5ZWIiXX19",
"repoDigests": [],
"architecture": "",
"os": ""
}
},
"distro": {
"prettyName": "debian",
"name": "debian",
"id": "debian",
"idLike": [
"like!"
],
"version": "1.2.3",
"versionID": "1.2.3"
},
"descriptor": {
"name": "syft",
"version": "v0.42.0-bogus",
"configuration": {
"config-key": "config-value"
}
}
}

View File

@ -1,106 +0,0 @@
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "user-image-input",
"documentNamespace":"redacted",
"creationInfo": {
"licenseListVersion":"redacted",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-v0.42.0-bogus"
],
"created":"redacted"
},
"packages": [
{
"name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"versionInfo": "1.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "MIT",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "a-purl-1"
}
]
},
{
"name": "package-2",
"SPDXID": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
"versionInfo": "2.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
}
]
},
{
"name": "user-image-input",
"SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input",
"versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"checksums": [
{
"algorithm": "SHA256",
"checksumValue": "2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
}
],
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch="
}
],
"primaryPackagePurpose": "CONTAINER"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-DOCUMENT",
"relatedSpdxElement": "SPDXRef-DocumentRoot-Image-user-image-input",
"relationshipType": "DESCRIBES"
}
]
}

View File

@ -1,246 +0,0 @@
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "user-image-input",
"documentNamespace":"redacted",
"creationInfo": {
"licenseListVersion":"redacted",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-v0.42.0-bogus"
],
"created":"redacted"
},
"packages": [
{
"name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"versionInfo": "1.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "MIT",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "a-purl-1"
}
]
},
{
"name": "package-2",
"SPDXID": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
"versionInfo": "2.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
}
]
},
{
"name": "user-image-input",
"SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input",
"versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"checksums": [
{
"algorithm": "SHA256",
"checksumValue": "2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
}
],
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch="
}
],
"primaryPackagePurpose": "CONTAINER"
}
],
"files": [
{
"fileName": "/a1/f6",
"SPDXID": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
"fileTypes": [
"OTHER"
],
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "0000000000000000000000000000000000000000"
}
],
"licenseConcluded": "NOASSERTION",
"licenseInfoInFiles": [
"NOASSERTION"
],
"copyrightText": "NOASSERTION"
},
{
"fileName": "/d1/f3",
"SPDXID": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
"fileTypes": [
"OTHER"
],
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "0000000000000000000000000000000000000000"
}
],
"licenseConcluded": "NOASSERTION",
"licenseInfoInFiles": [
"NOASSERTION"
],
"copyrightText": "NOASSERTION"
},
{
"fileName": "/d2/f4",
"SPDXID": "SPDXRef-File-d2-f4-c641caa71518099f",
"fileTypes": [
"OTHER"
],
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "0000000000000000000000000000000000000000"
}
],
"licenseConcluded": "NOASSERTION",
"licenseInfoInFiles": [
"NOASSERTION"
],
"copyrightText": "NOASSERTION"
},
{
"fileName": "/f1",
"SPDXID": "SPDXRef-File-f1-5265a4dde3edbf7c",
"fileTypes": [
"OTHER"
],
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "0000000000000000000000000000000000000000"
}
],
"licenseConcluded": "NOASSERTION",
"licenseInfoInFiles": [
"NOASSERTION"
],
"copyrightText": "NOASSERTION"
},
{
"fileName": "/f2",
"SPDXID": "SPDXRef-File-f2-f9e49132a4b96ccd",
"fileTypes": [
"OTHER"
],
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "0000000000000000000000000000000000000000"
}
],
"licenseConcluded": "NOASSERTION",
"licenseInfoInFiles": [
"NOASSERTION"
],
"copyrightText": "NOASSERTION"
},
{
"fileName": "/z1/f5",
"SPDXID": "SPDXRef-File-z1-f5-839d99ee67d9d174",
"fileTypes": [
"OTHER"
],
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "0000000000000000000000000000000000000000"
}
],
"licenseConcluded": "NOASSERTION",
"licenseInfoInFiles": [
"NOASSERTION"
],
"copyrightText": "NOASSERTION"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-DOCUMENT",
"relatedSpdxElement": "SPDXRef-DocumentRoot-Image-user-image-input",
"relationshipType": "DESCRIBES"
}
]
}

View File

@ -0,0 +1,60 @@
SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: user-image-input
DocumentNamespace: redacted
LicenseListVersion: redacted
Creator: Organization: Anchore, Inc
Creator: Tool: syft-v0.42.0-bogus
Created: redacted
##### Package: user-image-input
PackageName: user-image-input
SPDXID: SPDXRef-DocumentRoot-Image-user-image-input
PackageVersion: sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
PrimaryPackagePurpose: CONTAINER
FilesAnalyzed: false
PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
PackageLicenseConcluded: NOASSERTION
PackageLicenseDeclared: NOASSERTION
ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch=
##### Package: package-2
PackageName: package-2
SPDXID: SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
PackageVersion: 2.0.1
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false
PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
PackageLicenseConcluded: NOASSERTION
PackageLicenseDeclared: NOASSERTION
PackageCopyrightText: NOASSERTION
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1
PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-25f6913140cb5286
PackageVersion: 1.0.1
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false
PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
PackageLicenseConcluded: NOASSERTION
PackageLicenseDeclared: MIT
PackageCopyrightText: NOASSERTION
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
ExternalRef: PACKAGE-MANAGER purl a-purl-1
##### Relationships
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-25f6913140cb5286
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input

View File

@ -15,7 +15,7 @@
"packages": [
{
"name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-5a2b1ae000fcb51e",
"SPDXID": "SPDXRef-Package-python-package-1-cf21bacaa74c8c08",
"versionInfo": "1.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
@ -76,7 +76,7 @@
"relationships": [
{
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-5a2b1ae000fcb51e",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-cf21bacaa74c8c08",
"relationshipType": "CONTAINS"
},
{

View File

@ -15,7 +15,7 @@
"packages": [
{
"name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"SPDXID": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"versionInfo": "1.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
@ -90,7 +90,7 @@
"relationships": [
{
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"relationshipType": "CONTAINS"
},
{

View File

@ -15,7 +15,7 @@
"packages": [
{
"name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"SPDXID": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"versionInfo": "1.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
@ -199,38 +199,38 @@
],
"relationships": [
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"spdxElementId": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"spdxElementId": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"spdxElementId": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"spdxElementId": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"spdxElementId": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"spdxElementId": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-2d8996d6f81313df",
"relationshipType": "CONTAINS"
},
{

View File

@ -91,7 +91,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1
PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-c5cf7ac34cbca450
SPDXID: SPDXRef-Package-python-package-1-2d8996d6f81313df
PackageVersion: 1.0.1
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
@ -105,13 +105,13 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
##### Relationships
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-c5cf7ac34cbca450
Relationship: SPDXRef-Package-python-package-1-2d8996d6f81313df CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
Relationship: SPDXRef-Package-python-package-1-2d8996d6f81313df CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
Relationship: SPDXRef-Package-python-package-1-2d8996d6f81313df CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
Relationship: SPDXRef-Package-python-package-1-2d8996d6f81313df CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
Relationship: SPDXRef-Package-python-package-1-2d8996d6f81313df CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
Relationship: SPDXRef-Package-python-package-1-2d8996d6f81313df CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-2d8996d6f81313df
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input

View File

@ -38,7 +38,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1
PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-5a2b1ae000fcb51e
SPDXID: SPDXRef-Package-python-package-1-cf21bacaa74c8c08
PackageVersion: 1.0.1
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
@ -52,7 +52,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-2
##### Relationships
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-5a2b1ae000fcb51e
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-cf21bacaa74c8c08
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-39392bb5e270f669
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-some-path

View File

@ -41,7 +41,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1
PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-c5cf7ac34cbca450
SPDXID: SPDXRef-Package-python-package-1-2d8996d6f81313df
PackageVersion: 1.0.1
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
@ -55,7 +55,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
##### Relationships
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-c5cf7ac34cbca450
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-2d8996d6f81313df
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input

View File

@ -47,6 +47,7 @@ type licenses []License
type License struct {
Value string `json:"value"`
FullText string `json:"fullText"`
SPDXExpression string `json:"spdxExpression"`
Type license.Type `json:"type"`
URLs []string `json:"urls"`

View File

@ -1,7 +1,7 @@
{
"artifacts": [
{
"id": "5a2b1ae000fcb51e",
"id": "cf21bacaa74c8c08",
"name": "package-1",
"version": "1.0.1",
"type": "python",
@ -15,6 +15,7 @@
"licenses": [
{
"value": "MIT",
"fullText": "",
"spdxExpression": "MIT",
"type": "declared",
"urls": [],

View File

@ -1,7 +1,7 @@
{
"artifacts": [
{
"id": "ad3ecac55fe1c30f",
"id": "783177db0211edb6",
"name": "package-1",
"version": "1.0.1",
"type": "python",
@ -15,6 +15,7 @@
"licenses": [
{
"value": "MIT",
"fullText": "",
"spdxExpression": "MIT",
"type": "declared",
"urls": [],

View File

@ -1,7 +1,7 @@
{
"artifacts": [
{
"id": "c5cf7ac34cbca450",
"id": "2d8996d6f81313df",
"name": "package-1",
"version": "1.0.1",
"type": "python",
@ -16,6 +16,7 @@
"licenses": [
{
"value": "MIT",
"fullText": "",
"spdxExpression": "MIT",
"type": "declared",
"urls": [],

View File

@ -230,6 +230,7 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
modelLicenses = append(modelLicenses, model.License{
Value: l.Value,
FullText: l.FullText,
SPDXExpression: l.SPDXExpression,
Type: l.Type,
URLs: urls,

View File

@ -25,9 +25,15 @@ var _ sort.Interface = (*Licenses)(nil)
// in order to distinguish if packages should be kept separate
// this is different for licenses since we're only looking for evidence
// of where a license was declared/concluded for a given package
// If a license is given as it's full text in the metadata rather than it's value or SPDX expression
// The FullText field is used to represent this data
// A Concluded License type is the license the SBOM creator believes governs the package (human crafted or altered SBOM)
// The Declared License is what the authors of a project believe govern the package. This is the default type syft declares.
type License struct {
Value string
SPDXExpression string
Value string
FullText string
Type license.Type
URLs []string `hash:"ignore"`
Locations file.LocationSet `hash:"ignore"`
@ -68,8 +74,16 @@ func NewLicense(value string) License {
}
func NewLicenseFromType(value string, t license.Type) License {
var spdxExpression string
if value != "" {
var (
spdxExpression string
fullText string
)
// Check parsed value for newline character to see if it's the full license text
// License: <HERE IS THE FULL TEXT> <Expressions>
// DO we want to also submit file name when determining fulltext
if strings.Contains(strings.TrimSpace(value), "\n") {
fullText = value
} else {
var err error
spdxExpression, err = license.ParseExpression(value)
if err != nil {
@ -77,6 +91,14 @@ func NewLicenseFromType(value string, t license.Type) License {
}
}
if fullText != "" {
return License{
FullText: fullText,
Type: t,
Locations: file.NewLocationSet(),
}
}
return License{
Value: value,
SPDXExpression: spdxExpression,
@ -99,7 +121,7 @@ func NewLicensesFromLocation(location file.Location, values ...string) (licenses
}
licenses = append(licenses, NewLicenseFromLocations(v, location))
}
return
return licenses
}
func NewLicenseFromLocations(value string, locations ...file.Location) License {
@ -157,6 +179,10 @@ func NewLicenseFromFields(value, url string, location *file.Location) License {
return l
}
func (s License) Empty() bool {
return s.Value == "" && s.SPDXExpression == "" && s.FullText == ""
}
// Merge two licenses into a new license object. If the merge is not possible due to unmergeable fields
// (e.g. different values for Value, SPDXExpression, Type, or any non-collection type) an error is returned.
// TODO: this is a bit of a hack to not infinitely recurse when hashing a license

View File

@ -50,15 +50,16 @@ func (s *LicenseSet) Add(licenses ...License) {
s.set = make(map[artifact.ID]License)
}
for _, l := range licenses {
// we only want to add licenses that have a value
// we only want to add licenses that are not empty
if l.Empty() {
continue
}
// note, this check should be moved to the license constructor in the future
if l.Value != "" {
if id, merged, err := s.addToExisting(l); err == nil && !merged {
// doesn't exist, add it
s.set[id] = l
} else if err != nil {
log.Trace("license set failed to add license %#v: %+v", l, err)
}
if id, merged, err := s.addToExisting(l); err == nil && !merged {
// doesn't exist, add it
s.set[id] = l
} else if err != nil {
log.WithFields("error", err, "license", l).Trace("failed to add license to license set")
}
}
}

View File

@ -13,7 +13,6 @@ import (
)
func Test_Hash(t *testing.T) {
loc1 := file.NewLocation("place!")
loc1.FileSystemID = "fs1"
loc2 := file.NewLocation("place!")
@ -227,6 +226,33 @@ func TestLicense_Merge(t *testing.T) {
}
}
func TestFullText(t *testing.T) {
fullText := `I am a license with full text
my authors put new line characters in metadata for labeling a license`
tests := []struct {
name string
value string
want License
}{
{
name: "Full Text field is populated with the correct full text",
value: fullText,
want: License{
Value: "",
Type: license.Declared,
FullText: fullText,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewLicense(tt.value)
assert.Equal(t, tt.want, got)
})
}
}
func TestLicenseConstructors(t *testing.T) {
type input struct {
value string
@ -244,7 +270,6 @@ func TestLicenseConstructors(t *testing.T) {
urls: []string{
`
http://user-agent-utils.googlecode.com/svn/trunk/UserAgentUtils/LICENSE.txt
`},
},
expected: License{