Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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.55.0",
"version": "2.55.1",
"license" : "BSD-3-Clause",
"private": true,
"dependencies": {
Expand Down
5 changes: 4 additions & 1 deletion docs/config/_default/hugo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@ copyRight = "Copyright (c) 2020-2024 Thulite"
priority = 0.5

[caches]
[caches.getresource]
dir = ":cacheDir/:project"
maxAge = "1h"
[caches.getjson]
dir = ":cacheDir/:project"
maxAge = -1 # "30m"
maxAge = "1h"

[taxonomies]
contributor = "contributors"
Expand Down
5 changes: 3 additions & 2 deletions docs/layouts/_partials/head/custom-head.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<!-- Custom head -->
{{ if site.Params.add_ons.docSearch -}}
{{ if site.Params.add_ons.docSearch -}}
{{ $options := (dict "targetPath" "/css/main.min.css" "outputStyle" "compressed") }}
{{ $style := resources.Get "scss/app.scss" | css.Sass $options }}
<link rel="stylesheet" href="{{ $style.Permalink }}">
<link rel="stylesheet" href="{{ $style.Permalink }}">
{{ end -}}
<meta name="docsearch:audience" content="{{ .Params.audience | default " public" }}">
3 changes: 3 additions & 0 deletions docs/layouts/_partials/seo/robots.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{- with .Params.seo.robots }}
<meta name="robots" content="{{ . }}">
{{- end }}
3 changes: 3 additions & 0 deletions docs/layouts/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
User-agent: *
Disallow:
Sitemap: {{ "/sitemap.xml" | absURL }}
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.55.0"
__version__ = "2.55.1"
__url__ = "https://github.com/DefectDojo/django-DefectDojo" # noqa: RUF067
__docs__ = "https://documentation.defectdojo.com" # noqa: RUF067
3 changes: 3 additions & 0 deletions dojo/api_v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,9 @@ def notes(self, request, pk=None):
note_type=note_type,
)
note.save()
finding.last_reviewed = note.date
finding.last_reviewed_by = author
finding.save(update_fields=["last_reviewed", "last_reviewed_by", "updated"])
finding.notes.add(note)
# Determine if we need to send any notifications for user mentioned
process_tag_notifications(
Expand Down
8 changes: 5 additions & 3 deletions dojo/jira_link/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,11 @@ def check_for_and_create_comment(parsed_json):
finding.notes.add(new_note)
finding.jira_issue.jira_change = timezone.now()
finding.jira_issue.save()
# Only update the timestamp, not other fields like 'active' to avoid
finding.last_reviewed = new_note.date
finding.last_reviewed_by = author
# Only update the timestamp fields, not other fields like 'active' to avoid
# race conditions with concurrent webhook events (e.g. issue_updated)
finding.save(update_fields=["updated"])
finding.save(update_fields=["last_reviewed", "last_reviewed_by", "updated"])
return None


Expand Down Expand Up @@ -345,11 +347,11 @@ def post(self, request):
# Get the open and close keys
msg = "Unable to find Open/Close ID's (invalid issue key specified?). They will need to be found manually"
try:
open_key = close_key = None
issue_id = jform.cleaned_data.get("issue_key")
key_url = jira_server.strip("/") + "/rest/api/latest/issue/" + issue_id + "/transitions?expand=transitions.fields"
response = jira._session.get(key_url).json()
logger.debug("Retrieved JIRA issue successfully")
open_key = close_key = None
for node in response["transitions"]:
if node["to"]["statusCategory"]["name"] == "To Do":
open_key = open_key or int(node["id"])
Expand Down
13 changes: 5 additions & 8 deletions dojo/product_type/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.contrib import messages
from django.contrib.admin.utils import NestedObjects
from django.db import DEFAULT_DB_ALIAS
from django.db.models import Count, IntegerField, OuterRef, Subquery, Value
from django.db.models import OuterRef, Value
from django.db.models.functions import Coalesce
from django.db.models.query import QuerySet
from django.http import HttpResponseRedirect
Expand Down Expand Up @@ -82,13 +82,10 @@ def prefetch_for_product_type(prod_types):
logger.debug("unable to prefetch because query was already executed")
return prod_types

prod_subquery = Subquery(
Product.objects.filter(prod_type_id=OuterRef("pk"))
.values("prod_type_id")
.annotate(c=Count("*"))
.values("c")[:1],
output_field=IntegerField(),
)
prod_subquery = build_count_subquery(
Product.objects.filter(prod_type_id=OuterRef("pk")),
group_field="prod_type_id",
)
base_findings = Finding.objects.filter(test__engagement__product__prod_type_id=OuterRef("pk"))
count_subquery = partial(build_count_subquery, group_field="test__engagement__product__prod_type_id")

Expand Down
6 changes: 5 additions & 1 deletion dojo/query_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

def build_count_subquery(model_qs: QuerySet, group_field: str) -> Subquery:
"""Return a Subquery that yields one aggregated count per `group_field`."""
# Important: slicing (`[:1]`) on an unordered queryset makes Django add an implicit `ORDER BY <pk>`.
# With aggregation, Django then includes that pk in the GROUP BY, which collapses counts to 1.
# Ordering by `group_field` avoids that and keeps the GROUP BY stable.
model_qs = model_qs.order_by()
return Subquery(
model_qs.values(group_field).annotate(c=Count("*")).values("c")[:1], # one row per group_field
model_qs.values(group_field).annotate(c=Count("pk")).order_by(group_field).values("c")[:1], # one row per group_field
output_field=IntegerField(),
)
6 changes: 3 additions & 3 deletions helm/defectdojo/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
apiVersion: v2
appVersion: "2.55.0"
appVersion: "2.55.1"
description: A Helm chart for Kubernetes to install DefectDojo
name: defectdojo
version: 1.9.10
version: 1.9.11
icon: https://defectdojo.com/hubfs/DefectDojo_favicon.png
maintainers:
- name: madchap
Expand Down Expand Up @@ -34,4 +34,4 @@ dependencies:
# description: Critical bug
annotations:
artifacthub.io/prerelease: "false"
artifacthub.io/changes: "- kind: changed\n description: Update valkey Docker tag from 0.13.0 to v0.15.0 (_/defect_/Chart.yaml)\n- kind: changed\n description: chore(deps)_ update valkey _ tag from 0.15.0 to v0.15.1 (_/defect_/chart.yaml)\n- kind: changed\n description: chore(deps)_ update gcr.io/cloudsql__/gce_proxy _ tag from 1.37.11 to v1.37.12 (_/defect_/values.yaml)\n- kind: changed\n description: Update valkey Docker tag from 0.15.1 to v0.15.2 (_/defect_/Chart.yaml)\n- kind: changed\n description: Update valkey Docker tag from 0.15.2 to v0.15.3 (_/defect_/Chart.yaml)\n- kind: changed\n description: Bump DefectDojo to 2.55.0\n"
artifacthub.io/changes: "- kind: changed\n description: Bump DefectDojo to 2.55.1\n"
2 changes: 1 addition & 1 deletion helm/defectdojo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ The HELM schema will be generated for you.
# General information about chart values
![Version: 1.9.10](https://img.shields.io/badge/Version-1.9.10-informational?style=flat-square) ![AppVersion: 2.55.0](https://img.shields.io/badge/AppVersion-2.55.0-informational?style=flat-square)
![Version: 1.9.11](https://img.shields.io/badge/Version-1.9.11-informational?style=flat-square) ![AppVersion: 2.55.1](https://img.shields.io/badge/AppVersion-2.55.1-informational?style=flat-square)
A Helm chart for Kubernetes to install DefectDojo
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ django-slack==5.19.0
django-watson==1.6.3
django-permissions-policy==4.28.0
django-prometheus==2.4.1
Django==5.2.9
Django==5.2.11
django-single-session==0.2.0
djangorestframework==3.16.1
html2text==2025.4.15
Expand Down
19 changes: 19 additions & 0 deletions unittests/test_product_type_counts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from dojo.models import Product, Product_Type
from dojo.product_type.views import prefetch_for_product_type
from unittests.dojo_test_case import DojoTestCase, versioned_fixtures


@versioned_fixtures
class TestProductTypeCounts(DojoTestCase):
fixtures = ["dojo_testdata.json"]

def test_prefetch_for_product_type_prod_count_matches_direct_count(self):
product_type = Product_Type.objects.create(name="PT count test")
Product.objects.create(name="PT product 1", description="test", prod_type=product_type)
Product.objects.create(name="PT product 2", description="test", prod_type=product_type)

annotated = prefetch_for_product_type(Product_Type.objects.filter(id=product_type.id))
annotated_count = annotated.values_list("prod_count", flat=True).get()

direct_count = Product.objects.filter(prod_type_id=product_type.id).count()
self.assertEqual(annotated_count, direct_count)
21 changes: 21 additions & 0 deletions unittests/test_query_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.db.models import Count

from dojo.engagement.views import prefetch_for_view_tests
from dojo.models import Finding, Test
from unittests.dojo_test_case import DojoTestCase, versioned_fixtures


@versioned_fixtures
class TestQueryUtils(DojoTestCase):
fixtures = ["dojo_testdata.json"]

def test_prefetch_for_view_tests_finding_counts_match_direct_count(self):
test = Test.objects.annotate(finding_count=Count("finding")).filter(finding_count__gt=1).first()
# If fixtures ever change, ensure we still have a representative test case.
self.assertIsNotNone(test)

annotated = prefetch_for_view_tests(Test.objects.filter(id=test.id))
annotated_count = annotated.values_list("count_findings_test_all", flat=True).get()

direct_count = Finding.objects.filter(test_id=test.id).count()
self.assertEqual(annotated_count, direct_count)