@@ -35,6 +35,13 @@ def get_package_filter(self):
3535 """Return queryset filter kwargs for ProductPackage to identify violating packages."""
3636 return {}
3737
38+ def filter_queryset (self , queryset , parameters = None ):
39+ """Filter a ProductPackage queryset to packages that violate this rule."""
40+ package_filter = self .get_package_filter ()
41+ if package_filter :
42+ return queryset .filter (** package_filter )
43+ return queryset
44+
3845
3946class PackageBaseRule (BaseRule ):
4047 """Base for rules that count packages matching a fixed filter within a product."""
@@ -112,27 +119,24 @@ class VulnerabilityDetectedRule(BaseRule):
112119 rule_type = "vulnerability_detected"
113120 label = "Vulnerability Detected"
114121 severity = "error"
115- description = "Detects packages with at least one known vulnerability (non-null risk score) ."
122+ description = "Detects packages with at least one known vulnerability."
116123 parameters_schema = {
117124 "min_risk_score" : "Minimum risk score (0.0-10.0). Default: any vulnerability." ,
118125 }
119126
120- def get_package_filter (self ):
121- return {"package__risk_score__isnull" : False }
122-
123- def count_violations (self , product , threshold , parameters ):
124- Package = apps .get_model ("component_catalog" , "package" )
125-
126- packages = Package .objects .filter (
127- productpackages__product = product ,
128- risk_score__isnull = False ,
129- )
130-
127+ def filter_queryset (self , queryset , parameters = None ):
128+ parameters = parameters or {}
131129 min_risk_score = parameters .get ("min_risk_score" )
132130 if min_risk_score is not None :
133- packages = packages .filter (risk_score__gte = min_risk_score )
131+ return queryset .filter (
132+ package__affected_by_vulnerabilities__risk_score__gte = min_risk_score
133+ )
134+ return queryset .filter (package__affected_by_vulnerabilities__isnull = False )
134135
135- count = packages .count ()
136+ def count_violations (self , product , threshold , parameters ):
137+ ProductPackage = apps .get_model ("product_portfolio" , "productpackage" )
138+ product_packages = ProductPackage .objects .filter (product = product )
139+ count = self .filter_queryset (product_packages , parameters ).distinct ().count ()
136140 return count if count > threshold else 0
137141
138142
@@ -144,28 +148,28 @@ class UnresolvedVulnerabilityCountRule(BaseRule):
144148 "Detects packages with known vulnerabilities that have not been triaged or addressed."
145149 )
146150
147- def count_violations (self , product , threshold , parameters ):
151+ def filter_queryset (self , queryset , parameters = None ):
148152 PackageAffectedByVulnerability = apps .get_model (
149153 "component_catalog" , "packageaffectedbyvulnerability"
150154 )
151155 VulnerabilityAnalysis = apps .get_model ("vulnerabilities" , "vulnerabilityanalysis" )
152156
153157 terminal_analysis = VulnerabilityAnalysis .objects .filter (
154- product = product ,
158+ product_package = OuterRef ( OuterRef ( "pk" )) ,
155159 state__in = TERMINAL_VULNERABILITY_STATES ,
156- package = OuterRef ("package" ),
157160 vulnerability = OuterRef ("vulnerability" ),
158161 )
159-
160- count = (
161- PackageAffectedByVulnerability .objects .filter (
162- package__productpackages__product = product ,
163- )
164- .annotate (has_terminal_analysis = Exists (terminal_analysis ))
165- .filter (has_terminal_analysis = False )
166- .distinct ()
167- .count ()
162+ unresolved_link = (
163+ PackageAffectedByVulnerability .objects .filter (package = OuterRef ("package" ))
164+ .annotate (has_terminal = Exists (terminal_analysis ))
165+ .filter (has_terminal = False )
168166 )
167+ return queryset .filter (Exists (unresolved_link ))
168+
169+ def count_violations (self , product , threshold , parameters ):
170+ ProductPackage = apps .get_model ("product_portfolio" , "productpackage" )
171+ product_packages = ProductPackage .objects .filter (product = product )
172+ count = self .filter_queryset (product_packages ).distinct ().count ()
169173 return count if count > threshold else 0
170174
171175
@@ -187,35 +191,37 @@ class StaleVulnerabilityRule(BaseRule):
187191 ),
188192 }
189193
190- def count_violations (self , product , threshold , parameters ):
194+ def filter_queryset (self , queryset , parameters = None ):
191195 PackageAffectedByVulnerability = apps .get_model (
192196 "component_catalog" , "packageaffectedbyvulnerability"
193197 )
194198 VulnerabilityAnalysis = apps .get_model ("vulnerabilities" , "vulnerabilityanalysis" )
195199
200+ parameters = parameters or {}
196201 max_days = parameters .get ("max_days" , 30 )
197202 min_risk_score = parameters .get ("min_risk_score" , 8.0 )
198203 cutoff_date = timezone .now () - timedelta (days = max_days )
199204
200205 terminal_analysis = VulnerabilityAnalysis .objects .filter (
201- product = product ,
206+ product_package = OuterRef ( OuterRef ( "pk" )) ,
202207 state__in = TERMINAL_VULNERABILITY_STATES ,
203- package = OuterRef ("package" ),
204208 vulnerability = OuterRef ("vulnerability" ),
205209 )
206-
207- count = (
210+ stale_link = (
208211 PackageAffectedByVulnerability .objects .filter (
209- package__productpackages__product = product ,
212+ package = OuterRef ( "package" ) ,
210213 vulnerability__risk_score__gte = min_risk_score ,
211214 detected_date__lte = cutoff_date ,
212215 )
213- .annotate (has_terminal_analysis = Exists (terminal_analysis ))
214- .filter (has_terminal_analysis = False )
215- .values ("package_id" )
216- .distinct ()
217- .count ()
216+ .annotate (has_terminal = Exists (terminal_analysis ))
217+ .filter (has_terminal = False )
218218 )
219+ return queryset .filter (Exists (stale_link ))
220+
221+ def count_violations (self , product , threshold , parameters ):
222+ ProductPackage = apps .get_model ("product_portfolio" , "productpackage" )
223+ product_packages = ProductPackage .objects .filter (product = product )
224+ count = self .filter_queryset (product_packages , parameters ).distinct ().count ()
219225 return count if count > threshold else 0
220226
221227
0 commit comments