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
« 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
5import json
6from guardian.shortcuts import get_objects_for_user
8from bs4 import BeautifulSoup
10from product.models import ProductRiskEntry, ProductRiskAnalysis
11from .models import ThreatModel
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
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
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")
32 context = {"tm_list": tms}
33 return render(request, "threat_model_index.html", context)
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))
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] = []
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>"
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
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)
90 context = {"tm": tm, "svg_content": svg_content, "risk_entries": json.dumps(json_entries), "names": names}
91 return render(request, "threat_model.html", context)