Cleaning up unicode waffle flag

This commit is contained in:
Dillon Dumesnil
2018-10-15 09:55:33 -04:00
parent 0fe187dfac
commit 0a0df72fc9
14 changed files with 53 additions and 101 deletions

View File

@@ -35,7 +35,6 @@ 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 (
@@ -414,8 +413,7 @@ 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)
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
@@ -614,9 +612,7 @@ def do_create_account(form, custom_form=None):
email=form.cleaned_data["email"],
is_active=False
)
password = form.cleaned_data["password"]
if PASSWORD_UNICODE_NORMALIZE_FLAG.is_enabled():
password = normalize_password(password)
password = normalize_password(form.cleaned_data["password"])
user.set_password(password)
registration = Registration()

View File

@@ -144,7 +144,8 @@ class TestPasswordPolicy(TestCase):
create_validator_config('util.password_policy_validators.NumericValidator', {'min_numeric': 3})
])
def test_not_enough_numeric_characters(self):
self.url_params['password'] = u'thishouldfail½2'
# The unicode ២ is the number 2 in Khmer and the ٧ is the Arabic-Indic number 7
self.url_params['password'] = u'thisShouldFail២٧'
response = self.client.post(self.url, self.url_params)
self.assertEqual(response.status_code, 400)
obj = json.loads(response.content)
@@ -157,8 +158,8 @@ class TestPasswordPolicy(TestCase):
create_validator_config('util.password_policy_validators.NumericValidator', {'min_numeric': 3})
])
def test_enough_numeric_characters(self):
# This unicode 1/2 should count as a numeric value here
self.url_params['password'] = u'thisShouldPass½33'
# The unicode ២ is the number 2 in Khmer
self.url_params['password'] = u'thisShouldPass33'
response = self.client.post(self.url, self.url_params)
self.assertEqual(response.status_code, 200)
obj = json.loads(response.content)
@@ -260,7 +261,7 @@ class TestPasswordPolicy(TestCase):
"""
Tests that even if password policy is enforced, ext_auth registrations aren't subject to it
"""
self.url_params['password'] = 'aaa' # shouldn't pass validation
self.url_params['password'] = u'aaa' # shouldn't pass validation
request = self.request_factory.post(self.url, self.url_params)
request.site = SiteFactory.create()
# now indicate we are doing ext_auth by setting 'ExternalAuthMap' in the session.

View File

