Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6a0e40d
Update versions in application files
Jan 12, 2026
c0444a2
Merge pull request #14078 from DefectDojo/master-into-bugfix/2.54.1-2…
rossops Jan 12, 2026
4e9cda1
Remove unused asteval dependency (#14079)
valentijnscholten Jan 14, 2026
09c9821
:bug: fix Nonetype in nuclei #14071 (#14072)
manuel-sommer Jan 14, 2026
b7f0f99
Asset/Organizations Endpoints: Patches, permission checking, and API …
Maffooch Jan 14, 2026
52ba742
Add Report Builder submenu and improve form validation error messages…
valentijnscholten Jan 14, 2026
5057b4b
:arrow_up: Bump ruff from 0.14.10 to 0.14.11 (#14066)
manuel-sommer Jan 14, 2026
e797c5f
Refactor note fetching logic to improve permission checks and reduce …
Maffooch Jan 16, 2026
f5772ad
Enforce readonly name field for Test_Type instances and add dynamic s…
Maffooch Jan 16, 2026
cd98b5e
Import/Reimport: Push to jira when findings is not grouped
Maffooch Jan 16, 2026
045018a
Add unit tests
Maffooch Jan 16, 2026
8562659
Try more recordings?
Maffooch Jan 16, 2026
a8479e1
💄 ssl labs json files reformat (#14106)
manuel-sommer Jan 17, 2026
deb198d
:tada: Implement json part for Cloudflare insights parser (#14096)
manuel-sommer Jan 17, 2026
770e990
Consolidation of Template Tags: Make a single use case reusable, and …
Maffooch Jan 17, 2026
c54217c
Merge pull request #14107 from DefectDojo/import-push-to-jira-when-no…
rossops Jan 20, 2026
cc52641
Add additional fields to AssetSerializer (#14109)
Maffooch Jan 20, 2026
9768f1f
Fix Content-Type header bugs in file downloads and MIME type handling…
valentijnscholten Jan 20, 2026
84910a2
fix bleach memory leak & simplify git commit hash checker (#14117)
valentijnscholten Jan 20, 2026
6f48a10
prettify sample scan files (#14113)
valentijnscholten Jan 20, 2026
9c4f51e
tags from parser: fix parsers, add tests and fallback (#14111)
valentijnscholten Jan 20, 2026
311244a
Update versions in application files
Jan 20, 2026
24155ef
Merge pull request #14130 from DefectDojo/release/2.54.2
rossops Jan 20, 2026
91dca67
Update versions in application files
Jan 20, 2026
8ba30c5
Merge branch 'dev' into master-into-dev/2.54.2-2.55.0-dev
Maffooch Jan 20, 2026
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
290 changes: 132 additions & 158 deletions NOTICE

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "Cloudflare Insights"
toc_hide: true
---

Import Cloudflare Insights findings using the **CSV export** provided by Cloudflare.
Import Cloudflare Insights findings using the **CSV export** or via api the **JSON output** provided by Cloudflare.

### Sample Scan Data
Sample Cloudflare Insights files can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/cloudflare_insights).
Expand Down
4 changes: 2 additions & 2 deletions dojo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
from .celery import app as celery_app # noqa: F401

__version__ = "2.55.0-dev"
__url__ = "https://github.com/DefectDojo/django-DefectDojo"
__docs__ = "https://documentation.defectdojo.com"
__url__ = "https://github.com/DefectDojo/django-DefectDojo" # noqa: RUF067
__docs__ = "https://documentation.defectdojo.com" # noqa: RUF067
144 changes: 136 additions & 8 deletions dojo/api_v2/permissions.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import re

from django.db.models import Model
from django.shortcuts import get_object_or_404
from rest_framework import permissions, serializers
from rest_framework.exceptions import (
ParseError,
PermissionDenied,
ValidationError,
)
from rest_framework.request import Request

from dojo.authorization.authorization import (
user_has_configuration_permission,
Expand All @@ -29,7 +31,7 @@
)


def check_post_permission(request, post_model, post_pk, post_permission):
def check_post_permission(request: Request, post_model: Model, post_pk: str | list[str], post_permission: int) -> bool:
if request.method == "POST":
if request.data.get(post_pk) is None:
msg = f"Unable to check for permissions: Attribute '{post_pk}' is required"
Expand All @@ -40,13 +42,13 @@ def check_post_permission(request, post_model, post_pk, post_permission):


def check_object_permission(
request,
obj,
get_permission,
put_permission,
delete_permission,
post_permission=None,
):
request: Request,
obj: Model,
get_permission: int,
put_permission: int,
delete_permission: int,
post_permission: int | None = None,
) -> bool:
if request.method == "GET":
return user_has_permission(request.user, obj, get_permission)
if request.method in {"PUT", "PATCH"}:
Expand Down Expand Up @@ -507,6 +509,25 @@ def has_object_permission(self, request, view, obj):
)


class UserHasAssetPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
request,
Product_Type,
"organization",
Permissions.Product_Type_Add_Product,
)

def has_object_permission(self, request, view, obj):
return check_object_permission(
request,
obj,
Permissions.Product_View,
Permissions.Product_Edit,
Permissions.Product_Delete,
)


class UserHasProductMemberPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
Expand All @@ -523,6 +544,22 @@ def has_object_permission(self, request, view, obj):
)


class UserHasAssetMemberPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
request, Product, "asset", Permissions.Product_Manage_Members,
)

def has_object_permission(self, request, view, obj):
return check_object_permission(
request,
obj,
Permissions.Product_View,
Permissions.Product_Manage_Members,
Permissions.Product_Member_Delete,
)


class UserHasProductGroupPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
Expand All @@ -539,6 +576,22 @@ def has_object_permission(self, request, view, obj):
)


class UserHasAssetGroupPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
request, Product, "asset", Permissions.Product_Group_Add,
)

