From d40ce8c8d05cd9351902cd2969974b8a1cd29373 Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Mon, 12 Mar 2018 15:11:44 -0400 Subject: [PATCH] Use Levenshtein not nltk --- .../util/password_policy_validators.py | 7 +++-- .../tests/test_password_policy_validators.py | 31 +++++++++++++++++++ requirements/edx/base.txt | 2 +- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 common/djangoapps/util/tests/test_password_policy_validators.py diff --git a/common/djangoapps/util/password_policy_validators.py b/common/djangoapps/util/password_policy_validators.py index ac787bb5db..ee6a37356e 100644 --- a/common/djangoapps/util/password_policy_validators.py +++ b/common/djangoapps/util/password_policy_validators.py @@ -10,10 +10,11 @@ from __future__ import division import string import unicodedata -from nltk.metrics.distance import edit_distance from django.conf import settings from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ +from Levenshtein import distance +from six import text_type def validate_password_strength(value): @@ -118,6 +119,6 @@ def validate_password_dictionary(value): if password_max_edit_distance and password_dictionary: for word in password_dictionary: - distance = edit_distance(value, word) - if distance <= password_max_edit_distance: + edit_distance = distance(text_type(value), text_type(word)) + if edit_distance <= password_max_edit_distance: raise ValidationError(_("Too similar to a restricted dictionary word."), code="dictionary_word") diff --git a/common/djangoapps/util/tests/test_password_policy_validators.py b/common/djangoapps/util/tests/test_password_policy_validators.py new file mode 100644 index 0000000000..8fb0b8c4ae --- /dev/null +++ b/common/djangoapps/util/tests/test_password_policy_validators.py @@ -0,0 +1,31 @@ +"""Tests for util.password_policy_validators module.""" + +import unittest + +from django.core.exceptions import ValidationError +from django.test.utils import override_settings + +from util.password_policy_validators import validate_password_dictionary + + +class PasswordPolicyValidatorsTestCase(unittest.TestCase): + """ Tests for password validator utility functions """ + + @override_settings(PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD=2) + @override_settings(PASSWORD_DICTIONARY=['testme']) + def test_validate_password_dictionary(self): + """ Tests dictionary checks """ + # Direct match + with self.assertRaises(ValidationError): + validate_password_dictionary('testme') + + # Off by one + with self.assertRaises(ValidationError): + validate_password_dictionary('estme') + + # Off by two + with self.assertRaises(ValidationError): + validate_password_dictionary('bestmet') + + # Off by three (should pass) + validate_password_dictionary('bestem') diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index c4df241654..b1013b3c2d 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -85,7 +85,6 @@ Markdown>=2.6,<2.7 mongoengine==0.10.0 MySQL-python==1.2.5 networkx==1.7 -nltk==3.2.5 nose-xunitmp==0.3.2 oauthlib==1.0.3 path.py==8.2.1 @@ -105,6 +104,7 @@ python-memcached==1.48 django-memcached-hashring==0.1.2 python-openid==2.2.5 python-dateutil==2.1 +python-Levenshtein==0.12.0 social-auth-app-django==1.2.0 social-auth-core==1.4.0 pytz==2016.7