Opened 5 years ago
Closed 5 years ago
#31139 closed Cleanup/optimization (wontfix)
PasswordResetView return success message for emails not in database also
Reported by: | SANYAM MITTAL | Owned by: | sanyam19092000 |
---|---|---|---|
Component: | contrib.auth | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
PasswordResetView returns a success message for emails not in database also.
Problems Faced
- If the user is not Registered but strongly thinks they are registered and have forgotten the password they would keep trying to get Reset email.
- If they've typed a wrong email in PasswordResetForm. They would be expecting a reset email with reset URL but wouldn't receive any mail nor any Validation Error would be raised.
Current PasswordResetForm being used:
https://github.com/django/django/blob/0f843fdd5b9b2f2307148465cd60f4e1b2befbb4/django/contrib/auth/forms.py#L250
Here while saving the form by - https://github.com/django/django/blob/0f843fdd5b9b2f2307148465cd60f4e1b2befbb4/django/contrib/auth/forms.py#L292
for user in self.get_users(email): user_email = getattr(user, email_field_name) context = { 'email': user_email, 'domain': domain, 'site_name': site_name, 'uid': urlsafe_base64_encode(force_bytes(user.pk)), 'user': user, 'token': token_generator.make_token(user), 'protocol': 'https' if use_https else 'http', **(extra_email_context or {}), } self.send_mail( subject_template_name, email_template_name, context, from_email, user_email, html_email_template_name=html_email_template_name, )
It just mails the User (if existing) but doesn't raises an Validation Error if User is not found. Rather it redirects to Success URL.
If we raise a Validation Error when User query set is empty.
We can use def clean_email method
Or can add if else statement before iterating in Available Active Users
Change History (3)
comment:1 by , 5 years ago
Resolution: | → wontfix |
---|---|
Status: | assigned → closed |
comment:2 by , 5 years ago
Needs documentation: | set |
---|---|
Resolution: | wontfix |
Status: | closed → new |
Thanks for the Reference,
As mentioned in documentation
[https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.views.PasswordResetView]
This prevents information leaking to potential attackers
Facebook also raises a Validation Error when non registered email is entered
https://www.facebook.com/login/identify/?ctx=recover
Although a potential attacker can easily get these information from Sign-Up/Register page as Validation error is raised when a Duplicate Email Address is entered during sign-up.
If there's not a Unique email Validation during Sign-up there are chances that multiple users get registered with same email (if user mistakenly types someone else's email) and Password Reset email is sent multiple times for different Users which is more risky.
If still it prevents Information leak by any other ways then documentation should be more accurate and should have a sample or Suggestion method to achieve unique email Validation with current PasswordResetForm like
If you want to provide an error message in this case, you can subclass PasswordResetForm and use the form_class attribute.
class PasswordResetForm(forms.Form): error_messages = { 'not_registered': _('Email ID is not Registered'), } email = forms.EmailField( label=_("Email"), max_length=254, widget=forms.EmailInput(attrs={'autocomplete': 'email'}) ) def clean_email(self): email = self.cleaned_data.get('email') users = self.get_users(email=email) flag = False for user in users: if user: flag = True if not flag: raise forms.ValidationError( self.error_messages['not_registered'], code='not_registered', ) return email def send_mail(self, subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None): """ Send a django.core.mail.EmailMultiAlternatives to `to_email`. """ subject = loader.render_to_string(subject_template_name, context) # Email subject *must not* contain newlines subject = 'Testing Purpose' body = loader.render_to_string(email_template_name, context) email_message = EmailMultiAlternatives(subject, body, from_email, [to_email]) if html_email_template_name is not None: html_email = loader.render_to_string(html_email_template_name, context) email_message.attach_alternative(html_email, 'text/html') email_message.send() def get_users(self, email): """Given an email, return matching user(s) who should receive a reset. This allows subclasses to more easily customize the default policies that prevent inactive users and users with unusable passwords from resetting their password. """ email_field_name = UserModel.get_email_field_name() active_users = UserModel._default_manager.filter(**{ '%s__iexact' % email_field_name: email, 'is_active': True, }) return ( u for u in active_users if u.has_usable_password() and _unicode_ci_compare(email, getattr(u, email_field_name)) ) def save(self, domain_override=None, subject_template_name='registration/password_reset_subject.txt', email_template_name='registration/password_reset_email.html', use_https=False, token_generator=default_token_generator, from_email=None, request=None, html_email_template_name=None, extra_email_context=None): """ Generate a one-use only link for resetting password and send it to the user. """ email = self.cleaned_data["email"] email_field_name = UserModel.get_email_field_name() for user in self.get_users(email): if not domain_override: current_site = get_current_site(request) site_name = current_site.name domain = current_site.domain else: site_name = domain = domain_override user_email = getattr(user, email_field_name) context = { 'email': user_email, 'domain': domain, 'site_name': site_name, 'uid': urlsafe_base64_encode(force_bytes(user.pk)), 'user': user, 'token': token_generator.make_token(user), 'protocol': 'https' if use_https else 'http', **(extra_email_context or {}), } self.send_mail( subject_template_name, email_template_name, context, from_email, user_email, html_email_template_name=html_email_template_name, )
comment:3 by , 5 years ago
Easy pickings: | unset |
---|---|
Needs documentation: | unset |
Resolution: | → wontfix |
Status: | new → closed |
Thanks for the report, but this is by design. Please read the documentation which also gives a hint about how to change the behavior.
https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.views.PasswordResetView