File CVE-2019-19844.patch of Package python-Django

commit 49acc65fd86dad138803ca3c8810010f020f5b24
Author: Simon Charette <charette.s@gmail.com>
Date:   Mon Dec 16 21:51:57 2019 -0500

    [1.11.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>
    (cherry picked from commit f4cff43bf921fcea6a29b726eb66767f67753fa2)

diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index cc3be5ba0be2..c8c115d45baf 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -17,10 +17,25 @@ from django.utils.encoding import force_bytes
 from django.utils.html import format_html, format_html_join
 from django.utils.http import urlsafe_base64_encode
 from django.utils.safestring import mark_safe
+from django.utils.six import PY3
 from django.utils.text import capfirst
 from django.utils.translation import ugettext, ugettext_lazy as _
 
 
+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).
+    """
+    normalized1 = unicodedata.normalize('NFKC', s1)
+    normalized2 = unicodedata.normalize('NFKC', s2)
+    if PY3:
+        return normalized1.casefold() == normalized2.casefold()
+    # lower() is the best alternative available on Python 2.
+    return normalized1.lower() == normalized2.lower()
+
+
 class ReadOnlyPasswordHashWidget(forms.Widget):
     def render(self, name, value, attrs):
         encoded = value
@@ -220,9 +235,16 @@ class PasswordResetForm(forms.Form):
         resetting their password.
 
         """
-        active_users = get_user_model()._default_manager.filter(
-            email__iexact=email, is_active=True)
-        return (u for u in active_users if u.has_usable_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',
@@ -234,6 +256,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)
@@ -241,8 +264,9 @@ class PasswordResetForm(forms.Form):
                 domain = current_site.domain
             else:
                 site_name = domain = domain_override
+            user_email = getattr(user, email_field_name)
             context = {
-                'email': user.email,
+                'email': user_email,
                 'domain': domain,
                 'site_name': site_name,
                 'uid': urlsafe_base64_encode(force_bytes(user.pk)),
diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
index 9528b5792c33..9c083f72b02e 100644
--- a/tests/auth_tests/test_forms.py
+++ b/tests/auth_tests/test_forms.py
@@ -385,6 +385,48 @@ class PasswordResetFormTest(TestCase):
         self.assertFalse(form.is_valid())
         self.assertEqual(form['email'].errors, [_('Enter a valid email address.')])
 
+    def test_user_email_unicode_collision(self):
+        User.objects.create_user('mike123', 'mike@example.org', 'test123')
+        User.objects.create_user('mike456', 'mıke@example.org', 'test123')
+        data = {'email': 'mıke@example.org'}
+        form = PasswordResetForm(data)
+        if six.PY2:
+            self.assertFalse(form.is_valid())
+        else:
+            self.assertTrue(form.is_valid())
+            form.save()
+            self.assertEqual(len(mail.outbox), 1)
+            self.assertEqual(mail.outbox[0].to, ['mıke@example.org'])
+
+    def test_user_email_domain_unicode_collision(self):
+        User.objects.create_user('mike123', 'mike@ixample.org', 'test123')
+        User.objects.create_user('mike456', 'mike@ıxample.org', 'test123')
+        data = {'email': 'mike@ıxample.org'}
+        form = PasswordResetForm(data)
+        self.assertTrue(form.is_valid())
+        form.save()
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(mail.outbox[0].to, ['mike@ıxample.org'])
+
+    def test_user_email_unicode_collision_nonexistent(self):
+        User.objects.create_user('mike123', 'mike@example.org', 'test123')
+        data = {'email': 'mıke@example.org'}
+        form = PasswordResetForm(data)
+        if six.PY2:
+            self.assertFalse(form.is_valid())
+        else:
+            self.assertTrue(form.is_valid())
+            form.save()
+            self.assertEqual(len(mail.outbox), 0)
+
+    def test_user_email_domain_unicode_collision_nonexistent(self):
+        User.objects.create_user('mike123', 'mike@ixample.org', 'test123')
+        data = {'email': 'mike@ıxample.org'}
+        form = PasswordResetForm(data)
+        self.assertTrue(form.is_valid())
+        form.save()
+        self.assertEqual(len(mail.outbox), 0)
+
     def test_nonexistent_email(self):
         """
         Test nonexistent email address. This should not fail because it would
openSUSE Build Service is sponsored by