From 85d3dc8795b3c873287ca782ab927c3480d38345 Mon Sep 17 00:00:00 2001 From: Blake Owens <76979297+blakeaowens@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:56:01 -0500 Subject: [PATCH] addition of has_tags and product/finding sla filters (#8549) * addition of has_tags and product/finding sla filters * fix failing tests * fix random flake8 error regarding type() --- dojo/filters.py | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ dojo/models.py | 23 ++++++++++--- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/dojo/filters.py b/dojo/filters.py index 7b249c53b4..40f08f7938 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -142,6 +142,68 @@ def filter(self, qs, value): return self.options[value][1](self, qs, self.field_name) +class FindingSLAFilter(ChoiceFilter): + def any(self, qs, name): + return qs + + def satisfies_sla(self, qs, name): + non_sla_violations = [finding.id for finding in qs if not finding.violates_sla] + return Finding.objects.filter(id__in=non_sla_violations) + + def violates_sla(self, qs, name): + sla_violations = [finding.id for finding in qs if finding.violates_sla] + return Finding.objects.filter(id__in=sla_violations) + + options = { + None: (_('Any'), any), + 0: (_('False'), satisfies_sla), + 1: (_('True'), violates_sla), + } + + def __init__(self, *args, **kwargs): + kwargs['choices'] = [ + (key, value[0]) for key, value in six.iteritems(self.options)] + super(FindingSLAFilter, self).__init__(*args, **kwargs) + + def filter(self, qs, value): + try: + value = int(value) + except (ValueError, TypeError): + value = None + return self.options[value][1](self, qs, self.field_name) + + +class ProductSLAFilter(ChoiceFilter): + def any(self, qs, name): + return qs + + def satisfies_sla(self, qs, name): + non_sla_violations = [product.id for product in qs if not product.violates_sla] + return Product.objects.filter(id__in=non_sla_violations) + + def violates_sla(self, qs, name): + sla_violations = [product.id for product in qs if product.violates_sla] + return Product.objects.filter(id__in=sla_violations) + + options = { + None: (_('Any'), any), + 0: (_('False'), satisfies_sla), + 1: (_('True'), violates_sla), + } + + def __init__(self, *args, **kwargs): + kwargs['choices'] = [ + (key, value[0]) for key, value in six.iteritems(self.options)] + super(ProductSLAFilter, self).__init__(*args, **kwargs) + + def filter(self, qs, value): + try: + value = int(value) + except (ValueError, TypeError): + value = None + return self.options[value][1](self, qs, self.field_name) + + def get_earliest_finding(queryset=None): if queryset is None: # don't to 'if not queryset' which will trigger the query queryset = Finding.objects.all() @@ -683,6 +745,8 @@ class EngagementDirectFilter(DojoFilter): 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=( @@ -745,6 +809,8 @@ class EngagementFilter(DojoFilter): 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=( @@ -842,6 +908,7 @@ class ApiEngagementFilter(DojoFilter): lookup_expr='in', help_text='Comma separated list of exact tags not present on product', exclude='True') + has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags') o = OrderingFilter( # tuple-mapping retains order @@ -966,6 +1033,10 @@ class ProductFilter(DojoFilter): 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=( @@ -1040,6 +1111,8 @@ class ApiProductFilter(DojoFilter): not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', help_text='Not Tag name contains', exclude='True') not_tags = CharFieldInFilter(field_name='tags__name', lookup_expr='in', help_text='Comma separated list of exact tags not present on product', exclude='True') + has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags') + outside_of_sla = extend_schema_field(OpenApiTypes.NUMBER)(ProductSLAFilter()) # DateRangeFilter created = DateRangeFilter() @@ -1166,6 +1239,8 @@ class ApiFindingFilter(DojoFilter): lookup_expr='in', help_text='Comma separated list of exact tags not present on product', exclude='True') + has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags') + outside_of_sla = extend_schema_field(OpenApiTypes.NUMBER)(ProductSLAFilter()) o = OrderingFilter( # tuple-mapping retains order @@ -1336,6 +1411,10 @@ class FindingFilter(FindingFilterWithTags): not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', label='Not tag name contains', exclude=True) + outside_of_sla = FindingSLAFilter(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=( @@ -1770,6 +1849,8 @@ class EndpointFilter(DojoFilter): 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=( @@ -1803,6 +1884,8 @@ class ApiEndpointFilter(DojoFilter): not_tag = CharFilter(field_name='tags__name', lookup_expr='icontains', help_text='Not Tag name contains', exclude='True') not_tags = CharFieldInFilter(field_name='tags__name', lookup_expr='in', help_text='Comma separated list of exact tags not present on model', exclude='True') + has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags') + o = OrderingFilter( # tuple-mapping retains order fields=( @@ -1863,6 +1946,8 @@ class EngagementTestFilter(DojoFilter): 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=( @@ -1914,6 +1999,7 @@ class ApiTestFilter(DojoFilter): lookup_expr='in', help_text='Comma separated list of exact tags not present on product', exclude='True') + has_tags = BooleanFilter(field_name='tags', lookup_expr='isnull', exclude=True, label='Has tags') o = OrderingFilter( # tuple-mapping retains order diff --git a/dojo/models.py b/dojo/models.py index a5009500fc..8db4247a67 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -1116,8 +1116,7 @@ def get_product_type(self): @cached_property def open_findings_list(self): findings = Finding.objects.filter(test__engagement__product=self, - active=True, - ) + active=True) findings_list = [] for i in findings: findings_list.append(i.id) @@ -1132,6 +1131,15 @@ def get_absolute_url(self): from django.urls import reverse return reverse('view_product', args=[str(self.id)]) + @property + def violates_sla(self): + findings = Finding.objects.filter(test__engagement__product=self, + active=True) + for f in findings: + if f.violates_sla: + return True + return False + class Product_Member(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE) @@ -3019,6 +3027,11 @@ def inherit_tags(self, potentially_existing_tags): incoming_inherited_tags = [tag.name for tag in self.test.engagement.product.tags.all()] _manage_inherited_tags(self, incoming_inherited_tags, potentially_existing_tags=potentially_existing_tags) + @property + def violates_sla(self): + days_remaining = self.sla_days_remaining() + return days_remaining < 0 if days_remaining else False + class FindingAdmin(admin.ModelAdmin): # For efficiency with large databases, display many-to-many fields with raw @@ -3649,11 +3662,11 @@ class JIRA_Issue(models.Model): help_text=_("The date the linked Jira issue was last modified.")) def set_obj(self, obj): - if type(obj) == Finding: + if isinstance(obj, Finding): self.finding = obj - elif type(obj) == Finding_Group: + elif isinstance(obj, Finding_Group): self.finding_group = obj - elif type(obj) == Engagement: + elif isinstance(obj, Engagement): self.engagement = obj else: raise ValueError('unknown object type while creating JIRA_Issue: %s' % to_str_typed(obj))