Skip to content

Commit 80f78da

Browse files
authored
Merge pull request #237 from sbs2001/add_etags
Save etags to importers wherever possible
2 parents 2a9f725 + 091d479 commit 80f78da

4 files changed

Lines changed: 67 additions & 11 deletions

File tree

vulnerabilities/importer_yielder.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
'last_run': None,
6868
'data_source': 'SafetyDbDataSource',
6969
'data_source_cfg': {
70-
'url': 'https://raw.githubusercontent.com/pyupio/safety-db/master/data/insecure_full.json' # nopep8
70+
'url': 'https://raw.githubusercontent.com/pyupio/safety-db/master/data/insecure_full.json', # nopep8
71+
'etags': {}
7172
},
7273
},
7374
{
@@ -148,7 +149,9 @@
148149
'license': '',
149150
'last_run': None,
150151
'data_source': 'OpenSSLDataSource',
151-
'data_source_cfg': {},
152+
'data_source_cfg': {
153+
'etags': {}
154+
},
152155
},
153156
{
154157
'name': 'ubuntu_usn',

vulnerabilities/importers/openssl.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,58 @@
2020
# VulnerableCode is a free software code scanning tool from nexB Inc. and others.
2121
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2222

23+
import dataclasses
24+
import re
2325
from typing import Set
26+
import xml.etree.ElementTree as ET
2427

2528
from packageurl import PackageURL
2629
import requests
27-
import re
2830

2931
from vulnerabilities.data_source import Advisory
3032
from vulnerabilities.data_source import DataSource
3133
from vulnerabilities.data_source import Reference
34+
from vulnerabilities.data_source import DataSourceConfiguration
3235

33-
import xml.etree.ElementTree as ET
36+
37+
@dataclasses.dataclass
38+
class OpenSSLDataSourceConfiguration(DataSourceConfiguration):
39+
etags: dict
3440

3541

3642
class OpenSSLDataSource(DataSource):
43+
CONFIG_CLASS = OpenSSLDataSourceConfiguration
44+
45+
url = "https://www.openssl.org/news/vulnerabilities.xml"
46+
3747
def updated_advisories(self) -> Set[Advisory]:
38-
raw_data = self.fetch()
39-
advisories = self.to_advisories(raw_data)
40-
return self.batch_advisories(advisories)
48+
# Etags are like hashes of web responses. We maintain
49+
# (url, etag) mappings in the DB. `create_etag` creates
50+
# (url, etag) pair. If a (url, etag) already exists then the code
51+
# skips processing the response further to avoid duplicate work
52+
if self.create_etag(self.url):
53+
raw_data = self.fetch()
54+
advisories = self.to_advisories(raw_data)
55+
return self.batch_advisories(advisories)
56+
57+
return []
58+
59+
def create_etag(self, url):
60+
etag = requests.head(url).headers.get("ETag")
61+
if not etag:
62+
# Kind of inaccurate to return True since etag is
63+
# not created
64+
return True
65+
66+
elif url in self.config.etags:
67+
if self.config.etags[url] == etag:
68+
return False
69+
70+
self.config.etags[url] = etag
71+
return True
4172

4273
def fetch(self):
43-
return requests.get("https://www.openssl.org/news/vulnerabilities.xml").content
74+
return requests.get(self.url).content
4475

4576
@staticmethod
4677
def to_advisories(xml_response: str) -> Set[Advisory]:
@@ -58,7 +89,11 @@ def to_advisories(xml_response: str) -> Set[Advisory]:
5889
for info in element:
5990

6091
if info.tag == "cve":
61-
cve_id = "CVE-" + info.attrib.get("name")
92+
if info.attrib.get("name"):
93+
cve_id = "CVE-" + info.attrib.get("name")
94+
95+
else:
96+
continue
6297

6398
if info.tag == "affects":
6499
# Vulnerable package versions

vulnerabilities/importers/safety_db.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from typing import Tuple
3434
from urllib.error import HTTPError
3535
from urllib.request import urlopen
36+
import requests
3637

3738
from dephell_specifier import RangeSpecifier
3839
from packageurl import PackageURL
@@ -67,6 +68,7 @@ def validate_schema(advisory_dict):
6768
@dataclasses.dataclass
6869
class SafetyDbConfiguration(DataSourceConfiguration):
6970
url: str
71+
etags: dict
7072

7173

7274
class SafetyDbDataSource(DataSource):
@@ -90,8 +92,11 @@ def set_api(self, packages):
9092
asyncio.run(self._versions.load_api(packages))
9193

9294
def _fetch(self) -> Mapping[str, Any]:
93-
with urlopen(self.config.url) as response:
94-
return json.load(response)
95+
if self.create_etag(self.config.url):
96+
with urlopen(self.config.url) as response:
97+
return json.load(response)
98+
99+
return []
95100

96101
def collect_packages(self):
97102
return {pkg for pkg in self._api_response}
@@ -132,6 +137,18 @@ def updated_advisories(self) -> Set[Advisory]:
132137

133138
return self.batch_advisories(advisories)
134139

140+
def create_etag(self, url):
141+
etag = requests.head(url).headers.get('ETag')
142+
if not etag:
143+
# Kind of inaccurate to return True since etag is
144+
# not created
145+
return True
146+
elif url in self.config.etags:
147+
if self.config.etags[url] == etag:
148+
return False
149+
self.config.etags[url] = etag
150+
return True
151+
135152

136153
def categorize_versions(
137154
package_name: str, all_versions: Set[str], version_specs: Iterable[str],

vulnerabilities/tests/test_safety_db.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def setUpClass(cls) -> None:
5757
data_source='SafetyDbDataSource',
5858
data_source_cfg={
5959
'url': 'https://example.com',
60+
'etags': {}
6061
},
6162
)
6263

0 commit comments

Comments
 (0)