Skip to content

Commit 0430d6b

Browse files
committed
use github api to find github releases
previous version with svn uses blocking synchronous api and do not work well with proxies the tag api do not contain the commit infos and thus we don't have the release date. I think what we really want in vulnerable code is the list of actual releases, and not really the tags. Signed-off-by: Pierre Tardy <pierre.tardy@renault.com>
1 parent a0a733a commit 0430d6b

3 files changed

Lines changed: 135 additions & 10 deletions

File tree

vulnerabilities/package_managers.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
# for any legal advice.
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.
22+
import os
2223
import asyncio
2324
import dataclasses
2425
import xml.etree.ElementTree as ET
@@ -30,6 +31,7 @@
3031
from typing import Set
3132

3233
from aiohttp import ClientSession
34+
import aiohttp
3335
from aiohttp.client_exceptions import ClientResponseError
3436
from aiohttp.client_exceptions import ServerDisconnectedError
3537
from bs4 import BeautifulSoup
@@ -378,20 +380,36 @@ class GitHubTagsAPI(VersionAPI):
378380

379381
package_type = "github"
380382

381-
async def fetch(self, owner_repo: str, session) -> None:
383+
def __init__(self, cache: Mapping[str, Set[Version]] = None):
384+
self.gh_token = os.getenv("GH_TOKEN")
385+
super().__init__(cache=cache)
386+
387+
async def fetch(self, owner_repo: str, session: aiohttp.ClientSession) -> None:
382388
"""
383389
owner_repo is a string of format "{repo_owner}/{repo_name}"
384390
Example value of owner_repo = "nexB/scancode-toolkit"
385391
"""
386392
self.cache[owner_repo] = set()
387-
endpoint = f"https://github.com/{owner_repo}"
388-
389-
tags_xml = check_output(["svn", "ls", "--xml", f"{endpoint}/tags"], text=True)
390-
elements = ET.fromstring(tags_xml)
391-
for entry in elements.iter("entry"):
392-
name = entry.find("name").text
393-
release_date = dateparser.parse(entry.find("commit/date").text)
394-
self.cache[owner_repo].add(Version(value=name, release_date=release_date))
393+
if self.gh_token:
394+
headers = {"Authorization": "token " + self.gh_token}
395+
endpoint = f"https://api.github.com/repos/{owner_repo}/releases"
396+
response = await session.get(endpoint)
397+
resp_json = await response.json()
398+
399+
for entry in resp_json:
400+
name = entry["tag_name"]
401+
release_date = dateparser.parse(entry["created_at"])
402+
self.cache[owner_repo].add(Version(value=name, release_date=release_date))
403+
else:
404+
# In case we don't have GH_TOKEN, we use the svn ls method to get the tags
405+
# It allows to get all the information needed in one request without any rate limiting
406+
endpoint = f"https://github.com/{owner_repo}"
407+
tags_xml = check_output(["svn", "ls", "--xml", f"{endpoint}/tags"], text=True)
408+
elements = ET.fromstring(tags_xml)
409+
for entry in elements.iter("entry"):
410+
name = entry.find("name").text
411+
release_date = dateparser.parse(entry.find("commit/date").text)
412+
395413

396414

