Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django User model: what is the purpose of password changes in the save() method

Sometimes i want to set the password programmatically. I do the following:

https://docs.djangoproject.com/en/2.2/topics/auth/default/#changing-passwords
You can also change a password programmatically, using set_password():

from django.contrib.auth.models import User
u = User.objects.get(username='john')
u.set_password('new pass')
u.save()

I wanted to see how things are implemented in save() when we run u.save()

Basically I am trying to understand the save() method used in AbstractBaseUser

# ./.venv/lib/python3.7/site-packages/django/contrib/auth/base_user.py
class AbstractBaseUser(models.Model):
    password = models.CharField(_('password'), max_length=128)

    ........

    # Stores the raw password if set_password() is called so that it can
    # be passed to password_changed() after the model is saved.
    _password = None


    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if self._password is not None:
            password_validation.password_changed(self._password, self)
            self._password = None

    ........

    def set_password(self, raw_password):
        self.password = make_password(raw_password)
        self._password = raw_password

Here in def save(self, *args, **kwargs):

super().save(*args, **kwargs)

will save the password set by set_password before the next lines password_validation.password_changed which validate the password

So what is the point in saving the password first and then validating afterwards, because it's already gone into the database.

like image 334
Santhosh Avatar asked Oct 30 '25 18:10

Santhosh


1 Answers

password_validation.password_changed does not do what you think it does.

The actual password validation is done before the User object is saved, by calling the django.contrib.auth.password_validation.validate_password method. This method calls every validator in your settings AUTH_PASSWORD_VALIDATORS, and raises an exception when any of these validators fail. This can be any kind of validation, like whether the new password is complex enough, or whether it is different enough from the username.

According to the docs:

By default, validators are used in the forms to reset or change passwords and in the createsuperuser and changepassword management commands.

Validators aren’t applied at the model level, for example in User.objects.create_user() and create_superuser(), because we assume that developers, not users, interact with Django at that level and also because model validation doesn’t automatically run as part of creating models.

So, the User save model doesn't do any validation on the User's password, the Forms and commands do. If you manually set the password in the code somewhere, make sure you do that validation yourself.

So what does password_validation.password_changed(self._password, self) do?

Simply said, it informs all active validators that the user has changed their password, along with the actual raw new password. Why is this necessary? Suppose I write a validator that checks whether a user tries to change their password to a password they have used before. This validator will need to know which passwords the user has had in the past, so I'll need to save a list of (hashed) original passwords per user.

password_changed is basically a way for Django to say to all password validators: "Hey, take note, the user's password has been changed to this value, so if you need to keep track of for some reason, do it now, because after this, I'll destroy the unhashed version of this password"

like image 106
Nico Griffioen Avatar answered Nov 01 '25 08:11

Nico Griffioen