Skip to content

Commit

Permalink
Fixes #1045: Case Insensitive username and password comparison (#1149)
Browse files Browse the repository at this point in the history
  • Loading branch information
valaparthvi authored and oliverroick committed Mar 17, 2017
1 parent 8b7d1c3 commit 5c05510
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 30 deletions.
11 changes: 8 additions & 3 deletions cadasta/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ def clean_password1(self):
if password != self.data.get('password2'):
raise forms.ValidationError(_("Passwords do not match"))

email = self.data.get('email').lower().split('@')
if len(email[0]) and email[0] in password:
email = self.data.get('email').split('@')
if len(email[0]) and email[0].casefold() in password.casefold():
errors.append(_("Passwords cannot contain your email."))

username = self.data.get('username')
if len(username) and username in password:
if len(username) and username.casefold() in password.casefold():
errors.append(
_("The password is too similar to the username."))

Expand Down Expand Up @@ -122,6 +122,11 @@ def clean_password1(self):
password = self.cleaned_data['password1']
validate_password(password, user=self.user)

username = self.user.username
if len(username) and username.casefold() in password.casefold():
raise forms.ValidationError(
_("The password is too similar to the username."))

return password


Expand Down
24 changes: 18 additions & 6 deletions cadasta/accounts/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ def validate_password(self, password):

errors = []
if self.initial_data.get('email'):
email = self.initial_data.get('email').lower().split('@')
if len(email[0]) and email[0] in password:
email = self.initial_data.get('email').split('@')
if len(email[0]) and email[0].casefold() in password.casefold():
errors.append(_("Passwords cannot contain your email."))

username = self.initial_data.get('username')
if len(username) and username in password:
if len(username) and username.casefold() in password.casefold():
errors.append(
_("The password is too similar to the username."))

Expand Down Expand Up @@ -87,8 +87,8 @@ def validate_username(self, username):
instance = self.instance
if instance is not None:
if (username is not None and
username != instance.username and
self.context['request'].user != instance):
username != instance.username and
self.context['request'].user != instance):
raise ValidationError('Cannot update username')
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise ValidationError(
Expand All @@ -99,7 +99,7 @@ def validate_last_login(self, last_login):
instance = self.instance
if instance is not None:
if (last_login is not None and
last_login != instance.last_login):
last_login != instance.last_login):
raise ValidationError('Cannot update last_login')
return last_login

Expand All @@ -117,7 +117,19 @@ def validate(self, attrs):

class ChangePasswordSerializer(djoser_serializers.SetPasswordRetypeSerializer):
def validate(self, attrs):

if not self.context['request'].user.change_pw:
raise ValidationError(_("The password for this user can not "
"be changed."))
return super().validate(attrs)

def validate_new_password(self, password):
user = self.context['request'].user
validate_password(password, user=user)

username = user.username
if len(username) and username.casefold() in password.casefold():
raise ValidationError(
_("The password is too similar to the username."))

return password
64 changes: 54 additions & 10 deletions cadasta/accounts/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ def test_password_contains_username(self):
form.errors.get('password1'))
assert User.objects.count() == 0

def test_password_contains_username_case_insensitive(self):
data = {
'username': 'imagine71',
'email': '[email protected]',
'password1': 'LetsIMAGINE71things?',
'password2': 'LetsIMAGINE71things?',
'full_name': 'John Lennon',
}
form = forms.RegisterForm(data)

assert form.is_valid() is False
assert (_("The password is too similar to the username.") in
form.errors.get('password1'))
assert User.objects.count() == 0

