Skip to content

Commit ebcd590

Browse files
authored
Merge pull request #11511 from DefectDojo/bugfix
Merge `bugfix` -> `dev` for release 2.42.0
2 parents 7a7ed5c + 9e14120 commit ebcd590

File tree

6 files changed

+7573
-42
lines changed

6 files changed

+7573
-42
lines changed

dojo/finding/views.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,25 +1438,7 @@ def reopen_finding(request, fid):
14381438
status.save()
14391439
# Clear the risk acceptance, if present
14401440
ra_helper.risk_unaccept(request.user, finding)
1441-
1442-
# Manage the jira status changes
1443-
push_to_jira = False
1444-
# Determine if the finding is in a group. if so, not push to jira
1445-
finding_in_group = finding.has_finding_group
1446-
# Check if there is a jira issue that needs to be updated
1447-
jira_issue_exists = finding.has_jira_issue or (finding.finding_group and finding.finding_group.has_jira_issue)
1448-
# Only push if the finding is not in a group
1449-
if jira_issue_exists:
1450-
# Determine if any automatic sync should occur
1451-
push_to_jira = jira_helper.is_push_all_issues(finding) \
1452-
or jira_helper.get_jira_instance(finding).finding_jira_sync
1453-
# Save the finding
1454-
finding.save(push_to_jira=(push_to_jira and not finding_in_group))
1455-
1456-
# we only push the group after saving the finding to make sure
1457-
# the updated data of the finding is pushed as part of the group
1458-
if push_to_jira and finding_in_group:
1459-
jira_helper.push_to_jira(finding.finding_group)
1441+
jira_helper.save_and_push_to_jira(finding)
14601442

14611443
reopen_external_issue(finding, "re-opened by defectdojo", "github")
14621444

