Python Django prevent password reuse - django

I'm currently trying to prevent users from using their old passwords (configurable, like say last 5 passwords)
I have the following Infos:
User passwort in cleartext from the form during validation (form.cleaned_data['new_password1'])
Their current hashed Password (pwd_before_change)
Their last x passwords also hashed (pws)
Funnily enough my code works for the current password, but not for my old ones:
pwd_before_change = user_to_change.password
....
old_pws = Passwords.objects.filter(username=user_to_change).order_by('-password_changed')[:allowed_reuse].values_list('password', flat=True)
....
if old_pws:
for pws in old_pws:
if check_password(form.cleaned_data['new_password1'], pws) or \
check_password(form.cleaned_data['new_password1'], pwd_before_change)
messages.error(request,
f'You are not allowed a password which was already used in the last '
f'{allowed_reuse + 1} iterations')
return render(request, 'registration/password_change.html', {
'form': form
})
Any ideas what the issue may be? (additional info, I'm using Argon2).
Do I really need to encode the cleartext password again with all possible "salt" in the old passwords and check? (this would make the method depending on the hasher function which would not be ideal + I thought the check passwort function was exactly for scenarios like this?)

Ok, not sure what exactly was the issue, but I split up the "if" and now it works:
logger_user.info("User {} is attempting a password change".format(username))
if form.is_valid():
# Get the allowed Password counter from the settings, reduce by 1 as the current password is also checked!
allowed_reuse = settings.PASSWORD_REUSE - 1
logger_user.info(f"Allowed PWs: {allowed_reuse +1 }")
if check_password(form.cleaned_data['new_password1'], pwd_before_change):
messages.error(request, f'You are not allowed to re-use your current password')
return render(request, 'registration/password_change.html', {
'form': form
})
# Retrieve the last x passwords according to the Password Reuse Setting
old_pws = Passwords.objects.filter(username=user_to_change).order_by('-password_changed')[:allowed_reuse].values_list('password', flat=True)
# Compare the new Passwort with the old passwords
if old_pws:
for pw in old_pws:
if check_password(form.cleaned_data['new_password1'], pw):
messages.error(request,
f'You are not allowed a password which was already used in the last '
f'{allowed_reuse + 1} iterations')
return render(request, 'registration/password_change.html', {
'form': form
})

Related

python nested dictionaries to store usernames and passwords

What I'm trying to create is a dictionary that stores hashed usernames and passwords from user input... Now admittedly I don't think I fully understand how nested dictionaries work but here is the code of my function so far:
users = {}
def addUser():
print """"
########
Add user
########
"""
while True:
username = hashlib.sha512(raw_input("Enter username: ")).hexdigest()
passwd = hashlib.sha512(raw_input("Enter password: ")).hexdigest()
uid = int(username[:5], 16) % 32
users[username + passwd] = {
'User hash':username,
'Password hash':passwd,
}
print users
cont = raw_input("Press 'X/x' to exit and start the server or ANY other key to continue adding users: ")
if cont in ['X', 'x']:
break
What I want to do is use the uid variable to generate a unique identifier for each user and store it in a nested dictionary that will look something like this:
users = { 'uid': 28 { 'User hash': 'BFCDF3E6CA6CEF45543BFBB57509C92AEC9A39FB', 'Password hash': '9D989E8D27DC9E0EC3389FC855F142C3D40F0C50'},'uid': 10 { 'User hash': '8C4947E96C7C9F770AA386582E32CE7CE1B96E69', 'Password hash': '266F83D202FA3DA4A075CEA751B4B8D6A30DA1A8'}
}
Answered my own question after doing some reading and playing around with the code.
Here's the solved code if anyone else is having a similar issue
import hashlib
users = {}
def addUser()
print """"
########
Add user
########
"""
while True:
username = hashlib.sha512(raw_input("Enter username: ")).hexdigest() #use SHA512 to hash username from raw input
uid = int(username[:5], 16) % 32 #generate uid from hashed username, using math simliar to the chord algorithm but with SHA512 instead of SHA1
passwd = hashlib.sha512(raw_input("Enter password: ")).hexdigest() #use SHA512 to hash password from raw input
users[uid] = { #store username and password in dictionary
'User hash':username,
'Password hash':passwd,
}
print users
cont = raw_input("Press 'X/x' to exit and start the server or ANY other key to continue adding users: ")
if cont in ['X', 'x']:
break
I figured it out by looking at what the print users call actually did and it printed a combination of the two hashes, so replacing users[username + passwd] with users[uid] solved the issue!
Moral of the story: If it doesn't work... Do some research, have a play around & Try harder! ;)

Django form.save() not taking updated form.cleaned_data

I'm lost here and can't figure out what I'm missing, which is probably something stupid, but I need another set of eyes because as far as I can tell this should be working.
What I'm trying to do is allow my users to enter phone numbers in ways they are used to seeing, but then take that input and get a validated international phone number from Twilio and save it. By definition that means it will be in the following format - which is the format I need to have it in the database so that it interacts well with another part of the application:
+17085551212
I've debugged to the point there I know the values are coming in correctly, everything works right if I get an invalid number, etc. For some reason, the updated value is not being passed back to the form when I set form.cleaned_data['office_phone'] prior to the form.save(). So I am getting the original number (708) 555-1212 in the database.
forms.py
class ProfileForm(forms.ModelForm):
office_phone = forms.CharField(max_length=20, label="Office Phone")
views.py
if form.is_valid():
print (form.cleaned_data['office_phone'])
pn = form.cleaned_data['office_phone'].replace(" ","")
try:
response = validator.phone_numbers.get(str(pn))
form.cleaned_data['office_phone'] = str(response.phone_number)
print form.cleaned_data
form.save()
success_message = "Your changes have been saved"
except:
error_message = "The contact phone number you entered is invalid."
console.output
(708) 555-1212
+17085551212
+17085551212
{'office_phone': '+17085551212'}
<tr><th><label for="id_office_phone">Office Phone:</label></th>
<td><input id="id_office_phone" maxlength="20" name="office_phone" type="text" value="(708) 555-1212" /></td></tr>
What am I missing here?
Made an edit: I realise that instead of overriding save, we should instead clean/validate the phone number by using custom validation:
class ProfileForm(forms.ModelForm):
office_phone = forms.CharField(max_length=20, label="Office Phone")
def clean_office_phone(self):
value = self.cleaned_data.get("office_phone")
try:
value = value.replace(" ", "")
response = validator.phone_numbers.get(str(value))
except:
raise ValidationError("The contact phone number you entered is invalid")
return str(response.phone_number)
views.py:
if form.is_valid():
form.save()
success_message = "Your changes have been saved"

Django Form Validation don't keep password with ValidationError

I have written clean field based validation. To check I enter unmatching passwords this wipe off passwords from both the fields. This can annoy user. I want this like "If error let the user change a bit..don't retype whole password". However username validation keeps the username in the field
after error.Why this is happening?
class signupform(ModelForm):
prmqpass=forms.CharField(widget=forms.PasswordInput())
prmqpass1=forms.CharField(widget=forms.PasswordInput())
class Meta:
model=prmqsignup
exclude=['prmqactivated']
def clean_prmqpass(self):
cd=self.cleaned_data
pwd=cd.get("prmqpass")
if len(str(pwd))<6:
raise ValidationError("Password must be at least of six characters")
if not passstrength(pwd):
raise ValidationError("For stronger security password must contain at least one uppercase,lowercase,number and special character")
return pwd
def clean_prmquname(self):
cd=self.cleaned_data
usrname=cd.get("prmquname")
if len(usrname)<5:
raise ValidationError("Username too short choose more than four characters")
if prmqsignup.objects.exclude(pk=self.instance.pk).filter(prmquname=usrname).exists():
raise ValidationError('Username "%s" is already in use.' % usrname)
return usrname
def clean(self):
cd=self.cleaned_data
pwd=cd.get("prmqpass")
pwd1=cd.get("prmqpass1")
if not pwd==pwd1:
raise ValidationError("Passwords don't match.")
return cd
Why username value(staying in form) and password value(flashing away) after error.See validation logic is same.
From the documentation:
Password input: <input type='password' ...>
Takes one optional argument:
render_value: Determines whether the widget will have a value filled in when the form is re-displayed after a validation error (default is False).
So you need to initialise your inputs like so:
prmqpass = forms.CharField(widget=forms.PasswordInput(render_value=True))

Django unit test for function

I need to test if a user is active. I have that function:
Service.py
#validate_input({
'code': {'required': True}
})
def signup_complete(self, data):
try:
code = VerificationCode.objects.select_related('user').get(code=data["code"],
code_type="registration",
expiration_date__gt=timezone.now())
except VerificationCode.DoesNotExist:
raise NotFound(_(u"Неверный код восстановленыя"), 1001)
user = code.user
user.is_active = True
user.save()
code.delete()
and I try to write a test for this function, but I don't know what argument I need to send.
Test.py
def test_signup_complete(self):
user = SiteUser.objects.get(email="Test#gmail.com")
code = VerificationCode.objects.get_or_create(user=user, code_type="registration", code=user.code)
UserService(user).signup_complete()
self.assertEqual(user.is_active, True)
You can reproduce all the steps needed for user registration and activation. Accessing registration page, sending filled form, checking emails with activation code etc. With this approach (which is much more reliable in comparison to your current code) you can get the confirmation code in the same way as real user gets it (for example, from profile activation email).

