英文:
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</b>
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)
<b>if request.method == "OPTIONS":
allow_str = ", ".join(get_allow_list(request, self))
response.headers["Allow"] = allow_str</b>
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论