dojo/fixtures/dojo_testdata.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,8 +2158,8 @@
21582158
"fields": {
21592159
"configuration_name": "Happy little JIRA 2",
21602160
"url": "https://defectdojo.atlassian.net/",
2161-
"username": "YOUR USERNAME",
2162-
"password": "YOU API TOKEN",
2161+
"username": "[YOUR USERNAME]",
2162+
"password": "[YOUR API TOKEN]",
21632163
"default_issue_type": "Task",
21642164
"epic_name_id": 10011,
21652165
"open_status_key": 11,
@@ -2253,7 +2253,7 @@
22532253
"component": "",
22542254
"enable_engagement_epic_mapping": true,
22552255
"jira_instance": 2,
2256-
"project_key": "key1"
2256+
"project_key": "NTEST"
22572257
}
22582258
},
22592259
{

dojo/jira_link/helper.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,14 +785,15 @@ def failure_to_add_message(message: str, exception: Exception, object: Any) -> b
785785
JIRAError.log_to_tempfile = False
786786
jira = get_jira_connection(jira_instance)
787787
except Exception as e:
788-
message = f"The following jira instance could not be connected: {jira_instance} - {e.text}"
788+
message = f"The following jira instance could not be connected: {jira_instance} - {e}"
789789
return failure_to_add_message(message, e, obj)
790790
# Set the list of labels to set on the jira issue
791791
labels = get_labels(obj) + get_tags(obj)
792792
if labels:
793793
labels = list(dict.fromkeys(labels)) # de-dup
794794
# Determine what due date to set on the jira issue
795795
duedate = None
796+
796797
if System_Settings.objects.get().enable_finding_sla:
797798
duedate = obj.sla_deadline()
798799
# Set the fields that will compose the jira issue
@@ -1104,6 +1105,7 @@ def get_issuetype_fields(
11041105

11051106
issuetype_fields = None
11061107
use_cloud_api = jira.deploymentType.lower() == "cloud" or jira._version < (9, 0, 0)
1108+
11071109
try:
11081110
if use_cloud_api:
11091111
try:
@@ -1706,3 +1708,24 @@ def process_resolution_from_jira(finding, resolution_id, resolution_name, assign
17061708
if status_changed:
17071709
finding.save()
17081710
return status_changed
1711+
1712+
1713+
def save_and_push_to_jira(finding):
1714+
# Manage the jira status changes
1715+
push_to_jira = False
1716+
# Determine if the finding is in a group. if so, not push to jira yet
1717+
finding_in_group = finding.has_finding_group
1718+
# Check if there is a jira issue that needs to be updated
1719+
jira_issue_exists = finding.has_jira_issue or (finding.finding_group and finding.finding_group.has_jira_issue)
1720+
# Only push if the finding is not in a group
1721+
if jira_issue_exists:
1722+
# Determine if any automatic sync should occur
1723+
push_to_jira = is_push_all_issues(finding) \
1724+
or get_jira_instance(finding).finding_jira_sync
1725+
# Save the finding
1726+
finding.save(push_to_jira=(push_to_jira and not finding_in_group))
1727+
1728+
# we only push the group after saving the finding to make sure
1729+
# the updated data of the finding is pushed as part of the group
1730+
if push_to_jira and finding_in_group:
1731+
push_to_jira(finding.finding_group)

dojo/risk_acceptance/helper.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,21 @@ def expire_now(risk_acceptance):
2222
reactivated_findings = []
2323
if risk_acceptance.reactivate_expired:
2424
for finding in risk_acceptance.accepted_findings.all():
25-
if not finding.active:
26-
logger.debug("%i:%s: unaccepting a.k.a reactivating finding.", finding.id, finding)
27-
finding.active = True
28-
finding.risk_accepted = False
25+
if not finding.active: # not sure why this is important
26+
logger.debug("%i:%s: unaccepting/reactivating finding.", finding.id, finding)
27+
2928
# Update any endpoint statuses on each of the findings
3029
update_endpoint_statuses(finding, accept_risk=False)
30+
risk_unaccept(None, finding, post_comments=False) # comments will be posted at end
3131

3232
if risk_acceptance.restart_sla_expired:
3333
finding.sla_start_date = timezone.now().date()
34+
finding.save(dedupe_option=False) # resave if changed after risk_unaccept
3435

35-
finding.save(dedupe_option=False)
3636
reactivated_findings.append(finding)
37-
# findings remain in this risk acceptance for reporting / metrics purposes
3837
else:
3938
logger.debug("%i:%s already active, no changes made.", finding.id, finding)
4039

41-
# best effort JIRA integration, no status changes
4240
post_jira_comments(risk_acceptance, risk_acceptance.accepted_findings.all(), expiration_message_creator)
4341

4442
risk_acceptance.expiration_date = timezone.now()
@@ -189,7 +187,7 @@ def expiration_handler(*args, **kwargs):
189187
product=risk_acceptance.engagement.product,
190188
url=reverse("view_risk_acceptance", args=(risk_acceptance.engagement.id, risk_acceptance.id)))
191189

192-
post_jira_comments(risk_acceptance, expiration_warning_message_creator, heads_up_days)
190+
post_jira_comments(risk_acceptance, risk_acceptance.accepted_findings.all(), expiration_warning_message_creator, heads_up_days)
193191

194192
risk_acceptance.expiration_date_warned = timezone.now()
195193
risk_acceptance.save()
@@ -243,20 +241,22 @@ def unaccepted_message_creator(risk_acceptance, heads_up_days=0):
243241

244242

245243
def post_jira_comment(finding, message_factory, heads_up_days=0):
246-
if not finding or not finding.has_jira_issue:
244+
if not finding or (not finding.has_jira_issue and not finding.has_jira_group_issue):
247245
return
248-
249246
jira_project = jira_helper.get_jira_project(finding)
250247

251248
if jira_project and jira_project.risk_acceptance_expiration_notification:
252249
jira_instance = jira_helper.get_jira_instance(finding)
253-
254250
if jira_instance:
255251

256252
jira_comment = message_factory(None, heads_up_days)
257253

258-
logger.debug("Creating JIRA comment for something risk acceptance related")
259-
jira_helper.add_simple_jira_comment(jira_instance, finding.jira_issue, jira_comment)
254+
jira_issue = None
255+
if finding.has_jira_issue:
256+
jira_issue = finding.jira_issue
257+
elif finding.has_jira_group_issue:
258+
jira_issue = finding.finding_group.jira_issue
259+
jira_helper.add_simple_jira_comment(jira_instance, jira_issue, jira_comment)
260260

261261

262262
def post_jira_comments(risk_acceptance, findings, message_factory, heads_up_days=0):
@@ -270,11 +270,15 @@ def post_jira_comments(risk_acceptance, findings, message_factory, heads_up_days
270270

271271
if jira_instance:
272272
jira_comment = message_factory(risk_acceptance, heads_up_days)
273-
274273
for finding in findings:
274+
jira_issue = None
275275
if finding.has_jira_issue:
276-
logger.debug("Creating JIRA comment for something risk acceptance related")
277-
jira_helper.add_simple_jira_comment(jira_instance, finding.jira_issue, jira_comment)
276+
jira_issue = finding.jira_issue
277+
elif finding.has_jira_group_issue:
278+
jira_issue = finding.finding_group.jira_issue
279+
280+
if jira_issue:
281+
jira_helper.add_simple_jira_comment(jira_instance, jira_issue, jira_comment)
278282

279283

280284
def get_expired_risk_acceptances_to_handle():
@@ -319,7 +323,7 @@ def simple_risk_accept(user: Dojo_User, finding: Finding, perform_save=True) ->
319323
))
320324

321325

322-
def risk_unaccept(user: Dojo_User, finding: Finding, perform_save=True) -> None:
326+
def risk_unaccept(user: Dojo_User, finding: Finding, perform_save=True, post_comments=True) -> None:
323327
logger.debug("unaccepting finding %i:%s if it is currently risk accepted", finding.id, finding)
324328
if finding.risk_accepted:
325329
logger.debug("unaccepting finding %i:%s", finding.id, finding)
@@ -336,7 +340,12 @@ def risk_unaccept(user: Dojo_User, finding: Finding, perform_save=True) -> None:
336340

337341
# post_jira_comment might reload from database so see unaccepted finding. but the comment
338342
# only contains some text so that's ok
339-
post_jira_comment(finding, unaccepted_message_creator)
343+
if post_comments:
344+
post_jira_comment(finding, unaccepted_message_creator)
345+
346+
# Update the JIRA obect for this finding
347+
jira_helper.save_and_push_to_jira(finding)
348+
340349
# Add a note to reflect that the finding was removed from the risk acceptance
341350
if user is not None:
342351
finding.notes.add(Notes.objects.create(

unittests/test_jira_import_and_pushing_api.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
import logging
33

44
from crum import impersonate
5+
from django.urls import reverse
56
from rest_framework.authtoken.models import Token
67
from rest_framework.test import APIClient
78
from vcr import VCR
89

10+
import dojo.risk_acceptance.helper as ra_helper
911
from dojo.jira_link import helper as jira_helper
10-
from dojo.models import Finding, Finding_Group, JIRA_Instance, User
12+
from dojo.models import Finding, Finding_Group, JIRA_Instance, Risk_Acceptance, User
1113

1214
from .dojo_test_case import DojoVCRAPITestCase, get_unit_tests_path, toggle_system_setting_boolean
1315

@@ -68,6 +70,7 @@ def setUp(self):
6870
self.scans_path = "/scans/"
6971
self.zap_sample5_filename = self.scans_path + "zap/5_zap_sample_one.xml"
7072
self.npm_groups_sample_filename = self.scans_path + "npm_audit/many_vuln_with_groups.json"
73+
self.client.force_login(self.get_test_admin())
7174

7275
def test_import_no_push_to_jira(self):
7376
import0 = self.import_scan_with_params(self.zap_sample5_filename, verified=True)
@@ -281,6 +284,65 @@ def test_import_twice_push_to_jira(self):
281284
self.assert_jira_issue_count_in_test(test_id1, 0)
282285
self.assert_jira_group_issue_count_in_test(test_id, 0)
283286

287+
def add_risk_acceptance(self, eid, data_risk_accceptance, fid=None):
288+
args = (eid, fid) if fid else (eid,)
289+
response = self.client.post(reverse("add_risk_acceptance", args=args), data_risk_accceptance)
290+
self.assertEqual(302, response.status_code, response.content[:1000])
291+
return response
292+
293+
def test_import_grouped_reopen_expired_sla(self):
294+
# steps
295+
# import scan, make sure they are in grouped JIRA
296+
# risk acceptance all the grouped findings, make sure they are closed in JIRA
297+
# expire risk acceptance on all grouped findings, make sure they are open in JIRA
298+
import0 = self.import_scan_with_params(self.npm_groups_sample_filename, scan_type="NPM Audit Scan", group_by="component_name+component_version", push_to_jira=True, verified=True)
299+
test_id = import0["test"]
300+
self.assert_jira_issue_count_in_test(test_id, 0)
301+
self.assert_jira_group_issue_count_in_test(test_id, 3)
302+
findings = self.get_test_findings_api(test_id)
303+
finding_id = findings["results"][0]["id"]
304+
305+
ra_data = {
306+
"name": "Accept: Unit test",
307+
"accepted_findings": [],
308+
"recommendation": "A",
309+
"recommendation_details": "recommendation 1",
310+
"decision": "A",
311+
"decision_details": "it has been decided!",
312+
"accepted_by": "pointy haired boss",
313+
"owner": 1,
314+
"expiration_date": "2024-12-31",
315+
"reactivate_expired": True,
316+
}
317+
318+
for finding in findings["results"]:
319+
ra_data["accepted_findings"].append(finding["id"])
320+
321+
pre_jira_status = self.get_jira_issue_status(finding_id)
322+
323+
response = self.add_risk_acceptance(1, data_risk_accceptance=ra_data)
324+
self.assertEqual("/engagement/1", response.url)
325+
326+
# We do this to update the JIRA
327+
for finding in ra_data["accepted_findings"]:
328+
self.patch_finding_api(finding, {"push_to_jira": True})
329+
330+
post_jira_status = self.get_jira_issue_status(finding_id)
331+
self.assertNotEqual(pre_jira_status, post_jira_status)
332+
333+
pre_jira_status = post_jira_status
334+
ra = Risk_Acceptance.objects.last()
335+
ra_helper.expire_now(ra)
336+
# We do this to update the JIRA
337+
for finding in ra_data["accepted_findings"]:
338+
self.patch_finding_api(finding, {"push_to_jira": True})
339+
340+
post_jira_status = self.get_jira_issue_status(finding_id)
341+
self.assertNotEqual(pre_jira_status, post_jira_status)
342+
343+
# by asserting full cassette is played we know all calls to JIRA have been made as expected
344+
self.assert_cassette_played()
345+
284346
def test_import_with_groups_twice_push_to_jira(self):
285347
import0 = self.import_scan_with_params(self.npm_groups_sample_filename, scan_type="NPM Audit Scan", group_by="component_name+component_version", push_to_jira=True, verified=True)
286348
test_id = import0["test"]

0 commit comments

Comments
 (0)