Mercurial > farol > farolluz
view farolluz/cvrf.py @ 8:cb8b2a196f0b
Allow Name and Organization in anAcknowledgment to be plural
author | Benoît Allard <benoit.allard@greenbone.net> |
---|---|
date | Wed, 08 Oct 2014 12:43:34 +0200 |
parents | c924c15bd110 |
children | db2a02fff101 |
line wrap: on
line source
# -*- coding: utf-8 -*- # # Authors: # BenoƮt Allard <benoit.allard@greenbone.net> # # Copyright: # Copyright (C) 2014 Greenbone Networks GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. """\ Objects related to CVRF Documents """ class ValidationError(Exception): pass class CVRFPublisher(object): TYPES = ('Vendor', 'Discoverer', 'Coordinator', 'User', 'Other') def __init__(self, _type, vendorid=None): self._type = _type self._vendorid = vendorid self._contact = None self._authority = None def setContact(self, contact): self._contact = contact def setAuthority(self, authority): self._authority = authority def validate(self): if not self._type: raise ValidationError('Document Publisher needs to have a type') if self._type not in self.TYPES: raise ValidationError('Document Publisher Type needs to be one of %s' % ', '.join(self.TYPES)) def __str__(self): s = 'CVRFPublisher: %s' % self._type if self._vendorid is not None: s += ' ID: %s' % self._vendorid if self._contact is not None: s += ' Contact: "%s"' % self._contact if self._authority is not None: s += ' Authority: "%s"' % self._authority return s class CVRFTrackingID(object): def __init__(self, _id): self._id = _id self._aliases = [] def addAlias(self, alias): self._aliases.append(alias) def getId(self): return self._id def validate(self): if not self._id: raise ValidationError('Document ID cannot be left empty') def __str__(self): if self._aliases: return "%s (%s)" % (self._id, ', '.join(self._aliases)) return self._id class CVRFTracking(object): STATUSES = ('Draft', 'Interim', 'Final') def __init__(self, _id, status, version, initial, current): self._identification = _id self._status = status self._version = version self._history = [] self._initialDate = initial self._currentDate = current self._generator = None def addRevision(self, revision): self._history.append(revision) def setGenerator(self, generator): self._generator = generator def getId(self): return self._identification.getId() def validate(self): if self._identification is None: raise ValidationError('Document Tracking needs to have an Identification') self._identification.validate() if not self._status: raise ValidationError('Document status must be set') if self._status not in self.STATUSES: raise ValidationError('Document Status must be one of %s' % ', '.join(self.STATUSES)) if not self._version: raise ValidationError('Document Version must be set') if len(self._version) > 4: raise ValidationError('Document Version must be comprised between `nn` and `nn.nn.nn.nn`') if not self._history: raise ValidationError('Document must have at least a revision') if not self._initialDate: raise ValidationError('Document must have an initial Release date set') prev_date = self._initialDate if self._history[0]._date < self._initialDate: # Documents could have revisions before being released prev_date = self._history[0]._date prev = () for revision in self._history: revision.validate() if revision._number <= prev: raise ValidationError('Revision numbers must always be increasing') if revision._date < prev_date: raise ValidationError('Revision dates must always be increasing') prev = revision._number prev_date = revision._date if not self._currentDate: raise ValidationError('Document must have a Current Release Date set') if self._currentDate != self._history[-1]._date: raise ValidationError('Current Release Date must be the same as the Date from the last Revision') if self._initialDate > self._currentDate: raise ValidationError('Initial date must not be after current Date') if self._version != self._history[-1]._number: raise ValidationError('Document version must be the same as the number of the last Revision') def __str__(self): s = "ID: %s" % self._identification s += " Status: %s" % self._status s += " v%s" % '.'.join('%d' % i for i in self._version) s += " %d revisions" % len(self._history) s += " Initial release: %s" % self._initialDate.isoformat() return s class CVRFRevision(object): def __init__(self, number, date, description): self._number = number self._date = date self._description = description def validate(self): if not self._number: raise ValidationError('A Revision must have a Number') if not self._date: raise ValidationError('A Revision must have a Date') if not self._description: raise ValidationError('A Revision must have a Description') class CVRFGenerator(object): def __init__(self): self._engine = None self._date = None def setEngine(self, engine): self._engine = engine def setDate(self, date): self._date = date def validate(self): if (not self._engine) and (not self._date): raise ValidationError('The Generator must have at least an Engine or a Date') class CVRFNote(object): TYPES = ('General', 'Details', 'Description', 'Summary', 'FAQ', 'Legal Disclaimer', 'Other') def __init__(self, _type, ordinal, note, title=None, audience=None): self._type = _type self._ordinal = ordinal self._note = note self._title = title self._audience = audience def getTitle(self): """ returns something that can be used as a title """ if self._title is None: return "%s (#%d)" % (self._type, self._ordinal) return "%s (%s)" % (self._title, self._type) def validate(self): if not self._type: raise ValidationError('A Note needs to have a Type set') if self._type not in self.TYPES: raise ValidationError('A Note Type needs to be one of %s' % ', '.join(self.TYPES)) if self._ordinal < 0: raise ValidationError('A Note ordinal must be a positive integer') if not self._note: raise ValidationError('A Note must contain some text') def __str__(self): return self._note class CVRFAggregateSeverity(object): def __init__(self, severity): self._severity = severity self._namespace = None def setNamespace(self, namespace): self._namespace = namespace class CVRFReference(object): TYPES = ('Self', 'External') def __init__(self, url, description, _type=None): self._url = url self._description = description self._type = _type def validate(self): if (self._type is not None) and (self._type not in self.TYPES): raise ValidationError('If a Reference type is set, it mist be one of %s' % ', '.join(self.TYPES)) if not self._url: raise ValidationError('A Reference must contain an URL') if not self._description: raise ValidationError('A Reference must contain a description') class CVRFAcknowledgment(object): def __init__(self, names=[], organizations=[], description=None, url=None): self._names = names self._organizations = organizations self._description = description self._url = url def getTitle(self): return "%s - %s" % (', '.join(self._names), ', '.join(self._organizations)) def validate(self): if (not self._names) and (not self._organizations) and (not self._description): raise ValidationError('An Acknowledgment must have at least a Name, an Organization or a Description') class CVRFProductTree(object): def __init__(self): # All the branches, they can be order with their `parent` attribute self._branches = [] self._groups = [] self._relationships = [] self._products = [] self._groups = [] def addBranch(self, branch): parent = self.getBranch(branch.getParent().getPath()) if parent is self: self._branches.append(branch) else: parent._childs.append(branch) def addProduct(self, product): if product not in self._products: self._products.append(product) if product._parent is not self: product._parent._product = product def addRelationship(self, rel): self._relationships.append(rel) def addGroup(self, group): self._groups.append(group) def getProductForID(self, productid): for product in self._products: if product._productid == productid: return product raise KeyError(productid) def getGroupForID(self, groupid): for group in self._groups: if group._groupid == groupid: return group raise KeyError(groupid) def decomposeProduct(self, productid): """ In case of product defined as a relationship (product X installed on OS Y), this gives us the following tuple: (OS, product). """ product = self.getProductForID(productid) parent = product._parent if parent is None: return (None, None) if not isinstance(parent, CVRFRelationship): return (None, None) relationtype = parent._relationtype.replace(' ', '').lower() if relationtype not in ('defaultcomponentof', 'installedon'): return (None, None) return ( self.getProductForID(parent._relatestoproductreference), self.getProductForID(parent._productreference) ) def getBranch(self, path): if len(path) == 0: return self branches = self._branches node = None for idx in path: node = branches[idx] branches = node._childs return node def getBranches(self): for branch in self._branches: yield branch for sub_branch in branch.getBranches(): yield sub_branch def getPath(self): return () def getNameOfRelationship(self, relationship): if relationship is None: return '' return ' '.join((self.getProductForID(relationship._productreference)._name, 'as', relationship._relationtype.lower(), self.getProductForID(relationship._relatestoproductreference)._name)) def getOrphanedBranches(self, product=None): """ The branches that could accept `product` as Product Definition """ white_list = [] if product is not None: white_list = [product._parent] for branch in self.getBranches(): if (branch in white_list) or branch.isOrphaned(): yield branch def getNotTerminalBranches(self, b2=None): """\ The branches that could accept `b2` as new sub-branches Note that b2 and all its sub-branches cannot be listed """ black_list = [] if b2 is not None: black_list = [b2] + list(b2.getBranches()) for branch in self.getBranches(): if branch in black_list: continue if branch._product is None: yield branch def getOrphanedRelationships(self, product=None): """ The relationships that need a product defninition """ white_list = [] if product is not None: white_list = [product.getCurrentRelationship()] for i, relationship in enumerate(self._relationships): if (relationship in white_list) or relationship.isOrphaned(): yield (i, relationship) def nbProducts(self): """ Amount of 'raw' Products """ return len([p for p in self._products if p._parent is self]) def validate(self): for branch in self._branches: branch.validate() productids = set() for product in self._products: product.validate() if product._productid in productids: raise ValidationError('Each ProductID must be unique (%s)' % product._productid) productids.add(product._productid) for relationship in self._relationships: relationship.validate() for productid in (relationship._productreference, relationship._relatestoproductreference): if productid not in productids: raise ValidationError('ProductID %s is unknown' % productid) groupids = set() for group in self._groups: group.validate() if group._groupid in groupids: raise ValidationError('Duplicated GroupID: %s' % group._groupid) groupids.add(group._groupid) for productid in group._productids: if productid not in productids: raise ValidationError('ProductID %s is unknown' % productid) return productids, groupids def __str__(self): return 'Products: %s' % '\n'.join(str(p) for p in self._products) class CVRFProductBranch(object): TYPES = ('Vendor', 'Product Family', 'Product Name', 'Product Version', 'Patch Level', 'Service Pack', 'Architecture', 'Language', 'Legacy', 'Specification') def __init__(self, _type, name, parentbranch): self._type = _type self._name = name self._parentbranch = parentbranch self._childs = [] self._product = None def getParent(self): return self._parentbranch def getPath(self, string=False): """ return the path to that branch element as a tuple """ if self.isRoot(): for i, b in enumerate(self._parentbranch._branches): if b is self: if string: return '%d' % i return (i, ) else: for i, b in enumerate(self._parentbranch._childs): if b is self: if string: return '/'.join([self._parentbranch.getPath(string), '%d' % i]) return self._parentbranch.getPath(string) + (i,) if string: return '' return () def getTree(self): """ this returns a list of tuples (type, name) leading to here""" if self.isRoot(): return [(self._type, self._name)] return self._parentbranch.getTree() + [(self._type, self._name)] def getName(self): return ' / '.join("%s: %s" % (type_, name) for type_, name in self.getTree()) def getParentPath(self): """ return as string the path to the parent """ return '/'.join('%s' % p for p in self.getPath()[:-1]) def isRoot(self): return isinstance(self._parentbranch, CVRFProductTree) def isOrphaned(self): """ Has no childs and no product """ return len(self._childs) == 0 and (self._product is None) def getBranches(self): for branch in self._childs: yield branch for sub_branch in branch.getBranches(): yield sub_branch def unlink(self): """ Unset our _parent, and remove us from the _parent._childs """ if self.isRoot(): self.getParent()._branches.remove(self) else: self.getParent()._childs.remove(self) self._parentbranch = None def validate(self): if not self._type: raise ValidationError('A Branch must have a Type') if self._type not in self.TYPES: raise ValidationError('A Branch Type must be one of %s' % ', '.join(self.TYPES)) if not self._name: raise ValidationError('A Branch must have a Name') for branch in self._childs: branch.validate() if self.isOrphaned(): raise ValidationError('A Branch must have at least a sub-product or sub-branches') def __str__(self): return "%s: %s" % (self._type, self._name) class CVRFFullProductName(object): def __init__(self, productid, name, parent, cpe=None): self._productid = productid self._name = name # Can be None (directly under the tree), a ProductBranch, or a # Relationship self._parent = parent self._cpe = cpe def isRoot(self): return isinstance(self._parent, CVRFProductTree) def isRelationship(self): return isinstance(self._parent, CVRFRelationship) def getTree(self): if not isinstance(self._parent, CVRFProductBranch): return [] return self._parent.getTree() def getParentPath(self): if self.isRoot() or self.isRelationship(): return '' return self._parent.getPath(True) def getCurrentRelationship(self): if self.isRelationship(): return self._parent return None def unlink(self): """ Unset our _parent, and remove us from the _parent._childs """ if self.isRoot(): self._parent._products.remove(self) else: self._parent._product = None self._parent = None def validate(self): if not self._productid: raise ValidationError('A Product must have a ProductID') if not self._name: raise ValidationError('A Product must have a Name') def __str__(self): return "%s (%s)" % (self._productid, self._name) class CVRFRelationship(object): TYPES = ('Default Component Of', 'Optional Component Of', 'External Component Of', 'Installed On', 'Installed With') def __init__(self, productref, reltype, relatestoproductref): self._productreference = productref self._relationtype = reltype self._relatestoproductreference = relatestoproductref self._product = None def getParent(self): """ All parent element of a FullProductName should implement that method """ return None def isOrphaned(self): return self._product is None def validate(self): if not self._productreference: raise ValidationError('A Relationship must have a Product Reference') if not self._relationtype: raise ValidationError('A Relationship must have a Relation Type') if self._relationtype not in self.TYPES: raise ValidationError('Relation Type must be one of %s' % ', '.join(self.TYPES)) if not self._relatestoproductreference: raise ValidationError('A Relationship must have a "Relates To product Reference"') if self._productreference == self._relatestoproductreference: raise ValidationError('A Relationship cannot reference twice the same Product') class CVRFGroup(object): def __init__(self, groupid): self._groupid = groupid self._description = None self._productids = [] def setDescription(self, description): self._description = description def addProductID(self, productid): self._productids.append(productid) def getTitle(self): if self._description: return "%s (%d products)" % (self._description, len(self._productids)) return "#%s (%d products)" % (self._groupid, len(self._productids)) def validate(self): if not self._groupid: raise ValidationError('A Group must have a GroupID') if not self._productids or len(self._productids) < 2: raise ValidationError('A Group must contain at least two products') class CVRFVulnerabilityID(object): def __init__(self, systemname, value): self._systemname = systemname self._value = value def validate(self): if not self._systemname: raise ValidationError('A Vulnerability ID must have a System Name') if not self._value: raise ValidationError('A Vulnerability ID must have a value') class CVRFVulnerability(object): def __init__(self, ordinal): self._ordinal = ordinal self._title = None self._id = None self._notes = [] self._discoverydate = None self._releasedate = None self._involvements = [] self._cve = None self._cwes = [] self._productstatuses = [] self._threats = [] self._cvsss = [] self._remediations = [] self._references = [] self._acknowledgments = [] def setTitle(self, title): self._title = title def setID(self, _id): self._id = _id def addNote(self, note): self._notes.append(note) def setDiscoveryDate(self, date): self._discoverydate = date def setReleaseDate(self, date): self._releasedate = date def addInvolvement(self, involvement): self._involvements.append(involvement) def setCVE(self, cve): self._cve = cve def addCWE(self, cwe): self._cwes.append(cwe) def addProductStatus(self, productstatus): self._productstatuses.append(productstatus) def addThreat(self, threat): self._threats.append(threat) def addCVSSSet(self, cvss_set): self._cvsss.append(cvss_set) def addRemediation(self, remediation): self._remediations.append(remediation) def addReference(self, ref): self._references.append(ref) def addAcknowledgment(self, ack): self._acknowledgments.append(ack) def getTitle(self): """ return something that can be used as a title """ if self._title: if self._id: return "%s (%s)" % (self._title, self._id._value) return self._title if self._id: return self._id._value return "#%d" % self._ordinal def getNote(self, ordinal): for note in self._notes: if note._ordinal == ordinal: return note return None def validate(self, productids, groupids): if not self._ordinal: raise ValidationError('A Vulnerability must have an ordinal') if self._id is not None: self._id.validate() ordinals = set() for note in self._notes: note.validate() if note._ordinal in ordinals: raise ValidationError('Vulnerability Note Ordinal %d duplicated' % note._ordinal) ordinals.add(note._ordinal) for involvement in self._involvements: involvement.validate() for cwe in self._cwes: cwe.validate() for status in self._productstatuses: status.validate(productids) for threat in self._threats: threat.validate(productids, groupids) for cvss in self._cvsss: cvss.validate(productids) for remediation in self._remediations: remediation.validate(productids, groupids) for reference in self._references: reference.validate() for acknowledgment in self._acknowledgments: acknowledgment.validate() class CVRFInvolvement(object): PARTIES = CVRFPublisher.TYPES STATUSES = ('Open', 'Disputed', 'In Progress', 'Completed', 'Contact Attempted', 'Not Contacted') def __init__(self, party, status): self._party = party self._status = status self._description = None def setDescription(self, description): self._description = description def getTitle(self): return "From %s: %s" % (self._party, self._status) def validate(self): if not self._party: raise ValidationError('An Involvement must have a Party') if self._party not in self.PARTIES: raise ValidationError("An Involvement's Party must be one of %s" % ', '.join(self.PARTIES)) if not self._status: raise ValidationError('An Involvement must have a Status') if self._status not in self.STATUSES: raise ValidationError("An Involvement's Status must be one of %s" % ', '.join(self.STATUSES)) class CVRFCWE(object): def __init__(self, _id, value): self._id = _id self._value = value def validate(self): if not self._id: raise ValidationError('A CWE must have an ID') if not self._value: raise ValidationError('A CWE must have a description') class CVRFProductStatus(object): TYPES = ('First Affected', 'Known Affected', 'Known Not Affected', 'First Fixed', 'Fixed', 'Recommended', 'Last Affected') def __init__(self, _type): self._type = _type self._productids = [] def addProductID(self, productid): self._productids.append(productid) def getTitle(self): return "%s: %d products" % (self._type, len(self._productids)) def validate(self, productids): if not self._type: raise ValidationError('A Product Status must have a Type') if self._type not in self.TYPES: raise ValidationError("A Product Status' Type must be one of %s" % ', '.join(self.TYPES)) if len(self._productids) < 1: raise ValidationError('A Product Status must mention at least one Product') for productid in self._productids: if productid not in productids: raise ValidationError('Unknown ProductID: %s' % productid) class CVRFThreat(object): TYPES = ('Impact', 'Exploit Status', 'Target Set') def __init__(self, _type, description): self._type = _type self._description = description self._date = None self._productids = [] self._groupids = [] def setDate(self, date): self._date = date def addProductID(self, productid): self._productids.append(productid) def addGroupID(self, groupid): self._groupids.append(groupid) def getTitle(self): return self._type def validate(self, productids, groupids): if not self._type: raise ValidationError('A Threat must have a Type') if self._type not in self.TYPES: raise ValidationError("A Threat's Type must be one of %s" % ', '.join(self.TYPES)) if not self._description: raise ValidationError('A Threat must have a Description') for productid in self._productids: if productid not in productids: raise ValidationError('Unknown ProductID: %s' % productid) for groupid in self._groupids: if groupid not in groupids: raise ValidationError('Unknown GroupID: %s' % groupid) class CVRFCVSSSet(object): # To determine the base Score VALUES = {'AV': {'L':0.395, 'A':0.646, 'N':1.0}, 'AC': {'H':0.35, 'M':0.61 ,'L':0.71}, 'Au': {'M':0.45, 'S':0.56, 'N':0.704}, 'C': {'N':0.0, 'P':0.275, 'C':0.66}, 'I': {'N':0.0, 'P':0.275, 'C':0.66}, 'A': {'N':0.0, 'P':0.275, 'C':0.66}} def __init__(self, basescore): self._basescore = basescore self._temporalscore = None self._environmentalscore = None self._vector = None self.vector = None self._productids = [] def setTemporalScore(self, tempscore): self._temporalscore = tempscore def setEnvironmentalScore(self, envscore): self._environmentalscore = envscore def setVector(self, vector): self._vector = vector if vector is None: self.vector = vector return try: self.vector = {} for component in vector[:26].split('/'): name, value = component.split(':') self.vector[name] = self.VALUES[name][value] except (KeyError, ValueError): self.vector = None def addProductID(self, productid): self._productids.append(productid) def baseScore(self): v = self.vector # make an alias for shorter lines exploitability = 20 * v['AV'] * v['AC'] * v['Au'] impact = 10.41 * (1 - (1 - v['C']) * (1 - v['I']) * (1 - v['A'])) def f(i): return 0 if i == 0 else 1.176 return ((0.6 * impact) + (0.4 * exploitability) - 1.5) * f(impact) def validate(self, productids): if not self._basescore: raise ValidationError('A CVSS Score Set must have a Base Score') if self._vector and not self.vector: raise ValidationError('Syntax Error in CVSS Vector') if abs(self._basescore - self.baseScore()) >= 0.05: raise ValidationError('Inconsistency in CVSS Score Set between Vector (%f) and Base Score (%f)' % (self.baseScore(), self._basescore)) for productid in self._productids: if productid not in productids: raise ValidationError('Unknown ProductID: %s' % productid) class CVRFRemediation(object): TYPES = ('Workaround', 'Mitigation', 'Vendor Fix', 'None Available', 'Will Not Fix') def __init__(self, _type, description): self._type = _type self._description = description self._date = None self._entitlement = None self._url = None self._productids = [] self._groupids = [] def setDate(self, date): self._date = date def setEntitlement(self, entitlement): self._entitlement = entitlement def setURL(self, url): self._url = url def addProductID(self, productid): self._productids.append(productid) def addGroupID(self, groupid): self._groupids.append(groupid) def getTitle(self): return self._type def validate(self, productids, groupids): if not self._type: raise ValidationError('A Remediation must have a Type') if self._type not in self.TYPES: raise ValidationError("A Remediation's Type must be one of %s" % ', '.join(self.TYPES)) if not self._description: raise ValidationError('A Remediation must have a Description') for productid in self._productids: if productid not in productids: raise ValidationError('Unknown ProductID: %s' % productid) for groupid in self._groupids: if groupid not in groupids: raise ValidationError('Unknown GroupID: %s' % groupid) class CVRF(object): def __init__(self, title, _type): self._title = title self._type = _type self._publisher = None self._tracking = None self._notes = [] self._distribution = None self._aggregateseverity = None self._references = [] self._acknowledgments = [] self._producttree = None self._vulnerabilities = [] def setPublisher(self, publisher): self._publisher = publisher def setTracking(self, tracking): self._tracking = tracking def addNote(self, note): self._notes.append(note) def setDistribution(self, distribution): self._distribution = distribution def setAggregateSeverity(self, aggregateseverity): self._aggregateseverity = aggregateseverity def addReference(self, ref): self._references.append(ref) def addAcknowledgment(self, ack): self._acknowledgments.append(ack) def createProductTree(self): """ only done if the element is there """ self._producttree = CVRFProductTree() return self._producttree def addVulnerability(self, vuln): self._vulnerabilities.append(vuln) def getProductForID(self, productid): if self._producttree is None: raise ValueError('No ProductTree') return self._producttree.getProductForID(productid) def getGroupForID(self, groupid): if self._producttree is None: raise ValueError('No ProductTree') return self._producttree.getGroupForID(groupid) def getHighestCVSS(self): highestBaseScore = 0 highest = None for vulnerability in self._vulnerabilities: for cvss in vulnerability._cvsss: if cvss._basescore <= highestBaseScore: continue highestBaseScore = cvss._basescore highest = cvss return highest def getProductList(self, type_='Fixed'): products = set() if type_ == 'Fixed': # First try through the Remediation for vulnerability in self._vulnerabilities: for remediation in vulnerability._remediations: if remediation._type != 'Vendor Fix': continue for productid in remediation._productids: products.add(productid) for groupid in remediation._groupids: for productid in self.getGroupForID(groupid)._productids: products.add(productid) if not products: # If nothing there, try through the productstatuses for vulnerability in self._vulnerabilities: for status in vulnerability._productstatuses: if status._type != type_: continue for productid in status._productids: products.add(productid) return set(self.getProductForID(p) for p in products) def getNote(self, ordinal): for note in self._notes: if note._ordinal == ordinal: return note return None def getDocId(self): if self._tracking is not None: return self._tracking.getId() # Make up something ... return self._title.lower() def validate(self): if not self._title: raise ValidationError('Document Title cannot be empty') if not self._type: raise ValidationError('Document Type cannot be empty') if self._publisher is None: raise ValidationError('Document Publisher needs to be set') self._publisher.validate() if self._tracking is None: raise ValidationError('Document Tracking needs to be set') self._tracking.validate() ordinals = set() for note in self._notes: note.validate() if note._ordinal in ordinals: raise ValidationError('Document Note ordinal %d is issued twice' % note._ordinal) ordinals.add(note._ordinal) for reference in self._references: reference.validate() for acknowledgment in self._acknowledgments: acknowledgment.validate() productids = set() groupids = set() if self._producttree: productids, groupids = self._producttree.validate() ordinals = set() for vulnerability in self._vulnerabilities: vulnerability.validate(productids, groupids) if vulnerability._ordinal in ordinals: raise ValidationError('Vulnerability ordinal %d is issued twice' % vulnerability._ordinal) ordinals.add(vulnerability._ordinal) def __str__(self): s = [ 'Title: %s' % self._title, 'Type: %s' % self._type, 'Publisher: %s' % self._publisher, 'tracking: %s' % self._tracking, '%d Notes: %s' % (len(self._notes), ', '.join( str(n) for n in self._notes)) ] if self._distribution is not None: s.append('Distribution: %s' % self._distribution) s.extend([ '%d Acknowledgments' % len(self._acknowledgments), 'Products: %s' % self._producttree, ]) return '\n'.join(s)