view farolluz/cvrf.py @ 0:e18b61a73a68

Initial Release
author Benoît Allard <benoit.allard@greenbone.net>
date Tue, 23 Sep 2014 15:19:14 +0200
parents
children d47e1164740f
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 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 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 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, name=None, organization=None, description=None,
                 url=None):
        self._name = name
        self._organization = organization
        self._description = description
        self._url = url

    def getTitle(self):
        return "%s - %s" % (self._name, self._organization)

    def validate(self):
        if (not self._name) and (not self._organization) 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 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._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 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 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)
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)