Cleaning up unicode waffle flag
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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'thisShouldPass២33'
|
||||
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.
|
||||
|
||||
@@ -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}),
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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})],
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
Reference in New Issue
Block a user