Skip to content

Commit 1515fd5

Browse files
committed
refactor signal
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 463ccd2 commit 1515fd5

4 files changed

Lines changed: 82 additions & 66 deletions

File tree

vulnerabilities/triage/admin.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from dje.admin import DataspacedAdmin
1414
from dje.admin import dejacode_site
1515
from dje.list_display import AsLink
16+
from vulnerabilities.triage.engine import evaluate_ruleset
1617
from vulnerabilities.triage.forms import AnalysisPresetForm
1718
from vulnerabilities.triage.forms import TriageRulesetForm
1819
from vulnerabilities.triage.models import AnalysisPreset
@@ -130,6 +131,21 @@ def get_changes_details(self, form):
130131
]
131132
return super().get_changes_details(form)
132133

134+
def save_model(self, request, obj, form, change):
135+
super().save_model(request, obj, form, change)
136+
137+
if not obj.enabled:
138+
# Records with an open Request are kept so re-enabling the ruleset reconnects to
139+
# it instead of opening a duplicate Request.
140+
obj.triage_records.filter(request__isnull=True).delete()
141+
return
142+
143+
if not change:
144+
return
145+
146+
for assignment in obj.product_triage_rulesets.select_related("product"):
147+
evaluate_ruleset(ruleset=obj, product=assignment.product)
148+
133149
def get_form(self, request, obj=None, change=False, **kwargs):
134150
kwargs["fields"] = [
135151
"name",

vulnerabilities/triage/engine.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.apps import apps
1010
from django.utils import timezone
1111

12+
from vulnerabilities.triage.models import ProductTriageRuleset
1213
from vulnerabilities.triage.models import TriageRecord
1314
from vulnerabilities.triage.rules import RULE_REGISTRY
1415
from vulnerabilities.triage.rules import rule_parameters_from_config
@@ -225,3 +226,13 @@ def evaluate_ruleset(ruleset, product, apply_preset=True):
225226
matched_rules_per_vulnerability_id=matched_rules_per_vulnerability_id,
226227
apply_preset=apply_preset,
227228
)
229+
230+
231+
def reevaluate_product_rulesets(product, apply_preset=True):
232+
"""Re-evaluate all enabled triage rulesets currently assigned to the product."""
233+
assignments = ProductTriageRuleset.objects.filter(
234+
product=product, ruleset__enabled=True
235+
).select_related("ruleset", "ruleset__analysis_preset")
236+
237+
for assignment in assignments:
238+
evaluate_ruleset(ruleset=assignment.ruleset, product=product, apply_preset=apply_preset)

vulnerabilities/triage/management/commands/create_triage_rulesets.py

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,18 @@ def add_arguments(self, parser):
139139
action="store_true",
140140
help=(
141141
"Delete all existing triage rulesets in the dataspace before recreating them."
142-
" This also removes all associated product assignments and triage records."
142+
" This also removes all associated product assignments and triage records,"
143+
" and every product using these rulesets will need to be reassigned manually"
144+
" afterward."
143145
),
144146
)
147+
parser.add_argument(
148+
"--noinput",
149+
"--no-input",
150+
action="store_false",
151+
dest="interactive",
152+
help="Tells Django to NOT prompt the user for input of any kind.",
153+
)
145154

146155
def handle(self, *args, **options):
147156
dataspace_name = options["dataspace"]
@@ -152,62 +161,68 @@ def handle(self, *args, **options):
152161
raise CommandError(f'Dataspace "{dataspace_name}" does not exist.')
153162

154163
if options["reset"]:
164+
if options["interactive"]:
165+
confirm = input(
166+
"You have requested a reset of the triage rulesets in dataspace"
167+
f' "{dataspace_name}".\n'
168+
"This will delete all triage rulesets and analysis presets in this"
169+
" dataspace, along with every associated product assignment and triage"
170+
" record. Every product using these rulesets will need to be reassigned"
171+
" manually afterward.\n"
172+
"Are you sure you want to do this?\n\n"
173+
" Type 'yes' to continue, or 'no' to cancel: "
174+
)
175+
else:
176+
confirm = "yes"
177+
178+
if confirm != "yes":
179+
self.stdout.write("Reset cancelled.")
180+
return
181+
155182
deleted_rulesets, _ = TriageRuleset.objects.filter(dataspace=dataspace).delete()
156183
deleted_presets, _ = AnalysisPreset.objects.filter(dataspace=dataspace).delete()
157184
self.stdout.write(
158185
f" Deleted {deleted_rulesets} existing ruleset(s) and {deleted_presets} preset(s)."
159186
)
187+
elif TriageRuleset.objects.filter(dataspace=dataspace).exists():
188+
raise CommandError(
189+
f'Dataspace "{dataspace_name}" already has triage rulesets.'
190+
" Use --reset to delete and recreate them."
191+
)
160192

