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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ When you enable Locations on an existing DefectDojo Pro instance, the data alrea

Note that migration is **one-way**. There is no automated rollback path that re-creates Endpoints from Locations.

> **Endpoints are deprecated.** As of **3.2.201**, Endpoints are deprecated in favour of Locations and are scheduled for **removal in 3.4.0**. Until then the Endpoints UI and the read-only Endpoint API stay available, and the **DEPRECATED** badges shown on the Endpoints menu, the Endpoint list pages, and a Finding's endpoint tables link here. Enable Locations and run the migration below before 3.4.0.
> **Endpoints are deprecated.** As of **3.2.201**, Endpoints are deprecated in favour of Locations. The Endpoints pages go away in **3.6.0 (December 2026)**. The read-only Endpoint API (`/api/v2/endpoints/`, `/api/v2/endpoint_status/`) stays until its own deprecation announcement. The Endpoints menu, the Endpoint list pages, and a Finding's endpoint tables carry a deprecation banner. Enable Locations and run the migration below before 3.6.0.

## Running the migration from the Feature Flags page

Expand Down
13 changes: 8 additions & 5 deletions docs/content/navigation/PRO__menu_badges.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The distinction is deliberate, because the two states call for different respons

**`DEPRECATED`** means a removal has been announced. Hovering the badge tells you the release it goes away in, and clicking it opens the deprecation notice:

> \<Feature\> is deprecated and will be removed by \<release\>. Click for the deprecation notice.
> \<Feature\> is deprecated and will be removed by \<release\> (\<month\>). Click for the deprecation notice.

**`LEGACY`** means the feature has been superseded but no removal has been scheduled. There is deliberately no date in the hover text, because inventing one would be worse than saying nothing. Instead it names the replacement and links to its documentation:

Expand All @@ -42,6 +42,10 @@ A `LEGACY` feature keeps working and keeps getting fixes. It just will not gain

Both badges are links, because a tooltip closes the moment your pointer leaves it and so cannot hold a clickable link. Clicking either badge opens its notice in a new tab; it does not navigate the menu entry underneath.

## Pages of a deprecated feature carry a banner

Every page that belongs to a `DEPRECATED` feature opens with a warning banner. The banner carries the same red badge, names the release the feature goes away in, and says what to do. It reads the same announcement as the sidebar badge, so the two always agree.

## What currently carries a badge

**`SOON`**
Expand All @@ -55,10 +59,9 @@ Both badges are links, because a tooltip closes the moment your pointer leaves i

**`DEPRECATED`**

* **Settings > Configuration > Tool Types**
* **Settings > Configuration > Tool Configurations**

Both are removed in **3.5.0**, along with the API-based (pull) parsers they exist to configure. The [3.2 upgrade notes](/releases/os_upgrading/3.2/) explain what to migrate to and by when.
* **Settings > Configuration > Tool Types** and **Tool Configurations**, removed in **3.5.0 (November 2026)** along with the API-based (pull) parsers they exist to configure. The [3.2 upgrade notes](/releases/os_upgrading/3.2/) explain what to migrate to and by when.
* **API Scan Configurations** on an Asset, removed in **3.5.0 (November 2026)** for the same reason.
* **Endpoints** (the Endpoints menu, the Endpoint and Host list pages, and a Finding's endpoint tables), removed in **3.6.0 (December 2026)** in favour of Locations. See [Migrating from Endpoints](/asset_modelling/locations/pro__migrating_from_endpoints/).

![DEPRECATED badges under Settings > Configuration](images/menu_badge_deprecated.png)

Expand Down
28 changes: 28 additions & 0 deletions docs/content/releases/os_upgrading/3.4.100.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: 'Upgrading to DefectDojo Version 3.4.100'
toc_hide: true
weight: -20261013
description: The deprecated Tool Type, Tool Configuration, and API Scan Configuration API endpoints send a corrected end-of-life date and a single-sentence Classic UI warning.
---

## Deprecated API endpoints: corrected end-of-life date

The deprecated Tool Type, Tool Configuration, and API Scan Configuration API endpoints send a
corrected end-of-life date. They now send `X-End-Of-Life-Date: 2026-11-02T00:00:00`, the actual
3.5.0 release day. The earlier value was `2026-11-01T00:00:00`, one day off.

The affected endpoints:

- `/api/v2/tool_types/`
- `/api/v2/tool_configurations/`
- `/api/v2/product_api_scan_configurations/`
- `/api/v2/asset_api_scan_configurations/`

Their Classic UI warning is now one sentence: `<Feature> are deprecated and will be removed in
DefectDojo 3.5.0 (November 2026). Please plan to migrate away from this feature.`

### What you need to do

Nothing. If your automation reads `X-End-Of-Life-Date`, it now receives the correct day.

For more information, check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/3.4.100).
14 changes: 7 additions & 7 deletions dojo/api_v2/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, time

