Adding the auth token to the UserSerializer - django

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')

Related

Django Rest Framework - Check Password to Validate Form

I'm trying to validate a form through DRF, but it would require the user to enter their password for confirmation. I can't seem to get it to work. Here is my current View and Serializer. Its for a 'change email' form, two fields required, the email and user password. It's for a seperate email model. The serializer:
class UpdateEmailAddressSerializer(serializers.ModelSerializer):
class Meta:
model = EmailAddress
fields = ('email',)
And the APIView:
class UpdateEmailAPI(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = UpdateEmailAddressSerializer
def post(self, request, user, format=None):
user = User.objects.get(username=user)
serializer = UpdateEmailAddressSerializer(data=request.data, instance=user)
if serializer.is_valid():
## logic to check and send email
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I'm not sure where to place the password or what to do with it. Its from the User model itself. When I attempted to add password to the fields in the UpdateEmail serializer it ended up updating the User password with plain text and making that user object unable to use that password.
I just want to check the password of the user for confirmation of this form. Is there an obvious way to do this?
EDIT
When I attempt to bring 'password' into the serializer, an error tells "Field name password is not valid for model EmailAddress." So when I attempt to bring it in e.g.
password = serializers.CharField(required=True)
or try:
## UserPasswordSerializer
class UserPasswordSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'password',
)
## In UpdateEmailAddressSerializer
password = UserPasswordSerializer()
I get this error when submitting the form on DRF:
Got AttributeError when attempting to get a value for field
password on serializer UpdateEmailAddressSerializer. The
serializer field might be named incorrectly and not match any
attribute or key on the EmailAddress instance. Original exception
text was: 'EmailAddress' object has no attribute 'password'
So it seems to be telling me password isn't part of EmailAddress model which is correct. But I cant figure out how to simply check the password alongside the form post without making it part of EmailAddress.
I think you can try like this:
class UpdateEmailAddressSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = EmailAddress
fields = ('email', 'password',)
def create(self, validated_data):
validated_data.pop('password', None)
return super(UpdateEmailAddressSerializer, self).create(validated_data)
def update(self, instance, validated_data):
if instance.check_password(validated_data.get('password')):
instance.email = validated_data.get('email', instance.email)
# else throw validation error
return instance

Django Restframework _ update user model with special custom token?