password reset form, own clean method

I want to write my own method for the password reset form to make some tests about the new password (length, characters,...). So I added this class to my forms.py:
class PasswordResetForm(SetPasswordForm):
def clean(self):
if 'newpassword1' in self.cleaned_data and 'newpassword2' in self.cleaned_data:
if self.cleaned_data['newpassword1'] != self.cleaned_data['newpassword2']:
raise forms.ValidationError(("The two password fields didn't match."))
#here the passwords entered are the same
if len(self.cleaned_data['newpassword1']) < 8:
raise forms.ValidationError(("Your password has to be at least 8 characters long"))
if not re.search('\d+', self.cleaned_data['newpassword1']) or not re.search('([a-zA-Z])+', self.cleaned_data['new password1']):
raise forms.ValidationError(("Your password needs to contain at least one number and one character"))
return self.cleaned_data
and in the urls.py I added this:
url(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', django.contrib.auth.views.password_reset_confirm, {'template_name':'registration/password_reset_confirm.html',
'set_password_form': PasswordResetForm})
But my own clean method isn't called. Whats wrong with this?
this answer may look stupid and i don't have the justification but i ran in the same problem and i solved it by replacing the
self.cleaned_data['newpassword1']
by
self.cleaned_data.get('newpassword1')
for all the clean_data set:
class PasswordResetForm(SetPasswordForm):
def clean(self):
cleaned_data = self.cleaned_data
if 'newpassword1' in cleaned_data and 'newpassword2' in cleaned_data:
if cleaned_data.get('newpassword1') != cleaned_data.get('newpassword2'):
raise forms.ValidationError(("The two password fields didn't match."))
if len(cleaned_data.get('newpassword1')) < 8:
raise forms.ValidationError(("Your password has to be at least 8 characters long"))
if not re.search('\d+', cleaned_data.get('newpassword1')) or not re.search('([a-zA-Z])+', cleaned_data.get('new password1')):
raise forms.ValidationError(("Your password needs to contain at least one number and one character"))
return cleaned_data
I found the error. It was a mistake in my urls.py. It needed to be:
url(r'^password/reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',django.contrib.auth.views.password_reset_confirm,{'template_name':'registration/password_reset_confirm.html','set_password_form': PasswordResetForm})
I don't know why I still got an password reset form and not a 404. Maybe someone can tell me. I'm a bit worried that there might still be a possible way to use the standard reset form.