Skip to content

Commit

Permalink
Prepare model
Browse files Browse the repository at this point in the history
  • Loading branch information
kiblik committed Nov 20, 2024
1 parent e59c395 commit 9644307
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 12 deletions.
17 changes: 17 additions & 0 deletions dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,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 @@ -1408,6 +1415,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 @@ -475,6 +481,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 @@ -501,6 +509,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
15 changes: 13 additions & 2 deletions dojo/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ class ProductForm(forms.ModelForm):
queryset=Product_Type.objects.none(),
required=True)

sla_configuration = forms.ModelChoiceField(label="SLA Configuration",
sla_configuration = forms.ModelChoiceField(label="SLA Configuration", # TODO: add Eng+test but is it needed?
queryset=SLA_Configuration.objects.all(),
required=True,
initial="Default")
Expand Down Expand Up @@ -986,6 +986,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 @@ -1061,6 +1066,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 @@ -1072,7 +1083,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
24 changes: 21 additions & 3 deletions dojo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,7 @@ class Meta:
def __str__(self):
return self.name

# TODO: add this to eng and test
def save(self, *args, **kwargs):
# get the product's sla config before saving (if this is an existing product)
initial_sla_config = None
Expand Down Expand Up @@ -1176,7 +1177,7 @@ def save(self, *args, **kwargs):
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)
update_sla_expiration_dates_product_async(self, sla_config) # call the same in eng + test

def get_absolute_url(self):
from django.urls import reverse
Expand Down Expand Up @@ -1488,6 +1489,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 Down Expand Up @@ -2046,7 +2053,7 @@ class Meta:
ordering = ("-created", )


class Test(models.Model):
class Test(models.Model):
engagement = models.ForeignKey(Engagement, editable=False, on_delete=models.CASCADE)
lead = models.ForeignKey(Dojo_User, editable=True, null=True, blank=True, on_delete=models.RESTRICT)
test_type = models.ForeignKey(Test_Type, on_delete=models.CASCADE)
Expand Down Expand Up @@ -2080,6 +2087,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 Down Expand Up @@ -3015,7 +3028,12 @@ 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()
# TODO: test this
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 @@ -950,13 +950,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: How to handle this?
# reset the async updating flag to false for all products using this sla config
for product in products:
product.async_updating = False
Expand Down
2 changes: 1 addition & 1 deletion dojo/sla_config/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def edit_sla_config(request, slaid):

if request.method == "POST" and request.POST.get("delete"):
if sla_config.id != 1:
if Product.objects.filter(sla_configuration=sla_config).count():
if Product.objects.filter(sla_configuration=sla_config).count(): # TODO: Or eng, or test
msg = f'The "{sla_config}" SLA configuration could not be deleted, as it is currently in use by one or more products.'
messages.add_message(request,
messages.ERROR,
Expand Down
58 changes: 58 additions & 0 deletions dojo/templates/dojo/view_eng.html
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,64 @@ <h3 class="panel-title"><span class="fa-solid fa-server" aria-hidden="true"></sp
</div>
</div>
{% endif %}
<!-- SLA -->
{% if system_settings.enable_finding_sla and sla %}
<div class="panel panel-default panel-default-secondary">
<div class="panel-heading">
<h3 class="panel-title"><span class="fa-solid fa-calendar-check fa-fw" aria-hidden="true"></span>&nbsp;Service Level Agreement</h3>
</div><div class="panel-body">
<p>{{ sla.name }}</p>
<p>{{ sla.description }}</p>
</div>
<div class="table-responsive">
<table class="table table-striped" >
<tbody>
<tr>
<td style="width: 160px;"><strong>Critical</strong></td>
<td>
{% if sla.enforce_critical %}
{{ sla.critical }} days to remediate
{% else %}
<em class="text-muted">Not Enforced</em>
{% endif %}
</td>
</tr>
<tr>
<td style="width: 160px;"><strong>High</strong></td>
<td>
{% if sla.enforce_high %}
{{ sla.high }} days to remediate
{% else %}
<em class="text-muted">Not Enforced</em>
{% endif %}
</td>
</tr>
<tr>
<td style="width: 160px;"><strong>Medium</strong></td>
<td>
{% if sla.enforce_medium %}
{{ sla.medium }} days to remediate
{% else %}
<em class="text-muted">Not Enforced</em>
{% endif %}
</td>
</tr>
<tr>
<td style="width: 160px;"><strong>Low</strong></td>
<td>
{% if sla.enforce_low %}
{{ sla.low }} days to remediate
{% else %}
<em class="text-muted">Not Enforced</em>
{% endif %}
</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- End SLA -->
{% if system_settings.enable_credentials %}
<div>
<div class="panel panel-default-secondary">
Expand Down
Loading

0 comments on commit 9644307

Please sign in to comment.