in DRF i have a some custom action that will do something to user model.user instances are all in state of is_active = False.Im trying to make something that turns the user to is_active = True. i made some a token model that has OneToOne to my user model.the function im trying to make is : if token that user put in the form equals to user.token then set user.is_active = True.im confused how to do that. I made my own serializer class :
class ActivateUserSerializer(serializers.ModelSerializer):
phonenumber = serializers.CharField()
token = serializers.CharField()
class Meta:
model = UserProfile
fields = ['phonenumber','token']
def get_token(self, obj):
request = self.context.get('request')
x = request.data['phonenumber']
obj = UserProfile.objects.get(phonenumber=x)
if request.data['token'] == obj.first_token:
obj.is_active = True
obj.save()
i know this is not .create() .or update() function.so this is how I reach so far.I dont know what view i should use for this functionality.
You could create a new POST endpoint in your API in order to get this custom action, for example:
api/users/<phone number>/activate
Then, in the view class, you can implement the action:
from rest_framework import status, viewsets
from rest_framework.decorators import detail_route
from rest_framework.response import Response
class UserView(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
# Use your own user serializer
serializer_class = UserSerializer
#detail_route(methods=['post', ])
def activate(self, request, phonenumber):
obj = UserProfile.objects.get(phonenumber=phonenumber)
# The POST request expects a token
if not request.data['token']:
return Response({'message': 'Token not provided'},
status=status.HTTP_400_BAD_REQUEST)
# Compare the token
elif request.data['token'] == obj.first_token:
obj.is_active = True
obj.save()
return Response({'message': 'User activated'})
# Maybe you could add an error code if you need
return Response({'message': 'User not activated'})

User registration endpoint in Django Rest Framework

I'm using Django rest framework. I've written the following view to register a new user to the system.
#api_view(['POST'])
#csrf_exempt
#permission_classes((AllowAny, ))
def create_user(request):
email = request.DATA['email']
password = request.DATA['password']
try:
user = User.objects.get(email=email)
false = False
return HttpResponse(json.dumps({
'success': False,
'reason': 'User with this email already exists'
}), content_type='application/json')
except User.DoesNotExist:
user = User(email=email, username=email)
user.set_password(password)
user.save()
profile = UserProfile(user=user)
profile.save()
profile_serialized = UserProfileSerializer(profile)
token = Token(user=user)
token.save()
return HttpResponse(json.dumps({
'success': True,
'key': token.key,
'user_profile': profile_serialized.data
}), content_type='application/json')
Is there a better, slightly more secure way, of creating a user registration api in DRF that doesn't leave the endpoint so open to sql injection?
Forgive me to digress a little, but I can't help but wonder use could have gotten away with far less code, than you have if you had created a serializer and used a class-based view. Besides, if you had just created email as EmailField of serializer it would have automatically guaranteed the validation of email. Since you are using orm interface, risk of sql injection is much less than raw query in my opinion.
Sample Code:-
class UserList(CreateAPIView):
serializer_class = UserSerializer
class UserSerializer(ModelSerializer):
email = serializers.EmailField()
raw_password = serializers.CharField()
Something on these lines, Obviously I couldn't write entire code.
You could validate the email in your serializer using the validate-email-address module like this:
from validate_email_address import validate_email
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
def validate_email(self, value):
if not validate_email(value):
raise serializers.ValidationError("Not a valid email.")
return value
class Meta:
model = User
fields = ('email', 'password')
Also, you might consider a packaged auth/registration solution like Djoser.

Changing serializer fields on the fly

For example I have the following serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
But I don't want to output password on GET (there are other fields in my real example of course). How can I do that without writing other serializer? Change the field list on the fly. Is there any way to do that?
You appear to be looking for a write-only field. So the field will be required on creation, but it won't be displayed back to the user at all (the opposite of a read-only field). Luckily, Django REST Framework now supports write-only fields with the write_only attribute.
In Django REST Framework 3.0, you just need to add the extra argument to the extra_kwargs meta option.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
extra_kwargs = {
'password': {
'write_only': True,
},
}
Because the password should be hashed (you are using Django's user, right?), you are going to need to also hash the password as it is coming in. This should be done on your view, most likely by overriding the perform_create and perform_update methods.
from django.contrib.auth.hashers import make_password
class UserViewSet(viewsets.ViewSet):
def perform_create(self, serializer):
password = make_password(self.request.data['password'])
serializer.save(password=password)
def perform_update(self, serializer):
password = make_password(self.request.data['password'])
serializer.save(password=password)
In Django REST Framework 2.x, you need to completely redefine the password field on the serializer.
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = (
'userid',
'password'
)
In order to hash the password ahead of time in Django REST Framework 2.x, you need to override pre_save.
from django.contrib.auth.hashers import make_password
class UserViewSet(viewsets.ViewSet):
def pre_save(self, obj, created=False):
obj.password = make_password(obj.password)
super(UserViewSet, self).pre_save(obj, created=created)
This will solve the common issue with the other answers, which is that the same serializer that is used for creating/updating the user will also be used to return the updated user object as the response. This means that the password will still be returned in the response, even though you only wanted it to be write-only. The additional problem with this is that the password may or may not be hashed in the response, which is something you really don't want to do.
this should be what you need. I used a function view but you can use class View or ViewSet (override get_serializer_class) if you prefer.
Note that serializer_factory also accept exclude= but, for security reason, I prefer use fields=
serializer_factory create a Serializer class on the fly using an existing Serializer as base (same as django modelform_factory)
==============
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
#api_view(['GET', 'POST'])
def user_list(request):
User = get_user_model()
if request.method == 'GET':
fields=['userid']
elif request.method == 'POST':
fields = None
serializer = serializer_factory(User, UserSerializer, fields=fields)
return Response(serializer.data)
def serializer_factory(model, base=HyperlinkedModelSerializer,
fields=None, exclude=None):
attrs = {'model': model}
if fields is not None:
attrs['fields'] = fields
if exclude is not None:
attrs['exclude'] = exclude
parent = (object,)
if hasattr(base, 'Meta'):
parent = (base.Meta, object)
Meta = type(str('Meta'), parent, attrs)
if model:
class_name = model.__name__ + 'Serializer'
else:
class_name = 'Serializer'
return type(base)(class_name, (base,), {'Meta': Meta, })
Just one more thing to #Kevin Brown's solution.
Since partial update will also execute perform_update, it would be better to add extra code as following.
def perform_update(self, serializer):
if 'password' in self.request.data:
password = make_password(self.request.data['password'])
serializer.save(password=password)
else:
serializer.save()
As far as i can tell from the docs, the fastest way would be to simply have 2 serializers becalled conditionally from your view.
Also, the docs show this other alternative, but it's a little too meta:
http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
it involves creating smart initializer methods, gives an example. i'd just use 2 serializers, if i'd know those changes are the only ones i'll make. otherwise, check the example

Django TastyPie PUT Nested User Model Correctly

I'm trying to go with-the-grain using Django TastyPie to update my models. I have an Identity model, acting as a wrapper around default Django user model:
class Identity(ProfileBase):
user = models.OneToOneField(User, related_name='identity')
avatar = models.ImageField(upload_to=avatar_upload_path, blank=True,
null=True)
I have my UserResource:
class UserResource(ModelResource):
class Meta:
resource_name = 'user'
queryset = User.objects.all()
fields = ['email', 'first_name', 'last_name']
include_resource_uri = False
And I have my IdentityResource:
class IdentityResource(ModelResource):
user = fields.ToOneField(UserResource, 'user', full=True)
class Meta:
resource_name = 'identity'
queryset = Identity.objects.select_related()
fields = ['user', 'avatar']
always_return_data = True
include_resource_uri = False
authentication = OAuthTokenAuthentication()
authorization = Authorization()
I'm currently successfully updating first_name, last_name using the ModelResource obj_update method within IdentityResource:
def obj_update(self, bundle, request, **kwargs):
print 'updating object'
bundle = self.full_hydrate(bundle)
bundle.obj.user = request.user
user = bundle.data['user']
bundle.obj.user.first_name = user['first_name']
bundle.obj.user.last_name = user['last_name']
return super(IdentityResource, self).obj_update(bundle, request, user=request.user)
I want to make a PUT request and optionally update any field on the user or identity models (first_name, last_name on user, or the avatar field on identity). I would rather not have to manually access each field from the bundle data and set them on models manually, as I have done above.
How can I do this naturally in TastyPie? Can someone explain a better approach to solving this problem? Any direction is GREATLY appreciated. :)
Here's my shot at providing an answer that attempts to leverage Tastypie as much as possible.
It is a little more generic than the OP's request (it will update any user, not just the one logged in). In the real world you would probably want to add some sort of authentication/authorization.
from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from django.contrib.auth.models import User
from myapp.account.models import Identity
class IdentityResource(ModelResource):
class Meta:
queryset = Identity.objects.all()
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
allowed_list_methods = ['get']
allowed_detail_methods = ['get','put']
authorization = Authorization()
def dehydrate(self, bundle):
identity_bundle = self.build_identity_bundle(bundle)
identity_bundle = IdentityResource().full_dehydrate(identity_bundle)
return identity_bundle
def obj_update(self, bundle, request, **kwargs):
user_bundle = super(UserResource, self).obj_update(bundle, request, **kwargs)
identity_bundle = self.build_identity_bundle(user_bundle)
IdentityResource().obj_update(identity_bundle, request)
return user_bundle
def build_identity_bundle(self, user_bundle):
identity_bundle = IdentityResource().build_bundle(
obj=user_bundle.obj.get_profile(),
data=user_bundle.data
)
return identity_bundle
What the example supports is:
GET a flattened User+Identity resource
PUT a flattened User+Identity resource, updating both models
You would want to register the UserResource in the API, and probably not the IdentityResource.
You could do something like this.
# Find all properties in user model.
properties = [prop for prop in bunder.obj.user if not prop.startswith('__')]
bundle_user = bundle.data['user']
# Find the property in bundle user and set it back on user if it exists.
for property in properties:
if property in bundle_user:
setattr(bundle.obj.user, property, bundle_user[property])
Maybe I'm missing the point but did you try a PATCH-method request? Tastypie will take all the sent attributes and update them in the database leaving all not-send attributes untouched.