Skip to content

Commit 7820eb9

Browse files
authored
static group view
1 parent 7d6f14a commit 7820eb9

File tree

6 files changed

+441
-2
lines changed

6 files changed

+441
-2
lines changed

dojo/filters.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,41 @@ def set_related_object_fields(self, *args: list, **kwargs: dict):
19991999
self.form.fields["reviewers"].queryset = self.form.fields["reporter"].queryset
20002000

20012001

2002+
class FindingGroupsFilter(FilterSet):
2003+
name = CharFilter(method="filter_name", label="Name")
2004+
severity = ChoiceFilter(
2005+
choices=[
2006+
("Low", "Low"),
2007+
("Medium", "Medium"),
2008+
("High", "High"),
2009+
("Critical", "Critical"),
2010+
],
2011+
method="filter_min_severity",
2012+
label="Min Severity",
2013+
)
2014+
engagement = ModelMultipleChoiceFilter(queryset=Engagement.objects.none(), label="Engagement")
2015+
product = ModelMultipleChoiceFilter(queryset=Product.objects.none(), label="Product")
2016+
2017+
class Meta:
2018+
model = Finding
2019+
fields = ["name", "severity", "engagement", "product"]
2020+
2021+
def __init__(self, *args, **kwargs):
2022+
self.user = kwargs.pop("user", None)
2023+
self.pid = kwargs.pop("pid", None)
2024+
super().__init__(*args, **kwargs)
2025+
self.set_related_object_fields()
2026+
2027+
def set_related_object_fields(self):
2028+
if self.pid is not None:
2029+
self.form.fields["engagement"].queryset = Engagement.objects.filter(product_id=self.pid)
2030+
if "product" in self.form.fields:
2031+
del self.form.fields["product"]
2032+
else:
2033+
self.form.fields["product"].queryset = get_authorized_products(Permissions.Product_View)
2034+
self.form.fields["engagement"].queryset = get_authorized_engagements(Permissions.Engagement_View)
2035+
2036+
20022037
class AcceptedFindingFilter(FindingFilter):
20032038
risk_acceptance__created__date = DateRangeFilter(label="Acceptance Date")
20042039
risk_acceptance__owner = ModelMultipleChoiceFilter(

dojo/finding_group/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,9 @@
88
re_path(r"^finding_group/(?P<fgid>\d+)/delete$", views.delete_finding_group, name="delete_finding_group"),
99
re_path(r"^finding_group/(?P<fgid>\d+)/jira/push$", views.push_to_jira, name="finding_group_push_to_jira"),
1010
re_path(r"^finding_group/(?P<fgid>\d+)/jira/unlink$", views.unlink_jira, name="finding_group_unlink_jira"),
11+
12+
# finding group list views
13+
re_path(r"^finding_group/all$", views.ListFindingGroups.as_view(), name="all_finding_groups"),
14+
re_path(r"^finding_group/open$", views.ListOpenFindingGroups.as_view(), name="open_finding_groups"),
15+
re_path(r"^finding_group/closed$", views.ListClosedFindingGroups.as_view(), name="closed_finding_groups"),
1116
]

dojo/finding_group/views.py

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,28 @@
22

33
from django.contrib import messages
44
from django.contrib.admin.utils import NestedObjects
5+
from django.core.paginator import Page, Paginator
6+
from django.db.models import Count, Min, Q, QuerySet, Subquery
57
from django.db.utils import DEFAULT_DB_ALIAS
8+
from django.http import HttpRequest
69
from django.http.response import HttpResponse, HttpResponseRedirect, JsonResponse
710
from django.shortcuts import get_object_or_404, render
811
from django.urls.base import reverse
12+
from django.views import View
913
from django.views.decorators.http import require_POST
1014