397415
class HexVersionAPI(VersionAPI):
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
[
2+
{
3+
"url": "https://api.github.com/repos/nexB/vulnerablecode/releases/32748782",
4+
"assets_url": "https://api.github.com/repos/nexB/vulnerablecode/releases/32748782/assets",
5+
"upload_url": "https://uploads.github.com/repos/nexB/vulnerablecode/releases/32748782/assets{?name,label}",
6+
"html_url": "https://github.com/nexB/vulnerablecode/releases/tag/v20.10",
7+
"id": 32748782,
8+
"author": {
9+
"login": "pombredanne",
10+
"id": 675997,
11+
"node_id": "MDQ6VXNlcjY3NTk5Nw==",
12+
"avatar_url": "https://avatars.githubusercontent.com/u/675997?v=4",
13+
"gravatar_id": "",
14+
"url": "https://api.github.com/users/pombredanne",
15+
"html_url": "https://github.com/pombredanne",
16+
"followers_url": "https://api.github.com/users/pombredanne/followers",
17+
"following_url": "https://api.github.com/users/pombredanne/following{/other_user}",
18+
"gists_url": "https://api.github.com/users/pombredanne/gists{/gist_id}",
19+
"starred_url": "https://api.github.com/users/pombredanne/starred{/owner}{/repo}",
20+
"subscriptions_url": "https://api.github.com/users/pombredanne/subscriptions",
21+
"organizations_url": "https://api.github.com/users/pombredanne/orgs",
22+
"repos_url": "https://api.github.com/users/pombredanne/repos",
23+
"events_url": "https://api.github.com/users/pombredanne/events{/privacy}",
24+
"received_events_url": "https://api.github.com/users/pombredanne/received_events",
25+
"type": "User",
26+
"site_admin": false
27+
},
28+
"node_id": "MDc6UmVsZWFzZTMyNzQ4Nzgy",
29+
"tag_name": "v20.10",
30+
"target_commitish": "main",
31+
"name": "v20.10",
32+
"draft": false,
33+
"prerelease": false,
34+
"created_at": "2020-09-28T12:31:16Z",
35+
"published_at": "2020-10-19T10:46:17Z",
36+
"assets": [
37+
{
38+
"url": "https://api.github.com/repos/nexB/vulnerablecode/releases/assets/27230021",
39+
"id": 27230021,
40+
"node_id": "MDEyOlJlbGVhc2VBc3NldDI3MjMwMDIx",
41+
"name": "vulnerablecode-2020-10-19.json.xz",
42+
"label": null,
43+
"uploader": {
44+
"login": "pombredanne",
45+
"id": 675997,
46+
"node_id": "MDQ6VXNlcjY3NTk5Nw==",
47+
"avatar_url": "https://avatars.githubusercontent.com/u/675997?v=4",
48+
"gravatar_id": "",
49+
"url": "https://api.github.com/users/pombredanne",
50+
"html_url": "https://github.com/pombredanne",
51+
"followers_url": "https://api.github.com/users/pombredanne/followers",
52+
"following_url": "https://api.github.com/users/pombredanne/following{/other_user}",
53+
"gists_url": "https://api.github.com/users/pombredanne/gists{/gist_id}",
54+
"starred_url": "https://api.github.com/users/pombredanne/starred{/owner}{/repo}",
55+
"subscriptions_url": "https://api.github.com/users/pombredanne/subscriptions",
56+
"organizations_url": "https://api.github.com/users/pombredanne/orgs",
57+
"repos_url": "https://api.github.com/users/pombredanne/repos",
58+
"events_url": "https://api.github.com/users/pombredanne/events{/privacy}",
59+
"received_events_url": "https://api.github.com/users/pombredanne/received_events",
60+
"type": "User",
61+
"site_admin": false
62+
},
63+
"content_type": "application/x-xz",
64+
"state": "uploaded",
65+
"size": 13603356,
66+
"download_count": 20,
67+
"created_at": "2020-10-20T09:40:08Z",
68+
"updated_at": "2020-10-20T09:40:25Z",
69+
"browser_download_url": "https://github.com/nexB/vulnerablecode/releases/download/v20.10/vulnerablecode-2020-10-19.json.xz"
70+
}
71+
],
72+
"tarball_url": "https://api.github.com/repos/nexB/vulnerablecode/tarball/v20.10",
73+
"zipball_url": "https://api.github.com/repos/nexB/vulnerablecode/zipball/v20.10",
74+
"body": "This release comes with the new calver versioning scheme and an initial data dump.\r\n\r\nTo load the JSON data attached here:\r\n- extract it with `unxz vulnerablecode-2020-10-19.json.xz`\r\n- run `DJANGO_DEV=1 python manage.py loaddata vulnerablecode-2020-10-19.json`\r\n\r\nThe data import is not optimized yet and takes a long time."
75+
}
76+
]

vulnerabilities/tests/test_package_managers.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import os
2626
import xml.etree.ElementTree as ET
2727
from datetime import datetime
28-
from bs4 import BeautifulSoup
2928
from dateutil.tz import tzlocal
3029
from pytz import UTC
3130
from unittest import TestCase
@@ -34,8 +33,10 @@
3433
from vulnerabilities.package_managers import ComposerVersionAPI
3534
from vulnerabilities.package_managers import MavenVersionAPI
3635
from vulnerabilities.package_managers import NugetVersionAPI
36+
from vulnerabilities.package_managers import GitHubTagsAPI
3737
from vulnerabilities.package_managers import Version
3838
from vulnerabilities.package_managers import VersionResponse
39+
from vulnerabilities.package_managers import client_session
3940

4041
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
4142
TEST_DATA = os.path.join(BASE_DIR, "test_data")
@@ -51,6 +52,14 @@ async def request(self, *args, **kwargs):
5152
mock_response.read = self.read
5253
return mock_response
5354

55+
def get(self, *args, **kwargs):
56+
kwargs["method"] = "get"
57+
return self.request(*args, **kwargs)
58+
59+
def post(self, *args, **kwargs):
60+
kwargs["method"] = "post"
61+
return self.request(*args, **kwargs)
62+
5463
async def json(self):
5564
return self.return_val
5665

@@ -496,3 +505,25 @@ def test_fetch(self):
496505
# self.version_api.load_to_api("Exfat.Ntfs")
497506

498507
# assert self.version_api.get("Exfat.Ntfs") == self.expected_versions
508+
509+
510+
class TestGitHubTagsAPI(TestCase):
511+
def setUp(self) -> None:
512+
self.version_api = GitHubTagsAPI()
513+
with open(os.path.join(TEST_DATA, "github_api", "release_response.json")) as f:
514+
self.response = json.load(f)
515+
return super().setUp()
516+
517+
def test_fetch(self):
518+
async def async_run():
519+
session = MockClientSession(self.response)
520+
await self.version_api.fetch("nexB/vulnerablecode", session)
521+
522+
asyncio.run(async_run())
523+
524+
assert self.version_api.get("nexB/vulnerablecode") == VersionResponse(
525+
newer_versions=set(),
526+
valid_versions={
527+
"v20.10",
528+
},
529+
)

0 commit comments

Comments
 (0)