diff --git a/components/package.json b/components/package.json
index 4ccd0709397..8b076e0bbbc 100644
--- a/components/package.json
+++ b/components/package.json
@@ -1,6 +1,6 @@
{
"name": "defectdojo",
- "version": "2.33.6",
+ "version": "2.33.7",
"license" : "BSD-3-Clause",
"private": true,
"dependencies": {
diff --git a/dojo/__init__.py b/dojo/__init__.py
index 3a41507beca..24987ce2acc 100644
--- a/dojo/__init__.py
+++ b/dojo/__init__.py
@@ -4,6 +4,6 @@
# Django starts so that shared_task will use this app.
from .celery import app as celery_app # noqa: F401
-__version__ = '2.33.6'
+__version__ = '2.33.7'
__url__ = 'https://github.com/DefectDojo/django-DefectDojo'
__docs__ = 'https://documentation.defectdojo.com'
diff --git a/dojo/components/views.py b/dojo/components/views.py
index 717f79d86a1..82898f6fa8e 100644
--- a/dojo/components/views.py
+++ b/dojo/components/views.py
@@ -1,8 +1,8 @@
from django.shortcuts import render
from django.db.models import Count, Q
from django.db.models.expressions import Value
-from dojo.utils import add_breadcrumb, get_page_items
-from dojo.filters import ComponentFilter
+from dojo.utils import add_breadcrumb, get_page_items, get_system_setting
+from dojo.filters import ComponentFilter, ComponentFilterWithoutObjectLookups
from dojo.components.sql_group_concat import Sql_GroupConcat
from django.db import connection
from django.contrib.postgres.aggregates import StringAgg
@@ -52,7 +52,9 @@ def components(request):
"-total"
) # Default sort by total descending
- comp_filter = ComponentFilter(request.GET, queryset=component_query)
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = ComponentFilterWithoutObjectLookups if filter_string_matching else ComponentFilter
+ comp_filter = filter_class(request.GET, queryset=component_query)
result = get_page_items(request, comp_filter.qs, 25)
# Filter out None values for auto-complete
diff --git a/dojo/db_migrations/0211_system_settings_enable_similar_findings.py b/dojo/db_migrations/0211_system_settings_enable_similar_findings.py
new file mode 100644
index 00000000000..014977be7d0
--- /dev/null
+++ b/dojo/db_migrations/0211_system_settings_enable_similar_findings.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.1.13 on 2024-04-26 21:52
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dojo', '0210_system_settings_filter_string_matching'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='system_settings',
+ name='enable_similar_findings',
+ field=models.BooleanField(default=True, help_text='Enable the query of similar findings on the view finding page. This feature can involve potentially large queries and negatively impact performance', verbose_name='Enable Similar Findings'),
+ ),
+ ]
diff --git a/dojo/endpoint/views.py b/dojo/endpoint/views.py
index d35a1390988..bd1a85534e4 100644
--- a/dojo/endpoint/views.py
+++ b/dojo/endpoint/views.py
@@ -12,12 +12,12 @@
from django.db.models import Q, QuerySet, Count
from dojo.endpoint.utils import clean_hosts_run, endpoint_meta_import
-from dojo.filters import EndpointFilter
+from dojo.filters import EndpointFilter, EndpointFilterWithoutObjectLookups
from dojo.forms import EditEndpointForm, \
DeleteEndpointForm, AddEndpointForm, DojoMetaDataForm, ImportEndpointMetaForm
from dojo.models import Product, Endpoint, Finding, DojoMeta, Endpoint_Status
from dojo.utils import get_page_items, add_breadcrumb, get_period_counts, Product_Tab, calculate_grade, redirect, \
- add_error_message_to_response, is_scan_file_too_large
+ add_error_message_to_response, is_scan_file_too_large, get_system_setting
from dojo.notifications.helper import create_notification
from dojo.authorization.authorization_decorators import user_is_authorized
from dojo.authorization.roles_permissions import Permissions
@@ -42,12 +42,13 @@ def process_endpoints_view(request, host_view=False, vulnerable=False):
endpoints = endpoints.prefetch_related('product', 'product__tags', 'tags').distinct()
endpoints = get_authorized_endpoints(Permissions.Endpoint_View, endpoints, request.user)
-
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter
if host_view:
- ids = get_endpoint_ids(EndpointFilter(request.GET, queryset=endpoints, user=request.user).qs)
- endpoints = EndpointFilter(request.GET, queryset=endpoints.filter(id__in=ids), user=request.user)
+ ids = get_endpoint_ids(filter_class(request.GET, queryset=endpoints, user=request.user).qs)
+ endpoints = filter_class(request.GET, queryset=endpoints.filter(id__in=ids), user=request.user)
else:
- endpoints = EndpointFilter(request.GET, queryset=endpoints, user=request.user)
+ endpoints = filter_class(request.GET, queryset=endpoints, user=request.user)
paged_endpoints = get_page_items(request, endpoints.qs, 25)
diff --git a/dojo/engagement/views.py b/dojo/engagement/views.py
index 230c18cc3a0..bdf4c2cb143 100644
--- a/dojo/engagement/views.py
+++ b/dojo/engagement/views.py
@@ -1,6 +1,7 @@
import logging
import csv
import re
+from typing import List
from django.views import View
from openpyxl import Workbook
from openpyxl.styles import Font
@@ -14,7 +15,7 @@
from django.core.exceptions import ValidationError, PermissionDenied
from django.urls import reverse, Resolver404
from django.db.models import Q, Count
-from django.http import HttpResponseRedirect, StreamingHttpResponse, HttpResponse, FileResponse, QueryDict
+from django.http import HttpResponseRedirect, StreamingHttpResponse, HttpResponse, FileResponse, QueryDict, HttpRequest
from django.shortcuts import render, get_object_or_404
from django.views.decorators.cache import cache_page
from django.utils import timezone
@@ -23,7 +24,14 @@
from django.db import DEFAULT_DB_ALIAS
from dojo.engagement.services import close_engagement, reopen_engagement
-from dojo.filters import EngagementFilter, EngagementDirectFilter, EngagementTestFilter
+from dojo.filters import (
+ EngagementFilter,
+ EngagementFilterWithoutObjectLookups,
+ EngagementDirectFilter,
+ EngagementDirectFilterWithoutObjectLookups,
+ EngagementTestFilter,
+ EngagementTestFilterWithoutObjectLookups
+)
from dojo.forms import CheckForm, \
UploadThreatForm, RiskAcceptanceForm, NoteForm, DoneForm, \
EngForm, TestForm, ReplaceRiskAcceptanceProofForm, AddFindingsRiskAcceptanceForm, DeleteEngagementForm, ImportScanForm, \
@@ -112,7 +120,9 @@ def get_filtered_engagements(request, view):
'product__jira_project_set__jira_instance'
)
- engagements = EngagementDirectFilter(request.GET, queryset=engagements)
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = EngagementDirectFilterWithoutObjectLookups if filter_string_matching else EngagementDirectFilter
+ engagements = filter_class(request.GET, queryset=engagements)
return engagements
@@ -181,8 +191,9 @@ def engagements_all(request):
'engagement_set__jira_project__jira_instance',
'jira_project_set__jira_instance'
)
-
- filtered = EngagementFilter(
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = EngagementFilterWithoutObjectLookups if filter_string_matching else EngagementFilter
+ filtered = filter_class(
request.GET,
queryset=filter_qs
)
@@ -384,11 +395,21 @@ def get_risks_accepted(self, eng):
risks_accepted = eng.risk_acceptance.all().select_related('owner').annotate(accepted_findings_count=Count('accepted_findings__id'))
return risks_accepted
+ def get_filtered_tests(
+ self,
+ request: HttpRequest,
+ queryset: List[Test],
+ engagement: Engagement,
+ ):
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = EngagementTestFilterWithoutObjectLookups if filter_string_matching else EngagementTestFilter
+ return filter_class(request.GET, queryset=queryset, engagement=engagement)
+
def get(self, request, eid, *args, **kwargs):
eng = get_object_or_404(Engagement, id=eid)
tests = eng.test_set.all().order_by('test_type__name', '-updated')
default_page_num = 10
- tests_filter = EngagementTestFilter(request.GET, queryset=tests, engagement=eng)
+ tests_filter = self.get_filtered_tests(request, tests, eng)
paged_tests = get_page_items(request, tests_filter.qs, default_page_num)
paged_tests.object_list = prefetch_for_view_tests(paged_tests.object_list)
prod = eng.product
@@ -458,7 +479,7 @@ def post(self, request, eid, *args, **kwargs):
default_page_num = 10
- tests_filter = EngagementTestFilter(request.GET, queryset=tests, engagement=eng)
+ tests_filter = self.get_filtered_tests(request, tests, eng)
paged_tests = get_page_items(request, tests_filter.qs, default_page_num)
# prefetch only after creating the filters to avoid https://code.djangoproject.com/ticket/23771 and https://code.djangoproject.com/ticket/25375
paged_tests.object_list = prefetch_for_view_tests(paged_tests.object_list)
diff --git a/dojo/filters.py b/dojo/filters.py
index d74ce33f257..8dc61379104 100644
--- a/dojo/filters.py
+++ b/dojo/filters.py
@@ -826,6 +826,29 @@ class ProductComponentFilter(DojoFilter):
)
+class ComponentFilterWithoutObjectLookups(ProductComponentFilter):
+ test__engagement__product__prod_type__name = CharFilter(
+ field_name="test__engagement__product__prod_type__name",
+ lookup_expr="iexact",
+ label="Product Type Name",
+ help_text="Search for Product Type names that are an exact match")
+ test__engagement__product__prod_type__name_contains = CharFilter(
+ field_name="test__engagement__product__prod_type__name",
+ lookup_expr="icontains",
+ label="Product Type Name Contains",
+ help_text="Search for Product Type names that contain a given pattern")
+ test__engagement__product__name = CharFilter(
+ field_name="test__engagement__product__name",
+ lookup_expr="iexact",
+ label="Product Name",
+ help_text="Search for Product names that are an exact match")
+ test__engagement__product__name_contains = CharFilter(
+ field_name="test__engagement__product__name",
+ lookup_expr="icontains",
+ label="Product Name Contains",
+ help_text="Search for Product names that contain a given pattern")
+
+
class ComponentFilter(ProductComponentFilter):
test__engagement__product__prod_type = ModelMultipleChoiceFilter(
queryset=Product_Type.objects.none(),
@@ -842,125 +865,140 @@ def __init__(self, *args, **kwargs):
'test__engagement__product'].queryset = get_authorized_products(Permissions.Product_View)
-class EngagementDirectFilter(DojoFilter):
- name = CharFilter(lookup_expr='icontains', label='Engagement name contains')
- lead = ModelChoiceFilter(queryset=Dojo_User.objects.none(), label="Lead")
- version = CharFilter(field_name='version', lookup_expr='icontains', label='Engagement version')
- test__version = CharFilter(field_name='test__version', lookup_expr='icontains', label='Test version')
-
- product__name = CharFilter(lookup_expr='icontains', label='Product name contains')
- product__prod_type = ModelMultipleChoiceFilter(
- queryset=Product_Type.objects.none(),
- label="Product Type")
+class EngagementDirectFilterHelper(FilterSet):
+ name = CharFilter(lookup_expr="icontains", label="Engagement name contains")
+ version = CharFilter(field_name="version", lookup_expr="icontains", label="Engagement version")
+ test__version = CharFilter(field_name="test__version", lookup_expr="icontains", label="Test version")
+ product__name = CharFilter(lookup_expr="icontains", label="Product name contains")
+ status = MultipleChoiceFilter(choices=ENGAGEMENT_STATUS_CHOICES, label="Status")
+ tag = CharFilter(field_name="tags__name", lookup_expr="icontains", label="Tag name contains")
+ not_tag = CharFilter(field_name="tags__name", lookup_expr="icontains", label="Not tag name contains", exclude=True)
+ has_tags = BooleanFilter(field_name="tags", lookup_expr="isnull", exclude=True, label="Has tags")
test__engagement__product__lifecycle = MultipleChoiceFilter(
- choices=Product.LIFECYCLE_CHOICES, label='Product lifecycle', null_label='Empty')
- status = MultipleChoiceFilter(choices=ENGAGEMENT_STATUS_CHOICES,
- label="Status")
-
- tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- queryset=Engagement.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
-
- not_tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- exclude=True,
- queryset=Engagement.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True)
-
- has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags')
-
+ choices=Product.LIFECYCLE_CHOICES,
+ label="Product lifecycle",
+ null_label="Empty")
o = OrderingFilter(
# tuple-mapping retains order
fields=(
- ('target_start', 'target_start'),
- ('name', 'name'),
- ('product__name', 'product__name'),
- ('product__prod_type__name', 'product__prod_type__name'),
- ('lead__first_name', 'lead__first_name'),
+ ("target_start", "target_start"),
+ ("name", "name"),
+ ("product__name", "product__name"),
+ ("product__prod_type__name", "product__prod_type__name"),
+ ("lead__first_name", "lead__first_name"),
),
field_labels={
- 'target_start': 'Start date',
- 'name': 'Engagement',
- 'product__name': 'Product Name',
- 'product__prod_type__name': 'Product Type',
- 'lead__first_name': 'Lead',
+ "target_start": "Start date",
+ "name": "Engagement",
+ "product__name": "Product Name",
+ "product__prod_type__name": "Product Type",
+ "lead__first_name": "Lead",
}
-
)
+
+class EngagementDirectFilter(EngagementDirectFilterHelper, DojoFilter):
+ lead = ModelChoiceFilter(queryset=Dojo_User.objects.none(), label="Lead")
+ product__prod_type = ModelMultipleChoiceFilter(
+ queryset=Product_Type.objects.none(),
+ label="Product Type")
+ tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ queryset=Engagement.tags.tag_model.objects.all().order_by("name"))
+ not_tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ exclude=True,
+ queryset=Engagement.tags.tag_model.objects.all().order_by("name"))
+
def __init__(self, *args, **kwargs):
super(EngagementDirectFilter, self).__init__(*args, **kwargs)
- self.form.fields['product__prod_type'].queryset = get_authorized_product_types(Permissions.Product_Type_View)
- self.form.fields['lead'].queryset = get_authorized_users(Permissions.Product_Type_View) \
+ self.form.fields["product__prod_type"].queryset = get_authorized_product_types(Permissions.Product_Type_View)
+ self.form.fields["lead"].queryset = get_authorized_users(Permissions.Product_Type_View) \
.filter(engagement__lead__isnull=False).distinct()
class Meta:
model = Engagement
- fields = ['product__name', 'product__prod_type']
-
-
-class EngagementFilter(DojoFilter):
- engagement__name = CharFilter(lookup_expr='icontains', label='Engagement name contains')
- engagement__lead = ModelChoiceFilter(queryset=Dojo_User.objects.none(), label="Lead")
- engagement__version = CharFilter(field_name='engagement__version', lookup_expr='icontains', label='Engagement version')
- engagement__test__version = CharFilter(field_name='engagement__test__version', lookup_expr='icontains', label='Test version')
-
- name = CharFilter(lookup_expr='icontains', label='Product name contains')
- prod_type = ModelMultipleChoiceFilter(
- queryset=Product_Type.objects.none(),
- label="Product Type")
- engagement__product__lifecycle = MultipleChoiceFilter(
- choices=Product.LIFECYCLE_CHOICES, label='Product lifecycle', null_label='Empty')
- engagement__status = MultipleChoiceFilter(choices=ENGAGEMENT_STATUS_CHOICES,
- label="Status")
-
- tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- queryset=Engagement.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
+ fields = ["product__name", "product__prod_type"]
- tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
- not_tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- exclude=True,
- queryset=Engagement.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
+class EngagementDirectFilterWithoutObjectLookups(EngagementDirectFilterHelper):
+ lead = CharFilter(
+ field_name="lead__username",
+ lookup_expr="iexact",
+ label="Lead Username",
+ help_text="Search for Lead username that are an exact match")
+ lead_contains = CharFilter(
+ field_name="lead__username",
+ lookup_expr="icontains",
+ label="Lead Username Contains",
+ help_text="Search for Lead username that contain a given pattern")
+ product__prod_type__name = CharFilter(
+ field_name="product__prod_type__name",
+ lookup_expr="iexact",
+ label="Product Type Name",
+ help_text="Search for Product Type names that are an exact match")
+ product__prod_type__name_contains = CharFilter(
+ field_name="product__prod_type__name",
+ lookup_expr="icontains",
+ label="Product Type Name Contains",
+ help_text="Search for Product Type names that contain a given pattern")
- not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True)
+ class Meta:
+ model = Engagement
+ fields = ["product__name"]
- has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags')
+class EngagementFilterHelper(FilterSet):
+ name = CharFilter(lookup_expr="icontains", label="Product name contains")
+ tag = CharFilter(field_name="tags__name", lookup_expr="icontains", label="Tag name contains")
+ not_tag = CharFilter(field_name="tags__name", lookup_expr="icontains", label="Not tag name contains", exclude=True)
+ has_tags = BooleanFilter(field_name="tags", lookup_expr="isnull", exclude=True, label="Has tags")
+ engagement__name = CharFilter(lookup_expr="icontains", label="Engagement name contains")
+ engagement__version = CharFilter(field_name="engagement__version", lookup_expr="icontains", label="Engagement version")
+ engagement__test__version = CharFilter(field_name="engagement__test__version", lookup_expr="icontains", label="Test version")
+ engagement__product__lifecycle = MultipleChoiceFilter(
+ choices=Product.LIFECYCLE_CHOICES,
+ label="Product lifecycle",
+ null_label="Empty")
+ engagement__status = MultipleChoiceFilter(
+ choices=ENGAGEMENT_STATUS_CHOICES,
+ label="Status")
o = OrderingFilter(
# tuple-mapping retains order
fields=(
- ('name', 'name'),
- ('prod_type__name', 'prod_type__name'),
+ ("name", "name"),
+ ("prod_type__name", "prod_type__name"),
),
field_labels={
- 'name': 'Product Name',
- 'prod_type__name': 'Product Type',
+ "name": "Product Name",
+ "prod_type__name": "Product Type",
}
-
)
+
+class EngagementFilter(EngagementFilterHelper, DojoFilter):
+ engagement__lead = ModelChoiceFilter(
+ queryset=Dojo_User.objects.none(),
+ label="Lead")
+ prod_type = ModelMultipleChoiceFilter(
+ queryset=Product_Type.objects.none(),
+ label="Product Type")
+ tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ queryset=Engagement.tags.tag_model.objects.all().order_by("name"))
+ not_tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ exclude=True,
+ queryset=Engagement.tags.tag_model.objects.all().order_by("name"))
+
def __init__(self, *args, **kwargs):
super(EngagementFilter, self).__init__(*args, **kwargs)
- self.form.fields['prod_type'].queryset = get_authorized_product_types(Permissions.Product_Type_View)
- self.form.fields['engagement__lead'].queryset = get_authorized_users(Permissions.Product_Type_View) \
+ self.form.fields["prod_type"].queryset = get_authorized_product_types(Permissions.Product_Type_View)
+ self.form.fields["engagement__lead"].queryset = get_authorized_users(Permissions.Product_Type_View) \
.filter(engagement__lead__isnull=False).distinct()
class Meta:
@@ -968,37 +1006,42 @@ class Meta:
fields = ['name', 'prod_type']
-class ProductEngagementFilter(DojoFilter):
- lead = ModelChoiceFilter(queryset=Dojo_User.objects.none(), label="Lead")
+class EngagementFilterWithoutObjectLookups(EngagementFilterHelper):
+ engagement__lead = CharFilter(
+ field_name="engagement__lead__username",
+ lookup_expr="iexact",
+ label="Lead Username",
+ help_text="Search for Lead username that are an exact match")
+ engagement__lead_contains = CharFilter(
+ field_name="engagement__lead__username",
+ lookup_expr="icontains",
+ label="Lead Username Contains",
+ help_text="Search for Lead username that contain a given pattern")
+ prod_type__name = CharFilter(
+ field_name="prod_type__name",
+ lookup_expr="iexact",
+ label="Product Type Name",
+ help_text="Search for Product Type names that are an exact match")
+ prod_type__name_contains = CharFilter(
+ field_name="prod_type__name",
+ lookup_expr="icontains",
+ label="Product Type Name Contains",
+ help_text="Search for Product Type names that contain a given pattern")
+
+ class Meta:
+ model = Product
+ fields = ['name']
+
+
+class ProductEngagementFilterHelper(FilterSet):
version = CharFilter(lookup_expr='icontains', label='Engagement version')
test__version = CharFilter(field_name='test__version', lookup_expr='icontains', label='Test version')
-
name = CharFilter(lookup_expr='icontains')
- status = MultipleChoiceFilter(choices=ENGAGEMENT_STATUS_CHOICES,
- label="Status")
-
+ status = MultipleChoiceFilter(choices=ENGAGEMENT_STATUS_CHOICES, label="Status")
target_start = DateRangeFilter()
target_end = DateRangeFilter()
-
- tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- queryset=Engagement.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
-
- not_tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- exclude=True,
- queryset=Engagement.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True)
-
o = OrderingFilter(
# tuple-mapping retains order
fields=(
@@ -1012,19 +1055,44 @@ class ProductEngagementFilter(DojoFilter):
field_labels={
'name': 'Engagement Name',
}
-
)
- def __init__(self, *args, **kwargs):
- super(ProductEngagementFilter, self).__init__(*args, **kwargs)
- self.form.fields['lead'].queryset = get_authorized_users(Permissions.Product_Type_View) \
- .filter(engagement__lead__isnull=False).distinct()
-
class Meta:
model = Product
fields = ['name']
+class ProductEngagementFilter(ProductEngagementFilterHelper, DojoFilter):
+ lead = ModelChoiceFilter(queryset=Dojo_User.objects.none(), label="Lead")
+ tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ queryset=Engagement.tags.tag_model.objects.all().order_by("name"))
+ not_tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ exclude=True,
+ queryset=Engagement.tags.tag_model.objects.all().order_by("name"))
+
+ def __init__(self, *args, **kwargs):
+ super(ProductEngagementFilter, self).__init__(*args, **kwargs)
+ self.form.fields["lead"].queryset = get_authorized_users(
+ Permissions.Product_Type_View).filter(engagement__lead__isnull=False).distinct()
+
+
+class ProductEngagementFilterWithoutObjectLookups(ProductEngagementFilterHelper, DojoFilter):
+ lead = CharFilter(
+ field_name="lead__username",
+ lookup_expr="iexact",
+ label="Lead Username",
+ help_text="Search for Lead username that are an exact match")
+ lead_contains = CharFilter(
+ field_name="lead__username",
+ lookup_expr="icontains",
+ label="Lead Username Contains",
+ help_text="Search for Lead username that contain a given pattern")
+
+
class ApiEngagementFilter(DojoFilter):
product__prod_type = NumberInFilter(field_name='product__prod_type', lookup_expr='in')
tag = CharFilter(field_name='tags__name', lookup_expr='icontains', help_text='Tag name contains')
@@ -1069,107 +1137,19 @@ class Meta:
'pen_test', 'status', 'product', 'name', 'version', 'tags']
-class ProductFilter(DojoFilter):
+class ProductFilterHelper(FilterSet):
name = CharFilter(lookup_expr='icontains', label="Product Name")
name_exact = CharFilter(field_name='name', lookup_expr='iexact', label="Exact Product Name")
- prod_type = ModelMultipleChoiceFilter(
- queryset=Product_Type.objects.none(),
- label="Product Type")
business_criticality = MultipleChoiceFilter(choices=Product.BUSINESS_CRITICALITY_CHOICES, null_label="Empty")
platform = MultipleChoiceFilter(choices=Product.PLATFORM_CHOICES, null_label="Empty")
lifecycle = MultipleChoiceFilter(choices=Product.LIFECYCLE_CHOICES, null_label="Empty")
origin = MultipleChoiceFilter(choices=Product.ORIGIN_CHOICES, null_label="Empty")
external_audience = BooleanFilter(field_name='external_audience')
internet_accessible = BooleanFilter(field_name='internet_accessible')
-
- # not specifying anything for tags will render a multiselect input functioning as OR
-
- tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- # tags_and = ModelMultipleChoiceFilter(
- # field_name='tags__name',
- # to_field_name='name',
- # queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label='tags (AND)',
- # conjoined=True,
- # )
-
- # tags__name = ModelMultipleChoiceFilter(
- # queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label="tags (AND)"
- # )
-
tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label="Tag contains")
-
- # tags__name = CharFilter(
- # lookup_expr='icontains',
- # label="Tag contains",
- # )
-
- # tags__all = ModelMultipleChoiceFilter(
- # queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # field_name='tags__name',
- # label="tags (AND)"
- # )
-
- # not working below
-
- # tags = ModelMultipleChoiceFilter(
- # queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label="tags_widget", widget=TagWidget, tag_options=tagulous.models.TagOptions(
- # force_lowercase=True,)
- # ,)
-
- # tags__name = CharFilter(lookup_expr='icontains')
-
- # tags__and = ModelMultipleChoiceFilter(
- # field_name='tags__name',
- # to_field_name='name',
- # lookup_expr='in',
- # queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label="tags (AND)"
- # )
-
- # tags = ModelMultipleChoiceFilter(
- # queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label="tags (OR)"
- # )
-
- # tags = ModelMultipleChoiceFilter(
- # field_name='tags__name',
- # to_field_name='name',
- # queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label="tags (OR2)",
- # )
-
- # tags = ModelMultipleChoiceFilter(
- # field_name='tags',
- # to_field_name='name',
- # # lookup_expr='icontains', # nor working
- # # without lookup_expr we get an error: ValueError: invalid literal for int() with base 10: 'magento'
- # queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label="tags (OR3)",
- # )
-
- not_tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- exclude=True,
- queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True)
-
outside_of_sla = ProductSLAFilter(label="Outside of SLA")
-
has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags')
-
o = OrderingFilter(
# tuple-mapping retains order
fields=(
@@ -1194,24 +1174,61 @@ class ProductFilter(DojoFilter):
'external_audience': 'External Audience ',
'internet_accessible': 'Internet Accessible ',
}
-
)
- # tags = CharFilter(lookup_expr='icontains', label="Tags")
+
+class ProductFilter(ProductFilterHelper, DojoFilter):
+ prod_type = ModelMultipleChoiceFilter(
+ queryset=Product_Type.objects.none(),
+ label="Product Type")
+ tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ queryset=Product.tags.tag_model.objects.all().order_by("name"))
+ not_tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ exclude=True,
+ queryset=Product.tags.tag_model.objects.all().order_by("name"))
def __init__(self, *args, **kwargs):
self.user = None
- if 'user' in kwargs:
- self.user = kwargs.pop('user')
-
+ if "user" in kwargs:
+ self.user = kwargs.pop("user")
super(ProductFilter, self).__init__(*args, **kwargs)
+ self.form.fields["prod_type"].queryset = get_authorized_product_types(Permissions.Product_Type_View)
+
+ class Meta:
+ model = Product
+ fields = [
+ "name", "name_exact", "prod_type", "business_criticality",
+ "platform", "lifecycle", "origin", "external_audience",
+ "internet_accessible", "tags"
+ ]
+
+
+class ProductFilterWithoutObjectLookups(ProductFilterHelper):
+ prod_type__name = CharFilter(
+ field_name="prod_type__name",
+ lookup_expr="iexact",
+ label="Product Type Name",
+ help_text="Search for Product Type names that are an exact match")
+ prod_type__name_contains = CharFilter(
+ field_name="prod_type__name",
+ lookup_expr="icontains",
+ label="Product Type Name Contains",
+ help_text="Search for Product Type names that contain a given pattern")
- self.form.fields['prod_type'].queryset = get_authorized_product_types(Permissions.Product_Type_View)
+ def __init__(self, *args, **kwargs):
+ kwargs.pop("user", None)
+ super(ProductFilterWithoutObjectLookups, self).__init__(*args, **kwargs)
class Meta:
model = Product
- fields = ['name', 'name_exact', 'prod_type', 'business_criticality', 'platform', 'lifecycle', 'origin', 'external_audience',
- 'internet_accessible', 'tags']
+ fields = [
+ "name", "name_exact", "business_criticality", "platform",
+ "lifecycle", "origin", "external_audience", "internet_accessible",
+ ]
class ApiProductFilter(DojoFilter):
@@ -1444,7 +1461,7 @@ def filter(self, qs, value):
return super().filter(qs, value)
-class FindingFilterNonModelFilters(FilterSet):
+class FindingFilterHelper(FilterSet):
title = CharFilter(lookup_expr="icontains")
date = DateRangeFilter()
on = DateFilter(field_name="date", lookup_expr="exact", label="On")
@@ -1569,7 +1586,7 @@ def set_date_fields(self, *args: list, **kwargs: dict):
self.form.fields['cwe'].choices = cwe_options(self.queryset)
-class FindingFilterWithoutObjectLookups(FindingFilterNonModelFilters, FindingTagStringFilter):
+class FindingFilterWithoutObjectLookups(FindingFilterHelper, FindingTagStringFilter):
reporter = CharFilter(
field_name="reporter__username",
lookup_expr="iexact",
@@ -1677,7 +1694,7 @@ def __init__(self, *args, **kwargs):
del self.form.fields['test__name_contains']
-class FindingFilter(FindingFilterNonModelFilters, FindingTagFilter):
+class FindingFilter(FindingFilterHelper, FindingTagFilter):
reporter = ModelMultipleChoiceFilter(queryset=Dojo_User.objects.none())
reviewers = ModelMultipleChoiceFilter(queryset=Dojo_User.objects.none())
test__engagement__product__prod_type = ModelMultipleChoiceFilter(
@@ -2025,113 +2042,262 @@ class Meta(FindingFilterWithoutObjectLookups.Meta):
fields = get_finding_filterset_fields(metrics=True, filter_string_matching=True)
-class MetricsEndpointFilter(FilterSet):
- start_date = DateFilter(field_name='date', label='Start Date', lookup_expr=('gt'))
- end_date = DateFilter(field_name='date', label='End Date', lookup_expr=('lt'))
+class MetricsEndpointFilterHelper(FilterSet):
+ start_date = DateFilter(field_name="date", label="Start Date", lookup_expr=("gt"))
+ end_date = DateFilter(field_name="date", label="End Date", lookup_expr=("lt"))
date = MetricsDateRangeFilter()
+ finding__test__engagement__version = CharFilter(lookup_expr="icontains", label="Engagement Version")
+ finding__severity = MultipleChoiceFilter(choices=SEVERITY_CHOICES, label="Severity")
+ endpoint__host = CharFilter(lookup_expr="icontains", label="Endpoint Host")
+ finding_title = CharFilter(lookup_expr="icontains", label="Finding Title")
+ tag = CharFilter(field_name="tags__name", lookup_expr="icontains", label="Tag name contains")
+ not_tag = CharFilter(field_name="tags__name", lookup_expr="icontains", label="Not tag name contains", exclude=True)
+
+
+class MetricsEndpointFilter(MetricsEndpointFilterHelper):
finding__test__engagement__product__prod_type = ModelMultipleChoiceFilter(
queryset=Product_Type.objects.none(),
label="Product Type")
finding__test__engagement = ModelMultipleChoiceFilter(
queryset=Engagement.objects.none(),
label="Engagement")
- finding__test__engagement__version = CharFilter(lookup_expr='icontains', label="Engagement Version")
- finding__severity = MultipleChoiceFilter(choices=SEVERITY_CHOICES, label="Severity")
-
- endpoint__host = CharFilter(lookup_expr='icontains', label="Endpoint Host")
- finding_title = CharFilter(lookup_expr='icontains', label="Finding Title")
-
- tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
+ endpoint__tags = ModelMultipleChoiceFilter(
+ field_name='endpoint__tags__name',
to_field_name='name',
- queryset=Endpoint.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
-
- not_tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
+ label='Endpoint tags',
+ queryset=Endpoint.tags.tag_model.objects.all().order_by('name'))
+ finding__tags = ModelMultipleChoiceFilter(
+ field_name='finding__tags__name',
to_field_name='name',
- exclude=True,
- queryset=Endpoint.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True)
-
- not_tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
+ label='Finding tags',
+ queryset=Finding.tags.tag_model.objects.all().order_by('name'))
+ finding__test__tags = ModelMultipleChoiceFilter(
+ field_name='finding__test__tags__name',
+ to_field_name='name',
+ label='Test tags',
+ queryset=Test.tags.tag_model.objects.all().order_by('name'))
+ finding__test__engagement__tags = ModelMultipleChoiceFilter(
+ field_name='finding__test__engagement__tags__name',
+ to_field_name='name',
+ label='Engagement tags',
+ queryset=Engagement.tags.tag_model.objects.all().order_by('name'))
+ finding__test__engagement__product__tags = ModelMultipleChoiceFilter(
+ field_name='finding__test__engagement__product__tags__name',
+ to_field_name='name',
+ label='Product tags',
+ queryset=Product.tags.tag_model.objects.all().order_by('name'))
+ not_endpoint__tags = ModelMultipleChoiceFilter(
+ field_name='endpoint__tags__name',
to_field_name='name',
exclude=True,
- queryset=Finding.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- not_test__tags = ModelMultipleChoiceFilter(
- field_name='test__tags__name',
+ label='Endpoint without tags',
+ queryset=Endpoint.tags.tag_model.objects.all().order_by('name'))
+ not_finding__tags = ModelMultipleChoiceFilter(
+ field_name='finding__tags__name',
+ to_field_name='name',
+ exclude=True,
+ label='Finding without tags',
+ queryset=Finding.tags.tag_model.objects.all().order_by('name'))
+ not_finding__test__tags = ModelMultipleChoiceFilter(
+ field_name='finding__test__tags__name',
to_field_name='name',
exclude=True,
label='Test without tags',
- queryset=Test.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- not_test__engagement__tags = ModelMultipleChoiceFilter(
- field_name='test__engagement__tags__name',
+ queryset=Test.tags.tag_model.objects.all().order_by('name'))
+ not_finding__test__engagement__tags = ModelMultipleChoiceFilter(
+ field_name='finding__test__engagement__tags__name',
to_field_name='name',
exclude=True,
label='Engagement without tags',
- queryset=Engagement.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- not_test__engagement__product__tags = ModelMultipleChoiceFilter(
- field_name='test__engagement__product__tags__name',
+ queryset=Engagement.tags.tag_model.objects.all().order_by('name'))
+ not_finding__test__engagement__product__tags = ModelMultipleChoiceFilter(
+ field_name='finding__test__engagement__product__tags__name',
to_field_name='name',
exclude=True,
label='Product without tags',
- queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True)
-
- tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
+ queryset=Product.tags.tag_model.objects.all().order_by('name'))
def __init__(self, *args, **kwargs):
if args[0]:
- if args[0].get('start_date', '') != '' or args[0].get('end_date', '') != '':
+ if args[0].get("start_date", "") != "" or args[0].get("end_date", "") != "":
args[0]._mutable = True
- args[0]['date'] = 8
+ args[0]["date"] = 8
args[0]._mutable = False
self.pid = None
- if 'pid' in kwargs:
- self.pid = kwargs.pop('pid')
+ if "pid" in kwargs:
+ self.pid = kwargs.pop("pid")
super().__init__(*args, **kwargs)
if self.pid:
- del self.form.fields['finding__test__engagement__product__prod_type']
- self.form.fields['finding__test__engagement'].queryset = Engagement.objects.filter(
+ del self.form.fields["finding__test__engagement__product__prod_type"]
+ self.form.fields["finding__test__engagement"].queryset = Engagement.objects.filter(
product_id=self.pid
).all()
else:
- self.form.fields['finding__test__engagement'].queryset = get_authorized_engagements(Permissions.Engagement_View).order_by('name')
+ self.form.fields["finding__test__engagement"].queryset = get_authorized_engagements(Permissions.Engagement_View).order_by("name")
- if 'finding__test__engagement__product__prod_type' in self.form.fields:
+ if "finding__test__engagement__product__prod_type" in self.form.fields:
self.form.fields[
- 'finding__test__engagement__product__prod_type'].queryset = get_authorized_product_types(Permissions.Product_Type_View)
+ "finding__test__engagement__product__prod_type"].queryset = get_authorized_product_types(Permissions.Product_Type_View)
class Meta:
model = Endpoint_Status
- exclude = ['last_modified', 'endpoint', 'finding']
+ exclude = ["last_modified", "endpoint", "finding"]
-class EndpointFilter(DojoFilter):
- product = ModelMultipleChoiceFilter(
- queryset=Product.objects.none(),
- label="Product")
+class MetricsEndpointFilterWithoutObjectLookups(MetricsEndpointFilterHelper, FindingTagStringFilter):
+ finding__test__engagement__product__prod_type = CharFilter(
+ field_name="finding__test__engagement__product__prod_type",
+ lookup_expr="iexact",
+ label="Product Type Name",
+ help_text="Search for Product Type names that are an exact match")
+ finding__test__engagement__product__prod_type_contains = CharFilter(
+ field_name="finding__test__engagement__product__prod_type",
+ lookup_expr="icontains",
+ label="Product Type Name Contains",
+ help_text="Search for Product Type names that contain a given pattern")
+ finding__test__engagement = CharFilter(
+ field_name="finding__test__engagement",
+ lookup_expr="iexact",
+ label="Engagement Name",
+ help_text="Search for Engagement names that are an exact match")
+ finding__test__engagement_contains = CharFilter(
+ field_name="finding__test__engagement",
+ lookup_expr="icontains",
+ label="Engagement Name Contains",
+ help_text="Search for Engagement names that contain a given pattern")
+ endpoint__tags_contains = CharFilter(
+ label="Endpoint Tag Contains",
+ field_name="endpoint__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Endpoint that contain a given pattern")
+ endpoint__tags = CharFilter(
+ label="Endpoint Tag",
+ field_name="endpoint__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Endpoint that are an exact match")
+ finding__tags_contains = CharFilter(
+ label="Finding Tag Contains",
+ field_name="finding__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern")
+ finding__tags = CharFilter(
+ label="Finding Tag",
+ field_name="finding__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match")
+ finding__test__tags_contains = CharFilter(
+ label="Test Tag Contains",
+ field_name="finding__test__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern")
+ finding__test__tags = CharFilter(
+ label="Test Tag",
+ field_name="finding__test__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match")
+ finding__test__engagement__tags_contains = CharFilter(
+ label="Engagement Tag Contains",
+ field_name="finding__test__engagement__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern")
+ finding__test__engagement__tags = CharFilter(
+ label="Engagement Tag",
+ field_name="finding__test__engagement__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match")
+ finding__test__engagement__product__tags_contains = CharFilter(
+ label="Product Tag Contains",
+ field_name="finding__test__engagement__product__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern")
+ finding__test__engagement__product__tags = CharFilter(
+ label="Product Tag",
+ field_name="finding__test__engagement__product__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match")
+
+ not_endpoint__tags_contains = CharFilter(
+ label="Endpoint Tag Does Not Contain",
+ field_name="endpoint__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Endpoint that contain a given pattern, and exclude them",
+ exclude=True)
+ not_endpoint__tags = CharFilter(
+ label="Not Endpoint Tag",
+ field_name="endpoint__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Endpoint that are an exact match, and exclude them",
+ exclude=True)
+ not_finding__tags_contains = CharFilter(
+ label="Finding Tag Does Not Contain",
+ field_name="finding__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern, and exclude them",
+ exclude=True)
+ not_finding__tags = CharFilter(
+ label="Not Finding Tag",
+ field_name="finding__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match, and exclude them",
+ exclude=True)
+ not_finding__test__tags_contains = CharFilter(
+ label="Test Tag Does Not Contain",
+ field_name="finding__test__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Test that contain a given pattern, and exclude them",
+ exclude=True)
+ not_finding__test__tags = CharFilter(
+ label="Not Test Tag",
+ field_name="finding__test__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Test that are an exact match, and exclude them",
+ exclude=True)
+ not_finding__test__engagement__tags_contains = CharFilter(
+ label="Engagement Tag Does Not Contain",
+ field_name="finding__test__engagement__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Engagement that contain a given pattern, and exclude them",
+ exclude=True)
+ not_finding__test__engagement__tags = CharFilter(
+ label="Not Engagement Tag",
+ field_name="finding__test__engagement__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Engagement that are an exact match, and exclude them",
+ exclude=True)
+ not_finding__test__engagement__product__tags_contains = CharFilter(
+ label="Product Tag Does Not Contain",
+ field_name="finding__test__engagement__product__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Product that contain a given pattern, and exclude them",
+ exclude=True)
+ not_finding__test__engagement__product__tags = CharFilter(
+ label="Not Product Tag",
+ field_name="finding__test__engagement__product__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Product that are an exact match, and exclude them",
+ exclude=True)
+
+ def __init__(self, *args, **kwargs):
+ if args[0]:
+ if args[0].get("start_date", "") != "" or args[0].get("end_date", "") != "":
+ args[0]._mutable = True
+ args[0]["date"] = 8
+ args[0]._mutable = False
+ self.pid = None
+ if "pid" in kwargs:
+ self.pid = kwargs.pop("pid")
+ super().__init__(*args, **kwargs)
+ if self.pid:
+ del self.form.fields["finding__test__engagement__product__prod_type"]
+
+ class Meta:
+ model = Endpoint_Status
+ exclude = ["last_modified", "endpoint", "finding"]
+
+
+class EndpointFilterHelper(FilterSet):
protocol = CharFilter(lookup_expr='icontains')
userinfo = CharFilter(lookup_expr='icontains')
host = CharFilter(lookup_expr='icontains')
@@ -2139,65 +2305,77 @@ class EndpointFilter(DojoFilter):
path = CharFilter(lookup_expr='icontains')
query = CharFilter(lookup_expr='icontains')
fragment = CharFilter(lookup_expr='icontains')
-
- tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- queryset=Endpoint.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
+ tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
+ not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True)
+ has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags')
+ o = OrderingFilter(
+ # tuple-mapping retains order
+ fields=(
+ ('product', 'product'),
+ ('host', 'host'),
+ ),
)
- tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
+class EndpointFilter(EndpointFilterHelper, DojoFilter):
+ product = ModelMultipleChoiceFilter(
+ queryset=Product.objects.none(),
+ label="Product")
tags = ModelMultipleChoiceFilter(
field_name='tags__name',
to_field_name='name',
- queryset=Finding.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- test__tags = ModelMultipleChoiceFilter(
- field_name='test__tags__name',
+ label="Endpoint Tags",
+ queryset=Endpoint.tags.tag_model.objects.all().order_by('name'))
+ findings__tags = ModelMultipleChoiceFilter(
+ field_name='findings__tags__name',
to_field_name='name',
- queryset=Test.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- test__engagement__tags = ModelMultipleChoiceFilter(
- field_name='test__engagement__tags__name',
+ label="Finding Tags",
+ queryset=Finding.tags.tag_model.objects.all().order_by('name'))
+ findings__test__tags = ModelMultipleChoiceFilter(
+ field_name='findings__test__tags__name',
to_field_name='name',
- queryset=Engagement.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- test__engagement__product__tags = ModelMultipleChoiceFilter(
- field_name='test__engagement__product__tags__name',
+ label="Test Tags",
+ queryset=Test.tags.tag_model.objects.all().order_by('name'))
+ findings__test__engagement__tags = ModelMultipleChoiceFilter(
+ field_name='findings__test__engagement__tags__name',
to_field_name='name',
- queryset=Product.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
-
+ label="Engagement Tags",
+ queryset=Engagement.tags.tag_model.objects.all().order_by('name'))
+ findings__test__engagement__product__tags = ModelMultipleChoiceFilter(
+ field_name='findings__test__engagement__product__tags__name',
+ to_field_name='name',
+ label="Product Tags",
+ queryset=Product.tags.tag_model.objects.all().order_by('name'))
not_tags = ModelMultipleChoiceFilter(
field_name='tags__name',
to_field_name='name',
+ label="Not Endpoint Tags",
exclude=True,
- queryset=Finding.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
- not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True)
-
- has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags')
-
- o = OrderingFilter(
- # tuple-mapping retains order
- fields=(
- ('product', 'product'),
- ('host', 'host'),
- ),
- )
+ queryset=Endpoint.tags.tag_model.objects.all().order_by('name'))
+ not_findings__tags = ModelMultipleChoiceFilter(
+ field_name='findings__tags__name',
+ to_field_name='name',
+ label="Not Finding Tags",
+ exclude=True,
+ queryset=Finding.tags.tag_model.objects.all().order_by('name'))
+ not_findings__test__tags = ModelMultipleChoiceFilter(
+ field_name='findings__test__tags__name',
+ to_field_name='name',
+ label="Not Test Tags",
+ exclude=True,
+ queryset=Test.tags.tag_model.objects.all().order_by('name'))
+ not_findings__test__engagement__tags = ModelMultipleChoiceFilter(
+ field_name='findings__test__engagement__tags__name',
+ to_field_name='name',
+ label="Not Engagement Tags",
+ exclude=True,
+ queryset=Engagement.tags.tag_model.objects.all().order_by('name'))
+ not_findings__test__engagement__product__tags = ModelMultipleChoiceFilter(
+ field_name='findings__test__engagement__product__tags__name',
+ to_field_name='name',
+ label="Not Product Tags",
+ exclude=True,
+ queryset=Product.tags.tag_model.objects.all().order_by('name'))
def __init__(self, *args, **kwargs):
self.user = None
@@ -2213,7 +2391,147 @@ def qs(self):
class Meta:
model = Endpoint
- exclude = ['findings']
+ exclude = ["findings", "inherited_tags"]
+
+
+class EndpointFilterWithoutObjectLookups(EndpointFilterHelper):
+ product__name = CharFilter(
+ field_name="product__name",
+ lookup_expr="iexact",
+ label="Product Name",
+ help_text="Search for Product names that are an exact match")
+ product__name_contains = CharFilter(
+ field_name="product__name",
+ lookup_expr="icontains",
+ label="Product Name Contains",
+ help_text="Search for Product names that contain a given pattern")
+
+ tags_contains = CharFilter(
+ label="Endpoint Tag Contains",
+ field_name="tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Endpoint that contain a given pattern")
+ tags = CharFilter(
+ label="Endpoint Tag",
+ field_name="tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Endpoint that are an exact match")
+ findings__tags_contains = CharFilter(
+ label="Finding Tag Contains",
+ field_name="findings__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern")
+ findings__tags = CharFilter(
+ label="Finding Tag",
+ field_name="findings__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match")
+ findings__test__tags_contains = CharFilter(
+ label="Test Tag Contains",
+ field_name="findings__test__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern")
+ findings__test__tags = CharFilter(
+ label="Test Tag",
+ field_name="findings__test__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match")
+ findings__test__engagement__tags_contains = CharFilter(
+ label="Engagement Tag Contains",
+ field_name="findings__test__engagement__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern")
+ findings__test__engagement__tags = CharFilter(
+ label="Engagement Tag",
+ field_name="findings__test__engagement__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match")
+ findings__test__engagement__product__tags_contains = CharFilter(
+ label="Product Tag Contains",
+ field_name="findings__test__engagement__product__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern")
+ findings__test__engagement__product__tags = CharFilter(
+ label="Product Tag",
+ field_name="findings__test__engagement__product__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match")
+
+ not_tags_contains = CharFilter(
+ label="Endpoint Tag Does Not Contain",
+ field_name="tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Endpoint that contain a given pattern, and exclude them",
+ exclude=True)
+ not_tags = CharFilter(
+ label="Not Endpoint Tag",
+ field_name="tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Endpoint that are an exact match, and exclude them",
+ exclude=True)
+ not_findings__tags_contains = CharFilter(
+ label="Finding Tag Does Not Contain",
+ field_name="findings__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Finding that contain a given pattern, and exclude them",
+ exclude=True)
+ not_findings__tags = CharFilter(
+ label="Not Finding Tag",
+ field_name="findings__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Finding that are an exact match, and exclude them",
+ exclude=True)
+ not_findings__test__tags_contains = CharFilter(
+ label="Test Tag Does Not Contain",
+ field_name="findings__test__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Test that contain a given pattern, and exclude them",
+ exclude=True)
+ not_findings__test__tags = CharFilter(
+ label="Not Test Tag",
+ field_name="findings__test__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Test that are an exact match, and exclude them",
+ exclude=True)
+ not_findings__test__engagement__tags_contains = CharFilter(
+ label="Engagement Tag Does Not Contain",
+ field_name="findings__test__engagement__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Engagement that contain a given pattern, and exclude them",
+ exclude=True)
+ not_findings__test__engagement__tags = CharFilter(
+ label="Not Engagement Tag",
+ field_name="findings__test__engagement__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Engagement that are an exact match, and exclude them",
+ exclude=True)
+ not_findings__test__engagement__product__tags_contains = CharFilter(
+ label="Product Tag Does Not Contain",
+ field_name="findings__test__engagement__product__tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Product that contain a given pattern, and exclude them",
+ exclude=True)
+ not_findings__test__engagement__product__tags = CharFilter(
+ label="Not Product Tag",
+ field_name="findings__test__engagement__product__tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Product that are an exact match, and exclude them",
+ exclude=True)
+
+ def __init__(self, *args, **kwargs):
+ self.user = None
+ if 'user' in kwargs:
+ self.user = kwargs.pop('user')
+ super(EndpointFilterWithoutObjectLookups, self).__init__(*args, **kwargs)
+
+ @property
+ def qs(self):
+ parent = super(EndpointFilterWithoutObjectLookups, self).qs
+ return get_authorized_endpoints(Permissions.Endpoint_View, parent)
+
+ class Meta:
+ model = Endpoint
+ exclude = ["findings", "inherited_tags", "product"]
class ApiEndpointFilter(DojoFilter):
@@ -2257,37 +2575,15 @@ class Meta:
]
-class EngagementTestFilter(DojoFilter):
- lead = ModelChoiceFilter(queryset=Dojo_User.objects.none(), label="Lead")
+class EngagementTestFilterHelper(FilterSet):
version = CharFilter(lookup_expr='icontains', label='Version')
-
if settings.TRACK_IMPORT_HISTORY:
test_import__version = CharFilter(field_name='test_import__version', lookup_expr='icontains', label='Reimported Version')
-
target_start = DateRangeFilter()
target_end = DateRangeFilter()
-
- tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- queryset=Test.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Tag name contains')
-
- not_tags = ModelMultipleChoiceFilter(
- field_name='tags__name',
- to_field_name='name',
- exclude=True,
- queryset=Test.tags.tag_model.objects.all().order_by('name'),
- # label='tags', # doesn't work with tagulous, need to set in __init__ below
- )
-
not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True)
-
has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags')
-
o = OrderingFilter(
# tuple-mapping retains order
fields=(
@@ -2301,14 +2597,31 @@ class EngagementTestFilter(DojoFilter):
field_labels={
'name': 'Test Name',
}
-
)
+
+class EngagementTestFilter(EngagementTestFilterHelper, DojoFilter):
+ lead = ModelChoiceFilter(queryset=Dojo_User.objects.none(), label="Lead")
+ api_scan_configuration = ModelChoiceFilter(
+ queryset=Product_API_Scan_Configuration.objects.none(),
+ label="API Scan Configuration")
+ tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ queryset=Test.tags.tag_model.objects.all().order_by("name"))
+ not_tags = ModelMultipleChoiceFilter(
+ field_name="tags__name",
+ to_field_name="name",
+ exclude=True,
+ queryset=Test.tags.tag_model.objects.all().order_by("name"))
+
class Meta:
model = Test
- fields = ['id', 'title', 'test_type', 'target_start',
- 'target_end', 'percent_complete',
- 'version', 'api_scan_configuration']
+ fields = [
+ "title", "test_type", "target_start",
+ "target_end", "percent_complete",
+ "version", "api_scan_configuration",
+ ]
def __init__(self, *args, **kwargs):
self.engagement = kwargs.pop('engagement')
@@ -2319,6 +2632,63 @@ def __init__(self, *args, **kwargs):
.filter(test__lead__isnull=False).distinct()
+class EngagementTestFilterWithoutObjectLookups(EngagementTestFilterHelper):
+ lead = CharFilter(
+ field_name="lead__username",
+ lookup_expr="iexact",
+ label="Lead Username",
+ help_text="Search for Lead username that are an exact match")
+ lead_contains = CharFilter(
+ field_name="lead__username",
+ lookup_expr="icontains",
+ label="Lead Username Contains",
+ help_text="Search for Lead username that contain a given pattern")
+ api_scan_configuration__tool_configuration__name = CharFilter(
+ field_name="api_scan_configuration__tool_configuration__name",
+ lookup_expr="iexact",
+ label="API Scan Configuration Name",
+ help_text="Search for Lead username that are an exact match")
+ api_scan_configuration__tool_configuration__name_contains = CharFilter(
+ field_name="api_scan_configuration__tool_configuration__name",
+ lookup_expr="icontains",
+ label="API Scan Configuration Name Contains",
+ help_text="Search for Lead username that contain a given pattern")
+ tags_contains = CharFilter(
+ label="Test Tag Contains",
+ field_name="tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Test that contain a given pattern")
+ tags = CharFilter(
+ label="Test Tag",
+ field_name="tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Test that are an exact match")
+ not_tags_contains = CharFilter(
+ label="Test Tag Does Not Contain",
+ field_name="tags__name",
+ lookup_expr="icontains",
+ help_text="Search for tags on a Test that contain a given pattern, and exclude them",
+ exclude=True)
+ not_tags = CharFilter(
+ label="Not Test Tag",
+ field_name="tags__name",
+ lookup_expr="iexact",
+ help_text="Search for tags on a Test that are an exact match, and exclude them",
+ exclude=True)
+
+ class Meta:
+ model = Test
+ fields = [
+ "title", "test_type", "target_start",
+ "target_end", "percent_complete", "version",
+ ]
+
+ def __init__(self, *args, **kwargs):
+ self.engagement = kwargs.pop('engagement')
+ super(EngagementTestFilterWithoutObjectLookups, self).__init__(*args, **kwargs)
+ self.form.fields['test_type'].queryset = Test_Type.objects.filter(test__engagement=self.engagement).distinct().order_by('name')
+
+
class ApiTestFilter(DojoFilter):
tag = CharFilter(field_name='tags__name', lookup_expr='icontains', help_text='Tag name contains')
tags = CharFieldInFilter(field_name='tags__name', lookup_expr='in',
diff --git a/dojo/finding/views.py b/dojo/finding/views.py
index 8ac42af6598..9faef2cffc5 100644
--- a/dojo/finding/views.py
+++ b/dojo/finding/views.py
@@ -602,6 +602,14 @@ def get_test_import_data(self, request: HttpRequest, finding: Finding):
}
def get_similar_findings(self, request: HttpRequest, finding: Finding):
+ similar_findings_enabled = get_system_setting("enable_similar_findings", True)
+ if similar_findings_enabled is False:
+ return {
+ "similar_findings_enabled": similar_findings_enabled,
+ "duplicate_cluster": duplicate_cluster(request, finding),
+ "similar_findings": None,
+ "similar_findings_filter": None,
+ }
# add related actions for non-similar and non-duplicate cluster members
finding.related_actions = calculate_possible_related_actions_for_similar_finding(
request, finding, finding
@@ -638,6 +646,7 @@ def get_similar_findings(self, request: HttpRequest, finding: Finding):
)
return {
+ "similar_findings_enabled": similar_findings_enabled,
"duplicate_cluster": duplicate_cluster(request, finding),
"similar_findings": similar_findings,
"similar_findings_filter": similar_findings_filter,
diff --git a/dojo/jira_link/views.py b/dojo/jira_link/views.py
index 2f3ff1d3c30..e652b8703e4 100644
--- a/dojo/jira_link/views.py
+++ b/dojo/jira_link/views.py
@@ -7,7 +7,7 @@
from django.contrib.admin.utils import NestedObjects
from django.urls import reverse
from django.db import DEFAULT_DB_ALIAS
-from django.http import HttpResponseRedirect, HttpResponse, Http404, HttpResponseBadRequest
+from django.http import HttpResponseRedirect, HttpResponse, Http404
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from django.utils.dateparse import parse_datetime
@@ -15,8 +15,8 @@
from django.core.exceptions import PermissionDenied
# Local application/library imports
from dojo.forms import JIRAForm, DeleteJIRAInstanceForm, ExpressJIRAForm
-from dojo.models import User, JIRA_Instance, JIRA_Issue, Notes
-from dojo.utils import add_breadcrumb, add_error_message_to_response, get_system_setting
+from dojo.models import System_Settings, User, JIRA_Instance, JIRA_Issue, Notes
+from dojo.utils import add_breadcrumb, add_error_message_to_response
from dojo.notifications.helper import create_notification
from django.views.decorators.http import require_POST
import dojo.jira_link.helper as jira_helper
@@ -26,114 +26,140 @@
logger = logging.getLogger(__name__)
-# for examples of incoming json, see the unit tests for the webhook: https://github.com/DefectDojo/django-DefectDojo/blob/master/unittests/test_jira_webhook.py
-# or the officials docs (which are not always clear): https://developer.atlassian.com/server/jira/platform/webhooks/
+def webhook_responser_handler(
+ log_level: str,
+ message: str,
+) -> HttpResponse:
+ # These represent an error and will be sent to the debugger
+ # for development purposes
+ if log_level == "info":
+ logger.info(message)
+ # These are more common in misconfigurations and have a better
+ # chance of being seen by a user
+ elif log_level == "debug":
+ logger.debug(message)
+ # Return the response with the code
+ return HttpResponse(message, status=200)
+
+
@csrf_exempt
@require_POST
def webhook(request, secret=None):
- if not get_system_setting('enable_jira'):
- logger.debug('ignoring incoming webhook as JIRA is disabled.')
- raise Http404('JIRA disabled')
- elif not get_system_setting('enable_jira_web_hook'):
- logger.debug('ignoring incoming webhook as JIRA Webhook is disabled.')
- raise Http404('JIRA Webhook disabled')
- elif not get_system_setting('disable_jira_webhook_secret'):
- if not get_system_setting('jira_webhook_secret'):
- logger.warning('ignoring incoming webhook as JIRA Webhook secret is empty in Defect Dojo system settings.')
- raise PermissionDenied('JIRA Webhook secret cannot be empty')
- if secret != get_system_setting('jira_webhook_secret'):
- logger.warning('invalid secret provided to JIRA Webhook')
- raise PermissionDenied('invalid or no secret provided to JIRA Webhook')
+ """
+ for examples of incoming json, see the unit tests for the webhook:
+ https://github.com/DefectDojo/django-DefectDojo/blob/master/unittests/test_jira_webhook.py
+ or the officials docs (which are not always clear):
+ https://developer.atlassian.com/server/jira/platform/webhooks/
+ All responses here will return a 201 so that we may have control over the
+ logging level
+ """
+ # Make sure the request is a POST, otherwise, we reject
+ if request.method != "POST":
+ return webhook_responser_handler("debug", "Only POST requests are supported")
+ # Determine if th webhook is in use or not
+ system_settings = System_Settings.objects.get()
+ # If the jira integration is not enabled, then return a 404
+ if not system_settings.enable_jira:
+ return webhook_responser_handler("info", "Ignoring incoming webhook as JIRA is disabled.")
+ # If the webhook is not enabled, then return a 404
+ elif not system_settings.enable_jira_web_hook:
+ return webhook_responser_handler("info", "Ignoring incoming webhook as JIRA Webhook is disabled.")
+ # Determine if the request should be "authenticated"
+ elif not system_settings.disable_jira_webhook_secret:
+ # Make sure there is a value for the webhook secret before making a comparison
+ if not system_settings.jira_webhook_secret:
+ return webhook_responser_handler("info", "Ignoring incoming webhook as JIRA Webhook secret is empty in Defect Dojo system settings.")
+ # Make sure the secret supplied in the path of the webhook request matches the
+ # secret supplied in the system settings
+ if secret != system_settings.jira_webhook_secret:
+ return webhook_responser_handler("info", "Invalid or no secret provided to JIRA Webhook")
# if webhook secret is disabled in system_settings, we ignore the incoming secret, even if it doesn't match
-
# example json bodies at the end of this file
-
- if request.content_type != 'application/json':
- return HttpResponseBadRequest("only application/json supported")
-
- if request.method == 'POST':
- try:
- parsed = json.loads(request.body.decode('utf-8'))
- if parsed.get('webhookEvent') == 'jira:issue_updated':
- # xml examples at the end of file
- jid = parsed['issue']['id']
- jissue = get_object_or_404(JIRA_Issue, jira_id=jid)
-
- findings = None
- if jissue.finding:
- logging.info(f"Received issue update for {jissue.jira_key} for finding {jissue.finding.id}")
- findings = [jissue.finding]
- elif jissue.finding_group:
- logging.info(f"Received issue update for {jissue.jira_key} for finding group {jissue.finding_group}")
- findings = jissue.finding_group.findings.all()
- elif jissue.engagement:
- # if parsed['issue']['fields']['resolution'] != None:
- # eng.active = False
- # eng.status = 'Completed'
- # eng.save()
- return HttpResponse('Update for engagement ignored')
- else:
- logging.info(f"Received issue update for {jissue.jira_key} for unknown object")
- raise Http404(f'No finding, finding_group or engagement found for JIRA issue {jissue.jira_key}')
-
- assignee = parsed['issue']['fields'].get('assignee')
- assignee_name = 'Jira User'
- if assignee is not None:
- # First look for the 'name' field. If not present, try 'displayName'. Else put None
- assignee_name = assignee.get('name', assignee.get('displayName'))
-
- resolution = parsed['issue']['fields']['resolution']
-
- # "resolution":{
- # "self":"http://www.testjira.com/rest/api/2/resolution/11",
- # "id":"11",
- # "description":"Cancelled by the customer.",
- # "name":"Cancelled"
- # },
-
- # or
- # "resolution": null
-
- # or
- # "resolution": "None"
-
- resolution = resolution if resolution and resolution != "None" else None
- resolution_id = resolution['id'] if resolution else None
- resolution_name = resolution['name'] if resolution else None
- jira_now = parse_datetime(parsed['issue']['fields']['updated'])
-
- if findings:
- for finding in findings:
- jira_helper.process_resolution_from_jira(finding, resolution_id, resolution_name, assignee_name, jira_now, jissue)
- # Check for any comment that could have come along with the resolution
- if (error_response := check_for_and_create_comment(parsed)) is not None:
- return error_response
-
- if parsed.get('webhookEvent') == 'comment_created':
- if (error_response := check_for_and_create_comment(parsed)) is not None:
- return error_response
-
- if parsed.get('webhookEvent') not in ['comment_created', 'jira:issue_updated']:
- logger.info(f"Unrecognized JIRA webhook event received: {parsed.get('webhookEvent')}")
-
- except Exception as e:
- if isinstance(e, Http404):
- logger.warning('404 error processing JIRA webhook')
- logger.warning(str(e))
- else:
- logger.exception(e)
-
+ if request.content_type != "application/json":
+ return webhook_responser_handler("debug", "only application/json supported")
+ # Time to process the request
+ try:
+ parsed = json.loads(request.body.decode("utf-8"))
+ # Check if the events supplied are supported
+ if parsed.get('webhookEvent') not in ['comment_created', 'jira:issue_updated']:
+ return webhook_responser_handler("info", f"Unrecognized JIRA webhook event received: {parsed.get('webhookEvent')}")
+
+ if parsed.get('webhookEvent') == 'jira:issue_updated':
+ # xml examples at the end of file
+ jid = parsed['issue']['id']
+ # This may raise a 404, but it will be handled in the exception response
try:
- logger.debug('jira_webhook_body_parsed:')
- logger.debug(json.dumps(parsed, indent=4))
- except Exception:
- logger.debug('jira_webhook_body:')
- logger.debug(request.body.decode('utf-8'))
+ jissue = JIRA_Issue.objects.get(jira_id=jid)
+ except JIRA_Instance.DoesNotExist:
+ return webhook_responser_handler("info", f"JIRA issue {jid} is not linked to a DefectDojo Finding")
+ findings = None
+ # Determine what type of object we will be working with
+ if jissue.finding:
+ logging.debug(f"Received issue update for {jissue.jira_key} for finding {jissue.finding.id}")
+ findings = [jissue.finding]
+ elif jissue.finding_group:
+ logging.debug(f"Received issue update for {jissue.jira_key} for finding group {jissue.finding_group}")
+ findings = jissue.finding_group.findings.all()
+ elif jissue.engagement:
+ return webhook_responser_handler("debug", "Update for engagement ignored")
+ else:
+ return webhook_responser_handler("info", f"Received issue update for {jissue.jira_key} for unknown object")
+ # Process the assignee if present
+ assignee = parsed['issue']['fields'].get('assignee')
+ assignee_name = 'Jira User'
+ if assignee is not None:
+ # First look for the 'name' field. If not present, try 'displayName'. Else put None
+ assignee_name = assignee.get('name', assignee.get('displayName'))
+
+ # "resolution":{
+ # "self":"http://www.testjira.com/rest/api/2/resolution/11",
+ # "id":"11",
+ # "description":"Cancelled by the customer.",
+ # "name":"Cancelled"
+ # },
+
+ # or
+ # "resolution": null
+
+ # or
+ # "resolution": "None"
+
+ resolution = parsed['issue']['fields']['resolution']
+ resolution = resolution if resolution and resolution != "None" else None
+ resolution_id = resolution['id'] if resolution else None
+ resolution_name = resolution['name'] if resolution else None
+ jira_now = parse_datetime(parsed['issue']['fields']['updated'])
+
+ if findings:
+ for finding in findings:
+ jira_helper.process_resolution_from_jira(finding, resolution_id, resolution_name, assignee_name, jira_now, jissue)
+ # Check for any comment that could have come along with the resolution
+ if (error_response := check_for_and_create_comment(parsed)) is not None:
+ return error_response
+
+ if parsed.get('webhookEvent') == 'comment_created':
+ if (error_response := check_for_and_create_comment(parsed)) is not None:
+ return error_response
+
+ except Exception as e:
+ # Check if the issue is originally a 404
+ if isinstance(e, Http404):
+ return webhook_responser_handler("debug", str(e))
+ # Try to get a little more information on the exact exception
+ try:
+ message = (
+ f"Original Exception: {e}\n"
+ f"jira webhook body parsed:\n{json.dumps(parsed, indent=4)}"
+ )
+ except Exception:
+ message = (
+ f"Original Exception: {e}\n"
+ f"jira webhook body :\n{request.body.decode('utf-8')}"
+ )
+ return webhook_responser_handler("debug", message)
- # reraise to make sure we don't silently swallow things
- raise
- return HttpResponse('')
+ return webhook_responser_handler("No logging here", "Success!")
def check_for_and_create_comment(parsed_json):
@@ -194,31 +220,30 @@ def check_for_and_create_comment(parsed_json):
commenter_display_name = comment.get('updateAuthor', {}).get('displayName')
# example: body['comment']['self'] = "http://www.testjira.com/jira_under_a_path/rest/api/2/issue/666/comment/456843"
jid = comment.get('self', '').split('/')[-3]
- jissue = get_object_or_404(JIRA_Issue, jira_id=jid)
- logging.info(f"Received issue comment for {jissue.jira_key}")
+ try:
+ jissue = JIRA_Issue.objects.get(jira_id=jid)
+ except JIRA_Instance.DoesNotExist:
+ return webhook_responser_handler("info", f"JIRA issue {jid} is not linked to a DefectDojo Finding")
+ logging.debug(f"Received issue comment for {jissue.jira_key}")
logger.debug('jissue: %s', vars(jissue))
jira_usernames = JIRA_Instance.objects.values_list('username', flat=True)
for jira_user_id in jira_usernames:
# logger.debug('incoming username: %s jira config username: %s', commenter.lower(), jira_user_id.lower())
if jira_user_id.lower() == commenter.lower():
- logger.debug('skipping incoming JIRA comment as the user id of the comment in JIRA (%s) matches the JIRA username in DefectDojo (%s)', commenter.lower(), jira_user_id.lower())
- return HttpResponse('')
+ return webhook_responser_handler("debug", f"skipping incoming JIRA comment as the user id of the comment in JIRA {commenter.lower()} matches the JIRA username in DefectDojo {jira_user_id.lower()}")
findings = None
if jissue.finding:
findings = [jissue.finding]
create_notification(event='other', title=f'JIRA incoming comment - {jissue.finding}', finding=jissue.finding, url=reverse("view_finding", args=(jissue.finding.id,)), icon='check')
-
elif jissue.finding_group:
findings = [jissue.finding_group.findings.all()]
create_notification(event='other', title=f'JIRA incoming comment - {jissue.finding}', finding=jissue.finding, url=reverse("view_finding_group", args=(jissue.finding_group.id,)), icon='check')
-
elif jissue.engagement:
- return HttpResponse('Comment for engagement ignored')
+ return webhook_responser_handler("debug", "Comment for engagement ignored")
else:
- raise Http404(f'No finding or engagement found for JIRA issue {jissue.jira_key}')
-
+ return webhook_responser_handler("info", f"Received issue update for {jissue.jira_key} for unknown object")
# Set the fields for the notes
author, _ = User.objects.get_or_create(username='JIRA')
entry = f'({commenter_display_name} ({commenter})): {comment_text}'
diff --git a/dojo/metrics/views.py b/dojo/metrics/views.py
index b9892633ed8..865fa3a0e7b 100644
--- a/dojo/metrics/views.py
+++ b/dojo/metrics/views.py
@@ -20,7 +20,13 @@
from django.views.decorators.cache import cache_page
from django.utils import timezone
-from dojo.filters import MetricsFindingFilter, UserFilter, MetricsEndpointFilter, MetricsFindingFilterWithoutObjectLookups
+from dojo.filters import (
+ MetricsEndpointFilter,
+ MetricsEndpointFilterWithoutObjectLookups,
+ MetricsFindingFilter,
+ MetricsFindingFilterWithoutObjectLookups,
+ UserFilter,
+)
from dojo.forms import SimpleMetricsForm, ProductTypeCountsForm, ProductTagCountsForm
from dojo.models import Product_Type, Finding, Product, Engagement, Test, \
Risk_Acceptance, Dojo_User, Endpoint_Status
@@ -144,6 +150,7 @@ def finding_querys(prod_type, request):
filter_string_matching = get_system_setting("filter_string_matching", False)
finding_filter_class = MetricsFindingFilterWithoutObjectLookups if filter_string_matching else MetricsFindingFilter
findings = finding_filter_class(request.GET, queryset=findings_query)
+ form = findings.form
findings_qs = queryset_check(findings)
# Quick check to determine if the filters were too tight and filtered everything away
if not findings_qs and not findings_query:
@@ -207,6 +214,7 @@ def finding_querys(prod_type, request):
'weeks_between': weeks_between,
'start_date': start_date,
'end_date': end_date,
+ 'form': form,
}
@@ -220,8 +228,10 @@ def endpoint_querys(prod_type, request):
'finding__reporter')
endpoints_query = get_authorized_endpoint_status(Permissions.Endpoint_View, endpoints_query, request.user)
- endpoints = MetricsEndpointFilter(request.GET, queryset=endpoints_query)
-
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = MetricsEndpointFilterWithoutObjectLookups if filter_string_matching else MetricsEndpointFilter
+ endpoints = filter_class(request.GET, queryset=endpoints_query)
+ form = endpoints.form
endpoints_qs = queryset_check(endpoints)
if not endpoints_qs:
@@ -297,6 +307,7 @@ def endpoint_querys(prod_type, request):
'weeks_between': weeks_between,
'start_date': start_date,
'end_date': end_date,
+ 'form': form,
}
@@ -447,6 +458,7 @@ def metrics(request, mtype):
'closed_in_period_details': closed_in_period_details,
'punchcard': punchcard,
'ticks': ticks,
+ 'form': filters.get('form', None),
'show_pt_filter': show_pt_filter,
})
diff --git a/dojo/models.py b/dojo/models.py
index eb3c006334e..74b8da26888 100755
--- a/dojo/models.py
+++ b/dojo/models.py
@@ -421,6 +421,12 @@ class System_Settings(models.Model):
verbose_name=_('Enable Remediation Advice'),
help_text=_("Enables global remediation advice and matching on CWE and Title. The text will be replaced for mitigation, impact and references on a finding. Useful for providing consistent impact and remediation advice regardless of the scanner."))
+ enable_similar_findings = models.BooleanField(
+ default=True,
+ blank=False,
+ verbose_name=_("Enable Similar Findings"),
+ help_text=_("Enable the query of similar findings on the view finding page. This feature can involve potentially large queries and negatively impact performance"))
+
engagement_auto_close = models.BooleanField(
default=False,
blank=False,
diff --git a/dojo/product/views.py b/dojo/product/views.py
index 09f0e007b06..0724f7d4f35 100755
--- a/dojo/product/views.py
+++ b/dojo/product/views.py
@@ -25,8 +25,19 @@
from django.views import View
from dojo.templatetags.display_tags import asvs_calc_level
-from dojo.filters import ProductEngagementFilter, ProductFilter, EngagementFilter, MetricsEndpointFilter, \
- MetricsFindingFilter, MetricsFindingFilterWithoutObjectLookups, ProductComponentFilter
+from dojo.filters import (
+ ProductEngagementFilter,
+ ProductEngagementFilterWithoutObjectLookups,
+ ProductFilter,
+ ProductFilterWithoutObjectLookups,
+ EngagementFilter,
+ EngagementFilterWithoutObjectLookups,
+ MetricsEndpointFilter,
+ MetricsEndpointFilterWithoutObjectLookups,
+ MetricsFindingFilter,
+ MetricsFindingFilterWithoutObjectLookups,
+ ProductComponentFilter,
+)
from dojo.forms import ProductForm, EngForm, DeleteProductForm, DojoMetaDataForm, JIRAProjectForm, JIRAFindingForm, \
AdHocFindingForm, \
EngagementPresetsForm, DeleteEngagementPresetsForm, ProductNotificationsForm, \
@@ -39,7 +50,7 @@
Endpoint, Engagement_Presets, DojoMeta, Notifications, BurpRawRequestResponse, Product_Member, \
Product_Group, Product_API_Scan_Configuration
from dojo.utils import add_external_issue, add_error_message_to_response, add_field_errors_to_response, get_page_items, \
- add_breadcrumb, async_delete, \
+ add_breadcrumb, async_delete, calculate_finding_age, \
get_system_setting, get_setting, Product_Tab, get_punchcard_data, queryset_check, is_title_in_breadcrumbs, \
get_enabled_notifications_list, get_zero_severity_level, sum_by_severity_level, get_open_findings_burndown
@@ -67,8 +78,9 @@ def product(request):
# otherwise the paginator will perform all the annotations/prefetching already only to count the total number of records
# see https://code.djangoproject.com/ticket/23771 and https://code.djangoproject.com/ticket/25375
name_words = prods.values_list('name', flat=True)
-
- prod_filter = ProductFilter(request.GET, queryset=prods, user=request.user)
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = ProductFilterWithoutObjectLookups if filter_string_matching else ProductFilter
+ prod_filter = filter_class(request.GET, queryset=prods, user=request.user)
prod_list = get_page_items(request, prod_filter.qs, 25)
@@ -370,7 +382,9 @@ def endpoint_querys(request, prod):
'finding__test__engagement__risk_acceptance',
'finding__risk_acceptance_set',
'finding__reporter').annotate(severity=F('finding__severity'))
- endpoints = MetricsEndpointFilter(request.GET, queryset=endpoints_query)
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = MetricsEndpointFilterWithoutObjectLookups if filter_string_matching else MetricsEndpointFilter
+ endpoints = filter_class(request.GET, queryset=endpoints_query)
endpoints_qs = queryset_check(endpoints)
filters['form'] = endpoints.form
@@ -450,7 +464,9 @@ def view_product_metrics(request, pid):
engs = Engagement.objects.filter(product=prod, active=True)
view = identify_view(request)
- result = EngagementFilter(
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = EngagementFilterWithoutObjectLookups if filter_string_matching else EngagementFilter
+ result = filter_class(
request.GET,
queryset=Engagement.objects.filter(product=prod, active=False).order_by('-target_end'))
@@ -462,16 +478,9 @@ def view_product_metrics(request, pid):
elif view == 'Endpoint':
filters = endpoint_querys(request, prod)
- start_date = filters['start_date']
+ start_date = timezone.make_aware(datetime.combine(filters['start_date'], datetime.min.time()))
end_date = filters['end_date']
- tests = Test.objects.filter(engagement__product=prod).prefetch_related('finding_set', 'test_type')
- tests = tests.annotate(verified_finding_count=Count('finding__id', filter=Q(finding__verified=True)))
-
- open_vulnerabilities = filters['open_vulns']
- all_vulnerabilities = filters['all_vulns']
-
- start_date = timezone.make_aware(datetime.combine(start_date, datetime.min.time()))
r = relativedelta(end_date, start_date)
weeks_between = int(ceil((((r.years * 12) + r.months) * 4.33) + (r.days / 7)))
if weeks_between <= 0:
@@ -487,19 +496,45 @@ def view_product_metrics(request, pid):
critical_weekly = OrderedDict()
high_weekly = OrderedDict()
medium_weekly = OrderedDict()
+ open_objs_by_age = {}
open_objs_by_severity = get_zero_severity_level()
closed_objs_by_severity = get_zero_severity_level()
accepted_objs_by_severity = get_zero_severity_level()
- for finding in filters.get("all", []):
- iso_cal = finding.date.isocalendar()
+ # Optimization: Make all queries lists, and only pull values of fields for metrics based calculations
+ open_vulnerabilities = list(filters['open_vulns'].values('cwe', 'count'))
+ all_vulnerabilities = list(filters['all_vulns'].values('cwe', 'count'))
+
+ verified_objs_by_severity = list(filters.get('verified').values('severity'))
+ inactive_objs_by_severity = list(filters.get('inactive').values('severity'))
+ false_positive_objs_by_severity = list(filters.get('false_positive').values('severity'))
+ out_of_scope_objs_by_severity = list(filters.get('out_of_scope').values('severity'))
+ new_objs_by_severity = list(filters.get('new_verified').values('severity'))
+ all_objs_by_severity = list(filters.get('all').values('severity'))
+
+ all_findings = list(filters.get("all", []).values('id', 'date', 'severity'))
+ open_findings = list(filters.get("open", []).values('id', 'date', 'mitigated', 'severity'))
+ closed_findings = list(filters.get("closed", []).values('id', 'date', 'severity'))
+ accepted_findings = list(filters.get("accepted", []).values('id', 'date', 'severity'))
+
+ '''
+ Optimization: Create dictionaries in the structure of { finding_id: True } for index based search
+ Previously the for-loop below used "if finding in open_findings" -- an average O(n^2) time complexity
+ This allows for "if open_findings.get(finding_id, None)" -- an average O(n) time complexity
+ '''
+ open_findings_dict = {f.get('id'): True for f in open_findings}
+ closed_findings_dict = {f.get('id'): True for f in closed_findings}
+ accepted_findings_dict = {f.get('id'): True for f in accepted_findings}
+
+ for finding in all_findings:
+ iso_cal = finding.get('date').isocalendar()
date = iso_to_gregorian(iso_cal[0], iso_cal[1], 1)
html_date = date.strftime("%m/%d
%Y")
unix_timestamp = (tcalendar.timegm(date.timetuple()) * 1000)
# Open findings
- if finding in filters.get("open", []):
+ if open_findings_dict.get(finding.get('id', None), None):
if unix_timestamp not in critical_weekly:
critical_weekly[unix_timestamp] = {'count': 0, 'week': html_date}
if unix_timestamp not in high_weekly:
@@ -514,9 +549,15 @@ def view_product_metrics(request, pid):
open_close_weekly[unix_timestamp]['week'] = html_date
if view == 'Finding':
- severity = finding.severity
+ severity = finding.get('severity')
elif view == 'Endpoint':
- severity = finding.finding.severity
+ severity = finding.finding.get('severity')
+
+ finding_age = calculate_finding_age(finding)
+ if open_objs_by_age.get(finding_age, None):
+ open_objs_by_age[finding_age] += 1
+ else:
+ open_objs_by_age[finding_age] = 1
if unix_timestamp in severity_weekly:
if severity in severity_weekly[unix_timestamp]:
@@ -544,28 +585,33 @@ def view_product_metrics(request, pid):
else:
medium_weekly[unix_timestamp] = {'count': 1, 'week': html_date}
# Optimization: count severity level on server side
- if open_objs_by_severity.get(finding.severity) is not None:
- open_objs_by_severity[finding.severity] += 1
+ if open_objs_by_severity.get(finding.get('severity')) is not None:
+ open_objs_by_severity[finding.get('severity')] += 1
+
# Close findings
- if finding in filters.get("closed", []):
+ elif closed_findings_dict.get(finding.get('id', None), None):
if unix_timestamp in open_close_weekly:
open_close_weekly[unix_timestamp]['closed'] += 1
else:
open_close_weekly[unix_timestamp] = {'closed': 1, 'open': 0, 'accepted': 0}
open_close_weekly[unix_timestamp]['week'] = html_date
# Optimization: count severity level on server side
- if closed_objs_by_severity.get(finding.severity) is not None:
- closed_objs_by_severity[finding.severity] += 1
+ if closed_objs_by_severity.get(finding.get('severity')) is not None:
+ closed_objs_by_severity[finding.get('severity')] += 1
+
# Risk Accepted findings
- if finding in filters.get("accepted", []):
+ if accepted_findings_dict.get(finding.get('id', None), None):
if unix_timestamp in open_close_weekly:
open_close_weekly[unix_timestamp]['accepted'] += 1
else:
open_close_weekly[unix_timestamp] = {'closed': 0, 'open': 0, 'accepted': 1}
open_close_weekly[unix_timestamp]['week'] = html_date
# Optimization: count severity level on server side
- if accepted_objs_by_severity.get(finding.severity) is not None:
- accepted_objs_by_severity[finding.severity] += 1
+ if accepted_objs_by_severity.get(finding.get('severity')) is not None:
+ accepted_objs_by_severity[finding.get('severity')] += 1
+
+ tests = Test.objects.filter(engagement__product=prod).prefetch_related('finding_set', 'test_type')
+ tests = tests.annotate(verified_finding_count=Count('finding__id', filter=Q(finding__verified=True)))
test_data = {}
for t in tests:
@@ -574,9 +620,11 @@ def view_product_metrics(request, pid):
else:
test_data[t.test_type.name] = t.verified_finding_count
- product_tab = Product_Tab(prod, title=_("Product"), tab="metrics")
+ # Optimization: Format Open/Total CWE vulnerabilities graph data here, instead of template
+ open_vulnerabilities = [['CWE-' + str(f.get('cwe')), f.get('count')] for f in open_vulnerabilities]
+ all_vulnerabilities = [['CWE-' + str(f.get('cwe')), f.get('count')] for f in all_vulnerabilities]
- open_objs_by_age = {x: len([_ for _ in filters.get('open') if _.age == x]) for x in set([_.age for _ in filters.get('open')])}
+ product_tab = Product_Tab(prod, title=_("Product"), tab="metrics")
return render(request, 'dojo/product_metrics.html', {
'prod': prod,
@@ -584,28 +632,30 @@ def view_product_metrics(request, pid):
'engs': engs,
'inactive_engs': inactive_engs_page,
'view': view,
- 'verified_objs': filters.get('verified', None),
- 'verified_objs_by_severity': sum_by_severity_level(filters.get('verified')),
- 'open_objs': filters.get('open', None),
+ 'verified_objs': len(verified_objs_by_severity),
+ 'verified_objs_by_severity': sum_by_severity_level(verified_objs_by_severity),
+ 'open_objs': len(open_findings),
'open_objs_by_severity': open_objs_by_severity,
'open_objs_by_age': open_objs_by_age,
- 'inactive_objs': filters.get('inactive', None),
- 'inactive_objs_by_severity': sum_by_severity_level(filters.get('inactive')),
- 'closed_objs': filters.get('closed', None),
+ 'inactive_objs': len(inactive_objs_by_severity),
+ 'inactive_objs_by_severity': sum_by_severity_level(inactive_objs_by_severity),
+ 'closed_objs': len(closed_findings),
'closed_objs_by_severity': closed_objs_by_severity,
- 'false_positive_objs': filters.get('false_positive', None),
- 'false_positive_objs_by_severity': sum_by_severity_level(filters.get('false_positive')),
- 'out_of_scope_objs': filters.get('out_of_scope', None),
- 'out_of_scope_objs_by_severity': sum_by_severity_level(filters.get('out_of_scope')),
- 'accepted_objs': filters.get('accepted', None),
+ 'false_positive_objs': len(false_positive_objs_by_severity),
+ 'false_positive_objs_by_severity': sum_by_severity_level(false_positive_objs_by_severity),
+ 'out_of_scope_objs': len(out_of_scope_objs_by_severity),
+ 'out_of_scope_objs_by_severity': sum_by_severity_level(out_of_scope_objs_by_severity),
+ 'accepted_objs': len(accepted_findings),
'accepted_objs_by_severity': accepted_objs_by_severity,
- 'new_objs': filters.get('new_verified', None),
- 'new_objs_by_severity': sum_by_severity_level(filters.get('new_verified')),
- 'all_objs': filters.get('all', None),
- 'all_objs_by_severity': sum_by_severity_level(filters.get('all')),
+ 'new_objs': len(new_objs_by_severity),
+ 'new_objs_by_severity': sum_by_severity_level(new_objs_by_severity),
+ 'all_objs': len(all_objs_by_severity),
+ 'all_objs_by_severity': sum_by_severity_level(all_objs_by_severity),
'form': filters.get('form', None),
'reset_link': reverse('view_product_metrics', args=(prod.id,)) + '?type=' + view,
+ 'open_vulnerabilities_count': len(open_vulnerabilities),
'open_vulnerabilities': open_vulnerabilities,
+ 'all_vulnerabilities_count': len(all_vulnerabilities),
'all_vulnerabilities': all_vulnerabilities,
'start_date': start_date,
'punchcard': punchcard,
@@ -638,31 +688,36 @@ def async_burndown_metrics(request, pid):
@user_is_authorized(Product, Permissions.Engagement_View, 'pid')
def view_engagements(request, pid):
prod = get_object_or_404(Product, id=pid)
-
default_page_num = 10
recent_test_day_count = 7
-
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = ProductEngagementFilterWithoutObjectLookups if filter_string_matching else ProductEngagementFilter
# In Progress Engagements
engs = Engagement.objects.filter(product=prod, active=True, status="In Progress").order_by('-updated')
- active_engs_filter = ProductEngagementFilter(request.GET, queryset=engs, prefix='active')
+ active_engs_filter = filter_class(request.GET, queryset=engs, prefix='active')
result_active_engs = get_page_items(request, active_engs_filter.qs, default_page_num, prefix="engs")
- # prefetch only after creating the filters to avoid https://code.djangoproject.com/ticket/23771 and https://code.djangoproject.com/ticket/25375
- result_active_engs.object_list = prefetch_for_view_engagements(result_active_engs.object_list,
- recent_test_day_count)
-
+ # prefetch only after creating the filters to avoid https://code.djangoproject.com/ticket/23771
+ # and https://code.djangoproject.com/ticket/25375
+ result_active_engs.object_list = prefetch_for_view_engagements(
+ result_active_engs.object_list,
+ recent_test_day_count,
+ )
# Engagements that are queued because they haven't started or paused
engs = Engagement.objects.filter(~Q(status="In Progress"), product=prod, active=True).order_by('-updated')
- queued_engs_filter = ProductEngagementFilter(request.GET, queryset=engs, prefix='queued')
+ queued_engs_filter = filter_class(request.GET, queryset=engs, prefix='queued')
result_queued_engs = get_page_items(request, queued_engs_filter.qs, default_page_num, prefix="queued_engs")
- result_queued_engs.object_list = prefetch_for_view_engagements(result_queued_engs.object_list,
- recent_test_day_count)
-
+ result_queued_engs.object_list = prefetch_for_view_engagements(
+ result_queued_engs.object_list,
+ recent_test_day_count,
+ )
# Cancelled or Completed Engagements
engs = Engagement.objects.filter(product=prod, active=False).order_by('-target_end')
- inactive_engs_filter = ProductEngagementFilter(request.GET, queryset=engs, prefix='closed')
+ inactive_engs_filter = filter_class(request.GET, queryset=engs, prefix='closed')
result_inactive_engs = get_page_items(request, inactive_engs_filter.qs, default_page_num, prefix="inactive_engs")
- result_inactive_engs.object_list = prefetch_for_view_engagements(result_inactive_engs.object_list,
- recent_test_day_count)
+ result_inactive_engs.object_list = prefetch_for_view_engagements(
+ result_inactive_engs.object_list,
+ recent_test_day_count,
+ )
product_tab = Product_Tab(prod, title=_("All Engagements"), tab="engagements")
return render(request, 'dojo/view_engagements.html', {
diff --git a/dojo/reports/views.py b/dojo/reports/views.py
index 2eea646b74d..b6c3024bb0e 100644
--- a/dojo/reports/views.py
+++ b/dojo/reports/views.py
@@ -17,7 +17,7 @@
from django.views import View
from dojo.filters import ReportFindingFilter, EndpointReportFilter, \
- EndpointFilter
+ EndpointFilter, EndpointFilterWithoutObjectLookups
from dojo.forms import ReportOptionsForm
from dojo.models import Product_Type, Finding, Product, Engagement, Test, \
Dojo_User, Endpoint, Risk_Acceptance
@@ -63,8 +63,9 @@ def report_builder(request):
finding__duplicate=False,
finding__out_of_scope=False,
).distinct()
-
- endpoints = EndpointFilter(request.GET, queryset=endpoints, user=request.user)
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter
+ endpoints = filter_class(request.GET, queryset=endpoints, user=request.user)
in_use_widgets = [ReportOptions(request=request)]
available_widgets = [CoverPage(request=request),
diff --git a/dojo/reports/widgets.py b/dojo/reports/widgets.py
index 36831c4ad0c..fea53696673 100644
--- a/dojo/reports/widgets.py
+++ b/dojo/reports/widgets.py
@@ -11,10 +11,10 @@
from django.utils.html import format_html
from django.utils.safestring import mark_safe
-from dojo.filters import EndpointFilter, ReportFindingFilter
+from dojo.filters import EndpointFilter, EndpointFilterWithoutObjectLookups, ReportFindingFilter
from dojo.forms import CustomReportOptionsForm
from dojo.models import Endpoint, Finding
-from dojo.utils import get_page_items, get_words_for_field
+from dojo.utils import get_page_items, get_words_for_field, get_system_setting
"""
Widgets are content sections that can be included on reports. The report builder will allow any number of widgets
@@ -407,7 +407,9 @@ def report_widget_factory(json_data=None, request=None, user=None, finding_notes
d[item['name']] = item['value']
endpoints = Endpoint.objects.filter(id__in=endpoints)
- endpoints = EndpointFilter(d, queryset=endpoints, user=request.user)
+ filter_string_matching = get_system_setting("filter_string_matching", False)
+ filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter
+ endpoints = filter_class(d, queryset=endpoints, user=request.user)
user_id = user.id if user is not None else None
endpoints = EndpointList(request=request, endpoints=endpoints, finding_notes=finding_notes,
finding_images=finding_images, host=host, user_id=user_id)
diff --git a/dojo/templates/dojo/edit_finding.html b/dojo/templates/dojo/edit_finding.html
index dc52e0fd2f0..60e29494b11 100644
--- a/dojo/templates/dojo/edit_finding.html
+++ b/dojo/templates/dojo/edit_finding.html
@@ -208,7 +208,7 @@