import pghistory
from dateutil.relativedelta import relativedelta
Expand Down Expand Up @@ -37,6 +37,7 @@
)
from dojo.authorization import api_permissions as permissions
from dojo.authorization.authorization import user_has_permission_or_403
from dojo.deprecations import get_deprecation
from dojo.endpoint.ui.views import get_endpoint_ids
from dojo.engagement.queries import get_authorized_engagements
from dojo.filters import (
Expand Down Expand Up @@ -166,14 +167,13 @@ class PrefetchDojoModelViewSet(

class DeprecationNoticeMixin:

deprecated: bool | None = None
end_of_life_date: datetime | None = None
deprecation: str = ""

def finalize_response(self, request, response, *args, **kwargs):
if self.deprecated is not None:
response["X-Deprecated"] = self.deprecated
if self.end_of_life_date is not None:
response["X-End-Of-Life-Date"] = self.end_of_life_date.isoformat()
notice = get_deprecation(self.deprecation)
if notice is not None and not notice.removed:
response["X-Deprecated"] = True
response["X-End-Of-Life-Date"] = datetime.combine(notice.removal_date, time.min).isoformat()
return super().finalize_response(request, response, *args, **kwargs)


Expand Down
5 changes: 1 addition & 4 deletions dojo/asset/api/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from datetime import datetime
from functools import partial

from django.db.models import OuterRef, Value
Expand Down Expand Up @@ -34,14 +33,12 @@


# Authorization: object-based
# Deprecated in 3.2.0, removal planned for 3.5.0 (serves the API-based pull parsers).
@extend_schema_view(**schema_with_prefetch())
class AssetAPIScanConfigurationViewSet(
DeprecationNoticeMixin,
PrefetchDojoModelViewSet,
):
deprecated = True
end_of_life_date = datetime(2026, 11, 1)
deprecation = "api_scan_configuration"
serializer_class = serializers.AssetAPIScanConfigurationSerializer
queryset = Product_API_Scan_Configuration.objects.none()
filter_backends = (DjangoFilterBackend,)
Expand Down
24 changes: 14 additions & 10 deletions dojo/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
from django.http import Http404
from django_ratelimit import UNSAFE
from django_ratelimit.core import is_ratelimited
from django_ratelimit.exceptions import Ratelimited

from dojo.deprecations import get_deprecation
from dojo.models import Dojo_User

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -169,26 +171,28 @@ def _wrapped(request, *args, **kw):
return decorator


def deprecated_view(feature_name, removal_version="X.Y.Z", removal_date="some time in the future"):
def deprecated_view(key):
"""
Decorator that adds a deprecation warning message to a view.
Show the Classic UI warning that ``dojo.deprecations`` declares for ``key``.

Only adds the message on GET requests to avoid duplicate warnings
when POST requests redirect.
"""
if get_deprecation(key) is None:
msg = f"{key!r} has no declaration in dojo/deprecations.py"
raise ImproperlyConfigured(msg)

def decorator(func):
@wraps(func)
def _wrapped(request, *args, **kwargs):
if request.method == "GET":
messages.add_message(
request,
messages.WARNING,
f"{feature_name} is deprecated and will be removed in DefectDojo v{removal_version} "
f"({removal_date}). Please plan to migrate away from this feature.",
extra_tags="alert-warning",
)
notice = get_deprecation(key)
if request.method == "GET" and notice is not None and not notice.removed:
messages.add_message(request, messages.WARNING, notice.message(), extra_tags="alert-warning")
return func(request, *args, **kwargs)

_wrapped.deprecation = key
return _wrapped

return decorator


Expand Down
105 changes: 105 additions & 0 deletions dojo/deprecations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import re
from dataclasses import dataclass
from datetime import date

from django.utils import translation
from django.utils.translation import gettext_lazy as _

# Copied from the release milestones. A deprecation names its removal release, and its
# day always comes from here.
RELEASE_DATES = {
"3.3.0": date(2026, 9, 8),
"3.4.0": date(2026, 10, 5),
"3.5.0": date(2026, 11, 2),
"3.6.0": date(2026, 12, 7),
}

_MINOR_RELEASE = re.compile(r"\d+\.\d+\.0")


@dataclass(frozen=True)
class Deprecation:
key: str
title: str
removal_version: str
notice_url: str
action: str = ""
removed: bool = False

@property
def removal_date(self) -> date:
return RELEASE_DATES[self.removal_version]

@property
def removal_label(self) -> str:
return f"{self.removal_version} ({self.removal_date:%B %Y})"

def message(self) -> str:
# Keep the whole sentence in English: no catalog holds it.
with translation.override(None):
return (
f"{self.title} are deprecated and will be removed in DefectDojo {self.removal_label}. "
"Please plan to migrate away from this feature."
)


_DEPRECATIONS: dict[str, Deprecation] = {}


_VERSION = re.compile(r"v?(\d+)\.(\d+)\.(\d+)")


def _release(version: str) -> tuple[int, ...]:
match = _VERSION.match(version)
if match is None:
msg = f"{version!r} is not an X.Y.Z version"
raise ValueError(msg)
return tuple(int(part) for part in match.groups())


def register_deprecation(entry: Deprecation, *, override: bool = False) -> None:
if not _MINOR_RELEASE.fullmatch(entry.removal_version):
msg = f"{entry.key}: a feature is removed in a minor release (X.Y.0), not {entry.removal_version}"
raise ValueError(msg)
if entry.removal_version not in RELEASE_DATES:
msg = f"{entry.key}: add {entry.removal_version} to RELEASE_DATES before naming it"
raise ValueError(msg)
if entry.key in _DEPRECATIONS and not override:
return
_DEPRECATIONS[entry.key] = entry


def get_deprecation(key: str) -> Deprecation | None:
return _DEPRECATIONS.get(key)


def active_deprecations() -> list[Deprecation]:
return [entry for entry in _DEPRECATIONS.values() if not entry.removed]


def overdue_deprecations(version: str) -> list[Deprecation]:
current = _release(version)
return [entry for entry in active_deprecations() if _release(entry.removal_version) < current]


_UPGRADING_3_2 = "https://docs.defectdojo.com/releases/os_upgrading/3.2/"

register_deprecation(
Deprecation(key="tool_type", title=_("Tool Types"), removal_version="3.5.0", notice_url=_UPGRADING_3_2),
)
register_deprecation(
Deprecation(
key="tool_configuration",
title=_("Tool Configurations"),
removal_version="3.5.0",
notice_url=_UPGRADING_3_2,
),
)
register_deprecation(
Deprecation(
key="api_scan_configuration",
title=_("API Scan Configurations"),
removal_version="3.5.0",
notice_url=_UPGRADING_3_2,
),
)
5 changes: 1 addition & 4 deletions dojo/product/api/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from datetime import datetime
from functools import partial

from django.db.models import OuterRef, Value
Expand Down Expand Up @@ -35,14 +34,12 @@


# Authorization: object-based
# Deprecated in 3.2.0, removal planned for 3.5.0 (serves the API-based pull parsers).
@extend_schema_view(**schema_with_prefetch())
class ProductAPIScanConfigurationViewSet(
DeprecationNoticeMixin,
PrefetchDojoModelViewSet,
):
deprecated = True
end_of_life_date = datetime(2026, 11, 1)
deprecation = "api_scan_configuration"
serializer_class = ProductAPIScanConfigurationSerializer
queryset = Product_API_Scan_Configuration.objects.none()
filter_backends = (DjangoFilterBackend,)
Expand Down
8 changes: 4 additions & 4 deletions dojo/product/ui/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1727,7 +1727,7 @@ def delete_product_authorized_user(request, pid, user_id):
return HttpResponseRedirect(reverse("view_product", args=(pid,)))


@deprecated_view("API Scan Configuration", removal_version="3.5.0", removal_date="November 2026")
@deprecated_view("api_scan_configuration")
def add_api_scan_configuration(request, pid):
product = get_object_or_404(Product, id=pid)
if request.method == "POST":
Expand Down Expand Up @@ -1771,7 +1771,7 @@ def add_api_scan_configuration(request, pid):
})


