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

Filter EPSS-related values on Findings listing #9847

Merged
merged 3 commits into from
Apr 1, 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
106 changes: 72 additions & 34 deletions dojo/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from django.utils import timezone
from django_filters import FilterSet, CharFilter, OrderingFilter, \
ModelMultipleChoiceFilter, ModelChoiceFilter, MultipleChoiceFilter, \
BooleanFilter, NumberFilter, DateFilter
BooleanFilter, NumberFilter, DateFilter, RangeFilter
from django_filters import rest_framework as filters
from django_filters.filters import ChoiceFilter, _truncate
from django.db.models import JSONField
Expand Down Expand Up @@ -44,6 +44,7 @@
from dojo.user.queries import get_authorized_users
from django.forms import HiddenInput
from dojo.utils import is_finding_groups_enabled
import decimal

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -306,37 +307,39 @@ def get_finding_filterset_fields(metrics=False, similar=False):
])

fields.extend([
'date',
'cwe',
'severity',
'last_reviewed',
'last_status_update',
'mitigated',
'reporter',
'reviewers',
'test__engagement__product__prod_type',
'test__engagement__product',
'test__engagement',
'test',
'test__test_type',
'test__engagement__version',
'test__version',
'endpoints',
'status',
'active',
'verified',
'duplicate',
'is_mitigated',
'out_of_scope',
'false_p',
'has_component',
'has_notes',
'file_path',
'unique_id_from_tool',
'vuln_id_from_tool',
'service',
'epss_score',
'epss_percentile'
'date',
'cwe',
'severity',
'last_reviewed',
'last_status_update',
'mitigated',
'reporter',
'reviewers',
'test__engagement__product__prod_type',
'test__engagement__product',
'test__engagement',
'test',
'test__test_type',
'test__engagement__version',
'test__version',
'endpoints',
'status',
'active',
'verified',
'duplicate',
'is_mitigated',
'out_of_scope',
'false_p',
'has_component',
'has_notes',
'file_path',
'unique_id_from_tool',
'vuln_id_from_tool',
'service',
'epss_score',
'epss_score_range',
'epss_percentile',
'epss_percentile_range',
])

if similar:
Expand Down Expand Up @@ -1307,6 +1310,34 @@ class Meta:
'line', 'cve']


class PercentageFilter(NumberFilter):
def __init__(self, *args, **kwargs):
kwargs['method'] = self.filter_percentage
super().__init__(*args, **kwargs)

def filter_percentage(self, queryset, name, value):
value = value / decimal.Decimal('100.0')
# Provide some wiggle room for filtering since the UI rounds to two places (and because floats):
# a user may enter 0.15, but we'll return everything in [0.0015, 0.0016).
# To do this, add to our value 1^(whatever the exponent for our least significant digit place is), but ensure
# that the exponent is at MOST the ten thousandths place so we don't show a range of e.g. [0.2, 0.3).
exponent = min(value.normalize().as_tuple().exponent, -4)
max_val = value + decimal.Decimal(f"1E{exponent}")
lookup_kwargs = {
f"{name}__gte": value,
f"{name}__lt": max_val, }
return queryset.filter(**lookup_kwargs)


class PercentageRangeFilter(RangeFilter):
def filter(self, qs, value):
if value is not None:
start = value.start / decimal.Decimal('100.0') if value.start else None
stop = value.stop / decimal.Decimal('100.0') if value.stop else None
value = slice(start, stop)
return super().filter(qs, value)


class FindingFilter(FindingFilterWithTags):
# tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
title = CharFilter(lookup_expr='icontains')
Expand Down Expand Up @@ -1450,6 +1481,14 @@ class FindingFilter(FindingFilterWithTags):

has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags')

epss_score = PercentageFilter(field_name='epss_score', label='EPSS score')
epss_score_range = PercentageRangeFilter(field_name='epss_score', label='EPSS score range',
help_text='The range of EPSS score percentages to filter on; the left input is a lower bound, the right is an upper bound. Leaving one empty will skip that bound (e.g., leaving the lower bound input empty will filter only on the upper bound -- filtering on "less than or equal").')

epss_percentile = PercentageFilter(field_name='epss_percentile', label='EPSS percentile')
epss_percentile_range = PercentageRangeFilter(field_name='epss_percentile', label='EPSS percentile range',
help_text='The range of EPSS percentiles to filter on; the left input is a lower bound, the right is an upper bound. Leaving one empty will skip that bound (e.g., leaving the lower bound input empty will filter only on the upper bound -- filtering on "less than or equal").')

o = OrderingFilter(
# tuple-mapping retains order
fields=(
Expand Down Expand Up @@ -1488,8 +1527,7 @@ class Meta:
'numerical_severity', 'line', 'duplicate_finding',
'hash_code', 'reviewers', 'created', 'files',
'sla_start_date', 'sla_expiration_date', 'cvssv3',
'severity_justification', 'steps_to_reproduce',
'epss_score', 'epss_percentile']
'severity_justification', 'steps_to_reproduce',]

def __init__(self, *args, **kwargs):
self.user = None
Expand Down
3 changes: 2 additions & 1 deletion dojo/settings/settings.dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,8 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param
'drf_spectacular',
'drf_spectacular_sidecar', # required for Django collectstatic discovery
'tagulous',
'fontawesomefree'
'fontawesomefree',
'django_filters',
)

# ------------------------------------------------------------------------------
Expand Down
8 changes: 7 additions & 1 deletion dojo/templates/dojo/filter_snippet.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@
{% for field in form.visible_fields %}
<div class="filter-form-input">
{{ field.errors }}
<label for="{{ field.auto_id }}" style="display: block;">{{ field.label }}</label>
<label for="{{ field.auto_id }}" style="display: block;">
{{ field.label }}
{% if field.help_text %}
<i class="fa-solid fa-circle-question has-popover" data-trigger="hover" data-content="{{ field.help_text }}" data-placement="right" data-container="body">
</i>
{% endif %}
</label>
{% with placeholder="placeholder:"|add:field.label %}
{{ field|addcss:"class: form-control filter-form-control"|addcss:placeholder }}
{% endwith %}
Expand Down
Loading