【问题标题】:Django Rest Framework - Deny User from PUSH when User is not Object OwnerDjango Rest Framework - 当用户不是对象所有者时拒绝用户推送
【发布时间】:2017-07-22 20:42:23
【问题描述】:

目前我设置了权限,如果用户不是Stock 的对象所有者,则他们无法进行 GET、DELETE 和 PUT。但由于某种原因,当用户执行 PUSH 时权限不起作用,即任何用户都可以将 Note 推送到 Stock,即使他们不是 Stock Owner

为什么?以及我如何正确检查User 推送Note 时,他们必须是Stock 的所有者?


这是一个通过HTTPie发送的示例数据PUSH:

http -a testuser:testpw POST http://127.0.0.1:8000/api/v1/notes/ note="Testing API" stock="36"

其中“36”是现有 Stock 的 stock_id。

这里是stock_note/models.py

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
import uuid

class Stock(models.Model):
    '''
    Model representing the stock info.
    '''
    user = models.ForeignKey(User)
    book_code = models.CharField(max_length=14, null=True, blank=True)

    def __str__(self):
        return self.book_code

class Note(models.Model):
    '''
    Model representing the stock note.
    '''
    user = models.ForeignKey(User)
    note = models.TextField(max_length=560)
    stock = models.ForeignKey(Stock, related_name='notes')
    date_note_created = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.note

这是api/serializers.py

from stock_note.models import Stock, Note
from rest_framework import serializers

class StockSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())
    notes = serializers.PrimaryKeyRelatedField(read_only=True, many=True)

    class Meta:
        model = Stock
        fields = ('id', 'user', 'book_code', 'notes')

class NoteSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = Note
        fields = ('user', 'note', 'stock')

这是api/views.py

from rest_framework import generics
from stock_note.models import Stock, Note
from api.serializers import StockSerializer, NoteSerializer
from rest_framework.permissions import IsAuthenticated
from api.permissions import IsOwner

# Create your views here.

class StockList(generics.ListCreateAPIView):
    serializer_class = StockSerializer
    permission_classes = (IsAuthenticated, IsOwner)

    def get_queryset(self):
        user = self.request.user
        return Stock.objects.filter(user=user)

    def perform_create(self, serializer):
        serializer.save()

    def perform_update(self, serializer):
        serializer.save()

class NoteList(generics.ListCreateAPIView):
    serializer_class = NoteSerializer
    permission_classes = (IsAuthenticated, IsOwner)

    def get_queryset(self):
        user = self.request.user
        return Note.objects.filter(user=user)

    def perform_create(self, serializer):
        serializer.save()

    def perform_update(self, serializer):
        serializer.save()

class StockListDetail(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = StockSerializer
    permission_classes = (IsAuthenticated, IsOwner)
    lookup_url_kwarg = 'stock_id'

    def get_queryset(self):
        stock = self.kwargs['stock_id']
        return Stock.objects.filter(id=stock)

class NoteListDetail(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = NoteSerializer
    permission_classes = (IsAuthenticated, IsOwner)
    lookup_url_kwarg = 'note_id'

    def get_queryset(self):
        note = self.kwargs['note_id']
        return Note.objects.filter(id=note)

这是api/permissions.py

from rest_framework import permissions

class IsOwner(permissions.BasePermission):
    def has_permission(self, request, view):
        return request.user and request.user.is_authenticated()

    def has_object_permission(self, request, view, obj):
        return obj.user == request.user

最后这是api/urls.py

from django.conf.urls import url, include
from api import views

urlpatterns = [
    #Endpoint to allow GET and POST stocks.
    url(r'^v1/stocks/$', views.StockList.as_view()),
    #Endpoint to allow GET and POST a note to a stock.
    url(r'^v1/notes/$', views.NoteList.as_view()),
    #Endpoint to allow GET, POST, PUSH, DELETE a stocknote
    url(r'^v1/stocks/(?P<stock_id>[0-9]+)/$', views.StockListDetail.as_view()),
    #Endpoint to allow GET, POST, PUSH, DELETE a Note
    url(r'^v1/notes/(?P<note_id>[0-9]+)/$', views.NoteListDetail.as_view()),
]

更新:

根据 Tom 的回答,NoteSerializer 现在看起来像这样,这意味着用户现在只能在他们是 Stock 所有者的情况下推送注释(新增功能是 validate_stock 函数)。请注意,汤姆的答案与此代码之间存在一个区别:我不只是检查value,而是检查value.id。这在 validate_stock 函数的 cmets 中有进一步的解释:

class NoteSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = Note
        fields = ('user', 'note', 'stock')

    def validate_stock(self, value):
        '''
        This function checks if the User is the owner of Stock
        before allowing the User to PUSH a Note to the Stock.
        '''

        # You have to get the object ID because otherwise you get following error when
        # you try to perform Stock.object.get(...):
        #TypeError: int() argument must be a string, a bytes-like object or a number, not 'Stock'
        value_id = value.id

        stock_obj = Stock.objects.get(pk=value_id)
        user = self.context['request'].user

        if not stock_obj.user == user:
            raise serializers.ValidationError("You do not have permission to perform this action.")
        return value

【问题讨论】:

    标签: python-3.x django-rest-framework


    【解决方案1】:

    当您从POSTv1/notes/ 时,唯一会运行的权限检查是has_permission。 URL 中没有引用现有实例,因此不会在视图上调用 get_object,也不会调用 has_object_permission 检查(没有实例可以调用它。)

    在这种情况下,您需要对序列化程序类强制执行验证,以确保股票值必须对应于用户拥有的 Stock 实例。

    类似的东西...

    def validate_stock(self, value):
        stock = Stock.objects.get(pk=value)
        user = self.context['request'].user
        if not stock.user == user:
            raise serializers.ValidationError(...)
        return value
    

    【讨论】:

    • 感谢汤姆的回复。我猜验证强制需要在 NoteSerializer 中进行。当我将您上面提供的代码插入 NoteSerializer 时,我收到以下错误:TypeError: int() argument must be a string, a bytes-like object or a number, not 'Stock' 不确定为什么现在会出现此错误?请记住,我是 Django 和 DRF 的新手,如果我没有正确理解您的答案,我深表歉意。
    • 一点调试,如果我只是这样做,看起来 DRF 会生成提到的 TypeError:def validate_stock(self, value): stock = Stock.objects.get(pk=value)
    • 只是想补充一点,它需要几个小时的调试(!),但我最终通过检查value.id 而不仅仅是value 解决了TypeError。我已经用新代码更新了我的原始帖子,以防有人遇到类似问题。再次感谢汤姆的帮助。
    猜你喜欢
    • 2018-06-25
    • 1970-01-01
    • 2017-08-01
    • 2015-01-14
    • 1970-01-01
    • 2013-03-24
    • 1970-01-01
    • 2021-11-08
    • 1970-01-01
    相关资源
    最近更新 更多