diff options
| author | Simon Charette <charette.s@gmail.com> | 2019-12-16 21:51:57 -0500 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2019-12-18 09:16:08 +0100 |
| commit | 4d334bea06cac63dc1272abcec545b85136cca0e (patch) | |
| tree | 3ef955e8cc238ba9e06c856bddcec3a3f4828936 /django | |
| parent | 86befcc172c23170a720b3e0c06db51a99b3da59 (diff) | |
[2.2.x] Fixed CVE-2019-19844 -- Used verified user email for password reset requests.
Backport of 5b1fbcef7a8bec991ebe7b2a18b5d5a95d72cb70 from master.
Co-Authored-By: Florian Apolloner <florian@apolloner.eu>
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/auth/forms.py | 24 |
1 files changed, 20 insertions, 4 deletions
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index d6b5702fff..e6f73fe5ee 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -20,6 +20,15 @@ from django.utils.translation import gettext, gettext_lazy as _ UserModel = get_user_model() +def _unicode_ci_compare(s1, s2): + """ + Perform case-insensitive comparison of two identifiers, using the + recommended algorithm from Unicode Technical Report 36, section + 2.11.2(B)(2). + """ + return unicodedata.normalize('NFKC', s1).casefold() == unicodedata.normalize('NFKC', s2).casefold() + + class ReadOnlyPasswordHashWidget(forms.Widget): template_name = 'auth/widgets/read_only_password_hash.html' read_only = True @@ -256,11 +265,16 @@ class PasswordResetForm(forms.Form): 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' % UserModel.get_email_field_name(): email, + '%s__iexact' % email_field_name: email, 'is_active': True, }) - return (u for u in active_users if u.has_usable_password()) + 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', @@ -273,6 +287,7 @@ class PasswordResetForm(forms.Form): 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) @@ -280,8 +295,9 @@ class PasswordResetForm(forms.Form): domain = current_site.domain else: site_name = domain = domain_override + user_email = getattr(user, email_field_name) context = { - 'email': email, + 'email': user_email, 'domain': domain, 'site_name': site_name, 'uid': urlsafe_base64_encode(force_bytes(user.pk)), @@ -292,7 +308,7 @@ class PasswordResetForm(forms.Form): } self.send_mail( subject_template_name, email_template_name, context, from_email, - email, html_email_template_name=html_email_template_name, + user_email, html_email_template_name=html_email_template_name, ) |