@@ -25,9 +25,7 @@ from provider.oauth2 import models as dop_models
from openedx.core.djangoapps.oauth_dispatch.tests import factories as dot_factories
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_api.models import UserRetirementRequest
from openedx.core.djangoapps.user_api.config.waffle import (
PASSWORD_UNICODE_NORMALIZE_FLAG, PREVENT_AUTH_USER_WRITES, SYSTEM_MAINTENANCE_MSG, waffle
)
from openedx.core.djangoapps.user_api.config.waffle import PREVENT_AUTH_USER_WRITES, SYSTEM_MAINTENANCE_MSG, waffle
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from student.tests.factories import UserFactory
from student.tests.test_email import mock_render_to_string
@@ -360,21 +358,20 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
method of NFKC.
In this test, the input password is u'p\u212bssword'. It should be normalized to u'p\xc5ssword'
"""
with PASSWORD_UNICODE_NORMALIZE_FLAG.override(active=True):
url = reverse(
"password_reset_confirm",
kwargs={"uidb36": self.uidb36, "token": self.token}
)
url = reverse(
"password_reset_confirm",
kwargs={"uidb36": self.uidb36, "token": self.token}
)
password = u'p\u212bssword'
request_params = {'new_password1': password, 'new_password2': password}
confirm_request = self.request_factory.post(url, data=request_params)
response = password_reset_confirm_wrapper(confirm_request, self.uidb36, self.token)
password = u'p\u212bssword'
request_params = {'new_password1': password, 'new_password2': password}
confirm_request = self.request_factory.post(url, data=request_params)
response = password_reset_confirm_wrapper(confirm_request, self.uidb36, self.token)
user = User.objects.get(pk=self.user.pk)
salt_val = user.password.split('$')[1]
expected_user_password = make_password(unicodedata.normalize('NFKC', u'p\u212bssword'), salt_val)
self.assertEqual(expected_user_password, user.password)
user = User.objects.get(pk=self.user.pk)
salt_val = user.password.split('$')[1]
expected_user_password = make_password(unicodedata.normalize('NFKC', u'p\u212bssword'), salt_val)
self.assertEqual(expected_user_password, user.password)
@override_settings(AUTH_PASSWORD_VALIDATORS=[
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 2}),

View File

@@ -57,9 +57,7 @@ from openedx.core.djangoapps.programs.models import ProgramsApiConfig
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.theming.helpers import get_current_site
from openedx.core.djangoapps.user_api.config.waffle import (
PASSWORD_UNICODE_NORMALIZE_FLAG, PREVENT_AUTH_USER_WRITES, SYSTEM_MAINTENANCE_MSG, waffle
)
from openedx.core.djangoapps.user_api.config.waffle import PREVENT_AUTH_USER_WRITES, SYSTEM_MAINTENANCE_MSG, waffle
from openedx.core.djangoapps.user_api.errors import UserNotFound, UserAPIInternalError
from openedx.core.djangoapps.user_api.models import UserRetirementRequest
from openedx.core.djangoapps.user_api.preferences import api as preferences_api
@@ -829,16 +827,15 @@ def password_reset_confirm_wrapper(request, uidb36=None, token=None):
)
if request.method == 'POST':
if PASSWORD_UNICODE_NORMALIZE_FLAG.is_enabled():
# We have to make a copy of request.POST because it is a QueryDict object which is immutable until copied.
# We have to use request.POST because the password_reset_confirm method takes in the request and a user's
# password is set to the request.POST['new_password1'] field. We have to also normalize the new_password2
# field so it passes the equivalence check that new_password1 == new_password2
# In order to switch out of having to do this copy, we would want to move the normalize_password code into
# a custom User model's set_password method to ensure it is always happening upon calling set_password.
request.POST = request.POST.copy()
request.POST['new_password1'] = normalize_password(request.POST['new_password1'])
request.POST['new_password2'] = normalize_password(request.POST['new_password2'])
# We have to make a copy of request.POST because it is a QueryDict object which is immutable until copied.
# We have to use request.POST because the password_reset_confirm method takes in the request and a user's
# password is set to the request.POST['new_password1'] field. We have to also normalize the new_password2
# field so it passes the equivalence check that new_password1 == new_password2
# In order to switch out of having to do this copy, we would want to move the normalize_password code into
# a custom User model's set_password method to ensure it is always happening upon calling set_password.
request.POST = request.POST.copy()
request.POST['new_password1'] = normalize_password(request.POST['new_password1'])
request.POST['new_password2'] = normalize_password(request.POST['new_password2'])
password = request.POST['new_password1']

View File

@@ -759,11 +759,11 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
def test_first_party_auth_trumps_third_party_auth_and_fails_when_credentials_bad(self):
self.assert_first_party_auth_trumps_third_party_auth(
email='user@example.com', password='password', success=False)
email='user@example.com', password=u'password', success=False)
def test_first_party_auth_trumps_third_party_auth_and_succeeds_when_credentials_good(self):
self.assert_first_party_auth_trumps_third_party_auth(
email='user@example.com', password='password', success=True)
email='user@example.com', password=u'password', success=True)
def test_full_pipeline_succeeds_registering_new_account(self):
# First, create, the request and strategy that store pipeline state.

View File

@@ -14,7 +14,6 @@ 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__)
@@ -124,16 +123,7 @@ def validate_password(password, user=None):
Raises:
ValidationError if any of the password validators fail.
"""
if not isinstance(password, text_type):
try:
# some checks rely on unicode semantics (e.g. length)
password = text_type(password, encoding='utf8')
except UnicodeDecodeError:
# no reason to get into weeds
raise ValidationError([_('Invalid password.')])
if PASSWORD_UNICODE_NORMALIZE_FLAG.is_enabled():
password = normalize_password(password)
password = normalize_password(password)
django_validate_password(password, user)