@deprecated_view("API Scan Configuration", removal_version="3.5.0", removal_date="November 2026")
@deprecated_view("api_scan_configuration")
def view_api_scan_configurations(request, pid):
product_api_scan_configurations = Product_API_Scan_Configuration.objects.filter(product=pid)

Expand All @@ -1785,7 +1785,7 @@ def view_api_scan_configurations(request, pid):
})


@deprecated_view("API Scan Configuration", removal_version="3.5.0", removal_date="November 2026")
@deprecated_view("api_scan_configuration")
def edit_api_scan_configuration(request, pid, pascid):
product_api_scan_configuration = get_object_or_404(Product_API_Scan_Configuration, id=pascid)

Expand Down Expand Up @@ -1831,7 +1831,7 @@ def edit_api_scan_configuration(request, pid, pascid):
})


@deprecated_view("API Scan Configuration", removal_version="3.5.0", removal_date="November 2026")
@deprecated_view("api_scan_configuration")
def delete_api_scan_configuration(request, pid, pascid):
product_api_scan_configuration = get_object_or_404(Product_API_Scan_Configuration, id=pascid)

Expand Down
5 changes: 1 addition & 4 deletions dojo/tool_config/api/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
from datetime import datetime

from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema_view
Expand All @@ -13,14 +12,12 @@