def test_password_contains_blank_username(self):
data = {
'username': '',
Expand All @@ -81,8 +96,8 @@ def test_password_contains_email(self):
data = {
'username': 'imagine71',
'email': '[email protected]',
'password1': 'Isjohnreallythebest34?',
'password2': 'Isjohnreallythebest34?',
'password1': 'IsJOHNreallythebest34?',
'password2': 'IsJOHNreallythebest34?',
'full_name': 'John Lennon',
}
form = forms.RegisterForm(data)
Expand Down Expand Up @@ -338,8 +353,23 @@ def test_password_contains_username(self):

data = {
'oldpassword': 'beatles4Lyfe!',
'password1': 'Letsimagine71?',
'password2': 'Letsimagine71?',
'password1': 'Letsimagine71?1234567890',
'password2': 'Letsimagine71?1234567890',
}
form = forms.ChangePasswordForm(user, data)

assert form.is_valid() is False
assert (_("The password is too similar to the username.") in
form.errors.get('password1'))

def test_password_contains_username_case_insensitive(self):
user = UserFactory.create(
password='beatles4Lyfe!', username='imagine71')

data = {
'oldpassword': 'beatles4Lyfe!',
'password1': 'LetsIMAGINE71?1234567890',
'password2': 'LetsIMAGINE71?1234567890',
}
form = forms.ChangePasswordForm(user, data)

Expand All @@ -353,8 +383,8 @@ def test_password_contains_email(self):

data = {
'oldpassword': 'beatles4Lyfe!',
'password1': 'Isjohnreallythebest34?',
'password2': 'Isjohnreallythebest34?',
'password1': 'IsJOHNreallythebest34?',
'password2': 'IsJOHNreallythebest34?',
}
form = forms.ChangePasswordForm(user, data)

Expand Down Expand Up @@ -444,8 +474,22 @@ def test_password_contains_username(self):
password='beatles4Lyfe!', username='imagine71')

data = {
'password1': 'Letsimagine71?',
'password2': 'Letsimagine71?',
'password1': 'Letsimagine71?1234567890',
'password2': 'Letsimagine71?1234567890',
}
form = forms.ResetPasswordKeyForm(data, user=user)

assert form.is_valid() is False
assert (_("The password is too similar to the username.") in
form.errors.get('password1'))

def test_password_contains_username_case_insensitive(self):
user = UserFactory.create(
password='beatles4Lyfe!', username='imagine71')

data = {
'password1': 'LetsIMAGINE71?1234567890',
'password2': 'LetsIMAGINE71?1234567890',
}
form = forms.ResetPasswordKeyForm(data, user=user)

Expand All @@ -458,8 +502,8 @@ def test_password_contains_email(self):
password='beatles4Lyfe!', email='[email protected]')

data = {
'password1': 'Isjohnreallythebest34?',
'password2': 'Isjohnreallythebest34?',
'password1': 'IsJOHNreallythebest34?',
'password2': 'IsJOHNreallythebest34?',
}
form = forms.ResetPasswordKeyForm(data, user=user)

Expand Down
119 changes: 119 additions & 0 deletions cadasta/accounts/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ def test_password_contains_username(self):
assert (_("The password is too similar to the username.") in
serializer._errors['password'])

def test_password_contains_username_case_insensitive(self):
data = {
'username': 'yoko79',
'email': '[email protected]',
'password': 'iloveYOKO79!',
'password_repeat': 'iloveYOKO79!',
'full_name': 'John Lennon',
}
serializer = serializers.RegistrationSerializer(data=data)
assert not serializer.is_valid()
assert (_("The password is too similar to the username.") in
serializer._errors['password'])

