Skip to content

Commit ca2293a

Browse files
committed
Make review changes
Signed-off-by: Shivam Sandbhor <shivam@crowdsec.net>
1 parent dd4c14c commit ca2293a

5 files changed

Lines changed: 72 additions & 85 deletions

File tree

vulnerabilities/importers/apache_tomcat.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from bs4 import BeautifulSoup
3030
from univers.version_specifier import VersionSpecifier
3131
from univers.versions import MavenVersion
32+
from univers.versions import SemverVersion
3233
from packageurl import PackageURL
3334

3435
from vulnerabilities.data_source import Advisory
@@ -62,8 +63,10 @@ def updated_advisories(self):
6263
return self.batch_advisories(advisories)
6364

6465
def fetch_pages(self):
66+
# Here Semver is used because it has notion of major, minor versions.
6567
tomcat_major_versions = {
66-
i[0] for i in self.version_api.get("org.apache.tomcat:tomcat").valid_versions
68+
SemverVersion(i).value.major
69+
for i in self.version_api.get("org.apache.tomcat:tomcat").valid_versions
6770
}
6871
for version in tomcat_major_versions:
6972
page_url = self.base_url.format(version)

vulnerabilities/importers/github.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
import asyncio
2424
import os
2525
import dataclasses
26-
import json
27-
from xml.etree.ElementTree import parse
28-
from dateutil import parser
26+
from dateutil import parser as dateparser
2927
from typing import Set
3028
from typing import Tuple
3129
from typing import List
@@ -36,7 +34,6 @@
3634
from packageurl import PackageURL
3735
from univers.version_specifier import VersionSpecifier
3836
from univers.versions import version_class_by_package_type
39-
from univers.versions import InvalidVersion
4037

4138
from vulnerabilities.data_source import Advisory
4239
from vulnerabilities.data_source import DataSource
@@ -263,7 +260,7 @@ def process_response(self) -> List[Advisory]:
263260
for resp_page in self.advisories[ecosystem]:
264261
for adv in resp_page["data"]["securityVulnerabilities"]["edges"]:
265262
name = adv["node"]["package"]["name"]
266-
cutoff_time = parser.parse(adv["node"]["advisory"]["publishedAt"])
263+
cutoff_time = dateparser.parse(adv["node"]["advisory"]["publishedAt"])
267264
affected_purls = []
268265
unaffected_purls = []
269266
if self.process_name(ecosystem, name):

vulnerabilities/importers/safety_db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def updated_advisories(self) -> Set[Advisory]:
8686
# Skip it. The 'cumin' entry is wrong
8787
continue
8888

89-
all_package_versions = self.versions.get(package_name)
89+
all_package_versions = self.versions.get(package_name).valid_versions
9090
if not len(all_package_versions):
9191
# PyPi does not have data about this package, we skip these
9292
continue

vulnerabilities/package_managers.py

Lines changed: 47 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@
2323
import asyncio
2424
import dataclasses
2525
import pytz
26+
import xml.etree.ElementTree as ET
2627
from bs4 import BeautifulSoup
27-
from dateutil import parser
28+
from dateutil import parser as dateparser
2829
from json import JSONDecodeError
2930
from typing import Mapping
30-
from typing import Tuple
3131
from typing import Set
32+
from typing import List
3233
from datetime import datetime
3334

