Skip to content

Commit 4015354

Browse files
committed
Username and email checking is now case insensitive for signup, login and password reset forms. More unit test cases added for accounts to cover these cases. Fixes stephenmcd#2066
1 parent a89ef45 commit 4015354

File tree

3 files changed

+159
-5
lines changed

3 files changed

+159
-5
lines changed

mezzanine/accounts/forms.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def clean_email(self):
181181
Ensure the email address is not already registered.
182182
"""
183183
email = self.cleaned_data.get("email")
184-
qs = User.objects.exclude(id=self.instance.id).filter(email=email)
184+
qs = User.objects.exclude(id=self.instance.id).filter(email__iexact=email)
185185
if len(qs) == 0:
186186
return email
187187
raise forms.ValidationError(gettext("This email is already registered"))
@@ -261,7 +261,7 @@ class PasswordResetForm(Html5Mixin, forms.Form):
261261

262262
def clean(self):
263263
username = self.cleaned_data.get("username")
264-
username_or_email = Q(username=username) | Q(email=username)
264+
username_or_email = Q(username__iexact=username) | Q(email__iexact=username)
265265
try:
266266
user = User.objects.get(username_or_email, is_active=True)
267267
except User.DoesNotExist:

mezzanine/core/auth_backends.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class MezzanineBackend(ModelBackend):
1515
Args are either ``username`` and ``password``, or ``uidb36``
1616
and ``token``. In either case, ``is_active`` can also be given.
1717
18+
Usernames and Email addresses are not case sensitive
19+
1820
For login, is_active is not given, so that the login form can
1921
raise a specific error for inactive users.
2022
For password reset, True is given for is_active.
@@ -25,7 +27,7 @@ def authenticate(self, *args, **kwargs):
2527
if kwargs:
2628
username = kwargs.pop("username", None)
2729
if username:
28-
username_or_email = Q(username=username) | Q(email=username)
30+
username_or_email = Q(username__iexact=username) | Q(email__iexact=username)
2931
password = kwargs.pop("password", None)
3032
try:
3133
user = User.objects.get(username_or_email, **kwargs)

tests/test_accounts.py

+154-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
from django.contrib.auth import get_user_model
1+
from django.contrib.auth import get_user, get_user_model
22
from django.contrib.auth.tokens import default_token_generator
33
from django.core import mail
44
from django.forms.fields import DateField, DateTimeField
55
from django.urls import reverse
66
from django.utils.http import int_to_base36
77

88
from mezzanine.accounts import ProfileNotConfigured
9-
from mezzanine.accounts.forms import ProfileForm
9+
from mezzanine.accounts.forms import ProfileForm, PasswordResetForm
1010
from mezzanine.conf import settings
1111
from mezzanine.utils.tests import TestCase
1212

@@ -79,3 +79,155 @@ def test_account(self):
7979
self.assertEqual(response.status_code, 200)
8080
users = User.objects.filter(email=data["email"], is_active=True)
8181
self.assertEqual(len(users), 1)
82+
83+
self.client.logout()
84+
85+
# Create another account with the same user name
86+
settings.ACCOUNTS_VERIFICATION_REQUIRED = False
87+
data = self.account_data("test1")
88+
form = ProfileForm(data=data)
89+
self.assertFormError(form, 'username', 'This username is already registered')
90+
91+
# Create another account with the same user name, but case is different
92+
data['username'] = 'TEST1'
93+
form = ProfileForm(data=data)
94+
self.assertFormError(form, 'username', 'This username is already registered')
95+
96+
# Create another account with a different username, but same email
97+
data['username'] = 'test3'
98+
form = ProfileForm(data=data)
99+
self.assertFormError(form, 'email', 'This email is already registered')
100+
101+
# Create another account with a different username, but same email with different case
102+
data['email'] = '[email protected]'
103+
form = ProfileForm(data=data)
104+
self.assertFormError(form, 'email', 'This email is already registered')
105+
106+
107+
def test_account_login(self):
108+
"""
109+
Test account login
110+
"""
111+
# Create test user account
112+
data = self.account_data("test1")
113+
settings.ACCOUNTS_VERIFICATION_REQUIRED = False
114+
response = self.client.post(reverse("signup"), data, follow=True)
115+
self.assertEqual(response.status_code, 200)
116+
# Find the valid user
117+
users = User.objects.filter(email=data["email"], is_active=True)
118+
self.assertEqual(len(users), 1)
119+
test_user = users[0]
120+
121+
self.client.logout()
122+
123+
# Log in with username/password
124+
self.assertTrue(self.client.login(username=data['username'],
125+
password=data['password1']))
126+
user = get_user(self.client)
127+
self.assertEqual(user, test_user)
128+
self.assertTrue(user.is_authenticated)
129+
self.client.logout()
130+
131+
# Log in with email/password
132+
self.assertTrue(self.client.login(username=data['email'],
133+
password=data['password1']))
134+
user = get_user(self.client)
135+
self.assertEqual(user, test_user)
136+
self.assertTrue(user.is_authenticated)
137+
self.client.logout()
138+
139+
# Log in with bad password
140+
self.assertFalse(self.client.login(username=data['username'],
141+
password=data['password1'] + 'badbit'))
142+
user = get_user(self.client)
143+
self.assertFalse(user.is_authenticated)
144+
self.client.logout()
145+
146+
# Log in with username (different case) and password
147+
self.assertTrue(self.client.login(username=data['username'].upper(),
148+
password=data['password1']))
149+
user = get_user(self.client)
150+
self.assertEqual(user, test_user)
151+
self.assertTrue(user.is_authenticated)
152+
self.client.logout()
153+
154+
# Log in with email (different case) and password
155+
self.assertTrue(self.client.login(username=data['email'].upper(),
156+
password=data['password1']))
157+
user = get_user(self.client)
158+
self.assertEqual(user, test_user)
159+
self.assertTrue(user.is_authenticated)
160+
self.client.logout()
161+
162+
def _verify_password_reset_email(self, new_user, num_emails):
163+
# Check email was sent
164+
self.assertEqual(len(mail.outbox), num_emails + 1)
165+
self.assertEqual(len(mail.outbox[0].to), 1)
166+
self.assertEqual(mail.outbox[0].to[0], new_user.email)
167+
verification_url = reverse(
168+
"password_reset_verify",
169+
kwargs={
170+
"uidb36": int_to_base36(new_user.id),
171+
"token": default_token_generator.make_token(new_user),
172+
},
173+
)
174+
response = self.client.get(verification_url, follow=True)
175+
self.assertEqual(response.status_code, 200)
176+
177+
178+
def test_account_password_reset(self):
179+
"""
180+
Test account password reset verification email
181+
"""
182+
# Create test user account
183+
data = self.account_data("test1")
184+
settings.ACCOUNTS_VERIFICATION_REQUIRED = False
185+
response = self.client.post(reverse("signup"), data, follow=True)
186+
self.assertEqual(response.status_code, 200)
187+
# Find the valid user
188+
users = User.objects.filter(email=data["email"], is_active=True)
189+
self.assertEqual(len(users), 1)
190+
new_user = users[0]
191+
self.client.logout()
192+
193+
# Reset password with username
194+
emails = len(mail.outbox)
195+
rdata = {'username': data['username']}
196+
response = self.client.post(reverse("mezzanine_password_reset"), rdata, follow=True)
197+
self.assertEqual(response.status_code, 200)
198+
self._verify_password_reset_email(new_user, emails)
199+
self.client.logout()
200+
201+
# Reset password with email
202+
emails = len(mail.outbox)
203+
rdata = {'username': data['email']}
204+
response = self.client.post(reverse("mezzanine_password_reset"), rdata, follow=True)
205+
self.assertEqual(response.status_code, 200)
206+
self._verify_password_reset_email(new_user, emails)
207+
self.client.logout()
208+
209+
# Reset password with username (different case)
210+
emails = len(mail.outbox)
211+
rdata = {'username': data['username'].upper()}
212+
response = self.client.post(reverse("mezzanine_password_reset"), rdata, follow=True)
213+
self.assertEqual(response.status_code, 200)
214+
self._verify_password_reset_email(new_user, emails)
215+
self.client.logout()
216+
217+
# Reset password with email (different case)
218+
emails = len(mail.outbox)
219+
rdata = {'username': data['email'].upper()}
220+
response = self.client.post(reverse("mezzanine_password_reset"), rdata, follow=True)
221+
self.assertEqual(response.status_code, 200)
222+
self._verify_password_reset_email(new_user, emails)
223+
self.client.logout()
224+
225+
# Reset password with invalid username
226+
rdata = {'username': 'badusername'}
227+
form = PasswordResetForm(data=rdata)
228+
self.assertFormError(form, None, 'Invalid username/email')
229+
230+
# Reset password with invalid email
231+
rdata = {'username': '[email protected]'}
232+
form = PasswordResetForm(data=rdata)
233+
self.assertFormError(form, None, 'Invalid username/email')

0 commit comments

Comments
 (0)