Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release: Merge release into master from: release/2.42.2 #11605

Merged
merged 18 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "defectdojo",
"version": "2.42.1",
"version": "2.42.2",
"license" : "BSD-3-Clause",
"private": true,
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion docs/content/en/open_source/upgrading/2.42.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: 'Upgrading to DefectDojo Version 2.42.x'
toc_hide: true
weight: -20241104
weight: -20241202
description: No special instructions.
---
There are no special instructions for upgrading to 2.42.x. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.42.0) for the contents of the release.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ weight: 2
Once you have created one or more **Reports** in DefectDojo you can take further actions, including:

* Using a report as a template for subsequent reports
* Re\-running a report with updated data

* Re-running a report with updated data

* Deleting an old or unused report

![image](images/Working_with_Generated_Reports.png)
Expand Down
2 changes: 1 addition & 1 deletion dojo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
# Django starts so that shared_task will use this app.
from .celery import app as celery_app # noqa: F401

__version__ = "2.42.1"
__version__ = "2.42.2"
__url__ = "https://github.com/DefectDojo/django-DefectDojo"
__docs__ = "https://documentation.defectdojo.com"
2 changes: 1 addition & 1 deletion dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2286,7 +2286,7 @@ def setup_common_context(self, data: dict) -> dict:
"""
context = dict(data)
# update some vars
context["scan"] = data.pop("file")
context["scan"] = data.pop("file", None)

if context.get("auto_create_context"):
environment = Development_Environment.objects.get_or_create(name=data.get("environment", "Development"))[0]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 5.1.4 on 2025-01-10 16:01

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='system_settings',
name='enforce_verified_status_jira',
field=models.BooleanField(default=True, help_text='When enabled, findings must have a verified status to be pushed to jira.', verbose_name='Enforce Verified Status - Jira'),
),
migrations.AddField(
model_name='system_settings',
name='enforce_verified_status_metrics',
field=models.BooleanField(default=True, help_text='When enabled, findings must have a verified status to be counted in metric calculations, be included in reports, and filters.', verbose_name='Enforce Verified Status - Metrics'),
),
migrations.AddField(
model_name='system_settings',
name='enforce_verified_status_product_grading',
field=models.BooleanField(default=True, help_text="When enabled, findings must have a verified status to be considered as part of a product's grading.", verbose_name='Enforce Verified Status - Product Grading'),
),
migrations.AlterField(
model_name='system_settings',
name='enforce_verified_status',
field=models.BooleanField(default=True, help_text='When enabled, features such as product grading, jira integration, metrics, and reports will only interact with verified findings. This setting will override individually scoped verified toggles.', verbose_name='Enforce Verified Status - Globally'),
),
]
76 changes: 61 additions & 15 deletions dojo/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
CharFilter,
DateFilter,
DateFromToRangeFilter,
DateTimeFilter,
FilterSet,
ModelChoiceFilter,
ModelMultipleChoiceFilter,
Expand Down Expand Up @@ -1411,6 +1412,15 @@ class ApiProductFilter(DojoFilter):
)


class PercentageRangeFilter(RangeFilter):
def filter(self, qs, value):
if value is not None:
start = value.start / decimal.Decimal("100.0") if value.start else None
stop = value.stop / decimal.Decimal("100.0") if value.stop else None
value = slice(start, stop)
return super().filter(qs, value)


class ApiFindingFilter(DojoFilter):
# BooleanFilter
active = BooleanFilter(field_name="active")
Expand Down Expand Up @@ -1456,13 +1466,30 @@ class ApiFindingFilter(DojoFilter):
jira_change = DateRangeFilter(field_name="jira_issue__jira_change")
last_reviewed = DateRangeFilter()
mitigated = DateRangeFilter()
mitigated_on = DateFilter(field_name="mitigated", lookup_expr="exact")
mitigated_before = DateFilter(field_name="mitigated", lookup_expr="lt")
mitigated_after = DateFilter(field_name="mitigated", lookup_expr="gt")
mitigated_on = DateTimeFilter(field_name="mitigated", lookup_expr="exact", method="filter_mitigated_on")
mitigated_before = DateTimeFilter(field_name="mitigated", lookup_expr="lt")
mitigated_after = DateTimeFilter(field_name="mitigated", lookup_expr="gt", label="Mitigated After", method="filter_mitigated_after")
# NumberInFilter
cwe = NumberInFilter(field_name="cwe", lookup_expr="in")
defect_review_requested_by = NumberInFilter(field_name="defect_review_requested_by", lookup_expr="in")
endpoints = NumberInFilter(field_name="endpoints", lookup_expr="in")
epss_score = PercentageRangeFilter(
field_name="epss_score",
label="EPSS score range",
help_text=(
"The range of EPSS score percentages to filter on; the min input is a lower bound, "
"the max is an upper bound. Leaving one empty will skip that bound (e.g., leaving "
"the min bound input empty will filter only on the max bound -- filtering on "
'"less than or equal"). Leading 0 required.'
))
epss_percentile = PercentageRangeFilter(
field_name="epss_percentile",
label="EPSS percentile range",
help_text=(
"The range of EPSS percentiles to filter on; the min input is a lower bound, the max "
"is an upper bound. Leaving one empty will skip that bound (e.g., leaving the min bound "
'input empty will filter only on the max bound -- filtering on "less than or equal"). Leading 0 required.'
))
found_by = NumberInFilter(field_name="found_by", lookup_expr="in")
id = NumberInFilter(field_name="id", lookup_expr="in")
last_reviewed_by = NumberInFilter(field_name="last_reviewed_by", lookup_expr="in")
Expand Down Expand Up @@ -1544,6 +1571,20 @@ class Meta:
exclude = ["url", "thread_id", "notes", "files",
"line", "cve"]

def filter_mitigated_after(self, queryset, name, value):
if value.hour == 0 and value.minute == 0 and value.second == 0:
value = value.replace(hour=23, minute=59, second=59)

return queryset.filter(mitigated__gt=value)

def filter_mitigated_on(self, queryset, name, value):
if value.hour == 0 and value.minute == 0 and value.second == 0:
# we have a simple date without a time, lets get a range from this morning to tonight at 23:59:59:999
nextday = value + timedelta(days=1)
return queryset.filter(mitigated__gte=value, mitigated__lt=nextday)

return queryset.filter(mitigated=value)


class PercentageFilter(NumberFilter):
def __init__(self, *args, **kwargs):
Expand All @@ -1564,15 +1605,6 @@ def filter_percentage(self, queryset, name, value):
return queryset.filter(**lookup_kwargs)


class PercentageRangeFilter(RangeFilter):
def filter(self, qs, value):
if value is not None:
start = value.start / decimal.Decimal("100.0") if value.start else None
stop = value.stop / decimal.Decimal("100.0") if value.stop else None
value = slice(start, stop)
return super().filter(qs, value)


class FindingFilterHelper(FilterSet):
title = CharFilter(lookup_expr="icontains")
date = DateRangeFilter(field_name="date", label="Date Discovered")
Expand All @@ -1587,9 +1619,9 @@ class FindingFilterHelper(FilterSet):
duplicate = ReportBooleanFilter()
is_mitigated = ReportBooleanFilter()
mitigated = DateRangeFilter(field_name="mitigated", label="Mitigated Date")
mitigated_on = DateFilter(field_name="mitigated", lookup_expr="exact", label="Mitigated On")
mitigated_before = DateFilter(field_name="mitigated", lookup_expr="lt", label="Mitigated Before")
mitigated_after = DateFilter(field_name="mitigated", lookup_expr="gt", label="Mitigated After")
mitigated_on = DateTimeFilter(field_name="mitigated", lookup_expr="exact", label="Mitigated On", method="filter_mitigated_on")
mitigated_before = DateTimeFilter(field_name="mitigated", lookup_expr="lt", label="Mitigated Before")
mitigated_after = DateTimeFilter(field_name="mitigated", lookup_expr="gt", label="Mitigated After", method="filter_mitigated_after")
planned_remediation_date = DateRangeOmniFilter()
planned_remediation_version = CharFilter(lookup_expr="icontains", label=_("Planned remediation version"))
file_path = CharFilter(lookup_expr="icontains")
Expand Down Expand Up @@ -1705,6 +1737,20 @@ def set_date_fields(self, *args: list, **kwargs: dict):
self.form.fields["mitigated_after"].widget = date_input_widget
self.form.fields["cwe"].choices = cwe_options(self.queryset)

def filter_mitigated_after(self, queryset, name, value):
if value.hour == 0 and value.minute == 0 and value.second == 0:
value = value.replace(hour=23, minute=59, second=59)

return queryset.filter(mitigated__gt=value)

def filter_mitigated_on(self, queryset, name, value):
if value.hour == 0 and value.minute == 0 and value.second == 0:
# we have a simple date without a time, lets get a range from this morning to tonight at 23:59:59:999
nextday = value + timedelta(days=1)
return queryset.filter(mitigated__gte=value, mitigated__lt=nextday)

return queryset.filter(mitigated=value)


class FindingFilterWithoutObjectLookups(FindingFilterHelper, FindingTagStringFilter):
test__engagement__product__prod_type = NumberFilter(widget=HiddenInput())
Expand Down
2 changes: 1 addition & 1 deletion dojo/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3076,7 +3076,7 @@ def clean(self):
elif self.cleaned_data.get("push_to_jira", None):
active = self.finding_form["active"].value()
verified = self.finding_form["verified"].value()
if not active or (not verified and get_system_setting("enforce_verified_status", True)):
if not active or (not verified and (get_system_setting("enforce_verified_status", True) or get_system_setting("enforce_verified_status_jira", True))):
logger.debug("Findings must be active and verified to be pushed to JIRA")
error_message = "Findings must be active and verified to be pushed to JIRA"
self.add_error("push_to_jira", ValidationError(error_message, code="not_active_or_verified"))
Expand Down
3 changes: 1 addition & 2 deletions dojo/jira_link/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def can_be_pushed_to_jira(obj, form=None):

logger.debug("can_be_pushed_to_jira: %s, %s, %s", active, verified, severity)

isenforced = get_system_setting("enforce_verified_status", True)
isenforced = get_system_setting("enforce_verified_status", True) or get_system_setting("enforce_verified_status_jira", True)

if not active or (not verified and isenforced):
logger.debug("Findings must be active and verified, if enforced by system settings, to be pushed to JIRA")
Expand Down Expand Up @@ -1116,7 +1116,6 @@ def get_issuetype_fields(
except JIRAError as e:
e.text = f"Jira API call 'createmeta' failed with status: {e.status_code} and message: {e.text}"
raise

project = None
try:
project = meta["projects"][0]
Expand Down
2 changes: 1 addition & 1 deletion dojo/management/commands/jira_async_updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):

findings = Finding.objects.exclude(jira_issue__isnull=True)
if get_system_setting("enforce_verified_status", True):
if get_system_setting("enforce_verified_status", True) or get_system_setting("enforce_verified_status_jira", True):
findings = findings.filter(verified=True, active=True)
else:
findings = findings.filter(active=True)
Expand Down
2 changes: 1 addition & 1 deletion dojo/management/commands/push_to_jira_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):

findings = Finding.objects.exclude(jira_issue__isnull=True)
if get_system_setting("enforce_verified_status", True):
if get_system_setting("enforce_verified_status", True) or get_system_setting("enforce_verified_status_jira", True):
findings = findings.filter(verified=True, active=True)
else:
findings = findings.filter(active=True)
Expand Down
2 changes: 1 addition & 1 deletion dojo/metrics/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def finding_queries(
weekly_counts = query_counts_for_period(MetricsPeriod.WEEK, weeks_between)

top_ten = get_authorized_products(Permissions.Product_View)
if get_system_setting("enforce_verified_status", True):
if get_system_setting("enforce_verified_status", True) or get_system_setting("enforce_verified_status_metrics", True):
top_ten = top_ten.filter(engagement__test__finding__verified=True)

top_ten = top_ten.filter(engagement__test__finding__false_p=False,
Expand Down
8 changes: 4 additions & 4 deletions dojo/metrics/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def simple_metrics(request):
date__year=now.year,
)

if get_system_setting("enforce_verified_status", True):
if get_system_setting("enforce_verified_status", True) or get_system_setting("enforce_verified_status_metrics", True):
total = total.filter(verified=True)

total = total.distinct()
Expand Down Expand Up @@ -308,7 +308,7 @@ def product_type_counts(request):
then=Value(1)),
output_field=IntegerField())))["total"]

if get_system_setting("enforce_verified_status", True):
if get_system_setting("enforce_verified_status", True) or get_system_setting("enforce_verified_status_metrics", True):
overall_in_pt = Finding.objects.filter(date__lt=end_date,
verified=True,
false_p=False,
Expand Down Expand Up @@ -509,7 +509,7 @@ def product_tag_counts(request):
then=Value(1)),
output_field=IntegerField())))["total"]

if get_system_setting("enforce_verified_status", True):
if get_system_setting("enforce_verified_status", True) or get_system_setting("enforce_verified_status_metrics", True):
overall_in_pt = Finding.objects.filter(date__lt=end_date,
verified=True,
false_p=False,
Expand Down Expand Up @@ -685,7 +685,7 @@ def view_engineer(request, eid):
raise PermissionDenied
now = timezone.now()

if get_system_setting("enforce_verified_status", True):
if get_system_setting("enforce_verified_status", True) or get_system_setting("enforce_verified_status_metrics", True):
findings = Finding.objects.filter(reporter=user, verified=True)
else:
findings = Finding.objects.filter(reporter=user)
Expand Down
Loading
Loading