Skip to content

Commit f50bb18

Browse files
Maffoochclaude
andauthored
remove: Stub Findings (2.57 deprecation, 2.59 EOL) (#14837)
* remove: Stub Findings (announced 2.57, EOL in 2.59) Per the 2.59 release notes, retires the Stub Findings feature in its entirety: UI, API, model, and DB table. Stub_Finding has no inbound foreign keys, so the deletion is self-contained. Endpoint removed (now `404`): - /api/v2/stub_findings/ UI removed: - /finding/<id>/promote, /stub_finding/<id>/add, /stub_finding/<id>/delete - "Potential Findings" table on the test detail page (view_test.html) - The quick-add-form JS handler that powered it - The promote_to_finding.html template Code deleted: - `StubFindingsViewSet`, `StubFindingSerializer`, `StubFindingCreateSerializer` - `add_stub_finding`, `delete_stub_finding`, `promote_to_finding` views - `StubFindingForm`, `DeleteStubFindingForm` - `get_authorized_stub_findings` query helper - `get_stub_findings` method and call site in `dojo/test/views.py` - `Stub_Finding` admin registration and model class - The `Stub_Finding` branch in `dojo/authorization/authorization.py` (now just `Finding` instead of `Finding | Stub_Finding`) - The `Stub_Finding` early-return and union check in `dojo/jira/helper.py` - Unit tests: `StubFindingsTest` (REST), `TestGetAuthorizedStubFindings`, the two `test_user_has_permission_stub_finding_*` tests, and the three Selenium tests in `tests/test_test.py` - Dead `#stub_findings` JS in `view_objects.html` / `view_objects_eng.html` Schema dropped via 0265_remove_stub_finding: - `DeleteModel('Stub_Finding')` The 2.59 upgrade doc already documents the removal; no doc update. Note: PR 2 also adds a 0265_* migration. Whichever PR merges second must rebase the migration filename and `dependencies` tuple accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: ruff F401 + drop stub_finding refs from fixtures - dojo/finding/views.py: drop now-unused `json` and `formats` imports (the only callers were in the deleted stub-finding views). - tests/test_test.py: drop the now-unused `on_exception_html_source_logger` import. - Remove dojo.stub_finding rows and watson.searchentry rows pointing at that content type from all four data fixtures so loaddata stops faulting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: adjust deleted_objects count + drop dependent merge UI test - unittests/test_rest_framework.py: EngagementTest.deleted_objects went from 23 -> 21 because the cascading delete no longer pulls 2 Stub_Finding rows. - tests/test_test.py: drop test_merge_findings (the integration test needed two findings; the second one used to come from the stub finding promote flow which is now gone). The merge functionality is still covered by the unit tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 817a36c commit f50bb18

25 files changed

Lines changed: 109 additions & 1604 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,4 @@ docs/.hugo_build.lock
152152

153153
# claude etc
154154
MEMORY.md
155+
.claude/

dojo/api_v2/serializers.py

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@
8787
SLA_Configuration,
8888
Sonarqube_Issue,
8989
Sonarqube_Issue_Transition,
90-
Stub_Finding,
9190
System_Settings,
9291
Test,
9392
Test_Import,
@@ -2157,35 +2156,6 @@ class Meta:
21572156
fields = "__all__"
21582157

21592158

2160-
class StubFindingSerializer(serializers.ModelSerializer):
2161-
class Meta:
2162-
model = Stub_Finding
2163-
fields = "__all__"
2164-
2165-
def validate_severity(self, value: str) -> str:
2166-
if value not in SEVERITIES:
2167-
msg = f"Severity must be one of the following: {SEVERITIES}"
2168-
raise serializers.ValidationError(msg)
2169-
return value
2170-
2171-
2172-
class StubFindingCreateSerializer(serializers.ModelSerializer):
2173-
test = serializers.PrimaryKeyRelatedField(queryset=Test.objects.all())
2174-
2175-
class Meta:
2176-
model = Stub_Finding
2177-
fields = "__all__"
2178-
extra_kwargs = {
2179-
"reporter": {"default": serializers.CurrentUserDefault()},
2180-
}
2181-
2182-
def validate_severity(self, value: str) -> str:
2183-
if value not in SEVERITIES:
2184-
msg = f"Severity must be one of the following: {SEVERITIES}"
2185-
raise serializers.ValidationError(msg)
2186-
return value
2187-
2188-
21892159
class ProductSerializer(serializers.ModelSerializer):
21902160
findings_count = serializers.SerializerMethodField()
21912161
findings_list = serializers.SerializerMethodField()

dojo/api_v2/views.py

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
)
7676
from dojo.finding.queries import (
7777
get_authorized_findings,
78-
get_authorized_stub_findings,
7978
)
8079
from dojo.finding.views import (
8180
duplicate_cluster,
@@ -128,7 +127,6 @@
128127
SLA_Configuration,
129128
Sonarqube_Issue,
130129
Sonarqube_Issue_Transition,
131-
Stub_Finding,
132130
System_Settings,
133131
Test,
134132
Test_Import,
@@ -2213,77 +2211,6 @@ def partial_update(self, request, pk=None):
22132211
return Response(response, status=status.HTTP_405_METHOD_NOT_ALLOWED)
22142212

22152213

2216-
# Authorization: object-based
2217-
# @extend_schema_view(**schema_with_prefetch())
2218-
# Nested models with prefetch make the response schema too long for Swagger UI
2219-
class StubFindingsViewSet(
2220-
PrefetchDojoModelViewSet,
2221-
DeprecationNoticeMixin,
2222-
):
2223-
deprecated = True
2224-
end_of_life_date = datetime(2026, 6, 1)
2225-
serializer_class = serializers.StubFindingSerializer
2226-
queryset = Stub_Finding.objects.none()
2227-
filter_backends = (DjangoFilterBackend,)
2228-
filterset_fields = ["id", "title", "date", "severity", "description"]
2229-
permission_classes = (
2230-
IsAuthenticated,
2231-
permissions.UserHasFindingPermission,
2232-
)
2233-
2234-
def get_queryset(self):
2235-
return get_authorized_stub_findings(
2236-
Permissions.Finding_View,
2237-
).distinct()
2238-
2239-
def get_serializer_class(self):
2240-
if self.request and self.request.method == "POST":
2241-
return serializers.StubFindingCreateSerializer
2242-
return serializers.StubFindingSerializer
2243-
2244-
@extend_schema(
2245-
deprecated=True,
2246-
description="This endpoint is deprecated and will be removed on 2026-06-01.",
2247-
)
2248-
def list(self, request, *args, **kwargs):
2249-
return super().list(request, *args, **kwargs)
2250-
2251-
@extend_schema(
2252-
deprecated=True,
2253-
description="This endpoint is deprecated and will be removed on 2026-06-01.",
2254-
)
2255-
def retrieve(self, request, *args, **kwargs):
2256-
return super().retrieve(request, *args, **kwargs)
2257-
2258-
@extend_schema(
2259-
deprecated=True,
2260-
description="This endpoint is deprecated and will be removed on 2026-06-01.",
2261-
)
2262-
def create(self, request, *args, **kwargs):
2263-
return super().create(request, *args, **kwargs)
2264-
2265-
@extend_schema(
2266-
deprecated=True,
2267-
description="This endpoint is deprecated and will be removed on 2026-06-01.",
2268-
)
2269-
def update(self, request, *args, **kwargs):
2270-
return super().update(request, *args, **kwargs)
2271-
2272-
@extend_schema(
2273-
deprecated=True,
2274-
description="This endpoint is deprecated and will be removed on 2026-06-01.",
2275-
)
2276-
def partial_update(self, request, *args, **kwargs):
2277-
return super().partial_update(request, *args, **kwargs)
2278-
2279-
@extend_schema(
2280-
deprecated=True,
2281-
description="This endpoint is deprecated and will be removed on 2026-06-01.",
2282-
)
2283-
def destroy(self, request, *args, **kwargs):
2284-
return super().destroy(request, *args, **kwargs)
2285-
2286-
22872214
# Authorization: authenticated, configuration
22882215
class DevelopmentEnvironmentViewSet(
22892216
DojoModelViewSet,

dojo/authorization/authorization.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
Product_Type_Group,
2828
Product_Type_Member,
2929
Risk_Acceptance,
30-
Stub_Finding,
3130
Test,
3231
)
3332
from dojo.request_cache import cache_for_request
@@ -135,9 +134,9 @@ def user_has_permission(user: Dojo_User, obj: Model, permission: int) -> bool:
135134
if obj.engagement is not None:
136135
return user_has_permission(user, obj.engagement.product, permission)
137136
return user_has_global_permission(user, permission)
138-
if ((
139-
isinstance(obj, Finding | Stub_Finding)
140-
) and permission in Permissions.get_finding_permissions()) or (
137+
if (
138+
isinstance(obj, Finding) and permission in Permissions.get_finding_permissions()
139+
) or (
141140
isinstance(obj, Finding_Group)
142141
and permission in Permissions.get_finding_group_permissions()
143142
):
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Remove the Stub Findings feature.
2+
3+
Drops the ``Stub_Finding`` model. Stub Findings was deprecated in 2.57.0 and
4+
is end-of-life in 2.59. The model has no inbound foreign keys, so the
5+
deletion is self-contained.
6+
7+
Note: rebase the filename and the ``dependencies`` tuple to point at
8+
whatever the latest migration is at merge time if another migration has
9+
landed first.
10+
"""
11+
12+
from django.db import migrations
13+
14+
15+
class Migration(migrations.Migration):
16+
17+
dependencies = [
18+
("dojo", "0264_alter_url_identity_hash_alter_urlevent_identity_hash"),
19+
]
20+
21+
operations = [
22+
migrations.DeleteModel(
23+
name="Stub_Finding",
24+
),
25+
]

dojo/finding/queries.py

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
Product_Member,
1818
Product_Type_Group,
1919
Product_Type_Member,
20-
Stub_Finding,
2120
Test_Import_Finding_Action,
2221
Vulnerability_Id,
2322
)
@@ -112,48 +111,6 @@ def get_authorized_findings_for_queryset(permission, queryset, user=None):
112111
)
113112

114113

115-
# Cached: all parameters are hashable, no dynamic queryset filtering
116-
@cache_for_request
117-
def get_authorized_stub_findings(permission):
118-
user = get_current_user()
119-
120-
if user is None:
121-
return Stub_Finding.objects.none()
122-
123-
if user.is_superuser:
124-
return Stub_Finding.objects.all().order_by("id")
125-
126-
if user_has_global_permission(user, permission):
127-
return Stub_Finding.objects.all().order_by("id")
128-
129-
roles = get_roles_for_permission(permission)
130-
131-
# Get authorized product/product_type IDs via subqueries
132-
authorized_product_type_roles = Product_Type_Member.objects.filter(
133-
user=user, role__in=roles,
134-
).values("product_type_id")
135-
136-
authorized_product_roles = Product_Member.objects.filter(
137-
user=user, role__in=roles,
138-
).values("product_id")
139-
140-
authorized_product_type_groups = Product_Type_Group.objects.filter(
141-
group__users=user, role__in=roles,
142-
).values("product_type_id")
143-
144-
authorized_product_groups = Product_Group.objects.filter(
145-
group__users=user, role__in=roles,
146-
).values("product_id")
147-
148-
# Filter using IN with Subquery - no annotations needed
149-
return Stub_Finding.objects.filter(
150-
Q(test__engagement__product__prod_type_id__in=Subquery(authorized_product_type_roles))
151-
| Q(test__engagement__product_id__in=Subquery(authorized_product_roles))
152-
| Q(test__engagement__product__prod_type_id__in=Subquery(authorized_product_type_groups))
153-
| Q(test__engagement__product_id__in=Subquery(authorized_product_groups)),
154-
).order_by("id")
155-
156-
157114
# Cached: all parameters are hashable, no dynamic queryset filtering
158115
@cache_for_request
159116
def get_authorized_vulnerability_ids(permission, user=None):

dojo/finding/urls.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,6 @@
164164
views.set_finding_as_original, name="set_finding_as_original"),
165165
re_path(r"^finding/(?P<fid>\d+)/remediation_date$", views.remediation_date,
166166
name="remediation_date"),
167-
# stub findings
168-
re_path(r"^stub_finding/(?P<tid>\d+)/add$",
169-
views.add_stub_finding, name="add_stub_finding"),
170-
re_path(r"^stub_finding/(?P<fid>\d+)/promote$",
171-
views.promote_to_finding, name="promote_to_finding"),
172-
re_path(r"^stub_finding/(?P<fid>\d+)/delete$",
173-
views.delete_stub_finding, name="delete_stub_finding"),
174167

175168
# template findings
176169

0 commit comments

Comments
 (0)