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
6 changes: 6 additions & 0 deletions dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,12 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Scope endpoint_to_add to the locations/endpoints the requesting user is authorized for.
user = getattr(self.context.get("request"), "user", None)
# An unauthenticated user (e.g. AnonymousUser during OpenAPI schema
# generation, where the request has no real user) is truthy but cannot
# be authorization-scoped; treat it as no user so the field falls back to
# an empty queryset instead of raising on the AnonymousUser instance.
if user is not None and not user.is_authenticated:
user = None
if not settings.V3_FEATURE_LOCATIONS:
# TODO: why do we allow only existing endpoints?
self.fields["endpoint_to_add"] = serializers.PrimaryKeyRelatedField(
Expand Down
6 changes: 6 additions & 0 deletions dojo/finding/api/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,12 @@ def __init__(self, *args, **kwargs):
# Scope the endpoints field to the references the requesting user is authorized
# for, mirroring the scoping the finding UI form already applies.
user = getattr(self.context.get("request"), "user", None)
# An unauthenticated user (e.g. AnonymousUser during OpenAPI schema
# generation, where the request has no real user) is truthy but cannot
# be authorization-scoped; treat it as no user so the field falls back to
# an empty queryset instead of raising on the AnonymousUser instance.
if user is not None and not user.is_authenticated:
user = None
if not settings.V3_FEATURE_LOCATIONS:
self.fields["endpoints"] = serializers.PrimaryKeyRelatedField(
many=True, required=False,
Expand Down
32 changes: 32 additions & 0 deletions unittests/test_rest_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -4168,3 +4168,35 @@ def __init__(self, *args, **kwargs):
self.test_type = TestType.STANDARD
self.deleted_objects = 1
BaseClass.RESTEndpointTest.__init__(self, *args, **kwargs)


class OpenAPISchemaGenerationTest(DojoAPITestCase):

"""
Render the full OpenAPI v3 schema and gate that generation is error-free.

drf-spectacular does not raise when it fails to introspect a view or
serializer -- it logs an error and drops the operation from the schema, so a
broken endpoint (e.g. a serializer whose field querysets raise for the
unauthenticated request used at schema time) silently ships incomplete API
docs and can trip downstream postprocessing. Fail the build if schema
generation produces any error.
"""

def test_schema_generation_produces_no_errors(self):
GENERATOR_STATS.reset()
generator = spectacular_settings.DEFAULT_GENERATOR_CLASS()
schema = generator.get_schema(request=None, public=True)

# Structurally valid per the OpenAPI 3 spec ...
validate_schema(schema)

# ... and generated without drf-spectacular logging any error (a logged
# error means an operation was silently dropped from the schema).
errors = list(GENERATOR_STATS._error_cache)
self.assertEqual(
errors,
[],
f"OpenAPI schema generation produced {len(errors)} error(s):\n"
+ "\n".join(str(error) for error in errors),
)
Loading