def has_object_permission(self, request, view, obj):
return check_object_permission(
request,
obj,
Permissions.Product_Group_View,
Permissions.Product_Group_Edit,
Permissions.Product_Group_Delete,
)


class UserHasProductTypePermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.method == "POST":
Expand All @@ -557,6 +610,24 @@ def has_object_permission(self, request, view, obj):
)


class UserHasOrganizationPermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.method == "POST":
return user_has_global_permission(
request.user, Permissions.Product_Type_Add,
)
return True

def has_object_permission(self, request, view, obj):
return check_object_permission(
request,
obj,
Permissions.Product_Type_View,
Permissions.Product_Type_Edit,
Permissions.Product_Type_Delete,
)


class UserHasProductTypeMemberPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
Expand All @@ -576,6 +647,25 @@ def has_object_permission(self, request, view, obj):
)


class UserHasOrganizationMemberPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
request,
Product_Type,
"organization",
Permissions.Product_Type_Manage_Members,
)

def has_object_permission(self, request, view, obj):
return check_object_permission(
request,
obj,
Permissions.Product_Type_View,
Permissions.Product_Type_Manage_Members,
Permissions.Product_Type_Member_Delete,
)


class UserHasProductTypeGroupPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
Expand All @@ -595,6 +685,25 @@ def has_object_permission(self, request, view, obj):
)


class UserHasOrganizationGroupPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
request,
Product_Type,
"organization",
Permissions.Product_Type_Group_Add,
)

def has_object_permission(self, request, view, obj):
return check_object_permission(
request,
obj,
Permissions.Product_Type_Group_View,
Permissions.Product_Type_Group_Edit,
Permissions.Product_Type_Group_Delete,
)


class UserHasReimportPermission(permissions.BasePermission):
def has_permission(self, request, view):
# permission check takes place before validation, so we don't have access to serializer.validated_data()
Expand Down Expand Up @@ -739,6 +848,25 @@ def has_object_permission(self, request, view, obj):
)


