2

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.

Denis Rouzaud
  • 2,412
  • 2
  • 26
  • 45
WhyNotTryCalmer
  • 357
  • 5
  • 15

1 Answers1

4

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.

JPG
  • 82,442
  • 19
  • 127
  • 206
  • Thanks for the input. I've added the documentation of the endpoint we are implementing and it's stated that the option method might depend on user access control, so indeed authorization checks. – Denis Rouzaud Aug 01 '23 at 07:28
  • @JPG We are not _presupposing_ that rest-framework should let response headers reflect a request's authentication or permissions. This is indeed not a universal standard and we don't mind the defaults. The question for us is how to change the defaults in the way hinted in the post above, namely, to manually change headers to reflect the response's authentication or permissions. Our rationale for doing is the specs quoted by Denis Rouzaud. – WhyNotTryCalmer Aug 01 '23 at 09:49
  • Okay, that makes sense. I think The updated version of the answer might help you achieve what you need @DenisRouzaud – JPG Aug 01 '23 at 18:21
  • Ah that's interesting, thanks, will give it a try – WhyNotTryCalmer Aug 02 '23 at 13:48