Case insensitive username in Django signup form - python

Here is my signup form,
class SignupForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name', 'last_name','username', 'email', 'password']
def clean_username(self):
username = self.cleaned_data.get('username')
email = self.cleaned_data.get('email')
if username and User.objects.filter(username=username).exclude(email=email).count():
raise forms.ValidationError('This username has already been taken!')
return username
This works well to check if there is same username presents or not. However it does not check for case insensitivity. If there is a username e.g. 'userone', then it also accepts a username with 'Userone'. Although it does not break any functionality, but looks very unprofessional.
My question is how can I check for case insensitive right in the forms, and raise error?

Sometimes I faced the same issue. Django considers username unique different in lower or upper case. Like if I enter John, it a unique username and if I enter john, it's a new username. I need to consider John and john not in the database. As simple as facebook do, both uppercase and lower case username name is same, unique.
So I achieve this just changing my signup code like this.
username = self.cleaned_data.get('username').lower()
Also, in my login code, I convert my username to lower.
So that, all time it saves username lower in the database and log in with lower case username. Although a user tries to login with upper case username, then it saves to the database by converting to lower case.

You can use __iexact here:
User.objects.filter(username__iexact=username).exclude(email=email).exists() # instead of count, used exists() which does not make any DB query

Simplest method I think is:
Use .exists() method, if true then validation error else return ❤

Related

How to validate a field in django DRF while it can have either email or phone as value

I want to create a serializer that let users to login with their username or phone number,
Now if they use a phone number or username I need different validations.
I know that I can achieve this in the view, however, I'm looking for a solution to handle this situation in the serializer.
In the DRF the validate method can be used for example with the following code:
if '#' in data['value']:
# the validation for email should be done here
validator = EmailValidator()
validator(data['value'])
return data
else:
# here the mobile number should be validated
pass

Is it possible to deny access to copied password field in database?

My Django app password in database looks like this:
pbkdf2_sha256$100000$XXXXXXXXXX$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
But if I duplicate it to another user, I could log in to that user's account.
Assuming a database breach or some kind of injection, can I detect if password was somehow duplicated/copied so that I can deny access to the account or alert admins?
as Selcuk says if some one has access to write into your database, he/she can do anything like generate password as your system want.
but you can make it harder to change password.
if you want to deny password copy from database you must create your own user model and update hashing algorithm like this.
first create a user model:
from django.contrib.auth.hashers import make_password, check_password
class MyUser(User):
def set_password(self, raw_password):
self.password = make_password(self.username + raw_password)
self._password = raw_password
def check_password(raw_password):
def setter(raw_password):
self.set_password(raw_password)
self._password = None
self.save(update_fields=['password'])
return check_password(self.username + raw_password, self.password. setter)
by these changes user password hashes are contain username and copy hash for other user does not works correctly

Django how to get password after registration form is submitted?

