Django DRF restrict profile model creation to authenticated user - django

I have a many User model and a Customer model with a OneToOne relation to the User.
I have established authentication for User model where a user can edit/update/delete only his profile. But I want the customer model also to be accessible (list/create/update etc) by the authenticated user.
My permissions class for Customer:
class UpdateCustomerProfile(permissions.BasePermission):
"""Allow customers to edit their own profile """
def has_permission(self, request, view):
"""Check if user is authenticated and has permisson to access customer model """
if view.action == 'list':
return request.user.is_authenticated and request.user.is_superuser
elif view.action == 'create':
return request.user.is_authenticated
elif view.action in ['retrieve', 'update', 'partial_update', 'destroy']:
return request.user.is_authenticated
else:
return False
My customer view set:
class CustomerViewSet(viewsets.ModelViewSet):
"""Handle creating reading and updating Users in system"""
serializer_class = serializers.CustomerSerializer
queryset = models.Customer.objects.filter()
permission_classes = (permissions.UpdateCustomerProfile,)
But I get an error saying:
"detail": "Authentication credentials were not provided."
even If I add the token in Authorisation field of Header.
UPDATE:
If I add authentication_classes = (TokenAuthentication,) to my CustomerViewSet I get an error:
"detail": "You do not have permission to perform this action."
I'm confused, I want to leverage the current authorisation of an User to authorise creation of a customer. i.e Only An authenticated user should be able to create his Customer profile
How can I fix this?

You should add authentication_classes attribute to the view
from rest_framework.authentication import TokenAuthentication
class CustomerViewSet(viewsets.ModelViewSet):
"""Handle creating reading and updating Users in system"""
serializer_class = serializers.CustomerSerializer
queryset = models.Customer.objects.filter()
permission_classes = (permissions.UpdateCustomerProfile,)
authentication_classes = (TokenAuthentication,)

Related

Django rest framework : custom object permissions doesn't work

My problem is very simple : I'm trying to create some custom permissions for my django rest API. This is my code (permission.py) :
class UserPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj == request.user
I just want that the users can only get, delete and update their own account.
The problem is that I think my code is not read by Django. I have try to always return false (without any condition) and it does nothing. I have also try to print some debug message at the beginning of the file and it's does nothing.
(My file permissions.py is at the root of my application)$
This is my user view (UserView.py) :
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by("-date_joined")
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
swagger_tag = ["User"]
class LoginView(KnoxLoginView):
"""
API endpoint allowing the user to login and receive a token
"""
permission_classes = [
permissions.AllowAny,
]
#swagger_auto_schema(request_body=AuthTokenSerializer)
def post(self, request, format=None):
serializer = AuthTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data["user"]
login(request, user)
return super(LoginView, self).post(request, format=None)
As #UtkucanBıyıklı says in their comment, you should specify the permission in the ViewSet:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.order_by('-date_joined')
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated, UserPermissions]
swagger_tag = ['User']

How to set permissions for CRUD operations in ModelViewSet Django

