Skip to content

Commit 0a1d757

Browse files
Maffoochclaude
andauthored
fix(api): don't scope serializer querysets by AnonymousUser during schema generation (#15344)
The Finding and import serializers scope their endpoint/location PrimaryKeyRelatedField querysets to request.user. During OpenAPI schema generation drf-spectacular instantiates serializers with an AnonymousUser request, which is truthy, so the `if user` branch called get_authorized_*(user=AnonymousUser); filtering a PK field by the AnonymousUser instance raised "Field 'id' expected a number but got AnonymousUser". drf-spectacular then dropped the affected views, producing an operationId collision and a KeyError in the prefetch postprocessing hook. Treat an unauthenticated user as no user so the field falls back to an empty queryset, matching pre-existing behavior for anonymous/schema-time contexts. Add a regression test that renders the full OpenAPI v3 schema and gates that generation produces zero errors (drf-spectacular logs, rather than raises, when it drops a view -- so nothing caught this before). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 26d4032 commit 0a1d757

3 files changed

Lines changed: 44 additions & 0 deletions

File tree

dojo/api_v2/serializers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,12 @@ def __init__(self, *args, **kwargs):
518518
super().__init__(*args, **kwargs)
519519
# Scope endpoint_to_add to the locations/endpoints the requesting user is authorized for.
520520
user = getattr(self.context.get("request"), "user", None)
521+
# An unauthenticated user (e.g. AnonymousUser during OpenAPI schema
522+
# generation, where the request has no real user) is truthy but cannot
523+
# be authorization-scoped; treat it as no user so the field falls back to
524+
# an empty queryset instead of raising on the AnonymousUser instance.
525+
if user is not None and not user.is_authenticated:
526+
user = None
521527
if not settings.V3_FEATURE_LOCATIONS:
522528
# TODO: why do we allow only existing endpoints?
523529
self.fields["endpoint_to_add"] = serializers.PrimaryKeyRelatedField(

dojo/finding/api/serializer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,12 @@ def __init__(self, *args, **kwargs):
354354
# Scope the endpoints field to the references the requesting user is authorized
355355
# for, mirroring the scoping the finding UI form already applies.
356356
user = getattr(self.context.get("request"), "user", None)
357+
# An unauthenticated user (e.g. AnonymousUser during OpenAPI schema
358+
# generation, where the request has no real user) is truthy but cannot
359+
# be authorization-scoped; treat it as no user so the field falls back to
360+
# an empty queryset instead of raising on the AnonymousUser instance.
361+
if user is not None and not user.is_authenticated:
362+
user = None
357363
if not settings.V3_FEATURE_LOCATIONS:
358364
self.fields["endpoints"] = serializers.PrimaryKeyRelatedField(
359365
many=True, required=False,

unittests/test_rest_framework.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4168,3 +4168,35 @@ def __init__(self, *args, **kwargs):
41684168
self.test_type = TestType.STANDARD
41694169
self.deleted_objects = 1
41704170
BaseClass.RESTEndpointTest.__init__(self, *args, **kwargs)
4171+
4172+
4173+
class OpenAPISchemaGenerationTest(DojoAPITestCase):
4174+
4175+
"""
4176+
Render the full OpenAPI v3 schema and gate that generation is error-free.
4177+
4178+
drf-spectacular does not raise when it fails to introspect a view or
4179+
serializer -- it logs an error and drops the operation from the schema, so a
4180+
broken endpoint (e.g. a serializer whose field querysets raise for the
4181+
unauthenticated request used at schema time) silently ships incomplete API
4182+
docs and can trip downstream postprocessing. Fail the build if schema
4183+
generation produces any error.
4184+
"""
4185+
4186+
def test_schema_generation_produces_no_errors(self):
4187+
GENERATOR_STATS.reset()
4188+
generator = spectacular_settings.DEFAULT_GENERATOR_CLASS()
4189+
schema = generator.get_schema(request=None, public=True)
4190+
4191+
# Structurally valid per the OpenAPI 3 spec ...
4192+
validate_schema(schema)
4193+
4194+
# ... and generated without drf-spectacular logging any error (a logged
4195+
# error means an operation was silently dropped from the schema).
4196+
errors = list(GENERATOR_STATS._error_cache)
4197+
self.assertEqual(
4198+
errors,
4199+
[],
4200+
f"OpenAPI schema generation produced {len(errors)} error(s):\n"
4201+
+ "\n".join(str(error) for error in errors),
4202+
)

0 commit comments

Comments
 (0)