Skip to content

Commit 83442ed

Browse files
committed
Add tests for etags
Signed-off-by: Shivam Sandbhor <shivam.sandbhor@gmail.com>
1 parent cb5770a commit 83442ed

3 files changed

Lines changed: 115 additions & 22 deletions

File tree

vulnerabilities/data_source.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,15 @@ def _include_file(
393393

394394

395395
class OvalDataSource(DataSource):
396-
396+
"""
397+
All data sources which collect data from OVAL files must inherit from this (OvalDataSource) class.
398+
Subclasses must implement the methods `_fetch` and `set_api`.
399+
"""
397400
@staticmethod
398-
def create_purl(pkg_name: str, pkg_version: str, pkg_data: Mapping):
401+
def create_purl(pkg_name: str, pkg_version: str, pkg_data: Mapping) -> PackageURL:
399402
"""
403+
Helper method for creating different purls for subclasses without them reimplementing
404+
get_data_from_xml_doc method
400405
Note: pkg_data must include 'type' of package
401406
"""
402407
return PackageURL(name=pkg_name, version=pkg_version, **pkg_data)
@@ -405,7 +410,7 @@ def create_purl(pkg_name: str, pkg_version: str, pkg_data: Mapping):
405410
def _collect_pkgs(parsed_oval_data: Mapping) -> Set:
406411
"""
407412
Helper method, used for loading the API. It expects data from
408-
OvalParser.get_data() .
413+
OvalParser.get_data().
409414
"""
410415
all_pkgs = set()
411416
for definition_data in parsed_oval_data:
@@ -419,47 +424,66 @@ def _fetch() -> Tuple[Mapping, Iterable[ET.ElementTree]]:
419424
"""
420425
This method contains logic to fetch OVAL files and yield them into
421426
a tuple of file's metadata and it's ET.ElementTree.
422-
Subclasses must implement this method.
427+
Subclasses must implement this method.
428+
429+
Note: Mapping MUST INCLUDE "type" key. Example values of Mapping
430+
{"type":"deb","qualifiers":{"distro":"buster"} }
431+
423432
"""
424433
raise NotImplementedError
425434

426435
def added_advisories(self) -> List[Advisory]:
436+
"""
437+
Note: metadata MUST INCLUDE "type" key, implement _fetch accordingly.
438+
"""
427439
advisories = []
428440
for metadata, oval_file in self._fetch():
429441
advisories.extend(self.get_data_from_xml_doc(oval_file, metadata))
430-
return advisories
442+
return self.batch_advisories(advisories)
431443

432444
def set_api(self, all_pkgs: Iterable[str]):
433445
"""
434446
This method loads the self.pkg_manager_api with the specified packages. It fetches
435-
and caches the data about these packages exposes them through
436-
self.pkg_manager_api.get(<package_name>)
447+
and caches all the versions of these packages and exposes them through
448+
self.pkg_manager_api.get(<package_name>). Example
449+
450+
>>> self.set_api(['electron'])
451+
Assume 'electron' has only versions 1.0.0 and 1.2.0
452+
>>> assert self.pkg_manager_api.get('electron') == {'1.0.0','1.2.0'}
453+
437454
"""
438455
raise NotImplementedError
439456

440457
def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> List[Advisory]:
441458
"""
442-
The orchestration method of the OvalDataSource. Breaks an OVAL xml
443-
ElementTree into a list of Advisory.
459+
The orchestration method of the OvalDataSource. This method breaks an OVAL xml
460+
ElementTree into a list of `Advisory`.
461+
462+
Note: pkg_metadata MUST INCLUDE "type" key. Example value of pkg_metadata,
463+
{"type":"deb","qualifiers":{"distro":"buster"} }
444464
"""
445465
all_adv = []
446466
oval_doc = OvalParser(self.translations, xml_doc)
447467
raw_data = oval_doc.get_data()
448468
all_pkgs = self._collect_pkgs(raw_data)
449469
self.set_api(all_pkgs)
450470
for definition_data in raw_data: # definition_data -> Advisory
471+
472+
# These fields are definition level, i.e common for all
473+
# elements connected/linked to an OvalDefinition
451474
vuln_id = definition_data['vuln_id']
452475
description = definition_data['description']
453476
affected_purls = set()
454477
safe_purls = set()
455478
urls = definition_data['reference_urls']
479+
456480
for test_data in definition_data['test_data']:
457481
for package in test_data['package_list']:
458482
pkg_name = package
459483
aff_ver_range = test_data['version_ranges']
460484
all_versions = self.pkg_manager_api.get(package)
461-
# This filter is to filter out long versions.
462-
# 50 is limit because that's what db permits atm
485+
# This filter is for filtering out long versions.
486+
# 50 is limit because that's what db permits atm.
463487
all_versions = set(
464488
filter(
465489
lambda x: len(x) < 50,
@@ -489,4 +513,4 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
489513
resolved_package_urls=safe_purls,
490514
cve_id=vuln_id,
491515
reference_urls=urls))
492-
return self.batch_advisories(all_adv)
516+
return all_adv

vulnerabilities/tests/test_data_source.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,29 @@
2727
from unittest import TestCase
2828
from unittest.mock import MagicMock
2929
from unittest.mock import patch
30+
import xml.etree.ElementTree as ET
3031

3132
import pygit2
3233
import pytest
34+
from packageurl import PackageURL
3335

34-
from vulnerabilities.data_source import GitDataSource, _include_file
36+
from vulnerabilities.data_source import GitDataSource, _include_file, OvalDataSource
3537
from vulnerabilities.data_source import InvalidConfigurationError
38+
from vulnerabilities.oval_parser import OvalParser
3639

3740
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
3841
TEST_DATA = os.path.join(BASE_DIR, 'test_data/')
3942

43+
def load_oval_data():
44+
etrees_of_oval = {}
45+
for f in os.listdir(TEST_DATA):
46+
if f.endswith('oval_data.xml'):
47+
path = os.path.join(TEST_DATA, f)
48+
provider = f.split("_")[0]
49+
etrees_of_oval[provider] = ET.parse(path)
50+
return etrees_of_oval
51+
52+
4053

4154
def mk_ds(**kwargs):
4255
# just for convenience, since this is a manadory parameter we always pass a value
@@ -331,3 +344,46 @@ def test_file_changes_include_fixed_advisories(self):
331344
assert len(added_files) == 0
332345
assert len(updated_files) == 1
333346
assert os.path.join(self.repodir, 'crates/hyper/RUSTSEC-2020-0008.toml') in updated_files
347+
348+
class TestOvalDataSource(TestCase):
349+
350+
@classmethod
351+
def setUpClass(cls):
352+
cls.oval_data_src = OvalDataSource(1)
353+
354+
def test_create_purl(self):
355+
purl1 = PackageURL(name="ffmpeg",type="test",version="1.2.0")
356+
357+
assert purl1 == self.oval_data_src.create_purl(pkg_name="ffmpeg",
358+
pkg_version="1.2.0", pkg_data={"type":"test"})
359+
360+
purl2 = PackageURL(name="notepad",type="example",version="7.9.6",namespace="ns",
361+
qualifiers={"distro":"sample"},subpath="root")
362+
assert purl2 == self.oval_data_src.create_purl(pkg_name="notepad",
363+
pkg_version="7.9.6",pkg_data={
364+
"namespace":"ns","qualifiers":{"distro":"sample"},
365+
"subpath":"root","type":"example"
366+
}
367+
)
368+
369+
def test__collect_pkgs(self):
370+
371+
xmls = load_oval_data()
372+
373+
expected_suse_pkgs = {'cacti-spine', 'apache2-mod_perl', 'cacti', 'apache2-mod_perl-devel'}
374+
expected_ubuntu_pkgs = {'potrace', 'tor'}
375+
376+
translations = {"less than": "<"}
377+
378+
found_suse_pkgs = self.oval_data_src._collect_pkgs(
379+
OvalParser(translations,xmls['suse']).get_data())
380+
381+
found_ubuntu_pkgs = self.oval_data_src._collect_pkgs(
382+
OvalParser(translations,xmls['ubuntu']).get_data())
383+
384+
assert found_suse_pkgs == expected_suse_pkgs
385+
assert found_ubuntu_pkgs == expected_ubuntu_pkgs
386+
387+
388+
389+

vulnerabilities/tests/test_ubuntu.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import unittest
33
from unittest.mock import patch
4+
from unittest.mock import MagicMock
45
import xml.etree.ElementTree as ET
56
from collections import OrderedDict
67
import asyncio
@@ -15,6 +16,9 @@
1516
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
1617
TEST_DATA = os.path.join(BASE_DIR, "test_data/")
1718

19+
class MockResponse:
20+
21+
headers = {"ETag":"0x1234"}
1822

1923
class TestUbuntuOvalParser(unittest.TestCase):
2024
@classmethod
@@ -165,7 +169,7 @@ def test_get_data(self):
165169

166170
assert expected_data == self.parsed_oval.get_data()
167171

168-
#This is horrible, there must be a better way
172+
#This is horrible, there might be a better way
169173
async def mock(a,b):
170174
pass
171175

@@ -176,7 +180,10 @@ class TestUbuntuDataSource(unittest.TestCase):
176180

177181
@classmethod
178182
def setUpClass(cls):
179-
pass
183+
data_source_cfg = {
184+
'releases': 'eg-ubuntu',"etags":{}}
185+
cls.ubuntu_data_src = UbuntuDataSource(
186+
batch_size=1, config=data_source_cfg)
180187

181188
@patch(
182189
'vulnerabilities.importers.ubuntu.VersionAPI.get',
@@ -186,11 +193,6 @@ def setUpClass(cls):
186193
'2.14-2'})
187194
@patch('vulnerabilities.importers.ubuntu.VersionAPI.load_api',new=mock)
188195
def test_get_data_from_xml_doc(self, mock_write):
189-
190-
data_source_cfg = {
191-
'releases': 'eg-ubuntu',"etags":{}}
192-
ubuntu_data_src = UbuntuDataSource(
193-
batch_size=1, config=data_source_cfg)
194196
expected_data = {
195197
Advisory(
196198
summary=('Tor before 0.2.8.9 and 0.2.9.x before 0.2.9.4-alpha had '
@@ -268,8 +270,19 @@ def test_get_data_from_xml_doc(self, mock_write):
268270
cve_id='CVE-2016-8703')}
269271

270272
xml_doc = ET.parse(os.path.join(TEST_DATA, "ubuntu_oval_data.xml"))
271-
# Dirty quick patch to deal with batch_advisories
273+
# Dirty quick patch to mock batch_advisories
272274
with patch('vulnerabilities.importers.ubuntu.UbuntuDataSource.batch_advisories',
273275
new=return_adv):
274-
data = {i for i in ubuntu_data_src.get_data_from_xml_doc(xml_doc,{"type":"deb"})}
276+
data = {i for i in self.ubuntu_data_src.get_data_from_xml_doc(xml_doc,{"type":"deb"})}
275277
assert expected_data == data
278+
279+
def test_create_etag(self):
280+
281+
assert self.ubuntu_data_src.config.etags == {}
282+
with patch('vulnerabilities.importers.ubuntu.requests.head', return_value=MockResponse()):
283+
assert True == self.ubuntu_data_src.create_etag("https://example.org")
284+
assert self.ubuntu_data_src.config.etags == {"https://example.org":"0x1234"}
285+
assert False == self.ubuntu_data_src.create_etag("https://example.org")
286+
287+
288+

0 commit comments

Comments
 (0)