From 648554b67dc5b75aee04b74df6718caf47a15d3a Mon Sep 17 00:00:00 2001 From: kiblik Date: Thu, 15 Feb 2024 22:40:42 +0000 Subject: [PATCH] API: removal of drf_yasg (OpenAPI 2.0 Swagger) (#9108) * Removal of drf_yasg * Clean filterwarnings --- NOTICE | 43 - .../en/getting_started/upgrading/2.32.md | 9 +- docs/content/en/integrations/api-v2-docs.md | 7 +- dojo/api_v2/mixins.py | 5 - dojo/api_v2/prefetch/__init__.py | 3 +- dojo/api_v2/prefetch/schema.py | 168 +--- dojo/api_v2/schema/__init__.py | 17 - dojo/api_v2/schema/extra_schema.py | 140 --- dojo/api_v2/schema/utils.py | 63 -- dojo/api_v2/serializers.py | 23 +- dojo/api_v2/views.py | 443 +--------- dojo/risk_acceptance/api.py | 9 - dojo/settings/settings.dist.py | 30 - dojo/urls.py | 20 - requirements.txt | 1 - unittests/test_apiv2_metadata.py | 2 +- unittests/test_swagger_schema.py | 835 ------------------ 17 files changed, 70 insertions(+), 1748 deletions(-) delete mode 100644 dojo/api_v2/schema/__init__.py delete mode 100644 dojo/api_v2/schema/extra_schema.py delete mode 100644 dojo/api_v2/schema/utils.py delete mode 100644 unittests/test_swagger_schema.py diff --git a/NOTICE b/NOTICE index e939bd7fc9..7733257f54 100644 --- a/NOTICE +++ b/NOTICE @@ -3910,49 +3910,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -drf-yasg -1.20.0 -BSD License -.. |br| raw:: html - -
- -####### -License -####### - -******************** -BSD 3-Clause License -******************** - -Copyright (c) 2017 - 2019, Cristian V. |br|\ All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ecdsa 0.17.0 MIT diff --git a/docs/content/en/getting_started/upgrading/2.32.md b/docs/content/en/getting_started/upgrading/2.32.md index 0d04c771e3..59081b3085 100644 --- a/docs/content/en/getting_started/upgrading/2.32.md +++ b/docs/content/en/getting_started/upgrading/2.32.md @@ -2,6 +2,13 @@ title: 'Upgrading to DefectDojo Version 2.32.x' toc_hide: true weight: -20240205 -description: No special instructions. +description: Breaking change: Removal of OpenAPI 2.0 Swagger --- There are no special instructions for upgrading to 2.32.x. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.32.0) for the contents of the release. + +**Removal** + +The OpenAPI 2.0 Swagger API documentation was removed in favor of the existing +OpenAPI 3.0 API documentation page. + +*Note*: The API has not changed in any way and behaves the same between OAPI2 and OAPI3 \ No newline at end of file diff --git a/docs/content/en/integrations/api-v2-docs.md b/docs/content/en/integrations/api-v2-docs.md index c64dfcc891..7b8d1f7956 100644 --- a/docs/content/en/integrations/api-v2-docs.md +++ b/docs/content/en/integrations/api-v2-docs.md @@ -16,11 +16,8 @@ Docs link on the user drop down menu in the header. ![image](../../images/api_v2_1.png) -The documentation is generated using [Django Rest Framework -Yet Another Swagger Generator](https://github.com/axnsan12/drf-yasg/), and is -interactive. On the top of API v2 docs is a link that generates an OpenAPI v2 spec. - -As a preparation to move to OpenAPIv3, we have added an compatible spec and documentation at [`/api/v2/oa3/swagger-ui/`](https://demo.defectdojo.org/api/v2/oa3/swagger-ui/) +The documentation is generated using [drf-spectacular](https://drf-spectacular.readthedocs.io/) at [`/api/v2/oa3/swagger-ui/`](https://demo.defectdojo.org/api/v2/oa3/swagger-ui/), and is +interactive. On the top of API v2 docs is a link that generates an OpenAPI v3 spec. To interact with the documentation, a valid Authorization header value is needed. Visit the `/api/key-v2` view to generate your diff --git a/dojo/api_v2/mixins.py b/dojo/api_v2/mixins.py index e0770971f3..54d55a76d0 100644 --- a/dojo/api_v2/mixins.py +++ b/dojo/api_v2/mixins.py @@ -1,7 +1,6 @@ from django.db import DEFAULT_DB_ALIAS from django.contrib.admin.utils import NestedObjects from drf_spectacular.utils import extend_schema -from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework import status from rest_framework.authtoken.models import Token @@ -17,10 +16,6 @@ class DeletePreviewModelMixin: status.HTTP_200_OK: serializers.DeletePreviewSerializer(many=True) }, ) - @swagger_auto_schema( - method="get", - responses={"default": serializers.DeletePreviewSerializer(many=True)}, - ) @action(detail=True, methods=["get"], filter_backends=[], suffix="List") def delete_preview(self, request, pk=None): object = self.get_object() diff --git a/dojo/api_v2/prefetch/__init__.py b/dojo/api_v2/prefetch/__init__.py index f0449c7b30..3d02655ec2 100644 --- a/dojo/api_v2/prefetch/__init__.py +++ b/dojo/api_v2/prefetch/__init__.py @@ -1,4 +1,3 @@ from .mixins import PrefetchListMixin, PrefetchRetrieveMixin -from .schema import get_prefetch_schema -__all__ = ["PrefetchListMixin", "PrefetchRetrieveMixin", "get_prefetch_schema"] +__all__ = ["PrefetchListMixin", "PrefetchRetrieveMixin"] diff --git a/dojo/api_v2/prefetch/schema.py b/dojo/api_v2/prefetch/schema.py index 6d04e75180..6fc0868147 100644 --- a/dojo/api_v2/prefetch/schema.py +++ b/dojo/api_v2/prefetch/schema.py @@ -1,84 +1,5 @@ -from drf_yasg import openapi, utils from .prefetcher import _Prefetcher from .utils import _get_prefetchable_fields -from ..schema import extra_schema -from ..schema.utils import LazySchemaRef - - -def get_prefetch_schema(methods, serializer): - """Swagger / OpenAPI v2 (drf-yasg) Return a composable swagger schema that contains in the query the fields that can be prefetch from the model - supported by the serializer and in the reponse the structure of these fields in a new top-level attribute - named prefetch. - - Returns: - ComposableSchema: A swagger schema - """ - prefetcher = _Prefetcher() - fields = _get_prefetchable_fields(serializer()) - - field_to_serializer = dict( - [ - (name, prefetcher._find_serializer(field_type)) - for name, field_type in fields - if prefetcher._find_serializer(field_type) - ] - ) - fields_to_refname = dict( - [ - (name, utils.get_serializer_ref_name(serializer())) - for name, serializer in field_to_serializer.items() - ] - ) - fields_name = [ - name - for name, field_type in fields - if prefetcher._find_serializer(field_type) - ] - - # New openapi parameter corresponding to the prefetchable fields - prefetch_params = [ - openapi.Parameter( - "prefetch", - in_=openapi.IN_QUERY, - required=False, - type=openapi.TYPE_ARRAY, - items=openapi.Items(type=openapi.TYPE_STRING, enum=fields_name), - ) - ] - - additional_props = dict( - [ - ( - name, - openapi.Schema( - type=openapi.TYPE_OBJECT, - read_only=True, - additional_properties=LazySchemaRef( - fields_to_refname[name], True - ), - ), - ) - for name in fields_name - ] - ) - prefetch_response = { - "200": { - "prefetch": openapi.Schema( - type=openapi.TYPE_OBJECT, properties=additional_props - ) - } - } - - schema = extra_schema.IdentitySchema() - for method in methods: - schema = schema.composeWith( - extra_schema.ExtraParameters(method, prefetch_params) - ) - schema = schema.composeWith( - extra_schema.ExtraResponseField(method, prefetch_response) - ) - - return schema def _get_path_to_GET_serializer_map(generator): @@ -97,6 +18,25 @@ def _get_path_to_GET_serializer_map(generator): return path_to_GET_serializer +def get_serializer_ref_name(serializer): + """Get serializer's ref_name + inspired by https://github.com/axnsan12/drf-yasg/blob/78031f0c189585c30fccb5005a6899f2d34289a9/src/drf_yasg/utils.py#L416 + + :param serializer: Serializer instance + :return: Serializer's ``ref_name`` or ``None`` for inline serializer + :rtype: str or None + """ + serializer_meta = getattr(serializer, 'Meta', None) + serializer_name = type(serializer).__name__ + if hasattr(serializer_meta, 'ref_name'): + ref_name = serializer_meta.ref_name + else: + ref_name = serializer_name + if ref_name.endswith('Serializer'): + ref_name = ref_name[:-len('Serializer')] + return ref_name + + def prefetch_postprocessing_hook(result, generator, request, public): """OpenAPI v3 (drf-spectacular) Some endpoints are using the PrefetchListMixin and PrefetchRetrieveMixin. These have nothing to do with Django prefetch_related. @@ -131,55 +71,37 @@ def prefetch_postprocessing_hook(result, generator, request, public): "enum": field_names, } - field_to_serializer = dict( - [ - (name, prefetcher._find_serializer(field_type)) - for name, field_type in fields - if prefetcher._find_serializer(field_type) - ] - ) - fields_to_refname = dict( - [ - (name, utils.get_serializer_ref_name(serializer())) - for name, serializer in field_to_serializer.items() - ] - ) - properties = dict( - [ - ( - name, - dict( - [ - ("type", "object"), - ("readOnly", True), - ( - "additionalProperties", - dict( - [ - ( - "$ref", - "#/components/schemas/" - + fields_to_refname[ - name - ], - ) - ] - ), - ), - ] - ), - ) - for name in field_names - ] - ) + field_to_serializer = { + name: prefetcher._find_serializer(field_type) + for name, field_type in fields + if prefetcher._find_serializer(field_type) + } + + fields_to_refname = { + name: get_serializer_ref_name(serializer()) + for name, serializer in field_to_serializer.items() + } + + properties = { + name: { + "type": "object", + "readOnly": True, + "additionalProperties": { + "$ref": f"#/components/schemas/{fields_to_refname[name]}" + } + } + for name in field_names + } + ref = paths[path]["get"]["responses"]["200"]["content"][ "application/json" ]["schema"]["$ref"] component_name = ref.split("/")[-1] result["components"]["schemas"][component_name][ "properties" - ]["prefetch"] = dict( - [("type", "object"), ("properties", properties)] - ) + ]["prefetch"] = { + "type": "object", + "properties": properties, + } return result diff --git a/dojo/api_v2/schema/__init__.py b/dojo/api_v2/schema/__init__.py deleted file mode 100644 index 6a69a16702..0000000000 --- a/dojo/api_v2/schema/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from .extra_schema import ( - IdentitySchema, - ExtraParameters, - ExtraResponseField, - ComposableSchema, -) -from .utils import LazySchemaRef, try_apply, resolve_lazy_ref - -__all__ = [ - "IdentitySchema", - "ExtraParameters", - "ExtraResponseField", - "ComposableSchema", - "LazySchemaRef", - "try_apply", - "resolve_lazy_ref", -] diff --git a/dojo/api_v2/schema/extra_schema.py b/dojo/api_v2/schema/extra_schema.py deleted file mode 100644 index 86fd565e37..0000000000 --- a/dojo/api_v2/schema/extra_schema.py +++ /dev/null @@ -1,140 +0,0 @@ -from drf_yasg.inspectors.view import SwaggerAutoSchema -from drf_yasg.openapi import resolve_ref, Schema -from .utils import resolve_lazy_ref -import copy - - -class ComposableSchema: - """A composable schema defines a transformation on drf_yasg Operation. These - schema can then be composed with another composable schema using the composeWith method - yielding a new composable schema whose transformation is defined as the function composition - of the transformation of the two source schema. - """ - - def transform_operation(self, operation, resolver): - """Defines an operation transformation - - Args: - operation (Operation): the operation to transform - resolver (Resolver): the schema refs resolver - """ - - def composeWith(self, schema): - """Allow two schema to be composed into a new schema. - Given the caller schema 'self' and another schema 'schema', - this operation yields a new composable schema whose transform_operation - if defined as - transform_operation(op, res) = schema.transform_operation(self.transform_operation(op, res), res) - - Args: - schema (ComposableSchema): The schema to compose with - - Returns: - ComposableSchema: the newly composed schema - """ - op = self.transform_operation - - class _Wrapper(ComposableSchema): - def transform_operation(self, operation, resolver): - return schema.transform_operation( - op(operation, resolver), resolver - ) - - return _Wrapper() - - def to_schema(self): - """Convert the composable schema into a SwaggerAutoSchema that - can be used with the drf_yasg library code - - Returns: - SwaggerAutoSchema: the swagger auto schema derived from the composable schema - """ - op = self.transform_operation - - class _Schema(SwaggerAutoSchema): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def get_operation(self, operation_keys): - operation = super().get_operation(operation_keys) - return op(operation, self.components) - - return _Schema - - -class IdentitySchema(ComposableSchema): - def transform_operation(self, operation, resolver): - return operation - - -class ExtraParameters(ComposableSchema): - """Define a schema that can add parameters to the operation""" - - def __init__(self, operation_name, extra_parameters, *args, **kwargs): - """Initialize the schema - - Args: - operation_name (string): the name of the operation to transform - extra_parameters (list[Parameter]): list of openapi parameters to add - """ - super().__init__(*args, **kwargs) - self._extra_parameters = extra_parameters - self._operation_name = operation_name - - def transform_operation(self, operation, resolver): - operation_id = operation["operationId"] - if not operation_id.endswith(self._operation_name): - return operation - - for param in self._extra_parameters: - operation["parameters"].append(resolve_lazy_ref(param, resolver)) - return operation - - -class ExtraResponseField(ComposableSchema): - """Define a schema that can add fields to the responses of the operation""" - - def __init__(self, operation_name, extra_fields, *args, **kwargs): - """Initialize the schema - - Args: - operation_name (string): the name of the operation to transform - extra_fields (dict()): description of the fields to add to the responses. The format is - { - parameters: list[openapi.Parameter](params1, params2, ...), - responses: { - code1: { - field1: openapi.Schema, - field2: openapi.Schema, - ... - }, - code2: ... - } - } - """ - super().__init__(*args, **kwargs) - self._extra_fields = extra_fields - self._operation_name = operation_name - - def transform_operation(self, operation, resolver): - operation_id = operation["operationId"] - if not operation_id.endswith(self._operation_name): - return operation - - responses = operation["responses"] - for code, params in self._extra_fields.items(): - if code in responses: - original_schema = responses[code]["schema"] - schema = ( - original_schema - if isinstance(original_schema, Schema) - else resolve_ref(original_schema, resolver) - ) - schema = copy.deepcopy(schema) - - for name, param in params.items(): - schema["properties"][name] = resolve_lazy_ref( - param, resolver - ) - responses[code]["schema"] = schema - return operation diff --git a/dojo/api_v2/schema/utils.py b/dojo/api_v2/schema/utils.py deleted file mode 100644 index 1276202fc8..0000000000 --- a/dojo/api_v2/schema/utils.py +++ /dev/null @@ -1,63 +0,0 @@ -from drf_yasg.openapi import SchemaRef, Schema - - -class LazySchemaRef: - """Utility class to support SchemaRef definition without knowing the resolver. - The reference can be evaluated later in the context of a swagger generator - """ - - def __init__(self, schema_name, ignore_unresolved=False): - # Bind curried version of the SchemaRef init - self.schema_ref = lambda resolver: SchemaRef( - resolver, schema_name, ignore_unresolved - ) - - def apply(self, resolver): - """Resolve the LazySchemaRef with the given resolver - - Args: - resolver (ReferenceResolver): resolver containing the schema refs - - Returns: - SchemaRef: the corresponding SchemaRef - """ - return self.schema_ref(resolver) - - -def try_apply(obj, resolver): - """Try to resolve a LazySchemaRef - - Args: - obj (object): the object to resolve - resolver (resolver): the resolver to use - - Returns: - object: the original object if it was not resolve otherwise the resolved LazySchemaRef - """ - if isinstance(obj, LazySchemaRef): - return obj.apply(resolver) - else: - return obj - - -def resolve_lazy_ref(schema, resolver): - """Recursively evaluate the schema to unbox LazySchemaRef based on the underlying resolvers. - - Args: - schema (object): the schema to evaluate - - Returns: - object: the schema without LazySchemaRef - """ - if not isinstance(schema, Schema): - return try_apply(schema, resolver) - - if "properties" in schema: - for prop_name, prop in schema["properties"].items(): - schema["properties"][prop_name] = resolve_lazy_ref(prop, resolver) - if "additionalProperties" in schema: - schema["additionalProperties"] = resolve_lazy_ref( - schema["additionalProperties"], resolver - ) - - return schema diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index 5778f2147c..cf6aec4a07 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -2,7 +2,6 @@ from django.contrib.auth.models import Group from typing import List from drf_spectacular.utils import extend_schema_field -from drf_yasg.utils import swagger_serializer_method from rest_framework.exceptions import NotFound from rest_framework.fields import DictField, MultipleChoiceField from datetime import datetime @@ -1500,17 +1499,14 @@ class RiskAcceptanceSerializer(serializers.ModelSerializer): path = serializers.SerializerMethodField() @extend_schema_field(serializers.CharField()) - @swagger_serializer_method(serializers.CharField()) def get_recommendation(self, obj): return Risk_Acceptance.TREATMENT_TRANSLATIONS.get(obj.recommendation) @extend_schema_field(serializers.CharField()) - @swagger_serializer_method(serializers.CharField()) def get_decision(self, obj): return Risk_Acceptance.TREATMENT_TRANSLATIONS.get(obj.decision) @extend_schema_field(serializers.CharField()) - @swagger_serializer_method(serializers.CharField()) def get_path(self, obj): engagement = Engagement.objects.filter( risk_acceptance__id__in=[obj.id] @@ -1526,7 +1522,6 @@ def get_path(self, obj): return path @extend_schema_field(serializers.IntegerField()) - @swagger_serializer_method(serializers.IntegerField()) def get_engagement(self, obj): engagement = Engagement.objects.filter( risk_acceptance__id__in=[obj.id] @@ -1629,14 +1624,12 @@ class FindingRelatedFieldsSerializer(serializers.Serializer): jira = serializers.SerializerMethodField() @extend_schema_field(FindingTestSerializer) - @swagger_serializer_method(FindingTestSerializer) def get_test(self, obj): return FindingTestSerializer(read_only=True).to_representation( obj.test ) @extend_schema_field(JIRAIssueSerializer) - @swagger_serializer_method(JIRAIssueSerializer) def get_jira(self, obj): issue = jira_helper.get_jira_issue(obj) if issue is None: @@ -1683,17 +1676,14 @@ class Meta: ) @extend_schema_field(serializers.DateTimeField()) - @swagger_serializer_method(serializers.DateTimeField()) def get_jira_creation(self, obj): return jira_helper.get_jira_creation(obj) @extend_schema_field(serializers.DateTimeField()) - @swagger_serializer_method(serializers.DateTimeField()) def get_jira_change(self, obj): return jira_helper.get_jira_change(obj) @extend_schema_field(FindingRelatedFieldsSerializer) - @swagger_serializer_method(FindingRelatedFieldsSerializer) def get_related_fields(self, obj): request = self.context.get("request", None) if request is None: @@ -1798,9 +1788,6 @@ def build_relational_field(self, field_name, relation_info): return super().build_relational_field(field_name, relation_info) @extend_schema_field(BurpRawRequestResponseSerializer) - @swagger_serializer_method( - serializer_or_field=BurpRawRequestResponseSerializer - ) def get_request_response(self, obj): # burp_req_resp = BurpRawRequestResponse.objects.filter(finding=obj) burp_req_resp = obj.burprawrequestresponse_set.all() @@ -2039,12 +2026,7 @@ def validate(self, data): def get_findings_count(self, obj) -> int: return obj.findings_count - # -> List[int] as return type doesn't seem enough for drf-yasg - @swagger_serializer_method( - serializer_or_field=serializers.ListField( - child=serializers.IntegerField() - ) - ) + # TODO, maybe extend_schema_field is needed here? def get_findings_list(self, obj) -> List[int]: return obj.open_findings_list @@ -3185,9 +3167,6 @@ class QuestionnaireEngagementSurveySerializer(serializers.ModelSerializer): questions = serializers.SerializerMethodField() @extend_schema_field(serializers.ListField(child=serializers.CharField())) - @swagger_serializer_method( - serializers.ListField(child=serializers.CharField()) - ) def get_questions(self, obj): questions = obj.questions.all() formated_questions = [] diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index fceb87c7ea..793ff5d240 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -6,9 +6,6 @@ from django.utils import timezone from django.contrib.auth.models import Permission from django.core.exceptions import ValidationError -from django.utils.decorators import method_decorator -from drf_yasg.inspectors.base import NotHandled -from drf_yasg.inspectors.query import CoreAPICompatInspector from rest_framework import viewsets, mixins, status from rest_framework.response import Response from django.db import IntegrityError @@ -16,8 +13,6 @@ from rest_framework.decorators import action from rest_framework.parsers import MultiPartParser from django_filters.rest_framework import DjangoFilterBackend -from drf_yasg import openapi -from drf_yasg.utils import swagger_auto_schema, no_body import base64 import mimetypes from dojo.engagement.services import close_engagement, reopen_engagement @@ -119,7 +114,6 @@ serializers, permissions, prefetch, - schema, mixins as dojo_mixins, ) import dojo.jira_link.helper as jira_helper @@ -242,10 +236,6 @@ class DojoGroupViewSet( queryset = Dojo_Group.objects.none() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "name", "social_provider"] - swagger_schema = prefetch.get_prefetch_schema( - ["dojo_groups_list", "dojo_groups_read"], - serializers.DojoGroupSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasDojoGroupPermission, @@ -287,10 +277,6 @@ class DojoGroupMemberViewSet( queryset = Dojo_Group_Member.objects.none() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "group_id", "user_id"] - swagger_schema = prefetch.get_prefetch_schema( - ["dojo_group_members_list", "dojo_group_members_read"], - serializers.DojoGroupMemberSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasDojoGroupMemberPermission, @@ -302,9 +288,6 @@ def get_queryset(self): @extend_schema( exclude=True ) - @swagger_auto_schema( - auto_schema=None - ) def partial_update(self, request, pk=None): # Object authorization won't work if not all data is provided response = {"message": "Patch function is not offered in this path."} @@ -319,10 +302,6 @@ class GlobalRoleViewSet( queryset = Global_Role.objects.all() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "user", "group", "role"] - swagger_schema = prefetch.get_prefetch_schema( - ["global_roles_list", "global_roles_read"], - serializers.GlobalRoleSerializer, - ).to_schema() permission_classes = (permissions.IsSuperUser, DjangoModelPermissions) @@ -334,9 +313,7 @@ class EndPointViewSet( queryset = Endpoint.objects.none() filter_backends = (DjangoFilterBackend,) filterset_class = ApiEndpointFilter - swagger_schema = prefetch.get_prefetch_schema( - ["endpoints_list", "endpoints_read"], serializers.EndpointSerializer - ).to_schema() + permission_classes = ( IsAuthenticated, permissions.UserHasEndpointPermission, @@ -349,10 +326,6 @@ def get_queryset(self): request=serializers.ReportGenerateOptionSerializer, responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, ) - @swagger_auto_schema( - request_body=serializers.ReportGenerateOptionSerializer, - responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, - ) @action( detail=True, methods=["post"], permission_classes=[IsAuthenticated] ) @@ -403,10 +376,7 @@ class EndpointStatusViewSet( "finding", "endpoint", ] - swagger_schema = prefetch.get_prefetch_schema( - ["endpoint_status_list", "endpoint_status_read"], - serializers.EndpointStatusSerializer, - ).to_schema() + permission_classes = ( IsAuthenticated, permissions.UserHasEndpointStatusPermission, @@ -427,19 +397,7 @@ class EngagementViewSet( queryset = Engagement.objects.none() filter_backends = (DjangoFilterBackend,) filterset_class = ApiEngagementFilter - swagger_schema = ( - prefetch.get_prefetch_schema( - ["engagements_list", "engagements_read"], - serializers.EngagementSerializer, - ) - .composeWith( - prefetch.get_prefetch_schema( - ["engagements_complete_checklist_read"], - serializers.EngagementCheckListSerializer, - ) - ) - .to_schema() - ) + permission_classes = ( IsAuthenticated, permissions.UserHasEngagementPermission, @@ -468,9 +426,6 @@ def get_queryset(self): @extend_schema( request=OpenApiTypes.NONE, responses={status.HTTP_200_OK: ""} ) - @swagger_auto_schema( - request_body=no_body, responses={status.HTTP_200_OK: ""} - ) @action(detail=True, methods=["post"]) def close(self, request, pk=None): eng = self.get_object() @@ -480,9 +435,6 @@ def close(self, request, pk=None): @extend_schema( request=OpenApiTypes.NONE, responses={status.HTTP_200_OK: ""} ) - @swagger_auto_schema( - request_body=no_body, responses={status.HTTP_200_OK: ""} - ) @action(detail=True, methods=["post"]) def reopen(self, request, pk=None): eng = self.get_object() @@ -493,10 +445,6 @@ def reopen(self, request, pk=None): request=serializers.ReportGenerateOptionSerializer, responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, ) - @swagger_auto_schema( - request_body=serializers.ReportGenerateOptionSerializer, - responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, - ) @action( detail=True, methods=["post"], permission_classes=[IsAuthenticated] ) @@ -541,17 +489,6 @@ def generate_report(self, request, pk=None): request=serializers.AddNewNoteOptionSerializer, responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, ) - @swagger_auto_schema( - method="get", - responses={ - status.HTTP_200_OK: serializers.EngagementToNotesSerializer - }, - ) - @swagger_auto_schema( - methods=["post"], - request_body=serializers.AddNewNoteOptionSerializer, - responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, - ) @action(detail=True, methods=["get", "post"]) def notes(self, request, pk=None): engagement = self.get_object() @@ -602,17 +539,6 @@ def notes(self, request, pk=None): request=serializers.AddNewFileOptionSerializer, responses={status.HTTP_201_CREATED: serializers.FileSerializer}, ) - @swagger_auto_schema( - method="get", - responses={ - status.HTTP_200_OK: serializers.EngagementToFilesSerializer - }, - ) - @swagger_auto_schema( - method="post", - request_body=serializers.AddNewFileOptionSerializer, - responses={status.HTTP_201_CREATED: serializers.FileSerializer}, - ) @action( detail=True, methods=["get", "post"], parser_classes=(MultiPartParser,) ) @@ -650,13 +576,6 @@ def files(self, request, pk=None): status.HTTP_201_CREATED: serializers.EngagementCheckListSerializer }, ) - @swagger_auto_schema( - method="post", - request_body=serializers.EngagementCheckListSerializer, - responses={ - status.HTTP_201_CREATED: serializers.EngagementCheckListSerializer - }, - ) @action(detail=True, methods=["get", "post"]) def complete_checklist(self, request, pk=None): from dojo.api_v2.prefetch.prefetcher import _Prefetcher @@ -702,12 +621,6 @@ def complete_checklist(self, request, pk=None): status.HTTP_200_OK: serializers.RawFileSerializer, }, ) - @swagger_auto_schema( - method="get", - responses={ - status.HTTP_200_OK: serializers.RawFileSerializer, - }, - ) @action( detail=True, methods=["get"], @@ -749,10 +662,7 @@ class RiskAcceptanceViewSet( queryset = Risk_Acceptance.objects.none() filter_backends = (DjangoFilterBackend,) filterset_class = ApiRiskAcceptanceFilter - swagger_schema = prefetch.get_prefetch_schema( - ["risk_acceptance_list", "risk_acceptance_read"], - serializers.RiskAcceptanceSerializer, - ).to_schema() + permission_classes = ( IsAuthenticated, permissions.UserHasRiskAcceptancePermission, @@ -773,12 +683,6 @@ def get_queryset(self): status.HTTP_200_OK: serializers.RiskAcceptanceProofSerializer, }, ) - @swagger_auto_schema( - method="get", - responses={ - status.HTTP_200_OK: serializers.RiskAcceptanceProofSerializer, - }, - ) @action(detail=True, methods=["get"]) def download_proof(self, request, pk=None): risk_acceptance = self.get_object() @@ -815,10 +719,7 @@ class AppAnalysisViewSet( queryset = App_Analysis.objects.none() filter_backends = (DjangoFilterBackend,) filterset_class = ApiAppAnalysisFilter - swagger_schema = prefetch.get_prefetch_schema( - ["technologies_list", "technologies_read"], - serializers.AppAnalysisSerializer, - ).to_schema() + permission_classes = ( IsAuthenticated, permissions.UserHasAppAnalysisPermission, @@ -835,10 +736,7 @@ class CredentialsViewSet( serializer_class = serializers.CredentialSerializer queryset = Cred_User.objects.all() filter_backends = (DjangoFilterBackend,) - swagger_schema = prefetch.get_prefetch_schema( - ["credentials_list", "credentials_read"], - serializers.CredentialSerializer, - ).to_schema() + permission_classes = (permissions.IsSuperUser, DjangoModelPermissions) @@ -850,10 +748,7 @@ class CredentialsMappingViewSet( queryset = Cred_Mapping.objects.none() filter_backends = (DjangoFilterBackend,) filterset_class = ApiCredentialsFilter - swagger_schema = prefetch.get_prefetch_schema( - ["credential_mappings_list", "credential_mappings_read"], - serializers.CredentialMappingSerializer, - ).to_schema() + permission_classes = ( IsAuthenticated, permissions.UserHasCredentialPermission, @@ -934,27 +829,6 @@ class FindingViewSet( permissions.UserHasFindingPermission, ) - _related_field_parameters = [ - openapi.Parameter( - name="related_fields", - in_=openapi.IN_QUERY, - description="Expand finding external relations (engagement, environment, product, product_type, test, test_type)", - type=openapi.TYPE_BOOLEAN, - ) - ] - swagger_schema = ( - prefetch.get_prefetch_schema( - ["findings_list", "findings_read"], serializers.FindingSerializer - ) - .composeWith( - schema.ExtraParameters("findings_list", _related_field_parameters) - ) - .composeWith( - schema.ExtraParameters("findings_read", _related_field_parameters) - ) - .to_schema() - ) - # Overriding mixins.UpdateModeMixin perform_update() method to grab push_to_jira # data and add that as a parameter to .save() def perform_update(self, serializer): @@ -1003,11 +877,6 @@ def get_serializer_class(self): request=serializers.FindingCloseSerializer, responses={status.HTTP_200_OK: serializers.FindingCloseSerializer}, ) - @swagger_auto_schema( - method="post", - request_body=serializers.FindingCloseSerializer, - responses={status.HTTP_200_OK: serializers.FindingCloseSerializer}, - ) @action(detail=True, methods=["post"]) def close(self, request, pk=None): finding = self.get_object() @@ -1069,14 +938,6 @@ def close(self, request, pk=None): request=serializers.TagSerializer, responses={status.HTTP_201_CREATED: serializers.TagSerializer}, ) - @swagger_auto_schema( - method="get", responses={status.HTTP_200_OK: serializers.TagSerializer} - ) - @swagger_auto_schema( - method="post", - request_body=serializers.TagSerializer, - responses={status.HTTP_200_OK: serializers.TagSerializer}, - ) @action(detail=True, methods=["get", "post"]) def tags(self, request, pk=None): finding = self.get_object() @@ -1118,19 +979,6 @@ def tags(self, request, pk=None): status.HTTP_201_CREATED: serializers.BurpRawRequestResponseSerializer }, ) - @swagger_auto_schema( - method="get", - responses={ - status.HTTP_200_OK: serializers.BurpRawRequestResponseSerializer - }, - ) - @swagger_auto_schema( - method="post", - request_body=serializers.BurpRawRequestResponseSerializer, - responses={ - status.HTTP_200_OK: serializers.BurpRawRequestResponseSerializer - }, - ) @action(detail=True, methods=["get", "post"]) def request_response(self, request, pk=None): finding = self.get_object() @@ -1177,15 +1025,6 @@ def request_response(self, request, pk=None): request=serializers.AddNewNoteOptionSerializer, responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, ) - @swagger_auto_schema( - method="get", - responses={status.HTTP_200_OK: serializers.FindingToNotesSerializer}, - ) - @swagger_auto_schema( - methods=["post"], - request_body=serializers.AddNewNoteOptionSerializer, - responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, - ) @action(detail=True, methods=["get", "post"]) def notes(self, request, pk=None): finding = self.get_object() @@ -1239,15 +1078,6 @@ def notes(self, request, pk=None): request=serializers.AddNewFileOptionSerializer, responses={status.HTTP_201_CREATED: serializers.FileSerializer}, ) - @swagger_auto_schema( - method="get", - responses={status.HTTP_200_OK: serializers.FindingToFilesSerializer}, - ) - @swagger_auto_schema( - method="post", - request_body=serializers.AddNewFileOptionSerializer, - responses={status.HTTP_201_CREATED: serializers.FileSerializer}, - ) @action( detail=True, methods=["get", "post"], parser_classes=(MultiPartParser,) ) @@ -1284,12 +1114,6 @@ def files(self, request, pk=None): status.HTTP_200_OK: serializers.RawFileSerializer, }, ) - @swagger_auto_schema( - method="get", - responses={ - status.HTTP_200_OK: serializers.RawFileSerializer, - }, - ) @action( detail=True, methods=["get"], @@ -1327,10 +1151,6 @@ def download_file(self, request, file_id, pk=None): request=serializers.FindingNoteSerializer, responses={status.HTTP_204_NO_CONTENT: ""}, ) - @swagger_auto_schema( - request_body=serializers.FindingNoteSerializer, - responses={status.HTTP_204_NO_CONTENT: ""}, - ) @action(detail=True, methods=["patch"]) def remove_note(self, request, pk=None): """Remove Note From Finding Note""" @@ -1370,11 +1190,6 @@ def remove_note(self, request, pk=None): request=serializers.TagSerializer, responses={status.HTTP_204_NO_CONTENT: ""}, ) - @swagger_auto_schema( - methods=["put", "patch"], - request_body=serializers.TagSerializer, - responses={status.HTTP_204_NO_CONTENT: ""}, - ) @action(detail=True, methods=["put", "patch"]) def remove_tags(self, request, pk=None): """Remove Tag(s) from finding list of tags""" @@ -1423,11 +1238,6 @@ def remove_tags(self, request, pk=None): status.HTTP_200_OK: serializers.FindingSerializer(many=True) } ) - @swagger_auto_schema( - responses={ - status.HTTP_200_OK: serializers.FindingSerializer(many=True) - } - ) @action( detail=True, methods=["get"], @@ -1447,10 +1257,6 @@ def get_duplicate_cluster(self, request, pk): request=OpenApiTypes.NONE, responses={status.HTTP_204_NO_CONTENT: ""}, ) - @swagger_auto_schema( - request_body=no_body, - responses={status.HTTP_204_NO_CONTENT: ""}, - ) @action(detail=True, methods=["post"], url_path=r"duplicate/reset") def reset_finding_duplicate_status(self, request, pk): checked_duplicate_id = reset_finding_duplicate_status_internal( @@ -1469,9 +1275,6 @@ def reset_finding_duplicate_status(self, request, pk): ], responses={status.HTTP_204_NO_CONTENT: ""}, ) - @swagger_auto_schema( - responses={status.HTTP_204_NO_CONTENT: ""}, request_body=no_body - ) @action( detail=True, methods=["post"], url_path=r"original/(?P\d+)" ) @@ -1485,10 +1288,6 @@ def set_finding_as_original(self, request, pk, new_fid): request=serializers.ReportGenerateOptionSerializer, responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, ) - @swagger_auto_schema( - request_body=serializers.ReportGenerateOptionSerializer, - responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, - ) @action( detail=False, methods=["post"], permission_classes=[IsAuthenticated] ) @@ -1622,10 +1421,6 @@ def _remove_metadata(self, request, finding): description="Returned if there was a problem with the metadata information" ), }, - # manual_parameters=[openapi.Parameter( - # name="name", in_=openapi.IN_QUERY, type=openapi.TYPE_STRING, - # description="name of the metadata to retrieve. If name is empty, return all the \ - # metadata associated with the finding")] ) @extend_schema( methods=["PUT"], @@ -1639,9 +1434,6 @@ def _remove_metadata(self, request, finding): description="Returned if there was a problem with the metadata information" ), }, - # manual_parameters=[openapi.Parameter( - # name="name", in_=openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING, - # description="name of the metadata to edit")], ) @extend_schema( methods=["POST"], @@ -1656,58 +1448,6 @@ def _remove_metadata(self, request, finding): ), }, ) - @swagger_auto_schema( - responses={ - status.HTTP_200_OK: serializers.FindingMetaSerializer(many=True), - status.HTTP_404_NOT_FOUND: "Returned if finding does not exist", - }, - methods=["get"], - ) - @swagger_auto_schema( - responses={ - status.HTTP_200_OK: "Returned if the metadata was correctly deleted", - status.HTTP_404_NOT_FOUND: "Returned if finding does not exist", - status.HTTP_400_BAD_REQUEST: "Returned if there was a problem with the metadata information", - }, - methods=["delete"], - manual_parameters=[ - openapi.Parameter( - name="name", - in_=openapi.IN_QUERY, - required=True, - type=openapi.TYPE_STRING, - description="name of the metadata to retrieve. If name is empty, return all the \ - metadata associated with the finding", - ) - ], - ) - @swagger_auto_schema( - responses={ - status.HTTP_200_OK: serializers.FindingMetaSerializer, - status.HTTP_404_NOT_FOUND: "Returned if finding does not exist", - status.HTTP_400_BAD_REQUEST: "Returned if there was a problem with the metadata information", - }, - methods=["put"], - manual_parameters=[ - openapi.Parameter( - name="name", - in_=openapi.IN_QUERY, - required=True, - type=openapi.TYPE_STRING, - description="name of the metadata to edit", - ) - ], - request_body=serializers.FindingMetaSerializer, - ) - @swagger_auto_schema( - responses={ - status.HTTP_200_OK: serializers.FindingMetaSerializer, - status.HTTP_404_NOT_FOUND: "Returned if finding does not exist", - status.HTTP_400_BAD_REQUEST: "Returned if there was a problem with the metadata information", - }, - methods=["post"], - request_body=serializers.FindingMetaSerializer, - ) @action( detail=True, methods=["post", "put", "delete", "get"], @@ -1759,10 +1499,7 @@ class JiraIssuesViewSet( "engagement", "finding_group", ] - swagger_schema = prefetch.get_prefetch_schema( - ["jira_finding_mappings_list", "jira_finding_mappings_read"], - serializers.JIRAIssueSerializer, - ).to_schema() + permission_classes = ( IsAuthenticated, permissions.UserHasJiraIssuePermission, @@ -1790,10 +1527,7 @@ class JiraProjectViewSet( "enable_engagement_epic_mapping", "push_notes", ] - swagger_schema = prefetch.get_prefetch_schema( - ["jira_projects_list", "jira_projects_read"], - serializers.JIRAProjectSerializer, - ).to_schema() + permission_classes = ( IsAuthenticated, permissions.UserHasJiraProductPermission, @@ -1846,13 +1580,6 @@ class ProductAPIScanConfigurationViewSet( "service_key_2", "service_key_3", ] - swagger_schema = prefetch.get_prefetch_schema( - [ - "product_api_scan_configurations_list", - "product_api_scan_configurations_read", - ], - serializers.ProductAPIScanConfigurationSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasProductAPIScanConfigurationPermission, @@ -1907,34 +1634,14 @@ class DojoMetaViewSet( IsAuthenticated, permissions.UserHasDojoMetaPermission, ) - swagger_schema = prefetch.get_prefetch_schema( - ["metadata_list", "metadata_read"], serializers.MetaSerializer - ).to_schema() + # swagger_schema = prefetch.get_prefetch_schema( + # ["metadata_list", "metadata_read"], serializers.MetaSerializer + # ).to_schema() def get_queryset(self): return get_authorized_dojo_meta(Permissions.Product_View) -# Authorization: object-based -class DjangoFilterDescriptionInspector(CoreAPICompatInspector): - def get_filter_parameters(self, filter_backend): - if isinstance(filter_backend, DjangoFilterBackend): - result = super( - DjangoFilterDescriptionInspector, self - ).get_filter_parameters(filter_backend) - for param in result: - if not param.get("description", ""): - param.description = ( - "Filter the returned list by {field_name}".format( - field_name=param.name - ) - ) - - return result - - return NotHandled - - @extend_schema_view( list=extend_schema( parameters=[ @@ -1959,12 +1666,6 @@ def get_filter_parameters(self, filter_backend): ], ), ) -@method_decorator( - name="list", - decorator=swagger_auto_schema( - filter_inspectors=[DjangoFilterDescriptionInspector] - ), -) class ProductViewSet( prefetch.PrefetchListMixin, prefetch.PrefetchRetrieveMixin, @@ -1980,9 +1681,6 @@ class ProductViewSet( filter_backends = (DjangoFilterBackend,) filterset_class = ApiProductFilter - swagger_schema = prefetch.get_prefetch_schema( - ["products_list", "products_read"], serializers.ProductSerializer - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasProductPermission, @@ -2011,10 +1709,6 @@ def destroy(self, request, *args, **kwargs): request=serializers.ReportGenerateOptionSerializer, responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, ) - @swagger_auto_schema( - request_body=serializers.ReportGenerateOptionSerializer, - responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, - ) @action( detail=True, methods=["post"], permission_classes=[IsAuthenticated] ) @@ -2081,10 +1775,6 @@ class ProductMemberViewSet( queryset = Product_Member.objects.none() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "product_id", "user_id"] - swagger_schema = prefetch.get_prefetch_schema( - ["product_members_list", "product_members_read"], - serializers.ProductMemberSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasProductMemberPermission, @@ -2098,9 +1788,6 @@ def get_queryset(self): @extend_schema( exclude=True ) - @swagger_auto_schema( - auto_schema=None - ) def partial_update(self, request, pk=None): # Object authorization won't work if not all data is provided response = {"message": "Patch function is not offered in this path."} @@ -2139,10 +1826,6 @@ class ProductGroupViewSet( queryset = Product_Group.objects.none() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "product_id", "group_id"] - swagger_schema = prefetch.get_prefetch_schema( - ["product_groups_list", "product_groups_read"], - serializers.ProductGroupSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasProductGroupPermission, @@ -2156,9 +1839,6 @@ def get_queryset(self): @extend_schema( exclude=True ) - @swagger_auto_schema( - auto_schema=None - ) def partial_update(self, request, pk=None): # Object authorization won't work if not all data is provided response = {"message": "Patch function is not offered in this path."} @@ -2204,10 +1884,6 @@ class ProductTypeViewSet( "created", "updated", ] - swagger_schema = prefetch.get_prefetch_schema( - ["product_types_list", "product_types_read"], - serializers.ProductTypeSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasProductTypePermission, @@ -2243,10 +1919,6 @@ def destroy(self, request, *args, **kwargs): request=serializers.ReportGenerateOptionSerializer, responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, ) - @swagger_auto_schema( - request_body=serializers.ReportGenerateOptionSerializer, - responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, - ) @action( detail=True, methods=["post"], permission_classes=[IsAuthenticated] ) @@ -2313,10 +1985,6 @@ class ProductTypeMemberViewSet( queryset = Product_Type_Member.objects.none() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "product_type_id", "user_id"] - swagger_schema = prefetch.get_prefetch_schema( - ["product_type_members_list", "product_type_members_read"], - serializers.ProductTypeMemberSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasProductTypeMemberPermission, @@ -2344,9 +2012,6 @@ def destroy(self, request, *args, **kwargs): @extend_schema( exclude=True ) - @swagger_auto_schema( - auto_schema=None - ) def partial_update(self, request, pk=None): # Object authorization won't work if not all data is provided response = {"message": "Patch function is not offered in this path."} @@ -2385,10 +2050,6 @@ class ProductTypeGroupViewSet( queryset = Product_Type_Group.objects.none() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "product_type_id", "group_id"] - swagger_schema = prefetch.get_prefetch_schema( - ["product_type_groups_list", "product_type_groups_read"], - serializers.ProductTypeGroupSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasProductTypeGroupPermission, @@ -2402,9 +2063,6 @@ def get_queryset(self): @extend_schema( exclude=True ) - @swagger_auto_schema( - auto_schema=None - ) def partial_update(self, request, pk=None): # Object authorization won't work if not all data is provided response = {"message": "Patch function is not offered in this path."} @@ -2419,10 +2077,6 @@ class StubFindingsViewSet( queryset = Stub_Finding.objects.none() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "title", "date", "severity", "description"] - swagger_schema = prefetch.get_prefetch_schema( - ["stub_findings_list", "stub_findings_read"], - serializers.StubFindingSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasFindingPermission, @@ -2459,9 +2113,6 @@ class TestsViewSet( queryset = Test.objects.none() filter_backends = (DjangoFilterBackend,) filterset_class = ApiTestFilter - swagger_schema = prefetch.get_prefetch_schema( - ["tests_list", "tests_read"], serializers.TestSerializer - ).to_schema() permission_classes = (IsAuthenticated, permissions.UserHasTestPermission) @property @@ -2496,10 +2147,6 @@ def get_serializer_class(self): request=serializers.ReportGenerateOptionSerializer, responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, ) - @swagger_auto_schema( - request_body=serializers.ReportGenerateOptionSerializer, - responses={status.HTTP_200_OK: serializers.ReportGenerateSerializer}, - ) @action( detail=True, methods=["post"], permission_classes=[IsAuthenticated] ) @@ -2542,15 +2189,6 @@ def generate_report(self, request, pk=None): request=serializers.AddNewNoteOptionSerializer, responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, ) - @swagger_auto_schema( - method="get", - responses={status.HTTP_200_OK: serializers.TestToNotesSerializer}, - ) - @swagger_auto_schema( - methods=["post"], - request_body=serializers.AddNewNoteOptionSerializer, - responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, - ) @action(detail=True, methods=["get", "post"]) def notes(self, request, pk=None): test = self.get_object() @@ -2599,15 +2237,6 @@ def notes(self, request, pk=None): request=serializers.AddNewFileOptionSerializer, responses={status.HTTP_201_CREATED: serializers.FileSerializer}, ) - @swagger_auto_schema( - method="get", - responses={status.HTTP_200_OK: serializers.TestToFilesSerializer}, - ) - @swagger_auto_schema( - method="post", - request_body=serializers.AddNewFileOptionSerializer, - responses={status.HTTP_201_CREATED: serializers.FileSerializer}, - ) @action( detail=True, methods=["get", "post"], parser_classes=(MultiPartParser,) ) @@ -2644,12 +2273,6 @@ def files(self, request, pk=None): status.HTTP_200_OK: serializers.RawFileSerializer, }, ) - @swagger_auto_schema( - method="get", - responses={ - status.HTTP_200_OK: serializers.RawFileSerializer, - }, - ) @action( detail=True, methods=["get"], @@ -2740,10 +2363,6 @@ class TestImportViewSet( "test_import_finding_action__finding", "test_import_finding_action__created", ] - swagger_schema = prefetch.get_prefetch_schema( - ["test_imports_list", "test_imports_read"], - serializers.TestImportSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasTestImportPermission, @@ -2798,10 +2417,6 @@ class ToolConfigurationsViewSet( "url", "authentication_type", ] - swagger_schema = prefetch.get_prefetch_schema( - ["tool_configurations_list", "tool_configurations_read"], - serializers.ToolConfigurationSerializer, - ).to_schema() permission_classes = (permissions.UserHasConfigurationPermissionSuperuser,) @@ -2820,10 +2435,6 @@ class ToolProductSettingsViewSet( "tool_project_id", "url", ] - swagger_schema = prefetch.get_prefetch_schema( - ["tool_configurations_list", "tool_configurations_read"], - serializers.ToolConfigurationSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasToolProductSettingsPermission, @@ -2914,10 +2525,6 @@ class UserContactInfoViewSet( ): serializer_class = serializers.UserContactInfoSerializer queryset = UserContactInfo.objects.all() - swagger_schema = prefetch.get_prefetch_schema( - ["user_contact_infos_list", "user_contact_infos_read"], - serializers.UserContactInfoSerializer, - ).to_schema() filter_backends = (DjangoFilterBackend,) filterset_fields = "__all__" permission_classes = (permissions.IsSuperUser, DjangoModelPermissions) @@ -2929,10 +2536,6 @@ class UserProfileView(GenericAPIView): pagination_class = None serializer_class = serializers.UserProfileSerializer - @swagger_auto_schema( - method="get", - responses={status.HTTP_200_OK: serializers.UserProfileSerializer}, - ) @action( detail=True, methods=["get"], filter_backends=[], pagination_class=None ) @@ -3110,9 +2713,6 @@ class LanguageViewSet( queryset = Languages.objects.none() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "language", "product"] - swagger_schema = prefetch.get_prefetch_schema( - ["languages_list", "languages_read"], serializers.LanguageSerializer - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasLanguagePermission, @@ -3592,10 +3192,6 @@ class NotificationsViewSet( filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "user", "product", "template"] permission_classes = (permissions.IsSuperUser, DjangoModelPermissions) - swagger_schema = prefetch.get_prefetch_schema( - ["notifications_list", "notifications_read"], - serializers.NotificationsSerializer, - ).to_schema() class EngagementPresetsViewset( @@ -3605,10 +3201,6 @@ class EngagementPresetsViewset( queryset = Engagement_Presets.objects.none() filter_backends = (DjangoFilterBackend,) filterset_fields = ["id", "title", "product"] - swagger_schema = prefetch.get_prefetch_schema( - ["engagement_presets_list", "engagement_presets_read"], - serializers.EngagementPresetsSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasEngagementPresetPermission, @@ -3624,10 +3216,6 @@ class EngagementCheckListViewset( serializer_class = serializers.EngagementCheckListSerializer queryset = Check_List.objects.none() filter_backends = (DjangoFilterBackend,) - swagger_schema = prefetch.get_prefetch_schema( - ["engagement_checklists_list", "engagement_checklists_read"], - serializers.EngagementCheckListSerializer, - ).to_schema() permission_classes = ( IsAuthenticated, permissions.UserHasEngagementPermission, @@ -3731,13 +3319,6 @@ class QuestionnaireAnsweredSurveyViewSet( permissions.UserHasEngagementPermission, DjangoModelPermissions, ) - swagger_schema = prefetch.get_prefetch_schema( - [ - "questionnaire_answered_questionnaires_list", - "questionnaire_answered_questionnaires_read", - ], - serializers.QuestionnaireAnsweredSurveySerializer, - ).to_schema() # Authorization: configuration diff --git a/dojo/risk_acceptance/api.py b/dojo/risk_acceptance/api.py index d862453a2f..b23a0d1dfc 100644 --- a/dojo/risk_acceptance/api.py +++ b/dojo/risk_acceptance/api.py @@ -7,7 +7,6 @@ from rest_framework.decorators import action from rest_framework.permissions import IsAdminUser from rest_framework.response import Response -from drf_yasg.utils import swagger_auto_schema from dojo.api_v2.serializers import RiskAcceptanceSerializer from dojo.models import Risk_Acceptance, User, Vulnerability_Id @@ -38,10 +37,6 @@ class AcceptedRisksMixin(ABC): def risk_application_model_class(self): pass - @swagger_auto_schema( - request_body=AcceptedRiskSerializer(many=True), - responses={status.HTTP_201_CREATED: RiskAcceptanceSerializer(many=True)}, - ) @extend_schema( request=AcceptedRiskSerializer(many=True), responses={status.HTTP_201_CREATED: RiskAcceptanceSerializer(many=True)}, @@ -65,10 +60,6 @@ def accept_risks(self, request, pk=None): class AcceptedFindingsMixin(ABC): - @swagger_auto_schema( - request_body=AcceptedRiskSerializer(many=True), - responses={status.HTTP_201_CREATED: RiskAcceptanceSerializer(many=True)}, - ) @extend_schema( request=AcceptedRiskSerializer(many=True), responses={status.HTTP_201_CREATED: RiskAcceptanceSerializer(many=True)}, diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 5b772a6dae..5c86f79ddf 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -753,29 +753,6 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param if API_TOKENS_ENABLED: REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] += ('rest_framework.authentication.TokenAuthentication',) -SWAGGER_SETTINGS = { - 'SECURITY_DEFINITIONS': { - 'basicAuth': { - 'type': 'basic' - }, - 'cookieAuth': { - 'type': 'apiKey', - 'in': 'cookie', - 'name': 'sessionid' - }, - }, - 'DOC_EXPANSION': "none", - 'JSON_EDITOR': True, - 'SHOW_REQUEST_HEADERS': True, -} - -if API_TOKENS_ENABLED: - SWAGGER_SETTINGS['SECURITY_DEFINITIONS']['tokenAuth'] = { - 'type': 'apiKey', - 'in': 'header', - 'name': 'Authorization' - } - SPECTACULAR_SETTINGS = { 'TITLE': 'Defect Dojo API v2', 'DESCRIPTION': 'Defect Dojo - Open Source vulnerability Management made easy. Prefetch related parameters/responses not yet in the schema.', @@ -849,7 +826,6 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param 'dbbackup', 'django_celery_results', 'social_django', - 'drf_yasg', 'drf_spectacular', 'drf_spectacular_sidecar', # required for Django collectstatic discovery 'tagulous', @@ -1083,11 +1059,6 @@ def saml2_attrib_map_format(dict): ('dojo.remote_user.RemoteUserAuthentication',) + \ REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] - SWAGGER_SETTINGS['SECURITY_DEFINITIONS']['remoteUserAuth'] = { - 'type': 'apiKey', - 'in': 'header', - 'name': AUTH_REMOTEUSER_USERNAME_HEADER[5:].replace('_', '-') - } # ------------------------------------------------------------------------------ # CELERY # ------------------------------------------------------------------------------ @@ -1718,6 +1689,5 @@ def saml2_attrib_map_format(dict): from django.utils.deprecation import RemovedInDjango50Warning warnings.filterwarnings("ignore", category=RemovedInDjango50Warning) warnings.filterwarnings("ignore", message="invalid escape sequence.*") - warnings.filterwarnings("ignore", message="'cgi' is deprecated and slated for removal in Python 3\\.13") warnings.filterwarnings("ignore", message="DateTimeField .+ received a naive datetime .+ while time zone support is active\\.") warnings.filterwarnings("ignore", message="unclosed file .+") diff --git a/dojo/urls.py b/dojo/urls.py index fa15f977da..4500e1e49d 100755 --- a/dojo/urls.py +++ b/dojo/urls.py @@ -4,9 +4,6 @@ from django.contrib import admin from rest_framework.routers import DefaultRouter from rest_framework.authtoken import views as tokenviews -from rest_framework import permissions -from drf_yasg.views import get_schema_view -from drf_yasg import openapi from django.http import HttpResponse from dojo import views from dojo.api_v2.views import EndPointViewSet, EngagementViewSet, \ @@ -183,20 +180,6 @@ ) ] -schema_view = get_schema_view( - openapi.Info( - title="Defect Dojo API", - default_version='v2', - description="To use the API you need be authorized.\n\n## Deprecated - Removal in v2.30.0\n#### Please use the [OpenAPI3 version](/api/v2/oa3/swagger-ui/)", - ), - # if public=False, includes only endpoints the current user has access to - public=True, - # The API of a OpenSource project should be public accessible - permission_classes=[permissions.AllowAny], - # url pattersns specific to the API - patterns=api_v2_urls, -) - urlpatterns = [] # sometimes urlpatterns needed be added from local_settings.py before other URLs of core dojo @@ -208,9 +191,6 @@ re_path(r'^%shistory/(?P\d+)/(?P\d+)$' % get_system_setting('url_prefix'), views.action_history, name='action_history'), re_path(r'^%s' % get_system_setting('url_prefix'), include(ur)), - # drf-yasg = OpenAPI2 - re_path(r'^%sapi/v2/doc/' % get_system_setting('url_prefix'), schema_view.with_ui('swagger', cache_timeout=0), name='api_v2_schema'), - # drf-spectacular = OpenAPI3 re_path(r'^%sapi/v2/oa3/schema/' % get_system_setting('url_prefix'), SpectacularAPIView.as_view(), name='schema_oa3'), re_path(r'^%sapi/v2/oa3/swagger-ui/' % get_system_setting('url_prefix'), SpectacularSwaggerView.as_view(url=get_system_setting('url_prefix') + '/api/v2/oa3/schema/?format=json'), name='swagger-ui_oa3'), diff --git a/requirements.txt b/requirements.txt index e7821926bb..597607d749 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,7 +56,6 @@ Python-jose==3.3.0 gitpython==3.1.41 debugpy==1.8.0 python-gitlab==4.4.0 -drf_yasg==1.21.5 cpe==1.2.1 packageurl-python==0.13.4 django-crum==0.7.9 diff --git a/unittests/test_apiv2_metadata.py b/unittests/test_apiv2_metadata.py index 3e39dc2bbc..6da260ec32 100644 --- a/unittests/test_apiv2_metadata.py +++ b/unittests/test_apiv2_metadata.py @@ -26,7 +26,7 @@ def create(self, **kwargs): return self.client.post(reverse('metadata-list'), kwargs, format='json') def test_docs(self): - r = self.client.get(reverse('api_v2_schema')) + r = self.client.get(reverse('swagger-ui_oa3')) self.assertEqual(r.status_code, 200) def test_query_metadata(self): diff --git a/unittests/test_swagger_schema.py b/unittests/test_swagger_schema.py deleted file mode 100644 index b126335937..0000000000 --- a/unittests/test_swagger_schema.py +++ /dev/null @@ -1,835 +0,0 @@ -from django.test import tag -from rest_framework.test import APIRequestFactory -from rest_framework.views import APIView -from rest_framework.test import APITestCase, force_authenticate, APIClient -from rest_framework.mixins import \ - RetrieveModelMixin, ListModelMixin, CreateModelMixin, UpdateModelMixin -from rest_framework import status -from drf_yasg.generators import OpenAPISchemaGenerator -from drf_yasg.openapi import Info, SchemaRef -from drf_yasg.openapi import \ - TYPE_ARRAY, TYPE_BOOLEAN, TYPE_INTEGER, TYPE_NUMBER, TYPE_OBJECT, TYPE_STRING -from collections import OrderedDict - -from dojo.api_v2.views import \ - DevelopmentEnvironmentViewSet, EndpointStatusViewSet, EndPointViewSet, \ - EngagementViewSet, FindingTemplatesViewSet, FindingViewSet, \ - JiraInstanceViewSet, DojoMetaViewSet, NoteTypeViewSet, NotesViewSet, \ - ProductTypeViewSet, ProductViewSet, RegulationsViewSet, \ - SonarqubeIssueViewSet, ProductAPIScanConfigurationViewSet, \ - SonarqubeIssueTransitionViewSet, StubFindingsViewSet, SystemSettingsViewSet, \ - TestTypesViewSet, TestsViewSet, ToolConfigurationsViewSet, ToolProductSettingsViewSet, \ - ToolTypesViewSet, UsersViewSet, JiraIssuesViewSet, JiraProjectViewSet, AppAnalysisViewSet, \ - LanguageTypeViewSet, LanguageViewSet, AnnouncementViewSet - -from dojo.models import \ - Development_Environment, Endpoint_Status, Endpoint, Engagement, Finding_Template, \ - Finding, JIRA_Instance, JIRA_Issue, DojoMeta, Note_Type, Notes, Product_Type, Product, Regulation, \ - Sonarqube_Issue, Product_API_Scan_Configuration, Sonarqube_Issue_Transition, \ - Stub_Finding, System_Settings, Test_Type, Test, Tool_Configuration, Tool_Product_Settings, \ - Tool_Type, Dojo_User, JIRA_Project, App_Analysis, Language_Type, Languages, Announcement - -from dojo.api_v2.serializers import \ - DevelopmentEnvironmentSerializer, EndpointStatusSerializer, EndpointSerializer, \ - EngagementSerializer, FindingTemplateSerializer, FindingSerializer, \ - JIRAInstanceSerializer, JIRAIssueSerializer, JIRAProjectSerializer, MetaSerializer, NoteTypeSerializer, \ - ProductSerializer, RegulationSerializer, \ - SonarqubeIssueSerializer, ProductAPIScanConfigurationSerializer, SonarqubeIssueTransitionSerializer, \ - StubFindingSerializer, SystemSettingsSerializer, TestTypeSerializer, TestSerializer, ToolConfigurationSerializer, \ - ToolProductSettingsSerializer, ToolTypeSerializer, UserSerializer, NoteSerializer, ProductTypeSerializer, \ - AppAnalysisSerializer, LanguageTypeSerializer, LanguageSerializer, AnnouncementSerializer - -SWAGGER_SCHEMA_GENERATOR = OpenAPISchemaGenerator(Info("defectdojo", "v2")) -BASE_API_URL = "/api/v2" - - -def testIsBroken(method): - return tag("broken")(method) - - -def skipIfNotSubclass(baseclass): - def decorate(f): - def wrapper(self, *args, **kwargs): - if not issubclass(self.viewset, baseclass): - self.skipTest('This view is not %s' % baseclass) - else: - f(self, *args, **kwargs) - return wrapper - return decorate - - -def check_response_valid(expected_code, response): - def _data_to_str(response): - if hasattr(response, "data"): - return response.data - return None - - assert response.status_code == expected_code, \ - f"Response invalid, returned with code {response.status_code}\nResponse Data:\n{_data_to_str(response)}" - - -def format_url(path): - return f"{BASE_API_URL}{path}" - - -class SchemaChecker(): - def __init__(self, definitions): - self._prefix = [] - self._has_failed = False - self._definitions = definitions - self._errors = [] - - def _register_error(self, error): - self._errors += [error] - - def _check_or_fail(self, condition, message): - if not condition: - self._has_failed = True - self._register_error(message) - - def _get_prefix(self): - return '#'.join(self._prefix) - - def _push_prefix(self, prefix): - self._prefix += [prefix] - - def _pop_prefix(self): - self._prefix = self._prefix if len(self._prefix) == 0 else self._prefix[:-1] - - def _resolve_if_ref(self, schema): - if type(schema) is not SchemaRef: - return schema - - ref_name = schema["$ref"] - ref_name = ref_name[ref_name.rfind("/") + 1:] - return self._definitions[ref_name] - - def _check_has_required_fields(self, required_fields, obj): - for required_field in required_fields: - # passwords are writeOnly, but this is not supported by Swagger / OpenAPIv2 - if required_field != 'password': - field = f"{self._get_prefix()}#{required_field}" - self._check_or_fail(obj is not None and required_field in obj, f"{field} is required but was not returned") - - def _check_type(self, schema, obj): - schema_type = schema["type"] - is_nullable = schema.get("x-nullable", False) or schema.get("readOnly", False) - - def _check_helper(check): - self._check_or_fail(check, f"{self._get_prefix()} should be of type {schema_type} but value was of type {type(obj)}") - - if obj is None: - self._check_or_fail(is_nullable, f"{self._get_prefix()} is not nullable yet the value returned was null") - elif schema_type is TYPE_BOOLEAN: - _check_helper(isinstance(obj, bool)) - elif schema_type is TYPE_INTEGER: - _check_helper(isinstance(obj, int)) - elif schema_type is TYPE_NUMBER: - _check_helper(obj.isdecimal()) - elif schema_type is TYPE_ARRAY: - _check_helper(isinstance(obj, list)) - elif schema_type is TYPE_OBJECT: - _check_helper(isinstance(obj, OrderedDict) or isinstance(obj, dict)) - elif schema_type is TYPE_STRING: - _check_helper(isinstance(obj, str)) - else: - # Default case - _check_helper(False) - - def _with_prefix(self, prefix, callable, *args): - self._push_prefix(prefix) - callable(*args) - self._pop_prefix() - - def check(self, schema, obj): - def _check(schema, obj): - schema = self._resolve_if_ref(schema) - self._check_type(schema, obj) - - required_fields = schema.get("required", []) - self._check_has_required_fields(required_fields, obj) - - if obj is None: - return - - properties = schema.get("properties", None) - if properties is not None: - for name, prop in properties.items(): - # print('property: ', name) - # print('obj ', obj) - obj_child = obj.get(name, None) - if obj_child is not None: - self._with_prefix(name, _check, prop, obj_child) - - for child_name in obj.keys(): - # TODO prefetch mixins not picked up by spectcular? - if child_name not in ['prefetch']: - if not properties or child_name not in properties.keys(): - self._has_failed = True - self._register_error(f'unexpected property "{child_name}" found') - - additional_properties = schema.get("additionalProperties", None) - if additional_properties is not None: - for name, obj_child in obj.items(): - self._with_prefix(f"additionalProp<{name}>", _check, additional_properties, obj_child) - - if schema["type"] is TYPE_ARRAY: - items_schema = schema["items"] - for index in range(len(obj)): - self._with_prefix(f"item{index}", _check, items_schema, obj[index]) - - self._has_failed = False - self._errors = [] - self._prefix = [] - _check(schema, obj) - assert not self._has_failed, "\n" + '\n'.join(self._errors) + "\nFailed with " + str(len(self._errors)) + " errors" - - -class BaseClass(): - class SchemaTest(APITestCase): - fixtures = ['dojo_testdata.json'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewset = None - self.viewname = None - self.model = None - self.serializer = None - self.field_transformers = dict() - - def setUp(self): - super().setUp() - testuser = Dojo_User.objects.get(username='admin') - - factory = APIRequestFactory() - request = factory.get('/') - force_authenticate(request, user=testuser) - request = APIView().initialize_request(request) - - self.schema = SWAGGER_SCHEMA_GENERATOR.get_schema(request, public=True) - self.client = APIClient() - self.client.force_authenticate(user=testuser) - - def check_schema(self, schema, obj): - schema_checker = SchemaChecker(self.schema["definitions"]) - # print(vars(schema_checker)) - schema_checker.check(schema, obj) - - def get_valid_object_id(self): - response = self.client.get(format_url(f"/{self.viewname}/")) - check_response_valid(status.HTTP_200_OK, response) - if len(response.data["results"]) == 0: - return None - - return response.data["results"][0].get('id', None) - - def get_endpoint_schema(self, path, method): - paths = self.schema["paths"] - methods = paths.get(path, None) - assert methods is not None, f"{path} not found in {[path for path in paths.keys()]}" - - endpoint = methods.get(method, None) - assert endpoint is not None, f"Method {method} not found in {[method for method in methods.keys()]}" - - return endpoint - - def construct_response_data(self, obj_id): - obj = self.model.objects.get(id=obj_id) - request = APIView().initialize_request(APIRequestFactory().request()) - serialized_obj = self.serializer(context={"request": request}).to_representation(obj) - - for name, transformer in self.field_transformers.items(): - serialized_obj[name] = transformer(serialized_obj[name]) - - return serialized_obj - - @skipIfNotSubclass(ListModelMixin) - def test_list_endpoint(self, extra_args=None): - endpoints = self.schema["paths"][f"/{self.viewname}/"] - response = self.client.get(format_url(f"/{self.viewname}/"), extra_args) - check_response_valid(status.HTTP_200_OK, response) - - schema = endpoints['get']['responses']['200']['schema'] - obj = response.data - - self.check_schema(schema, obj) - - @skipIfNotSubclass(RetrieveModelMixin) - def test_retrieve_endpoint(self, extra_args=None): - endpoints = self.schema["paths"][f"/{self.viewname}/{{id}}/"] - response = self.client.get(format_url(f"/{self.viewname}/")) - check_response_valid(status.HTTP_200_OK, response) - ids = [obj['id'] for obj in response.data["results"]] - - schema = endpoints['get']['responses']['200']['schema'] - for id in ids: - print('id:', id) - response = self.client.get(format_url(f"/{self.viewname}/{id}/"), extra_args) - print('response type:', type(response)) - print('response data:', response.data) - check_response_valid(status.HTTP_200_OK, response) - obj = response.data - self.check_schema(schema, obj) - - @skipIfNotSubclass(UpdateModelMixin) - def test_patch_endpoint(self, extra_args=None): - operation = self.schema["paths"][f"/{self.viewname}/{{id}}/"]["patch"] - - id = self.get_valid_object_id() - if id is None: - self.skipTest("No data exists to test endpoint") - - data = self.construct_response_data(id) - - schema = operation['responses']['200']['schema'] - response = self.client.patch(format_url(f"/{self.viewname}/{id}/"), data, format='json') - check_response_valid(status.HTTP_200_OK, response) - - obj = response.data - self.check_schema(schema, obj) - - @skipIfNotSubclass(UpdateModelMixin) - def test_put_endpoint(self, extra_data={}, extra_args=None): - operation = self.schema["paths"][f"/{self.viewname}/{{id}}/"]['put'] - - id = self.get_valid_object_id() - if id is None: - self.skipTest("No data exists to test endpoint") - - data = self.construct_response_data(id) - data.update(extra_data) - - schema = operation['responses']['200']['schema'] - response = self.client.put(format_url(f"/{self.viewname}/{id}/"), data, format='json') - check_response_valid(status.HTTP_200_OK, response) - - obj = response.data - self.check_schema(schema, obj) - - @skipIfNotSubclass(CreateModelMixin) - def test_post_endpoint(self, extra_data=[], extra_args=None): - operation = self.schema["paths"][f"/{self.viewname}/"]["post"] - - id = self.get_valid_object_id() - if id is None: - self.skipTest("No data exists to test endpoint") - - data = self.construct_response_data(id) - data.update(extra_data) - - print('data:', data) - - schema = operation['responses']['201']['schema'] - response = self.client.post(format_url(f"/{self.viewname}/"), data, format='json') - check_response_valid(status.HTTP_201_CREATED, response) - - print('response.data:', response.data) - - obj = response.data - self.check_schema(schema, obj) - - -class DevelopmentEnvironmentTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "development_environments" - self.viewset = DevelopmentEnvironmentViewSet - self.model = Development_Environment - self.serializer = DevelopmentEnvironmentSerializer - - -# Test will only work when FEATURE_AUTHENTICATION_V2 is the default -# class DojoGroupTest(BaseClass.SchemaTest): -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# self.viewname = "group" -# self.viewset = DojoGroupViewSet -# self.model = Dojo_Group -# self.serializer = DojoGroupSerializer - - -class EndpointStatusTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "endpoint_status" - self.viewset = EndpointStatusViewSet - self.model = Endpoint_Status - self.serializer = EndpointStatusSerializer - - # We can not simulate creating of the endpoint-finding relation with the same parameters as existing one. We will use another finding for this case - def test_post_endpoint(self): - super().test_post_endpoint(extra_data={"finding": "3"}) - - -class EndpointTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "endpoints" - self.viewset = EndPointViewSet - self.model = Endpoint - self.serializer = EndpointSerializer - self.field_transformers = { - "path": lambda v: (v if v else '') + "transformed/" - } - - -class EngagementTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "engagements" - self.viewset = EngagementViewSet - self.model = Engagement - self.serializer = EngagementSerializer - - # @testIsBroken - # fixed - def test_accept_risks(self): - operation = self.get_endpoint_schema("/engagements/{id}/accept_risks/", "post") - schema = operation['responses']['201']['schema'] - print(schema) - id = self.get_valid_object_id() - if id is None: - self.skipTest("No data exists to test endpoint") - - data = [ - { - "vulnerability_id": 1, - "justification": "test", - "accepted_by": "2" - } - ] - - response = self.client.post(format_url(f"/engagements/{id}/accept_risks/"), data, format='json') - check_response_valid(201, response) - print('response.data') - # print(vars(response)) - print(response.content) - obj = response.data - self.check_schema(schema, obj) - - # fixed - def test_notes_read(self): - operation = self.get_endpoint_schema("/engagements/{id}/notes/", "get") - schema = operation['responses']['200']['schema'] - id = self.get_valid_object_id() - if id is None: - self.skipTest("No data exists to test endpoint") - - response = self.client.get(format_url(f"/engagements/{id}/notes/")) - check_response_valid(200, response) - obj = response.data - self.check_schema(schema, obj) - - # fixed - def test_notes_create(self): - operation = self.get_endpoint_schema("/engagements/{id}/notes/", "post") - schema = operation['responses']['201']['schema'] - id = self.get_valid_object_id() - if id is None: - self.skipTest("No data exists to test endpoint") - - data = { - "entry": "test", - "author": 2, - } - - response = self.client.post(format_url(f"/engagements/{id}/notes/"), data, format='json') - check_response_valid(201, response) - obj = response.data - self.check_schema(schema, obj) - - -class FindingTemplateTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "finding_templates" - self.viewset = FindingTemplatesViewSet - self.model = Finding_Template - self.serializer = FindingTemplateSerializer - - # fixed - def test_post_endpoint(self): - super().test_post_endpoint() - - # fixed - def test_patch_endpoint(self): - super().test_patch_endpoint() - - # fixed - def test_put_endpoint(self): - super().test_put_endpoint() - - -class FindingTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "findings" - self.viewset = FindingViewSet - self.model = Finding - self.serializer = FindingSerializer - - # fixed - def test_list_endpoint(self): - super().test_list_endpoint({ - "related_fields": True - }) - - # fixed - def test_patch_endpoint(self): - super().test_patch_endpoint() - - # fixed - def test_put_endpoint(self): - super().test_put_endpoint() - - # fixed - def test_retrieve_endpoint(self): - super().test_retrieve_endpoint({ - "related_fields": True - }) - - -class JiraInstanceTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "jira_instances" - self.viewset = JiraInstanceViewSet - self.model = JIRA_Instance - self.serializer = JIRAInstanceSerializer - - # fixed - def test_list_endpoint(self): - super().test_list_endpoint() - - # fixed - def test_patch_endpoint(self): - super().test_patch_endpoint() - - # fixed - def test_put_endpoint(self): - super().test_put_endpoint(extra_data={"password": "12345"}) - - # fixed - def test_retrieve_endpoint(self): - super().test_retrieve_endpoint() - - # fixed - def test_post_endpoint(self): - super().test_post_endpoint(extra_data={"password": "12345"}) - - -class JiraFindingMappingsTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "jira_finding_mappings" - self.viewset = JiraIssuesViewSet - self.model = JIRA_Issue - self.serializer = JIRAIssueSerializer - self.field_transformers = { - "finding": lambda v: 3, - } - - -class JiraProjectTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "jira_projects" - self.viewset = JiraProjectViewSet - self.model = JIRA_Project - self.serializer = JIRAProjectSerializer - - -class MetadataTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "metadata" - self.viewset = DojoMetaViewSet - self.model = DojoMeta - self.serializer = MetaSerializer - - -class NoteTypeTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "note_type" - self.viewset = NoteTypeViewSet - self.model = Note_Type - self.serializer = NoteTypeSerializer - self.field_transformers = { - "name": lambda v: v + "_new" - } - - -class NoteTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "notes" - self.viewset = NotesViewSet - self.model = Notes - self.serializer = NoteSerializer - - -class ProductTypeTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "product_types" - self.viewset = ProductTypeViewSet - self.model = Product_Type - self.serializer = ProductTypeSerializer - self.field_transformers = { - "name": lambda v: v + "_new" - } - - -# Test will only work when FEATURE_AUTHENTICATION_V2 is the default -# class ProductTypeMemberTest(BaseClass.SchemaTest): -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# self.viewname = "product_type_members" -# self.viewset = ProductTypeMemberViewSet -# self.model = Product_Type_Member -# self.serializer = ProductTypeMemberSerializer - - -# Test will only work when FEATURE_AUTHENTICATION_V2 is the default -# class ProductTypeGroupTest(BaseClass.SchemaTest): -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# self.viewname = "product_type_groups" -# self.viewset = ProductTypeGroupViewSet -# self.model = Product_Type_Group -# self.serializer = ProductTypeGroupSerializer - - -class ProductTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "products" - self.viewset = ProductViewSet - self.model = Product - self.serializer = ProductSerializer - self.field_transformers = { - "name": lambda v: v + "_new" - } - - # fixed - def test_list_endpoint(self): - super().test_list_endpoint() - - # fixed - def test_patch_endpoint(self): - super().test_patch_endpoint() - - # fixed - def test_put_endpoint(self): - super().test_put_endpoint() - - # fixed - def test_retrieve_endpoint(self): - super().test_retrieve_endpoint() - - # fixed - def test_post_endpoint(self): - super().test_post_endpoint() - - -# Test will only work when FEATURE_AUTHENTICATION_V2 is the default -# class ProductMemberTest(BaseClass.SchemaTest): -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# self.viewname = "product_members" -# self.viewset = ProductMemberViewSet -# self.model = Product_Member -# self.serializer = ProductMemberSerializer - -# @testIsBroken -# def test_post_endpoint(self): -# super().test_post_endpoint() - -# @testIsBroken -# def test_patch_endpoint(self): -# super().test_post_endpoint() - - -# Test will only work when FEATURE_AUTHENTICATION_V2 is the default -# class ProductGroupTest(BaseClass.SchemaTest): -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# self.viewname = "product_groups" -# self.viewset = ProductGroupViewSet -# self.model = Product_Group -# self.serializer = ProductGroupSerializer - - -class RegulationTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "regulations" - self.viewset = RegulationsViewSet - self.model = Regulation - self.serializer = RegulationSerializer - - -class SonarqubeIssuesTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "sonarqube_issues" - self.viewset = SonarqubeIssueViewSet - self.model = Sonarqube_Issue - self.serializer = SonarqubeIssueSerializer - self.field_transformers = { - "key": lambda v: v + "_new" - } - - -class ProductAPIScanConfigurationTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "product_api_scan_configurations" - self.viewset = ProductAPIScanConfigurationViewSet - self.model = Product_API_Scan_Configuration - self.serializer = ProductAPIScanConfigurationSerializer - - -class SonarqubeTransitionTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "sonarqube_transitions" - self.viewset = SonarqubeIssueTransitionViewSet - self.model = Sonarqube_Issue_Transition - self.serializer = SonarqubeIssueTransitionSerializer - - -class StubFindingTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "stub_findings" - self.viewset = StubFindingsViewSet - self.model = Stub_Finding - self.serializer = StubFindingSerializer - - -class SystemSettingTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "system_settings" - self.viewset = SystemSettingsViewSet - self.model = System_Settings - self.serializer = SystemSettingsSerializer - - -class AppAnalysisTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "technologies" - self.viewset = AppAnalysisViewSet - self.model = App_Analysis - self.serializer = AppAnalysisSerializer - - # fixed - def test_patch_endpoint(self): - super().test_patch_endpoint() - - # fixed - def test_put_endpoint(self): - super().test_put_endpoint() - - # fixed - def test_post_endpoint(self): - super().test_post_endpoint() - - -class TestTypeTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "test_types" - self.viewset = TestTypesViewSet - self.model = Test_Type - self.serializer = TestTypeSerializer - self.field_transformers = { - "name": lambda v: v + "_new" - } - - -class TestsTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "tests" - self.viewset = TestsViewSet - self.model = Test - self.serializer = TestSerializer - - -class ToolConfigurationTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "tool_configurations" - self.viewset = ToolConfigurationsViewSet - self.model = Tool_Configuration - self.serializer = ToolConfigurationSerializer - - -class ToolProductSettingTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "tool_product_settings" - self.viewset = ToolProductSettingsViewSet - self.model = Tool_Product_Settings - self.serializer = ToolProductSettingsSerializer - - -class ToolTypeTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "tool_types" - self.viewset = ToolTypesViewSet - self.model = Tool_Type - self.serializer = ToolTypeSerializer - self.field_transformers = { - "name": lambda v: v + "_new" - } - - -class UserTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "users" - self.viewset = UsersViewSet - self.model = Dojo_User - self.serializer = UserSerializer - self.field_transformers = { - "username": lambda v: v + "_transformed" - } - - -class LanguageTypeTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "language_types" - self.viewset = LanguageTypeViewSet - self.model = Language_Type - self.serializer = LanguageTypeSerializer - - -class LanguageTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "languages" - self.viewset = LanguageViewSet - self.model = Languages - self.serializer = LanguageSerializer - - def test_post_endpoint(self): - super().test_post_endpoint(extra_data={"language": 2}) - - -class AnnouncementTest(BaseClass.SchemaTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.viewname = "announcements" - self.viewset = AnnouncementViewSet - self.model = Announcement - self.serializer = AnnouncementSerializer - - def test_post_endpoint(self, extra_data=[], extra_args=None): - self.skipTest('Only one Announcement can exists')