Skip to content

Commit

Permalink
Prepare model
Browse files Browse the repository at this point in the history
  • Loading branch information
kiblik committed Nov 30, 2024
1 parent 9cd9d14 commit eaf7e66
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 13 deletions.
17 changes: 17 additions & 0 deletions dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,13 @@ def validate(self, data):
if data.get("target_start") > data.get("target_end"):
msg = "Your target start date exceeds your target end date"
raise serializers.ValidationError(msg)
async_updating = getattr(self.instance.product, "async_updating", None)
if async_updating: # TODO: test
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:
msg = "Finding SLA expiration dates are currently being recalculated. The SLA configuration for this product cannot be changed until the calculation is complete."
raise serializers.ValidationError(msg)
return data

def build_relational_field(self, field_name, relation_info):
Expand Down Expand Up @@ -1399,6 +1406,16 @@ class Meta:
model = Test
exclude = ("inherited_tags",)

def validate(self, data):
async_updating = getattr(self.instance.engagement.product, "async_updating", None)
if async_updating: # TODO: test
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:
msg = "Finding SLA expiration dates are currently being recalculated. The SLA configuration for this product cannot be changed until the calculation is complete."
raise serializers.ValidationError(msg)
return data

def build_relational_field(self, field_name, relation_info):
if field_name == "notes":
return NoteSerializer, {"many": True, "read_only": True}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 5.1.3 on 2024-11-20 08:56

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dojo', '0218_system_settings_enforce_verified_status_and_more'),
]

operations = [
migrations.AddField(
model_name='engagement',
name='sla_configuration',
field=models.ForeignKey(blank=True, default=None, help_text='If no configuration will be configured, inherited (from product) will be applied.', null=True, on_delete=django.db.models.deletion.RESTRICT, to='dojo.sla_configuration'),
),
migrations.AddField(
model_name='test',
name='sla_configuration',
field=models.ForeignKey(blank=True, default=None, help_text='If no configuration will be configured, inherited (from engagement) will be applied.', null=True, on_delete=django.db.models.deletion.RESTRICT, to='dojo.sla_configuration'),
),
]
11 changes: 10 additions & 1 deletion dojo/engagement/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
Product,
Product_API_Scan_Configuration,
Risk_Acceptance,
SLA_Configuration,
System_Settings,
Test,
Test_Import,
Expand Down Expand Up @@ -277,6 +278,7 @@ def edit_engagement(request, eid):
# first save engagement details
new_status = form.cleaned_data.get("status")
engagement.product = form.cleaned_data.get("product")
initial_sla_config = Engagement.objects.get(pk=form.instance.id).sla_configuration
engagement = form.save(commit=False)
if (new_status == "Cancelled" or new_status == "Completed"):
engagement.active = False
Expand All @@ -285,10 +287,14 @@ def edit_engagement(request, eid):
engagement.save()
form.save_m2m()

msg = _("Engagement updated successfully.")
# check if the SLA config was changed, append additional context to message
if initial_sla_config != form.instance.sla_configuration:
msg += _(" All SLA expiration dates for findings within this product will be recalculated asynchronously for the newly assigned SLA configuration.")
messages.add_message(
request,
messages.SUCCESS,
"Engagement updated successfully.",
msg,
extra_tags="alert-success")

success, jira_project_form = jira_helper.process_jira_project_form(request, instance=jira_project, target="engagement", engagement=engagement, product=engagement.product)
Expand Down Expand Up @@ -469,6 +475,8 @@ def get(self, request, eid, *args, **kwargs):
cred_eng = Cred_Mapping.objects.filter(
engagement=eng.id).select_related("cred_id").order_by("cred_id")

sla = SLA_Configuration.objects.filter(id=eng.sla_configuration_id).first()

add_breadcrumb(parent=eng, top_level=False, request=request)

title = ""
Expand All @@ -495,6 +503,7 @@ def get(self, request, eid, *args, **kwargs):
"cred_eng": cred_eng,
"network": network,
"preset_test_type": preset_test_type,
"sla": sla,
})

def post(self, request, eid, *args, **kwargs):
Expand Down
13 changes: 12 additions & 1 deletion dojo/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,11 @@ def __init__(self, *args, **kwargs):

super().__init__(*args, **kwargs)

# if this product has findings being asynchronously updated, disable the sla config field
if self.instance.product.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."
if product:
self.fields["preset"] = forms.ModelChoiceField(help_text="Settings and notes for performing this engagement.", required=False, queryset=Engagement_Presets.objects.filter(product=product))
self.fields["lead"].queryset = get_authorized_users_for_product_and_product_type(None, product, Permissions.Product_View).filter(is_active=True)
Expand Down Expand Up @@ -1058,6 +1063,12 @@ def __init__(self, *args, **kwargs):

super().__init__(*args, **kwargs)

# if this product has findings being asynchronously updated, disable the sla config field
if self.instance.engagement.product.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."