3435
from aiohttp import ClientSession
@@ -122,13 +123,19 @@ async def fetch(self, pkg, session):
122123
try:
123124
response = await session.request(method="GET", url=url)
124125
response = await response.json()
125-
for version in response["releases"]:
126-
if response["releases"][version]:
126+
for version, download_items in response["releases"].items():
127+
if download_items:
128+
latest_download_item = max(
129+
download_items,
130+
key=lambda download_item: dateparser.parse(
131+
download_item["upload_time_iso_8601"]
132+
),
133+
)
127134
versions.add(
128135
Version(
129136
value=version,
130-
release_date=parser.parse(
131-
response["releases"][version][-1]["upload_time_iso_8601"]
137+
release_date=dateparser.parse(
138+
latest_download_item["upload_time_iso_8601"]
132139
),
133140
)
134141
)
@@ -157,7 +164,8 @@ async def fetch(self, pkg, session):
157164
for version_info in response["versions"]:
158165
versions.add(
159166
Version(
160-
value=version_info["num"], release_date=parser.parse(version_info["updated_at"])
167+
value=version_info["num"],
168+
release_date=dateparser.parse(version_info["updated_at"]),
161169
)
162170
)
163171

@@ -183,7 +191,8 @@ async def fetch(self, pkg, session):
183191
for release in response:
184192
versions.add(
185193
Version(
186-
value=release["number"], release_date=parser.parse(release["created_at"])
194+
value=release["number"],
195+
release_date=dateparser.parse(release["created_at"]),
187196
)
188197
)
189198
except (ClientResponseError, JSONDecodeError):
@@ -211,7 +220,7 @@ async def fetch(self, pkg, session):
211220
for version in response.get("versions", []):
212221
release_date = response.get("time", {}).get(version)
213222
if release_date:
214-
release_date = parser.parse(release_date)
223+
release_date = dateparser.parse(release_date)
215224
versions.add(Version(value=version, release_date=release_date))
216225
else:
217226
versions.add(Version(value=version, release_date=None))
@@ -265,15 +274,12 @@ class MavenVersionAPI(VersionAPI):
265274
async def load_api(self, pkg_set):
266275
async with client_session() as session:
267276
await asyncio.gather(
268-
*[
269-
self.fetch(pkg, session)
270-
for pkg in pkg_set
271-
if pkg not in self.cache and "camel" not in pkg
272-
]
277+
*[self.fetch(pkg, session) for pkg in pkg_set if pkg not in self.cache]
273278
)
274279

275280
async def fetch(self, pkg, session) -> None:
276-
endpoint = self.artifact_url(pkg)
281+
artifact_comps = pkg.split(":")
282+
endpoint = self.artifact_url(artifact_comps)
277283
try:
278284
resp = await session.request(method="GET", url=endpoint)
279285
resp = await resp.read()
@@ -282,15 +288,11 @@ async def fetch(self, pkg, session) -> None:
282288
self.cache[pkg] = set()
283289
return
284290

285-
soup = BeautifulSoup(resp, features="lxml")
286-
try:
287-
self.cache[pkg] = self.extract_versions(soup)
288-
except:
289-
raise
291+
xml_resp = ET.ElementTree(ET.fromstring(resp.decode("utf-8")))
292+
self.cache[pkg] = self.extract_versions(xml_resp)
290293

291294
@staticmethod
292-
def artifact_url(pkg: str) -> str:
293-
artifact_comps = pkg.split(":")
295+
def artifact_url(artifact_comps: List[str]) -> str:
294296
base_url = "https://repo1.maven.org/maven2/{}"
295297
try:
296298
group_id, artifact_id = artifact_comps
@@ -306,35 +308,19 @@ def artifact_url(pkg: str) -> str:
306308
raise
307309

308310
group_url = group_id.replace(".", "/")
309-
suffix = group_url + "/" + artifact_id + "/"
311+
suffix = group_url + "/" + artifact_id + "/" + "maven-metadata.xml"
310312
endpoint = base_url.format(suffix)
311313

312314
return endpoint
313315

314316
@staticmethod
315-
def extract_versions(soup: BeautifulSoup) -> Set[Version]:
316-
pre_tag = soup.find("pre")
317-
prev_tag = None
318-
versions = set()
319-
for i, atag in enumerate(pre_tag):
320-
if atag.name == "a" and i != 0:
321-
prev_tag = atag
322-
elif prev_tag:
323-
text_groups = atag.split()
324-
if text_groups[-1] != "-":
325-
break
326-
date = " ".join(text_groups[:-1])
327-
if date != "-":
328-
versions.add(
329-
Version(
330-
value=prev_tag.text[:-1],
331-
release_date=parser.parse(date).replace(tzinfo=pytz.UTC),
332-
)
333-
)
334-
else:
335-
versions.add(Version(value=prev_tag.text[:-1], release_date=None))
317+
def extract_versions(xml_response: ET.ElementTree) -> Set[str]:
318+
all_versions = set()
319+
for child in xml_response.getroot().iter():
320+
if child.tag == "version":
321+
all_versions.add(Version(child.text))
336322

337-
return versions
323+
return all_versions
338324

339325

340326
class NugetVersionAPI(VersionAPI):
@@ -368,7 +354,7 @@ def extract_versions(resp: dict) -> Set[str]:
368354
all_versions.add(
369355
Version(
370356
value=entry["catalogEntry"]["version"],
371-
release_date=parser.parse(entry["catalogEntry"]["published"]),
357+
release_date=dateparser.parse(entry["catalogEntry"]["published"]),
372358
)
373359
)
374360
# FIXME: json response for YamlDotNet.Signed triggers this exception.
@@ -412,15 +398,16 @@ def extract_versions(resp: dict, pkg_name: str) -> Set[str]:
412398
for version in resp["packages"][pkg_name]:
413399
if "dev" in version:
414400
continue
401+
402+
# This if statement ensures, that all_versions contains only released versions
403+
# See https://github.com/composer/composer/blob/44a4429978d1b3c6223277b875762b2930e83e8c/doc/articles/versions.md#tags # nopep8
404+
# for explanation of removing 'v'
415405
all_versions.add(
416406
Version(
417-
value=version.replace("v", ""),
418-
release_date=parser.parse(resp["packages"][pkg_name][version]["time"]),
407+
value=version.lstrip("v"),
408+
release_date=dateparser.parse(resp["packages"][pkg_name][version]["time"]),
419409
)
420410
)
421-
# This if statement ensures, that all_versions contains only released versions
422-
# See https://github.com/composer/composer/blob/44a4429978d1b3c6223277b875762b2930e83e8c/doc/articles/versions.md#tags # nopep8
423-
# for explanation of removing 'v'
424411
return all_versions
425412

426413

@@ -440,8 +427,10 @@ async def load_api(self, repo_set):
440427
)
441428

