1+ import logging
2+ import mimetypes
3+
4+ from django .conf import settings
5+ from django .contrib import messages
6+ from django .core .exceptions import PermissionDenied
7+ from django .db .models import Q
8+ from django .http import HttpResponseRedirect , StreamingHttpResponse
9+ from django .shortcuts import get_object_or_404 , render
10+ from django .urls import reverse
11+ from django .utils import timezone
12+
13+ import dojo .risk_acceptance .helper as ra_helper
14+ from dojo .authorization .authorization_decorators import user_is_authorized
15+ from dojo .authorization .roles_permissions import Permissions
16+ from dojo .finding .helper import NOT_ACCEPTED_FINDINGS_QUERY
17+ from dojo .forms import (
18+ AddFindingsRiskAcceptanceForm ,
19+ EditRiskAcceptanceForm ,
20+ NoteForm ,
21+ ReplaceRiskAcceptanceProofForm ,
22+ RiskAcceptanceForm ,
23+ )
24+ from dojo .models import Finding , Notes , Product , Risk_Acceptance
25+ from dojo .product .queries import get_authorized_products
26+ from dojo .risk_acceptance .helper import prefetch_for_expiration
27+ from dojo .utils import (
28+ FileIterWrapper ,
29+ Product_Tab ,
30+ get_page_items ,
31+ get_return_url ,
32+ get_system_setting ,
33+ redirect_to_return_url_or_else ,
34+ )
35+
36+ logger = logging .getLogger (__name__ )
37+
38+
39+ @user_is_authorized (Product , Permissions .Risk_Acceptance , "pid" )
40+ def add_risk_acceptance (request , pid , fid = None ):
41+ product = get_object_or_404 (Product , id = pid )
42+ finding = None
43+ if fid :
44+ finding = get_object_or_404 (Finding , id = fid )
45+
46+ if not product .enable_full_risk_acceptance :
47+ raise PermissionDenied
48+
49+ if request .method == "POST" :
50+ form = RiskAcceptanceForm (request .POST , request .FILES )
51+ if form .is_valid ():
52+ # first capture notes param as it cannot be saved directly as m2m
53+ notes = None
54+ if form .cleaned_data ["notes" ]:
55+ notes = Notes (
56+ entry = form .cleaned_data ["notes" ],
57+ author = request .user ,
58+ date = timezone .now ())
59+ notes .save ()
60+
61+ del form .cleaned_data ["notes" ]
62+
63+ try :
64+ # we sometimes see a weird exception here, but are unable to reproduce.
65+ # we add some logging in case it happens
66+ risk_acceptance = form .save (commit = False )
67+ risk_acceptance .product = product
68+ risk_acceptance .save ()
69+ except Exception :
70+ logger .debug (vars (request .POST ))
71+ logger .error (vars (form ))
72+ logger .exception ("Creation of Risk Acc. is not possible" )
73+ raise
74+
75+ # attach note to risk acceptance object now in database
76+ if notes :
77+ risk_acceptance .notes .add (notes )
78+
79+ findings = form .cleaned_data ["accepted_findings" ]
80+
81+ risk_acceptance = ra_helper .add_findings_to_risk_acceptance (request .user , risk_acceptance , findings )
82+
83+ messages .add_message (
84+ request ,
85+ messages .SUCCESS ,
86+ "Risk acceptance saved." ,
87+ extra_tags = "alert-success" )
88+
89+ return redirect_to_return_url_or_else (request , reverse ("view_product" , args = (pid , )))
90+ else :
91+ risk_acceptance_title_suggestion = f"Accept: { finding } " if finding else "Risk Acceptance"
92+ form = RiskAcceptanceForm (initial = {"owner" : request .user , "name" : risk_acceptance_title_suggestion })
93+
94+ finding_choices = Finding .objects .filter (duplicate = False , test__engagement__product = product ).filter (NOT_ACCEPTED_FINDINGS_QUERY ).order_by ("title" )
95+
96+ form .fields ["accepted_findings" ].queryset = finding_choices
97+ if fid :
98+ form .fields ["accepted_findings" ].initial = {fid }
99+ product_tab = Product_Tab (product , title = "Risk Acceptance" , tab = "risk_acceptance" )
100+
101+ return render (request , "dojo/add_risk_acceptance.html" , {
102+ "product" : product ,
103+ "product_tab" : product_tab ,
104+ "form" : form ,
105+ })
106+
107+
108+ @user_is_authorized (Risk_Acceptance , Permissions .Risk_Acceptance , "raid" )
109+ def view_risk_acceptance (request , raid ):
110+ return view_edit_risk_acceptance (request , raid = raid , edit_mode = False )
111+
112+
113+ @user_is_authorized (Risk_Acceptance , Permissions .Risk_Acceptance , "raid" )
114+ def edit_risk_acceptance (request , raid ):
115+ return view_edit_risk_acceptance (request , raid = raid , edit_mode = True )
116+
117+
118+ # will only be called by view_risk_acceptance and edit_risk_acceptance
119+ def view_edit_risk_acceptance (request , raid , * , edit_mode = False ):
120+ risk_acceptance = get_object_or_404 (Risk_Acceptance , pk = raid )
121+ product = risk_acceptance .product
122+
123+ if edit_mode and not product .enable_full_risk_acceptance :
124+ raise PermissionDenied
125+
126+ risk_acceptance_form = None
127+ errors = False
128+
129+ if request .method == "POST" :
130+ # deleting before instantiating the form otherwise django messes up and we end up with an empty path value
131+ if len (request .FILES ) > 0 :
132+ logger .debug ("new proof uploaded" )
133+ risk_acceptance .path .delete ()
134+
135+ if "decision" in request .POST :
136+ old_expiration_date = risk_acceptance .expiration_date
137+ risk_acceptance_form = EditRiskAcceptanceForm (request .POST , request .FILES , instance = risk_acceptance )
138+ errors = errors or not risk_acceptance_form .is_valid ()
139+ if not errors :
140+ logger .debug (f"path: { risk_acceptance_form .cleaned_data ['path' ]} " )
141+
142+ risk_acceptance_form .save ()
143+
144+ if risk_acceptance .expiration_date != old_expiration_date :
145+ # risk acceptance was changed, check if risk acceptance needs to be reinstated and findings made accepted again
146+ ra_helper .reinstate (risk_acceptance , old_expiration_date )
147+
148+ messages .add_message (
149+ request ,
150+ messages .SUCCESS ,
151+ "Risk Acceptance saved successfully." ,
152+ extra_tags = "alert-success" )
153+
154+ if "entry" in request .POST :
155+ note_form = NoteForm (request .POST )
156+ errors = errors or not note_form .is_valid ()
157+ if not errors :
158+ new_note = note_form .save (commit = False )
159+ new_note .author = request .user
160+ new_note .date = timezone .now ()
161+ new_note .save ()
162+ risk_acceptance .notes .add (new_note )
163+ messages .add_message (
164+ request ,
165+ messages .SUCCESS ,
166+ "Note added successfully." ,
167+ extra_tags = "alert-success" )
168+
169+ if "delete_note" in request .POST :
170+ note = get_object_or_404 (Notes , pk = request .POST ["delete_note_id" ])
171+ if note .author .username == request .user .username :
172+ risk_acceptance .notes .remove (note )
173+ note .delete ()
174+ messages .add_message (
175+ request ,
176+ messages .SUCCESS ,
177+ "Note deleted successfully." ,
178+ extra_tags = "alert-success" )
179+ else :
180+ messages .add_message (
181+ request ,
182+ messages .ERROR ,
183+ "Since you are not the note's author, it was not deleted." ,
184+ extra_tags = "alert-danger" )
185+
186+ if "remove_finding" in request .POST :
187+ finding = get_object_or_404 (
188+ Finding , pk = request .POST ["remove_finding_id" ])
189+
190+ ra_helper .remove_finding_from_risk_acceptance (request .user , risk_acceptance , finding )
191+
192+ messages .add_message (
193+ request ,
194+ messages .SUCCESS ,
195+ "Finding removed successfully from risk acceptance." ,
196+ extra_tags = "alert-success" )
197+
198+ if "replace_file" in request .POST :
199+ replace_form = ReplaceRiskAcceptanceProofForm (
200+ request .POST , request .FILES , instance = risk_acceptance )
201+
202+ errors = errors or not replace_form .is_valid ()
203+ if not errors :
204+ replace_form .save ()
205+
206+ messages .add_message (
207+ request ,
208+ messages .SUCCESS ,
209+ "New Proof uploaded successfully." ,
210+ extra_tags = "alert-success" )
211+ else :
212+ logger .error (replace_form .errors )
213+
214+ if "add_findings" in request .POST :
215+ add_findings_form = AddFindingsRiskAcceptanceForm (
216+ request .POST , request .FILES , instance = risk_acceptance )
217+ errors = errors or not add_findings_form .is_valid ()
218+ if not errors :
219+ findings = add_findings_form .cleaned_data ["accepted_findings" ]
220+
221+ ra_helper .add_findings_to_risk_acceptance (request .user , risk_acceptance , findings )
222+
223+ messages .add_message (
224+ request ,
225+ messages .SUCCESS ,
226+ f"Finding{ 's' if len (findings ) > 1 else '' } added successfully." ,
227+ extra_tags = "alert-success" )
228+ if not errors :
229+ logger .debug ("redirecting to return_url" )
230+ return redirect_to_return_url_or_else (request , reverse ("view_risk_acceptance" , args = (raid ,)))
231+ logger .error ("errors found" )
232+
233+ elif edit_mode :
234+ risk_acceptance_form = EditRiskAcceptanceForm (instance = risk_acceptance )
235+
236+ note_form = NoteForm ()
237+ replace_form = ReplaceRiskAcceptanceProofForm (instance = risk_acceptance )
238+ add_findings_form = AddFindingsRiskAcceptanceForm (instance = risk_acceptance )
239+
240+ accepted_findings = risk_acceptance .accepted_findings .order_by ("numerical_severity" )
241+ fpage = get_page_items (request , accepted_findings , 15 )
242+
243+ unaccepted_findings = Finding .objects .filter (test__engagement__product = product , risk_accepted = False ) \
244+ .exclude (id__in = accepted_findings ).order_by ("title" )
245+ add_fpage = get_page_items (request , unaccepted_findings , 25 , "apage" )
246+ # on this page we need to add unaccepted findings as possible findings to add as accepted
247+
248+ add_findings_form .fields [
249+ "accepted_findings" ].queryset = add_fpage .object_list
250+
251+ add_findings_form .fields ["accepted_findings" ].widget .request = request
252+ add_findings_form .fields ["accepted_findings" ].widget .findings = unaccepted_findings
253+ add_findings_form .fields ["accepted_findings" ].widget .page_number = add_fpage .number
254+
255+ product_tab = Product_Tab (product , title = "Risk Acceptance" , tab = "risk_acceptance" )
256+ return render (
257+ request , "dojo/view_risk_acceptance.html" , {
258+ "risk_acceptance" : risk_acceptance ,
259+ "product" : product ,
260+ "product_tab" : product_tab ,
261+ "accepted_findings" : fpage ,
262+ "notes" : risk_acceptance .notes .all (),
263+ "edit_mode" : edit_mode ,
264+ "risk_acceptance_form" : risk_acceptance_form ,
265+ "note_form" : note_form ,
266+ "replace_form" : replace_form ,
267+ "add_findings_form" : add_findings_form ,
268+ "request" : request ,
269+ "add_findings" : add_fpage ,
270+ "return_url" : get_return_url (request ),
271+ "enable_table_filtering" : get_system_setting ("enable_ui_table_based_searching" ),
272+ })
273+
274+
275+ @user_is_authorized (Risk_Acceptance , Permissions .Risk_Acceptance , "raid" )
276+ def expire_risk_acceptance (request , raid ):
277+ risk_acceptance = get_object_or_404 (prefetch_for_expiration (Risk_Acceptance .objects .all ()), pk = raid )
278+
279+ ra_helper .expire_now (risk_acceptance )
280+
281+ return redirect_to_return_url_or_else (request , reverse ("view_risk_acceptance" , args = (raid ,)))
282+
283+
284+ @user_is_authorized (Risk_Acceptance , Permissions .Risk_Acceptance , "raid" )
285+ def reinstate_risk_acceptance (request , raid ):
286+ risk_acceptance = get_object_or_404 (prefetch_for_expiration (Risk_Acceptance .objects .all ()), pk = raid )
287+ product = risk_acceptance .product
288+
289+ if not product .enable_full_risk_acceptance :
290+ raise PermissionDenied
291+
292+ ra_helper .reinstate (risk_acceptance , risk_acceptance .expiration_date )
293+
294+ return redirect_to_return_url_or_else (request , reverse ("view_risk_acceptance" , args = (raid ,)))
295+
296+
297+ @user_is_authorized (Risk_Acceptance , Permissions .Risk_Acceptance , "raid" )
298+ def delete_risk_acceptance (request , raid ):
299+ risk_acceptance = get_object_or_404 (Risk_Acceptance , pk = raid )
300+ product = risk_acceptance .product
301+
302+ ra_helper .delete (product , risk_acceptance )
303+
304+ messages .add_message (
305+ request ,
306+ messages .SUCCESS ,
307+ "Risk acceptance deleted successfully." ,
308+ extra_tags = "alert-success" )
309+ return HttpResponseRedirect (reverse ("view_product" , args = (product .id , )))
310+
311+
312+ @user_is_authorized (Risk_Acceptance , Permissions .Risk_Acceptance , "raid" )
313+ def download_risk_acceptance (request , raid ):
314+ mimetypes .init ()
315+ risk_acceptance = get_object_or_404 (Risk_Acceptance , pk = raid )
316+ response = StreamingHttpResponse (
317+ FileIterWrapper (
318+ open (settings .MEDIA_ROOT + "/" + risk_acceptance .path .name , mode = "rb" )))
319+ response ["Content-Disposition" ] = f'attachment; filename="{ risk_acceptance .filename ()} "'
320+ mimetype , _encoding = mimetypes .guess_type (risk_acceptance .path .name )
321+ response ["Content-Type" ] = mimetype
322+ return response
0 commit comments