if obj:
product = get_product(obj)
self.fields["lead"].queryset = get_authorized_users_for_product_and_product_type(None, product, Permissions.Product_View).filter(is_active=True)
Expand All @@ -1069,7 +1080,7 @@ class Meta:
model = Test
fields = ["title", "test_type", "target_start", "target_end", "description",
"environment", "percent_complete", "tags", "lead", "version", "branch_tag", "build_id", "commit_hash",
"api_scan_configuration"]
"api_scan_configuration", "sla_configuration"]


class DeleteTestForm(forms.ModelForm):
Expand Down
94 changes: 91 additions & 3 deletions dojo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
from tagulous.models import TagField
from tagulous.models.managers import FakeTagRelatedManager

from dojo.product.helpers import update_sla_expiration_dates_product_async
from dojo.sla_config.helpers import update_sla_expiration_dates_sla_config_async

logger = logging.getLogger(__name__)
deduplicationLogger = logging.getLogger("dojo.specific-loggers.deduplication")

Expand Down Expand Up @@ -1010,8 +1013,22 @@ def save(self, *args, **kwargs):
product.async_updating = True
super(Product, product).save()
# launch the async task to update all finding sla expiration dates
from dojo.sla_config.helpers import update_sla_expiration_dates_sla_config_async
update_sla_expiration_dates_sla_config_async(self, tuple(severities), products)
# set the async updating flag to true for all eng's product using this sla config
engs = Engagement.objects.filter(sla_configuration=self)
for eng in engs:
eng.product.async_updating = True
super(Product, product).save()
# launch the async task to update all finding sla expiration dates
update_sla_expiration_dates_sla_config_async(self, tuple(severities), products)
# set the async updating flag to true for all test's product using this sla config
tests = Test.objects.filter(sla_configuration=self)
for test in tests:
test.engproduct.async_updating = True
super(Product, product).save()
# launch the async task to update all finding sla expiration dates
update_sla_expiration_dates_sla_config_async(self, tuple(severities), products)
# TODO: Merge these products ^

def clean(self):
sla_days = [self.critical, self.high, self.medium, self.low]
Expand Down Expand Up @@ -1172,7 +1189,6 @@ def save(self, *args, **kwargs):
sla_config.async_updating = True
super(SLA_Configuration, sla_config).save()
# launch the async task to update all finding sla expiration dates
from dojo.product.helpers import update_sla_expiration_dates_product_async
update_sla_expiration_dates_product_async(self, sla_config)

def get_absolute_url(self):
Expand Down Expand Up @@ -1485,6 +1501,12 @@ class Engagement(models.Model):
source_code_management_uri = models.URLField(max_length=600, null=True, blank=True, editable=True, verbose_name=_("Repo"), help_text=_("Resource link to source code"))
orchestration_engine = models.ForeignKey(Tool_Configuration, verbose_name=_("Orchestration Engine"), help_text=_("Orchestration service responsible for CI/CD test"), null=True, blank=True, related_name="orchestration", on_delete=models.CASCADE)
deduplication_on_engagement = models.BooleanField(default=False, verbose_name=_("Deduplication within this engagement only"), help_text=_("If enabled deduplication will only mark a finding in this engagement as duplicate of another finding if both findings are in this engagement. If disabled, deduplication is on the product level."))
sla_configuration = models.ForeignKey(SLA_Configuration,
null=True,
blank=True,
default=None,
on_delete=models.RESTRICT,
help_text=_("If no configuration will be configured, inherited (from product) will be applied."))

tags = TagField(blank=True, force_lowercase=True, help_text=_("Add tags that help describe this engagement. Choose from the list or add new tags. Press Enter key to add."))
inherited_tags = TagField(blank=True, force_lowercase=True, help_text=_("Internal use tags sepcifically for maintaining parity with product. This field will be present as a subset in the tags field"))
Expand All @@ -1500,6 +1522,34 @@ def __str__(self):
self.target_start.strftime(
"%b %d, %Y"))

def save(self, *args, **kwargs):
# get the engagement's sla config before saving (if this is an existing product)
initial_sla_config = None
if self.pk is not None:
initial_sla_config = getattr(Engagement.objects.get(pk=self.pk), "sla_configuration", None)
# if initial sla config exists and async finding update is already running, revert sla config before saving
if initial_sla_config and self.product.async_updating:
self.sla_configuration = initial_sla_config

super().save(*args, **kwargs)

# if the initial sla config exists and async finding update is not running
if initial_sla_config is not None and not self.product.async_updating:
# get the new sla config from the saved engagement
new_sla_config = getattr(self, "sla_configuration", None)
# if the sla config has changed, update finding sla expiration dates within this engagement's product
if new_sla_config and (initial_sla_config != new_sla_config):
# set the async updating flag to true for this engagement's product
self.product.async_updating = True
super(Product, self.product).save(*args, **kwargs)
# set the async updating flag to true for the sla config assigned to this product
sla_config = getattr(self, "sla_configuration", None)
if sla_config:
sla_config.async_updating = True
super(SLA_Configuration, sla_config).save()
# launch the async task to update all finding sla expiration dates
update_sla_expiration_dates_product_async(self.product, sla_config)

