check_password() from a user again - python

I have the following form. How can I check the password from the user again, before the user can change his email address finally? Even if the user is logged in, I just want to be sure that it is really the user. Just a security thing.
How do I do it with .check_password()?
'EmailChangeForm' object has no attribute 'user'
/home/craphunter/workspace/project/trunk/project/auth/user/email_change/forms.py in clean_password, line 43
from django import forms
from django.db.models.loading import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
class EmailChangeForm(forms.Form):
email = forms.EmailField(label='New E-mail', max_length=75)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
super(EmailChangeForm, self).__init__(*args, **kwargs)
self.user = user
def clean_password(self):
valid = self.user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
return valid
def __init__(self, username=None, *args, **kwargs):
"""Constructor.
**Mandatory arguments**
``username``
The username of the user that requested the email change.
"""
self.username = username
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_email(self):
"""Checks whether the new email address differs from the user's current
email address.
"""
email = self.cleaned_data.get('email')
User = cache.get_model('auth', 'User')
user = User.objects.get(username__exact=self.username)
# Check if the new email address differs from the current email address.
if user.email == email:
raise forms.ValidationError('New email address cannot be the same \
as your current email address')
return email

I would refactor your code to look something like this:
View:
#login_required
def view(request, extra_context=None, ...):
form = EmailChangeForm(user=request.user, data=request.POST or None)
if request.POST and form.is_valid():
send_email_change_request(request.user,
form.cleaned_data['email'],
https=request.is_secure())
return redirect(success_url)
...
Password validation goes to form:
class EmailChangeForm(Form):
email = ...
old_password = CharField(..., widget=Password())
def __init__(self, user, data=None):
self.user = user
super(EmailChangeForm, self).__init__(data=data)
def clean_old_password(self):
password = self.cleaned_data.get('password', None)
if not self.user.check_password(password):
raise ValidationError('Invalid password')
Extract logic from view:
def send_email_change_request(user, new_email, https=True):
site = cache.get_model('sites', 'Site')
email = new_email
verification_key = generate_key(user, email)
current_site = Site.objects.get_current()
site_name = current_site.name
domain = current_site.domain
protocol = 'https' if https else 'http'
# First clean all email change requests made by this user
qs = EmailChangeRequest.objects.filter(user=request.user)
qs.delete()
# Create an email change request
change_request = EmailChangeRequest(
user = request.user,
verification_key = verification_key,
email = email
)
change_request.save()
# Prepare context
c = {
'email': email,
'site_domain': 'dev.tolisto.de',
'site_name': 'tolisto',
'user': self.user,
'verification_key': verification_key,
'protocol': protocol,
}
c.update(extra_context)
context = Context(c)
# Send success email
subject = "Subject" # I don't think that using template for
# subject is good idea
message = render_to_string(email_message_template_name, context_instance=context)
send_mail(subject, message, None, [email])
Don't put complicated things inside views (such as rendering and sending email).

I feel like you answered your own question : )
The docs on the check_password method are here:
https://docs.djangoproject.com/en/dev/topics/auth/customizing/#django.contrib.auth.models.AbstractBaseUser.check_password
success = user.check_password(request.POST['submitted_password'])
if success:
# do your email changing magic
else:
return http.HttpResponse("Your password is incorrect")
# or more appropriately your template with errors
Since you're already passing in request.user into your form constructor (looks like you've overriden __init__ for your own reasons) you could put all of your logic in the form without any trouble.
class MyForm(forms.Form):
# ...
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.user = user
def clean_password(self):
valid = self.user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
return valid
update after seeing your forms
OK. The main problem is that __init__ has been defined twice, making the first statement useless. Second problem I see is that we'd be doing multiple queries for user when we really don't have to.
We've strayed from your original question quite a bit, but hopefully this is a learning experience.
I've changed only a few things:
Removed the extra __init__ definition
Changed __init__ to accept a User instance instead of a text username
Removed the query for User.objects.get(username=username) since we're passing in a user object.
Just remember to pass the form constructor user=request.user instead of username=request.user.username
class EmailChangeForm(forms.Form):
email = forms.EmailField(label='New E-mail', max_length=75)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user=None, *args, **kwargs):
self.user = user
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_password(self):
valid = self.user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
def clean_email(self):
email = self.cleaned_data.get('email')
# no need to query a user object if we're passing it in anyways.
user = self.user
# Check if the new email address differs from the current email address.
if user.email == email:
raise forms.ValidationError('New email address cannot be the same \
as your current email address')
return email
Finally since we're talking about good practice here, I'd recommend following through with Skirmantas suggestions about moving your current view code to a form method so you can simply call myform.send_confirmation_email.
Sounds like a good exercise!

