如何在DRF中更新’Allow’标头以匹配OPTIONS请求的权限?

huangapple go评论75阅读模式
英文:

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

问题

我正在使用Django Rest Framework 3.14与ModelViewsets以及全局设置的DjangoModelOrAnonReadOnly权限类。

在这个配置下,初始情况下,我的JSON API似乎对OPTIONS请求做出了误导性的响应,即对未经身份验证的OPTIONS请求/api/collections/collectionA/items的响应标头中包含了Allow: GET, POST, HEAD, OPTIONS(正确应该是:GET, HEAD, OPTIONS)。然而,如果我定义自己的metadataclass并执行以下操作:

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})

我能够获得正确的allowed_actions(GET, OPTIONS, HEAD)。然而,我的问题在于,上述代码块中的最后一个语句未修改标头。

我应该如何更新标头,以确保Allow标头正确反映我的API的状态?


背景信息:这是在实现OGC API Features端点时需要的。这是用于地理空间数据的OpenAPI定义。详细信息可以在这里找到。

来自第4部分(CRUD操作):

服务器不需要为其提供的每个可变资源实现本规范中描述的每种方法(即POST、PUT、PATCH或DELETE)。此外,支持在集合中添加、修改或删除资源的服务器不太可能是一个开放的服务器。也就是说,对服务器的访问,特别是允许资源创建、修改和/或删除的操作,将受到控制。此类控制可能采取策略要求的形式(例如,此服务器上的资源可以插入或更新但不能删除)或用户访问控制要求的形式(例如,用户“X”只允许创建资源但不允许更新或删除资源)。无论服务器必须能够在所采用的控制上下文中宣传,对于它提供的每个资源,哪些方法是可用的。这是使用HTTP OPTIONS方法来实现的。

HTTP OPTIONS方法允许服务器明确声明对于特定资源端点支持哪些HTTP方法。本规范处理了HTTP POST、PUT、PATCH和DELETE方法,但可以为特定资源列出任何相关的HTTP方法。

英文:

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.

答案1

得分: 4

更新 - 1

看起来你需要覆盖 finalize_response(...) 方法来修补 Allow 标头。

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

我觉得你误解了 Allow 标头的概念。

Allow 标头列出了资源支持的方法集。

由于你正在使用 ModelViewsets,我假设你将它与路由器一起使用,并使用所有默认配置,那么很可能客户端已启用所有的 HTTP 方法。换句话说,Allow 标头返回的值不受授权检查的影响。

英文:

Update - 1

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

<pre><code>
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

<b>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&lt;/b&gt;

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)
    &lt;b&gt;if request.method == &quot;OPTIONS&quot;:
        allow_str = &quot;, &quot;.join(get_allow_list(request, self))
        response.headers[&quot;Allow&quot;] = allow_str&lt;/b&gt;
    return response

</code></pre>


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.

huangapple
  • 本文由 发表于 2023年7月27日 22:05:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76780569.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定