|
5 | 5 | import re |
6 | 6 |
|
7 | 7 | # Third party imports |
| 8 | +import pghistory |
8 | 9 | from django.contrib import messages |
9 | 10 | from django.contrib.admin.utils import NestedObjects |
10 | 11 | from django.core.exceptions import PermissionDenied |
@@ -85,85 +86,98 @@ def webhook(request, secret=None): |
85 | 86 | if request.content_type != "application/json": |
86 | 87 | return webhook_responser_handler("debug", "only application/json supported") |
87 | 88 | # Time to process the request |
| 89 | + # Parse the JSON first to get webhook event type for context |
88 | 90 | try: |
89 | 91 | parsed = json.loads(request.body.decode("utf-8")) |
90 | | - # Check if the events supplied are supported |
91 | | - if parsed.get("webhookEvent") not in {"comment_created", "jira:issue_updated"}: |
92 | | - return webhook_responser_handler("info", f"Unrecognized JIRA webhook event received: {parsed.get('webhookEvent')}") |
93 | | - |
94 | | - if parsed.get("webhookEvent") == "jira:issue_updated": |
95 | | - # xml examples at the end of file |
96 | | - jid = parsed["issue"]["id"] |
97 | | - # This may raise a 404, but it will be handled in the exception response |
98 | | - try: |
99 | | - jissue = JIRA_Issue.objects.get(jira_id=jid) |
100 | | - except JIRA_Instance.DoesNotExist: |
101 | | - return webhook_responser_handler("info", f"JIRA issue {jid} is not linked to a DefectDojo Finding") |
102 | | - findings = None |
103 | | - # Determine what type of object we will be working with |
104 | | - if jissue.finding: |
105 | | - logger.debug(f"Received issue update for {jissue.jira_key} for finding {jissue.finding.id}") |
106 | | - findings = [jissue.finding] |
107 | | - elif jissue.finding_group: |
108 | | - logger.debug(f"Received issue update for {jissue.jira_key} for finding group {jissue.finding_group}") |
109 | | - findings = jissue.finding_group.findings.all() |
110 | | - elif jissue.engagement: |
111 | | - return webhook_responser_handler("debug", "Update for engagement ignored") |
112 | | - else: |
113 | | - return webhook_responser_handler("info", f"Received issue update for {jissue.jira_key} for unknown object") |
114 | | - # Process the assignee if present |
115 | | - assignee = parsed["issue"]["fields"].get("assignee") |
116 | | - assignee_name = "Jira User" |
117 | | - if assignee is not None: |
118 | | - # First look for the 'name' field. If not present, try 'displayName'. Else put None |
119 | | - assignee_name = assignee.get("name", assignee.get("displayName")) |
120 | | - |
121 | | - # "resolution":{ |
122 | | - # "self":"http://www.testjira.com/rest/api/2/resolution/11", |
123 | | - # "id":"11", |
124 | | - # "description":"Cancelled by the customer.", |
125 | | - # "name":"Cancelled" |
126 | | - # }, |
127 | | - |
128 | | - # or |
129 | | - # "resolution": null |
130 | | - |
131 | | - # or |
132 | | - # "resolution": "None" |
133 | | - |
134 | | - resolution = parsed["issue"]["fields"]["resolution"] |
135 | | - resolution = resolution if resolution and resolution != "None" else None |
136 | | - resolution_id = resolution["id"] if resolution else None |
137 | | - resolution_name = resolution["name"] if resolution else None |
138 | | - jira_now = parse_datetime(parsed["issue"]["fields"]["updated"]) |
139 | | - |
140 | | - if findings: |
141 | | - for finding in findings: |
142 | | - jira_helper.process_resolution_from_jira(finding, resolution_id, resolution_name, assignee_name, jira_now, jissue, finding_group=jissue.finding_group) |
143 | | - # Check for any comment that could have come along with the resolution |
144 | | - if (error_response := check_for_and_create_comment(parsed)) is not None: |
145 | | - return error_response |
146 | | - |
147 | | - if parsed.get("webhookEvent") == "comment_created": |
148 | | - if (error_response := check_for_and_create_comment(parsed)) is not None: |
149 | | - return error_response |
150 | | - |
151 | 92 | except Exception as e: |
152 | | - # Check if the issue is originally a 404 |
153 | | - if isinstance(e, Http404): |
154 | | - return webhook_responser_handler("debug", str(e)) |
155 | | - # Try to get a little more information on the exact exception |
| 93 | + return webhook_responser_handler("debug", f"Failed to parse JSON: {e}") |
| 94 | + |
| 95 | + # Check if the events supplied are supported |
| 96 | + if parsed.get("webhookEvent") not in {"comment_created", "jira:issue_updated"}: |
| 97 | + return webhook_responser_handler("info", f"Unrecognized JIRA webhook event received: {parsed.get('webhookEvent')}") |
| 98 | + |
| 99 | + # Wrap processing with pghistory context for audit trail |
| 100 | + # JIRA webhooks don't have a user session, so we create a new context |
| 101 | + with pghistory.context( |
| 102 | + source="jira_webhook", |
| 103 | + jira_event=parsed.get("webhookEvent"), |
| 104 | + ): |
156 | 105 | try: |
157 | | - message = ( |
158 | | - f"Original Exception: {e}\n" |
159 | | - f"jira webhook body parsed:\n{json.dumps(parsed, indent=4)}" |
160 | | - ) |
161 | | - except Exception: |
162 | | - message = ( |
163 | | - f"Original Exception: {e}\n" |
164 | | - f"jira webhook body :\n{request.body.decode('utf-8')}" |
165 | | - ) |
166 | | - return webhook_responser_handler("debug", message) |
| 106 | + if parsed.get("webhookEvent") == "jira:issue_updated": |
| 107 | + # xml examples at the end of file |
| 108 | + jid = parsed["issue"]["id"] |
| 109 | + # This may raise a 404, but it will be handled in the exception response |
| 110 | + try: |
| 111 | + jissue = JIRA_Issue.objects.get(jira_id=jid) |
| 112 | + except JIRA_Instance.DoesNotExist: |
| 113 | + return webhook_responser_handler("info", f"JIRA issue {jid} is not linked to a DefectDojo Finding") |
| 114 | + # Add jira_key to context now that we have it |
| 115 | + pghistory.context(jira_key=jissue.jira_key) |
| 116 | + findings = None |
| 117 | + # Determine what type of object we will be working with |
| 118 | + if jissue.finding: |
| 119 | + logger.debug(f"Received issue update for {jissue.jira_key} for finding {jissue.finding.id}") |
| 120 | + findings = [jissue.finding] |
| 121 | + elif jissue.finding_group: |
| 122 | + logger.debug(f"Received issue update for {jissue.jira_key} for finding group {jissue.finding_group}") |
| 123 | + findings = jissue.finding_group.findings.all() |
| 124 | + elif jissue.engagement: |
| 125 | + return webhook_responser_handler("debug", "Update for engagement ignored") |
| 126 | + else: |
| 127 | + return webhook_responser_handler("info", f"Received issue update for {jissue.jira_key} for unknown object") |
| 128 | + # Process the assignee if present |
| 129 | + assignee = parsed["issue"]["fields"].get("assignee") |
| 130 | + assignee_name = "Jira User" |
| 131 | + if assignee is not None: |
| 132 | + # First look for the 'name' field. If not present, try 'displayName'. Else put None |
| 133 | + assignee_name = assignee.get("name", assignee.get("displayName")) |
| 134 | + |
| 135 | + # "resolution":{ |
| 136 | + # "self":"http://www.testjira.com/rest/api/2/resolution/11", |
| 137 | + # "id":"11", |
| 138 | + # "description":"Cancelled by the customer.", |
| 139 | + # "name":"Cancelled" |
| 140 | + # }, |
| 141 | + |
| 142 | + # or |
| 143 | + # "resolution": null |
| 144 | + |
| 145 | + # or |
| 146 | + # "resolution": "None" |
| 147 | + |
| 148 | + resolution = parsed["issue"]["fields"]["resolution"] |
| 149 | + resolution = resolution if resolution and resolution != "None" else None |
| 150 | + resolution_id = resolution["id"] if resolution else None |
| 151 | + resolution_name = resolution["name"] if resolution else None |
| 152 | + jira_now = parse_datetime(parsed["issue"]["fields"]["updated"]) |
| 153 | + |
| 154 | + if findings: |
| 155 | + for finding in findings: |
| 156 | + jira_helper.process_resolution_from_jira(finding, resolution_id, resolution_name, assignee_name, jira_now, jissue, finding_group=jissue.finding_group) |
| 157 | + # Check for any comment that could have come along with the resolution |
| 158 | + if (error_response := check_for_and_create_comment(parsed)) is not None: |
| 159 | + return error_response |
| 160 | + |
| 161 | + if parsed.get("webhookEvent") == "comment_created": |
| 162 | + if (error_response := check_for_and_create_comment(parsed)) is not None: |
| 163 | + return error_response |
| 164 | + |
| 165 | + except Exception as e: |
| 166 | + # Check if the issue is originally a 404 |
| 167 | + if isinstance(e, Http404): |
| 168 | + return webhook_responser_handler("debug", str(e)) |
| 169 | + # Try to get a little more information on the exact exception |
| 170 | + try: |
| 171 | + message = ( |
| 172 | + f"Original Exception: {e}\n" |
| 173 | + f"jira webhook body parsed:\n{json.dumps(parsed, indent=4)}" |
| 174 | + ) |
| 175 | + except Exception: |
| 176 | + message = ( |
| 177 | + f"Original Exception: {e}\n" |
| 178 | + f"jira webhook body :\n{request.body.decode('utf-8')}" |
| 179 | + ) |
| 180 | + return webhook_responser_handler("debug", message) |
167 | 181 |
|
168 | 182 | return webhook_responser_handler("No logging here", "Success!") |
169 | 183 |
|
|
0 commit comments