thanks again to Yuji. It works when I don't have in my first def __init__ the variable user. I also added in def clean_password the first 2 lines from the def clean_email
from django import forms
from django.db.models.loading import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
class EmailChangeForm(forms.Form):
email = forms.EmailField(label='New E-mail', max_length=75)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
self.user = user
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_password(self):
User = cache.get_model('auth', 'User')
user = User.objects.get(username__exact=self.username)
valid = user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
return valid
def __init__(self, username=None, *args, **kwargs):
"""Constructor.
**Mandatory arguments**
``username``
The username of the user that requested the email change.
"""
self.username = username
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_email(self):
"""Checks whether the new email address differs from the user's current
email address.
"""
email = self.cleaned_data.get('email')
User = cache.get_model('auth', 'User')
user = User.objects.get(username__exact=self.username)
# Check if the new email address differs from the current email address.
if user.email == email:
raise forms.ValidationError('New email address cannot be the same \
as your current email address')
return email

Related

KeyError at /api/login 'user'

This user keyword error is showing in my login api. I am not sure why. I am guessing user instance is not available in the login view. I am new and I dont know the reason.
This is my login view:
class LoginUserView(GenericAPIView):
permission_classes = [AllowAny]
serializer_class = UserLoginSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = UserLoginSerializer(data=data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data["user"]
token, created = Token.objects.get_or_create(user=user)
return response.Response({"token": token.key}, status=status.HTTP_200_OK)
This is my serializers:
class UserLoginSerializer(serializers.ModelSerializer):
email = serializers.EmailField(label='Email Address')
class Meta:
model = User
fields = [
'email', 'password',
]
extra_kwargs = {"password":
{"write_only": True}}
def validate(self, data):
# user = None
email = data.get("email", None)
password = data.get("password")
if not email:
raise serializers.ValidationError("Email is required for login")
if not password:
raise serializers.ValidationError("Password is required for login")
user = authenticate(email=email, password=password)
if not user:
raise serializers.ValidationError("This email is not valid/already exists")
return data
The serializer has no user in its validated data. Note that serializer.validated_data['user'] makes not much sense, since you never passed this to the validator.
You should add it to the data with:
def validate(self, data):
# user = None
email = data.get('email', None)
password = data.get('password')
if not email:
raise serializers.ValidationError('Email is required for login')
if not password:
raise serializers.ValidationError('Password is required for login')
user = authenticate(email=email, password=password)
if not user:
raise serializers.ValidationError('This email is not valid/already exists')
data['user'] = user
return data

auth is returned as none when trying to log in user when signing up

I am trying to solve the use case where when user signs up then the user is automatically logged in and return the token of that user so that i can that token in cookies from frontend. However, I get issue "Email already registered". when i debug my code using pdb, i found that auth = authenticate(username=email, password=password) is returning None.
How can I authenticate user during signup and pass token of that user?
Here is how i am doing
class Register(graphene.Mutation):
'''
Mutation to register a user
'''
class Arguments:
email = graphene.String(required=True)
password = graphene.String(required=True)
password_repeat = graphene.String(required=True)
success = graphene.Boolean()
errors = graphene.List(graphene.String)
email = graphene.String()
def mutate(self, info, email, password, password_repeat):
if password == password_repeat:
try:
serializer = RegistrationSerializer(data={
'email': email,
'password': password,
'is_active': False
})
if serializer.is_valid():
user = serializer.save()
auth = authenticate(username=email, password=password)
import pdb
pdb.set_trace()
# login user and pass the token
login(info.context, auth)
return Register(success=bool(user.id), email=user.email)
else:
print("error", serializer.errors)
except Exception:
errors = ["email", "Email already registered"]
return Register(success=False, errors=errors)
errors = ["password", "Passwords don't match."]
return Register(success=False, errors=errors)
class Login(graphene.Mutation):
"""
Mutation to login a user
"""
class Arguments:
email = graphene.String(required=True)
password = graphene.String(required=True)
success = graphene.Boolean()
errors = graphene.List(graphene.String)
token = graphene.String()
user = graphene.Field(UserQuery)
def mutate(self, info, email, password):
user = {'email': email, 'password': password}
serializer = JSONWebTokenSerializer(data=user)
if serializer.is_valid():
token = serializer.object['token']
user = serializer.object['user']
print('user', user)
return Login(success=True, user=user, token=token)
else:
print("serializers errors", serializer.errors)
return Login(
success=False,
token=None,
errors=['email', 'Unable to login with provided credentials.']
)
RegistrationSerializer
class RegistrationSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email', 'is_active', 'password', )
def create(self, validated_data):
print("validated_data", validated_data)
user = User.objects.create(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
I am using graphql instead of REST API and I am using graphene-django for graphql.

Overwriting the AuthenticationForm class to log in with email

I have an application in django 1.11 and I created a login by email instead of a username. In order to log in using the login form I had to overwrite the AuthenticationForm class and insert an email instead of username.
forms.py
UserModel = get_user_model()
Here I overwritted AuthenticationForm class and change username to email.
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
email = forms.EmailField(
label=_("Email address"),
max_length=254,
widget=forms.EmailInput(attrs={'autofocus': True}),
)
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput,
)
error_messages = {
'invalid_login': _(
"Please enter a correct %(username)s and password. Note that both "
"fields may be case-sensitive."
),
'inactive': _("This account is inactive."),
}
def __init__(self, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user_cache = None
super(AuthenticationForm, self).__init__(*args, **kwargs)
# Set the label for the "username" field.
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
if self.fields['email'].label is None:
self.fields['email'].label = capfirst(self.username_field.verbose_name)
def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if email is not None and password:
self.user_cache = authenticate(self.request, email=email, password=password)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user):
"""
Controls whether the given User may log in. This is a policy setting,
independent of end-user authentication. This default behavior is to
allow login by active users, and reject login by inactive users.
If the given user cannot log in, this method should raise a
``forms.ValidationError``.
If the given user may log in, this method should return None.
"""
if not user.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
class LoginForm(AuthenticationForm):
email = forms.CharField(label='Email', max_length=50)
password = forms.CharField(label='Password', max_length=50)
field_order = ['email', 'password']
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
Is it as it is now, is it ok? Is it possible to do it shorter / better?
You don't need to override full Class as you extending AuthenticationForm class. You just need to override clean() and change USERNAME_FIELD to email

Overwriting rest-auth RegisterSerializer, add age validation

I'm trying to validate age for user during creation by rest-auth. I managed to add field and save it during registration, but now I'm having hard time to validate if age is < 18.
Someone could point me at the way I should do it?
I have tried with validation through my AbstractUser model, with #property method, and it was raising ValidationError during registration, but the User account was saving anyway, and i couldn't access to user detail view because of the ValidationError, so I came to the conclusion that I would just prefer to prevent registration through validation, but it isn't working in my case.
class RegisterSerializer(serializers.Serializer):
username = serializers.CharField(
max_length=get_username_max_length(),
min_length=allauth_settings.USERNAME_MIN_LENGTH,
required=allauth_settings.USERNAME_REQUIRED
)
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
date_of_birthday = serializers.DateField() ### ADDED BY ME
password1 = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True)
def validate_username(self, username):
username = get_adapter().clean_username(username)
return username
def validate_email(self, email):
email = get_adapter().clean_email(email)
if allauth_settings.UNIQUE_EMAIL:
if email and email_address_exists(email):
raise serializers.ValidationError(
_("A user is already registered with this e-mail address."))
return email
def validate_age(self, date_of_birthday): ### ADDED BY ME
age = relativedelta(datetime.now(), date_of_birthday).years
if age < 18:
raise serializers.ValidationError('Must be at least 18 years old to register.')
else:
return age
def validate_password1(self, password):
return get_adapter().clean_password(password)
def validate(self, data):
if data['password1'] != data['password2']:
raise serializers.ValidationError(_("The two password fields didn't match."))
return data
def custom_signup(self, request, user):
pass
def get_cleaned_data(self):
return {
'username': self.validated_data.get('username', ''),
'date_of_birthday': self.validated_data.get('date_of_birthday', ''), ### ADDED BY ME
'password1': self.validated_data.get('password1', ''),
'email': self.validated_data.get('email', '')
}
def save(self, request):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
adapter.save_user(request, user, self)
self.custom_signup(request, user)
setup_user_email(request, user, [])
user.date_of_birth = self.cleaned_data.get('date_of_birthday') ### ADDED BY ME
user.save() ### ADDED BY ME
return user
You can validated serializer fields by defining methods prefixing validate_ to the field name. In your case, replace validate_age with validate_date_of_birthday
def validate_date_of_birthday(self, date_of_birthday):
age = relativedelta(datetime.now(), date_of_birthday).years
if age < 18:
raise serializers.ValidationError('Must be at least 18 years old to register.')
else:
return date_of_birthday

