Detect a changed password in Django
Asked Answered
E

3

7

When a user changes their password, I want to send a signal so that I can do some stuff on some models.

How can I create this signal?

I've looked at the post_save signal for User:

post_save.connect(user_updated, sender=User)

However, there doesn't seem to be anything in there for me to check if the password was changed:

def user_updated(sender, **kwargs):
    print(kwargs) # {'created': False, 'raw': False, 'instance': <User: 100002>, 'update_fields': None, 'signal': <django.db.models.signals.ModelSignal object at 0x7ff8862f03c8>, 'using': 'default'}

I also see that there is a password_change_done auth view, but I'm not sure how I'd use it. https://docs.djangoproject.com/en/1.10/topics/auth/default/#built-in-auth-views

Any ideas?

Emblements answered 1/1, 2017 at 4:44 Comment(0)
V
9

You could use a pre_save signal. kwargs['instance'] will contain the updated password and you can get the old password with User.objects.get(id= user.id).password

@receiver(pre_save, sender=User)
def user_updated(sender, **kwargs):
    user = kwargs.get('instance', None)
    if user:
        new_password = user.password
        try:
            old_password = User.objects.get(pk=user.pk).password
        except User.DoesNotExist:
            old_password = None
        if new_password != old_password:
        # do what you need here
Vide answered 1/1, 2017 at 6:41 Comment(3)
Note that this well detect changes in the salt or hashing algorithm as a password change. If you need to detect just the actual password changes, you need to hook into the view and check the new password before it is hashed.Loaning
When could the salt or hash alg change? An upgrade scenario?Emblements
@Emblements newer versions of Django update the password algorithm / rounds used, and when users log in it updates the password to use the newer versionCrux
C
6

If you're using a custom user model you could presumably intersect the set_password() call to set a flag on the instance, and then pick up its presence within a signal:

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db.models.signals import post_save


class User(AbstractBaseUser, PermissionsMixin):
    def set_password(self, password):
        super(User, self).set_password(password)
        self._set_password = True

    @classmethod
    def user_saved(cls, sender, instance, **kwargs):
        if getattr(instance, '_set_password', False):
            # Take action


post_save.connect(User.user_saved, sender=User)
Crux answered 9/3, 2017 at 15:4 Comment(1)
If you don't mind involving implementation details, you can just check instance._password instead. It's always None initially, and becomes the raw password if one is set.Canonist
S
6

I found a solution so you don't have to compare the password hashes, that can be different even if the password is the same.

@receiver(pre_save, sender=get_user_model())
def detect_password_change(sender, instance, **kwargs):
    """
    Checks if the user changed his password
    """
    if instance._password is None:
        return

    try:
        user = get_user_model().objects.get(id=instance.id)
    except get_user_model().DoesNotExist:
        return

    print("password changed")
    # if you get here, the user changed his password
Stepha answered 30/4, 2021 at 17:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.