1115
import dojo.jira_link.helper as jira_helper
1216
from dojo.authorization.authorization import user_has_permission_or_403
1317
from dojo.authorization.authorization_decorators import user_is_authorized
1418
from dojo.authorization.roles_permissions import Permissions
15-
from dojo.filters import FindingFilter, FindingFilterWithoutObjectLookups
19+
from dojo.filters import (
20+
FindingFilter,
21+
FindingFilterWithoutObjectLookups,
22+
FindingGroupsFilter,
23+
)
1624
from dojo.finding.views import prefetch_for_findings
1725
from dojo.forms import DeleteFindingGroupForm, EditFindingGroupForm, FindingBulkUpdateForm
18-
from dojo.models import Engagement, Finding, Finding_Group, GITHUB_PKey, Product
26+
from dojo.models import Dojo_Group, Engagement, Finding, Finding_Group, GITHUB_PKey, Global_Role, Product
1927
from dojo.utils import Product_Tab, add_breadcrumb, get_page_items, get_setting, get_system_setting, get_words_for_field
2028

2129
logger = logging.getLogger(__name__)
@@ -204,3 +212,118 @@ def push_to_jira(request, fgid):
204212
"Error pushing to JIRA",
205213
extra_tags="alert-danger")
206214
return HttpResponse(status=500)
215+
216+
217+
class ListFindingGroups(View):
218+
filter_name: str = "All"
219+
220+
SEVERITY_ORDER = {
221+
"Critical": 4,
222+
"High": 3,
223+
"Medium": 2,
224+
"Low": 1,
225+
"Info": 0,
226+
}
227+
228+
def get_template(self) -> str:
229+
return "dojo/finding_groups_list.html"
230+
231+
def order_field(self, request: HttpRequest, group_findings_queryset: QuerySet[Finding_Group]) -> QuerySet[Finding_Group]:
232+
order_field_param: str | None = request.GET.get("o")
233+
if order_field_param:
234+
reverse_order = order_field_param.startswith("-")
235+
order_field_param = order_field_param[1:] if reverse_order else order_field_param
236+
if order_field_param in {"name", "creator", "findings_count", "sla_deadline"}:
237+
prefix = "-" if reverse_order else ""
238+
group_findings_queryset = group_findings_queryset.order_by(f"{prefix}{order_field_param}")
239+
return group_findings_queryset
240+
241+
def filters(self, request: HttpRequest) -> tuple[str, str | None, list[str], list[str]]:
242+
name_filter: str = request.GET.get("name", "").lower()
243+
min_severity_filter: str | None = request.GET.get("severity")
244+
engagement_filter: list[str] = request.GET.getlist("engagement")
245+
product_filter: list[str] = request.GET.getlist("product")
246+
return name_filter, min_severity_filter, engagement_filter, product_filter
247+
248+
def filter_check(self, request: HttpRequest) -> Q:
249+
name_filter, min_severity_filter, engagement_filter, product_filter = self.filters(request)
250+
q_objects = Q()
251+
if name_filter:
252+
q_objects &= Q(name__icontains=name_filter)
253+
if product_filter:
254+
q_objects &= Q(findings__test__engagement__product__id__in=product_filter)
255+
if engagement_filter:
256+
q_objects &= Q(findings__test__engagement__id__in=engagement_filter)
257+
if min_severity_filter:
258+
min_severity_order_value = self.SEVERITY_ORDER.get(min_severity_filter, -1)
259+
valid_severities_for_filter = [
260+
sev for sev, order in self.SEVERITY_ORDER.items() if order >= min_severity_order_value
261+
]
262+
q_objects &= Q(findings__severity__in=valid_severities_for_filter)
263+
return q_objects
264+
265+
def get_findings(self, products: QuerySet[Product] | None) -> tuple[QuerySet[Finding], QuerySet[Finding]]:
266+
filters: dict = {}
267+
if products:
268+
filters["test__engagement__product__in"] = products
269+
user_findings_qs = Finding.objects.filter(**filters)
270+
return user_findings_qs, user_findings_qs.filter(active=True)
271+
272+
def get_finding_groups(self, request: HttpRequest, products: QuerySet[Product] | None = None) -> QuerySet[Finding_Group]:
273+
finding_groups_queryset = Finding_Group.objects.all()
274+
if products is not None:
275+
user_findings, _ = self.get_findings(products)
276+
finding_groups_queryset = finding_groups_queryset.filter(findings__id__in=Subquery(user_findings.values("id"))).distinct()
277+
request_filters_q = self.filter_check(request)
278+
finding_groups_queryset = finding_groups_queryset.filter(request_filters_q).distinct()
279+
finding_groups_queryset = finding_groups_queryset.annotate(
280+
findings_count=Count("findings", distinct=True),
281+
sla_deadline=Min("findings__sla_expiration_date"),
282+
)
283+
return self.order_field(request, finding_groups_queryset)
284+
285+
def paginate_queryset(self, queryset: QuerySet[Finding_Group], request: HttpRequest) -> Page:
286+
page_size = int(request.GET.get("page_size", 25))
287+
paginator = Paginator(queryset, page_size)
288+
page_number = request.GET.get("page")
289+
return paginator.get_page(page_number)
290+
291+
def get(self, request: HttpRequest) -> HttpResponse:
292+
global_role = Global_Role.objects.filter(user=request.user).first()
293+
user_groups = Dojo_Group.objects.filter(users=request.user)
294+
products = Product.objects.filter(Q(members=request.user) | Q(authorization_groups__in=user_groups)).distinct()
295+
if request.user.is_superuser or (global_role and global_role.role):
296+
finding_groups = self.get_finding_groups(request)
297+
elif products.exists():
298+
finding_groups = self.get_finding_groups(request, products)
299+
else:
300+
finding_groups = Finding_Group.objects.none()
301+
302+
paginated_finding_groups = self.paginate_queryset(finding_groups, request)
303+
304+
context = {
305+
"filter_name": self.filter_name,
306+
"filtered": FindingGroupsFilter(request.GET),
307+
"finding_groups": paginated_finding_groups,
308+
}
309+
310+
add_breadcrumb(title="Finding Group", top_level=not request.GET, request=request)
311+
return render(request, self.get_template(), context)
312+
313+
314+
class ListOpenFindingGroups(ListFindingGroups):
315+
filter_name: str = "Open"
316+
317+
def get_finding_groups(self, request: HttpRequest, products: QuerySet[Product] | None = None) -> QuerySet[Finding_Group]:
318+
finding_groups_queryset = super().get_finding_groups(request, products)
319+
_, active_findings = self.get_findings(products)
320+
return finding_groups_queryset.filter(findings__id__in=Subquery(active_findings.values("id"))).distinct()
321+
322+
323+
class ListClosedFindingGroups(ListFindingGroups):
324+
filter_name: str = "Closed"
325+
326+
def get_finding_groups(self, request: HttpRequest, products: QuerySet[Product] | None = None) -> QuerySet[Finding_Group]:
327+
finding_groups_queryset = super().get_finding_groups(request, products)
328+
_, active_findings = self.get_findings(products)
329+
return finding_groups_queryset.exclude(findings__id__in=Subquery(active_findings.values("id"))).distinct()

