2525from typing import Iterable
2626from typing import List
2727from typing import Mapping
28+ from urllib .parse import urljoin
2829
2930import requests
3031from bs4 import BeautifulSoup
32+ from django .db .models .query import QuerySet
3133from packageurl import PackageURL
3234from univers .versions import AlpineLinuxVersion
3335
3436from vulnerabilities .helpers import is_cve
3537from vulnerabilities .importer import AdvisoryData
3638from vulnerabilities .importer import AffectedPackage
3739from vulnerabilities .importer import Importer
40+ from vulnerabilities .improver import MAX_CONFIDENCE
41+ from vulnerabilities .improver import Improver
42+ from vulnerabilities .improver import Inference
43+ from vulnerabilities .models import Advisory
3844from vulnerabilities .references import WireSharkReference
3945from vulnerabilities .references import XsaReference
4046from vulnerabilities .references import ZbxReference
@@ -60,7 +66,8 @@ def advisory_data(self) -> Iterable[AdvisoryData]:
6066 for link in advisory_links :
6167 record = fetch_response (link ).json ()
6268 if not record ["packages" ]:
63- raise Exception (f'"packages" not found in { link } ' )
69+ LOGGER .error (f'"packages" not found in { link !r} ' )
70+ continue
6471 advisories .extend (process_record (record ))
6572 return advisories
6673
@@ -72,50 +79,67 @@ def fetch_response(url):
7279 response = requests .get (url )
7380 if response .status_code == 200 :
7481 return response
75- raise Exception ("Failed to fetch data from the URL " )
82+ raise Exception (f "Failed to fetch data from { url !r } " )
7683
7784
78- def fetch_advisory_directory_links (page_response_content ) :
85+ def fetch_advisory_directory_links (page_response_content : str ) -> List [ str ] :
7986 """
80- Return `advisory_directory_links` present in `index_page`
87+ Return a list of advisory directory links present in `page_response_content` html string
8188 """
8289 index_page = BeautifulSoup (page_response_content , features = "lxml" )
8390 alpine_versions = [link .text for link in index_page .find_all ("a" ) if link .text .startswith ("v" )]
8491
85- assert alpine_versions , f"No versions found in { BASE_URL } "
92+ if not alpine_versions :
93+ LOGGER .error (f"No versions found in { BASE_URL !r} " )
94+ return []
8695
87- advisory_directory_links = [f" { BASE_URL } { version } " for version in alpine_versions ]
96+ advisory_directory_links = [urljoin ( BASE_URL , version ) for version in alpine_versions ]
8897
8998 return advisory_directory_links
9099
91100
92- def fetch_advisory_links (advisory_directory_page , advisory_directory_link ):
101+ def fetch_advisory_links (
102+ advisory_directory_page : str , advisory_directory_link : str
103+ ) -> Iterable [str ]:
93104 """
94105 Yield json file urls present in `advisory_directory_page`
95106 """
96107 advisory_directory_page = BeautifulSoup (advisory_directory_page , features = "lxml" )
97108 anchor_tags = advisory_directory_page .find_all ("a" )
98- assert anchor_tags , f"No anchor tags found in { advisory_directory_link } "
109+ if not anchor_tags :
110+ LOGGER .error (f"No anchor tags found in { advisory_directory_link !r} " )
111+ return iter ([])
99112 for anchor_tag in anchor_tags :
100113 if anchor_tag .text .endswith ("json" ):
101114 yield f"{ advisory_directory_link } { anchor_tag .text } "
102115
103116
117+ def check_for_attributes (record ) -> bool :
118+ attributes = ["distroversion" , "reponame" , "archs" ]
119+ for attribute in attributes :
120+ if attribute not in record :
121+ LOGGER .error (f'"{ attribute !r} " not found in { record !r} ' )
122+ return False
123+ return True
124+
125+
104126def process_record (record : dict ) -> List [AdvisoryData ]:
105127 """
106128 Return a list of AdvisoryData objects by processing data
107129 present in that `record`
108130 """
109131 if not record ["packages" ]:
110- raise Exception (f'"packages" not found in this { record !r} ' )
132+ LOGGER .error (f'"packages" not found in this record { record !r} ' )
133+ return []
111134
112135 advisories : List [AdvisoryData ] = []
113136
114137 for package in record ["packages" ]:
115- assert package ["pkg" ]
116- attributes = ["distroversion" , "reponame" , "archs" ]
117- for attribute in attributes :
118- assert attribute in record
138+ if not package ["pkg" ]:
139+ LOGGER .error (f'"pkg" not found in this package { package !r} ' )
140+ continue
141+ if not check_for_attributes (record ):
142+ continue
119143 loaded_advisories = load_advisories (
120144 package ["pkg" ],
121145 record ["distroversion" ],
@@ -133,18 +157,23 @@ def load_advisories(
133157 archs : List [str ],
134158) -> Iterable [AdvisoryData ]:
135159 """
136- Yields AdvisoryData by mapping data from `pkg_infos`
160+ Yield AdvisoryData by mapping data from `pkg_infos`
137161 and form PURL for AffectedPackages by using
138162 `distroversion`, `reponame`, `archs`
139163 """
140- assert pkg_infos .get ("name" ), '"name" is not available in package'
164+ if not pkg_infos .get ("name" ):
165+ LOGGER .error (f'"name" is not available in package { pkg_infos !r} ' )
166+ return []
141167
142168 for version , fixed_vulns in pkg_infos ["secfixes" ].items ():
143-
144- assert fixed_vulns , f"No fixed vulnerabilities in { version } "
169+ if not fixed_vulns :
170+ LOGGER .error (f"No fixed vulnerabilities in version { version !r} " )
171+ continue
145172
146173 for vuln_ids in fixed_vulns :
147- assert isinstance (vuln_ids , str )
174+ if not isinstance (vuln_ids , str ):
175+ LOGGER .error (f"{ vuln_ids !r} is not of `str` instance" )
176+ continue
148177 vuln_ids = vuln_ids .split ()
149178 aliases = []
150179 vuln_id = vuln_ids [0 ]
@@ -176,8 +205,11 @@ def load_advisories(
176205 try :
177206 fixed_version = AlpineLinuxVersion (version )
178207 except Exception as e :
179- raise Exception (f"{ version !r} Not a valid Alpine Version" ) from e
180- assert isinstance (archs , List )
208+ LOGGER .error (f"{ version !r} is not a valid AlpineVersion { e !r} " )
209+ continue
210+ if not isinstance (archs , List ):
211+ LOGGER .error (f"{ archs !r} is not of `List` instance" )
212+ continue
181213 if archs :
182214 for arch in archs :
183215 qualifiers ["arch" ] = arch
@@ -209,3 +241,21 @@ def load_advisories(
209241 affected_packages = affected_packages ,
210242 aliases = aliases ,
211243 )
244+
245+
246+ class AlpineBasicImprover (Improver ):
247+ @property
248+ def interesting_advisories (self ) -> QuerySet :
249+ return Advisory .objects .filter (created_by = AlpineImporter .qualified_name )
250+
251+ def get_inferences (self , advisory_data : AdvisoryData ) -> Iterable [Inference ]:
252+ """
253+ Generate and return Inferences for the given advisory data
254+ """
255+ for affected_package in advisory_data .affected_packages :
256+ fixed_purl = affected_package .get_fixed_purl ()
257+ yield Inference .from_advisory_data (
258+ advisory_data ,
259+ confidence = MAX_CONFIDENCE ,
260+ fixed_purl = fixed_purl ,
261+ )
0 commit comments