View File

@@ -9,15 +9,13 @@ 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(CacheIsolationTestCase):
class PasswordPolicyValidatorsTestCase(unittest.TestCase):
"""
Tests for password validator utility functions
@@ -66,14 +64,11 @@ class PasswordPolicyValidatorsTestCase(CacheIsolationTestCase):
# 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.')
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})],

View File

@@ -23,7 +23,6 @@ from util.password_policy_validators import validate_password, normalize_passwor
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_api import errors, accounts, forms, helpers
from openedx.core.djangoapps.user_api.config.waffle import (
PASSWORD_UNICODE_NORMALIZE_FLAG,
PREVENT_AUTH_USER_WRITES,
SYSTEM_MAINTENANCE_MSG,
waffle,
@@ -336,8 +335,7 @@ def create_account(username, password, email):
# Create the user account, setting them to "inactive" until they activate their account.
user = User(username=username, email=email, is_active=False)
if PASSWORD_UNICODE_NORMALIZE_FLAG.is_enabled():
password = normalize_password(password)
password = normalize_password(password)
user.set_password(password)
try:

View File

@@ -41,7 +41,6 @@ from openedx.core.djangoapps.user_api.accounts.tests.testutils import (
VALID_USERNAMES_UNICODE
)
from openedx.core.djangoapps.user_api.config.waffle import (
PASSWORD_UNICODE_NORMALIZE_FLAG,
PREVENT_AUTH_USER_WRITES,
SYSTEM_MAINTENANCE_MSG,
waffle
@@ -361,7 +360,6 @@ class AccountSettingsOnCreationTest(TestCase):
'extended_profile': [],
})
@override_waffle_flag(PASSWORD_UNICODE_NORMALIZE_FLAG, active=True)
def test_normalize_password(self):
"""
Test that unicode normalization on passwords is happening when a user is created.

View File

@@ -5,12 +5,10 @@ from __future__ import absolute_import
from django.utils.translation import ugettext_lazy as _
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace, WaffleFlagNamespace, WaffleFlag
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
SYSTEM_MAINTENANCE_MSG = _(u'System maintenance in progress. Please try again later.')
WAFFLE_NAMESPACE = u'user_api'
_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(WAFFLE_NAMESPACE)
PASSWORD_UNICODE_NORMALIZE_FLAG = WaffleFlag(_WAFFLE_FLAG_NAMESPACE, u'password_unicode_normalize')
# Switches
PREVENT_AUTH_USER_WRITES = u'prevent_auth_user_writes'

View File