442429
async def fetch(self, owner_repo: str, endpoint=None) -> None:
443-
# owner_repo is a string of format "{repo_owner}/{repo_name}"
444-
# Example value of owner_repo = "nexB/scancode-toolkit"
430+
"""
431+
owner_repo is a string of format "{repo_owner}/{repo_name}"
432+
Example value of owner_repo = "nexB/scancode-toolkit"
433+
"""
445434
if owner_repo not in self.cache:
446435
self.cache[owner_repo] = set()
447436

@@ -456,12 +445,13 @@ async def fetch(self, owner_repo: str, endpoint=None) -> None:
456445
version = None
457446
for links in release_entry.find_all("a"):
458447
if f"/{owner_repo}/releases/tag/" in links["href"].lower():
459-
version = links["href"].split("/")[-1]
448+
prefix, _slash, version = links["href"].rpartition("/")
449+
version = version.lstrip("v")
460450
break
461451

462452
release_date = release_entry.find("relative-time")["datetime"]
463453
self.cache[owner_repo].add(
464-
Version(value=version, release_date=parser.parse(release_date))
454+
Version(value=version, release_date=dateparser.parse(release_date))
465455
)
466456

467457
url = None
@@ -492,7 +482,8 @@ async def fetch(self, pkg, session):
492482
for release in response["releases"]:
493483
versions.add(
494484
Version(
495-
value=release["version"], release_date=parser.parse(release["inserted_at"])
485+
value=release["version"],
486+
release_date=dateparser.parse(release["inserted_at"]),
496487
)
497488
)
498489
except (ClientResponseError, JSONDecodeError):