dojo/templates/base.html

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,31 @@
338338
</ul>
339339
<!-- /.nav-second-level -->
340340
</li>
341+
<li>
342+
<a href="{% url 'all_finding_groups' %}" aria-expanded="false" aria-label="Problems">
343+
<i class="fa-solid fa-triangle-exclamation fa-fw"></i>
344+
<span>{% trans "Dashboard" %}</span>
345+
<span class="glyphicon arrow"></span>
346+
</a>
347+
<ul class="nav nav-second-level">
348+
<li>
349+
<a href="{% url 'open_finding_groups' %}">
350+
{% trans "Open Findings Groups" %}
351+
</a>
352+
</li>
353+
<li>
354+
<a href="{% url 'all_finding_groups' %}">
355+
{% trans "All Findings Groups" %}
356+
</a>
357+
</li>
358+
<li>
359+
<a href="{% url 'closed_finding_groups' %}">
360+
{% trans "Closed Findings Groups" %}
361+
</a>
362+
</li>
363+
</ul>
364+
<!-- /.nav-second-level -->
365+
</li>
341366
<li>
342367
<a href="{% url 'components' %}" id="product_component_view" aria-expanded="false" aria-label="Components">
343368
<i class="fa-solid fa-table-cells-large fa-fw"></i>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{% extends "base.html" %}
2+
{% load navigation_tags %}
3+
{% load display_tags %}
4+
{% load static %}
5+
{% block content %}
6+
{% comment %} All/Open/Closed Finding Groups {% endcomment %}
7+
{% include "dojo/finding_groups_list_snippet.html" %}
8+
{% endblock %}

0 commit comments

Comments
 (0)