def get_absolute_url(self):
from django.urls import reverse
return reverse("view_engagement", args=[str(self.id)])
Expand Down Expand Up @@ -2066,6 +2116,12 @@ class Test(models.Model):
branch_tag = models.CharField(editable=True, max_length=150,
null=True, blank=True, help_text=_("Tag or branch that was tested, a reimport may update this field."), verbose_name=_("Branch/Tag"))
api_scan_configuration = models.ForeignKey(Product_API_Scan_Configuration, null=True, editable=True, blank=True, on_delete=models.CASCADE, verbose_name=_("API Scan Configuration"))
sla_configuration = models.ForeignKey(SLA_Configuration,
null=True,
blank=True,
default=None,
on_delete=models.RESTRICT,
help_text=_("If no configuration will be configured, inherited (from engagement) will be applied."))

class Meta:
indexes = [
Expand All @@ -2077,6 +2133,34 @@ def __str__(self):
return f"{self.title} ({self.test_type})"
return str(self.test_type)

def save(self, *args, **kwargs):
# get the test's sla config before saving (if this is an existing product)
initial_sla_config = None
if self.pk is not None:
initial_sla_config = getattr(Test.objects.get(pk=self.pk), "sla_configuration", None)
# if initial sla config exists and async finding update is already running, revert sla config before saving
if initial_sla_config and self.engagement.product.async_updating:
self.sla_configuration = initial_sla_config

super().save(*args, **kwargs)

# if the initial sla config exists and async finding update is not running
if initial_sla_config is not None and not self.engagement.product.async_updating:
# get the new sla config from the saved test
new_sla_config = getattr(self, "sla_configuration", None)
# if the sla config has changed, update finding sla expiration dates within this test's product
if new_sla_config and (initial_sla_config != new_sla_config):
# set the async updating flag to true for this test's product
self.product.async_updating = True
super(Product, self.engagement.product).save(*args, **kwargs)
# set the async updating flag to true for the sla config assigned to this test's product
sla_config = getattr(self, "sla_configuration", None)
if sla_config:
sla_config.async_updating = True
super(SLA_Configuration, sla_config).save()
# launch the async task to update all finding sla expiration dates
update_sla_expiration_dates_product_async(self.engagement.product, sla_config)

def get_absolute_url(self):
from django.urls import reverse
return reverse("view_test", args=[str(self.id)])
Expand Down Expand Up @@ -3001,7 +3085,11 @@ def get_sla_start_date(self):
return self.date

def get_sla_period(self):
sla_configuration = SLA_Configuration.objects.filter(id=self.test.engagement.product.sla_configuration_id).first()
sla_configuration = SLA_Configuration.objects.filter(id=self.test.sla_configuration_id).first()
if not sla_configuration:
sla_configuration = SLA_Configuration.objects.filter(id=self.test.engagement.sla_configuration_id).first()
if not sla_configuration:
sla_configuration = SLA_Configuration.objects.filter(id=self.test.engagement.product.sla_configuration_id).first()
sla_period = getattr(sla_configuration, self.severity.lower(), None)
enforce_period = getattr(sla_configuration, str("enforce_" + self.severity.lower()), None)
return sla_period, enforce_period
Expand Down
6 changes: 3 additions & 3 deletions dojo/product/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -944,13 +944,13 @@ def edit_product(request, pid):
if form.is_valid():
initial_sla_config = Product.objects.get(pk=form.instance.id).sla_configuration
form.save()
msg = "Product updated successfully."
msg = _("Product updated successfully.")
# check if the SLA config was changed, append additional context to message
if initial_sla_config != form.instance.sla_configuration:
msg += " All SLA expiration dates for findings within this product will be recalculated asynchronously for the newly assigned SLA configuration."
msg += _(" All SLA expiration dates for findings within this product will be recalculated asynchronously for the newly assigned SLA configuration.")
messages.add_message(request,
messages.SUCCESS,
_(msg),
msg,
extra_tags="alert-success")

success, jform = jira_helper.process_jira_project_form(request, instance=jira_project, product=product)
Expand Down
2 changes: 1 addition & 1 deletion dojo/sla_config/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def update_sla_expiration_dates_sla_config_sync(sla_config, severities, products
logger.info(f"Updating finding SLA expiration dates within the {sla_config} SLA configuration")
# update each finding that is within the SLA configuration that was saved
for f in Finding.objects.filter(test__engagement__product__sla_configuration_id=sla_config.id, severity__in=severities):
f.save()
f.save() # TODO: Add for other layers as well
# reset the async updating flag to false for all products using this sla config
for product in products:
product.async_updating = False
Expand Down
Loading

0 comments on commit eaf7e66

Please sign in to comment.