# Authorization: configurations
# Deprecated in 3.2.0, removal planned for 3.5.0 (serves the API-based pull parsers).
@extend_schema_view(**schema_with_prefetch())
class ToolConfigurationsViewSet(
DeprecationNoticeMixin,
PrefetchDojoModelViewSet,
):
deprecated = True
end_of_life_date = datetime(2026, 11, 1)
deprecation = "tool_configuration"
serializer_class = ToolConfigurationSerializer
queryset = Tool_Configuration.objects.none()
filter_backends = (DjangoFilterBackend,)
Expand Down
6 changes: 3 additions & 3 deletions dojo/tool_config/ui/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
logger = logging.getLogger(__name__)


@deprecated_view("Tool Configuration", removal_version="3.5.0", removal_date="November 2026")
@deprecated_view("tool_configuration")
def new_tool_config(request):
if request.method == "POST":
tform = ToolConfigForm(request.POST)
Expand Down Expand Up @@ -51,7 +51,7 @@ def new_tool_config(request):
{"tform": tform})


@deprecated_view("Tool Configuration", removal_version="3.5.0", removal_date="November 2026")
@deprecated_view("tool_configuration")
def edit_tool_config(request, ttid):
tool_config = Tool_Configuration.objects.get(pk=ttid)
# Read before the form binds, which overwrites the instance in place.
Expand Down Expand Up @@ -100,7 +100,7 @@ def edit_tool_config(request, ttid):
})


@deprecated_view("Tool Configuration", removal_version="3.5.0", removal_date="November 2026")
@deprecated_view("tool_configuration")
def tool_config(request):
confs = Tool_Configuration.objects.all().order_by("name")
add_breadcrumb(title="Tool Configuration List", top_level=not len(request.GET), request=request)
Expand Down
Loading
Loading