Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add metrics page: "Product Tag Count" (fixes #9151) #9152

Merged
merged 5 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/content/en/usage/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,9 @@ Product Type Counts

![Product Type Counts](../../images/met_2.png)

Product Tag Counts
: Same as above, but for a group of products sharing a tag.

Simple Metrics
: Provides tabular data for all Product Types. The data displayed in
this view is the total number of S0, S1, S2, S3, S4, Opened This
Expand Down
20 changes: 18 additions & 2 deletions dojo/forms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os

Check warning on line 1 in dojo/forms.py

View check run for this annotation

DryRunSecurity / Configured Sensitive Files Check

Sensitive File Edit

This file edit was detected to be sensitive according to the DryRun Security Configuration for this repository. Any edit to this file by an Author not in the allowedAuthors list will be considered sensitive.

Check warning on line 1 in dojo/forms.py

View check run for this annotation

DryRunSecurity / Configured Sensitive Files Check

Sensitive File Edit

This file edit was detected to be sensitive according to the DryRun Security Configuration for this repository. Any edit to this file by an Author not in the allowedAuthors list will be considered sensitive.
import re
from datetime import datetime, date
import pickle
Expand Down Expand Up @@ -2111,21 +2111,37 @@
return [(now.year, now.year), (now.year - 1, now.year - 1), (now.year - 2, now.year - 2)]


class ProductTypeCountsForm(forms.Form):
class ProductCountsFormBase(forms.Form):
month = forms.ChoiceField(choices=list(MONTHS.items()), required=True, error_messages={
'required': '*'})
year = forms.ChoiceField(choices=get_years, required=True, error_messages={
'required': '*'})


class ProductTypeCountsForm(ProductCountsFormBase):
product_type = forms.ModelChoiceField(required=True,
queryset=Product_Type.objects.none(),
error_messages={
'required': '*'})

def __init__(self, *args, **kwargs):
super(ProductTypeCountsForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.fields['product_type'].queryset = get_authorized_product_types(Permissions.Product_Type_View)


class ProductTagCountsForm(ProductCountsFormBase):
product_tag = forms.ModelChoiceField(required=True,
queryset=Product.tags.tag_model.objects.none().order_by('name'),
error_messages={
'required': '*'})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
prods = get_authorized_products(Permissions.Product_View)
tags_available_to_user = Product.tags.tag_model.objects.filter(product__in=prods)
self.fields['product_tag'].queryset = tags_available_to_user


class APIKeyForm(forms.ModelForm):
id = forms.IntegerField(required=True,
widget=forms.widgets.HiddenInput())
Expand Down
4 changes: 4 additions & 0 deletions dojo/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -2692,6 +2692,10 @@ msgstr ""
msgid "Product Type Counts"
msgstr ""

#: dojo/templates/base.html
msgid "Product Tag Counts"
msgstr ""

#: dojo/templates/base.html
msgid "Users"
msgstr ""
Expand Down
2 changes: 2 additions & 0 deletions dojo/metrics/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.urls import re_path

Check warning on line 1 in dojo/metrics/urls.py

View check run for this annotation

DryRunSecurity / Configured Sensitive Files Check

Sensitive File Edit

This file edit was detected to be sensitive according to the DryRun Security Configuration for this repository. Any edit to this file by an Author not in the allowedAuthors list will be considered sensitive.

from dojo.metrics import views

Expand All @@ -18,6 +18,8 @@
views.metrics, name='product_type_metrics'),
re_path(r'^metrics/product/type/counts$',
views.product_type_counts, name='product_type_counts'),
re_path(r'^metrics/product/tag/counts$',
views.product_tag_counts, name='product_tag_counts'),
re_path(r'^metrics/engineer$', views.engineer_metrics,
name='engineer_metrics'),
re_path(r'^metrics/engineer/(?P<eid>\d+)$', views.view_engineer,
Expand Down
164 changes: 161 additions & 3 deletions dojo/metrics/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# # metrics

Check warning on line 1 in dojo/metrics/views.py

View check run for this annotation

DryRunSecurity / Configured Sensitive Files Check

Sensitive File Edit

This file edit was detected to be sensitive according to the DryRun Security Configuration for this repository. Any edit to this file by an Author not in the allowedAuthors list will be considered sensitive.

Check warning on line 1 in dojo/metrics/views.py

View check run for this annotation

DryRunSecurity / Configured Sensitive Files Check

Sensitive File Edit

This file edit was detected to be sensitive according to the DryRun Security Configuration for this repository. Any edit to this file by an Author not in the allowedAuthors list will be considered sensitive.
import collections
import logging
import operator
Expand All @@ -21,7 +21,7 @@
from django.utils import timezone

from dojo.filters import MetricsFindingFilter, UserFilter, MetricsEndpointFilter
from dojo.forms import SimpleMetricsForm, ProductTypeCountsForm
from dojo.forms import SimpleMetricsForm, ProductTypeCountsForm, ProductTagCountsForm
from dojo.models import Product_Type, Finding, Product, Engagement, Test, \
Risk_Acceptance, Dojo_User, Endpoint_Status
from dojo.utils import get_page_items, add_breadcrumb, findings_this_period, opened_in_period, count_findings, \
Expand Down Expand Up @@ -586,13 +586,13 @@
end_date.month, end_date.day,
tzinfo=timezone.get_current_timezone())

oip = opened_in_period(start_date, end_date, pt)
oip = opened_in_period(start_date, end_date, test__engagement__product__prod_type=pt)

# trending data - 12 months
for x in range(12, 0, -1):
opened_in_period_list.append(
opened_in_period(start_date + relativedelta(months=-x), end_of_month + relativedelta(months=-x),
pt))
test__engagement__product__prod_type=pt))

