Integrate django password validation with django rest framework validate_password

I am trying to integrate django validators 1.9 with django rest framework serializers. But the serialized "user" (django rest framework) is incompatible with django validators.

Here are the .py serializers

import django.contrib.auth.password_validation as validators from rest_framework import serializers class RegisterUserSerializer(serializers.ModelSerializer): password = serializers.CharField(style={'input_type': 'password'}, write_only=True) class Meta: model = User fields = ('id', 'username', 'email, 'password') def validate_password(self, data): validators.validate_password(password=data, user=User) return data def create(self, validated_data): user = User.objects.create_user(**validated_data) user.is_active = False user.save() return user 

I managed to get MinimumLengthValidator and NumericPasswordValidator correctly, because both function checks do not use "user" when checking. Source code here

Excerpt from django source code:

 def validate(self, password, user=None): if password.isdigit(): raise ValidationError( _("This password is entirely numeric."), code='password_entirely_numeric', ) 

For other validators, such as UserAttributeSimilarityValidator, the function uses another argument "user" when checking ("user" is the django user model, if I'm not mistaken)

Excerpt from django source code:

  def validate(self, password, user=None): if not user: return for attribute_name in self.user_attributes: value = getattr(user, attribute_name, None) 

How to change serialized user to what django validators (UserAttributeSimilarityValidator) can see

Excerpt from django source code:

 def validate(self, password, user=None): if not user: return for attribute_name in self.user_attributes: value = getattr(user, attribute_name, None) if not value or not isinstance(value, string_types): continue 

Edit

The Django Rest Framework can get all the built-in Django password checks (but this is how to hack). Here's the problem:

ValidationError value looks like this

[ValidationError (['This password is too short, it must be less than 8 characters.']), ValidationError (['This password is fully Numeric.])]

Validation does not contain a field. Django rest structure treats it as

 { "non_field_errors": [ "This password is too short. It must contain at least 8 characters.", "This password is entirely numeric." ] } 

How can I enter a field in raise ValidationError

+5
source share
4 answers

As you mentioned, when you test the password in validate_password method with the UserAttributeSimilarityValidator validator, you do not have a user object.

What I propose, instead of checking at the field level, is to check the object level by implementing the validate method in the serializer

 import sys from django.core import exceptions import django.contrib.auth.password_validation as validators class RegisterUserSerializer(serializers.ModelSerializer): # rest of the code def validate(self, data): # here data has all the fields which have validated values # so we can create a User instance out of it user = User(**data) # get the password from the data password = data.get('password') errors = dict() try: # validate the password and catch the exception validators.validate_password(password=password, user=User) # the exception raised here is different than serializers.ValidationError except exceptions.ValidationError as e: errors['password'] = list(e.messages) if errors: raise serializers.ValidationError(errors) return super(RegisterUserSerializer, self).validate(data) 
+9
source

You can access the user object through the self.instance of the serializer object even when performing field-level validation. Something like this should work:

  from django.contrib.auth import password_validation def validate_password(self, value): password_validation.validate_password(value, self.instance) return value 
+6
source

Use serializers! You have a validate_fieldname method!

 class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ( 'id', 'username', 'password', 'first_name', 'last_name', 'email' ) extra_kwargs = { 'password': {'write_only': True}, 'username': {'read_only': True} } def validate_password(self, value): try: validate_password(value) except ValidationError as exc: raise serializers.ValidationError(str(exc)) return value def create(self, validated_data): validated_data = self.check_for_unique_email(validated_data) validated_data.setdefault('username', validated_data['email']) user = super().create(validated_data) user.set_password(validated_data['password']) user.is_active = False user.save() return user def update(self, instance, validated_data): validated_data = self.check_for_unique_email(validated_data) user = super().update(instance, validated_data) if 'password' in validated_data: user.set_password(validated_data['password']) user.save() return user 
+1
source

When creating a new user (registration), then self.instance will not, it will work when you rest your password, change the password or update the user data with a password. But if you want to verify that the password should not be similar to your email address or username, then you need to include "SequenceMatcher" in your check

 data = self.get_initial() username = data.get("username") email = data.get("email") password = data.get("password") max_similarity = 0.7 if SequenceMatcher(a=password.lower(), b=username.lower()).quick_ratio() > max_similarity: raise serializers.ValidationError("The password is too similar to the username.") if SequenceMatcher(a=password.lower(), b=email.lower()).quick_ratio() > max_similarity: raise serializers.ValidationError("The password is too similar to the email.") 
0
source

Source: https://habr.com/ru/post/1246437/


All Articles