Skip to content

Commit

Permalink
finding sla expiration date field (part one) (#9473)
Browse files Browse the repository at this point in the history
* addition of sla expiration date field on the finding model

* add migration and fix indentation issue

* fix mitigated finding remaining sla days calculation

* fix sla violation filter to return only active, sla violating findings

* migration system settings fix

* fix mitigation date vs datetime discrepancy

* fix breaking unit test

* move product save check to signal

* fix unit test failure

* make signal operations async, fix sla config delete 500 error

* add unit tests to test sla expiration date functionality

* restarting without signals

* add async updating flags, redo migration

* move signal logic to overriden save

* fix errors for non-existing objects at creation

* clean up comments and a few logical expressions

* fix flake8 error

* addition of new unit tests

* fix unit test error

* add message to form fields when async updating flag is true

* fix save location, reword form messages, reword redirect messages

* remove commented lines from unit tests

* add a bit more description to API validation errors

* migration fix

* migration performance improvements

* fix datetime - str comparison issue

* clean up for part one of sla expiration date field

* fix flake8

* Update dojo/db_migrations/0200_finding_sla_expiration_date_product_async_updating_and_more.py

Co-authored-by: Charles Neill <[email protected]>

* Update dojo/models.py

Co-authored-by: Charles Neill <[email protected]>

---------

Co-authored-by: Charles Neill <[email protected]>
  • Loading branch information
blakeaowens and cneill authored Feb 5, 2024
1 parent 3e81d6d commit 19ecb49
Show file tree
Hide file tree
Showing 16 changed files with 454 additions and 65 deletions.
28 changes: 27 additions & 1 deletion dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2004,8 +2004,20 @@ class Meta:
exclude = (
"tid",
"updated",
"async_updating"
)

def validate(self, data):
async_updating = getattr(self.instance, 'async_updating', None)
if async_updating:
new_sla_config = data.get('sla_configuration', None)
old_sla_config = getattr(self.instance, 'sla_configuration', None)
if new_sla_config and old_sla_config and new_sla_config != old_sla_config:
raise serializers.ValidationError(
'Finding SLA expiration dates are currently being recalculated. The SLA configuration for this product cannot be changed until the calculation is complete.'
)
return data

def get_findings_count(self, obj) -> int:
return obj.findings_count

Expand Down Expand Up @@ -3031,7 +3043,21 @@ class Meta:
class SLAConfigurationSerializer(serializers.ModelSerializer):
class Meta:
model = SLA_Configuration
fields = "__all__"
exclude = (
"async_updating",
)

def validate(self, data):
async_updating = getattr(self.instance, 'async_updating', None)
if async_updating:
for field in ['critical', 'high', 'medium', 'low']:
old_days = getattr(self.instance, field, None)
new_days = data.get(field, None)
if old_days and new_days and (old_days != new_days):
raise serializers.ValidationError(
'Finding SLA expiration dates are currently being calculated. The SLA days for this SLA configuration cannot be changed until the calculation is complete.'
)
return data


class UserProfileSerializer(serializers.Serializer):
Expand Down
1 change: 1 addition & 0 deletions dojo/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def ready(self):
import dojo.announcement.signals # noqa
import dojo.product.signals # noqa
import dojo.test.signals # noqa
import dojo.sla_config.helpers # noqa


def get_model_fields_with_extra(model, extra_fields=()):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.1.13 on 2024-01-17 03:07

from django.db import migrations, models
import logging

logger = logging.getLogger(__name__)


class Migration(migrations.Migration):

dependencies = [
('dojo', '0199_whitesource_to_mend'),
]

operations = [
migrations.AddField(
model_name='finding',
name='sla_expiration_date',
field=models.DateField(blank=True, help_text="(readonly)The date SLA expires for this finding. Empty by default, causing a fallback to 'date'.", null=True, verbose_name='SLA Expiration Date'),
),
migrations.AddField(
model_name='product',
name='async_updating',
field=models.BooleanField(default=False, help_text='Findings under this Product or SLA configuration are asynchronously being updated'),
),
migrations.AddField(
model_name='sla_configuration',
name='async_updating',
field=models.BooleanField(default=False, help_text='Findings under this SLA configuration are asynchronously being updated'),
),
]
21 changes: 10 additions & 11 deletions dojo/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,22 +147,22 @@ class FindingSLAFilter(ChoiceFilter):
def any(self, qs, name):
return qs

def satisfies_sla(self, qs, name):
def sla_satisfied(self, qs, name):
for finding in qs:
if finding.violates_sla:
qs = qs.exclude(id=finding.id)
return qs

def violates_sla(self, qs, name):
def sla_violated(self, qs, name):
for finding in qs:
if not finding.violates_sla:
qs = qs.exclude(id=finding.id)
return qs

options = {
None: (_('Any'), any),
0: (_('False'), satisfies_sla),
1: (_('True'), violates_sla),
0: (_('False'), sla_satisfied),
1: (_('True'), sla_violated),
}

def __init__(self, *args, **kwargs):
Expand All @@ -182,22 +182,22 @@ class ProductSLAFilter(ChoiceFilter):
def any(self, qs, name):
return qs

def satisfies_sla(self, qs, name):
def sla_satisifed(self, qs, name):
for product in qs:
if product.violates_sla:
qs = qs.exclude(id=product.id)
return qs

def violates_sla(self, qs, name):
def sla_violated(self, qs, name):
for product in qs:
if not product.violates_sla:
qs = qs.exclude(id=product.id)
return qs

options = {
None: (_('Any'), any),
0: (_('False'), satisfies_sla),
1: (_('True'), violates_sla),
0: (_('False'), sla_satisifed),
1: (_('True'), sla_violated),
}

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -1465,9 +1465,8 @@ class Meta:
'endpoints', 'references',
'thread_id', 'notes', 'scanner_confidence',
'numerical_severity', 'line', 'duplicate_finding',
'hash_code',
'reviewers',
'created', 'files', 'sla_start_date', 'cvssv3',
'hash_code', 'reviewers', 'created', 'files',
'sla_start_date', 'sla_expiration_date', 'cvssv3',
'severity_justification', 'steps_to_reproduce']