I have a viewset for model News.
I want to do next permissions:
All people can see news.
Only authorized users and admin can create news.
Only owner and admin can update news.
Only admin can delete news.
How can I set different permissions for each operation? For create I want to use: IsAuthenticated and IsAdminUser. For update I want to use IsAdminUser and I create my own permission for owner. For delete I want to use also IsAdminUser.
view:
class NewsViewSet(viewsets.ModelViewSet):
queryset = News.objects.all()
serializer_class = NewsSerializer
permission:
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
class Viewset(BaseModelViewSet):enter code here
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes_by_action = {
'create': (permissions.IsAdminUser,),
'list': (permissions.IsAuthenticatedOrReadOnly,),
'retrieve': (permissions.AllowAny,),
'update': (permissions.AllowAny,),
'destroy': (permissions.IsAdminUser,),
'search': (permissions.IsAuthenticated,)
Like this you can use the pre-built permission or create custom permission class

DRF Viewset remove permission for detail route

I have a basic Viewset:
class UsersViewSet(viewsets.ModelViewSet):
permission_classes = (OnlyStaff,)
queryset = User.objects.all()
serializer_class = UserSerializer
It is bind to the /api/users/ endpoint. I want to create a user profile page, so I need only a particular user, so I can retrieve it from /api/users/<id>/, but the problem is that I want /api/users/<id>/ to be allowed to anyone, but /api/users/ to keep its permission OnlyStaff, so no one can have access to the full list of users.
Note: Perhaps it's not such a good implementation, since anyone could brute force the data incremeting the id, but I'm willing to change it from <id> to <slug>.
How can I delete the permission from detail route?
Thanks in advance.
Override the get_permissions() method as below
from rest_framework.permissions import AllowAny
class UsersViewSet(viewsets.ModelViewSet):
permission_classes = (OnlyStaff,)
queryset = User.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
if self.action == 'retrieve':
return [AllowAny(), ]
return super(UsersViewSet, self).get_permissions()
It would help if you posted the permission class.
But going off what you posted, it appears that only staff users can have access to the endpoints bound to that viewset. Meaning no other user type/role can access those endpoints.
Going off your question, it seems like you want to setup a IsOwnerOrStaffOrReadOnly permission and over ride the list route function of the ModelViewSet and replace permission_classes and then call super
class UsersViewSet(viewsets.ModelViewSet):
permission_classes = (IsOwnerOrStaffOrReadOnly,)
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request, *arg, **kwargs):
self.permission_classes = (OnlyStaffCanReadList,)
super(UsersViewSet, self).list(request, *args, **kwargs) // python3 super().list(request, *args, **kwargs)
is Owner object permission class
class IsOwnerOrStaffOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
if request.user.role == 'staff':
return True
# Instance must have an attribute named `owner`.
return obj.owner == request.user
only staff can read permission class
class OnlyStaffCanReadList(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.user.role == 'Staff':
return True
else:
return False
as provided in the comments, your user model must have the owner role. if you are using the django user model you can just do a obj.id == request.user.id comparison

How to set different permissions for different methods such as POST and List when use Django REST Framework's ListCreateAPIView

I built a Follow model to record the social networking behaviour and would like to simulate the following action. Every authenticated user can follow the others.
class Follow(models.Model):
user = models.ForeignKey(User)
follower = models.ForeignKey(User, related_name="followers")
follow_time = models.DateTimeField(auto_now_add=True, blank=True)
class Meta:
unique_together = ('user', 'follower')
def __unicode__(self):
return u'%s, %s' % (self.user.username, self.follower)
And the FollowSerializer is:
class FollowSerializer(serializers.ModelSerializer):
class Meta:
model = Follow
field = ('user', 'follower', 'follow_time')
The view that I am using is:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
def get_queryset(self):
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
I am registering it in the urls as:
url(r'^api/users/(?P<pk>[0-9]+)/following/$', views.FollowingEnumByUserID.as_view()),
Every authenticated user can view the following relation, no restricts. But I would like to just allow the authenticated user to add the following relation by himself/herself, which means there should be request.user == follower. How can I do this?
I would like to add the FollowingDelete view to just allow the user to add a following relation by himself/herself.
So I updated the url.py as:
url(r'^api/users/(?P<pk>[0-9]+)/following/$', views.FollowingEnumByUserID.as_view()),
url(r'^api/users/(?P<pk>[0-9]+)/following/(?P<following_id>[0-9]+)/$', views.FollowingDelete.as_view()),
The permission that I am using is:
class IsFollowerOrReadOnly(permissions.BasePermission):
"""
View-level permission to allow the follower to edit the following relation
"""
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
try:
follower = User.objects.get(id=view.kwargs["pk"])
except User.DoesNotExist:
#Reject any request for an invalid user
return False
return follower == request.user
And the views are:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_class = (IsFollowerOrReadOnly)
def get_queryset(self):
"""
List all the people the input user is following
"""
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
class FollowingDelete(generics.DestroyAPIView):
serializer_class = FollowSerializer
permission_class = (IsAuthenticated, IsFollowerOrReadOnly)
def get_queryset(self):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
return Follow.objects.filter(user=user_id, follower=follower_id)
Now the questions are:
The permission class doesn't work totally.
How to rewrite the DestroyAPIView, should I override the get_queryset function?
Django REST framework provides custom permission handling that allows you to handle complex permissions on the view and object level. In order to do what you are looking for, you are going to have to create a custom permission, but it's surprisingly easy.
Every authenticated user can follow the others.
DRF provides an IsAuthenticated permission that allows you to do this very easily. All you have to do is add it to the permission_classes on the view, or globally through the settings.
from rest_framework import permissions
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_classes = (permissions.IsAuthenticated, )
def get_queryset(self):
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
There is another restriction, which is the one that requires the custom permission class.
But I would like to just allow the authenticated user to add the following relation by himself/herself
This requires checking the request method (which I'm assuming is POST) and also the user who is being followed.
Lets start off with the easy check, the request method. Django REST framework provides permission classes that check the request method, such as IsAuthenticatedOrReadOnly, so we can look at the code to see how it is being done. From there it's just a matter of having a check against the request type.
class PostIfFollower(BasePermission):
"""
The request is not POST or the request user is the follower.
"""
def has_permission(self, request, view):
if request.method != "POST":
return True
return False
This code will reject all requests that come in using the POST method, while allowing all others. The second step in creating this permission is doing the user check, so only the follower can add new people that they are following. This requires getting the follower and checking that against request.user.
class PostIfFollower(BasePermission):
"""
The request is not POST or the request user is the follower.
"""
def has_permission(self, request, view):
if request.method != "POST":
return True
try:
follower = User.objects.get(id=view.kwargs["pk"])
except User.DoesNotExist:
# Reject any requests for an invalid user
return False
return follower == request.user
This builds upon the last permission class by getting the user from the url (not allowing it if the user doesn't exist) and checking if they are the current user.
After I modified the typos, the permission class works now:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_classes = (IsFollowerOrReadOnly,)
def get_queryset(self):
"""
List all the people the input user is following
"""
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
class FollowingDelete(generics.DestroyAPIView):
serializer_class = FollowSerializer
permission_classes = (IsAuthenticated, IsFollowerOrReadOnly,)
def get_queryset(self):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
return Follow.objects.filter(user=user_id, follower=follower_id)
And I make the FollowingDelete view work successfully by overriding the get_object() function.
def get_object(self, *args, **kwargs):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
try:
return Follow.objects.get(user=user_id, follower=follower_id)
except Follow.DoesNotExist:
raise Http404("No such following relation")

Adding the auth token to the UserSerializer

How would I add the auth token to the userSeralizer?
This is my serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
And then in my views the url:
#api_view(['POST', 'DELETE'])
def create_user(request):
"""
API endpoint to register a new user
"""
model = User
serializer_class = UserSerializer
username, password = request.POST['username'], request.POST['password']
try:
user = User.objects.create_user(username, username, password)
except IntegrityError:
user = User.objects.get(username=username, email=username)
# the users token, we will send this to him now.
token = Token.objects.get(user=user)
if request.method == "POST":
serializer = UserSerializer(user)
return Response(data)
I think it would be nice to have the token in the serializer, or not?
From a security standpoint, auth tokens should not be passed around in the serializer. If your User view can be seen by anyone, then anyone could to impersonate any user without much trouble.
Tokens are meant to be returned only after successful login, not when an user is created. This is why most sites require Users to sign in just after the account was created.
But for the sake of the question, there are several ways to add items to serializers.
First, is a little hacky but doesn't require custom models
# Not adding context would raise a DeprecationWarning in the console
serializer = UserSerializer(user, context={'request': request})
data = serializer.data
data['token'] = token
return Response(data)
Last but not least, is a bit more elegant but requires a custom User class. However you could use it in your app models.
# in models.py inside your User model
def get_my_token(self):
return Token.objects.get(user=user)
my_token = property(get_my_token)
and then in the serializer class add the field with the token (remember to add it to the fields attribute in your meta class)
class UserSerializer(serializers.ModelSerializer):
token = serializers.Field(source='my_token')
class Meta:
model = User
fields = ('id', 'username', 'token')