Coverage for product_risk_suite / threat_model / views.py: 23%

69 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-13 23:42 +0000

1from django.shortcuts import get_object_or_404, render 

2from django.views.decorators.csrf import csrf_protect 

3from django.contrib.auth.decorators import login_required 

4 

5import json 

6from guardian.shortcuts import get_objects_for_user 

7 

8from bs4 import BeautifulSoup 

9 

10from product.models import ProductRiskEntry, ProductRiskAnalysis 

11from .models import ThreatModel 

12 

13def __remove_svg_attribute(svg, tag, attr): 

14 for element in svg.findAll(tag): 

15 if attr in element.attrs: 

16 del element.attrs[attr] 

17 return svg 

18 

19def __remove_dark_mode_from_style(svg): 

20 for element in svg.findAll('svg'): 

21 if 'style' in element.attrs: 

22 s = element.attrs['style'] 

23 element.attrs['style'] = s.replace('color-scheme: light dark;', '') 

24 return svg 

25 

26@csrf_protect 

27@login_required 

28def threat_model_index(request): 

29 products = get_objects_for_user(request.user, 'product.view_product') 

30 tms = ThreatModel.objects.filter(product__in=products).order_by("product").order_by("title") 

31 

32 context = {"tm_list": tms} 

33 return render(request, "threat_model_index.html", context) 

34 

35@csrf_protect 

36@login_required 

37def threat_model(request, threat_model_slug): 

38 products = get_objects_for_user(request.user, 'product.view_product') 

39 tm = get_object_or_404(ThreatModel.objects.filter(product__in=products), slug=threat_model_slug) 

40 svg_content = None 

41 with tm.svg.open("r") as f: 

42 soup = BeautifulSoup(f, 'xml') 

43 soup = __remove_svg_attribute(soup, 'svg', 'content') 

44 svg_content = str(__remove_dark_mode_from_style(soup)) 

45 

46 svg_ids = tm.connection_names.all() 

47 risk_entries = ProductRiskEntry.objects.filter(svg_id__in=svg_ids).distinct().order_by("risk__custom_id") 

48 json_entries = {} 

49 names = {} 

50 for entry in risk_entries: 

51 for s in entry.svg_id.all(): 

52 if not s.name in json_entries: 

53 json_entries[s.name] = [] 

54 

55 stride_ul = "<ul>" 

56 for stride in entry.risk.list_stride: 

57 stride_ul = f"{stride_ul}<li>{stride}</li>" 

58 stride_ul = f"{stride_ul}</ul>" 

59 

60 product_id = tm.product.slug 

61 # always link to the last analysis found 

62 analysis_id = None 

63 try: 

64 last_analysis = ProductRiskAnalysis.objects.filter(risk_entries=entry).latest('id') 

65 analysis_id = last_analysis.slug 

66 except (ProductRiskEntry.DoesNotExist, AttributeError): 

67 pass 

68 except (ProductRiskAnalysis.DoesNotExist, AttributeError): 

69 pass 

70 except (Product.DoesNotExist, AttributeError): 

71 pass 

72 

73 mit = entry.risk.suggested_mitigation_validation.suggested_mitigation if entry.risk.suggested_mitigation_validation else "" 

74 val = entry.risk.suggested_mitigation_validation.suggested_validation if entry.risk.suggested_mitigation_validation else "" 

75 analysis_link = entry.risk.custom_id 

76 if analysis_id: 

77 analysis_link = f"""<a target="_blank" rel="noopener noreferrer" href="/product/{product_id}/{analysis_id}/#{entry.risk.custom_id}">{entry.risk.custom_id}</a>""" 

78 tr = f"""<tr> 

79 <td>{analysis_link}</td> 

80 <td>{entry.risk.asset.name}</td> 

81 <td>{stride_ul}</td> 

82 <td>{entry.risk.title}</td> 

83 <td>{entry.risk.description}</td> 

84 <td>{mit}</td> 

85 <td>{val}</td> 

86 </tr>""" 

87 json_entries[s.name].append(tr) 

88 names[s.name] = str(s) 

89 

90 context = {"tm": tm, "svg_content": svg_content, "risk_entries": json.dumps(json_entries), "names": names} 

91 return render(request, "threat_model.html", context)