def __init__(self, *args, **kwargs):
Expand Down
40 changes: 32 additions & 8 deletions dojo/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['prod_type'].queryset = get_authorized_product_types(Permissions.Product_Type_Add_Product)

# if this product has findings being asynchronously updated, disable the sla config field
if self.instance.async_updating:
self.fields['sla_configuration'].disabled = True
self.fields['sla_configuration'].widget.attrs['message'] = 'Finding SLA expiration dates are currently being recalculated. ' + \
'This field cannot be changed until the calculation is complete.'

class Meta:
model = Product
fields = ['name', 'description', 'tags', 'product_manager', 'technical_contact', 'team_manager', 'prod_type', 'sla_configuration', 'regulations',
Expand Down Expand Up @@ -1073,7 +1079,7 @@ class AdHocFindingForm(forms.ModelForm):
# the only reliable way without hacking internal fields to get predicatble ordering is to make it explicit
field_order = ('title', 'date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3', 'description', 'mitigation', 'impact', 'request', 'response', 'steps_to_reproduce',
'severity_justification', 'endpoints', 'endpoints_to_add', 'references', 'active', 'verified', 'false_p', 'duplicate', 'out_of_scope',
'risk_accepted', 'under_defect_review', 'sla_start_date')
'risk_accepted', 'under_defect_review', 'sla_start_date', 'sla_expiration_date')

