I'm creating a simple web app and I cannot find any way to forbid other users from changing your password. This is minimal code:
# serializers.py
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# have to use custom create method because default method calls `User.objects.create()` which doesn't take care of password hashing and other important stuff
return User.objects.create_user(**validated_data)
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
read_only_fields = ('id', 'username')
# password is set to write_only because I don't want to send it to anybode (even though it's just a hash)
extra_kwargs = {'password': {'write_only': True}}
# views.py
from .serializers import UserSerializer
from rest_framework import generics
from django.contrib.auth.models import User
class UserDetails(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
I could take care of this myself by using APIView.
# views.py
from .serializers import UserSerializer
from rest_framework.views import APIView
from django.contrib.auth.models import User
class UserDetails(APIView):
def put(self, request, format=None):
serialized = UserSerializer(data=request.DATA)
if not serialized.is_valid():
return # probably HTTP 400 Error code
if request.user.id != serialized.data['id']:
# this if is what I'm trying to achieve
return # probably HTTP 403 Error code
user = User.objects.update(
id=serialized.data['id'],
email=serialized.data['email']
)
if 'password' in request.DATA:
user.set_password(request.DATA['password'])
return # probably HTTP 200 Error code
Unfortunately, that would have caused that scheme generated by rest_framework.schemas.get_schema_view would be incomplete. And I use for communication CoreAPI (which cannot communicate with something that is not described in the scheme) so I cannot do that. I haven't found anything in the official documentation.
This seems to be a too basic problem that has a super easy solution that I missed. Thanks for any ideas or places where to look.
PS: I'm using django2.1 with python3.6
Edit: osobacho's solutions is clean and works like charm. Anyway I also need to allow modifications (let's say of TODO-list) only to creator of that todo list. Thought that solution for password problem would be applicable but it's not.
You can get the user in the request. That way each user will only be able to change their own password.
class UserDetails(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_object(self):
return self.request.user
For your second question take a look at django_restframework http://www.django-rest-framework.org/api-guide/permissions/ permissions here is an example:
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `owner` attribute.
"""
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
# Instance must have an attribute named `owner`.
return obj.owner == request.user
then you need to add to your view:
permission_classes = (IsOwnerOrReadOnly,)
hope it helps
Related
How to log out in rest_framework?
This is my user serializer. I'm using rest and very very newbie. Sign up and Login working, but have no clue how to impelement logout.
class UserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(write_only=True)
class Meta:
model = User
fields = ('id', 'username', 'password', 'email')
write_only_fields = ('username', 'email', 'password',)
read_only_fields = ('id', )
Sign up part
class UserCreateAPIView(generics.CreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.AllowAny]
Loggin code; i am using JWT authentication.
path(r'login/', obtain_jwt_token, name='ObtainJWTToken'),
When i use this code:
#api_view(['POST'])
def logout(request):
request.auth.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
i get error: 'bytes' object has no attribute 'delete'
When i use Djoser code:
permission_classes = settings.PERMISSIONS.token_destroy
def post(self, request):
utils.logout_user(request)
return Response(status=status.HTTP_204_NO_CONTENT)
getting this error: type object 'Token' has no attribute 'objects'
I assume you use TokenAuthentication.
In this case, give this method a shot:
class LogoutAPIView(APIView):
def get(self, request):
request.user.auth_token.delete()
return Response(status=status.HTTP_200_OK)
However, please note that this deletes the token and consequently forces a logout.
In case you use SessionAuthentication, the body of your get() method inside the LogoutAPIView class could be just logout(request).
eg.
def get(self, request):
logout(request)
return Response(status=status.HTTP_200_OK)
In the case of JWT authentication, as it is stateless and each JWT token is valid for some time, that means even if you remove the token from the DB, the user will still be able to use your endpoints for a short amount of time, until it's expired.
Therefore, depending on how deep you want to go, you can eg. implement a "tokens_unable_to_login" cache key and check if the requests include a JWT token that should not log in. Or you let it expire - you can toy around with the expiration time of the token and see what's ideal for your use-case. That's up to your business requirements.
Let me know how did this go for you.
As stated in DRF documentation http://www.django-rest-framework.org/api-guide/parsers/#multipartparser, in order to parse multipart/form-data, the MultiPart and form parser must be used. I have a supiscion this is a problem in the Django Rest Framework because I saw a solution on their github issue saying it work using APIView.
from django.contrib.auth.models import User
from rest_framework import viewsets
from api.serializers import UserSerializer,
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework import status
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
parser_classes = (MultiPartParser, FormParser)
serializer_class = UserSerializer
Picture of me sending request to Postman with result:
Edit: Adding UserSerializer class
class UserSerializer(serializers.HyperlinkedModelSerializer):
snapcapsules = SnapCapsuleSerializer(
many=True,
read_only=True,
allow_null=True,
)
class Meta:
model = User
fields = ('snapcapsules', 'url', 'username', 'email', 'password', )
write_only_fields = ('password',)
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, instance, validated_data):
capsules_data = validated_data.pop('snapcapsules')
for capsule in capsules_data:
SnapCapsule.objects.create(user=instance, **capsule)
return instance
This is might not the issue with the ContentType. The error response says that,the UserSerializer excpect a payloadordata which include username and password field
So, Please try to add those fields to the request body and try again
UPDATE
The problem with the Orginal Post was, he added extra headers in POSTMAN (see the pic below) .
Postman will add appropriate Headers aromatically for you. So, you don't have to explicitly mention it
I removed one line and then it worked!
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
Also, the serializer can be rewrite as follows:
class UserSerializer(serializers.HyperlinkedModelSerializer):
...
def create(self, validated_data):
return User.objects.create_user(
username=validated_data['username'],
email=validated_data['email'],
password=validated_data['password']
)
...
I'm using Django User model in my Django project. my user view is:
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from rest_framework import generics
from rest_framework import permissions
from rest_framework import status
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from myapp.serializers.user import UserSerializer, UserListSerializer
class UserList(generics.ListCreateAPIView):
model = User
permission_classes = (permissions.IsAuthenticated, )
_ignore_model_permissions = True
serializer_class = UserListSerializer
queryset = User.objects.exclude(pk=-1)
def post(self, request, *args, **kwargs):
userName = request.DATA.get('username', None)
userPass = request.DATA.get('password', None)
user = User.objects.create_user(username=userName, password=userPass)
if not user:
return Response({'message': "error creating user"}, status=status.HTTP_200_OK)
return Response({'username': user.username}, status=status.HTTP_201_CREATED)
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
model = User
permission_classes = (permissions.IsAuthenticated, )
_ignore_model_permissions = True
serializer_class = UserSerializer
queryset = User.objects.exclude(pk=-1)
When I try to view users page logged in as a superuser, I can see the list of all the users. But when I try to access it with a non-superuser, I get an empty list.
I like every user to be able to view the user list but only its own user detail if it is non superuser. I tried using signals (such as post_migrate) but the problem is that for each user I need to give view permission to every other user every time I migrate.
Is there any easier way to do this?
I can see the list of all the users. But when I try to access it with a non-superuser, I get an empty list.
From your code you should be able to access UserList even if you are not superuser.
I like every user to be able to view the user list but only its own user detail if it is non superuser.
Try custom permission.
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
return obj == request.user
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
model = User
permission_classes = (permissions.IsOwner, )
_ignore_model_permissions = True
serializer_class = UserSerializer
queryset = User.objects.exclude(pk=-1)
Now only owner can see their details
I have a basic django rest service, which
registers a person and
updates his password.
I want to add jwt authentication on top of it. If I follow the tutorial I would need to add a new url named "api-token-auth" in project's urls.py. But, I don't want to add this new url and want my register call to send a token in response.
Here's my code:
serializers.py
class UserSerializer(serializers.HyperlinkedModelSerializer):
def create(self, validated_data):
user = User(
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, instance, validated_data):
instance.set_password(validated_data['password'])
instance.save()
return instance
class Meta:
model = User
fields = ('url', 'username', 'password')
lookup_field = 'username'
write_only_fields = ('password',)
views.py
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.exclude(is_superuser=1)
serializer_class = UserSerializer
lookup_field = 'username'
What should be done to achieve this ? should I call api-auth-token inside my serializer's create method ?
How does django-rest-framework-jwt handles multiple authentication tokens and correctly identifies which token belongs to which user ? Especially when it doesn't store tokens in a db.
How can I use this authentication mechanism to limit my user to view/update/delete only his user ?
How can I use this authentication mechanism to do anything in general. For instance if a user wants to write his name to /tmp/abcd.txt. How can I make sure that only authenticated users would be able to do so ?
Are there any potential loopholes in this approach. Should I use the same code if my app is going to store lots of classified data ?
Question 1: To generate tokens manually on registration you can define and make use of a method like this:
import jwt
from rest_framework_jwt.utils import jwt_payload_handler
def create_token(user):
payload = jwt_payload_handler(user)
token = jwt.encode(payload, settings.SECRET_KEY)
return token.decode('unicode_escape')
you can add this method to the view and generate the token once the user has been registered and return it in the response.
Question 2: JWT tokens do not need to be stored in the database. You can read more about how JWT works at http://jwt.io/.
Question 3 and 4: To use tokens to limit access to a specific view, especially an APIView or one of its subclasses or a view provided by Django Rest framework, you need to specify the permission classes. for example:
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class ExampleView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
Question 5: One potential loophole while working with Django Rest Framework is the default permissions that you setup from the settings of your application; if for example you AllowAny in the settings it'll make all the views publicly accessible unless you specifically override the permission classes in each view.
The Accepted answer has some code that generates token but it doesn't show how to integrate it in serializer/view. Also not sure that manual jwt.encode is a good modern way of doing this if we already have jwt_encode_handler
to do this. You can create SerializerMethodField and create tokens there:
token = serializers.SerializerMethodField()
def get_token(self, obj):
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(obj)
token = jwt_encode_handler(payload)
return token
Then add token field to Meta.fields.
Working Example
I am trying to create a simple service which allows anonymous users to submit their name and email. I want to AllowAny on adding their info, and IsAuthenticated on everything else. I'm having trouble getting this granularity.
models.py
from django.db import models
class Invitee(models.Model):
name = models.CharField(max_length=255)
email = models.EmailField(max_length=70,blank=True)
modified = models.DateTimeField(auto_now=True)
serializers.py
class InviteeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Invitee
fields = ('name', 'email')
def create(self, validated_data):
return Invitee(**validated_data)
views.py
class InviteeViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Invitee.objects.all()
serializer_class = InviteeSerializer
What and where should I put to make it so users can submit their name and email, but only admins can read, update, delete? Thanks any help.
The easiest and safest way to do this is with multiple serializers, one of each user class you need. You will also need to use custom permissions to enforce the read/write difference between authenticated and anonymous users.
class InviteeSerializer(serializers.HyperlinkedModelSerializer):
def create(self, validated_data):
return Invitee(**validated_data)
class LimitedInviteeSerializer(InviteeSerializer):
class Meta:
model = Invitee
fields = ('name', 'email', ) # a limited subset of the fields
class FullInviteeSerializer(InviteeSerializer):
class Meta:
model = Invitee
fields = ('name', 'email', "modified", ) # all of the fields
While right now it only looks like you need read/write, if you need full read/write/delete permissions I would recommend reading this Stack Overflow question.
You will also need to control what serializer is being used on the view level. This needs to be done using a combination of permissions and overridden view methods.
class InviteeViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Invitee.objects.all()
serializer_class = LimitedInviteeSerializer
def get_serializer_class(self):
if self.request.user.is_active:
return FullInviteeSerializer
return super(InviteeViewSet, self).get_serializer_class()
On the view you will need to override get_serializer_class to determine what serializer should be used based on if the user is active. Anonymous users should never be marked as active, so this is the best way to check while excluding deactivated accounts.
You will also need to create a custom permissions class that will do the opposite of the built-in IsAuthenticatedOrReadOnly permission. You are looking for authenticated users to do everything, and anonymous users to only be write-only. I've called this class IsAuthenticatedOrWriteOnly to match the other permission class.
class IsAuthenticatedOrWriteOnly(BasePermission):
"""
The request is authenticated as a user, or is a write-only request.
"""
def has_permission(self, request, view):
WRITE_METHODS = ["POST", ]
return (
request.method in WRITE_METHODS or
request.user and
request.user.is_authenticated()
)
You just need to add this your existing list of permission classes, or override it manually it on the view using permission_classes.