|
36 | 36 | from packageurl import PackageURL |
37 | 37 |
|
38 | 38 | from vulnerabilities.data_source import Advisory |
39 | | -from vulnerabilities.data_source import DataSource |
| 39 | +from vulnerabilities.data_source import GitDataSource |
40 | 40 |
|
41 | 41 | NPM_URL = 'https://registry.npmjs.org{}' |
42 | | -PAGE = '/-/npm/v1/security/advisories?perPage=100&page=0' |
43 | 42 |
|
44 | 43 |
|
45 | | -class NpmDataSource(DataSource): |
| 44 | +class NpmDataSource(GitDataSource): |
46 | 45 |
|
47 | | - def __init__(self, *args, **kwargs): |
48 | | - super().__init__(*args, **kwargs) |
49 | | - self._api_response = None |
| 46 | + def __enter__(self): |
| 47 | + super(NpmDataSource, self).__enter__() |
50 | 48 | self._versions = VersionAPI() |
51 | | - self._added_records, self._updated_records = [], [] |
52 | | - self._added_advisories, self._updated_advisories = [], [] |
| 49 | + if not getattr(self, '_added_files', None): |
| 50 | + self._added_files, self._updated_files = self.file_changes( |
| 51 | + recursive=True, file_ext='json', subdir='./vuln/npm') |
53 | 52 |
|
54 | | - def __enter__(self): |
55 | | - self._api_response = self._fetch() |
56 | | - self._categorize_records() |
| 53 | + def updated_advisories(self) -> Set[Advisory]: |
| 54 | + files = self._updated_files.union(self._added_files) |
| 55 | + advisories = [] |
| 56 | + for f in files: |
| 57 | + processed_data = self.process_file(f) |
| 58 | + if processed_data: |
| 59 | + advisories.extend(processed_data) |
| 60 | + print(advisories) |
| 61 | + return self.batch_advisories(advisories) |
57 | 62 |
|
58 | 63 | @property |
59 | 64 | def versions(self): # quick hack to make it patchable |
60 | 65 | return self._versions |
61 | 66 |
|
62 | | - def _fetch(self) -> Mapping[str, Any]: |
63 | | - data = None |
64 | | - nextpage = PAGE |
65 | | - while nextpage: |
66 | | - try: |
67 | | - with urlopen(NPM_URL.format(nextpage)) as response: |
68 | | - response = json.load(response) |
69 | | - |
70 | | - if data is None: |
71 | | - data = response |
72 | | - else: |
73 | | - data['objects'].extend(response.get('objects', [])) |
74 | | - |
75 | | - nextpage = response.get('urls', {}).get('next') |
76 | | - |
77 | | - except HTTPError as error: |
78 | | - if error.code == 404: |
79 | | - return data |
80 | | - else: |
81 | | - raise |
82 | | - |
83 | | - return data |
84 | | - |
85 | | - def _categorize_records(self) -> None: |
86 | | - for advisory in self._api_response['objects']: |
87 | | - created = parse(advisory['created']).timestamp() |
88 | | - updated = parse(advisory['updated']).timestamp() |
89 | | - |
90 | | - if created > self.cutoff_timestamp: |
91 | | - self._added_records.append(advisory) |
92 | | - elif updated > self.cutoff_timestamp: |
93 | | - self._updated_records.append(advisory) |
94 | | - |
95 | | - def _parse(self, records: List[Mapping[str, Any]]) -> List[Advisory]: |
| 67 | + def process_file(self, file) -> List[Advisory]: |
| 68 | + print(file) |
| 69 | + with open(file) as f: |
| 70 | + record = json.load(f) |
| 71 | + print(record) |
96 | 72 | advisories = [] |
97 | | - |
98 | | - for record in records: |
99 | | - package_name = record['module_name'] |
100 | | - all_versions = self.versions.get(package_name) |
101 | | - aff_range = record.get('vulnerable_versions', '') |
102 | | - fixed_range = record.get('patched_versions', '') |
103 | | - |
104 | | - impacted_versions, resolved_versions = categorize_versions( |
105 | | - all_versions, |
106 | | - aff_range, |
107 | | - fixed_range |
108 | | - ) |
109 | | - |
110 | | - impacted_purls = _versions_to_purls(package_name, impacted_versions) |
111 | | - resolved_purls = _versions_to_purls(package_name, resolved_versions) |
112 | | - |
113 | | - for cve_id in record.get('cves') or ['']: |
114 | | - advisories.append(Advisory( |
115 | | - summary=record.get('overview', ''), |
116 | | - cve_id=cve_id, |
117 | | - impacted_package_urls=impacted_purls, |
118 | | - resolved_package_urls=resolved_purls, |
119 | | - reference_urls=[NPM_URL.format(f'/-/npm/v1/advisories/{record["id"]}')], |
120 | | - )) |
| 73 | + package_name = record['module_name'] |
| 74 | + all_versions = self.versions.get(package_name) |
| 75 | + aff_range = record.get('vulnerable_versions', '') |
| 76 | + fixed_range = record.get('patched_versions', '') |
| 77 | + |
| 78 | + impacted_versions, resolved_versions = categorize_versions( |
| 79 | + all_versions, |
| 80 | + aff_range, |
| 81 | + fixed_range |
| 82 | + ) |
| 83 | + |
| 84 | + impacted_purls = _versions_to_purls(package_name, impacted_versions) |
| 85 | + resolved_purls = _versions_to_purls(package_name, resolved_versions) |
| 86 | + |
| 87 | + for cve_id in record.get('cves') or ['']: |
| 88 | + advisories.append(Advisory( |
| 89 | + summary=record.get('overview', ''), |
| 90 | + cve_id=cve_id, |
| 91 | + impacted_package_urls=impacted_purls, |
| 92 | + resolved_package_urls=resolved_purls, |
| 93 | + reference_urls=[NPM_URL.format(f'/-/npm/v1/advisories/{record["id"]}')], |
| 94 | + )) |
121 | 95 |
|
122 | 96 | return advisories |
123 | 97 |
|
124 | | - def added_advisories(self) -> Set[Advisory]: |
125 | | - return self.batch_advisories(self._parse(self._added_records)) |
126 | | - |
127 | | - def updated_advisories(self) -> Set[Advisory]: |
128 | | - return self.batch_advisories(self._parse(self._updated_records)) |
129 | | - |
130 | 98 |
|
131 | 99 | def _versions_to_purls(package_name, versions): |
132 | 100 | purls = {f'pkg:npm/{quote(package_name)}@{v}' for v in versions} |
|
0 commit comments