I need to provide the raw password of an allauth user to a third party provider when he resets his password. So everytime when the password gets resetted I call the #receiver(password_reset). However, then the password was already salted. I need to get the raw password data to realise the password change also at an external service. How would get the new "raw" password, which wasn't already salted or how could I desalt it?
from allauth.account.signals import password_reset
from django.dispatch import receiver
#receiver(password_reset)
def password_change_callback(sender, request, user, **kwargs):
#run third party api call containing the new password
If what you want is to get the new password a user inputed while changing his/her password. What you can do is that on the post request you store the value of the new password the user inputed in the form in a variable. This way after you have reseted the password you can still access the raw password since you saved it in a variable before resetting. So something like the below:
new_password = request.POST.get("new_password")
# your code to set the new password goes here
and after that you can still access the new_password variable and do whatever you want with it.
class ChangeUserPasswordView(UpdateAPIView):
queryset = User.objects.filter(is_active=True)
permission_classes = (IsAuthenticated,)
serializer_class = ChangePasswordSerializer
def get_object(self, *args, **kwargs):
return self.request.user
Then for the serializer
from django.contrib.auth.password_validation import validate_password
class ChangePasswordSerializer(serializers.ModelSerializer):
new_password = serializers.CharField(
write_only=True, required=True, validators=[validate_password]
)
confirm_password = serializers.CharField(write_only=True, required=True)
old_password = serializers.CharField(write_only=True, required=True)
class Meta:
model = User
fields = ("old_password", "new_password", "confirm_password")
def validate_old_password(self, value):
user = self.context["request"].user
if not user.check_password(value):
raise serializers.ValidationError(
{"old_password": "Old password is not correct"}
)
return value
def validate(self, attrs):
if attrs["new_password"] != attrs["confirm_password"]:
raise serializers.ValidationError(
{"password": "Password fields didn't match."}
)
return attrs
def update(self, instance, validated_data):
instance.set_password(validated_data["new_password"])
instance.save()
return instance
In your user model add function
from django.contrib.auth.hashers import check_password
def check_password(self, raw_password, *args, **kwargs):
return check_password(raw_password, self.password)
If you want a reset password, where you don’t want to add the old password you can replace your serilalizer code with the below.
class ChangePasswordSerializer(serializers.ModelSerializer):
new_password = serializers.CharField(
write_only=True, required=True, validators=[validate_password]
)
confirm_password = serializers.CharField(write_only=True, required=True)
class Meta:
model = User
fields = ("new_password", "confirm_password")
def validate(self, attrs):
if attrs["new_password"] != attrs["confirm_password"]:
raise serializers.ValidationError(
{"password": "Password fields didn't match."}
)
return attrs
def update(self, instance, validated_data):
instance.set_password(validated_data["new_password"])
instance.save()
return instance
Here in the serializer validate function you have access to the user's inputed password using
attrs["new_password"]
Related
I am very new to Django Rest Framework and am confused about coding the following task: when a user on the front-end enters an email address, without any other user information, the API should determine whether or not the email already exists. Likewise, when the user enters a username, without any other user information, the API should determine whether or not the username already exists. This is so the user has feedback on whether their email or username is valid before proceeding to the next sign up stage.
I am aware of the existence of validators, but I don't understand how to use them on partial data.
Here is my serializers.py class
class CustomUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
username = serializers.CharField(min_length=2)
password = serializers.CharField(min_length=8, write_only=True)
first_name = serializers.CharField(min_length=2)
last_name = serializers.CharField(min_length=2)
class Meta:
model = CustomUser
fields = ('pk', 'email', 'username', 'password', 'created_at', 'first_name', 'last_name')
extra_kwargs = {'password': {'write_only': True}}
def validate_email(self, value):
if CustomUser.objects.filter(email=value).exists():
raise serializers.ValidationError("An account already exists with that email address.")
return value
def validate_username(self, value):
if CustomUser.objects.filter(username=value).exists():
raise serializers.ValidationError("An account aready exists with that username.")
return value
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
And here is my views.py class
class CustomUserCreate(APIView):
permission_classes = (permissions.AllowAny,)
authentication_classes = ()
def post(self, request, format='json'):
"""
Receives an HTTP request. Serializes and saves a user object.
"""
serializer = CustomUserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
json = serializer.data
return Response(json, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class CustomUserGet(APIView):
def get_user(self, username):
try:
return CustomUser.objects.get(username=username)
except CustomUser.DoesNotExist:
raise Http404
def get(self, request, format='json'):
user = self.get_user(request.user.username)
serializer = CustomUserSerializer(user)
return Response(serializer.data)
class LogoutAndBlacklistRefreshTokenForUserView(APIView):
permission_classes = (permissions.AllowAny,)
def post(self, request):
try:
refresh_token = request.data['refresh_token']
token = RefreshToken(refresh_token)
token.blacklist()
return Response(status=status.HTTP_205_RESET_CONTENT)
except:
return Response(status=status.HTTP_400_BAD_REQUEST)
CustomUser is simply an implementation of AbstractUser.
Again, this successfully validates when a user is created with complete data. However, I want to validate on partial data, such as a lone email address and lone username without the presence of other attributes such as first_name, last_name, etc. What is the proper way of approaching this?
I have 2 models, User and UserProfile. UserProfile model has OneToOneField with User model. Here I am trying to update both the models in a single request.
Request Payload:
{'email': ['xxx#gmail.com'], 'first_name': ['Nalin'], 'last_name': ['Dobhal'],}
I have created serializer for both models.
serializers.py
class UserAccountSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False, read_only=True)
mobile = serializers.IntegerField(read_only=True)
email = serializers.EmailField(required=False, read_only=False)
username = serializers.CharField(read_only=True)
class Meta:
model = User
fields = ("id", "mobile", 'email', "username",)
class UserProfileSerializer(serializers.ModelSerializer):
user = UserAccountSerializer(required=False, read_only=False)
# other fields
class Meta:
model = UserProfile
fields = ("user", # other fields)
def update(self, instance, validated_data):
# validated data doesn't have email here, that's why getting value from self.initial_data
if self.initial_data.get("email"):
instance.user.email = self.initial_data.get("email")
instance.user.save()
instance.save()
return instance
views.py
class UserAccountSettingsAPI(generics.RetrieveUpdateAPIView):
http_method_names = ["options", "get", "put", "patch"]
permission_classes = (IsAuthenticated,)
authentication_classes = (TokenAuthentication,)
serializer_class = UserProfileSerializer
def get(self, request, *args, **kwargs):
# some processing
def update(self, request, *args, **kwargs):
profile = UserProfile.objects.select_related("user").get(user_id=request.user.id)
serializer = self.get_serializer(profile, data=request.data)
if serializer.is_valid(raise_exception=False):
serializer.save()
# some other processing only to set key value for context.
return Response(context)
I would like to perform some validation before updating user's email. So my question is where to perform that validation? And is there any better way of doing this? I tried to add def validate_email(self, email): in UserAccountSerializer but it is not getting executed. So I want to make sure that email does not belong to another user and if email exists, I would like to send some custom error message to client.
I have removed unnecessary code.
Try adding this code
create or update in views
if User.objects.filter(email=self.request.data['email']).exists():
return Response({"error": "This email id already exists."})
You could try raising a ValidationError which is part of the serializers library:
from rest_framework import serializers
def update(self, instance, validated_data):
# If `self.request.data["email"]` works
if User.objects.filter(email=self.request.data["email"]).exists():
raise serializers.ValidationError("This email already exists.")
# If `self.request.data["email"]` doesn't work
new_email = self.initial_data.get("email"):
if new_email and User.objects.filter(email=new_email).exists():
raise serializers.ValidationError("This email already exists.")
# Save and return the modified instanced
or if you unique=True to the email field, it will raise an IntegrityError so you don't need to do any further checks. You simply catch the error and handle it yourself.
I assume you want email to be unique. Then you should add unique=True to your user model's email field.
class YourUserModel(AbstractUser):
email = models.EmailField(unique=True)
After you make email a unique field, your database will not allow to add another entry with the same email and it will raise IntegrityError. You can catch this error and return a better error message to your user. Like this:
try:
if serializer.is_valid(raise_exception=False):
serializer.save()
except IntegrityError:
return Response(data={'message':'that email is in use'}, status=HTTP_400_BAD_REQUEST)
I have this following model
class User(models.Model):
UserName = models.CharField(max_length=20)
Password = models.CharField(max_length=255)
RoleName = models.CharField(max_length=30)
Email = models.EmailField(max_length=50)
ApartmentName = models.CharField(max_length=50)
UserId = models.BigAutoField(primary_key=True)
I have saved the data by calling this view
class Register(generics.CreateAPIView):
serializer_class = serializers.UserSerializer
def get_queryset(self, *args, **kwargs):
return models.User.objects.all()
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
But before the row to be created in the database table i need to change the password to the hashed form, i cant alter the post variables in the request since it is immutable.How to hash the password with make_password before saving the data?
You can do it in serializer class
class UserSerializer(ModelSerializer):
class Meta:
model=User
fields = ('Username', 'Password', #others)
def create(self, validated_data):
user = User()
user.Username = validated_data['Username']
user.Password = make_password(validated_data['Password'])
# other
'make_password' could be any function that you want
Then in view just save the serializer
If the User objects are created only from Register view, override the create method of UserSerializer works fine. However, users created through others ways (admin interface, django form, management command, etc.) will not have their passwords encrypted unless you provide some code to do so in all of theese ways.
To hash the password before save any user, a better aproach is create a pre_save signal or override the save method of User to hash the password (serializer and view will not change)
class User(models.Model):
...
def save(self, **kwargs):
self.password = make_password(self.password)
return super(User, self).save(**kwargs)
Make sure the password does not exist or has been changed before call make_password to not encode an already encoded password
I'm doing a login app for my Django project using Django Rest Framework.
At registration, in my serializer, i'm checking if user already exist by doing this:
def create(self, validated_data):
"""
Create an user but validate datafirst(checking if exist,
if mail is right,not already in-use, and hash password).
"""
queryset = PictureUser.objects.filter(username=validated_data['username'])
try:
queryset[0]
raise serializers.ValidationError('The username is already used.')
except IndexError:
print ("User don't exist.")
user = PictureUser.objects.create(
email = validated_data['email'],
username = validated_data['username'],
password = make_password(validated_data['password']),
)
user.save()
return user
This work pretty well. But the HTTP code sent is 400 when the user already exist. How can I change it to 409 or 422 to be more accurate on what went wrong ? (400 error would imply many other errors.)
Must I return an HttpResponse instead?
Thanks.
You can't do that in the serializer.
Maybe you can have something like this:
view.py
class YourView(APIView):
def post(self, request, format=None):
serializer = YourSerializer(data=request.data)
if serializer.is_valid():
return Response({'yourinfo':'something'}, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_409_CONFLICT) # change it to
serializer.py
class YourSerializer(serializers.ModelSerializer):
# Override this if you want. Default Django Auth
username = serializers.CharField()
password = serializers.CharField()
class Meta:
model = User
def validate(self, attrs):
username = attrs['username']
password = attrs['password']
# do your validations here
if everything_ok:
return attrs
else:
raise serializers.ValidationError("User does not belong to API group")
I am using the Django REST Framework (DRF) to create an endpoint with which I can register new users. However, when I hit the creation endpoint with a POST, the new user is saved via a serializer, but the password is saved in cleartext in the database. The code for my serializer is as follows:
from django.contrib.auth import get_user_model
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['password', 'username', 'first_name', 'last_name', 'email']
read_only_fields = ['is_staff', 'is_superuser']
write_only_fields = ['password']
Please note that I am using the default User model from the Django auth package, and that I am very new to working with DRF! Additionally, I have found this question which provides a solution, but this appears to require two database interactions -- I do not believe that this is efficient, but that might be an incorrect assumption on my part.
The issue is DRF will simply set the field values onto the model. Therefore, the password is set on the password field, and saved in the database. But to properly set a password, you need to call the set_password() method, that will do the hashing.
There are several ways to do this, but the best way on rest framework v3 is to override the update() and create() methods on your Serializer.
class UserSerializer(serializers.ModelSerializer):
# <Your other UserSerializer stuff here>
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
def update(self, instance, validated_data):
for attr, value in validated_data.items():
if attr == 'password':
instance.set_password(value)
else:
setattr(instance, attr, value)
instance.save()
return instance
Two things here:
we user self.Meta.model, so if the model is changed on the
serializer, it still works (as long as it has a set_password
method of course).
we iterate on validated_data items and not
the fields, to account for optionally excludeed fields.
Also, this version of create does not save M2M relations. Not needed in your example, but it could be added if required. You would need to pop those from the dict, save the model and set them afterwards.
FWIW, I thereby make all python code in this answer public domain worldwide. It is distributed without any warranty.
This worked for me.
class UserSerializer(serializers.ModelSerializer):
def create(self, *args, **kwargs):
user = super().create(*args, **kwargs)
p = user.password
user.set_password(p)
user.save()
return user
def update(self, *args, **kwargs):
user = super().update(*args, **kwargs)
p = user.password
user.set_password(p)
user.save()
return user
class Meta:
model = get_user_model()
fields = "__all__"
just override the create and update methods of the serializer:
def create(self, validated_data):
user = get_user_model(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, instance, validated_data):
for f in UserSerializer.Meta.fields + UserSerializer.Meta.write_only_fields:
set_attr(instance, f, validated_data[f])
instance.set_password(validated_data['password'])
instance.save()
return instance
Here is an alternative to accepted answer.
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User.objects.create_user(
email=validated_data['email'],
username=validated_data['username'],
password=validated_data['password'],
)
user.save()
return user
create_user function is defined in UserManager and it uses set_password(), we don't need to use it explicitly. I have found many answers and articles which suggest to use set_password but after trying many things I figured the above and it works with CustomUserManager too.
Suppose phone number and password is required to register a user. So our CustomUserManager will look something like this and CreateUserSerializer will handle this too with no changes.
class CustomUserManager(BaseUserManager):
def create_user(self, phone_number, password):
if not phone_number:
raise ValueError('Phone Number must be set')
user = self.model(phone_number=phone_number)
user.set_password(password)
user.save(using=self._db)
return user