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