view farolluz/document.py @ 31:1ea1a3c3c790

Add method to change a Group ID
author Benoît Allard <benoit.allard@greenbone.net>
date Mon, 27 Oct 2014 12:29:56 +0100
parents b15022ae484a
children b87f2a6e613a
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
"""

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 _ in self.mentionsProductId(productid):
            return False
        return True

    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 mentionsGroupId(self, groupid):
        for vulnerability in self._vulnerabilities:
            for item in vulnerability.mentionsGroupId(groupid):
                yield item

    def isGroupOrphan(self, groupid):
        """ Returns if a group can be safely deleted """
        for _ in self.mentionsGroupId(groupid):
            return False
        return True

    def changeGroupID(self, old, new):
        for item in self.mentionsGroupId(old):
            item._groupids.remove(old)
            item._groupids.append(new)

    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)
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)