diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index cbc61ff212..d0fe775b07 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -59,6 +59,7 @@ ApiTemplateFindingFilter, ApiTestFilter, ReportFindingFilter, + ReportFindingFilterWithoutObjectLookups, ) from dojo.finding.queries import ( get_authorized_findings, @@ -2866,13 +2867,15 @@ def report_generate(request, obj, options): include_finding_images = options.get("include_finding_images", False) include_executive_summary = options.get("include_executive_summary", False) include_table_of_contents = options.get("include_table_of_contents", False) + filter_string_matching = get_system_setting("filter_string_matching", False) + report_finding_filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter if type(obj).__name__ == "Product_Type": product_type = obj report_name = "Product Type Report: " + str(product_type) - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, prod_type=product_type, queryset=prefetch_related_findings_for_report( @@ -2901,7 +2904,7 @@ def report_generate(request, obj, options): report_name = "Product Report: " + str(product) - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, product=product, queryset=prefetch_related_findings_for_report( @@ -2915,7 +2918,7 @@ def report_generate(request, obj, options): elif type(obj).__name__ == "Engagement": engagement = obj - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, engagement=engagement, queryset=prefetch_related_findings_for_report( @@ -2932,7 +2935,7 @@ def report_generate(request, obj, options): elif type(obj).__name__ == "Test": test = obj - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, engagement=test.engagement, queryset=prefetch_related_findings_for_report( @@ -2948,7 +2951,7 @@ def report_generate(request, obj, options): endpoints = Endpoint.objects.filter( host=host, product=endpoint.product ).distinct() - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, queryset=prefetch_related_findings_for_report( Finding.objects.filter(endpoints__in=endpoints) @@ -2956,7 +2959,7 @@ def report_generate(request, obj, options): ) elif type(obj).__name__ == "CastTaggedQuerySet": - findings = ReportFindingFilter( + findings = report_finding_filter_class( request.GET, queryset=prefetch_related_findings_for_report(obj).distinct(), ) diff --git a/dojo/filters.py b/dojo/filters.py index 0b12cc3961..9d3a94a43d 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -595,6 +595,10 @@ class FindingTagStringFilter(FilterSet): help_text="Search for tags on a Product that are an exact match, and exclude them", exclude=True) + def delete_tags_from_form(self, tag_list: list): + for tag in tag_list: + self.form.fields.pop(tag, None) + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -1716,12 +1720,12 @@ class FindingFilterWithoutObjectLookups(FindingFilterHelper, FindingTagStringFil label="Engagement name Contains", help_text="Search for Engagement names that contain a given pattern") test__name = CharFilter( - field_name="test__engagement__name", + field_name="test__name", lookup_expr="iexact", label="Test Name", help_text="Search for Test names that are an exact match") test__name_contains = CharFilter( - field_name="test__engagement__name", + field_name="test__name", lookup_expr="icontains", label="Test name Contains", help_text="Search for Test names that contain a given pattern") @@ -2877,42 +2881,31 @@ class Meta: exclude = ['product'] -class ReportFindingFilter(FindingTagFilter): +class ReportFindingFilterHelper(FilterSet): title = CharFilter(lookup_expr='icontains', label='Name') date = DateFromToRangeFilter(field_name='date', label="Date Discovered") - test__engagement__product = ModelMultipleChoiceFilter( - queryset=Product.objects.none(), label="Product") - test__engagement__product__prod_type = ModelMultipleChoiceFilter( - queryset=Product_Type.objects.none(), - label="Product Type") - test__engagement__product__lifecycle = MultipleChoiceFilter(choices=Product.LIFECYCLE_CHOICES, label="Product Lifecycle") - test__engagement = ModelMultipleChoiceFilter(queryset=Engagement.objects.none(), label="Engagement") severity = MultipleChoiceFilter(choices=SEVERITY_CHOICES) active = ReportBooleanFilter() is_mitigated = ReportBooleanFilter() mitigated = DateRangeFilter(label="Mitigated Date") verified = ReportBooleanFilter() false_p = ReportBooleanFilter(label="False Positive") - risk_acceptance = ReportRiskAcceptanceFilter( - label="Risk Accepted") - # queryset will be restricted in __init__, here we don't have access to the logged in user + risk_acceptance = ReportRiskAcceptanceFilter(label="Risk Accepted") duplicate = ReportBooleanFilter() - duplicate_finding = ModelChoiceFilter(queryset=Finding.objects.filter(original_finding__isnull=False).distinct()) out_of_scope = ReportBooleanFilter() outside_of_sla = FindingSLAFilter(label="Outside of SLA") - file_path = CharFilter(lookup_expr='icontains') class Meta: model = Finding # exclude sonarqube issue as by default it will show all without checking permissions exclude = ['date', 'cwe', 'url', 'description', 'mitigation', 'impact', - 'references', 'sonarqube_issue', - 'thread_id', 'notes', + 'references', 'sonarqube_issue', 'duplicate_finding', + 'thread_id', 'notes', 'inherited_tags', 'endpoints', 'numerical_severity', 'reporter', 'last_reviewed', 'jira_creation', 'jira_change', 'files'] - def __init__(self, *args, **kwargs): + def manage_kwargs(self, kwargs): self.prod_type = None self.product = None self.engagement = None @@ -2926,6 +2919,24 @@ def __init__(self, *args, **kwargs): if 'test' in kwargs: self.test = kwargs.pop('test') + @property + def qs(self): + parent = super().qs + return get_authorized_findings(Permissions.Finding_View, parent) + + +class ReportFindingFilter(ReportFindingFilterHelper, FindingTagFilter): + test__engagement__product = ModelMultipleChoiceFilter( + queryset=Product.objects.none(), label="Product") + test__engagement__product__prod_type = ModelMultipleChoiceFilter( + queryset=Product_Type.objects.none(), + label="Product Type") + test__engagement__product__lifecycle = MultipleChoiceFilter(choices=Product.LIFECYCLE_CHOICES, label="Product Lifecycle") + test__engagement = ModelMultipleChoiceFilter(queryset=Engagement.objects.none(), label="Engagement") + duplicate_finding = ModelChoiceFilter(queryset=Finding.objects.filter(original_finding__isnull=False).distinct()) + + def __init__(self, *args, **kwargs): + self.manage_kwargs(kwargs) super().__init__(*args, **kwargs) # duplicate_finding queryset needs to restricted in line with permissions @@ -2961,10 +2972,161 @@ def __init__(self, *args, **kwargs): if 'test__engagement' in self.form.fields: self.form.fields['test__engagement'].queryset = get_authorized_engagements(Permissions.Engagement_View) - @property - def qs(self): - parent = super().qs - return get_authorized_findings(Permissions.Finding_View, parent) + +class ReportFindingFilterWithoutObjectLookups(ReportFindingFilterHelper, FindingTagStringFilter): + test__engagement__product__prod_type = NumberFilter(widget=HiddenInput()) + test__engagement__product = NumberFilter(widget=HiddenInput()) + test__engagement = NumberFilter(widget=HiddenInput()) + test = NumberFilter(widget=HiddenInput()) + endpoint = NumberFilter(widget=HiddenInput()) + reporter = CharFilter( + field_name="reporter__username", + lookup_expr="iexact", + label="Reporter Username", + help_text="Search for Reporter names that are an exact match") + reporter_contains = CharFilter( + field_name="reporter__username", + lookup_expr="icontains", + label="Reporter Username Contains", + help_text="Search for Reporter names that contain a given pattern") + reviewers = CharFilter( + field_name="reviewers__username", + lookup_expr="iexact", + label="Reviewer Username", + help_text="Search for Reviewer names that are an exact match") + reviewers_contains = CharFilter( + field_name="reviewers__username", + lookup_expr="icontains", + label="Reviewer Username Contains", + help_text="Search for Reviewer usernames that contain a given pattern") + last_reviewed_by = CharFilter( + field_name="last_reviewed_by__username", + lookup_expr="iexact", + label="Last Reviewed By Username", + help_text="Search for Last Reviewed By names that are an exact match") + last_reviewed_by_contains = CharFilter( + field_name="last_reviewed_by__username", + lookup_expr="icontains", + label="Last Reviewed By Username Contains", + help_text="Search for Last Reviewed By usernames that contain a given pattern") + review_requested_by = CharFilter( + field_name="review_requested_by__username", + lookup_expr="iexact", + label="Review Requested By Username", + help_text="Search for Review Requested By names that are an exact match") + review_requested_by_contains = CharFilter( + field_name="review_requested_by__username", + lookup_expr="icontains", + label="Review Requested By Username Contains", + help_text="Search for Review Requested By usernames that contain a given pattern") + mitigated_by = CharFilter( + field_name="mitigated_by__username", + lookup_expr="iexact", + label="Mitigator Username", + help_text="Search for Mitigator names that are an exact match") + mitigated_by_contains = CharFilter( + field_name="mitigated_by__username", + lookup_expr="icontains", + label="Mitigator Username Contains", + help_text="Search for Mitigator usernames that contain a given pattern") + defect_review_requested_by = CharFilter( + field_name="defect_review_requested_by__username", + lookup_expr="iexact", + label="Requester of Defect Review Username", + help_text="Search for Requester of Defect Review names that are an exact match") + defect_review_requested_by_contains = CharFilter( + field_name="defect_review_requested_by__username", + lookup_expr="icontains", + label="Requester of Defect Review Username Contains", + help_text="Search for Requester of Defect Review usernames that contain a given pattern") + 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") + test__engagement__name = CharFilter( + field_name="test__engagement__name", + lookup_expr="iexact", + label="Engagement Name", + help_text="Search for Engagement names that are an exact match") + test__engagement__name_contains = CharFilter( + field_name="test__engagement__name", + lookup_expr="icontains", + label="Engagement name Contains", + help_text="Search for Engagement names that contain a given pattern") + test__name = CharFilter( + field_name="test__name", + lookup_expr="iexact", + label="Test Name", + help_text="Search for Test names that are an exact match") + test__name_contains = CharFilter( + field_name="test__name", + lookup_expr="icontains", + label="Test name Contains", + help_text="Search for Test names that contain a given pattern") + + def __init__(self, *args, **kwargs): + self.manage_kwargs(kwargs) + super().__init__(*args, **kwargs) + + product_type_refs = [ + "test__engagement__product__prod_type__name", + "test__engagement__product__prod_type__name_contains", + ] + product_refs = [ + "test__engagement__product__name", + "test__engagement__product__name_contains", + "test__engagement__product__tags", + "test__engagement__product__tags_contains", + "not_test__engagement__product__tags", + "not_test__engagement__product__tags_contains", + ] + engagement_refs = [ + "test__engagement__name", + "test__engagement__name_contains", + "test__engagement__tags", + "test__engagement__tags_contains", + "not_test__engagement__tags", + "not_test__engagement__tags_contains", + ] + test_refs = [ + "test__name", + "test__name_contains", + "test__tags", + "test__tags_contains", + "not_test__tags", + "not_test__tags_contains", + ] + + if self.test: + self.delete_tags_from_form(product_type_refs) + self.delete_tags_from_form(product_refs) + self.delete_tags_from_form(engagement_refs) + self.delete_tags_from_form(test_refs) + elif self.engagement: + self.delete_tags_from_form(product_type_refs) + self.delete_tags_from_form(product_refs) + self.delete_tags_from_form(engagement_refs) + elif self.product: + self.delete_tags_from_form(product_type_refs) + self.delete_tags_from_form(product_refs) + elif self.prod_type: + self.delete_tags_from_form(product_type_refs) class UserFilter(DojoFilter): diff --git a/dojo/jira_link/views.py b/dojo/jira_link/views.py index 4e2a033305..6ae03f8d4c 100644 --- a/dojo/jira_link/views.py +++ b/dojo/jira_link/views.py @@ -241,8 +241,10 @@ def check_for_and_create_comment(parsed_json): findings = [jissue.finding] create_notification(event='jira_comment', 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='jira_comment', title=f'JIRA incoming comment - {jissue.finding}', finding=jissue.finding, url=reverse("view_finding_group", args=(jissue.finding_group.id,)), icon='check') + findings = jissue.finding_group.findings.all() + first_finding_group = findings.first() + if first_finding_group: + create_notification(event='jira_comment', title=f'JIRA incoming comment - {jissue.finding_group}', finding=first_finding_group, url=reverse("view_finding_group", args=(jissue.finding_group.id,)), icon='check') elif jissue.engagement: return webhook_responser_handler("debug", "Comment for engagement ignored") else: diff --git a/dojo/reports/views.py b/dojo/reports/views.py index a810214276..b815c81eca 100644 --- a/dojo/reports/views.py +++ b/dojo/reports/views.py @@ -18,7 +18,13 @@ from dojo.authorization.authorization import user_has_permission_or_403 from dojo.authorization.authorization_decorators import user_is_authorized from dojo.authorization.roles_permissions import Permissions -from dojo.filters import EndpointFilter, EndpointFilterWithoutObjectLookups, EndpointReportFilter, ReportFindingFilter +from dojo.filters import ( + EndpointFilter, + EndpointFilterWithoutObjectLookups, + EndpointReportFilter, + ReportFindingFilter, + ReportFindingFilterWithoutObjectLookups, +) from dojo.finding.queries import get_authorized_findings from dojo.finding.views import BaseListFindings from dojo.forms import ReportOptionsForm @@ -73,7 +79,9 @@ def get(self, request: HttpRequest) -> HttpResponse: def get_findings(self, request: HttpRequest): findings = get_authorized_findings(Permissions.Finding_View) - return ReportFindingFilter(self.request.GET, queryset=findings) + filter_string_matching = get_system_setting("filter_string_matching", False) + filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter + return filter_class(self.request.GET, queryset=findings) def get_endpoints(self, request: HttpRequest): endpoints = Endpoint.objects.filter(finding__active=True, @@ -162,8 +170,9 @@ def get_context(self): def report_findings(request): findings = Finding.objects.filter() - - findings = ReportFindingFilter(request.GET, queryset=findings) + filter_string_matching = get_system_setting("filter_string_matching", False) + filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter + findings = filter_class(request.GET, queryset=findings) title_words = get_words_for_field(Finding, 'title') component_words = get_words_for_field(Finding, 'component_name') @@ -415,14 +424,15 @@ def generate_report(request, obj, host_view=False): disclaimer = 'Please configure in System Settings.' generate = "_generate" in request.GET report_name = str(obj) + filter_string_matching = get_system_setting("filter_string_matching", False) + report_finding_filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter add_breadcrumb(title="Generate Report", top_level=False, request=request) if type(obj).__name__ == "Product_Type": product_type = obj template = "dojo/product_type_pdf_report.html" report_name = "Product Type Report: " + str(product_type) report_title = "Product Type Report" - - findings = ReportFindingFilter(request.GET, prod_type=product_type, queryset=prefetch_related_findings_for_report(Finding.objects.filter( + findings = report_finding_filter_class(request.GET, prod_type=product_type, queryset=prefetch_related_findings_for_report(Finding.objects.filter( test__engagement__product__prod_type=product_type))) products = Product.objects.filter(prod_type=product_type, engagement__test__finding__in=findings.qs).distinct() @@ -472,7 +482,7 @@ def generate_report(request, obj, host_view=False): template = "dojo/product_pdf_report.html" report_name = "Product Report: " + str(product) report_title = "Product Report" - findings = ReportFindingFilter(request.GET, product=product, queryset=prefetch_related_findings_for_report(Finding.objects.filter( + findings = report_finding_filter_class(request.GET, product=product, queryset=prefetch_related_findings_for_report(Finding.objects.filter( test__engagement__product=product))) ids = set(finding.id for finding in findings.qs) # noqa: C401 engagements = Engagement.objects.filter(test__finding__id__in=ids).distinct() @@ -499,7 +509,7 @@ def generate_report(request, obj, host_view=False): elif type(obj).__name__ == "Engagement": logger.debug('generating report for Engagement') engagement = obj - findings = ReportFindingFilter(request.GET, engagement=engagement, + findings = report_finding_filter_class(request.GET, engagement=engagement, queryset=prefetch_related_findings_for_report(Finding.objects.filter(test__engagement=engagement))) report_name = "Engagement Report: " + str(engagement) template = 'dojo/engagement_pdf_report.html' @@ -528,7 +538,7 @@ def generate_report(request, obj, host_view=False): elif type(obj).__name__ == "Test": test = obj - findings = ReportFindingFilter(request.GET, engagement=test.engagement, + findings = report_finding_filter_class(request.GET, engagement=test.engagement, queryset=prefetch_related_findings_for_report(Finding.objects.filter(test=test))) template = "dojo/test_pdf_report.html" report_name = "Test Report: " + str(test) @@ -561,7 +571,7 @@ def generate_report(request, obj, host_view=False): endpoints = Endpoint.objects.filter(pk=endpoint.id).distinct() report_title = "Endpoint Report" template = 'dojo/endpoint_pdf_report.html' - findings = ReportFindingFilter(request.GET, + findings = report_finding_filter_class(request.GET, queryset=prefetch_related_findings_for_report(Finding.objects.filter(endpoints__in=endpoints))) context = {'endpoint': endpoint, @@ -580,7 +590,7 @@ def generate_report(request, obj, host_view=False): 'host': report_url_resolver(request), 'user_id': request.user.id} elif type(obj).__name__ in ["QuerySet", "CastTaggedQuerySet", "TagulousCastTaggedQuerySet"]: - findings = ReportFindingFilter(request.GET, queryset=prefetch_related_findings_for_report(obj).distinct()) + findings = report_finding_filter_class(request.GET, queryset=prefetch_related_findings_for_report(obj).distinct()) report_name = 'Finding' template = 'dojo/finding_pdf_report.html' report_title = "Finding Report" diff --git a/dojo/reports/widgets.py b/dojo/reports/widgets.py index 665b7758b4..b6593a6a0d 100644 --- a/dojo/reports/widgets.py +++ b/dojo/reports/widgets.py @@ -11,7 +11,12 @@ from django.utils.html import format_html from django.utils.safestring import mark_safe -from dojo.filters import EndpointFilter, EndpointFilterWithoutObjectLookups, ReportFindingFilter +from dojo.filters import ( + EndpointFilter, + EndpointFilterWithoutObjectLookups, + ReportFindingFilter, + ReportFindingFilterWithoutObjectLookups, +) from dojo.forms import CustomReportOptionsForm from dojo.models import Endpoint, Finding from dojo.utils import get_page_items, get_system_setting, get_words_for_field @@ -427,8 +432,9 @@ def report_widget_factory(json_data=None, request=None, user=None, finding_notes d.appendlist(item['name'], item['value']) else: d[item['name']] = item['value'] - - findings = ReportFindingFilter(d, queryset=findings) + filter_string_matching = get_system_setting("filter_string_matching", False) + filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter + findings = filter_class(d, queryset=findings) user_id = user.id if user is not None else None selected_widgets[list(widget.keys())[0] + '-' + str(idx)] = FindingList(request=request, findings=findings, finding_notes=finding_notes, diff --git a/dojo/templates/dojo/report_filter_snippet.html b/dojo/templates/dojo/report_filter_snippet.html index de1c68f58e..8018544457 100644 --- a/dojo/templates/dojo/report_filter_snippet.html +++ b/dojo/templates/dojo/report_filter_snippet.html @@ -5,7 +5,10 @@ {% endblock %}