class UserHasAssetAPIScanConfigurationPermission(permissions.BasePermission):
def has_permission(self, request, view):
return check_post_permission(
request,
Product,
"asset",
Permissions.Product_API_Scan_Configuration_Add,
)

def has_object_permission(self, request, view, obj):
return check_object_permission(
request,
obj,
Permissions.Product_API_Scan_Configuration_View,
Permissions.Product_API_Scan_Configuration_Edit,
Permissions.Product_API_Scan_Configuration_Delete,
)


class UserHasJiraProductPermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.method == "POST":
Expand Down
9 changes: 8 additions & 1 deletion dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1472,8 +1472,15 @@ class Meta:
exclude = ("inherited_tags",)


class TestTypeCreateSerializer(serializers.ModelSerializer):

class Meta:
model = Test_Type
exclude = ("dynamically_generated",)


class TestTypeSerializer(serializers.ModelSerializer):
tags = TagListSerializerField(required=False)
name = serializers.ReadOnlyField()

class Meta:
model = Test_Type
Expand Down
7 changes: 6 additions & 1 deletion dojo/api_v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ def download_proof(self, request, pk=None):
# send file
response = FileResponse(
file_handle,
content_type=f"{mimetypes.guess_type(str(file_path))}",
content_type=mimetypes.guess_type(str(file_path))[0] or "application/octet-stream",
status=status.HTTP_200_OK,
)
response["Content-Length"] = file_object.size
Expand Down Expand Up @@ -2263,6 +2263,11 @@ class TestTypesViewSet(
def get_queryset(self):
return Test_Type.objects.all().order_by("id")

def get_serializer_class(self):
if self.action == "create":
return serializers.TestTypeCreateSerializer
return serializers.TestTypeSerializer


# @extend_schema_view(**schema_with_prefetch())
# Nested models with prefetch make the response schema too long for Swagger UI
Expand Down
21 changes: 11 additions & 10 deletions dojo/asset/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
CharFieldInFilter,
DateRangeFilter,
DojoFilter,
MultipleChoiceFilter,
NumberInFilter,
ProductSLAFilter,
custom_filter,
)
from dojo.labels import get_labels
from dojo.models import (
Product,
Product_API_Scan_Configuration,
Product_Group,
Product_Member,
Expand All @@ -38,18 +39,18 @@ class ApiAssetFilter(DojoFilter):
name = CharFilter(lookup_expr="icontains")
name_exact = CharFilter(field_name="name", lookup_expr="iexact")
description = CharFilter(lookup_expr="icontains")
business_criticality = CharFilter(method=custom_filter, field_name="business_criticality")
platform = CharFilter(method=custom_filter, field_name="platform")
lifecycle = CharFilter(method=custom_filter, field_name="lifecycle")
origin = CharFilter(method=custom_filter, field_name="origin")
business_criticality = MultipleChoiceFilter(choices=Product.BUSINESS_CRITICALITY_CHOICES)
platform = MultipleChoiceFilter(choices=Product.PLATFORM_CHOICES)
lifecycle = MultipleChoiceFilter(choices=Product.LIFECYCLE_CHOICES)
origin = MultipleChoiceFilter(choices=Product.ORIGIN_CHOICES)
# NumberInFilter
id = NumberInFilter(field_name="id", lookup_expr="in")
asset_manager = NumberInFilter(field_name="product_manager", lookup_expr="in")
technical_contact = NumberInFilter(field_name="technical_contact", lookup_expr="in")
team_manager = NumberInFilter(field_name="team_manager", lookup_expr="in")
prod_type = NumberInFilter(field_name="prod_type", lookup_expr="in")
organization = NumberInFilter(field_name="prod_type", lookup_expr="in")
tid = NumberInFilter(field_name="tid", lookup_expr="in")
prod_numeric_grade = NumberInFilter(field_name="prod_numeric_grade", lookup_expr="in")
asset_numeric_grade = NumberInFilter(field_name="prod_numeric_grade", lookup_expr="in")
user_records = NumberInFilter(field_name="user_records", lookup_expr="in")
regulations = NumberInFilter(field_name="regulations", lookup_expr="in")

Expand Down Expand Up @@ -80,7 +81,7 @@ class ApiAssetFilter(DojoFilter):
("tid", "tid"),
("name", "name"),
("created", "created"),
("prod_numeric_grade", "prod_numeric_grade"),
("prod_numeric_grade", "asset_numeric_grade"),
("business_criticality", "business_criticality"),
("platform", "platform"),
("lifecycle", "lifecycle"),
Expand All @@ -97,8 +98,8 @@ class ApiAssetFilter(DojoFilter):
("team_manager", "team_manager"),
("team_manager__first_name", "team_manager__first_name"),
("team_manager__last_name", "team_manager__last_name"),
("prod_type", "prod_type"),
("prod_type__name", "prod_type__name"),
("prod_type", "organization"),
("prod_type__name", "organization__name"),
("updated", "updated"),
("user_records", "user_records"),
),
Expand Down
12 changes: 9 additions & 3 deletions dojo/asset/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,17 @@ class AssetSerializer(serializers.ModelSerializer):
# V3 fields
asset_meta = ProductMetaSerializer(source="product_meta", read_only=True, many=True)
organization = RelatedOrganizationField(source="prod_type")
asset_numeric_grade = serializers.IntegerField(source="prod_numeric_grade")
enable_asset_tag_inheritance = serializers.BooleanField(source="enable_product_tag_inheritance")
asset_numeric_grade = serializers.IntegerField(source="prod_numeric_grade", required=False, allow_null=True)
enable_asset_tag_inheritance = serializers.BooleanField(source="enable_product_tag_inheritance", required=False, default=False)
asset_managers = serializers.PrimaryKeyRelatedField(
source="product_manager",
queryset=Dojo_User.objects.exclude(is_active=False))
queryset=Dojo_User.objects.exclude(is_active=False),
required=False, allow_null=True,
)
business_criticality = serializers.ChoiceField(choices=Product.BUSINESS_CRITICALITY_CHOICES, allow_blank=True, allow_null=True, required=False)
platform = serializers.ChoiceField(choices=Product.PLATFORM_CHOICES, allow_blank=True, allow_null=True, required=False)
lifecycle = serializers.ChoiceField(choices=Product.LIFECYCLE_CHOICES, allow_blank=True, allow_null=True, required=False)
origin = serializers.ChoiceField(choices=Product.ORIGIN_CHOICES, allow_blank=True, allow_null=True, required=False)

