Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #946: Make usernames case insensitive #1276

Merged
merged 1 commit into from
Mar 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions cadasta/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from allauth.account import forms as allauth_forms

from .models import User, now_plus_48_hours
from .validators import check_username_case_insensitive
from parsley.decorators import parsleyfy


Expand All @@ -23,6 +24,7 @@ class Meta:

def clean_username(self):
username = self.data.get('username')
check_username_case_insensitive(username)
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise forms.ValidationError(
_("Username cannot be “add” or “new”."))
Expand Down Expand Up @@ -76,10 +78,8 @@ def __init__(self, *args, **kwargs):

def clean_username(self):
username = self.data.get('username')
if (self.instance.username != username and
User.objects.filter(username=username).exists()):
raise forms.ValidationError(
_("Another user with this username already exists"))
if self.instance.username.casefold() != username.casefold():
check_username_case_insensitive(username)
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise forms.ValidationError(
_("Username cannot be “add” or “new”."))
Expand All @@ -102,7 +102,6 @@ def clean_email(self):

def save(self, *args, **kwargs):
user = super().save(commit=False, *args, **kwargs)

if self._send_confirmation:
send_email_confirmation(self.request, user)
self._send_confirmation = False
Expand Down
4 changes: 4 additions & 0 deletions cadasta/accounts/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from djoser import serializers as djoser_serializers

from .models import User
from .validators import check_username_case_insensitive
from .exceptions import EmailNotVerifiedError


Expand Down Expand Up @@ -35,6 +36,7 @@ class Meta:
}

def validate_username(self, username):
check_username_case_insensitive(username)
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise ValidationError(
_("Username cannot be “add” or “new”."))
Expand Down Expand Up @@ -90,6 +92,8 @@ def validate_username(self, username):
username != instance.username and
self.context['request'].user != instance):
raise ValidationError('Cannot update username')
if instance.username.casefold() != username.casefold():
check_username_case_insensitive(username)
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise ValidationError(
_("Username cannot be “add” or “new”."))
Expand Down
75 changes: 69 additions & 6 deletions cadasta/accounts/tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import random

from django.utils.translation import gettext as _
from django.test import TestCase
from core.tests.utils.cases import UserTestCase
from django.contrib.messages.storage.fallback import FallbackStorage
from django.core import mail
from django.http import HttpRequest
from django.contrib.messages.storage.fallback import FallbackStorage
from django.db import IntegrityError
from allauth.account.models import EmailAddress
from allauth.account.utils import send_email_confirmation
from django.test import TestCase
from django.utils.translation import gettext as _

from ..models import User
from .. import forms
from core.tests.utils.cases import UserTestCase

from ..models import User
from .factories import UserFactory


Expand All @@ -34,6 +33,37 @@ def test_valid_data(self):
user = User.objects.first()
assert user.check_password('iloveyoko79!') is True

def test_case_insensitive_username(self):
chars = ['', 'İ', 'É', 'Ø', 'œ', 'ß', 'ss', 'Ξ', 'ф', '大家好']
usernames = ['UsEr%s' % c for c in chars]
users = [
UserFactory.create(username=username) for username in usernames]
for user in users:
data = {
'username': user.username.lower(),
'email': '%[email protected]' % user.username,
'password1': 'iloveyoko79!',
'password2': 'iloveyoko79!',
'full_name': 'John Lennon',
}
form = forms.RegisterForm(data)
assert form.is_valid() is False
assert (_("A user with that username already exists") in
form.errors.get('username'))

user = UserFactory.create(username='JohNlEnNoN')
data = {
'username': 'johnLennon',
'email': '[email protected]',
'password1': 'iloveyoko79!',
'password2': 'iloveyoko79!',
'full_name': 'John Lennon',
}
form = forms.RegisterForm(data)
assert form.is_valid() is False
assert (_("A user with that username already exists") in
form.errors.get('username'))

