1# 2# Copyright OpenEmbedded Contributors 3# 4# SPDX-License-Identifier: GPL-2.0-only 5# 6 7# 8# This library is intended to capture the JSON SPDX specification in a type 9# safe manner. It is not intended to encode any particular OE specific 10# behaviors, see the sbom.py for that. 11# 12# The documented SPDX spec document doesn't cover the JSON syntax for 13# particular configuration, which can make it hard to determine what the JSON 14# syntax should be. I've found it is actually much simpler to read the official 15# SPDX JSON schema which can be found here: https://github.com/spdx/spdx-spec 16# in schemas/spdx-schema.json 17# 18 19import hashlib 20import itertools 21import json 22 23SPDX_VERSION = "2.2" 24 25 26# 27# The following are the support classes that are used to implement SPDX object 28# 29 30class _Property(object): 31 """ 32 A generic SPDX object property. The different types will derive from this 33 class 34 """ 35 36 def __init__(self, *, default=None): 37 self.default = default 38 39 def setdefault(self, dest, name): 40 if self.default is not None: 41 dest.setdefault(name, self.default) 42 43 44class _String(_Property): 45 """ 46 A scalar string property for an SPDX object 47 """ 48 49 def __init__(self, **kwargs): 50 super().__init__(**kwargs) 51 52 def set_property(self, attrs, name): 53 def get_helper(obj): 54 return obj._spdx[name] 55 56 def set_helper(obj, value): 57 obj._spdx[name] = value 58 59 def del_helper(obj): 60 del obj._spdx[name] 61 62 attrs[name] = property(get_helper, set_helper, del_helper) 63 64 def init(self, source): 65 return source 66 67 68class _Object(_Property): 69 """ 70 A scalar SPDX object property of a SPDX object 71 """ 72 73 def __init__(self, cls, **kwargs): 74 super().__init__(**kwargs) 75 self.cls = cls 76 77 def set_property(self, attrs, name): 78 def get_helper(obj): 79 if not name in obj._spdx: 80 obj._spdx[name] = self.cls() 81 return obj._spdx[name] 82 83 def set_helper(obj, value): 84 obj._spdx[name] = value 85 86 def del_helper(obj): 87 del obj._spdx[name] 88 89 attrs[name] = property(get_helper, set_helper) 90 91 def init(self, source): 92 return self.cls(**source) 93 94 95class _ListProperty(_Property): 96 """ 97 A list of SPDX properties 98 """ 99 100 def __init__(self, prop, **kwargs): 101 super().__init__(**kwargs) 102 self.prop = prop 103 104 def set_property(self, attrs, name): 105 def get_helper(obj): 106 if not name in obj._spdx: 107 obj._spdx[name] = [] 108 return obj._spdx[name] 109 110 def set_helper(obj, value): 111 obj._spdx[name] = list(value) 112 113 def del_helper(obj): 114 del obj._spdx[name] 115 116 attrs[name] = property(get_helper, set_helper, del_helper) 117 118 def init(self, source): 119 return [self.prop.init(o) for o in source] 120 121 122class _StringList(_ListProperty): 123 """ 124 A list of strings as a property for an SPDX object 125 """ 126 127 def __init__(self, **kwargs): 128 super().__init__(_String(), **kwargs) 129 130 131class _ObjectList(_ListProperty): 132 """ 133 A list of SPDX objects as a property for an SPDX object 134 """ 135 136 def __init__(self, cls, **kwargs): 137 super().__init__(_Object(cls), **kwargs) 138 139 140class MetaSPDXObject(type): 141 """ 142 A metaclass that allows properties (anything derived from a _Property 143 class) to be defined for a SPDX object 144 """ 145 def __new__(mcls, name, bases, attrs): 146 attrs["_properties"] = {} 147 148 for key in attrs.keys(): 149 if isinstance(attrs[key], _Property): 150 prop = attrs[key] 151 attrs["_properties"][key] = prop 152 prop.set_property(attrs, key) 153 154 return super().__new__(mcls, name, bases, attrs) 155 156 157class SPDXObject(metaclass=MetaSPDXObject): 158 """ 159 The base SPDX object; all SPDX spec classes must derive from this class 160 """ 161 def __init__(self, **d): 162 self._spdx = {} 163 164 for name, prop in self._properties.items(): 165 prop.setdefault(self._spdx, name) 166 if name in d: 167 self._spdx[name] = prop.init(d[name]) 168 169 def serializer(self): 170 return self._spdx 171 172 def __setattr__(self, name, value): 173 if name in self._properties or name == "_spdx": 174 super().__setattr__(name, value) 175 return 176 raise KeyError("%r is not a valid SPDX property" % name) 177 178# 179# These are the SPDX objects implemented from the spec. The *only* properties 180# that can be added to these objects are ones directly specified in the SPDX 181# spec, however you may add helper functions to make operations easier. 182# 183# Defaults should *only* be specified if the SPDX spec says there is a certain 184# required value for a field (e.g. dataLicense), or if the field is mandatory 185# and has some sane "this field is unknown" (e.g. "NOASSERTION") 186# 187 188class SPDXAnnotation(SPDXObject): 189 annotationDate = _String() 190 annotationType = _String() 191 annotator = _String() 192 comment = _String() 193 194class SPDXChecksum(SPDXObject): 195 algorithm = _String() 196 checksumValue = _String() 197 198 199class SPDXRelationship(SPDXObject): 200 spdxElementId = _String() 201 relatedSpdxElement = _String() 202 relationshipType = _String() 203 comment = _String() 204 annotations = _ObjectList(SPDXAnnotation) 205 206 207class SPDXExternalReference(SPDXObject): 208 referenceCategory = _String() 209 referenceType = _String() 210 referenceLocator = _String() 211 212 213class SPDXPackageVerificationCode(SPDXObject): 214 packageVerificationCodeValue = _String() 215 packageVerificationCodeExcludedFiles = _StringList() 216 217 218class SPDXPackage(SPDXObject): 219 name = _String() 220 SPDXID = _String() 221 versionInfo = _String() 222 downloadLocation = _String(default="NOASSERTION") 223 supplier = _String(default="NOASSERTION") 224 homepage = _String() 225 licenseConcluded = _String(default="NOASSERTION") 226 licenseDeclared = _String(default="NOASSERTION") 227 summary = _String() 228 description = _String() 229 sourceInfo = _String() 230 copyrightText = _String(default="NOASSERTION") 231 licenseInfoFromFiles = _StringList(default=["NOASSERTION"]) 232 externalRefs = _ObjectList(SPDXExternalReference) 233 packageVerificationCode = _Object(SPDXPackageVerificationCode) 234 hasFiles = _StringList() 235 packageFileName = _String() 236 annotations = _ObjectList(SPDXAnnotation) 237 238 239class SPDXFile(SPDXObject): 240 SPDXID = _String() 241 fileName = _String() 242 licenseConcluded = _String(default="NOASSERTION") 243 copyrightText = _String(default="NOASSERTION") 244 licenseInfoInFiles = _StringList(default=["NOASSERTION"]) 245 checksums = _ObjectList(SPDXChecksum) 246 fileTypes = _StringList() 247 248 249class SPDXCreationInfo(SPDXObject): 250 created = _String() 251 licenseListVersion = _String() 252 comment = _String() 253 creators = _StringList() 254 255 256class SPDXExternalDocumentRef(SPDXObject): 257 externalDocumentId = _String() 258 spdxDocument = _String() 259 checksum = _Object(SPDXChecksum) 260 261 262class SPDXExtractedLicensingInfo(SPDXObject): 263 name = _String() 264 comment = _String() 265 licenseId = _String() 266 extractedText = _String() 267 268 269class SPDXDocument(SPDXObject): 270 spdxVersion = _String(default="SPDX-" + SPDX_VERSION) 271 dataLicense = _String(default="CC0-1.0") 272 SPDXID = _String(default="SPDXRef-DOCUMENT") 273 name = _String() 274 documentNamespace = _String() 275 creationInfo = _Object(SPDXCreationInfo) 276 packages = _ObjectList(SPDXPackage) 277 files = _ObjectList(SPDXFile) 278 relationships = _ObjectList(SPDXRelationship) 279 externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef) 280 hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo) 281 282 def __init__(self, **d): 283 super().__init__(**d) 284 285 def to_json(self, f, *, sort_keys=False, indent=None, separators=None): 286 class Encoder(json.JSONEncoder): 287 def default(self, o): 288 if isinstance(o, SPDXObject): 289 return o.serializer() 290 291 return super().default(o) 292 293 sha1 = hashlib.sha1() 294 for chunk in Encoder( 295 sort_keys=sort_keys, 296 indent=indent, 297 separators=separators, 298 ).iterencode(self): 299 chunk = chunk.encode("utf-8") 300 f.write(chunk) 301 sha1.update(chunk) 302 303 return sha1.hexdigest() 304 305 @classmethod 306 def from_json(cls, f): 307 return cls(**json.load(f)) 308 309 def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None): 310 if isinstance(_from, SPDXObject): 311 from_spdxid = _from.SPDXID 312 else: 313 from_spdxid = _from 314 315 if isinstance(_to, SPDXObject): 316 to_spdxid = _to.SPDXID 317 else: 318 to_spdxid = _to 319 320 r = SPDXRelationship( 321 spdxElementId=from_spdxid, 322 relatedSpdxElement=to_spdxid, 323 relationshipType=relationship, 324 ) 325 326 if comment is not None: 327 r.comment = comment 328 329 if annotation is not None: 330 r.annotations.append(annotation) 331 332 self.relationships.append(r) 333 334 def find_by_spdxid(self, spdxid): 335 for o in itertools.chain(self.packages, self.files): 336 if o.SPDXID == spdxid: 337 return o 338 return None 339 340 def find_external_document_ref(self, namespace): 341 for r in self.externalDocumentRefs: 342 if r.spdxDocument == namespace: 343 return r 344 return None 345