Search code examples
djangodjango-rest-frameworkhttp-headersdjango-permissionsogc

How to update 'Allow' headers in DRF to match an OPTIONS request's permissions?


I am using Django Rest Framework 3.14 with ModelViewsets and a settings-wide DjangoModelOrAnonReadOnly permission class.

Given this config, out of the box my JSON API seems to respond to OPTIONS requests in a misleading way, i.e. sending unauthenticated OPTIONS requests to /api/collections/collectionA/items is replied with Allow: GET, POST, HEAD, OPTIONS in the headers (correct would be: GET, HEAD, OPTIONS). However if I define my own metadataclass and do something like:

def options(self, request, *args, **kwargs) -> response.Response:
    allowed_actions = self.metadata_class().determine_actions(request, self)
    allowed_actions = ", ".join(allowed_actions.keys())
    # ^ allowed_actions is correct 
    data = self.metadata_class().determine_metadata(request, self)
    return response.Response(data, headers={"Allow": allowed_actions})

I am able to get the correct allowed_actions (GET, OPTIONS, HEAD). However, and that is my issue, headers are unmodified by the last statement in the snipper above.

How can I update my headers to ensure that the Allow headers correctly reflect the state of my API?


Context: this is required while implementing an OGC API Features endpoint. It's an OpenAPI defintion for Geospatial data. Details can be found here.

And from the part 4 (CRUD operations):

A server is not required to implement every method described in this specification (i.e. POST, PUT, PATCH or DELETE) for every mutable resource that it offers. Furthermore, a server that supports the ability to add, modify or remove resources from collections is not likely to be an open server. That is, access to the server, and specifically the operations that allow resource creation, modification and/or removal, will be controlled. Such controls might, for example, take the form of policy requirements (e.g. resources on this server can be inserted or updated but not deleted) or user access control requirements (e.g. user "X" is only allowed to create resources but not update or delete resources). Regardless of the controls the server must be able to advertise, within the control context in place, which methods are available for each resource that it offers. This is accomplished using the HTTP OPTIONS method.

The HTTP OPTIONS method allows the server to explicitly declare which HTTP methods are supported for a particular resource endpoint. This specification deals with the HTTP POST, PUT, PATCH and DELETE methods but any relevant HTTP method may be listed for a particular resource.


Solution

  • Update - 1

    It seems to be you need to override the finalize_response(...) method to patch the Allow header.

    
    from django.http import Http404
    from rest_framework import permissions, viewsets
    from rest_framework.exceptions import APIException, PermissionDenied
    from rest_framework.request import clone_request
    
    from polls.models import Poll
    
    from .serializers import PollSerializer
    
    
    def get_allow_list(request, view) -> list[str]:
        allowed_methods = []
        for method in view.allowed_methods:
            view.request = clone_request(request, method)
            try:
                view.check_permissions(view.request)
                allowed_methods.append(method)
            except (APIException, PermissionDenied, Http404):
                pass
    
        return allowed_methods
    
    
    class PollViewSet(viewsets.ModelViewSet):
        serializer_class = PollSerializer
        queryset = Poll.objects.all()
        permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]
    
        def finalize_response(self, request, response, *args, **kwargs):
            response = super().finalize_response(request, response, *args, **kwargs)
            if request.method == "OPTIONS":
                allow_str = ", ".join(get_allow_list(request, self))
                response.headers["Allow"] = allow_str
            return response
    

    I think you missunderstood the concept of Allow header

    The Allow header lists the set of methods supported by a resource.

    Since you are using a ModelViewsets, and I assume you are using it with a router, with all default configurations, then, most likely, you will have all the HTTP methods enabled for the client. In other words, the Allow header returns the value irrespective of the Authorization checks.