class Meta:
model = Product
Expand Down
8 changes: 4 additions & 4 deletions dojo/asset/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class AssetAPIScanConfigurationViewSet(
filterset_class = AssetAPIScanConfigurationFilterSet
permission_classes = (
IsAuthenticated,
permissions.UserHasProductAPIScanConfigurationPermission,
permissions.UserHasAssetAPIScanConfigurationPermission,
)

def get_queryset(self):
Expand All @@ -68,7 +68,7 @@ class AssetViewSet(
filterset_class = ApiAssetFilter
permission_classes = (
IsAuthenticated,
permissions.UserHasProductPermission,
permissions.UserHasAssetPermission,
)

def get_queryset(self):
Expand Down Expand Up @@ -138,7 +138,7 @@ class AssetMemberViewSet(
filterset_class = AssetMemberFilterSet
permission_classes = (
IsAuthenticated,
permissions.UserHasProductMemberPermission,
permissions.UserHasAssetMemberPermission,
)

def get_queryset(self):
Expand Down Expand Up @@ -166,7 +166,7 @@ class AssetGroupViewSet(
filterset_class = AssetGroupFilterSet
permission_classes = (
IsAuthenticated,
permissions.UserHasProductGroupPermission,
permissions.UserHasAssetGroupPermission,
)

def get_queryset(self):
Expand Down
Loading