Use unicode passwords when validating

Requiring that up front lets us properly validate length and such.
This commit is contained in:
Michael Terry
2018-03-21 12:15:52 -04:00
committed by Michael Terry
parent 25f0ac5c05
commit 1c1b8451ba
2 changed files with 35 additions and 6 deletions

View File

@@ -160,6 +160,12 @@ def validate_password(password, user=None, username=None):
user: A User model object, if available. Required to check against security policy.
username: The user-provided username, if available. Taken from 'user' if not provided.
"""
if not isinstance(password, text_type):
try:
password = text_type(password, encoding='utf8') # some checks rely on unicode semantics (e.g. length)
except UnicodeDecodeError:
raise ValidationError(_('Invalid password.')) # no reason to get into weeds
username = username or (user and user.username)
if user:
@@ -308,7 +314,7 @@ def _validate_password_dictionary(value):
if password_max_edit_distance and password_dictionary:
for word in password_dictionary:
edit_distance = distance(text_type(value), text_type(word))
edit_distance = distance(value, text_type(word))
if edit_distance <= password_max_edit_distance:
raise ValidationError(_("Password is too similar to a dictionary word."),
code="dictionary_word")

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""Tests for util.password_policy_validators module."""
import mock
@@ -8,7 +9,9 @@ from django.conf import settings
from django.core.exceptions import ValidationError
from django.test.utils import override_settings
from util.password_policy_validators import password_instructions, validate_password, _validate_password_dictionary
from util.password_policy_validators import (
password_instructions, password_min_length, validate_password, _validate_password_dictionary
)
@ddt
@@ -22,18 +25,38 @@ class PasswordPolicyValidatorsTestCase(unittest.TestCase):
""" Tests dictionary checks """
# Direct match
with self.assertRaises(ValidationError):
_validate_password_dictionary('testme')
_validate_password_dictionary(u'testme')
# Off by one
with self.assertRaises(ValidationError):
_validate_password_dictionary('estme')
_validate_password_dictionary(u'estme')
# Off by two
with self.assertRaises(ValidationError):
_validate_password_dictionary('bestmet')
_validate_password_dictionary(u'bestmet')
# Off by three (should pass)
_validate_password_dictionary('bestem')
_validate_password_dictionary(u'bestem')
def test_unicode_password(self):
""" Tests that validate_password enforces unicode """
byte_str = b'𤭮'
unicode_str = u'𤭮'
# Sanity checks and demonstration of why this test is useful
self.assertEqual(len(byte_str), 4)
self.assertEqual(len(unicode_str), 1)
self.assertEqual(password_min_length(), 2)
# Test length check
with self.assertRaises(ValidationError):
validate_password(byte_str)
validate_password(byte_str + byte_str)
# Test badly encoded password
with self.assertRaises(ValidationError) as cm:
validate_password(b'\xff\xff')
self.assertEquals('Invalid password.', cm.exception.message)
@data(
(u'', 'at least 2 characters & 2 letters & 1 number.'),