def __init__(self, *args, **kwargs):
req_resp = kwargs.pop('req_resp')
Expand Down Expand Up @@ -1113,7 +1119,8 @@ def clean(self):
class Meta:
model = Finding
exclude = ('reporter', 'url', 'numerical_severity', 'under_review', 'reviewers', 'cve', 'inherited_tags',
'review_requested_by', 'is_mitigated', 'jira_creation', 'jira_change', 'endpoint_status', 'sla_start_date')
'review_requested_by', 'is_mitigated', 'jira_creation', 'jira_change', 'endpoints', 'sla_start_date',
'sla_expiration_date')


class PromoteFindingForm(forms.ModelForm):
Expand All @@ -1139,9 +1146,9 @@ class PromoteFindingForm(forms.ModelForm):
references = forms.CharField(widget=forms.Textarea, required=False)

# the onyl reliable way without hacking internal fields to get predicatble ordering is to make it explicit
field_order = ('title', 'group', 'date', 'sla_start_date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3', 'cvssv3_score', 'description', 'mitigation', 'impact',
'request', 'response', 'steps_to_reproduce', 'severity_justification', 'endpoints', 'endpoints_to_add', 'references',
'active', 'mitigated', 'mitigated_by', 'verified', 'false_p', 'duplicate',
field_order = ('title', 'group', 'date', 'sla_start_date', 'sla_expiration_date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3',
'cvssv3_score', 'description', 'mitigation', 'impact', 'request', 'response', 'steps_to_reproduce', 'severity_justification',
'endpoints', 'endpoints_to_add', 'references', 'active', 'mitigated', 'mitigated_by', 'verified', 'false_p', 'duplicate',
'out_of_scope', 'risk_accept', 'under_defect_review')

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -1211,9 +1218,9 @@ class FindingForm(forms.ModelForm):
'invalid_choice': EFFORT_FOR_FIXING_INVALID_CHOICE})

# the only reliable way without hacking internal fields to get predicatble ordering is to make it explicit
field_order = ('title', 'group', 'date', 'sla_start_date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3', 'cvssv3_score', 'description', 'mitigation', 'impact',
'request', 'response', 'steps_to_reproduce', 'severity_justification', 'endpoints', 'endpoints_to_add', 'references',
'active', 'mitigated', 'mitigated_by', 'verified', 'false_p', 'duplicate',
field_order = ('title', 'group', 'date', 'sla_start_date', 'sla_expiration_date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3',
'cvssv3_score', 'description', 'mitigation', 'impact', 'request', 'response', 'steps_to_reproduce', 'severity_justification',
'endpoints', 'endpoints_to_add', 'references', 'active', 'mitigated', 'mitigated_by', 'verified', 'false_p', 'duplicate',
'out_of_scope', 'risk_accept', 'under_defect_review')

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -1251,6 +1258,7 @@ def __init__(self, *args, **kwargs):
self.fields['duplicate'].help_text = "You can mark findings as duplicate only from the view finding page."

self.fields['sla_start_date'].disabled = True
self.fields['sla_expiration_date'].disabled = True

if self.can_edit_mitigated_data:
if hasattr(self, 'instance'):
Expand Down Expand Up @@ -2436,6 +2444,22 @@ def clean(self):


class SLAConfigForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SLAConfigForm, self).__init__(*args, **kwargs)

# if this sla config has findings being asynchronously updated, disable the days by severity fields
if self.instance.async_updating:
msg = 'Finding SLA expiration dates are currently being recalculated. ' + \
'This field cannot be changed until the calculation is complete.'
self.fields['critical'].disabled = True
self.fields['critical'].widget.attrs['message'] = msg
self.fields['high'].disabled = True
self.fields['high'].widget.attrs['message'] = msg
self.fields['medium'].disabled = True
self.fields['medium'].widget.attrs['message'] = msg
self.fields['low'].disabled = True
self.fields['low'].widget.attrs['message'] = msg

class Meta:
model = SLA_Configuration
fields = ['name', 'description', 'critical', 'high', 'medium', 'low']
Expand Down
Loading

0 comments on commit 19ecb49

Please sign in to comment.