At the moment i have permissions set which prevent a user from GET, DELETE and PUT if they are not the Object Owner of Stock
. But for some reason, the permissions do not work when the user performs a PUSH i.e. any user can PUSH a Note
to a Stock
even if they are not the Stock Owner
.
Why? And how do i properly check that when a User
PUSHs a Note
, they must be the Owner of Stock
?
This is an example data PUSH sent via HTTPie
:
http -a testuser:testpw POST http://127.0.0.1:8000/api/v1/notes/ note="Testing API" stock="36"
Where "36" is the stock_id for an existing Stock.
Here is the 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
This is the 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')
This is the 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)
This is the 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
And finally this is the 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()),
]
UPDATE:
Following on from Tom's answer the NoteSerializer now looks like this which means a User is now only able to PUSH a Note if they are the Owner of Stock (the new addition is the validate_stock function). Keep note that there is one difference between Tom's answer and this code: instead of just checking the value
, i am checking for the value.id
. This is explained further in the comments of the validate_stock function:
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