def test_password_contains_blank_username(self):
data = {
'username': '',
Expand Down Expand Up @@ -287,3 +300,109 @@ def test_user_can_not_change_pw(self):
assert serializer.is_valid() is False
assert ("The password for this user can not be changed."
in serializer.errors['non_field_errors'])

def test_password_contains_username(self):
user = UserFactory.create(
password=BASIC_TEST_DATA['password'],
username='imagine71',
)
request = APIRequestFactory().patch('/user/imagine71', {})
force_authenticate(request, user=user)
data = {
'username': 'imagine71',
'current_password': BASIC_TEST_DATA['password'],
'new_password': 'Letsimagine71!12345',
're_new_password': 'Letsimagine71!12345',
}
serializer = serializers.ChangePasswordSerializer(
user, data=data, context={'request': Request(request)})

assert serializer.is_valid() is False
assert (_("The password is too similar to the username.")
in serializer._errors['new_password'])

def test_password_contains_username_case_insensitive(self):
user = UserFactory.create(
password=BASIC_TEST_DATA['password'],
username='imagine71',
)
request = APIRequestFactory().patch('/user/imagine71', {})
force_authenticate(request, user=user)
data = {
'username': 'imagine71',
'current_password': BASIC_TEST_DATA['password'],
'new_password': 'LetsIMAGINE71!12345',
're_new_password': 'LetsIMAGINE71!12345',
}
serializer = serializers.ChangePasswordSerializer(
user, data=data, context={'request': Request(request)})

assert serializer.is_valid() is False
assert (_("The password is too similar to the username.")
in serializer._errors['new_password'])

def test_password_contains_email(self):
user = UserFactory.create(
password=BASIC_TEST_DATA['password'],
email=BASIC_TEST_DATA['email'],
username='imagine71!',
)
request = APIRequestFactory().patch('/user/imagine71', {})
force_authenticate(request, user=user)
data = {
'current_password': BASIC_TEST_DATA['password'],
'new_password': 'JOHNisjustheBest!!',
're_new_password': 'JOHNisjustheBest!!',
}

serializer = serializers.ChangePasswordSerializer(
user, data=data, context={'request': Request(request)})
assert serializer.is_valid() is False
assert (_("Passwords cannot contain your email.")
in serializer._errors['new_password'])

def test_password_contains_less_than_min_characters(self):
user = UserFactory.create(
username='imagine71',
password=BASIC_TEST_DATA['password'],
)
request = APIRequestFactory().patch('/user/imagine71', {})
force_authenticate(request, user=user)

data = {
'current_password': BASIC_TEST_DATA['password'],
'new_password': 'yoko<3',
're_new_password': 'yoko<3',
}

serializer = serializers.ChangePasswordSerializer(
user, data=data, context={'request': Request(request)})

assert serializer.is_valid() is False
assert (_("This password is too short."
" It must contain at least 10 characters.")
in serializer._errors['new_password'])

def test_password_does_not_meet_unique_character_requirements(self):
user = UserFactory.create(
username='imagine71',
password=BASIC_TEST_DATA['password'],
)
request = APIRequestFactory().patch('/user/imagine71', {})
force_authenticate(request, user=user)

data = {
'current_password': BASIC_TEST_DATA['password'],
'new_password': 'iloveyoko',
're_new_password': 'iloveyoko',
}

serializer = serializers.ChangePasswordSerializer(
user, data=data, context={'request': Request(request)})

assert serializer.is_valid() is False
assert (_("Passwords must contain at least 3"
" of the following 4 character types:\n"
"lowercase characters, uppercase characters,"
" special characters, and/or numerical character.\n"
) in serializer._errors['new_password'])
14 changes: 7 additions & 7 deletions cadasta/accounts/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@


DEFAULT_CHARACTER_TYPES = [
string.ascii_lowercase,
string.ascii_uppercase,
string.punctuation,
string.digits
]
string.ascii_lowercase,
string.ascii_uppercase,
string.punctuation,
string.digits
]


class CharacterTypePasswordValidator(object):
Expand Down Expand Up @@ -43,7 +43,7 @@ def validate(self, password, user=None):
if not user:
return None

email = user.email.lower().split('@')
if len(email[0]) and email[0] in password:
email = user.email.split('@')
if len(email[0]) and email[0].casefold() in password.casefold():
raise ValidationError(
_("Passwords cannot contain your email."))
7 changes: 4 additions & 3 deletions cadasta/organization/importers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ def get_attribute_map(self, type, entity_types, flatten=False):

# build list of attribute names not selected for import
exclude_attrs = list(set([
name.lower() for content_type, selectors in project_attrs.items()
name.lower() for content_type, selectors
in project_attrs.items()
for selector, attrs in selectors.items()
for name, attr in attrs.items()
if content_type not in selected_content_types
Expand Down Expand Up @@ -284,8 +285,8 @@ def _map_attrs_to_content_types(self, headers, row, content_types,
if attribute.attr_type.name == 'select_multiple':
val = [v.strip() for v in val.split(',')]
if attribute.attr_type.name in ['integer', 'decimal']:
val = self._cast_to_type(
val, attribute.attr_type.name)
val = self._cast_to_type(
val, attribute.attr_type.name)
if content_type:
content_type['attributes'][attribute.name] = val
return content_types
Expand Down
3 changes: 2 additions & 1 deletion cadasta/organization/importers/xls.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def get_csv_from_dataframe(df, entity_types):
if not (locations.empty or
relationships.empty or parties.empty):
locations.rename(
columns=lambda x: 'spatialunit::' + x.lower(), inplace=True
columns=lambda x: 'spatialunit::' + x.lower(),
inplace=True
)
relationships.rename(
columns={'tenure_type.id': 'tenure_type'},
Expand Down

0 comments on commit 5c05510

Please sign in to comment.