Proposal: Password Validity Layer

274 views
Skip to first unread message

Keith Hackbarth

unread,
Aug 5, 2014, 1:09:02 PM8/5/14
to django-d...@googlegroups.com
First of all, apologies in advance if this is not the right place for this or if this topic has already been brought up. Long time listener, first time caller.

I would like to propose having some sort of password validation layer that can be activated every time a user's password is created or changed.


Here's the core of my problem:

I've worked on a few different Django-based applications. Where possible, we've tried to leverage the contrib.auth module when it comes to user management. Eventually, we will fall under some sort of compliance (SOX, PCI, HIPAA, etc.) and need to enact the security best practices. These always include enforcing password length, complexity, etc..

My problem is there ends up being a bunch of places were the password can be changed: our website via emailed password reset, our website via password change form, the admin console, our REST api for mobile, etc.. I end up needing to create a bunch of custom overrides forms and functions. And make sure our other team members know to do the same.

I've come up with a few solutions that I'd love to share them with the community. However, the level that they are implemented at make them difficult to just include in Django as a separate third-party module / application.

Anyway, looking through various forums, I see that I'm not the first person to have this problem. I was wondering what people thought about having a configurable password validation function that gets called within auth every time a password is changed?

In settings.py it could look like this:

AUTH_PASSWORD_VALIDATION = 'account_mgnt.validators.password'

by default it would be

AUTH_PASSWORD_VALIDATION = None





Tim Graham

unread,
Aug 5, 2014, 1:24:30 PM8/5/14
to django-d...@googlegroups.com
In fact, there is an accepted ticket: https://code.djangoproject.com/ticket/16860

It may be better to try out the "AppConfig setting" route as discussed in https://groups.google.com/d/topic/django-developers/qnnCLppwA3o/discussion rather than adding a new top level setting.

Perhaps you can try to address some of the questions posed in that ticket as well.

Collin Anderson

unread,
Aug 5, 2014, 2:45:05 PM8/5/14
to django-d...@googlegroups.com
Could we handle it with model validation on a custom user model? Maybe add a validate_password() method on the user model?

Florian Apolloner

unread,
Aug 5, 2014, 5:33:50 PM8/5/14
to django-d...@googlegroups.com
On Tuesday, August 5, 2014 8:45:05 PM UTC+2, Collin Anderson wrote:
Could we handle it with model validation on a custom user model? Maybe add a validate_password() method on the user model?

I don't think so, using a custom user just for password validation is way to much work given that you'd also have to rewrite a few forms etc (at least that was my impression last time I looked at custom users, they do work, but they are not without work so to say)…

Keith Hackbarth

unread,
Aug 7, 2014, 5:51:30 PM8/7/14
to django-d...@googlegroups.com
I actually think Colin's approach seems the best. Have a validate_password function that can be overridden by a custom user model.

Tim, if I wanted to move this forward, what would be the next steps? I looked at the trac ticket you mentioned and it looks much more in-depth (full javascript / front-end integrtion). I also didn't see any questions on the ticket. Should I respond to that ticket or create one of my own?

Tim Graham

unread,
Aug 7, 2014, 6:23:52 PM8/7/14
to django-d...@googlegroups.com
The custom user idea did seem like a good one to me. I don't think you'd have to rewrite much (anything?) if the only change in your custom user is to add a validate_password() function. If you'd like code up a proof of concept we can take a look. I don't think front-end integration  is necessary at this point. The "questions" I was referring to are the "Problems to be solved" on the ticket. Many of them seem out of scope for getting a v1 working, but are things to consider as you do the implementation.

charettes

unread,
Aug 7, 2014, 9:04:40 PM8/7/14
to django-d...@googlegroups.com
We could override `AbstractBaseUser.clean_fields` to call an empty `AbstractBaseUser.clean_password` (given the password field is not excluded from validation) and gracefully handle the possible `ValidationError`:

class AbstractBaseUser(models.Model):
    ....
    def clean_password(self):
        pass

    def clean_fields(self, exclude=None):
        super(AbstractBaseUser, self).clean_fields(exclude=exclude)

        if exclude is None or 'password' not in exclude:
            try:
                self.clean_password()
            except ValidationError as e:
                raise ValidationError({'password': e.error_list})

I guess we should just implement the `clean_<field_name>` paradigm at the model level at this point.

Keith Hackbarth

unread,
Aug 18, 2014, 11:31:05 AM8/18/14
to django-d...@googlegroups.com
Tim, et al.,

I've coded up a proof of concept here. I'd be very interested in your feedback.
https://github.com/keithhackbarth/django/commit/c0897dd4b3d49e39246d1e74d636e32862881451

Thank you,

Keith

Collin Anderson

unread,
Aug 18, 2014, 11:36:49 AM8/18/14
to django-d...@googlegroups.com
Wow. That's simple.

Florian Apolloner

unread,
Aug 18, 2014, 1:05:55 PM8/18/14
to django-d...@googlegroups.com
Validation errors are only caught inside form validation. Forms set the password usually in save, not in clean, so I don't think that patch covers it (or at least the relevant forms have to call validate_password in clean too)

Chris Foresman

unread,
Aug 19, 2014, 5:49:10 PM8/19/14
to django-d...@googlegroups.com


On Monday, August 18, 2014 12:05:55 PM UTC-5, Florian Apolloner wrote:
Validation errors are only caught inside form validation. Forms set the password usually in save, not in clean, so I don't think that patch covers it (or at least the relevant forms have to call validate_password in clean too)

Is there a way to enforce that the validation calls `check_password`? Maybe create a `forms.PasswordField`? 

Chris Foresman

unread,
Aug 19, 2014, 5:53:39 PM8/19/14
to django-d...@googlegroups.com
Or maybe the solution is to define a `check_password` function on your model, and pass that in as a keyword option for the field declaration.

```python
class RegistrationForm(forms.Form):
    password = forms.PasswordField(validator='user.MyUser.check_password')
    ...
```

Chris Foresman

unread,
Aug 19, 2014, 5:57:01 PM8/19/14
to django-d...@googlegroups.com
Or, define the default validator for the field to be 'settings.AUTH_USER_MODEL.validate_password()`. Then it can be overridden if one wants, but by default you would set it on your custom user model. 

Shai Berger

unread,
Aug 24, 2014, 12:43:23 PM8/24/14
to django-d...@googlegroups.com
The problem with these suggestions are that a function is, generally speaking,
not the obvious structure for supplying everything that is required. Consider
the ticket mentioned by Tim above

https://code.djangoproject.com/ticket/16860

You need a validation function, but also:

- Optionally some translatable help-text telling the user the requirements
(this text should be set as the help-text for the field, probably)

- Optionally provide a regular expression to HTML5 browsers and/or Javascript
validators

- (not mentioned) validating the password against other fields such as
username, email, or previous passwords -- these should probably all be
parameterised in some form

While you can, technically, do all this with functions (e.g. providing help-
text as a function property), it seems like a class is more suitable.

HTH,
Shai.
Reply all
Reply to author
Forward
0 new messages