vulnerabilities/tests/test_package_managers.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import asyncio
2424
import json
2525
import os
26+
import xml.etree.ElementTree as ET
2627
from datetime import datetime
2728
from bs4 import BeautifulSoup
2829
from dateutil.tz import tzlocal
@@ -353,40 +354,35 @@ class TestMavenVersionAPI(TestCase):
353354
@classmethod
354355
def setUpClass(cls):
355356
cls.version_api = MavenVersionAPI()
356-
with open(os.path.join(TEST_DATA, "maven_api", "easygcm.html"), "rb") as f:
357-
data = f.read()
358-
cls.response = BeautifulSoup(data, features="lxml")
359-
cls.content = data
357+
with open(os.path.join(TEST_DATA, "maven_api", "maven-metadata.xml")) as f:
358+
cls.response = ET.parse(f)
359+
360+
with open(os.path.join(TEST_DATA, "maven_api", "maven-metadata.xml"), "rb") as f:
361+
cls.content = f.read()
360362

361363
def test_artifact_url(self):
362-
eg_pkg1 = "org.apache:kafka"
363-
eg_pkg2 = "apple.msft.windows.mac.oss:exfat-ntfs"
364+
eg_comps1 = ["org.apache", "kafka"]
365+
eg_comps2 = ["apple.msft.windows.mac.oss", "exfat-ntfs"]
364366

365-
url1 = self.version_api.artifact_url(eg_pkg1)
366-
url2 = self.version_api.artifact_url(eg_pkg2)
367+
url1 = self.version_api.artifact_url(eg_comps1)
368+
url2 = self.version_api.artifact_url(eg_comps2)
367369

368-
assert "https://repo1.maven.org/maven2/org/apache/kafka/" == url1
369-
assert "https://repo1.maven.org/maven2/apple/msft/windows/mac/oss/exfat-ntfs/" == url2
370+
assert "https://repo1.maven.org/maven2/org/apache/kafka/maven-metadata.xml" == url1
371+
assert (
372+
"https://repo1.maven.org/maven2"
373+
"/apple/msft/windows/mac/oss/exfat-ntfs/maven-metadata.xml" == url2
374+
)
370375

371376
def test_extract_versions(self):
372-
expected_versions = {
373-
Version(value="1.3.0", release_date=datetime(2015, 3, 12, 15, 20, tzinfo=UTC)),
374-
Version(value="1.2.3", release_date=datetime(2014, 12, 22, 10, 53, tzinfo=UTC)),
375-
Version(value="1.2.2", release_date=datetime(2014, 12, 22, 10, 29, tzinfo=UTC)),
376-
}
377+
expected_versions = {Version("1.2.2"), Version("1.2.3"), Version("1.3.0")}
377378
assert expected_versions == self.version_api.extract_versions(self.response)
378379

379380
def test_fetch(self):
380381
assert self.version_api.get("org.apache:kafka") == VersionResponse()
381-
expected = {
382-
Version(value="1.2.2", release_date=datetime(2014, 12, 22, 10, 29, tzinfo=UTC)),
383-
Version(value="1.3.0", release_date=datetime(2015, 3, 12, 15, 20, tzinfo=UTC)),
384-
Version(value="1.2.3", release_date=datetime(2014, 12, 22, 10, 53, tzinfo=UTC)),
385-
}
386-
382+
expected = {"1.2.2", "1.2.3", "1.3.0"}
387383
client_session = MockClientSession(self.content)
388384
asyncio.run(self.version_api.fetch("org.apache:kafka", client_session))
389-
assert self.version_api.cache["org.apache:kafka"] == expected
385+
assert self.version_api.get("org.apache:kafka") == VersionResponse(valid_versions=expected)
390386

391387

392388
class TestNugetVersionAPI(TestCase):

0 commit comments

Comments
 (0)