Accessing user object when creating a user in Django

I'm using Django 1.10.6 and I'm working on a user registration form. During the validation process in my forms.py I'm trying to call the UserAttributeSimilarityValidator from within the clean method, but I'm having some trouble figuring out what the user object argument is that it requires. The request data is a QueryDict, not an object, so I'm confused as to what object I'm supposed to be using.
Here is the UserAttributeSimilarityValidator class found in the link I provided
class UserAttributeSimilarityValidator(object):
DEFAULT_USER_ATTRIBUTES = ('username', 'first_name', 'last_name', 'email')
def __init__(self, user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7):
self.user_attributes = user_attributes
self.max_similarity = max_similarity
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
value_parts = re.split('\W+', value) + [value]
for value_part in value_parts:
if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() > self.max_similarity:
verbose_name = force_text(user._meta.get_field(attribute_name).verbose_name)
raise ValidationError(
_("The password is too similar to the %(verbose_name)s."),
code='password_too_similar',
params={'verbose_name': verbose_name},
)
def get_help_text(self):
return _("Your password can't be too similar to your other personal information.")
Here's my clean method in forms.py
def clean(self):
cleaned_data = super(RegisterForm, self).clean()
password = cleaned_data.get('password')
password_validation.UserAttributeSimilarityValidator().validate(password, USER_OBJET_HERE)
I tried to create a user object by using namedtuple like so
user = namedtuple('user', 'username email')
user_attributes = user(username = self.data.get('username'), email = self.data.get('email'))
password_validation.UserAttributeSimilarityValidator().validate(password1, user_attributes)
When I do this it fails with the following error 'user' object has no attribute '_meta'. Is there an actual user object created by the form that I'm supposed to access, or am I supposed to create it like I attempted?
UPDATE I was way overthinking this and got it to work. I had to manually set the username and email attributes for the User object like so
from django.contrib.auth.models import User
...
User.username = self.data.get('username')
User.email = self.data.get('email')

Categories