Why isn't my Django User Model's Password Hashed? - python

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

Related

Django : Can I use CreateView to create a User object where User is just Django's built in User model?

I am trying to create a simple user login system where a user gets to sign up on one page and then use those credentials to login to the website on another page. Here's my sign-up and login views:
class SignupView(CreateView):
model = User
form_class = SignupForm
template_name = 'journal_app/signup.html'
success_url = reverse_lazy('home')
class LoginUserView(LoginView):
template_name = 'journal_app/login.html'
As you can see I'm using the CreateView to create User objects. After the user signs up I can see that the record is successfully updated in the Users group in my Admin console. The problem is that when I try to login, it always throws me a username/password don't match error. Any ideas what could be the reason? I am a beginner at Django so it could be something pretty simple.
SignupForm-
class SignupForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name', 'username', 'password']
widgets = {
'password': forms.PasswordInput()
}
The problem is that you need to hash the password. Django stores a hash of the password [Django-doc]. If you make a custom user model, you should normally implement a UserManager [Django-doc] as well. This takes a password, and will hash it, for examply by calling a method .set_password(…) [Django-doc]. This method will then hash the password.
You thus can rewrite the form to save the user with:
class SignupForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name', 'username', 'password']
widgets = {
'password': forms.PasswordInput()
}
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data['password'])
if commit:
user.save()
return user

How to change the data before .create is called in django rest

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

How to Hash Django user password in Django Rest Framework?

I'm trying to create an API for my user registration using Django Rest Framework. I created a serializer by following the step from the api-guide
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(
email=validated_data['email'],
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
However, I keep getting the Invalid password format or unknown hashing algorithm. for my newly created user. I've tried to use make_password from django.contrib.auth.hashers, but I still can't resolve this issue.
Thanks
You can try it in this way
from django.contrib.auth.hashers import make_password
user = User.objects.create(
email=validated_data['email'],
username=validated_data['username'],
password = make_password(validated_data['password'])
)
You can overwrite the perform_create method in CreateAPIView
from rest_framework.generics import CreateAPIView
class SignUpView(CreateAPIView):
serializer_class = SignUpSerializers
def perform_create(self, serializer):
instance = serializer.save()
instance.set_password(instance.password)
instance.save()
You could also use a field validation function for the password field by adding a validate_password method to your serializer and make it return the hash.
from rest_framework.serializers import ModelSerializer
from django.contrib.auth.hashers import make_password
class UserSerializer(ModelSerializer):
class Meta:
model = backend.models.User
fields = ('username', 'email', 'password',)
validate_password = make_password
In the serializer redefine the function create with this:
from django.contrib.auth.hashers import make_password
class UserSerializer(ModelSerializer):
def create(self, validated_data):
validated_data['password'] = make_password(validated_data['password'])
return super(UserSerializer, self).create(validated_data)
And this all! :D
You can do this in the views perform_create method:
from django.contrib.auth.hashers import make_password
def perform_create(self, instance):
current_user = self.request.user
user_exists = CustomUser.objects.filter(
email=self.request.data['email']).first()
if user_exists:
raise MethodNotAllowed
else:
instance.save(is_active=False, is_confirmed=False,
password=make_password(self.request.data['password']))
Another simple solution is to use User.objects.create_user() in your create method like below
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
I know it's an old thread but i got this question today and i like to share my solution here.
You can define your serializer simple and like below:
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
on the other hand in your view you can override perform_create method as below:
class UserView(ViewSets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserCreateSerializer
def perform_create(self , serializer):
new_user =
User.objects.create(username=self.request.data.get("username"))
new_user.set_password(self.request.data.get("password"))
serializer.save(password=user.password)
in this way, you can pass extra information to serializer to save.

Django Rest not creating extended UserProfile

I'm using Django Rest Framework and I've created an extended UserProfile model as follows:
class UserProfile(models.Model):
user = models.OneToOneField(User)
#Some Fields for UserProfile
def user_profile_url(self):
return reverse('user_profile', args=(self.user.id, "{}-{}".format(self.user.first_name, self.user.last_name)))
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
However, on signing up using rest_auth's /registration endpoint: http://django-rest-auth.readthedocs.org/en/latest/api_endpoints.html#registration, the UserProfile is not being created even though the User is created. In my serializers.py, I've done the following for users who sign up
class UserSignUpSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email',)
def create(self, validated_data):
user = User(email=validated_data['email'], username=validated_data['email'])
user.set_password(validated_data['password'])
user.save()
profile = UserProfile(user=user)
profile.save()
return user
Where am I going wrong?
Because request goes here https://github.com/Tivix/django-rest-auth/blob/master/rest_auth/registration/views.py#L38 and doesn't call serializer.create() actually.
Try to override signup form as suggested in the docs:
ACCOUNT_FORMS = {
'signup': 'path.to.custom.SignupForm'
}
example of the profile form:
https://djangosnippets.org/snippets/2081/

Saving custom user model with django-allauth

I have django custom user model MyUser with one extra field:
# models.py
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
age = models.PositiveIntegerField(_("age"))
# settings.py
AUTH_USER_MODEL = "web.MyUser"
I also have according to these instructions custom all-auth Signup form class:
# forms.py
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
age = forms.IntegerField(max_value=100)
class Meta:
model = MyUser
def save(self, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.age = self.cleaned_data['age']
user.save()
# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'
After submitting SignupForm (field for property MyUser.age is rendered corectly), I get this error:
IntegrityError at /accounts/signup/
(1048, "Column 'age' cannot be null")
What is the proper way to store Custom user model?
django-allauth: 0.12.0; django: 1.5.1; Python 2.7.2
Though it is a bit late but in case it helps someone.
You need to create your own Custom AccountAdapter by subclassing DefaultAccountAdapter and setting the
class UserAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
"""
This is called when saving user via allauth registration.
We override this to set additional data on user object.
"""
# Do not persist the user yet so we pass commit=False
# (last argument)
user = super(UserAccountAdapter, self).save_user(request, user, form, commit=False)
user.age = form.cleaned_data.get('age')
user.save()
and you also need to define the following in settings:
ACCOUNT_ADAPTER = 'api.adapter.UserAccountAdapter'
This is also useful, if you have a custom SignupForm to create other models during user registration and you need to make an atomic transaction that would prevent any data from saving to the database unless all of them succeed.
The DefaultAdapter for django-allauth saves the user, so if you have an error in the save method of your custom SignupForm the user would still be persisted to the database.
So for anyone facing this issue, your CustomAdpater would look like this
class UserAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
"""
This is called when saving user via allauth registration.
We override this to set additional data on user object.
"""
# Do not persist the user yet so we pass commit=False
# (last argument)
user = super(UserAccountAdapter, self).save_user(request, user, form, commit=commit)
user.age = form.cleaned_data.get('age')
# user.save() This would be called later in your custom SignupForm
Then you can decorate your custom SignupForm's with #transaction.atomic
#transaction.atomic
def save(self, request, user):
user.save() #save the user object first so you can use it for relationships
...
Side note
With Django 1.5 custom user model, the best practice is to use the get_user_model function:
from django.contrib.auth import get_user_model
# forms.py
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
age = forms.IntegerField(max_value=100)
class Meta:
model = get_user_model() # use this function for swapping user model
def save(self, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.age = self.cleaned_data['age']
user.save()
# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'
Maybe it's not related, but I thought it would be worth noticing.
i think you should define fields property in class Meta in SignupForm and set list of fields that contains age, like this :
class SignupForm(forms.Form):
...
class Meta:
model = MyUser
fields = ['first_name', 'last_name', 'age']
and if it's not worked, look at
this

Categories

Resources