@@ -27,7 +27,6 @@ from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
from openedx.core.djangoapps.password_policy import compliance as password_policy_compliance
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
from openedx.core.djangoapps.user_api.config.waffle import PASSWORD_UNICODE_NORMALIZE_FLAG
from student.models import (
LoginFailures,
PasswordHistory,
@@ -213,9 +212,7 @@ def _authenticate_first_party(request, unauthenticated_user):
username = unauthenticated_user.username if unauthenticated_user else ""
try:
password = request.POST['password']
if PASSWORD_UNICODE_NORMALIZE_FLAG.is_enabled():
password = normalize_password(password)
password = normalize_password(request.POST['password'])
return authenticate(
username=username,
password=password,

View File

@@ -23,11 +23,7 @@ from openedx.core.djangoapps.password_policy.compliance import (
NonCompliantPasswordException,
NonCompliantPasswordWarning
)
from openedx.core.djangoapps.user_api.config.waffle import (
PASSWORD_UNICODE_NORMALIZE_FLAG,
PREVENT_AUTH_USER_WRITES,
waffle
)
from openedx.core.djangoapps.user_api.config.waffle import PREVENT_AUTH_USER_WRITES, waffle
from openedx.core.djangoapps.user_authn.cookies import jwt_cookies
from openedx.core.djangoapps.user_authn.tests.utils import setup_login_oauth_client
from openedx.core.djangoapps.user_authn.waffle import JWT_COOKIES_FLAG
@@ -491,31 +487,23 @@ class LoginTest(CacheIsolationTestCase):
self.assertTrue(response_content.get('success'))
@ddt.data(
('test_password', 'test_password', True, True),
('test_password', 'test_password', True),
(unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'),
unicodedata.normalize('NFKC', u'Ṗŕệṿïệẅ Ṯệẍt'), False, True),
unicodedata.normalize('NFKC', u'Ṗŕệṿïệẅ Ṯệẍt'), False),
(unicodedata.normalize('NFKC', u'Ṗŕệṿïệẅ Ṯệẍt'),
unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'), True, True),
unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'), True),
(unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'),
unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'), False, True),
('test_password', 'test_password', True, False),
(unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'),
unicodedata.normalize('NFKC', u'Ṗŕệṿïệẅ Ṯệẍt'), False, False),
(unicodedata.normalize('NFKC', u'Ṗŕệṿïệẅ Ṯệẍt'),
unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'), False, False),
(unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'),
unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'), True, False),
unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt'), False),
)
@ddt.unpack
def test_password_unicode_normalization_login(self, password, password_entered, login_success, waffle_flag):
def test_password_unicode_normalization_login(self, password, password_entered, login_success):
"""
Tests unicode normalization on user's passwords on login.
"""
with PASSWORD_UNICODE_NORMALIZE_FLAG.override(active=waffle_flag):
self.user.set_password(password)
self.user.save()
response, _ = self._login_response(self.user.email, password_entered)
self._assert_response(response, success=login_success)
self.user.set_password(password)
self.user.save()
response, _ = self._login_response(self.user.email, password_entered)
self._assert_response(response, success=login_success)
def _login_response(self, email, password, patched_audit_log=None, extra_post_params=None):
"""

View File

@@ -31,9 +31,7 @@ from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
from openedx.core.djangoapps.user_api.accounts import (
USERNAME_BAD_LENGTH_MSG, USERNAME_INVALID_CHARS_ASCII, USERNAME_INVALID_CHARS_UNICODE
)
from openedx.core.djangoapps.user_api.config.waffle import (
PASSWORD_UNICODE_NORMALIZE_FLAG, PREVENT_AUTH_USER_WRITES, waffle
)
from openedx.core.djangoapps.user_api.config.waffle import PREVENT_AUTH_USER_WRITES, waffle
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
from student.models import UserAttribute
from student.tests.factories import UserFactory
@@ -104,7 +102,7 @@ class TestCreateAccount(SiteMixin, TestCase):
self.params = {
"username": self.username,
"email": "test@example.org",
"password": "testpass",
"password": u"testpass",
"name": "Test User",
"honor_code": "true",
"terms_of_service": "true",
@@ -135,7 +133,6 @@ class TestCreateAccount(SiteMixin, TestCase):
user = User.objects.get(username=self.username)
return user.profile
@override_waffle_flag(PASSWORD_UNICODE_NORMALIZE_FLAG, active=True)
def test_create_account_and_normalize_password(self):
"""
Test that unicode normalization on passwords is happening when a user registers.

View File

@@ -58,7 +58,7 @@ class UserAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin):
USERNAME = u"heisenberg"
ALTERNATE_USERNAME = u"walt"
OLD_PASSWORD = u"ḅḷüëṡḳÿ"
NEW_PASSWORD = u"🄱🄸🄶🄱🄻🅄🄴"
NEW_PASSWORD = u"B🄸🄶B🄻🅄🄴"
OLD_EMAIL = u"walter@graymattertech.com"
NEW_EMAIL = u"walt@savewalterwhite.com"
@@ -292,7 +292,7 @@ class LoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMixin, ModuleSto
shard = 7
USERNAME = "bob"
EMAIL = "bob@example.com"
PASSWORD = "password"
PASSWORD = u"password"
URLCONF_MODULES = ['openedx.core.djangoapps.embargo']