From 875a5ea8d47c2c10432572fec788a81217073a4f Mon Sep 17 00:00:00 2001
From: Jannis Leidel <jannis@leidel.info>
Date: Thu, 9 Feb 2012 18:58:53 +0000
Subject: [PATCH] Fixed #17504 -- Fixed normalization of email addresses that
 have '@' in the name when calling `User.objects.create_user`. Thanks, marw85.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17482 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/contrib/auth/models.py         | 34 ++++++++++++++++-----------
 django/contrib/auth/tests/__init__.py |  3 ++-
 django/contrib/auth/tests/models.py   | 29 ++++++++++++++++++++++-
 3 files changed, 50 insertions(+), 16 deletions(-)

diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 571bf7902c..eb39868ca9 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -128,25 +128,31 @@ class Group(models.Model):
 
 
 class UserManager(models.Manager):
+
+    @classmethod
+    def normalize_email(cls, email):
+        """
+        Normalize the address by lowercasing the domain part of the email
+        address.
+        """
+        email = email or ''
+        try:
+            email_name, domain_part = email.strip().rsplit('@', 1)
+        except ValueError:
+            pass
+        else:
+            email = '@'.join([email_name, domain_part.lower()])
+        return email
+
     def create_user(self, username, email=None, password=None):
         """
         Creates and saves a User with the given username, email and password.
         """
         now = timezone.now()
-
-        # Normalize the address by lowercasing the domain part of the email
-        # address.
-        email = email or ''
-        try:
-            email_name, domain_part = email.strip().split('@', 1)
-        except ValueError:
-            pass
-        else:
-            email = '@'.join([email_name, domain_part.lower()])
-
-        user = self.model(username=username, email=email, is_staff=False,
-                         is_active=True, is_superuser=False, last_login=now,
-                         date_joined=now)
+        email = UserManager.normalize_email(email)
+        user = self.model(username=username, email=email,
+                          is_staff=False, is_active=True, is_superuser=False,
+                          last_login=now, date_joined=now)
 
         user.set_password(password)
         user.save(using=self._db)
diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py
index e7188c0e4c..0c650d7237 100644
--- a/django/contrib/auth/tests/__init__.py
+++ b/django/contrib/auth/tests/__init__.py
@@ -11,7 +11,8 @@ from django.contrib.auth.tests.remote_user import (RemoteUserTest,
     RemoteUserNoCreateTest, RemoteUserCustomTest)
 from django.contrib.auth.tests.management import GetDefaultUsernameTestCase
 from django.contrib.auth.tests.models import (ProfileTestCase, NaturalKeysTestCase,
-    LoadDataWithoutNaturalKeysTestCase, LoadDataWithNaturalKeysTestCase)
+    LoadDataWithoutNaturalKeysTestCase, LoadDataWithNaturalKeysTestCase,
+    UserManagerTestCase)
 from django.contrib.auth.tests.hashers import TestUtilsHashPass
 from django.contrib.auth.tests.signals import SignalTestCase
 from django.contrib.auth.tests.tokens import TokenGeneratorTest
diff --git a/django/contrib/auth/tests/models.py b/django/contrib/auth/tests/models.py
index b48da4b42e..2d570aee15 100644
--- a/django/contrib/auth/tests/models.py
+++ b/django/contrib/auth/tests/models.py
@@ -1,9 +1,12 @@
 from django.conf import settings
 from django.test import TestCase
-from django.contrib.auth.models import Group, User, SiteProfileNotAvailable
+from django.contrib.auth.models import (Group, User,
+    SiteProfileNotAvailable, UserManager)
+
 
 class ProfileTestCase(TestCase):
     fixtures = ['authtestdata.json']
+
     def setUp(self):
         """Backs up the AUTH_PROFILE_MODULE"""
         self.old_AUTH_PROFILE_MODULE = getattr(settings,
@@ -59,8 +62,32 @@ class LoadDataWithoutNaturalKeysTestCase(TestCase):
 
 class LoadDataWithNaturalKeysTestCase(TestCase):
     fixtures = ['natural.json']
+
     def test_user_is_created_and_added_to_group(self):
         user = User.objects.get(username='my_username')
         group = Group.objects.get(name='my_group')
         self.assertEquals(group, user.groups.get())
 
+
+class UserManagerTestCase(TestCase):
+
+    def test_create_user(self):
+        email_lowercase = 'normal@normal.com'
+        user = User.objects.create_user('user', email_lowercase)
+        self.assertEquals(user.email, email_lowercase)
+        self.assertEquals(user.username, 'user')
+        self.assertEquals(user.password, '!')
+
+    def test_create_user_email_domain_normalize_rfc3696(self):
+        # According to  http://tools.ietf.org/html/rfc3696#section-3
+        # the "@" symbol can be part of the local part of an email address
+        returned = UserManager.normalize_email(r'Abc\@DEF@EXAMPLE.com')
+        self.assertEquals(returned, r'Abc\@DEF@example.com')
+
+    def test_create_user_email_domain_normalize(self):
+        returned = UserManager.normalize_email('normal@DOMAIN.COM')
+        self.assertEquals(returned, 'normal@domain.com')
+
+    def test_create_user_email_domain_normalize_with_whitespace(self):
+        returned = UserManager.normalize_email('email\ with_whitespace@D.COM')
+        self.assertEquals(returned, 'email\ with_whitespace@d.com')