opened_in_period_list.append(oip)

Expand Down Expand Up @@ -697,6 +697,164 @@
)


def product_tag_counts(request):
form = ProductTagCountsForm()
opened_in_period_list = []
oip = None
cip = None
aip = None
all_current_in_pt = None
top_ten = None
pt = None
today = timezone.now()
first_of_month = today.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
mid_month = first_of_month.replace(day=15, hour=23, minute=59, second=59, microsecond=999999)
end_of_month = mid_month.replace(day=monthrange(today.year, today.month)[1], hour=23, minute=59, second=59,
microsecond=999999)
start_date = first_of_month
end_date = end_of_month

if request.method == 'GET' and 'month' in request.GET and 'year' in request.GET and 'product_tag' in request.GET:
form = ProductTagCountsForm(request.GET)
if form.is_valid():
prods = get_authorized_products(Permissions.Product_View)

pt = form.cleaned_data['product_tag']
month = int(form.cleaned_data['month'])
year = int(form.cleaned_data['year'])
first_of_month = first_of_month.replace(month=month, year=year)

month_requested = datetime(year, month, 1)

end_of_month = month_requested.replace(day=monthrange(month_requested.year, month_requested.month)[1],
hour=23, minute=59, second=59, microsecond=999999)
start_date = first_of_month
start_date = datetime(start_date.year,
start_date.month, start_date.day,
tzinfo=timezone.get_current_timezone())
end_date = end_of_month
end_date = datetime(end_date.year,
end_date.month, end_date.day,
tzinfo=timezone.get_current_timezone())

oip = opened_in_period(start_date, end_date,
test__engagement__product__tags__name=pt,
test__engagement__product__in=prods)

# trending data - 12 months
for x in range(12, 0, -1):
opened_in_period_list.append(
opened_in_period(start_date + relativedelta(months=-x), end_of_month + relativedelta(months=-x),
test__engagement__product__tags__name=pt, test__engagement__product__in=prods))

opened_in_period_list.append(oip)

closed_in_period = Finding.objects.filter(mitigated__date__range=[start_date, end_date],
test__engagement__product__tags__name=pt,
test__engagement__product__in=prods,
severity__in=('Critical', 'High', 'Medium', 'Low')).values(
'numerical_severity').annotate(Count('numerical_severity')).order_by('numerical_severity')