I'm trying to edit django-registration's RegistrationFormUniqueEmail form, which currently looks like the following:
class RegistrationFormUniqueEmail(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which enforces uniqueness of
email addresses.
"""
def clean_email(self):
"""
Validate that the supplied email address is unique for the
site.
"""
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError(validators.DUPLICATE_EMAIL)
return self.cleaned_data['email']
I want to implement a form that checks if the password has a number and a special character, but I'm failing to get the password? I'm not sure if it's even possible, but here is what I tried to get the password:
self.cleaned_data['id_password2']
self.cleaned_data['password2']
self.cleaned_data['password']
self.cleaned_data.get('id_password2')
self.cleaned_data.get('password2')
self.cleaned_data.get('password')
all of these return a NoneType object.
I also tried to define a clean_password2 function, but no help. Is this doable? And how so?
Thanks for any help,
Cleaned data isn't an attribute when you create a form, it is added when you call is_valid() on a form.
is_valid() will check that the data conforms to the constraint you put on it when creating the form (in this case, matching passwords and unique email by default for a RegistrationFormUniqueEmail). It might make sense to overwrite the is_valid() or create another function that calls is_valid() in addition to performing additional checks on password strength, instead of performing additional validation outside of the standard procedure
def custom_is_valid(self):
valid = self.is_valid()
if password.is.not.secure():
self.add_error('password', ValidationError(('Insecure Password'), code='invalid'))
valid = False
return valid
The above snipped might do the trick. The string 'password' in adding the error tacks it onto the form field you indicate, you may want to use password1 or password2 because I think that's the field name for the build in form you're using. Also, you wouldn't use cleaned_data to check password validity. You would just use the data attribute, form.data['password']

Admin(only) registration of users, Flask-Security

I'm currently building a login for a webapp using Flask-Security (which includes Flask-WTForms, Flask-SQLalchemy and Flask-Login). I've been able to fairly painlessly set up the majority of my login flow, including forgotten password; however I want to make it so that the only way users can be registered is through a page only accessible to the admins. I've managed to configure Roles (Admin, User) and set up the following view:
#app.route('/adminregister')
#roles_accepted('admin')
def adminregister():
return render_template('*')
This creates the portal that is successfully limited to accounts with an Admin role. I'm unsure how to proceed for here however, as Flask-security has no built in means to enable what I'm trying to do.
I've overridden RegisterForm already to enforce password rules through a regexp:
# fixed register form
class ExtendedRegisterForm(RegisterForm):
password = TextField('Password', [validators.Required(), validators.Regexp(r'(?=.*?[0-9])(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[$-/:-?{-~!"^_`\[\]])')])
Basically I want a form, located at /adminregister, that when visited by an admin allows for the entry of an email address, at which point first the user is created in the database with a random and secure password, and then a similar process to a forgotten password happens and a 1 time password code is created to reset the password.
Useful things I've looked at:
Within flask-security/views.py there is the forgotten passsword code:
def forgot_password():
"""View function that handles a forgotten password request."""
form_class = _security.forgot_password_form
if request.json:
form = form_class(MultiDict(request.json))
else:
form = form_class()
if form.validate_on_submit():
send_reset_password_instructions(form.user)
if request.json is None:
do_flash(*get_message('PASSWORD_RESET_REQUEST', email=form.user.email))
if request.json:
return _render_json(form, include_user=False)
return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'),
forgot_password_form=form,
**_ctx('forgot_password'))
Within flask_security/registerable.py there is the code for register_user
def register_user(**kwargs):
confirmation_link, token = None, None
kwargs['password'] = encrypt_password(kwargs['password'])
user = _datastore.create_user(**kwargs)
_datastore.commit()
if _security.confirmable:
confirmation_link, token = generate_confirmation_link(user)
do_flash(*get_message('CONFIRM_REGISTRATION', email=user.email))
user_registered.send(app._get_current_object(),
user=user, confirm_token=token)
if config_value('SEND_REGISTER_EMAIL'):
send_mail(config_value('EMAIL_SUBJECT_REGISTER'), user.email, 'welcome',
user=user, confirmation_link=confirmation_link)
return user
I want to somehow combine these two, so that upon submission of a form with the sole field "Email" at '/adminregister' the email is added with a secure, random password in the database and the email address is sent an email with a link to change there password (and ideally a message explaining). I'm not even sure where I would add such code, as there is nothing to specifically override, especially as I can't find a way to override RegisterForm to have FEWER fields and the same functionality.
The structure of my code is in line with the flask-security documentation's quickstart.
Thank you in advance for any guidance you can offer.
I ended up using a work around as follows:
I enabled registration but limited registration view to users with an admin role.
I used del form.password in views -> register to no longer send the form with a password field.
I did the following in .registerable, generating a random password to fill the table.
kwargs['password'] = encrypt_password(os.urandom(24))
Upon admin entry of an email in the registration form, I had confimable enabled. This means the user would immediatly get an email to confirm their account and explaining they'd been registered. Upon confirmation they are redirected to the forgotten password page and asked to change their password (which is limited based on security).
If anyone comes up with a more direct way I'd appreciate it. I'm leaving this here in case anyone has the same problem.
The register process creates a signal with blinker that you can access like this:
from flask.ext.security.signals import user_registered
#user_registered.connect_via(app)
def user_registered_sighandler(app, user, confirm_token):
user_datastore.deactivate_user(user)
db.session.commit()
Which will deactivate any newly registered users.
I know this is an ancient question, but I think I have an elegant answer.
first import register_user
from flask_security.registerable import register_user
Then since you do not want just anyone to register ensure registerable is disabled (though disabled is the default so you can omit this) and since you want to send confirmation email, enable confirmable, and changeable for users to change their paswords
app.config['SECURITY_CONFIRMABLE'] = True
app.config['SECURITY_REGISTERABLE'] = False
app.config['SECURITY_RECOVERABLE'] = True
Then, you can do your create your user registration view and decorate it with role required. I have used my own custom registration form so I have had to go an extra mile to check if user already exists and return an error accourdingly
#app.route('/admin/create/user', methods=['GET', 'POST'])
#roles_required('admin')
def admin_create_user():
form = RegistrationForm(request.form)
if request.method == 'POST' and form.validate_on_submit():
email = form.email.data
password = form.password.data
user_exists = session.query(User).filter_by(email=email).first()
if user_exists:
form.email.errors.append(email + ' is already associated with another user')
form.email.data = email
email = ''
return render_template('create-user.html', form = form)
else:
register_user(
email=email,
password = password)
flash('User added successfully')
return render_template('create-user.html', form = form)
Also see flask-security - admin create user, force user to choose password
Here's another solution I found after poking through flask-security-too. I made an admin create user form, and simply add the following code after creating the user in the database:
from flask_security.recoverable import send_reset_password_instructions
# my code is maintains self.created_id after creating the user record
# this is due to some complex class involved which handles my crudapi stuff
# your code may vary
user = User.query.filter_by(id=self.created_id).one()
send_reset_password_instructions(user)

Django - revisiting "change password at next login”

I apologize, as this has been asked before (https://stackoverflow.com/a/5570717/3529404). However, I am having trouble with the accepted answer from user Chris Pratt. The code generally works - I am able to force password reset. However, where I am having trouble is trying to ensure that the new password is different than the old password. As the code is currently written, the same password is allowed.
From Chris's answer:
def password_change_signal(sender, instance, **kwargs):
try:
user = User.objects.get(username=instance.username)
if not user.password == instance.password:
profile = user.get_profile()
profile.force_password_change = False
profile.save()
except User.DoesNotExist:
pass
It seems as this should be checked in the line:
if not user.password == instance.password:
However, when I print user.password and instance.password (despite entering in the same password in both fields), the hashed value is not equal. Oddly enough, if I keep changing the password, the value that is printed for instance.password becomes the value for user.password on the next change.
Basically, all I want to do is use the code from the previous linked answer (https://stackoverflow.com/a/5570717/3529404), but enforce that the new password is different than the old password.
Thank you!!
UPDATE!
As discussed in the comments, I think my main area of frustration at the moment is not understanding exactly how user/instance differ. In particular, when print both user and instance passwords (see below), the hashed values are different even though the same password has been entered in each.
My code is slightly different than #Chris Pratt's because I am not using the depreciated user profile commands. Hopefully I didn't leave anything out!
webapp/models.py
class UserAdditional(models.Model):
user = models.ForeignKey(User, unique=True)
force_password_change = models.BooleanField(default=True)
def create_user_additional_signal(sender, instance, created, **kwargs):
if created:
UserAdditional.objects.create(user=instance)
def password_change_signal(sender, instance, **kwargs):
try:
user = User.objects.get(username=instance.username)
# these hashed values differ, even when the instance password entered is the same as the stored user password
print user.password
print instance.password
if not user.password == instance.password:
useradditional_obj = UserAdditional.objects.get(user=user)
useradditional_obj.force_password_change = False
useradditional_obj.save()
except User.DoesNotExist:
pass
signals.pre_save.connect(password_change_signal, sender=User, dispatch_uid='webapp.models')
signals.post_save.connect(create_user_additional_signal, sender=User, dispatch_uid='webapp.models')
webapp/middleware.py
class PasswordChangeMiddleware:
def process_request(self, request):
if request.user.is_authenticated() and not re.match(r'^/password_change/?', request.path) \
and not re.match(r'^/logout/?', request.path):
useradditional_obj = UserAdditional.objects.get(user=request.user)
if useradditional_obj.force_password_change:
return HttpResponseRedirect('/password_change/')
webapp/forms.py --- for password requirement enforcing
class ValidatingPasswordForm(object):
MIN_LENGTH = 8
def clean_new_password1(self):
password1 = self.cleaned_data.get('new_password1')
# At least MIN_LENGTH long
if len(password1) < self.MIN_LENGTH:
raise forms.ValidationError("The new password must be at least %d characters long." % self.MIN_LENGTH)
# check numbers and special characters
nums = len(set(re.findall(r'[0-9]',password1)))
symb = len(set(re.findall(r'[~!##$%^&\*()_+=-`]',password1)))
if nums <= 0 or symb <= 0:
raise forms.ValidationError("The new password must contain at least one number and one special character [~!##$%^&\*()_+=-`]")
return password1
class ValidatingPasswordChangeForm(ValidatingPasswordForm, auth.forms.PasswordChangeForm):
pass
class ValidatingSetPasswordForm(ValidatingPasswordForm, auth.forms.SetPasswordForm):
pass
It is generally a good practice to require an authenticated user provide the current password when changing passwords. This protects against the case, even if unlikely, that a logged in user leaves the workstation with an active session and some "evil" user attempts to hijack their account by changing the password.
By requiring the user to enter both the old and new passwords you can also prevent password re-use both on the client and server side. This allows for increased usability for your users since you can warn them and disable submission of the form using JavaScript. By capturing the old and new passwords, you can pass both to the server and verify against re-use similar to the answer provided by warath-coder.
Update
As you've mentioned Django saved the hashes and not the actual passwords, and as a further protection the passwords are salted, see the Django docs on how passwords are stored. Because of this, you will not be able to simply compare the hashes. You can test that the old and new passwords match in the clean_new_password1 method of your form using the form data prior to the User object being updated. This can be done by simple comparison or by trying to authenticate with the old password as warath-coder has described.
I would do this:
def password_change_signal(sender, instance, **kwargs):
try:
user = authenticate(username=instance.username, password=instance.password)
if user is None: # means auth failed, which means password is not the same as the current password.
user = User.objects.get(username=instance.username)
user.set_password(instance.password)
user.save()
profile = user.get_profile()
profile.force_password_change = False
profile.save()
except User.DoesNotExist:
pass
basically i try to authenticate the user with the password they supply, and it should fail if the new password is different than the current.

Categories

Resources