diff --git a/docs/content/releases/os_upgrading/2.57.md b/docs/content/releases/os_upgrading/2.57.md new file mode 100644 index 00000000000..a7956a0900d --- /dev/null +++ b/docs/content/releases/os_upgrading/2.57.md @@ -0,0 +1,39 @@ +--- +title: 'Upgrading to DefectDojo Version 2.57.x' +toc_hide: true +weight: -20260406 +description: Deprecation of Credential Manager and Stub Findings +--- + +## Deprecation: Credential Manager + +The Credential Manager feature is being deprecated and will be removed in DefectDojo 2.59.0 on June 1st, 2026. The following API endpoints are affected: + +- `/api/v2/credentials/` +- `/api/v2/credential_mappings/` + +### Required Actions + +Support for the Credential Manager will be fully removed in DefectDojo 2.59.0 (scheduled for June 1st, 2026). After this date, any requests to these endpoints will return a 404 Not Found error and the Credential Manager UI will no longer be available. + +### Timeline + +- **DefectDojo 2.57.x onwards**: Credential Manager is deprecated with deprecation headers on API endpoints +- **DefectDojo 2.59.0 (June 1st, 2026)**: Credential Manager will be removed entirely + +## Deprecation: Stub Findings + +The Stub Findings feature is being deprecated and will be removed in DefectDojo 2.59.0 on June 1st, 2026. The following API endpoint is affected: + +- `/api/v2/stub_findings/` + +### Required Actions + +Support for Stub Findings will be fully removed in DefectDojo 2.59.0 (scheduled for June 1st, 2026). After this date, any requests to this endpoint will return a 404 Not Found error and the Stub Findings UI will no longer be available. + +### Timeline + +- **DefectDojo 2.57.x onwards**: Stub Findings is deprecated with deprecation headers on API endpoints +- **DefectDojo 2.59.0 (June 1st, 2026)**: Stub Findings will be removed entirely + +For more information, check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.57.0). diff --git a/docs/content/releases/os_upgrading/2.59.md b/docs/content/releases/os_upgrading/2.59.md index d5bb68b3ab6..c9921cf6be8 100644 --- a/docs/content/releases/os_upgrading/2.59.md +++ b/docs/content/releases/os_upgrading/2.59.md @@ -2,7 +2,7 @@ title: 'Upgrading to DefectDojo Version 2.59.x' toc_hide: true weight: -20260602 -description: Removal of Questionnaire API Endpoints +description: Removal of Questionnaire API Endpoints, Credential Manager, and Stub Findings --- ## Removal: Questionnaire API Endpoints @@ -19,4 +19,25 @@ As announced in DefectDojo 2.56.0, the following Questionnaire API endpoints hav Any requests to these endpoints will now return a 404 Not Found error. +## Removal: Credential Manager + +As announced in DefectDojo 2.57.0, the Credential Manager feature has been removed. The following API endpoints are no longer available: + +- `/api/v2/credentials/` +- `/api/v2/credential_mappings/` + +### Required Actions + +Any requests to these endpoints will now return a 404 Not Found error. The Credential Manager UI is no longer available. + +## Removal: Stub Findings + +As announced in DefectDojo 2.57.0, the Stub Findings feature has been removed. The following API endpoint is no longer available: + +- `/api/v2/stub_findings/` + +### Required Actions + +Any requests to this endpoint will now return a 404 Not Found error. The Stub Findings UI is no longer available. + For more information, check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.59.0). diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index ffc07f5c1b0..b48f99e0f96 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -880,7 +880,10 @@ def get_queryset(self): @extend_schema_view(**schema_with_prefetch()) class CredentialsViewSet( PrefetchDojoModelViewSet, + DeprecationNoticeMixin, ): + deprecated = True + end_of_life_date = datetime(2026, 6, 1) serializer_class = serializers.CredentialSerializer queryset = Cred_User.objects.all() filter_backends = (DjangoFilterBackend,) @@ -889,13 +892,58 @@ class CredentialsViewSet( def get_queryset(self): return Cred_User.objects.all().order_by("id") + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def retrieve(self, request, *args, **kwargs): + return super().retrieve(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def create(self, request, *args, **kwargs): + return super().create(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def update(self, request, *args, **kwargs): + return super().update(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def partial_update(self, request, *args, **kwargs): + return super().partial_update(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def destroy(self, request, *args, **kwargs): + return super().destroy(request, *args, **kwargs) + # Authorization: configuration # @extend_schema_view(**schema_with_prefetch()) # Nested models with prefetch make the response schema too long for Swagger UI class CredentialsMappingViewSet( PrefetchDojoModelViewSet, + DeprecationNoticeMixin, ): + deprecated = True + end_of_life_date = datetime(2026, 6, 1) serializer_class = serializers.CredentialMappingSerializer queryset = Cred_Mapping.objects.none() filter_backends = (DjangoFilterBackend,) @@ -909,6 +957,48 @@ class CredentialsMappingViewSet( def get_queryset(self): return get_authorized_cred_mappings(Permissions.Credential_View) + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def retrieve(self, request, *args, **kwargs): + return super().retrieve(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def create(self, request, *args, **kwargs): + return super().create(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def update(self, request, *args, **kwargs): + return super().update(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def partial_update(self, request, *args, **kwargs): + return super().partial_update(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def destroy(self, request, *args, **kwargs): + return super().destroy(request, *args, **kwargs) + # Authorization: configuration class FindingTemplatesViewSet( @@ -2167,7 +2257,10 @@ def partial_update(self, request, pk=None): # Nested models with prefetch make the response schema too long for Swagger UI class StubFindingsViewSet( PrefetchDojoModelViewSet, + DeprecationNoticeMixin, ): + deprecated = True + end_of_life_date = datetime(2026, 6, 1) serializer_class = serializers.StubFindingSerializer queryset = Stub_Finding.objects.none() filter_backends = (DjangoFilterBackend,) @@ -2187,6 +2280,48 @@ def get_serializer_class(self): return serializers.StubFindingCreateSerializer return serializers.StubFindingSerializer + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def retrieve(self, request, *args, **kwargs): + return super().retrieve(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def create(self, request, *args, **kwargs): + return super().create(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def update(self, request, *args, **kwargs): + return super().update(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def partial_update(self, request, *args, **kwargs): + return super().partial_update(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def destroy(self, request, *args, **kwargs): + return super().destroy(request, *args, **kwargs) + # Authorization: authenticated, configuration class DevelopmentEnvironmentViewSet( diff --git a/dojo/cred/views.py b/dojo/cred/views.py index ac6a47f2ae3..1ba33939733 100644 --- a/dojo/cred/views.py +++ b/dojo/cred/views.py @@ -9,6 +9,7 @@ from dojo.authorization.authorization_decorators import user_is_authorized, user_is_configuration_authorized from dojo.authorization.roles_permissions import Permissions from dojo.cred.queries import get_authorized_cred_mappings_for_queryset +from dojo.decorators import deprecated_view from dojo.forms import CredMappingForm, CredMappingFormProd, CredUserForm, NoteForm from dojo.models import Cred_Mapping, Cred_User, Engagement, Finding, Product, Test from dojo.utils import Product_Tab, add_breadcrumb, dojo_crypto_encrypt, prepare_for_view @@ -17,6 +18,7 @@ @user_is_configuration_authorized(Permissions.Credential_Add) +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def new_cred(request): if request.method == "POST": tform = CredUserForm(request.POST) @@ -39,6 +41,7 @@ def new_cred(request): @user_is_authorized(Product, Permissions.Product_Edit, "pid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def all_cred_product(request, pid): prod = get_object_or_404(Product, id=pid) creds = Cred_Mapping.objects.filter(product=prod).order_by("cred_id__name") @@ -48,6 +51,7 @@ def all_cred_product(request, pid): @user_is_configuration_authorized(Permissions.Credential_Edit) +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def edit_cred(request, ttid): tool_config = Cred_User.objects.get(pk=ttid) if request.method == "POST": @@ -80,6 +84,7 @@ def edit_cred(request, ttid): @user_is_configuration_authorized(Permissions.Credential_View) +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def view_cred_details(request, ttid): cred = Cred_User.objects.get(pk=ttid) notes = cred.notes.all() @@ -118,6 +123,7 @@ def view_cred_details(request, ttid): @user_is_configuration_authorized(Permissions.Credential_View) +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def cred(request): confs = Cred_User.objects.all().order_by("name", "environment", "username") add_breadcrumb(title="Credential Manager", top_level=True, request=request) @@ -128,6 +134,7 @@ def cred(request): @user_is_authorized(Product, Permissions.Product_View, "pid") @user_is_authorized(Cred_Mapping, Permissions.Credential_View, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def view_cred_product(request, pid, ttid): cred = get_object_or_404( Cred_Mapping.objects.select_related("cred_id"), id=ttid) @@ -184,6 +191,7 @@ def view_cred_product(request, pid, ttid): @user_is_authorized(Engagement, Permissions.Engagement_View, "eid") @user_is_authorized(Cred_Mapping, Permissions.Credential_View, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def view_cred_product_engagement(request, eid, ttid): cred = get_object_or_404( Cred_Mapping.objects.select_related("cred_id"), id=ttid) @@ -233,6 +241,7 @@ def view_cred_product_engagement(request, eid, ttid): @user_is_authorized(Test, Permissions.Test_View, "tid") @user_is_authorized(Cred_Mapping, Permissions.Credential_View, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def view_cred_engagement_test(request, tid, ttid): cred = get_object_or_404( Cred_Mapping.objects.select_related("cred_id"), id=ttid) @@ -284,6 +293,7 @@ def view_cred_engagement_test(request, tid, ttid): @user_is_authorized(Finding, Permissions.Finding_View, "fid") @user_is_authorized(Cred_Mapping, Permissions.Credential_View, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def view_cred_finding(request, fid, ttid): cred = get_object_or_404( Cred_Mapping.objects.select_related("cred_id"), id=ttid) @@ -335,6 +345,7 @@ def view_cred_finding(request, fid, ttid): @user_is_authorized(Product, Permissions.Product_Edit, "pid") @user_is_authorized(Cred_Mapping, Permissions.Credential_Edit, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def edit_cred_product(request, pid, ttid): cred = get_object_or_404( Cred_Mapping.objects.select_related("cred_id"), id=ttid) @@ -363,6 +374,7 @@ def edit_cred_product(request, pid, ttid): @user_is_authorized(Engagement, Permissions.Engagement_Edit, "eid") @user_is_authorized(Cred_Mapping, Permissions.Credential_Edit, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def edit_cred_product_engagement(request, eid, ttid): cred = get_object_or_404( Cred_Mapping.objects.select_related("cred_id"), id=ttid) @@ -396,6 +408,7 @@ def edit_cred_product_engagement(request, eid, ttid): @user_is_authorized(Product, Permissions.Product_Edit, "pid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def new_cred_product(request, pid): prod = get_object_or_404(Product, pk=pid) if request.method == "POST": @@ -431,6 +444,7 @@ def new_cred_product(request, pid): @user_is_authorized(Engagement, Permissions.Engagement_Edit, "eid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def new_cred_product_engagement(request, eid): eng = get_object_or_404(Engagement, pk=eid) @@ -482,6 +496,7 @@ def new_cred_product_engagement(request, eid): @user_is_authorized(Test, Permissions.Test_Edit, "tid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def new_cred_engagement_test(request, tid): test = get_object_or_404(Test, pk=tid) @@ -532,6 +547,7 @@ def new_cred_engagement_test(request, tid): @user_is_authorized(Finding, Permissions.Finding_Edit, "fid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def new_cred_finding(request, fid): finding = get_object_or_404(Finding, pk=fid) @@ -662,29 +678,34 @@ def delete_cred_controller(request, destination_url, elem_id, ttid): @user_is_configuration_authorized(Permissions.Credential_Delete) +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def delete_cred(request, ttid): return delete_cred_controller(request, "cred", 0, ttid=ttid) @user_is_authorized(Product, Permissions.Product_Edit, "pid") @user_is_authorized(Cred_Mapping, Permissions.Credential_Delete, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def delete_cred_product(request, pid, ttid): return delete_cred_controller(request, "all_cred_product", pid, ttid) @user_is_authorized(Engagement, Permissions.Engagement_Edit, "eid") @user_is_authorized(Cred_Mapping, Permissions.Credential_Delete, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def delete_cred_engagement(request, eid, ttid): return delete_cred_controller(request, "view_engagement", eid, ttid) @user_is_authorized(Test, Permissions.Test_Edit, "tid") @user_is_authorized(Cred_Mapping, Permissions.Credential_Delete, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def delete_cred_test(request, tid, ttid): return delete_cred_controller(request, "view_test", tid, ttid) @user_is_authorized(Finding, Permissions.Finding_Edit, "fid") @user_is_authorized(Cred_Mapping, Permissions.Credential_Delete, "ttid") +@deprecated_view("Credential Manager", removal_version="2.59.0", removal_date="June 1, 2026") def delete_cred_finding(request, fid, ttid): return delete_cred_controller(request, "view_finding", fid, ttid) diff --git a/dojo/decorators.py b/dojo/decorators.py index e8a78841bba..f3045402079 100644 --- a/dojo/decorators.py +++ b/dojo/decorators.py @@ -3,6 +3,7 @@ from functools import wraps from django.conf import settings +from django.contrib import messages from django.http import Http404 from django_ratelimit import UNSAFE from django_ratelimit.core import is_ratelimited @@ -162,6 +163,29 @@ def _wrapped(request, *args, **kw): return decorator +def deprecated_view(feature_name, removal_version="X.Y.Z", removal_date="some time in the future"): + """ + Decorator that adds a deprecation warning message to a view. + + Only adds the message on GET requests to avoid duplicate warnings + when POST requests redirect. + """ + 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", + ) + return func(request, *args, **kwargs) + return _wrapped + return decorator + + def require_v3_feature_set(): """Decorator that raises 404 if the V3_FEATURE_LOCATIONS is False.""" diff --git a/dojo/finding/views.py b/dojo/finding/views.py index b361e402d96..c3fa4abb3e2 100644 --- a/dojo/finding/views.py +++ b/dojo/finding/views.py @@ -39,6 +39,7 @@ ) from dojo.authorization.roles_permissions import Permissions from dojo.celery_dispatch import dojo_dispatch_task +from dojo.decorators import deprecated_view from dojo.filters import ( AcceptedFindingFilter, AcceptedFindingFilterWithoutObjectLookups, @@ -2000,6 +2001,7 @@ def add_stub_finding(request, tid): @user_is_authorized(Stub_Finding, Permissions.Finding_Delete, "fid") +@deprecated_view("Stub Findings", removal_version="2.59.0", removal_date="June 1, 2026") def delete_stub_finding(request, fid): finding = get_object_or_404(Stub_Finding, id=fid) @@ -2026,6 +2028,7 @@ def delete_stub_finding(request, fid): @user_is_authorized(Stub_Finding, Permissions.Finding_Edit, "fid") +@deprecated_view("Stub Findings", removal_version="2.59.0", removal_date="June 1, 2026") def promote_to_finding(request, fid): finding = get_object_or_404(Stub_Finding, id=fid) test = finding.test