total_closed_in_period = Finding.objects.filter(mitigated__date__range=[start_date, end_date],
test__engagement__product__tags__name=pt,
test__engagement__product__in=prods,
severity__in=(
'Critical', 'High', 'Medium', 'Low')).aggregate(
total=Sum(
Case(When(severity__in=('Critical', 'High', 'Medium', 'Low'),
then=Value(1)),
output_field=IntegerField())))['total']

overall_in_pt = Finding.objects.filter(date__lt=end_date,
verified=True,
false_p=False,
duplicate=False,
out_of_scope=False,
mitigated__isnull=True,
test__engagement__product__tags__name=pt,
test__engagement__product__in=prods,
severity__in=('Critical', 'High', 'Medium', 'Low')).values(
'numerical_severity').annotate(Count('numerical_severity')).order_by('numerical_severity')

total_overall_in_pt = Finding.objects.filter(date__lte=end_date,
verified=True,
false_p=False,
duplicate=False,
out_of_scope=False,
mitigated__isnull=True,
test__engagement__product__tags__name=pt,
test__engagement__product__in=prods,
severity__in=('Critical', 'High', 'Medium', 'Low')).aggregate(
total=Sum(
Case(When(severity__in=('Critical', 'High', 'Medium', 'Low'),
then=Value(1)),
output_field=IntegerField())))['total']

all_current_in_pt = Finding.objects.filter(date__lte=end_date,
verified=True,
false_p=False,
duplicate=False,
out_of_scope=False,
mitigated__isnull=True,
test__engagement__product__tags__name=pt,
test__engagement__product__in=prods,
severity__in=(
'Critical', 'High', 'Medium', 'Low')).prefetch_related(
'test__engagement__product',
'test__engagement__product__prod_type',
'test__engagement__risk_acceptance',
'reporter').order_by(
'numerical_severity')

top_ten = Product.objects.filter(engagement__test__finding__date__lte=end_date,
engagement__test__finding__verified=True,
engagement__test__finding__false_p=False,
engagement__test__finding__duplicate=False,
engagement__test__finding__out_of_scope=False,
engagement__test__finding__mitigated__isnull=True,
engagement__test__finding__severity__in=(
'Critical', 'High', 'Medium', 'Low'),
tags__name=pt, engagement__product__in=prods)
top_ten = severity_count(top_ten, 'annotate', 'engagement__test__finding__severity').order_by('-critical', '-high', '-medium', '-low')[:10]

cip = {'S0': 0,
'S1': 0,
'S2': 0,
'S3': 0,
'Total': total_closed_in_period}

aip = {'S0': 0,
'S1': 0,
'S2': 0,
'S3': 0,
'Total': total_overall_in_pt}

for o in closed_in_period:
cip[o['numerical_severity']] = o['numerical_severity__count']

for o in overall_in_pt:
aip[o['numerical_severity']] = o['numerical_severity__count']
else:
messages.add_message(request, messages.ERROR, _("Please choose month and year and the Product Tag."),
extra_tags='alert-danger')

add_breadcrumb(title=_("Bi-Weekly Metrics"), top_level=True, request=request)

return render(request,
'dojo/pt_counts.html',
{'form': form,
'start_date': start_date,
'end_date': end_date,
'opened_in_period': oip,
'trending_opened': opened_in_period_list,
'closed_in_period': cip,
'overall_in_pt': aip,
'all_current_in_pt': all_current_in_pt,
'top_ten': top_ten,
'pt': pt}
)


def engineer_metrics(request):
# only superusers can select other users to view
if request.user.is_superuser:
Expand Down
5 changes: 5 additions & 0 deletions dojo/templates/base.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% load navigation_tags %}

Check warning on line 1 in dojo/templates/base.html

View check run for this annotation

DryRunSecurity / Configured Sensitive Files Check

Sensitive File Edit

This file edit was detected to be sensitive according to the DryRun Security Configuration for this repository. Any edit to this file by an Author not in the allowedAuthors list will be considered sensitive.
{% load display_tags %}
{% load authorization_tags %}
{% load i18n %}
Expand Down Expand Up @@ -413,6 +413,11 @@
{% trans "Product Type Counts" %}
</a>
</li>
<li>
<a href="{% url 'product_tag_counts' %}">
{% trans "Product Tag Counts" %}
</a>
</li>
<li>
<a href="{% url 'simple_metrics' %}">
{% trans "Simple Metrics" %}
Expand Down
10 changes: 7 additions & 3 deletions dojo/templates/dojo/pt_counts.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% extends "base.html" %}

