1- from dataclasses import dataclass
2- import dataclasses
1+ # Copyright (c) nexB Inc. and others. All rights reserved.
2+ # http://nexb.com and https://github.com/nexB/vulnerablecode/
3+ # The VulnerableCode software is licensed under the Apache License version 2.0.
4+ # Data generated with VulnerableCode require an acknowledgment.
5+ #
6+ # You may not use this software except in compliance with the License.
7+ # You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
8+ # Unless required by applicable law or agreed to in writing, software distributed
9+ # under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR
10+ # CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+ # specific language governing permissions and limitations under the License.
12+ #
13+ # When you publish or redistribute any data created with VulnerableCode or any VulnerableCode
14+ # derivative work, you must accompany this data with the following acknowledgment:
15+ #
16+ # Generated with VulnerableCode and provided on an 'AS IS' BASIS, WITHOUT WARRANTIES
17+ # OR CONDITIONS OF ANY KIND, either express or implied. No content created from
18+ # VulnerableCode should be considered or used as legal advice. Consult an Attorney
19+ # for any legal advice.
20+ # VulnerableCode is a free software from nexB Inc. and others.
21+ # Visit https://github.com/nexB/vulnerablecode/ for support and download.
22+
23+ import logging
324import os
25+ from typing import Iterable
426from typing import List
27+
28+ import yaml
29+ from dateutil import parser as dateparser
530from fetchcode .vcs import fetch_via_vcs
31+ from packageurl import PackageURL
32+ from univers .version_range import RANGE_CLASS_BY_SCHEMES
33+ from univers .version_range import VersionRange
34+ from univers .versions import Version
35+
36+ from vulnerabilities .importer import AdvisoryData
37+ from vulnerabilities .importer import AffectedPackage
38+ from vulnerabilities .importer import Importer
39+ from vulnerabilities .importer import Reference
40+ from vulnerabilities .importers .github import get_reference_id
41+
42+ LOGGER = logging .getLogger (__name__ )
43+
44+
45+ PURL_TYPE_BY_ECOSYSTEM = {
46+ "gem" : "gem" ,
47+ "go" : "golang" ,
48+ "maven" : "maven" ,
49+ "npm" : "npm" ,
50+ "nuget" : "nuget" ,
51+ "pypi" : "pypi" ,
52+ "packagist" : "composer" ,
53+ }
54+
655
756def fork_and_get_dir (url ):
857 return fetch_via_vcs (url ).dest_dir
958
10- def walk_in_directory (dir ):
11- for root , dirs , files in os .walk (dir ):
59+
60+ class ForkError (Exception ):
61+ pass
62+
63+
64+ class GitLabAPIImporter (Importer ):
65+ spdx_license_expression = "MIT"
66+ license_url = "https://gitlab.com/gitlab-org/advisories-community/-/blob/main/LICENSE"
67+ gitlab_url = "git+https://gitlab.com/gitlab-org/advisories-community/"
68+
69+ def advisory_data (self ) -> Iterable [AdvisoryData ]:
70+ """
71+ Return a list of AdvisoryData objects
72+ """
73+ try :
74+ fork_directory = fork_and_get_dir (self .gitlab_url )
75+ except Exception as e :
76+ LOGGER .error (f"Can't fetch url { self .gitlab_url } " )
77+ raise ForkError (e )
78+ ecosystems = ["nuget" , "maven" , "gem" , "npm" , "go" , "packagist" , "pypi" ]
79+ for ecosystem in ecosystems :
80+ for file in get_files (os .path .join (fork_directory , ecosystem )):
81+ yield parse_yaml_file (file )
82+
83+
84+ def get_files (dir ):
85+ for root , _ , files in os .walk (dir ):
1286 for file in files :
13- yield os .path .join (root , file )
14-
15- # binary search
16- def binary_search (lst , item ):
17- low = 0
18- high = len (lst ) - 1
19-
20- while low <= high :
21- mid = (low + high ) // 2
22- guess = lst [mid ]
23- if guess == item :
24- return mid
25- if guess > item :
26- high = mid - 1
87+ if "git" in root :
88+ yield os .path .join (root , file )
89+
90+
91+ def extract_references (urls : List [str ]) -> Iterable [Reference ]:
92+ """
93+ Yield `reference` by iterating over `reference_data`
94+ >>> list(extract_references(["https://github.com/advisories/GHSA-c9hw-wf7x-jp9j"]))
95+ [Reference(reference_id='GHSA-c9hw-wf7x-jp9j', url='https://github.com/advisories/GHSA-c9hw-wf7x-jp9j', severities=[])]
96+ >>> list(extract_references(["https://github.com/advisories/c9hw-wf7x-jp9j"]))
97+ [Reference(reference_id='', url='https://github.com/advisories/c9hw-wf7x-jp9j', severities=[])]
98+ """
99+ for url in urls :
100+ if not isinstance (url , str ):
101+ LOGGER .error (f"extract_references: url is not of type `str`: { url } " )
102+ continue
103+ if "GHSA-" in url .upper ():
104+ reference = Reference (url = url , reference_id = get_reference_id (url ))
27105 else :
28- low = mid + 1
29- return None
30-
31- #bubble sort
32- def bubble_sort (lst ):
33- for i in range (len (lst ) - 1 ):
34- for j in range (len (lst ) - i - 1 ):
35- if lst [j ] > lst [j + 1 ]:
36- lst [j ], lst [j + 1 ] = lst [j + 1 ], lst [j ]
37- return lst
38-
39- @dataclasses .dataclass (order = True )
40- class Test :
41- name : str
42- number : int
43- grades : List [str ] = dataclasses .field (default_factory = list )
44-
45- def __repr__ (self ):
46- return self .name
47-
48- def to_dict (self ):
49- return {
50- 'name' : self .name ,
51- 'number' : self .number ,
52- 'grades' : [grade for grade in self .list_of_grades ]
53- }
106+ reference = Reference (url = url )
107+ yield reference
108+
109+
110+ def not_empty (value ):
111+ return value is not None and value != ""
112+
113+
114+ def get_purl (package_slug ):
115+ """
116+ Return a PackageURL object from a package slug
117+ """
118+ LOGGER .error (package_slug )
119+ parts = package_slug .split ("/" )
120+ parts = list (filter (not_empty , parts ))
121+ # if package slug is of the form:
122+ # "nuget/NuGet.Core"
123+ if len (parts ) == 2 :
124+ type , name = parts
125+ return PackageURL (type = PURL_TYPE_BY_ECOSYSTEM [type ], name = name )
126+ # if package slug is of the form:
127+ # "nuget/github/user/abc/NuGet.Core"
128+ if len (parts ) >= 3 :
129+ type = parts [0 ]
130+ name = parts [- 1 ]
131+ namespace = "/" .join (parts [1 :- 1 ])
132+ return PackageURL (type = PURL_TYPE_BY_ECOSYSTEM [type ], namespace = namespace , name = name )
133+ LOGGER .error (f"get_purl: package_slug can not be parsed: { package_slug !r} " )
134+
135+
136+ def extract_affected_packages (
137+ affected_version_range : VersionRange , fixed_versions : List [str ], purl : PackageURL
138+ ) -> Iterable [AffectedPackage ]:
139+ """
140+ Yield a list of AffectedPackage objects
141+ """
142+ for fixed_version in fixed_versions or []:
143+ yield AffectedPackage (
144+ package = purl ,
145+ fixed_version = affected_version_range .version_class (fixed_version ),
146+ affected_version_range = affected_version_range ,
147+ )
148+
149+
150+ def parse_yaml_file (file ):
151+ with open (file , "r" ) as f :
152+ yaml_file = yaml .safe_load (f )
153+ if not isinstance (yaml_file , dict ):
154+ LOGGER .error (f"parse_yaml_file: yaml_file is not of type `dict`: { yaml_file !r} " )
155+ return
156+
157+ # refer to schema here https://gitlab.com/gitlab-org/advisories-community/-/blob/main/ci/schema/schema.json
158+ aliases = yaml_file .get ("identifiers" )
159+ summary = yaml_file .get ("title" )
160+ urls = yaml_file .get ("urls" )
161+ references = list (extract_references (urls ))
162+ date_published = dateparser .parse (yaml_file .get ("pubdate" ))
163+ purl : PackageURL = get_purl (yaml_file .get ("package_slug" ))
164+ fixed_versions = yaml_file .get ("fixed_versions" )
165+ vrc : VersionRange = RANGE_CLASS_BY_SCHEMES [purl .type ]
166+ affected_range = yaml_file .get ("affected_range" )
167+ affected_version_range = None
168+ try :
169+ affected_version_range = vrc .from_native (affected_range ) if affected_range else None
170+ except Exception :
171+ LOGGER .error (f"parse_yaml_file: affected_range is not parsable`: { affected_range !r} " )
172+
173+ if affected_version_range == NotImplementedError :
174+ LOGGER .error (f"parse_yaml_file: from_native is not implemented yet for { vrc .__name__ } `" )
175+ affected_version_range = None
176+
177+ affected_packages = list (
178+ extract_affected_packages (affected_version_range , fixed_versions , purl )
179+ )
180+
181+ return AdvisoryData (
182+ aliases = aliases ,
183+ summary = summary ,
184+ references = references ,
185+ date_published = date_published ,
186+ affected_packages = affected_packages ,
187+ )
0 commit comments