Merge pull request #19051 from edx/cstenson/unicode_normalization
Add unicode normalization to passwords.
This commit is contained in:
@@ -35,6 +35,7 @@ from lms.djangoapps.verify_student.utils import is_verification_expiring_soon, v
|
||||
from openedx.core.djangoapps.certificates.api import certificates_viewable_for_course
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.theming import helpers as theming_helpers
|
||||
from openedx.core.djangoapps.user_api.config.waffle import PASSWORD_UNICODE_NORMALIZE_FLAG
|
||||
from openedx.core.djangoapps.theming.helpers import get_themes
|
||||
from openedx.core.djangoapps.user_authn.utils import is_safe_login_or_logout_redirect
|
||||
from student.models import (
|
||||
@@ -47,6 +48,7 @@ from student.models import (
|
||||
email_exists_or_retired,
|
||||
username_exists_or_retired
|
||||
)
|
||||
from util.password_policy_validators import normalize_password
|
||||
|
||||
|
||||
# Enumeration of per-course verification statuses
|
||||
@@ -412,6 +414,8 @@ def authenticate_new_user(request, username, password):
|
||||
logged in until they close the browser. They can't log in again until they click
|
||||
the activation link from the email.
|
||||
"""
|
||||
if PASSWORD_UNICODE_NORMALIZE_FLAG.is_enabled():
|
||||
password = normalize_password(password)
|
||||
backend = load_backend(NEW_USER_AUTH_BACKEND)
|
||||
user = backend.authenticate(request=request, username=username, password=password)
|
||||
user.backend = NEW_USER_AUTH_BACKEND
|
||||
@@ -610,7 +614,10 @@ def do_create_account(form, custom_form=None):
|
||||
email=form.cleaned_data["email"],
|
||||
is_active=False
|
||||
)
|
||||
user.set_password(form.cleaned_data["password"])
|
||||
password = form.cleaned_data["password"]
|
||||
if PASSWORD_UNICODE_NORMALIZE_FLAG.is_enabled():
|
||||
password = normalize_password(password)
|
||||
user.set_password(password)
|
||||
registration = Registration()
|
||||
|
||||
# TODO: Rearrange so that if part of the process fails, the whole process fails.
|
||||
|
||||
@@ -7,7 +7,6 @@ from __future__ import unicode_literals
|
||||
import logging
|
||||
import unicodedata
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.password_validation import (
|
||||
get_default_password_validators,
|
||||
validate_password as django_validate_password,
|
||||
@@ -15,6 +14,7 @@ from django.contrib.auth.password_validation import (
|
||||
)
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
from openedx.core.djangoapps.user_api.config.waffle import PASSWORD_UNICODE_NORMALIZE_FLAG
|
||||
from six import text_type
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -89,6 +89,14 @@ def password_validators_restrictions():
|
||||
return complexity_restrictions
|
||||
|
||||
|
||||
def normalize_password(password):
|
||||
"""
|
||||
Normalize all passwords to 'NFKC' across the platform to prevent mismatched hash strings when comparing entered
|
||||
passwords on login. See LEARNER-4283 for more context.
|
||||
"""
|
||||
return unicodedata.normalize('NFKC', password)
|
||||
|
||||
|
||||
def validate_password(password, user=None):
|
||||
"""
|
||||
EdX's custom password validator for passwords. This function performs the
|
||||
@@ -117,6 +125,8 @@ def validate_password(password, user=None):
|
||||
# no reason to get into weeds
|
||||
raise ValidationError([_('Invalid password.')])
|
||||
|
||||
if PASSWORD_UNICODE_NORMALIZE_FLAG.is_enabled():
|
||||
password = normalize_password(password)
|
||||
django_validate_password(password, user)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for util.password_policy_validators module."""
|
||||
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
@@ -10,13 +9,15 @@ from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from openedx.core.djangoapps.user_api.config.waffle import PASSWORD_UNICODE_NORMALIZE_FLAG
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from util.password_policy_validators import (
|
||||
create_validator_config, validate_password, password_validators_instruction_texts,
|
||||
)
|
||||
|
||||
|
||||
@ddt
|
||||
class PasswordPolicyValidatorsTestCase(unittest.TestCase):
|
||||
class PasswordPolicyValidatorsTestCase(CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for password validator utility functions
|
||||
|
||||
@@ -25,7 +26,6 @@ class PasswordPolicyValidatorsTestCase(unittest.TestCase):
|
||||
2) requiring multiple instances of the check (also checks proper plural message)
|
||||
3) successful check
|
||||
"""
|
||||
|
||||
def validation_errors_checker(self, password, msg, user=None):
|
||||
"""
|
||||
This helper function is used to check the proper error messages are
|
||||
@@ -61,6 +61,20 @@ class PasswordPolicyValidatorsTestCase(unittest.TestCase):
|
||||
# Test badly encoded password
|
||||
self.validation_errors_checker(b'\xff\xff', 'Invalid password.')
|
||||
|
||||
def test_password_unicode_normalization(self):
|
||||
""" Tests that validate_password normalizes passwords """
|
||||
# s ̣ ̇ (s with combining dot below and combining dot above)
|
||||
not_normalized_password = u'\u0073\u0323\u0307'
|
||||
self.assertEqual(len(not_normalized_password), 3)
|
||||
# When the flag is not set, the validation should succeed since len > 2
|
||||
self.validation_errors_checker(not_normalized_password, None)
|
||||
|
||||
# When we normalize we expect the not_normalized password to fail
|
||||
# because it should be normalized to u'\u1E69' -> ṩ
|
||||
with PASSWORD_UNICODE_NORMALIZE_FLAG.override(active=True):
|
||||
self.validation_errors_checker(not_normalized_password,
|
||||
'This password is too short. It must contain at least 2 characters.')
|
||||
|
||||
@data(
|
||||
([create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 2})],
|
||||
'at least 2 characters.'),
|
||||
|
||||
Reference in New Issue
Block a user