-
-
Notifications
You must be signed in to change notification settings - Fork 302
Expand file tree
/
Copy pathimprover.py
More file actions
141 lines (118 loc) · 4.64 KB
/
improver.py
File metadata and controls
141 lines (118 loc) · 4.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#
import dataclasses
import logging
from typing import Iterable
from typing import List
from typing import Optional
from uuid import uuid4
from django.db.models.query import QuerySet
from packageurl import PackageURL
from vulnerabilities.importer import AdvisoryData
from vulnerabilities.importer import Reference
from vulnerabilities.utils import classproperty
logger = logging.getLogger(__name__)
MAX_CONFIDENCE = 100
@dataclasses.dataclass(order=True)
class Inference:
"""
This data class expresses the contract between improvers and the improve runner.
Only inferences with highest confidence for one vulnerability <-> package
relationship is to be inserted into the database
"""
vulnerability_id: str = None
aliases: Optional[List[str]] = dataclasses.field(default_factory=list)
confidence: int = MAX_CONFIDENCE
summary: Optional[str] = ""
affected_purls: Optional[List[PackageURL]] = dataclasses.field(default_factory=list)
fixed_purl: PackageURL = None
references: List[Reference] = dataclasses.field(default_factory=list)
weaknesses: List[int] = dataclasses.field(default_factory=list)
def __post_init__(self):
if self.confidence > MAX_CONFIDENCE or self.confidence < 0:
raise ValueError
assert (
self.vulnerability_id
or self.aliases
or self.summary
or self.affected_purls
or self.fixed_purl
or self.references
or self.weaknesses
)
versionless_purls = []
purls = []
if self.fixed_purl:
purls.append(self.fixed_purl)
if self.affected_purls:
purls.extend(self.affected_purls)
for purl in purls:
if purl and not purl.version:
versionless_purls.append(purl)
assert (
not versionless_purls
), f"Version-less purls are not supported in an Inference: {versionless_purls}"
def to_dict(self):
"""
Return a dict representation of this Inference
"""
from vulnerabilities.utils import purl_to_dict
return {
"vulnerability_id": self.vulnerability_id,
"aliases": self.aliases,
"confidence": self.confidence,
"summary": self.summary,
"affected_purls": [
purl_to_dict(affected_purl) for affected_purl in self.affected_purls
],
"fixed_purl": purl_to_dict(self.fixed_purl) if self.fixed_purl else None,
"references": [ref.to_dict() for ref in self.references],
"weaknesses": self.weaknesses,
}
@classmethod
def from_advisory_data(cls, advisory_data, confidence, fixed_purl, affected_purls=None):
"""
Return an Inference object while keeping the same values as of advisory_data
for aliases, summary and references
"""
return cls(
aliases=advisory_data.aliases,
confidence=confidence,
summary=advisory_data.summary,
affected_purls=affected_purls or [],
fixed_purl=fixed_purl,
references=advisory_data.references,
weaknesses=advisory_data.weaknesses,
)
class Improver:
"""
Improvers are responsible to improve already imported data by an importer. An improver is
required to override the ``interesting_advisories`` property method to return a QuerySet of
``Advisory`` objects. These advisories are then passed to ``get_inferences`` method which is
responsible for returning an iterable of ``Inferences`` for that particular ``Advisory``
"""
@classproperty
def qualified_name(cls):
"""
Fully qualified name prefixed with the module name of the improver used in logging.
"""
return f"{cls.__module__}.{cls.__qualname__}"
@property
def interesting_advisories(self) -> QuerySet:
"""
Return QuerySet for the advisories this improver is interested in.
Subclasses must implement.
"""
raise NotImplementedError
def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]:
"""
Return an iterable of Inferences from the ``advisory data``.
Subclasses must implement.
"""
raise NotImplementedError