def test_passwords_do_not_match(self):
data = {
'username': 'imagine71',
Expand Down Expand Up @@ -259,6 +289,39 @@ def test_update_user_with_existing_username(self):
form = forms.ProfileForm(data, instance=user)
assert form.is_valid() is False

def test_case_insensitive_username(self):
existing_user = UserFactory.create(username='TestUser')
chars = ['', 'İ', 'É', 'Ø', 'œ', 'ß', 'ss', 'Ξ', 'ф', '大家好']
usernames = ['UsEr%s' % c for c in chars]
users = [
UserFactory.create(username=username) for username in usernames]
for user in users:
data = {
'username': user.username.lower(),
'email': '%[email protected]' % user.username,
'full_name': 'John Lennon',
}
form = forms.ProfileForm(data, instance=existing_user)
assert form.is_valid() is False
assert (_("A user with that username already exists") in
form.errors.get('username'))
existing_user.refresh_from_db()
assert existing_user.username == 'TestUser'

user = UserFactory.create(
username='JohNlEnNoN', email='[email protected]')
data = {
'username': 'johnLennon',
'email': '[email protected]',
'full_name': 'John Lennon',
}
form = forms.ProfileForm(data, instance=user)
assert form.is_valid() is True
form.save()

user.refresh_from_db()
assert user.username == 'johnLennon'

def test_update_user_with_existing_email(self):
UserFactory.create(email='[email protected]')
user = UserFactory.create(username='imagine71',
Expand Down
38 changes: 38 additions & 0 deletions cadasta/accounts/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ def test_create_with_valid_data(self):
assert user_obj.is_active
assert not user_obj.email_verified

def test_case_insensitive_username(self):
usernames = ['UsErOne', 'useRtWo', 'uSERthReE']
users = [
UserFactory.create(username=username) for username in usernames]
for user in users:
TEST_DATA = BASIC_TEST_DATA.copy()
TEST_DATA['username'] = user.username.lower()
serializer = serializers.RegistrationSerializer(data=TEST_DATA)
assert serializer.is_valid() is False
assert (_("A user with that username already exists")
in serializer._errors['username'])
TEST_DATA = BASIC_TEST_DATA.copy()
TEST_DATA['username'] = 'userFour'
serializer = serializers.RegistrationSerializer(data=TEST_DATA)
assert serializer.is_valid()
serializer.save()
assert User.objects.count() == 4

def test_create_without_email(self):
"""Serialiser should be invalid when no email address is provided."""

Expand Down Expand Up @@ -221,6 +239,26 @@ def test_update_username_fails(self):
assert not serializer2.is_valid()
assert serializer2.errors['username'] == ['Cannot update username']

def test_case_insensitive_username(self):
data = {
'username': 'USERtwO',
'email': '[email protected]',
'password': 'iloveyoko79',
'full_name': 'John Lennon'
}
usernames = ['UsErOne', 'useRtWo', 'uSERthReE']
users = [
UserFactory.create(username=username) for username in usernames]
request = APIRequestFactory().patch(
'/user/userone', data)
force_authenticate(request, user=users[0])
serializer = serializers.UserSerializer(
users[0], data, context={'request': Request(request)})
assert serializer.is_valid() is False
assert serializer.errors['username'] == [
_("A user with that username already exists")
]

def test_update_last_login_fails(self):
serializer = serializers.UserSerializer(data=BASIC_TEST_DATA)
assert serializer.is_valid()
Expand Down
12 changes: 12 additions & 0 deletions cadasta/accounts/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _

from .models import User

DEFAULT_CHARACTER_TYPES = [
string.ascii_lowercase,
Expand Down Expand Up @@ -47,3 +48,14 @@ def validate(self, password, user=None):
if len(email[0]) and email[0].casefold() in password.casefold():
raise ValidationError(
_("Passwords cannot contain your email."))


def check_username_case_insensitive(username):
usernames = [
u.casefold() for u in
User.objects.values_list('username', flat=True)
]
if username.casefold() in usernames:
raise ValidationError(
_("A user with that username already exists")
)