161-
ruleset_created_count = 0
162193
for ruleset_data in REFERENCE_RULESETS:
163-
_, created = TriageRuleset.objects.get_or_create(
194+
TriageRuleset.objects.create(
164195
dataspace=dataspace,
165196
name=ruleset_data["name"],
166-
defaults={
167-
"description": ruleset_data["description"],
168-
"recommended_action": ruleset_data["recommended_action"],
169-
"precedence": ruleset_data["precedence"],
170-
"rules_config": ruleset_data["rules_config"],
171-
"enabled": True,
172-
},
197+
description=ruleset_data["description"],
198+
recommended_action=ruleset_data["recommended_action"],
199+
precedence=ruleset_data["precedence"],
200+
rules_config=ruleset_data["rules_config"],
201+
enabled=True,
173202
)
174-
if created:
175-
ruleset_created_count += 1
176-
self.stdout.write(f" Created: {ruleset_data['name']}")
177-
else:
178-
self.stdout.write(f" Already exists: {ruleset_data['name']}")
203+
self.stdout.write(f" Created: {ruleset_data['name']}")
179204

180-
preset_created_count = 0
181205
for preset_data in REFERENCE_PRESETS:
182-
ruleset_name = preset_data["ruleset_name"]
183-
preset, preset_created = AnalysisPreset.objects.get_or_create(
206+
preset = AnalysisPreset.objects.create(
184207
dataspace=dataspace,
185208
name=preset_data["name"],
186-
defaults={
187-
"description": preset_data.get("description", ""),
188-
"state": preset_data.get("state", ""),
189-
"justification": preset_data.get("justification", ""),
190-
"responses": preset_data.get("responses"),
191-
"detail": preset_data.get("detail", ""),
192-
},
209+
description=preset_data.get("description", ""),
210+
state=preset_data.get("state", ""),
211+
justification=preset_data.get("justification", ""),
212+
responses=preset_data.get("responses"),
213+
detail=preset_data.get("detail", ""),
193214
)
194-
if preset_created:
195-
preset_created_count += 1
196-
self.stdout.write(f" Created preset: {preset_data['name']}")
197-
else:
198-
self.stdout.write(f" Already exists: {preset_data['name']}")
199-
try:
200-
ruleset = TriageRuleset.objects.get(dataspace=dataspace, name=ruleset_name)
201-
if ruleset.analysis_preset_id != preset.pk:
202-
ruleset.analysis_preset = preset
203-
ruleset.save(update_fields=["analysis_preset"])
204-
self.stdout.write(f" Linked preset to ruleset: {ruleset_name}")
205-
except TriageRuleset.DoesNotExist:
206-
self.stdout.write(f" Ruleset not found: {ruleset_name}")
215+
self.stdout.write(f" Created preset: {preset_data['name']}")
216+
217+
ruleset_name = preset_data["ruleset_name"]
218+
ruleset = TriageRuleset.objects.get(dataspace=dataspace, name=ruleset_name)
219+
ruleset.analysis_preset = preset
220+
ruleset.save(update_fields=["analysis_preset"])
221+
self.stdout.write(f" Linked preset to ruleset: {ruleset_name}")
207222

208223
self.stdout.write(
209224
self.style.SUCCESS(
210-
f"{ruleset_created_count} ruleset(s) and {preset_created_count} preset(s)"
225+
f"{len(REFERENCE_RULESETS)} ruleset(s) and {len(REFERENCE_PRESETS)} preset(s)"
211226
f" created in dataspace '{dataspace_name}'."
212227
)
213228
)

vulnerabilities/triage/signals.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,10 @@
1212

1313
from vulnerabilities.triage.engine import delete_preset_analyses_for_product
1414
from vulnerabilities.triage.engine import evaluate_ruleset
15-
from vulnerabilities.triage.models import ProductTriageRuleset
15+
from vulnerabilities.triage.engine import reevaluate_product_rulesets
1616
from vulnerabilities.triage.models import TriageRecord
1717

1818

19-
def reevaluate_product_rulesets(product, apply_preset=True):
20-
"""Re-evaluate all enabled triage rulesets currently assigned to the product."""
21-
assignments = ProductTriageRuleset.objects.filter(
22-
product=product, ruleset__enabled=True
23-
).select_related("ruleset", "ruleset__analysis_preset")
24-
25-
for assignment in assignments:
26-
evaluate_ruleset(ruleset=assignment.ruleset, product=product, apply_preset=apply_preset)
27-
28-
29-
@receiver(post_save, sender="vulnerabilities_triage.TriageRuleset")
30-
def reevaluate_or_delete_on_ruleset_save(sender, instance, created, **kwargs):
31-
"""Re-evaluate assigned products on config change; delete records when disabled."""
32-
if not instance.enabled:
33-
# Records with an open Request are kept so re-enabling the ruleset reconnects to it
34-
# instead of opening a duplicate Request.
35-
instance.triage_records.filter(request__isnull=True).delete()
36-
return
37-
38-
if created:
39-
return
40-
41-
for assignment in instance.product_triage_rulesets.select_related("product"):
42-
evaluate_ruleset(ruleset=instance, product=assignment.product)
43-
44-
4519
@receiver(post_save, sender="vulnerabilities_triage.ProductTriageRuleset")
4620
def evaluate_on_assign(sender, instance, created, **kwargs):
4721
"""Evaluate the ruleset against the product as soon as it is assigned."""

0 commit comments

Comments
 (0)