Search code examples
djangodjango-admindjango-authentication

custom password validation in django 1.7


I am trying to add custom password validation to the create user and change password admin forms. I did not see anything in the django docs about how to do this. I found this post on SO: Enforcing password strength requirements with django.contrib.auth.views.password_change, which gives 2 solutions. I tried both, but neither worked for me.

Here's what I have now in my apps admin.py:

def validate_password_strength(value):
    """Validates that a password is as least 10 characters long and has at least
    2 digits and 1 Upper case letter.
    """
    min_length = 10

    if len(value) < min_length:
        raise ValidationError(_('Password must be at least {0} characters '
                                'long.').format(min_length))

    # check for 2 digits
    if sum(c.isdigit() for c in value) < 2:
        raise ValidationError(_('Password must container at least 2 digits.'))

    # check for uppercase letter
    if not any(c.isupper() for c in value):
        raise ValidationError(_('Password must container at least 1 uppercase letter.'))

class MySetPasswordForm(SetPasswordForm):
    def __init__(self, *args, **kwargs):
        super(MySetPasswordForm, self).__init__(*args, **kwargs)
        self.fields['password1'].validators.append(validate_password_strength)

But what I can't figure out is how do I get MySetPasswordForm to be used.

I've tried a few different things. First I did this:

class UserAdmin(UserAdmin):
    form = MySetPasswordForm

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

And I got this error:

<class 'elex_apis.energy.webservice.admin.UserAdmin'>: (admin.E016) The value of 'form' must inherit from 'BaseModelForm'.

So then I changed MySetPasswordForm to inherit from BaseModelForm and then I got this error:

__init__() missing 1 required positional argument: 'user'

So then I added the user param so now MySetPasswordForm looked like this:

class MySetPasswordForm(BaseModelForm):
    def __init__(self, user, *args, **kwargs):
        super(MySetPasswordForm, self).__init__(user, *args, **kwargs)
        self.fields['password1'].validators.append(validate_password_strength)

But I still got the same error as before.

I can't believe it's this hard to add a password validator. It must be a very common need, so clearly I must be missing something simple. Can anyone please provide some assistance here.


Solution

  • The easiest way is to inherit the original UserAdmin and just override the change_password_form.

    Example:

    from django.contrib.auth import models as auth_models
    from django.contrib.auth import admin as auth_admin
    from django.contrib.auth import forms as auth_forms
    from django.core.exceptions import ValidationError
    
    
    def validate_password_strength(value):
        """Validates that a password is as least 10 characters long and has at least
        2 digits and 1 Upper case letter.
        """
        min_length = 10
    
        if len(value) < min_length:
            raise ValidationError(_('Password must be at least {0} characters '
                                    'long.').format(min_length))
    
        # check for 2 digits
        if sum(c.isdigit() for c in value) < 2:
            raise ValidationError(_('Password must container at least 2 digits.'))
    
        # check for uppercase letter
        if not any(c.isupper() for c in value):
            raise ValidationError(_('Password must container at least 1 uppercase letter.'))
    
        return value
    
    
    class AdminPasswordChangeForm(auth_forms.AdminPasswordChangeForm):
        def clean_password1(self):
            return validate_password_strength(self.cleaned_data['password1'])
    
    
    class UserCreationForm(auth_forms.UserCreationForm):
        def clean_password1(self):
            return validate_password_strength(self.cleaned_data['password1'])
    
    
    class UserAdmin(auth_admin.UserAdmin):
        change_password_form = AdminPasswordChangeForm
        add_form = UserCreationForm
    
    
    # Re-register UserAdmin
    admin.site.unregister(auth_models.User)
    admin.site.register(auth_models.User, UserAdmin)