Improving consistency between field validators and password validators

Hello, I am Antoliny :smiley:. I am currently working on the issue related to
ticket #35693.
PR.

This issue highlights the consistency problem between password validators and general field validators.
However, I think that while the role of general validators and password validators is the same, they are different types due to the difference in how they are called and the fact that password validators have an additional user parameter.
Iā€™m not sure if itā€™s correct to modify the password validators to maintain consistency in this regard. Even if we were to add the __call__ and __eq__ magic methods to ensure consistency, Iā€™m not sure they would be useful.
Iā€™m curious to know what Django community think about this.
How about using the term ā€˜password validatorā€™ instead of ā€˜validatorā€™ to clarify the difference between password validators and general validators in the password validation documentation?

I think youā€™re correct that theyā€™re different types, that serve different purposes. And also that the names should reflect that. What are the current names, and what namespaces are they in, and what might you suggest changing them to? Often I find that Iā€™m ok with a less specific name as long as its in an appropriate namespace so that it isnā€™t ambiguous in context.

1 Like

If we conclude that it is clear that field validators and password validators are different things and we donā€™t expect them to have the same behavior - we can also wontfix the ticket :+1:

1 Like

Hereā€™s a couple links from django-developers when password validators were being worked on:

And the ticket: #16860 (Provide hooks for password policy) ā€“ Django


I havenā€™t been able to track down why they didnā€™t just make it __call__ in the first place, with user being an optional argument. If we can track that down, I think it would really help us to understand what to do. Itā€™s possible that there was no particular reason, perhaps it pre-dated inclination to override the __call__ method generally.

One thing Iā€™ve not got my head around is why these password validators are expected to pass silently if the user isnā€™t given when they need a user to do their validation. Iā€™m not sure whether or not thatā€™s the most appropriate behavior for general field validators, as I tend to prefer raising loud errors when I use a function in the wrong place. If that intuition is right, then it might mean that the __call__ method is appropriate, but perhaps needs slightly different behavior than validate!

Based on what Iā€™ve figured out so far, Iā€™m mildly in favor of adding the __call__ and __eq__ methods to make these usable as general validators, but using a password validator as a validator isnā€™t difficult:

CharField(..., validators=[MinimumLengthValidator().validate])

Thank you for the great opinion. @ryanhiebert :smiley:
I was also curious as to why the __call__ method wasnā€™t used initially to implement the Field Validator approach, but I think I found some of the reasons through the links in ryanhiebertā€™s comment.

Password validation in Django revisited discussion

According to Erik Romihn, the reason for not using standard Django validators for the password validator was because they didnā€™t provide sufficient flexibility and configurability.

In my opinion, since the password validator is closely related to the user and login, there are cases where it needs to validate user attributes, such as with the UserAttributeSimilarityValidator. This might be one of the reasons why the current password validator was designed this way.

I agree with the point that using the password validator as a validator is not difficult in its current form. However, if methods like __call__ were added, it would allow access in the same way as standard validators, making it easier for users to approach with a more consistent usage.

1 Like

That explains why they needed classes instead of just functions, but not why they didnā€™t use the __call__ method to implement it. I suspect that they probably just didnā€™t think about doing that.


Given my concerns about password validators that require a user to process not raising errors (by design), my suggestion is to add a __call__ method only to the password validators that do not need the user to be passed in. Iā€™d think that there shouldnā€™t be any optional arguments to the __call__ method when implemented to conform to the normal protocol for validators.

1 Like

I agree with this opinion as well. :grinning:

I think all password validators, except for UserAttributeSimilarityValidator, can be used as general purpose validators.
In particular, MinimumLengthValidator is functionally identical to MinLengthValidator and MaxLengthValidator in the standard validators (itā€™s closer to a customized version of MinLengthValidator for passwords).
Also, I believe that NumericPasswordValidator can be effectively used in scenarios beyond just password validation.

I consider my skills to be lacking and think Djangoā€™s password validation logic is excellently and beautifully designed. However, I struggle to understand the decision to design password validation using AUTH_PASSWORD_VALIDATORS in settings.py. I believe this could have been sufficiently handled using general Field and Validator functionality.
Of course, compared to the developers of Django, my perspective might seem like that of a beginner, which could explain my impression.

In the ticket, the description was:

By default validating fields use this syntax validator(value) but password validators donā€™t implement .__call__(). Therefore making them unusable outside of validate_password().

As @ryanhiebert pointed out, these are not ā€œunusable outside validate_password()ā€ as they can be used with CharField(..., validators=[MinimumLengthValidator().validate]).
Those that need a user will pass silently (adding a __call__ doesnā€™t change that).

So I think the problem this wanted to solve ā€œmake them usable outside validate_password()ā€ is not a valid problem.


That doesnā€™t mean we couldnā€™t change the design of these.
As @Antoliny0919 mentioned, thereā€™s some potential refactoring we can do with MinimumLengthValidator being functionally MinLengthValidator.

So in short, I think the original ticket (add __call__ to password validators so they are usable outside the validate_password context) is a wontfix and once we have an agreement on ā€œwhere do we want to end upā€ with validators and password validators - then we can make steps in that direction :+1:

2 Likes

Thank you for the ticket @Antoliny0919 :star:
https://code.djangoproject.com/ticket/36015

I think it has a much better direction and states the current design quirks

I donā€™t think weā€™re ready to work on this until is has been discussed more.

The main question mark I had from the ticket is what this means for the setting AUTH_PASSWORD_VALIDATORS (which has no split between ones that need a user and do not need a user).
Bare in mind folks can have written their own password validators which would need migrating to a new format.

I think:

  • what would be the end state
  • how could folks customize it
  • what would be the migration path

This has been around soooo long, and almost certainly will include breaking changes. So I am even tempted for this to consider a DEP or certainly needs more input than what we have right now.
If we mess up password validation during the migration process, that would not be good, so we need to tread carefully and really consider whether the end state is worth the migration.