How to check password against previously used passwords in django - django

I have the following model for storing previously used hashed passwords:
class PasswordHistory(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
password = models.CharField(max_length=128, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now = True)
In the change password form I want check if the new password the user is changing to has not been used the past 5 times.
Here is my form validation:
class ProfileForm(forms.ModelForm):
password1 = forms.CharField(widget=forms.PasswordInput(), required=False)
password2 = forms.CharField(widget=forms.PasswordInput(), required=False)
class Meta:
model = Employee
user_id = None
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id', None)
super(ProfileForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 != password2:
raise forms.ValidationError('Passwords do not match.')
user = User.objects.get(pk=self.user_id)
hashed_password = make_password(password1)
password_histories = PasswordHistory.objects.filter(
user=user,
password_hashed_password
)
if password_histories.exists():
raise forms.ValidationError('That password has already been used')
return password2
The problem is that the passwords are different every time, even when I attempt the same plain text password over and over again. Therefore:
if password_histories.exists():
Never returns true.
How can I compare past passwords if they are always different due to salt?
Thanks

The .set_password function indeed does not return anything, it simply sets the password. Like you say however, the hashing is based on a (random) salt, and thus the hash will be different each time. Therefore you should use the .check_password(…) function [Django-doc], to verify if it somehow matches a hashed variant:
from django.contrib.auth.hashers import check_password
class ProfileForm(forms.ModelForm):
password1 = forms.CharField(widget=forms.PasswordInput(), required=False)
password2 = forms.CharField(widget=forms.PasswordInput(), required=False)
class Meta:
model = Employee
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id', None)
super(ProfileForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 != password2:
raise forms.ValidationError('Passwords do not match.')
user = User.objects.get(pk=self.user_id)
password_histories = PasswordHistory.objects.filter(
user=user
)
for pw in password_histories:
if check_password(password2, pw.password):
raise forms.ValidationError('That password has already been used')
return password2
So if we found a hashed password that matches the given raw password, we can return the password. If by the end of the for loop, we did not find any such password, we can return password2, otherwise we raise an errro.

Related

why does clean_password2() method work but not clean_password1() in Django usercreationform

I am trying to figure out why this works if someone could maybe explain it to me.
I've just created a custom user model (shown below) and for the password validation it uses the clean_password2(self): (shown below) method however when I try to use clean_password1(self): (shown below) the validation does not work. why? Surely using either password1 or password2 to clean the data would work since they are the same?
Django docs state that we can use clean_<fieldname>(): methods to clean/validate data and since password1 is a fieldname in my mind that should work.
Custom user model
class UserManager(BaseUserManager):
def create_user(self, email, password=None):
if not email:
raise ValueError("Users must have an email address")
user = self.model(email=self.normalize_email(email))
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None):
user = self.create_user(email=email, password=password)
user.is_staff = True
user.is_admin = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
email = models.EmailField(verbose_name="Email Address", max_length=255, unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
This works
class UserCreationForm(forms.ModelForm):
password1 = forms.CharField(
label="Password",
help_text=password_validation.password_validators_help_text_html(),
widget=forms.PasswordInput,
)
password2 = forms.CharField(
label="Confirm Password",
help_text="Enter the same password as before for validation",
widget=forms.PasswordInput,
)
class Meta:
model = User
fields = ["email"]
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
This doesn't
def clean_password1(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password1
Summary: don't reference other fields in clean_<fieldname> methods. Do this logic in the clean method instead. https://docs.djangoproject.com/en/stable/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
The why goes deep into Django forms. The method that calls clean_<fieldname> is the following:
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
In _clean_fields, each field is resolved in the order they appear on the form, and this is the first time cleaned_data is filled in for that field. Because password1 comes before password2 in the fields list, when clean_password1 is run, cleaned_data["password2"] has not yet been set. Here is what happens to your code (I added comments):
def clean_password1(self):
password1 = self.cleaned_data.get("password1")
# `"password2"` is not present in cleaned_data, so `password2` is set to None
password2 = self.cleaned_data.get("password2")
# With `password2 == None`, this condition resolves to false
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
# `password1` is returned without any validation error
return password1
The reason clean_password2 works is because password2 comes later in the field list.

self.cleaned_data.get returns none even if field was declared

When i'm in the admin page and trying to create a new instance of MyUser I am unable to create it. When I print out password and confirm_password, I get the proper value for password but None for confirm_password.
In my admins.py file, I have the following:
class MyUserCreationForm(forms.ModelForm):
"""
A form for creating new users.
"""
password = forms.CharField(label='Password', widget=forms.PasswordInput)
confirm_password = forms.CharField(
label='Confirm password', widget=forms.PasswordInput)
class Meta:
models = MyUser
fields = ('email', )
def clean_password(self):
"""
Check if two password entries match.
"""
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if (password == confirm_password):
return password
else:
raise forms.ValidationError("Passwords don't match")
def save(self):
"""
Save the provided password in hash format.
"""
user = super().save(commit=False)
user.set_password(self.cleaned_data['password'])
user.save()
return user
What could be causing as to why confirm_password always have the value None?
You should not do this code in the clean_password.
It makes more sense to do it inside clean_confirm_password
Here is a snipet retrieve from django source code:
def clean_confirm_password(self):
password = self.cleaned_data.get("password")
confirm_password = self.cleaned_data.get("confirm_password")
if password and confirm_password and password != confirm_password:
#you can keep your simply Validation Error here
raise forms.ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
And for the save try:
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
Otherwise your code seems to be good.
So maybe the error is somewhere else.
Here for your information: https://github.com/django/django/blob/master/django/contrib/auth/forms.py#L70

Field validation in a Django form

I have a Django form consisting of a email and name field. I want to validate the name to have more than 8 characters. I have used the following code. But it is not working.
class SignUpForm(forms.ModelForm):
class Meta:
model=SignUp
fields=('email','name')
def emailValidation(self):
name=self.cleaned_data.get('name')
if len(name) <=8:
raise forms.ValidationError("name cannot be less than 8")
models.py
class SignUp(models.Model):
name=models.CharField(max_length=200)
email=models.EmailField()
timestamp=models.DateTimeField(auto_now_add=True, auto_now=False)
updated=models.DateTimeField(auto_now=True,auto_now_add=False)
def __unicode__(self):
return self.name
views.py
def home(request):
form=SignUpForm(request.POST or None)
if form.is_valid():
instance=form.save(commit=False)
instance.save()
print instance.timestamp
return render(request, 'home.html',{'form':form})
In your SignUpForm, in the function emailValidation, you haven't returned 'name'. Also a major mistake is that you have to name the function clean_(field_name) and NOT emailValidation.
This should do it I guess:
class SignUpForm(forms.ModelForm):
class Meta:
model=SignUp
fields=('email','name')
def clean_name(self):
name=self.cleaned_data.get('name')
if len(name) <=8:
raise forms.ValidationError("name cannot be less than 8")
return name
You need to use correct name for your validation method. Django forms will call methods with the format clean_<fieldname>.
Also you seem to be confused about which field you are validating; your email validation method should be called clean_email and should access the email value via form.cleaned_data['email'], and the name one should be called clean_name and access form.cleaned_data['name'].
Something like this may give you some guidance.
class RegistrationForm(forms.ModelForm):
"""
Form for registering a new account.
"""
firstname = forms.CharField(label="First Name")
lastname = forms.CharField(label="Last Name")
phone = forms.CharField(label="Phone")
email = forms.EmailField(label="Email")
password1 = forms.CharField(label="Password")
password2 = forms.CharField(label="Password (again)")
min_password_length = 8
class Meta:
model = User
fields = ['firstname', 'lastname', 'phone', 'email', 'password1', 'password2']
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError(u'Email "%s" is already in use! Please log in or use another email!' % email)
return email
def clean_password1(self):
" Minimum length "
password1 = self.cleaned_data.get('password1', '')
if len(password1) < self.min_password_length:
raise forms.ValidationError("Password must have at least %i characters" % self.min_password_length)
else:
return password1
def clean(self):
"""
Verifies that the values entered into the password fields match
NOTE: Errors here will appear in ``non_field_errors()`` because it applies to more than one field.
"""
cleaned_data = super(RegistrationForm, self).clean()
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("Passwords didn't match. Please try again.")
return self.cleaned_data
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user

How to populate two models using single Form

class CustomAccount(models.Model):
user = models.OneToOneField("auth.User")
role = models.CharField(max_length = 50, default = 'student', choices=APPROVAL_CHOICES)
balance = models.FloatField( default = 0 )
timezone = models.CharField(max_length = 200)
def __str__(self):
return self.user.username +" ["+ self.role + "]"
class CustomAccountForm(forms.ModelForm):
username = forms.CharField(max_length=30 )
email = forms.EmailField(max_length=255 )
password1 = forms.CharField(label= "Password",widget=forms.PasswordInput())
password2 = forms.CharField(label="Password confirmation", widget=forms.PasswordInput , help_text="Enter the same password as above, for verification.")
def save(self, commit= True):
user = User.objects.create_user(username = self.cleaned_data['username'], email = self.cleaned_data['email'] , password = self.cleaned_data['password1'])
user.save()
self.user = user
return super(CustomAccountForm, self).save(commit=commit)
def clean_username(self):
username = self.cleaned_data["username"]
try:
User.objects.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError("A user with that username already exists.")
def clean_password2(self):
password1 = self.cleaned_data.get("password1", "")
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError("The two password fields didn't match.")
return password2
def clean_email(self):
email = self.cleaned_data["email"]
try:
User.objects.get(email=email)
except User.DoesNotExist:
return email
raise forms.ValidationError("A user with that emailaddress already exists.")
class Meta:
model = CustomAccount
exclude = ['balance','user']
I want to create Custom account in Django Admin section using single form which has fields of auth.User and CustomAccount Model. I have getting error IntegrityError at /admin/mylogin/customaccount/add/
NOT NULL constraint failed: mylogin_customaccount.user_id
Since you are getting a not null error, I think you need to specify that the field is not required. Here is an example from one of my apps. This says that the form field is not required.
class arunModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
myuser = kwargs.pop('user', None)
super(arunModelForm, self).__init__(*args, **kwargs)
self.fields['nanoadded'].required = False
self.fields['response'].required = False
I also include this in my models so that my model fields can be null, like this:
class arun(models.Model):
auser = models.CharField(max_length=50, null=True)
adate = models.DateField(null=True)
atime = models.TimeField(null=True)
If I still get not null errors after I do both of these things, I fill the field with a place holder value to allow me to further investigate the problem.
You have removed the user from the form and in the save method, you have assigned self.user in save method. So when saving the form, the user attribute is not used for CustomAccountForm and the user field for CustomAccount is None.
Your save method should look like:
def save(self, commit= True):
user = User.objects.create_user(username =self.cleaned_data['username'], email = self.cleaned_data['email'] , password = self.cleaned_data['password1'])
user.save()
obj = super(CustomAccountForm, self).save(commit=False)
obj.user = user
obj.save()

Django custom user authentication is not working properly

i am using custom user authentication model which uses an email address as the username and creates the user but even if password1 is different than password2 the registration completes successfully. i am unable to debug the problem.
here is models.py file:
class UserManager(auth_models.BaseUserManager):
def create_user(self, email, first_name, last_name, password):
"""
Creates and saves a user with given email,
first name, last name and password.
"""
if not email:
raise ValueError("users must have an email address")
user = self.model(
email=UserManager.normalize_email(email),
first_name=first_name,
last_name=last_name,
)
user.set_password(password)
user.save(self._db)
return user
def create_superuser(self, email, first_name, last_name, password):
"""
Creates and saves a super_user with given email,
first name, last name and password.
"""
if not email:
raise ValueError("users must have an email address")
user = self.model(
email=UserManager.normalize_email(email),
first_name=first_name,
last_name=last_name,
)
user.is_admin = True
user.set_password(password)
user.save(self._db)
return user
class User(auth_models.AbstractBaseUser):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
joined_at = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name', ]
def get_full_name(self):
return self.first_name + " " + self.last_name
def get_short_name(self):
return self.first_name
def __str__(self):
return self.get_full_name() + ", email= " + self.email
#property
def is_staff(self):
return self.is_admin
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
class Meta:
verbose_name_plural = "users"
admin.py
class UserCreationForm(forms.ModelForm):
"""
A form for creating new users. Includes all the required
fields, plus a repeated password.
"""
password1 = forms.CharField(label='password', widget=forms.PasswordInput)
password2 = forms.CharField(label='confirm password', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
def clean_password(self):
"""
check that the two password entries match
"""
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("passwords don't match")
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
"""
A form for updating users. includes all the fields
on the user, but replaces the password field with
the admin's password hash display field.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'password', 'is_active', 'is_admin')
def clean_password(self):
"""
Regardless of what the user provides, return the initial value.
This is done here, rather than on the field, because the field
does not have access to the initial value.
"""
return self.initial["password"]
views.py
def register(request):
args = {}
args.update(csrf(request))
if request.method == 'POST':
form = UserCreationForm(request.POST)
args['form'] = form
if form.is_valid():
form.save()
return HttpResponseRedirect('/home/')
else:
args['form'] = UserCreationForm()
return render_to_response('authentication/signup.html', args, context_instance=RequestContext(request))
Try moving the password comparison logic present in the clean_password() function to a clean() function in the UserCreationForm.
The clean_fieldname() function should operate on the field fieldname and not any other field.
Also, when fields validation depends on each other then the best place to place the validation logic is inside the clean() method.
From Django docs:
We are performing validation on more than one field at a time, so the
form’s clean() method is a good spot to do this.
By the time the form’s clean() method is called, all the individual
field clean methods will have been run (the previous two sections), so
self.cleaned_data will be populated with any data that has survived so
far. So you also need to remember to allow for the fact that the
fields you are wanting to validate might not have survived the initial
individual field checks.
Code:
class UserCreationForm(forms.ModelForm):
...
def clean(self):
cleaned_data = super(UserCreationForm, self).clean()
password1 = cleaned_data.get("password1")
password2 = cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("passwords don't match")
return cleaned_data
The problem is, you doesn't has a 'password' field,
you need change the method name to, clean_password1 or clean_password2.
the method you created clean_password, will never be called
because there is no field called password
def clean_password1(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("passwords don't match"")
return password1