[PATCH 27 of 54] Reorganize the code in smaller mpodules
Wald Commits
scm-commit at wald.intevation.org
Wed Jan 7 10:56:44 CET 2015
# HG changeset patch
# User Benoît Allard <benoit.allard at greenbone.net>
# Date 1414162886 -7200
# Node ID 809db989cac5528456964d27e801d268da17e3a5
# Parent 3cab052872f43b49707f9dc53bdb3fff53e5fcfa
Reorganize the code in smaller mpodules
diff -r 3cab052872f4 -r 809db989cac5 CHANGES
--- a/CHANGES Fri Oct 24 16:43:31 2014 +0200
+++ b/CHANGES Fri Oct 24 17:01:26 2014 +0200
@@ -5,6 +5,7 @@
----------------------------------
* Add tests
* Add method to rename productIDs in the whole document
+* Split the big cvrf.py file into smaller ones
FarolLuz 0.1.1 (2014-10-17)
===========================
diff -r 3cab052872f4 -r 809db989cac5 farolluz/common.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/farolluz/common.py Fri Oct 24 17:01:26 2014 +0200
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+#
+# Authors:
+# Benoît Allard <benoit.allard at 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.
+
+"""\
+Common Objects related to CVRF Documents
+"""
+
+class ValidationError(Exception): pass
+
+
+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 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')
+
+
diff -r 3cab052872f4 -r 809db989cac5 farolluz/cvrf.py
--- a/farolluz/cvrf.py Fri Oct 24 16:43:31 2014 +0200
+++ b/farolluz/cvrf.py Fri Oct 24 17:01:26 2014 +0200
@@ -21,1132 +21,16 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
"""\
-Objects related to CVRF Documents
+This is the public interface to the CVRF Elements
"""
-class ValidationError(Exception): pass
+from .common import (CVRFNote, CVRFReference, CVRFAcknowledgment,
+ ValidationError)
+from .document import (CVRF, CVRFPublisher, CVRFTracking, CVRFTrackingID,
+ CVRFAggregateSeverity, CVRFRevision, CVRFGenerator)
+from .producttree import (CVRFProductBranch, CVRFFullProductName,
+ CVRFRelationship, CVRFGroup)
+from .vulnerability import (CVRFVulnerability, CVRFVulnerabilityID,
+ CVRFThreat, CVRFProductStatus, CVRFCVSSSet, CVRFRemediation,
+ CVRFInvolvement, CVRFCWE)
-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 addProduct(self, product):
- """ Add to the product list """
- self._products.append(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._childs = []
- self._product = None
- self.link(parentbranch)
-
- 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 link(self, parent):
- """ Actually, only set the parent """
- self._parentbranch = parent
- if self.isRoot():
- parent._branches.append(self)
- else:
- parent._childs.append(self)
-
-
- 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
- self._cpe = cpe
- # Can be None (directly under the tree), a ProductBranch, or a
- # Relationship
- self.link(parent)
-
- 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
- We are still in the product list.
- """
- if not self.isRoot():
- self._parent._product = None
- self._parent = None
-
- def link(self, parent):
- self._parent = parent
- if not self.isRoot():
- parent._product = self
-
- 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 mentionsProdId(self, productid):
- """ Returns in which sub element, self is mentioning the productid """
- for category in (self._productstatuses, self._threats, self._cvsss, self._remediations):
- for subelem in category:
- if productid in subelem._productids:
- yield subelem
-
- def isMentioningProdId(self, productid):
- """ Returns if self is mentioning the productid """
- for e in self.mentionsProdId(productid):
- # We only need to know if the generator yield at least one elem.
- return True
- return False
-
- def mentionsGroupId(self, groupid):
- for category in (self._threats, self._remediations):
- for subelem in category:
- if groupid in subelem._groupids:
- yield subelem
-
- def isMentioningGroupId(self, groupids):
- """ Make sure you call this with a list (not a generator or a tuple)
- when wished """
- if not isinstance(groupids, list):
- groupids = [groupids]
- for groupid in groupids:
- for _ in self.mentionsGroupId(groupid):
- # We only need to know if the generator yield at least one elem.
- return True
- return False
-
- 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)
- pids = set()
- for status in self._productstatuses:
- for pid in status._productids:
- if pid in pids:
- raise ValidationError('ProductID %s mentionned in two different ProductStatuses for Vulnerability %d' % (pid, self._ordinal))
- pids.add(pid)
- for threat in self._threats:
- threat.validate(productids, groupids)
- for cvss in self._cvsss:
- cvss.validate(productids)
- pids = set()
- for cvss in self._cvsss:
- for pid in (cvss._productids or productids):
- if pid in pids:
- raise ValidationError('ProductID %s mentionned in two different CVSS Score Sets for Vulnerability %d' % (pid, self._ordinal))
- pids.add(pid)
- 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')
- NAME = "Product Status"
- 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')
- NAME = "Threat"
- 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}}
- NAME = "CVSS Score Set"
- 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 self.vector and (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')
- NAME = "Remediation"
- 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 mentionsProductId(self, productid):
- # We first look at the ProductTree
- ptree = self._producttree
- for relation in ptree._relationships:
- if productid == relation._productreference:
- yield relation
- elif productid == relation._relatestoproductreference:
- yield relation
- # Then go through the groups
- for group in ptree._groups:
- if productid in group._productids:
- yield group
- # Finally, go through all the Vulnerabilities
- for vulnerability in self._vulnerabilities:
- for item in vulnerability.mentionsProdId(productid):
- yield item
-
- def isProductOrphan(self, productid):
- """ Returns if a productid is mentioned nowhere in the document """
- for item in self.mentionsProductId(productid):
- return True
- return False
-
- def changeProductID(self, old, new):
- for item in self.mentionsProductId(old):
- if isinstance(item, CVRFRelationship):
- if old == item._productreference:
- item._productreference = new
- elif old == item._relatestoproductreference:
- item._relatestoproductreference = new
- else:
- item._productids.remove(old)
- item._productids.append(new)
-
- def isGroupOrphan(self, groupid):
- """ Returns if a group can be safely deleted """
- for vulnerability in self._vulnerabilities:
- if vulnerability.isMentioningGroupId(groupid):
- return False
- return True
-
- def isProductTreeOrphan(self):
- """ Difference with the previous method is that we don;t care about
- inter-producttree references """
- for vulnerability in self._vulnerabilities:
- for product in self._producttree._products:
- if vulnerability.isMentioningProdId(product._productid):
- return False
- for group in self._producttree._groups:
- if vulnerability.isMentioningGroupId(group._groupid):
- return False
- return True
-
- 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)
diff -r 3cab052872f4 -r 809db989cac5 farolluz/document.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/farolluz/document.py Fri Oct 24 17:01:26 2014 +0200
@@ -0,0 +1,387 @@
+# -*- coding: utf-8 -*-
+#
+# Authors:
+# Benoît Allard <benoit.allard at 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
+"""
+
+from .common import ValidationError
+from .producttree import CVRFProductTree, CVRFRelationship
+
+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 CVRFAggregateSeverity(object):
+ def __init__(self, severity):
+ self._severity = severity
+ self._namespace = None
+
+ def setNamespace(self, namespace):
+ self._namespace = namespace
+
+
+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 mentionsProductId(self, productid):
+ # We first look at the ProductTree
+ ptree = self._producttree
+ for relation in ptree._relationships:
+ if productid == relation._productreference:
+ yield relation
+ elif productid == relation._relatestoproductreference:
+ yield relation
+ # Then go through the groups
+ for group in ptree._groups:
+ if productid in group._productids:
+ yield group
+ # Finally, go through all the Vulnerabilities
+ for vulnerability in self._vulnerabilities:
+ for item in vulnerability.mentionsProdId(productid):
+ yield item
+
+ def isProductOrphan(self, productid):
+ """ Returns if a productid is mentioned nowhere in the document """
+ for item in self.mentionsProductId(productid):
+ return True
+ return False
+
+ def changeProductID(self, old, new):
+ for item in self.mentionsProductId(old):
+ if isinstance(item, CVRFRelationship):
+ if old == item._productreference:
+ item._productreference = new
+ elif old == item._relatestoproductreference:
+ item._relatestoproductreference = new
+ else:
+ item._productids.remove(old)
+ item._productids.append(new)
+
+ def isGroupOrphan(self, groupid):
+ """ Returns if a group can be safely deleted """
+ for vulnerability in self._vulnerabilities:
+ if vulnerability.isMentioningGroupId(groupid):
+ return False
+ return True
+
+ def isProductTreeOrphan(self):
+ """ Difference with the previous method is that we don;t care about
+ inter-producttree references """
+ for vulnerability in self._vulnerabilities:
+ for product in self._producttree._products:
+ if vulnerability.isMentioningProdId(product._productid):
+ return False
+ for group in self._producttree._groups:
+ if vulnerability.isMentioningGroupId(group._groupid):
+ return False
+ return True
+
+ 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)
diff -r 3cab052872f4 -r 809db989cac5 farolluz/producttree.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/farolluz/producttree.py Fri Oct 24 17:01:26 2014 +0200
@@ -0,0 +1,365 @@
+# -*- coding: utf-8 -*-
+#
+# Authors:
+# Benoît Allard <benoit.allard at 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.
+
+"""\
+Product Tree Objects related to CVRF Documents
+"""
+
+from .common import ValidationError
+
+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 addProduct(self, product):
+ """ Add to the product list """
+ self._products.append(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._childs = []
+ self._product = None
+ self.link(parentbranch)
+
+ 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 link(self, parent):
+ """ Actually, only set the parent """
+ self._parentbranch = parent
+ if self.isRoot():
+ parent._branches.append(self)
+ else:
+ parent._childs.append(self)
+
+
+ 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
+ self._cpe = cpe
+ # Can be None (directly under the tree), a ProductBranch, or a
+ # Relationship
+ self.link(parent)
+
+ 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
+ We are still in the product list.
+ """
+ if not self.isRoot():
+ self._parent._product = None
+ self._parent = None
+
+ def link(self, parent):
+ self._parent = parent
+ if not self.isRoot():
+ parent._product = self
+
+ 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')
+
diff -r 3cab052872f4 -r 809db989cac5 farolluz/renderer.py
--- a/farolluz/renderer.py Fri Oct 24 16:43:31 2014 +0200
+++ b/farolluz/renderer.py Fri Oct 24 17:01:26 2014 +0200
@@ -25,8 +25,6 @@
from __future__ import print_function
import os
-import sys
-from datetime import datetime
import jinja2
from .parsers import cvrf
diff -r 3cab052872f4 -r 809db989cac5 farolluz/vulnerability.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/farolluz/vulnerability.py Fri Oct 24 17:01:26 2014 +0200
@@ -0,0 +1,394 @@
+# -*- coding: utf-8 -*-
+#
+# Authors:
+# Benoît Allard <benoit.allard at 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.
+
+"""\
+Vulnerability Objects related to CVRF Documents
+"""
+
+from .common import ValidationError
+from .document import CVRFPublisher
+
+
+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 mentionsProdId(self, productid):
+ """ Returns in which sub element, self is mentioning the productid """
+ for category in (self._productstatuses, self._threats, self._cvsss, self._remediations):
+ for subelem in category:
+ if productid in subelem._productids:
+ yield subelem
+
+ def isMentioningProdId(self, productid):
+ """ Returns if self is mentioning the productid """
+ for e in self.mentionsProdId(productid):
+ # We only need to know if the generator yield at least one elem.
+ return True
+ return False
+
+ def mentionsGroupId(self, groupid):
+ for category in (self._threats, self._remediations):
+ for subelem in category:
+ if groupid in subelem._groupids:
+ yield subelem
+
+ def isMentioningGroupId(self, groupids):
+ """ Make sure you call this with a list (not a generator or a tuple)
+ when wished """
+ if not isinstance(groupids, list):
+ groupids = [groupids]
+ for groupid in groupids:
+ for _ in self.mentionsGroupId(groupid):
+ # We only need to know if the generator yield at least one elem.
+ return True
+ return False
+
+ 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)
+ pids = set()
+ for status in self._productstatuses:
+ for pid in status._productids:
+ if pid in pids:
+ raise ValidationError('ProductID %s mentionned in two different ProductStatuses for Vulnerability %d' % (pid, self._ordinal))
+ pids.add(pid)
+ for threat in self._threats:
+ threat.validate(productids, groupids)
+ for cvss in self._cvsss:
+ cvss.validate(productids)
+ pids = set()
+ for cvss in self._cvsss:
+ for pid in (cvss._productids or productids):
+ if pid in pids:
+ raise ValidationError('ProductID %s mentionned in two different CVSS Score Sets for Vulnerability %d' % (pid, self._ordinal))
+ pids.add(pid)
+ 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')
+ NAME = "Product Status"
+ 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')
+ NAME = "Threat"
+ 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}}
+ NAME = "CVSS Score Set"
+ 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 self.vector and (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')
+ NAME = "Remediation"
+ 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)
+
More information about the Farol-commits
mailing list