Check warning on line 1 in dojo/templates/dojo/pt_counts.html

View check run for this annotation

DryRunSecurity / Configured Sensitive Files Check

Sensitive File Edit

This file edit was detected to be sensitive according to the DryRun Security Configuration for this repository. Any edit to this file by an Author not in the allowedAuthors list will be considered sensitive.
{% load i18n %}
{% load display_tags %}
{% block add_styles %}
Expand All @@ -12,16 +12,20 @@
{% block content %}
{{ block.super }}

<form class="biweekly-metrics" action="{% url 'product_type_counts' %}" method="get">
<form class="biweekly-metrics" method="get">
{{ form.as_p }}
<input class="btn btn-sm btn-primary" type="submit" value="{% trans "Generate Metrics For Selected Period" %}"/>
</form>
<br/>
{% if pt %}
<h2>{% blocktrans with start_date=start_date.date end_date=end_date.date%}Finding Information For Period of {{ start_date }} - {{ end_date }}
{% endblocktrans %}</h2>
<h3 class="inline-block">{{ pt.name }}</h3> [
<a href="{% url 'product_type_metrics' pt.id %}" class="inline-block">{% trans "View Details" %}</a>]
<h3 class="inline-block">{{ pt.name }}</h3>
{% if pt|class_name == "Product_Type" %}
[<a href="{% url 'product_type_metrics' pt.id %}" class="inline-block">{% trans "View Details" %}</a>]
{% elif pt|class_name == "Tagulous_Product_tags" %}
[<a href="{% url 'product' %}?tags={{ pt }}" class="inline-block">{% trans "View Details" %}</a>]
{% endif %}
<div class="panel panel-default table-responsive">
<div class="panel-heading">
<h4>{% trans "Total Security Bug Count In Period" %}</h4>
Expand Down
10 changes: 5 additions & 5 deletions dojo/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from dojo.authorization.roles_permissions import Permissions

Check warning on line 1 in dojo/utils.py

View check run for this annotation

DryRunSecurity / Configured Sensitive Files Check

Sensitive File Edit

This file edit was detected to be sensitive according to the DryRun Security Configuration for this repository. Any edit to this file by an Author not in the allowedAuthors list will be considered sensitive.
from dojo.finding.queries import get_authorized_findings
import re
import binascii
Expand Down Expand Up @@ -1082,7 +1082,7 @@
}


def opened_in_period(start_date, end_date, pt):
def opened_in_period(start_date, end_date, **kwargs):
start_date = datetime(
start_date.year,
start_date.month,
Expand All @@ -1095,7 +1095,7 @@
tzinfo=timezone.get_current_timezone())
opened_in_period = Finding.objects.filter(
date__range=[start_date, end_date],
test__engagement__product__prod_type=pt,
**kwargs,
verified=True,
false_p=False,
duplicate=False,
Expand All @@ -1107,7 +1107,7 @@
Count('numerical_severity')).order_by('numerical_severity')
total_opened_in_period = Finding.objects.filter(
date__range=[start_date, end_date],
test__engagement__product__prod_type=pt,
**kwargs,
verified=True,
false_p=False,
duplicate=False,
Expand Down Expand Up @@ -1139,7 +1139,7 @@
'closed':
Finding.objects.filter(
mitigated__date__range=[start_date, end_date],
test__engagement__product__prod_type=pt,
**kwargs,
severity__in=('Critical', 'High', 'Medium', 'Low')).aggregate(
total=Sum(
Case(
Expand All @@ -1155,7 +1155,7 @@
duplicate=False,
out_of_scope=False,
mitigated__isnull=True,
test__engagement__product__prod_type=pt,
**kwargs,
severity__in=('Critical', 'High', 'Medium', 'Low')).count()
}

Expand Down
Loading