Merge pull request #22086 from edx/arch/user-authn-delete-deprecated
User Authn: Remove deprecated, ENABLE_COMBINED_LOGIN_REGISTRATION
This commit is contained in:
@@ -315,7 +315,6 @@ FEATURES = {
|
||||
'ENABLE_GRADE_DOWNLOADS': True,
|
||||
'ENABLE_MKTG_SITE': False,
|
||||
'ENABLE_DISCUSSION_HOME_PANEL': True,
|
||||
'ENABLE_COMBINED_LOGIN_REGISTRATION': True,
|
||||
'ENABLE_CORS_HEADERS': False,
|
||||
'ENABLE_CROSS_DOMAIN_CSRF_COOKIE': False,
|
||||
'ENABLE_COUNTRY_ACCESS': False,
|
||||
|
||||
@@ -222,8 +222,6 @@ FEATURES['ENABLE_SERVICE_STATUS'] = True
|
||||
# Toggles embargo on for testing
|
||||
FEATURES['EMBARGO'] = True
|
||||
|
||||
FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION'] = True
|
||||
|
||||
TEST_THEME = COMMON_ROOT / "test" / "test-theme"
|
||||
|
||||
# For consistency in user-experience, keep the value of this setting in sync with
|
||||
|
||||
@@ -395,8 +395,6 @@ def get_registration_extension_form(*args, **kwargs):
|
||||
|
||||
An example form app for this can be found at http://github.com/open-craft/custom-form-app
|
||||
"""
|
||||
if not settings.FEATURES.get("ENABLE_COMBINED_LOGIN_REGISTRATION"):
|
||||
return None
|
||||
if not getattr(settings, 'REGISTRATION_EXTENSION_FORM', None):
|
||||
return None
|
||||
module, klass = settings.REGISTRATION_EXTENSION_FORM.rsplit('.', 1)
|
||||
|
||||
@@ -220,40 +220,6 @@ def check_verify_status_by_course(user, course_enrollments):
|
||||
return status_by_course
|
||||
|
||||
|
||||
def auth_pipeline_urls(auth_entry, redirect_url=None):
|
||||
"""Retrieve URLs for each enabled third-party auth provider.
|
||||
|
||||
These URLs are used on the "sign up" and "sign in" buttons
|
||||
on the login/registration forms to allow users to begin
|
||||
authentication with a third-party provider.
|
||||
|
||||
Optionally, we can redirect the user to an arbitrary
|
||||
url after auth completes successfully. We use this
|
||||
to redirect the user to a page that required login,
|
||||
or to send users to the payment flow when enrolling
|
||||
in a course.
|
||||
|
||||
Args:
|
||||
auth_entry (string): Either `pipeline.AUTH_ENTRY_LOGIN` or `pipeline.AUTH_ENTRY_REGISTER`
|
||||
|
||||
Keyword Args:
|
||||
redirect_url (unicode): If provided, send users to this URL
|
||||
after they successfully authenticate.
|
||||
|
||||
Returns:
|
||||
dict mapping provider IDs to URLs
|
||||
|
||||
"""
|
||||
if not third_party_auth.is_enabled():
|
||||
return {}
|
||||
|
||||
return {
|
||||
provider.provider_id: third_party_auth.pipeline.get_login_url(
|
||||
provider.provider_id, auth_entry, redirect_url=redirect_url
|
||||
) for provider in third_party_auth.provider.Registry.displayed_for_login()
|
||||
}
|
||||
|
||||
|
||||
# Query string parameters that can be passed to the "finish_auth" view to manage
|
||||
# things like auto-enrollment.
|
||||
POST_AUTH_PARAMS = ('course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow')
|
||||
|
||||
@@ -37,13 +37,13 @@ class TestLongUsernameEmail(TestCase):
|
||||
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['username'][0]['user_message'],
|
||||
USERNAME_BAD_LENGTH_MSG,
|
||||
)
|
||||
|
||||
def test_spoffed_name(self):
|
||||
"""
|
||||
Test name cannot contains html.
|
||||
Test name cannot contain html.
|
||||
"""
|
||||
self.url_params['name'] = '<p style="font-size:300px; color:green;"></br>Name<input type="text"></br>Content spoof'
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
@@ -65,6 +65,6 @@ class TestLongUsernameEmail(TestCase):
|
||||
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['email'][0]['user_message'],
|
||||
"Email cannot be more than 254 characters long",
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ from django.urls import reverse
|
||||
from mock import patch
|
||||
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
|
||||
from openedx.core.djangoapps.user_authn.views.deprecated import create_account
|
||||
from util.password_policy_validators import create_validator_config
|
||||
|
||||
|
||||
@@ -43,7 +42,7 @@ class TestPasswordPolicy(TestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['password'][0]['user_message'],
|
||||
"This password is too short. It must contain at least 6 characters.",
|
||||
)
|
||||
|
||||
@@ -66,7 +65,7 @@ class TestPasswordPolicy(TestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['password'][0]['user_message'],
|
||||
"This password is too long. It must contain no more than 12 characters.",
|
||||
)
|
||||
|
||||
@@ -79,7 +78,7 @@ class TestPasswordPolicy(TestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['password'][0]['user_message'],
|
||||
"This password must contain at least 3 uppercase letters.",
|
||||
)
|
||||
|
||||
@@ -102,7 +101,7 @@ class TestPasswordPolicy(TestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['password'][0]['user_message'],
|
||||
"This password must contain at least 3 lowercase letters.",
|
||||
)
|
||||
|
||||
@@ -125,7 +124,7 @@ class TestPasswordPolicy(TestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['password'][0]['user_message'],
|
||||
"This password must contain at least 3 punctuation marks.",
|
||||
)
|
||||
|
||||
@@ -149,7 +148,7 @@ class TestPasswordPolicy(TestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['password'][0]['user_message'],
|
||||
"This password must contain at least 3 numbers.",
|
||||
)
|
||||
|
||||
@@ -173,7 +172,7 @@ class TestPasswordPolicy(TestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['password'][0]['user_message'],
|
||||
"This password must contain at least 3 letters.",
|
||||
)
|
||||
|
||||
@@ -198,12 +197,13 @@ class TestPasswordPolicy(TestCase):
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
errstring = (
|
||||
"This password must contain at least 3 uppercase letters. "
|
||||
"This password must contain at least 3 numbers. "
|
||||
"This password must contain at least 3 punctuation marks."
|
||||
)
|
||||
self.assertEqual(obj['value'], errstring)
|
||||
error_strings = [
|
||||
"This password must contain at least 3 uppercase letters.",
|
||||
"This password must contain at least 3 numbers.",
|
||||
"This password must contain at least 3 punctuation marks.",
|
||||
]
|
||||
for i in range(3):
|
||||
self.assertEqual(obj['password'][i]['user_message'], error_strings[i])
|
||||
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 3}),
|
||||
@@ -228,7 +228,7 @@ class TestPasswordPolicy(TestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['password'][0]['user_message'],
|
||||
"This password is too common.",
|
||||
)
|
||||
|
||||
@@ -280,7 +280,7 @@ class TestUsernamePasswordNonmatch(TestCase):
|
||||
self.assertEquals(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
obj['value'],
|
||||
obj['password'][0]['user_message'],
|
||||
"The password is too similar to the username.",
|
||||
)
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ class ProviderConfig(ConfigurationModel):
|
||||
def get_register_form_data(cls, pipeline_kwargs):
|
||||
"""Gets dict of data to display on the register form.
|
||||
|
||||
openedx.core.djangoapps.user_authn.views.deprecated.register_user uses this to populate
|
||||
register_user uses this to populate
|
||||
the new account creation form with values supplied by the user's chosen
|
||||
provider, preventing duplicate data entry.
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@ from social_django import utils as social_utils
|
||||
from social_django import views as social_views
|
||||
|
||||
from lms.djangoapps.commerce.tests import TEST_API_URL
|
||||
from openedx.core.djangoapps.user_authn.views.deprecated import signin_user, create_account, register_user
|
||||
from openedx.core.djangoapps.user_api.views import RegistrationView
|
||||
from openedx.core.djangoapps.user_authn.views.login import login_user
|
||||
from openedx.core.djangoapps.user_authn.views.login_form import login_and_registration_form
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
|
||||
from openedx.core.djangoapps.user_api.accounts.settings_views import account_settings_context
|
||||
from student import models as student_models
|
||||
@@ -32,6 +33,10 @@ from third_party_auth import middleware, pipeline
|
||||
from third_party_auth.tests import testutil
|
||||
|
||||
|
||||
def create_account(request):
|
||||
return RegistrationView().post(request)
|
||||
|
||||
|
||||
class HelperMixin(object):
|
||||
"""
|
||||
Contains helper methods for IntegrationTestMixin and IntegrationTest classes below.
|
||||
@@ -65,14 +70,18 @@ class HelperMixin(object):
|
||||
# Check that the correct provider was selected.
|
||||
self.assertContains(
|
||||
response,
|
||||
u'successfully signed in with <strong>%s</strong>' % self.provider.name,
|
||||
u'"errorMessage": null'
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
u'"currentProvider": "{}"'.format(self.provider.name),
|
||||
)
|
||||
# Expect that each truthy value we've prepopulated the register form
|
||||
# with is actually present.
|
||||
form_field_data = self.provider.get_register_form_data(pipeline_kwargs)
|
||||
for prepopulated_form_data in form_field_data:
|
||||
if prepopulated_form_data in required_fields:
|
||||
self.assertIn(form_field_data[prepopulated_form_data], response.content.decode('utf-8'))
|
||||
self.assertContains(response, form_field_data[prepopulated_form_data])
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def assert_account_settings_context_looks_correct(self, context, duplicate=False, linked=None):
|
||||
@@ -129,17 +138,18 @@ class HelperMixin(object):
|
||||
|
||||
def assert_json_failure_response_is_username_collision(self, response):
|
||||
"""Asserts the json response indicates a username collision."""
|
||||
self.assertEqual(400, response.status_code)
|
||||
self.assertEqual(409, response.status_code)
|
||||
payload = json.loads(response.content.decode('utf-8'))
|
||||
self.assertFalse(payload.get('success'))
|
||||
self.assertIn('belongs to an existing account', payload.get('value'))
|
||||
self.assertIn('belongs to an existing account', payload['username'][0]['user_message'])
|
||||
|
||||
def assert_json_success_response_looks_correct(self, response):
|
||||
def assert_json_success_response_looks_correct(self, response, verify_redirect_url):
|
||||
"""Asserts the json response indicates success and redirection."""
|
||||
self.assertEqual(200, response.status_code)
|
||||
payload = json.loads(response.content.decode('utf-8'))
|
||||
self.assertTrue(payload.get('success'))
|
||||
self.assertEqual(pipeline.get_complete_url(self.provider.backend_name), payload.get('redirect_url'))
|
||||
if verify_redirect_url:
|
||||
self.assertEqual(pipeline.get_complete_url(self.provider.backend_name), payload.get('redirect_url'))
|
||||
|
||||
def assert_login_response_before_pipeline_looks_correct(self, response):
|
||||
"""Asserts a GET of /login not in the pipeline looks correct."""
|
||||
@@ -285,7 +295,6 @@ class HelperMixin(object):
|
||||
"""Creates user, profile, registration, and (usually) social auth.
|
||||
|
||||
This synthesizes what happens during /register.
|
||||
See student.views.register and student.helpers.do_create_account.
|
||||
"""
|
||||
response_data = self.get_response_data()
|
||||
uid = strategy.request.backend.get_user_id(response_data, response_data)
|
||||
@@ -541,7 +550,6 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
|
||||
actions.do_complete(request.backend, social_views._do_login, # pylint: disable=protected-access
|
||||
request=request)
|
||||
|
||||
signin_user(strategy.request)
|
||||
login_user(strategy.request)
|
||||
actions.do_complete(request.backend, social_views._do_login, # pylint: disable=protected-access
|
||||
request=request)
|
||||
@@ -598,7 +606,6 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
|
||||
request=request)
|
||||
|
||||
with self._patch_edxmako_current_request(strategy.request):
|
||||
signin_user(strategy.request)
|
||||
login_user(strategy.request)
|
||||
actions.do_complete(request.backend, social_views._do_login, user=user, # pylint: disable=protected-access
|
||||
request=request)
|
||||
@@ -665,7 +672,6 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
|
||||
request=request)
|
||||
|
||||
with self._patch_edxmako_current_request(strategy.request):
|
||||
signin_user(strategy.request)
|
||||
login_user(strategy.request)
|
||||
actions.do_complete(request.backend, social_views._do_login, # pylint: disable=protected-access
|
||||
user=user, request=request)
|
||||
@@ -710,12 +716,12 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
|
||||
# At this point we know the pipeline has resumed correctly. Next we
|
||||
# fire off the view that displays the login form and posts it via JS.
|
||||
with self._patch_edxmako_current_request(strategy.request):
|
||||
self.assert_login_response_in_pipeline_looks_correct(signin_user(strategy.request))
|
||||
self.assert_login_response_in_pipeline_looks_correct(login_user(strategy.request))
|
||||
|
||||
# Next, we invoke the view that handles the POST, and expect it
|
||||
# redirects to /auth/complete. In the browser ajax handlers will
|
||||
# redirect the user to the dashboard; we invoke it manually here.
|
||||
self.assert_json_success_response_looks_correct(login_user(strategy.request))
|
||||
self.assert_json_success_response_looks_correct(login_user(strategy.request), verify_redirect_url=True)
|
||||
|
||||
# We should be redirected back to the complete page, setting
|
||||
# the "logged in" cookie for the marketing site.
|
||||
@@ -806,7 +812,7 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
|
||||
# fire off the view that displays the registration form.
|
||||
with self._patch_edxmako_current_request(request):
|
||||
self.assert_register_response_in_pipeline_looks_correct(
|
||||
register_user(strategy.request),
|
||||
login_and_registration_form(strategy.request, initial_mode='register'),
|
||||
pipeline.get(request)['kwargs'],
|
||||
['name', 'username', 'email']
|
||||
)
|
||||
@@ -828,7 +834,7 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
|
||||
# ...but when we invoke create_account the existing edX view will make
|
||||
# it, but not social auths. The pipeline creates those later.
|
||||
with self._patch_edxmako_current_request(strategy.request):
|
||||
self.assert_json_success_response_looks_correct(create_account(strategy.request))
|
||||
self.assert_json_success_response_looks_correct(create_account(strategy.request), verify_redirect_url=False)
|
||||
# We've overridden the user's password, so authenticate() with the old
|
||||
# value won't work:
|
||||
created_user = self.get_user_by_email(strategy, email)
|
||||
@@ -881,7 +887,7 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
|
||||
|
||||
with self._patch_edxmako_current_request(request):
|
||||
self.assert_register_response_in_pipeline_looks_correct(
|
||||
register_user(strategy.request),
|
||||
login_and_registration_form(strategy.request, initial_mode='register'),
|
||||
pipeline.get(request)['kwargs'],
|
||||
['name', 'username', 'email']
|
||||
)
|
||||
|
||||
@@ -22,7 +22,6 @@ from social_django.models import UserSocialAuth
|
||||
from testfixtures import LogCapture
|
||||
|
||||
from enterprise.models import EnterpriseCustomerIdentityProvider, EnterpriseCustomerUser
|
||||
from openedx.core.djangoapps.user_authn.views.deprecated import signin_user
|
||||
from openedx.core.djangoapps.user_authn.views.login import login_user
|
||||
from openedx.core.djangoapps.user_api.accounts.settings_views import account_settings_context
|
||||
from openedx.features.enterprise_support.tests.factories import EnterpriseCustomerFactory
|
||||
@@ -210,7 +209,6 @@ class TestShibIntegrationTest(SamlIntegrationTestUtilities, IntegrationTestMixin
|
||||
request=request)
|
||||
|
||||
with self._patch_edxmako_current_request(strategy.request):
|
||||
signin_user(strategy.request)
|
||||
login_user(strategy.request)
|
||||
actions.do_complete(request.backend, social_views._do_login, user=user, # pylint: disable=protected-access
|
||||
request=request)
|
||||
|
||||
@@ -110,10 +110,6 @@ class ResetPasswordPage(PageObject):
|
||||
class CombinedLoginAndRegisterPage(PageObject):
|
||||
"""Interact with combined login and registration page.
|
||||
|
||||
This page is currently hidden behind the feature flag
|
||||
`ENABLE_COMBINED_LOGIN_REGISTRATION`, which is enabled
|
||||
in the bok choy settings.
|
||||
|
||||
When enabled, the new page is available from either
|
||||
`/login` or `/register`; the new page is also served at
|
||||
`/account/login/` or `/account/register/`, where it was
|
||||
|
||||
@@ -79,7 +79,7 @@ EVENT_TRACKING_BACKENDS:
|
||||
OPTIONS: {collection: events, database: test}
|
||||
FEATURES: {ALLOW_AUTOMATED_SIGNUPS: true, AUTH_USE_OPENID_PROVIDER: true, AUTOMATIC_AUTH_FOR_TESTING: true,
|
||||
AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING: true, CERTIFICATES_HTML_VIEW: true,
|
||||
CERTIFICATES_INSTRUCTOR_GENERATION: true, CUSTOM_COURSES_EDX: true, ENABLE_COMBINED_LOGIN_REGISTRATION: true,
|
||||
CERTIFICATES_INSTRUCTOR_GENERATION: true, CUSTOM_COURSES_EDX: true,
|
||||
ENABLE_COURSE_DISCOVERY: true, ENABLE_DISCUSSION_SERVICE: true, ENABLE_GRADE_DOWNLOADS: true,
|
||||
ENABLE_PAYMENT_FAKE: true, ENABLE_SPECIAL_EXAMS: true, ENABLE_THIRD_PARTY_AUTH: true,
|
||||
ENABLE_VERIFIED_CERTIFICATES: true, EXPOSE_CACHE_PROGRAMS_ENDPOINT: true, MODE_CREATION_FOR_TESTING: true,
|
||||
|
||||
@@ -83,7 +83,7 @@ EVENT_TRACKING_BACKENDS:
|
||||
port: 27017
|
||||
FEATURES: {ALLOW_AUTOMATED_SIGNUPS: true, AUTH_USE_OPENID_PROVIDER: true, AUTOMATIC_AUTH_FOR_TESTING: true,
|
||||
AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING: true, CERTIFICATES_HTML_VIEW: true,
|
||||
CERTIFICATES_INSTRUCTOR_GENERATION: true, CUSTOM_COURSES_EDX: true, ENABLE_COMBINED_LOGIN_REGISTRATION: true,
|
||||
CERTIFICATES_INSTRUCTOR_GENERATION: true, CUSTOM_COURSES_EDX: true,
|
||||
ENABLE_COURSE_DISCOVERY: true, ENABLE_DISCUSSION_SERVICE: true, ENABLE_GRADE_DOWNLOADS: true,
|
||||
ENABLE_PAYMENT_FAKE: true, ENABLE_SPECIAL_EXAMS: true, ENABLE_THIRD_PARTY_AUTH: true,
|
||||
ENABLE_VERIFIED_CERTIFICATES: true, EXPOSE_CACHE_PROGRAMS_ENDPOINT: true, MODE_CREATION_FOR_TESTING: true,
|
||||
|
||||
@@ -262,8 +262,6 @@ FEATURES = {
|
||||
# ENABLE_OAUTH2_PROVIDER to True
|
||||
'ENABLE_MOBILE_REST_API': False,
|
||||
|
||||
# Enable the combined login/registration form
|
||||
'ENABLE_COMBINED_LOGIN_REGISTRATION': False,
|
||||
'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER': False,
|
||||
|
||||
# Enable organizational email opt-in
|
||||
|
||||
@@ -39,7 +39,6 @@ FEATURES.update({
|
||||
'ENABLE_DISCUSSION_SERVICE': True,
|
||||
'SHOW_HEADER_LANGUAGE_SELECTOR': True,
|
||||
'ENABLE_ENTERPRISE_INTEGRATION': False,
|
||||
'ENABLE_COMBINED_LOGIN_REGISTRATION': True,
|
||||
})
|
||||
|
||||
ENABLE_MKTG_SITE = os.environ.get('ENABLE_MARKETING_SITE', False)
|
||||
|
||||
@@ -82,8 +82,6 @@ FEATURES['ENABLE_VERIFIED_CERTIFICATES'] = True
|
||||
# Toggles embargo on for testing
|
||||
FEATURES['EMBARGO'] = True
|
||||
|
||||
FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION'] = True
|
||||
|
||||
# Enable the milestones app in tests to be consistent with it being enabled in production
|
||||
FEATURES['MILESTONES_APP'] = True
|
||||
|
||||
|
||||
@@ -188,24 +188,14 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase):
|
||||
|
||||
# Use an actual call to the login endpoint, to validate that the middleware
|
||||
# stack does the right thing
|
||||
if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):
|
||||
response = self.client.post(
|
||||
reverse('user_api_login_session'),
|
||||
data={
|
||||
'email': self.user.email,
|
||||
'password': UserFactory._DEFAULT_PASSWORD,
|
||||
'remember': True,
|
||||
}
|
||||
)
|
||||
else:
|
||||
response = self.client.post(
|
||||
reverse('login_post'),
|
||||
data={
|
||||
'email': self.user.email,
|
||||
'password': UserFactory._DEFAULT_PASSWORD,
|
||||
'honor_code': True,
|
||||
}
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse('user_api_login_session'),
|
||||
data={
|
||||
'email': self.user.email,
|
||||
'password': UserFactory._DEFAULT_PASSWORD, # pylint: disable=protected-access
|
||||
'remember': True,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
@@ -37,12 +37,11 @@ urlpatterns = [
|
||||
),
|
||||
]
|
||||
|
||||
if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):
|
||||
urlpatterns += [
|
||||
url(r'^v1/account/login_session/$', user_api_views.LoginSessionView.as_view(),
|
||||
name="user_api_login_session"),
|
||||
url(r'^v1/account/registration/$', user_api_views.RegistrationView.as_view(),
|
||||
name="user_api_registration"),
|
||||
url(r'^v1/account/password_reset/$', user_api_views.PasswordResetView.as_view(),
|
||||
name="user_api_password_reset"),
|
||||
]
|
||||
urlpatterns += [
|
||||
url(r'^v1/account/login_session/$', user_api_views.LoginSessionView.as_view(),
|
||||
name="user_api_login_session"),
|
||||
url(r'^v1/account/registration/$', user_api_views.RegistrationView.as_view(),
|
||||
name="user_api_registration"),
|
||||
url(r'^v1/account/password_reset/$', user_api_views.PasswordResetView.as_view(),
|
||||
name="user_api_password_reset"),
|
||||
]
|
||||
|
||||
@@ -825,9 +825,10 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
@@ -867,9 +868,10 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
@@ -909,9 +911,10 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"username": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
@@ -946,9 +949,10 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
@@ -983,9 +987,10 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
u"success": False,
|
||||
u"username": [{
|
||||
u"user_message": (
|
||||
u"An account with the Public Username '{}' already exists."
|
||||
@@ -1019,9 +1024,10 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
@@ -2182,9 +2188,10 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
@@ -2217,9 +2224,10 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"username": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
@@ -2252,9 +2260,10 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"username": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
@@ -2295,9 +2304,10 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
u"success": False,
|
||||
u"username": [{u"user_message": USERNAME_BAD_LENGTH_MSG}],
|
||||
u"password": [{u"user_message": u"This field is required."}],
|
||||
}
|
||||
@@ -2454,18 +2464,24 @@ class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTe
|
||||
"""Assert that the given response was an error for the access_token field with the given error message."""
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{"access_token": [{"user_message": expected_error_message}]}
|
||||
{
|
||||
"success": False,
|
||||
"access_token": [{"user_message": expected_error_message}],
|
||||
}
|
||||
)
|
||||
|
||||
def _assert_third_party_session_expired_error(self, response, expected_error_message):
|
||||
"""Assert that given response is an error due to third party session expiry"""
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{"session_expired": [{"user_message": expected_error_message}]}
|
||||
{
|
||||
"success": False,
|
||||
"session_expired": [{"user_message": expected_error_message}],
|
||||
}
|
||||
)
|
||||
|
||||
def _verify_user_existence(self, user_exists, social_link_exists, user_is_active=None, username=None):
|
||||
|
||||
@@ -98,12 +98,18 @@ class LoginSessionView(APIView):
|
||||
|
||||
|
||||
class RegistrationView(APIView):
|
||||
# pylint: disable=missing-docstring
|
||||
"""HTTP end-points for creating a new user. """
|
||||
|
||||
# This end-point is available to anonymous users,
|
||||
# so do not require authentication.
|
||||
authentication_classes = []
|
||||
|
||||
@method_decorator(transaction.non_atomic_requests)
|
||||
@method_decorator(sensitive_post_parameters("password"))
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(RegistrationView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
def get(self, request):
|
||||
return HttpResponse(RegistrationFormFactory().get_registration_form(request).to_json(),
|
||||
@@ -129,11 +135,25 @@ class RegistrationView(APIView):
|
||||
HttpResponse: 403 operation not allowed
|
||||
"""
|
||||
data = request.POST.copy()
|
||||
self._handle_terms_of_service(data)
|
||||
|
||||
response = self._handle_duplicate_email_username(data)
|
||||
if response:
|
||||
return response
|
||||
|
||||
response, user = self._create_account(request, data)
|
||||
if response:
|
||||
return response
|
||||
|
||||
response = self._create_response({}, status_code=200)
|
||||
set_logged_in_cookies(request, response, user)
|
||||
return response
|
||||
|
||||
def _handle_duplicate_email_username(self, data):
|
||||
# TODO Verify whether this check is needed here - it may be duplicated in user_api.
|
||||
email = data.get('email')
|
||||
username = data.get('username')
|
||||
|
||||
# Handle duplicate email/username
|
||||
conflicts = check_account_exists(email=email, username=username)
|
||||
if conflicts:
|
||||
conflict_messages = {
|
||||
@@ -144,8 +164,9 @@ class RegistrationView(APIView):
|
||||
field: [{"user_message": conflict_messages[field]}]
|
||||
for field in conflicts
|
||||
}
|
||||
return JsonResponse(errors, status=409)
|
||||
return self._create_response(errors, status_code=409)
|
||||
|
||||
def _handle_terms_of_service(self, data):
|
||||
# Backwards compatibility: the student view expects both
|
||||
# terms of service and honor code values. Since we're combining
|
||||
# these into a single checkbox, the only value we may get
|
||||
@@ -156,33 +177,32 @@ class RegistrationView(APIView):
|
||||
if data.get("honor_code") and "terms_of_service" not in data:
|
||||
data["terms_of_service"] = data["honor_code"]
|
||||
|
||||
def _create_account(self, request, data):
|
||||
response, user = None, None
|
||||
try:
|
||||
user = create_account_with_params(request, data)
|
||||
except AccountValidationError as err:
|
||||
errors = {
|
||||
err.field: [{"user_message": text_type(err)}]
|
||||
}
|
||||
return JsonResponse(errors, status=409)
|
||||
response = self._create_response(errors, status_code=409)
|
||||
except ValidationError as err:
|
||||
# Should only get non-field errors from this function
|
||||
# Should only get field errors from this exception
|
||||
assert NON_FIELD_ERRORS not in err.message_dict
|
||||
# Only return first error for each field
|
||||
errors = {
|
||||
field: [{"user_message": error} for error in error_list]
|
||||
for field, error_list in err.message_dict.items()
|
||||
}
|
||||
return JsonResponse(errors, status=400)
|
||||
response = self._create_response(errors, status_code=400)
|
||||
except PermissionDenied:
|
||||
return HttpResponseForbidden(_("Account creation not allowed."))
|
||||
response = HttpResponseForbidden(_("Account creation not allowed."))
|
||||
|
||||
response = JsonResponse({"success": True})
|
||||
set_logged_in_cookies(request, response, user)
|
||||
return response
|
||||
return response, user
|
||||
|
||||
@method_decorator(transaction.non_atomic_requests)
|
||||
@method_decorator(sensitive_post_parameters("password"))
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(RegistrationView, self).dispatch(request, *args, **kwargs)
|
||||
def _create_response(self, response_dict, status_code):
|
||||
response_dict['success'] = (status_code == 200)
|
||||
return JsonResponse(response_dict, status=status_code)
|
||||
|
||||
|
||||
class PasswordResetView(APIView):
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.conf.urls import include, url
|
||||
|
||||
from openedx.core.djangoapps.user_api.accounts import settings_views
|
||||
|
||||
from .views import deprecated, login, login_form
|
||||
from .views import login, login_form
|
||||
|
||||
urlpatterns = [
|
||||
# TODO this should really be declared in the user_api app
|
||||
@@ -18,17 +18,10 @@ urlpatterns = [
|
||||
]
|
||||
|
||||
|
||||
if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):
|
||||
# Backwards compatibility with old URL structure, but serve the new views
|
||||
urlpatterns += [
|
||||
url(r'^login$', login_form.login_and_registration_form,
|
||||
{'initial_mode': 'login'}, name='signin_user'),
|
||||
url(r'^register$', login_form.login_and_registration_form,
|
||||
{'initial_mode': 'register'}, name='register_user'),
|
||||
]
|
||||
else:
|
||||
# Serve the old views
|
||||
urlpatterns += [
|
||||
url(r'^login$', deprecated.signin_user, name='signin_user'),
|
||||
url(r'^register$', deprecated.register_user, name='register_user'),
|
||||
]
|
||||
# Backwards compatibility with old URL structure, but serve the new views
|
||||
urlpatterns += [
|
||||
url(r'^login$', login_form.login_and_registration_form,
|
||||
{'initial_mode': 'login'}, name='signin_user'),
|
||||
url(r'^register$', login_form.login_and_registration_form,
|
||||
{'initial_mode': 'register'}, name='register_user'),
|
||||
]
|
||||
|
||||
@@ -11,10 +11,12 @@ from __future__ import absolute_import
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import auto_auth, deprecated, login, logout
|
||||
from openedx.core.djangoapps.user_api.views import RegistrationView
|
||||
from .views import auto_auth, login, logout
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^create_account$', deprecated.create_account, name='create_account'),
|
||||
url(r'^create_account$', RegistrationView.as_view(), name='create_account'),
|
||||
url(r'^login_post$', login.login_user, name='login_post'),
|
||||
url(r'^login_ajax$', login.login_user, name="login"),
|
||||
url(r'^login_ajax/(?P<error>[^/]*)$', login.login_user),
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
""" User Authn code for deprecated views. """
|
||||
from __future__ import absolute_import
|
||||
|
||||
import warnings
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.validators import ValidationError
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
||||
from six import iteritems, text_type
|
||||
|
||||
import third_party_auth
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.user_api.config.waffle import PREVENT_AUTH_USER_WRITES, SYSTEM_MAINTENANCE_MSG, waffle
|
||||
from openedx.core.djangoapps.user_authn.cookies import set_logged_in_cookies
|
||||
from openedx.core.djangoapps.user_authn.views.register import create_account_with_params
|
||||
from student.helpers import AccountValidationError, auth_pipeline_urls, get_next_url_for_login_page
|
||||
from third_party_auth import pipeline, provider
|
||||
from util.json_request import JsonResponse
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def signin_user(request):
|
||||
"""Deprecated. To be replaced by :class:`user_authn.views.login_form.login_and_registration_form`."""
|
||||
# Determine the URL to redirect to following login:
|
||||
redirect_to = get_next_url_for_login_page(request)
|
||||
if request.user.is_authenticated:
|
||||
return redirect(redirect_to)
|
||||
|
||||
third_party_auth_error = None
|
||||
for msg in messages.get_messages(request):
|
||||
if msg.extra_tags.split()[0] == "social-auth":
|
||||
# msg may or may not be translated. Try translating [again] in case we are able to:
|
||||
third_party_auth_error = _(text_type(msg))
|
||||
break
|
||||
|
||||
context = {
|
||||
'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in the header
|
||||
# Bool injected into JS to submit form if we're inside a running third-
|
||||
# party auth pipeline; distinct from the actual instance of the running
|
||||
# pipeline, if any.
|
||||
'pipeline_running': 'true' if pipeline.running(request) else 'false',
|
||||
'pipeline_url': auth_pipeline_urls(pipeline.AUTH_ENTRY_LOGIN, redirect_url=redirect_to),
|
||||
'platform_name': configuration_helpers.get_value(
|
||||
'platform_name',
|
||||
settings.PLATFORM_NAME
|
||||
),
|
||||
'third_party_auth_error': third_party_auth_error
|
||||
}
|
||||
|
||||
return render_to_response('login.html', context)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def register_user(request, extra_context=None):
|
||||
"""
|
||||
Deprecated. To be replaced by :class:`user_authn.views.login_form.login_and_registration_form`.
|
||||
"""
|
||||
# Determine the URL to redirect to following login:
|
||||
redirect_to = get_next_url_for_login_page(request)
|
||||
if request.user.is_authenticated:
|
||||
return redirect(redirect_to)
|
||||
|
||||
context = {
|
||||
'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in the header
|
||||
'email': '',
|
||||
'name': '',
|
||||
'running_pipeline': None,
|
||||
'pipeline_urls': auth_pipeline_urls(pipeline.AUTH_ENTRY_REGISTER, redirect_url=redirect_to),
|
||||
'platform_name': configuration_helpers.get_value(
|
||||
'platform_name',
|
||||
settings.PLATFORM_NAME
|
||||
),
|
||||
'selected_provider': '',
|
||||
'username': '',
|
||||
}
|
||||
|
||||
if extra_context is not None:
|
||||
context.update(extra_context)
|
||||
|
||||
if context.get("extauth_domain", '').startswith(settings.SHIBBOLETH_DOMAIN_PREFIX):
|
||||
return render_to_response('register-shib.html', context)
|
||||
|
||||
# If third-party auth is enabled, prepopulate the form with data from the
|
||||
# selected provider.
|
||||
if third_party_auth.is_enabled() and pipeline.running(request):
|
||||
running_pipeline = pipeline.get(request)
|
||||
current_provider = provider.Registry.get_from_pipeline(running_pipeline)
|
||||
if current_provider is not None:
|
||||
overrides = current_provider.get_register_form_data(running_pipeline.get('kwargs'))
|
||||
overrides['running_pipeline'] = running_pipeline
|
||||
overrides['selected_provider'] = current_provider.name
|
||||
context.update(overrides)
|
||||
|
||||
return render_to_response('register.html', context)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@transaction.non_atomic_requests
|
||||
def create_account(request, post_override=None):
|
||||
"""
|
||||
Deprecated. Use RegistrationView instead.
|
||||
JSON call to create new edX account.
|
||||
Used by form in signup_modal.html, which is included into header.html
|
||||
"""
|
||||
# Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
|
||||
if not configuration_helpers.get_value(
|
||||
'ALLOW_PUBLIC_ACCOUNT_CREATION',
|
||||
settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
|
||||
):
|
||||
return HttpResponseForbidden(_("Account creation not allowed."))
|
||||
|
||||
if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
|
||||
return HttpResponseForbidden(SYSTEM_MAINTENANCE_MSG)
|
||||
|
||||
warnings.warn("Please use RegistrationView instead.", DeprecationWarning)
|
||||
|
||||
try:
|
||||
user = create_account_with_params(request, post_override or request.POST)
|
||||
except AccountValidationError as exc:
|
||||
return JsonResponse({'success': False, 'value': text_type(exc), 'field': exc.field}, status=400)
|
||||
except ValidationError as exc:
|
||||
field, error_list = next(iteritems(exc.message_dict))
|
||||
return JsonResponse(
|
||||
{
|
||||
"success": False,
|
||||
"field": field,
|
||||
"value": ' '.join(error_list),
|
||||
},
|
||||
status=400
|
||||
)
|
||||
|
||||
redirect_url = None # The AJAX method calling should know the default destination upon success
|
||||
|
||||
# Resume the third-party-auth pipeline if necessary.
|
||||
if third_party_auth.is_enabled() and pipeline.running(request):
|
||||
running_pipeline = pipeline.get(request)
|
||||
redirect_url = pipeline.get_complete_url(running_pipeline['backend'])
|
||||
|
||||
response = JsonResponse({
|
||||
'success': True,
|
||||
'redirect_url': redirect_url,
|
||||
})
|
||||
set_logged_in_cookies(request, response, user)
|
||||
return response
|
||||
@@ -17,7 +17,6 @@ from django.views.decorators.http import require_http_methods
|
||||
import third_party_auth
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.theming.helpers import is_request_in_themed_site
|
||||
from openedx.core.djangoapps.user_api.accounts.utils import is_secondary_email_feature_enabled
|
||||
from openedx.core.djangoapps.user_api.api import (
|
||||
RegistrationFormFactory,
|
||||
@@ -25,8 +24,6 @@ from openedx.core.djangoapps.user_api.api import (
|
||||
get_password_reset_form
|
||||
)
|
||||
from openedx.core.djangoapps.user_authn.cookies import are_logged_in_cookies_set
|
||||
from openedx.core.djangoapps.user_authn.views.deprecated import register_user as old_register_view
|
||||
from openedx.core.djangoapps.user_authn.views.deprecated import signin_user as old_login_view
|
||||
from openedx.features.enterprise_support.api import enterprise_customer_for_request
|
||||
from openedx.features.enterprise_support.utils import (
|
||||
handle_enterprise_cookies_for_logistration,
|
||||
@@ -71,31 +68,25 @@ def login_and_registration_form(request, initial_mode="login"):
|
||||
if '?' in redirect_to:
|
||||
try:
|
||||
next_args = six.moves.urllib.parse.parse_qs(six.moves.urllib.parse.urlparse(redirect_to).query)
|
||||
provider_id = next_args['tpa_hint'][0]
|
||||
tpa_hint_provider = third_party_auth.provider.Registry.get(provider_id=provider_id)
|
||||
if tpa_hint_provider:
|
||||
if tpa_hint_provider.skip_hinted_login_dialog:
|
||||
# Forward the user directly to the provider's login URL when the provider is configured
|
||||
# to skip the dialog.
|
||||
if initial_mode == "register":
|
||||
auth_entry = pipeline.AUTH_ENTRY_REGISTER
|
||||
else:
|
||||
auth_entry = pipeline.AUTH_ENTRY_LOGIN
|
||||
return redirect(
|
||||
pipeline.get_login_url(provider_id, auth_entry, redirect_url=redirect_to)
|
||||
)
|
||||
third_party_auth_hint = provider_id
|
||||
initial_mode = "hinted_login"
|
||||
if 'tpa_hint' in next_args:
|
||||
provider_id = next_args['tpa_hint'][0]
|
||||
tpa_hint_provider = third_party_auth.provider.Registry.get(provider_id=provider_id)
|
||||
if tpa_hint_provider:
|
||||
if tpa_hint_provider.skip_hinted_login_dialog:
|
||||
# Forward the user directly to the provider's login URL when the provider is configured
|
||||
# to skip the dialog.
|
||||
if initial_mode == "register":
|
||||
auth_entry = pipeline.AUTH_ENTRY_REGISTER
|
||||
else:
|
||||
auth_entry = pipeline.AUTH_ENTRY_LOGIN
|
||||
return redirect(
|
||||
pipeline.get_login_url(provider_id, auth_entry, redirect_url=redirect_to)
|
||||
)
|
||||
third_party_auth_hint = provider_id
|
||||
initial_mode = "hinted_login"
|
||||
except (KeyError, ValueError, IndexError) as ex:
|
||||
log.exception(u"Unknown tpa_hint provider: %s", ex)
|
||||
|
||||
# We are defaulting to true because all themes should now be using the newer page.
|
||||
if is_request_in_themed_site() and not configuration_helpers.get_value('ENABLE_COMBINED_LOGIN_REGISTRATION', True):
|
||||
if initial_mode == "login":
|
||||
return old_login_view(request)
|
||||
elif initial_mode == "register":
|
||||
return old_register_view(request)
|
||||
|
||||
# Account activation message
|
||||
account_activation_messages = [
|
||||
{
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
"""Tests for the login and registration form rendering. """
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
import ddt
|
||||
import six
|
||||
import six.moves.urllib.error # pylint: disable=import-error
|
||||
import six.moves.urllib.parse # pylint: disable=import-error
|
||||
import six.moves.urllib.request # pylint: disable=import-error
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from mock import patch
|
||||
|
||||
from openedx.core.djangolib.js_utils import js_escaped_string
|
||||
from third_party_auth.tests.testutil import ThirdPartyAuthTestMixin
|
||||
from util.testing import UrlResetMixin
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
# This relies on third party auth being enabled in the test
|
||||
# settings with the feature flag `ENABLE_THIRD_PARTY_AUTH`
|
||||
THIRD_PARTY_AUTH_BACKENDS = ["google-oauth2", "facebook"]
|
||||
THIRD_PARTY_AUTH_PROVIDERS = ["Google", "Facebook"]
|
||||
|
||||
|
||||
def _third_party_login_url(backend_name, auth_entry, redirect_url=None):
|
||||
"""Construct the login URL to start third party authentication. """
|
||||
params = [("auth_entry", auth_entry)]
|
||||
if redirect_url:
|
||||
params.append(("next", redirect_url))
|
||||
|
||||
return u"{url}?{params}".format(
|
||||
url=reverse("social:begin", kwargs={"backend": backend_name}),
|
||||
params=six.moves.urllib.parse.urlencode(params)
|
||||
)
|
||||
|
||||
|
||||
def _finish_auth_url(params):
|
||||
""" Construct the URL that follows login/registration if we are doing auto-enrollment """
|
||||
return u"{}?{}".format(reverse('finish_auth'), six.moves.urllib.parse.urlencode(params))
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class LoginFormTest(ThirdPartyAuthTestMixin, UrlResetMixin, SharedModuleStoreTestCase):
|
||||
"""Test rendering of the login form. """
|
||||
|
||||
URLCONF_MODULES = [
|
||||
'openedx.core.djangoapps.user_authn.urls',
|
||||
'openedx.core.djangoapps.user_api.legacy_urls',
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(LoginFormTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
@patch.dict(settings.FEATURES, {"ENABLE_COMBINED_LOGIN_REGISTRATION": False})
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(LoginFormTest, self).setUp()
|
||||
|
||||
self.url = reverse("signin_user")
|
||||
self.course_id = six.text_type(self.course.id)
|
||||
self.courseware_url = reverse("courseware", args=[self.course_id])
|
||||
self.configure_google_provider(enabled=True, visible=True)
|
||||
self.configure_facebook_provider(enabled=True, visible=True)
|
||||
|
||||
@patch.dict(settings.FEATURES, {"ENABLE_THIRD_PARTY_AUTH": False})
|
||||
@ddt.data(THIRD_PARTY_AUTH_PROVIDERS)
|
||||
def test_third_party_auth_disabled(self, provider_name):
|
||||
response = self.client.get(self.url)
|
||||
self.assertNotContains(response, provider_name)
|
||||
|
||||
@ddt.data(*THIRD_PARTY_AUTH_BACKENDS)
|
||||
def test_third_party_auth_no_course_id(self, backend_name):
|
||||
response = self.client.get(self.url)
|
||||
expected_url = _third_party_login_url(backend_name, "login")
|
||||
self.assertContains(response, expected_url)
|
||||
|
||||
@ddt.data(*THIRD_PARTY_AUTH_BACKENDS)
|
||||
def test_third_party_auth_with_course_id(self, backend_name):
|
||||
# Provide a course ID to the login page, simulating what happens
|
||||
# when a user tries to enroll in a course without being logged in
|
||||
params = [('course_id', self.course_id)]
|
||||
response = self.client.get(self.url, params)
|
||||
|
||||
# Expect that the course ID is added to the third party auth entry
|
||||
# point, so that the pipeline will enroll the student and
|
||||
# redirect the student to the track selection page.
|
||||
expected_url = _third_party_login_url(
|
||||
backend_name,
|
||||
"login",
|
||||
redirect_url=_finish_auth_url(params),
|
||||
)
|
||||
self.assertContains(response, expected_url)
|
||||
|
||||
@ddt.data(*THIRD_PARTY_AUTH_BACKENDS)
|
||||
def test_courseware_redirect(self, backend_name):
|
||||
# Try to access courseware while logged out, expecting to be
|
||||
# redirected to the login page.
|
||||
response = self.client.get(self.courseware_url, follow=True, HTTP_ACCEPT="text/html")
|
||||
self.assertRedirects(
|
||||
response,
|
||||
u"{url}?next={redirect_url}".format(
|
||||
url=reverse("signin_user"),
|
||||
redirect_url=self.courseware_url
|
||||
)
|
||||
)
|
||||
|
||||
# Verify that the third party auth URLs include the redirect URL
|
||||
# The third party auth pipeline will redirect to this page
|
||||
# once the user successfully authenticates.
|
||||
expected_url = _third_party_login_url(
|
||||
backend_name,
|
||||
"login",
|
||||
redirect_url=self.courseware_url
|
||||
)
|
||||
self.assertContains(response, expected_url)
|
||||
|
||||
@ddt.data(*THIRD_PARTY_AUTH_BACKENDS)
|
||||
def test_third_party_auth_with_params(self, backend_name):
|
||||
params = [
|
||||
('course_id', self.course_id),
|
||||
('enrollment_action', 'enroll'),
|
||||
('course_mode', 'honor'),
|
||||
('email_opt_in', 'true'),
|
||||
('next', '/custom/final/destination'),
|
||||
]
|
||||
response = self.client.get(self.url, params, HTTP_ACCEPT="text/html")
|
||||
expected_url = _third_party_login_url(
|
||||
backend_name,
|
||||
"login",
|
||||
redirect_url=_finish_auth_url(params),
|
||||
)
|
||||
self.assertContains(response, expected_url)
|
||||
|
||||
@ddt.data(None, "true", "false")
|
||||
def test_params(self, opt_in_value):
|
||||
params = [
|
||||
('course_id', self.course_id),
|
||||
('enrollment_action', 'enroll'),
|
||||
('course_mode', 'honor'),
|
||||
('email_opt_in', opt_in_value),
|
||||
('next', '/custom/final/destination'),
|
||||
]
|
||||
|
||||
# Get the login page
|
||||
response = self.client.get(self.url, params, HTTP_ACCEPT="text/html")
|
||||
|
||||
# Verify that the parameters are sent on to the next page correctly
|
||||
post_login_handler = _finish_auth_url(params)
|
||||
js_success_var = u'var nextUrl = "{}";'.format(js_escaped_string(post_login_handler))
|
||||
self.assertContains(response, js_success_var)
|
||||
|
||||
# Verify that the login link preserves the querystring params
|
||||
login_link = u"{url}?{params}".format(
|
||||
url=reverse('signin_user'),
|
||||
params=six.moves.urllib.parse.urlencode([('next', post_login_handler)])
|
||||
)
|
||||
self.assertContains(response, login_link)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class RegisterFormTest(ThirdPartyAuthTestMixin, UrlResetMixin, SharedModuleStoreTestCase):
|
||||
"""Test rendering of the registration form. """
|
||||
|
||||
URLCONF_MODULES = ['openedx.core.djangoapps.user_authn.urls']
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(RegisterFormTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
@patch.dict(settings.FEATURES, {"ENABLE_COMBINED_LOGIN_REGISTRATION": False})
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(RegisterFormTest, self).setUp()
|
||||
|
||||
self.url = reverse("register_user")
|
||||
self.course_id = six.text_type(self.course.id)
|
||||
self.configure_google_provider(enabled=True, visible=True)
|
||||
self.configure_facebook_provider(enabled=True, visible=True)
|
||||
|
||||
@patch.dict(settings.FEATURES, {"ENABLE_THIRD_PARTY_AUTH": False})
|
||||
@ddt.data(*THIRD_PARTY_AUTH_PROVIDERS)
|
||||
def test_third_party_auth_disabled(self, provider_name):
|
||||
response = self.client.get(self.url)
|
||||
self.assertNotContains(response, provider_name)
|
||||
|
||||
@ddt.data(*THIRD_PARTY_AUTH_BACKENDS)
|
||||
def test_register_third_party_auth_no_course_id(self, backend_name):
|
||||
response = self.client.get(self.url)
|
||||
expected_url = _third_party_login_url(backend_name, "register")
|
||||
self.assertContains(response, expected_url)
|
||||
|
||||
@ddt.data(*THIRD_PARTY_AUTH_BACKENDS)
|
||||
def test_register_third_party_auth_with_params(self, backend_name):
|
||||
params = [
|
||||
('course_id', self.course_id),
|
||||
('enrollment_action', 'enroll'),
|
||||
('course_mode', 'honor'),
|
||||
('email_opt_in', 'true'),
|
||||
('next', '/custom/final/destination'),
|
||||
]
|
||||
response = self.client.get(self.url, params, HTTP_ACCEPT="text/html")
|
||||
expected_url = _third_party_login_url(
|
||||
backend_name,
|
||||
"register",
|
||||
redirect_url=_finish_auth_url(params),
|
||||
)
|
||||
self.assertContains(response, expected_url)
|
||||
|
||||
@ddt.data(None, "true", "false")
|
||||
def test_params(self, opt_in_value):
|
||||
params = [
|
||||
('course_id', self.course_id),
|
||||
('enrollment_action', 'enroll'),
|
||||
('course_mode', 'honor'),
|
||||
('email_opt_in', opt_in_value),
|
||||
('next', '/custom/final/destination'),
|
||||
]
|
||||
|
||||
# Get the login page
|
||||
response = self.client.get(self.url, params, HTTP_ACCEPT="text/html")
|
||||
|
||||
# Verify that the parameters are sent on to the next page correctly
|
||||
post_login_handler = _finish_auth_url(params)
|
||||
js_success_var = u'var nextUrl = "{}";'.format(js_escaped_string(post_login_handler))
|
||||
self.assertContains(response, js_success_var)
|
||||
|
||||
# Verify that the login link preserves the querystring params
|
||||
login_link = u"{url}?{params}".format(
|
||||
url=reverse('signin_user'),
|
||||
params=six.moves.urllib.parse.urlencode([('next', post_login_handler)])
|
||||
)
|
||||
self.assertContains(response, login_link)
|
||||
@@ -1,866 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for account creation"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import unicodedata
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from lms.djangoapps.discussion.notification_prefs import NOTIFICATION_PREF_KEY
|
||||
from openedx.core.djangoapps.django_comment_common.models import ForumsConfig
|
||||
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
|
||||
from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
|
||||
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration
|
||||
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 PREVENT_AUTH_USER_WRITES, waffle
|
||||
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
|
||||
from openedx.core.djangoapps.user_authn.views.register import (
|
||||
REGISTRATION_AFFILIATE_ID,
|
||||
REGISTRATION_UTM_CREATED_AT,
|
||||
REGISTRATION_UTM_PARAMETERS,
|
||||
_skip_activation_email
|
||||
)
|
||||
from student.models import UserAttribute
|
||||
from student.tests.factories import UserFactory
|
||||
from third_party_auth.tests import factories as third_party_auth_factory
|
||||
|
||||
TEST_CS_URL = 'https://comments.service.test:123/'
|
||||
|
||||
TEST_USERNAME = 'test_user'
|
||||
TEST_EMAIL = 'test@test.com'
|
||||
|
||||
|
||||
def get_mock_pipeline_data(username=TEST_USERNAME, email=TEST_EMAIL):
|
||||
"""
|
||||
Return mock pipeline data.
|
||||
"""
|
||||
return {
|
||||
'backend': 'tpa-saml',
|
||||
'kwargs': {
|
||||
'username': username,
|
||||
'auth_entry': 'register',
|
||||
'request': {
|
||||
'SAMLResponse': [],
|
||||
'RelayState': [
|
||||
'testshib-openedx'
|
||||
]
|
||||
},
|
||||
'is_new': True,
|
||||
'new_association': True,
|
||||
'user': None,
|
||||
'social': None,
|
||||
'details': {
|
||||
'username': username,
|
||||
'fullname': 'Test Test',
|
||||
'last_name': 'Test',
|
||||
'first_name': 'Test',
|
||||
'email': email,
|
||||
},
|
||||
'response': {},
|
||||
'uid': 'testshib-openedx:{}'.format(username)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@with_site_configuration(
|
||||
configuration={"extended_profile_fields": ["extra1", "extra2"]}
|
||||
)
|
||||
@override_settings(
|
||||
REGISTRATION_EXTRA_FIELDS={
|
||||
key: "optional"
|
||||
for key in [
|
||||
"level_of_education", "gender", "mailing_address", "city", "country", "goals",
|
||||
"year_of_birth"
|
||||
]
|
||||
}
|
||||
)
|
||||
class TestCreateAccount(SiteMixin, TestCase):
|
||||
"""Tests for account creation"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateAccount, self).setUp()
|
||||
self.username = "test_user"
|
||||
self.url = reverse("create_account")
|
||||
self.request_factory = RequestFactory()
|
||||
self.params = {
|
||||
"username": self.username,
|
||||
"email": "test@example.org",
|
||||
"password": u"testpass",
|
||||
"name": "Test User",
|
||||
"honor_code": "true",
|
||||
"terms_of_service": "true",
|
||||
}
|
||||
|
||||
@ddt.data("en", "eo")
|
||||
def test_default_lang_pref_saved(self, lang):
|
||||
with mock.patch("django.conf.settings.LANGUAGE_CODE", lang):
|
||||
response = self.client.post(self.url, self.params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
user = User.objects.get(username=self.username)
|
||||
self.assertEqual(get_user_preference(user, LANGUAGE_KEY), lang)
|
||||
|
||||
@ddt.data("en", "eo")
|
||||
def test_header_lang_pref_saved(self, lang):
|
||||
response = self.client.post(self.url, self.params, HTTP_ACCEPT_LANGUAGE=lang)
|
||||
user = User.objects.get(username=self.username)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(get_user_preference(user, LANGUAGE_KEY), lang)
|
||||
|
||||
def create_account_and_fetch_profile(self, host='test.localhost'):
|
||||
"""
|
||||
Create an account with self.params, assert that the response indicates
|
||||
success, and return the UserProfile object for the newly created user
|
||||
"""
|
||||
response = self.client.post(self.url, self.params, HTTP_HOST=host)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
user = User.objects.get(username=self.username)
|
||||
return user.profile
|
||||
|
||||
def test_create_account_and_normalize_password(self):
|
||||
"""
|
||||
Test that unicode normalization on passwords is happening when a user registers.
|
||||
"""
|
||||
# Set user password to NFKD format so that we can test that it is normalized to
|
||||
# NFKC format upon account creation.
|
||||
self.params['password'] = unicodedata.normalize('NFKD', u'Ṗŕệṿïệẅ Ṯệẍt')
|
||||
response = self.client.post(self.url, self.params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
user = User.objects.get(username=self.username)
|
||||
salt_val = user.password.split('$')[1]
|
||||
|
||||
expected_user_password = make_password(unicodedata.normalize('NFKC', u'Ṗŕệṿïệẅ Ṯệẍt'), salt_val)
|
||||
self.assertEqual(expected_user_password, user.password)
|
||||
|
||||
def test_marketing_cookie(self):
|
||||
response = self.client.post(self.url, self.params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, self.client.cookies)
|
||||
self.assertIn(settings.EDXMKTG_USER_INFO_COOKIE_NAME, self.client.cookies)
|
||||
|
||||
def test_profile_saved_no_optional_fields(self):
|
||||
profile = self.create_account_and_fetch_profile()
|
||||
self.assertEqual(profile.name, self.params["name"])
|
||||
self.assertEqual(profile.level_of_education, "")
|
||||
self.assertEqual(profile.gender, "")
|
||||
self.assertEqual(profile.mailing_address, "")
|
||||
self.assertEqual(profile.city, "")
|
||||
self.assertEqual(profile.country, "")
|
||||
self.assertEqual(profile.goals, "")
|
||||
self.assertEqual(
|
||||
profile.get_meta(),
|
||||
{
|
||||
"extra1": "",
|
||||
"extra2": "",
|
||||
}
|
||||
)
|
||||
self.assertIsNone(profile.year_of_birth)
|
||||
|
||||
@override_settings(LMS_SEGMENT_KEY="testkey")
|
||||
@mock.patch('openedx.core.djangoapps.user_authn.views.register.segment.track')
|
||||
@mock.patch('openedx.core.djangoapps.user_authn.views.register.segment.identify')
|
||||
def test_segment_tracking(self, mock_segment_identify, _):
|
||||
year = datetime.now().year
|
||||
year_of_birth = year - 14
|
||||
self.params.update({
|
||||
"level_of_education": "a",
|
||||
"gender": "o",
|
||||
"mailing_address": "123 Example Rd",
|
||||
"city": "Exampleton",
|
||||
"country": "US",
|
||||
"goals": "To test this feature",
|
||||
"year_of_birth": str(year_of_birth),
|
||||
"extra1": "extra_value1",
|
||||
"extra2": "extra_value2",
|
||||
})
|
||||
|
||||
expected_payload = {
|
||||
'email': self.params['email'],
|
||||
'username': self.params['username'],
|
||||
'name': self.params['name'],
|
||||
'age': 13,
|
||||
'yearOfBirth': year_of_birth,
|
||||
'education': 'Associate degree',
|
||||
'address': self.params['mailing_address'],
|
||||
'gender': 'Other/Prefer Not to Say',
|
||||
'country': self.params['country'],
|
||||
}
|
||||
|
||||
profile = self.create_account_and_fetch_profile()
|
||||
|
||||
mock_segment_identify.assert_called_with(profile.user.id, expected_payload)
|
||||
|
||||
def test_profile_saved_all_optional_fields(self):
|
||||
self.params.update({
|
||||
"level_of_education": "a",
|
||||
"gender": "o",
|
||||
"mailing_address": "123 Example Rd",
|
||||
"city": "Exampleton",
|
||||
"country": "US",
|
||||
"goals": "To test this feature",
|
||||
"year_of_birth": "2015",
|
||||
"extra1": "extra_value1",
|
||||
"extra2": "extra_value2",
|
||||
})
|
||||
profile = self.create_account_and_fetch_profile()
|
||||
self.assertEqual(profile.level_of_education, "a")
|
||||
self.assertEqual(profile.gender, "o")
|
||||
self.assertEqual(profile.mailing_address, "123 Example Rd")
|
||||
self.assertEqual(profile.city, "Exampleton")
|
||||
self.assertEqual(profile.country, "US")
|
||||
self.assertEqual(profile.goals, "To test this feature")
|
||||
self.assertEqual(
|
||||
profile.get_meta(),
|
||||
{
|
||||
"extra1": "extra_value1",
|
||||
"extra2": "extra_value2",
|
||||
}
|
||||
)
|
||||
self.assertEqual(profile.year_of_birth, 2015)
|
||||
|
||||
def test_profile_saved_empty_optional_fields(self):
|
||||
self.params.update({
|
||||
"level_of_education": "",
|
||||
"gender": "",
|
||||
"mailing_address": "",
|
||||
"city": "",
|
||||
"country": "",
|
||||
"goals": "",
|
||||
"year_of_birth": "",
|
||||
"extra1": "",
|
||||
"extra2": "",
|
||||
})
|
||||
profile = self.create_account_and_fetch_profile()
|
||||
self.assertEqual(profile.level_of_education, "")
|
||||
self.assertEqual(profile.gender, "")
|
||||
self.assertEqual(profile.mailing_address, "")
|
||||
self.assertEqual(profile.city, "")
|
||||
self.assertEqual(profile.country, "")
|
||||
self.assertEqual(profile.goals, "")
|
||||
self.assertEqual(
|
||||
profile.get_meta(),
|
||||
{"extra1": "", "extra2": ""}
|
||||
)
|
||||
self.assertEqual(profile.year_of_birth, None)
|
||||
|
||||
def test_profile_year_of_birth_non_integer(self):
|
||||
self.params["year_of_birth"] = "not_an_integer"
|
||||
profile = self.create_account_and_fetch_profile()
|
||||
self.assertIsNone(profile.year_of_birth)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_discussions_email_digest_pref(self, digest_enabled):
|
||||
with mock.patch.dict("student.models.settings.FEATURES", {"ENABLE_DISCUSSION_EMAIL_DIGEST": digest_enabled}):
|
||||
response = self.client.post(self.url, self.params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
user = User.objects.get(username=self.username)
|
||||
preference = get_user_preference(user, NOTIFICATION_PREF_KEY)
|
||||
if digest_enabled:
|
||||
self.assertIsNotNone(preference)
|
||||
else:
|
||||
self.assertIsNone(preference)
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_affiliate_referral_attribution(self):
|
||||
"""
|
||||
Verify that a referral attribution is recorded if an affiliate
|
||||
cookie is present upon a new user's registration.
|
||||
"""
|
||||
affiliate_id = 'test-partner'
|
||||
self.client.cookies[settings.AFFILIATE_COOKIE_NAME] = affiliate_id
|
||||
user = self.create_account_and_fetch_profile().user
|
||||
self.assertEqual(UserAttribute.get_user_attribute(user, REGISTRATION_AFFILIATE_ID), affiliate_id)
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_utm_referral_attribution(self):
|
||||
"""
|
||||
Verify that a referral attribution is recorded if an affiliate
|
||||
cookie is present upon a new user's registration.
|
||||
"""
|
||||
utm_cookie_name = 'edx.test.utm'
|
||||
with mock.patch('student.models.RegistrationCookieConfiguration.current') as config:
|
||||
instance = config.return_value
|
||||
instance.utm_cookie_name = utm_cookie_name
|
||||
|
||||
timestamp = 1475521816879
|
||||
utm_cookie = {
|
||||
'utm_source': 'test-source',
|
||||
'utm_medium': 'test-medium',
|
||||
'utm_campaign': 'test-campaign',
|
||||
'utm_term': 'test-term',
|
||||
'utm_content': 'test-content',
|
||||
'created_at': timestamp
|
||||
}
|
||||
|
||||
created_at = datetime.fromtimestamp(timestamp / float(1000), tz=pytz.UTC)
|
||||
|
||||
self.client.cookies[utm_cookie_name] = json.dumps(utm_cookie)
|
||||
user = self.create_account_and_fetch_profile().user
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_source')),
|
||||
utm_cookie.get('utm_source')
|
||||
)
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_medium')),
|
||||
utm_cookie.get('utm_medium')
|
||||
)
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_campaign')),
|
||||
utm_cookie.get('utm_campaign')
|
||||
)
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_term')),
|
||||
utm_cookie.get('utm_term')
|
||||
)
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_content')),
|
||||
utm_cookie.get('utm_content')
|
||||
)
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_CREATED_AT),
|
||||
str(created_at)
|
||||
)
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_no_referral(self):
|
||||
"""Verify that no referral is recorded when a cookie is not present."""
|
||||
utm_cookie_name = 'edx.test.utm'
|
||||
with mock.patch('student.models.RegistrationCookieConfiguration.current') as config:
|
||||
instance = config.return_value
|
||||
instance.utm_cookie_name = utm_cookie_name
|
||||
|
||||
self.assertIsNone(self.client.cookies.get(settings.AFFILIATE_COOKIE_NAME))
|
||||
self.assertIsNone(self.client.cookies.get(utm_cookie_name))
|
||||
user = self.create_account_and_fetch_profile().user
|
||||
self.assertIsNone(UserAttribute.get_user_attribute(user, REGISTRATION_AFFILIATE_ID))
|
||||
self.assertIsNone(UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_source')))
|
||||
self.assertIsNone(UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_medium')))
|
||||
self.assertIsNone(UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_campaign')))
|
||||
self.assertIsNone(UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_term')))
|
||||
self.assertIsNone(UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_content')))
|
||||
self.assertIsNone(UserAttribute.get_user_attribute(user, REGISTRATION_UTM_CREATED_AT))
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_incomplete_utm_referral(self):
|
||||
"""Verify that no referral is recorded when a cookie is not present."""
|
||||
utm_cookie_name = 'edx.test.utm'
|
||||
with mock.patch('student.models.RegistrationCookieConfiguration.current') as config:
|
||||
instance = config.return_value
|
||||
instance.utm_cookie_name = utm_cookie_name
|
||||
|
||||
utm_cookie = {
|
||||
'utm_source': 'test-source',
|
||||
'utm_medium': 'test-medium',
|
||||
# No campaign
|
||||
'utm_term': 'test-term',
|
||||
'utm_content': 'test-content',
|
||||
# No created at
|
||||
}
|
||||
|
||||
self.client.cookies[utm_cookie_name] = json.dumps(utm_cookie)
|
||||
user = self.create_account_and_fetch_profile().user
|
||||
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_source')),
|
||||
utm_cookie.get('utm_source')
|
||||
)
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_medium')),
|
||||
utm_cookie.get('utm_medium')
|
||||
)
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_term')),
|
||||
utm_cookie.get('utm_term')
|
||||
)
|
||||
self.assertEqual(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_content')),
|
||||
utm_cookie.get('utm_content')
|
||||
)
|
||||
self.assertIsNone(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_PARAMETERS.get('utm_campaign'))
|
||||
)
|
||||
self.assertIsNone(
|
||||
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_CREATED_AT)
|
||||
)
|
||||
|
||||
@mock.patch("openedx.core.djangoapps.site_configuration.helpers.get_value", mock.Mock(return_value=False))
|
||||
def test_create_account_not_allowed(self):
|
||||
"""
|
||||
Test case to check user creation is forbidden when ALLOW_PUBLIC_ACCOUNT_CREATION feature flag is turned off
|
||||
"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_create_account_prevent_auth_user_writes(self):
|
||||
with waffle().override(PREVENT_AUTH_USER_WRITES, True):
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_created_on_site_user_attribute_set(self):
|
||||
profile = self.create_account_and_fetch_profile(host=self.site.domain)
|
||||
self.assertEqual(UserAttribute.get_user_attribute(profile.user, 'created_on_site'), self.site.domain)
|
||||
|
||||
@ddt.data(
|
||||
(
|
||||
False, get_mock_pipeline_data(),
|
||||
{
|
||||
'SKIP_EMAIL_VALIDATION': False, 'AUTOMATIC_AUTH_FOR_TESTING': False,
|
||||
},
|
||||
False # Do not skip activation email for normal scenario.
|
||||
),
|
||||
(
|
||||
False, get_mock_pipeline_data(),
|
||||
{
|
||||
'SKIP_EMAIL_VALIDATION': True, 'AUTOMATIC_AUTH_FOR_TESTING': False,
|
||||
},
|
||||
True # Skip activation email when `SKIP_EMAIL_VALIDATION` FEATURE flag is active.
|
||||
),
|
||||
(
|
||||
False, get_mock_pipeline_data(),
|
||||
{
|
||||
'SKIP_EMAIL_VALIDATION': False, 'AUTOMATIC_AUTH_FOR_TESTING': True,
|
||||
},
|
||||
True # Skip activation email when `AUTOMATIC_AUTH_FOR_TESTING` FEATURE flag is active.
|
||||
),
|
||||
(
|
||||
True, get_mock_pipeline_data(),
|
||||
{
|
||||
'SKIP_EMAIL_VALIDATION': False, 'AUTOMATIC_AUTH_FOR_TESTING': False,
|
||||
},
|
||||
True # Skip activation email if `skip_email_verification` is set for third party authentication.
|
||||
),
|
||||
(
|
||||
False, get_mock_pipeline_data(email='invalid@yopmail.com'),
|
||||
{
|
||||
'SKIP_EMAIL_VALIDATION': False, 'AUTOMATIC_AUTH_FOR_TESTING': False,
|
||||
},
|
||||
False # Send activation email when `skip_email_verification` is not set.
|
||||
)
|
||||
)
|
||||
@ddt.unpack
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_should_skip_activation_email(
|
||||
self, skip_email_verification, running_pipeline, feature_overrides, expected,
|
||||
):
|
||||
"""
|
||||
Test `skip_activation_email` works as expected.
|
||||
"""
|
||||
third_party_provider = third_party_auth_factory.SAMLProviderConfigFactory(
|
||||
skip_email_verification=skip_email_verification,
|
||||
)
|
||||
user = UserFactory(username=TEST_USERNAME, email=TEST_EMAIL)
|
||||
|
||||
with override_settings(FEATURES=dict(settings.FEATURES, **feature_overrides)):
|
||||
result = _skip_activation_email(
|
||||
user=user,
|
||||
running_pipeline=running_pipeline,
|
||||
third_party_provider=third_party_provider
|
||||
)
|
||||
|
||||
assert result == expected
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestCreateAccountValidation(TestCase):
|
||||
"""
|
||||
Test validation of various parameters in the create_account view
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestCreateAccountValidation, self).setUp()
|
||||
self.url = reverse("create_account")
|
||||
self.minimal_params = {
|
||||
"username": "test_username",
|
||||
"email": "test_email@example.com",
|
||||
"password": "test_password",
|
||||
"name": "Test Name",
|
||||
"honor_code": "true",
|
||||
"terms_of_service": "true",
|
||||
}
|
||||
|
||||
def assert_success(self, params):
|
||||
"""
|
||||
Request account creation with the given params and assert that the
|
||||
response properly indicates success
|
||||
"""
|
||||
response = self.client.post(self.url, params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response_data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertTrue(response_data["success"])
|
||||
|
||||
def assert_error(self, params, expected_field, expected_value):
|
||||
"""
|
||||
Request account creation with the given params and assert that the
|
||||
response properly indicates an error with the given field and value
|
||||
"""
|
||||
response = self.client.post(self.url, params)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertFalse(response_data["success"])
|
||||
self.assertEqual(response_data["field"], expected_field)
|
||||
self.assertEqual(response_data["value"], expected_value)
|
||||
|
||||
def test_minimal_success(self):
|
||||
self.assert_success(self.minimal_params)
|
||||
|
||||
def test_username(self):
|
||||
params = dict(self.minimal_params)
|
||||
|
||||
def assert_username_error(expected_error):
|
||||
"""
|
||||
Assert that requesting account creation results in the expected
|
||||
error
|
||||
"""
|
||||
self.assert_error(params, "username", expected_error)
|
||||
|
||||
# Missing
|
||||
del params["username"]
|
||||
assert_username_error(USERNAME_BAD_LENGTH_MSG)
|
||||
|
||||
# Empty, too short
|
||||
for username in ["", "a"]:
|
||||
params["username"] = username
|
||||
assert_username_error(USERNAME_BAD_LENGTH_MSG)
|
||||
|
||||
# Too long
|
||||
params["username"] = "this_username_has_31_characters"
|
||||
assert_username_error(USERNAME_BAD_LENGTH_MSG)
|
||||
|
||||
# Invalid
|
||||
params["username"] = "invalid username"
|
||||
assert_username_error(str(USERNAME_INVALID_CHARS_ASCII))
|
||||
|
||||
def test_email(self):
|
||||
params = dict(self.minimal_params)
|
||||
|
||||
def assert_email_error(expected_error):
|
||||
"""
|
||||
Assert that requesting account creation results in the expected
|
||||
error
|
||||
"""
|
||||
self.assert_error(params, "email", expected_error)
|
||||
|
||||
# Missing
|
||||
del params["email"]
|
||||
assert_email_error("A properly formatted e-mail is required")
|
||||
|
||||
# Empty
|
||||
params["email"] = ""
|
||||
assert_email_error("A properly formatted e-mail is required")
|
||||
|
||||
#too short
|
||||
params["email"] = "a"
|
||||
assert_email_error("A properly formatted e-mail is required "
|
||||
"Ensure this value has at least 3 characters (it has 1).")
|
||||
|
||||
# Too long
|
||||
params["email"] = '{email}@example.com'.format(
|
||||
email='this_email_address_has_254_characters_in_it_so_it_is_unacceptable' * 4
|
||||
)
|
||||
|
||||
# Assert that we get error when email has more than 254 characters.
|
||||
self.assertGreater(len(params['email']), 254)
|
||||
assert_email_error("Email cannot be more than 254 characters long")
|
||||
|
||||
# Valid Email
|
||||
params["email"] = "student@edx.com"
|
||||
# Assert success on valid email
|
||||
self.assertLess(len(params["email"]), 254)
|
||||
self.assert_success(params)
|
||||
|
||||
# Invalid
|
||||
params["email"] = "not_an_email_address"
|
||||
assert_email_error("A properly formatted e-mail is required")
|
||||
|
||||
@override_settings(
|
||||
REGISTRATION_EMAIL_PATTERNS_ALLOWED=[
|
||||
r'.*@edx.org', # Naive regex omitting '^', '$' and '\.' should still work.
|
||||
r'^.*@(.*\.)?example\.com$',
|
||||
r'^(^\w+\.\w+)@school.tld$',
|
||||
]
|
||||
)
|
||||
@ddt.data(
|
||||
('bob@we-are.bad', False),
|
||||
('bob@edx.org.we-are.bad', False),
|
||||
('staff@edx.org', True),
|
||||
('student@example.com', True),
|
||||
('student@sub.example.com', True),
|
||||
('mr.teacher@school.tld', True),
|
||||
('student1234@school.tld', False),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_email_pattern_requirements(self, email, expect_success):
|
||||
"""
|
||||
Test the REGISTRATION_EMAIL_PATTERNS_ALLOWED setting, a feature which
|
||||
can be used to only allow people register if their email matches a
|
||||
against a whitelist of regexs.
|
||||
"""
|
||||
params = dict(self.minimal_params)
|
||||
params["email"] = email
|
||||
if expect_success:
|
||||
self.assert_success(params)
|
||||
else:
|
||||
self.assert_error(params, "email", "Unauthorized email address.")
|
||||
|
||||
def test_password(self):
|
||||
params = dict(self.minimal_params)
|
||||
|
||||
def assert_password_error(expected_error):
|
||||
"""
|
||||
Assert that requesting account creation results in the expected
|
||||
error
|
||||
"""
|
||||
self.assert_error(params, "password", expected_error)
|
||||
|
||||
# Missing
|
||||
del params["password"]
|
||||
assert_password_error("This field is required.")
|
||||
|
||||
# Empty
|
||||
params["password"] = ""
|
||||
assert_password_error("This field is required.")
|
||||
|
||||
# Too short
|
||||
params["password"] = "a"
|
||||
assert_password_error("This password is too short. It must contain at least 2 characters.")
|
||||
|
||||
# Password policy is tested elsewhere
|
||||
|
||||
# Matching username
|
||||
params["username"] = params["password"] = "test_username_and_password"
|
||||
assert_password_error("The password is too similar to the username.")
|
||||
|
||||
def test_name(self):
|
||||
params = dict(self.minimal_params)
|
||||
|
||||
def assert_name_error(expected_error):
|
||||
"""
|
||||
Assert that requesting account creation results in the expected
|
||||
error
|
||||
"""
|
||||
self.assert_error(params, "name", expected_error)
|
||||
|
||||
# Missing
|
||||
del params["name"]
|
||||
assert_name_error("Your legal name must be a minimum of one character long")
|
||||
|
||||
# Empty, too short
|
||||
params["name"] = ""
|
||||
assert_name_error("Your legal name must be a minimum of one character long")
|
||||
|
||||
def test_honor_code(self):
|
||||
params = dict(self.minimal_params)
|
||||
|
||||
def assert_honor_code_error(expected_error):
|
||||
"""
|
||||
Assert that requesting account creation results in the expected
|
||||
error
|
||||
"""
|
||||
self.assert_error(params, "honor_code", expected_error)
|
||||
|
||||
with override_settings(REGISTRATION_EXTRA_FIELDS={"honor_code": "required"}):
|
||||
# Missing
|
||||
del params["honor_code"]
|
||||
assert_honor_code_error("To enroll, you must follow the honor code.")
|
||||
|
||||
# Empty, invalid
|
||||
for honor_code in ["", "false", "not_boolean"]:
|
||||
params["honor_code"] = honor_code
|
||||
assert_honor_code_error("To enroll, you must follow the honor code.")
|
||||
|
||||
# True
|
||||
params["honor_code"] = "tRUe"
|
||||
self.assert_success(params)
|
||||
|
||||
with override_settings(REGISTRATION_EXTRA_FIELDS={"honor_code": "optional"}):
|
||||
# Missing
|
||||
del params["honor_code"]
|
||||
# Need to change username/email because user was created above
|
||||
params["username"] = "another_test_username"
|
||||
params["email"] = "another_test_email@example.com"
|
||||
self.assert_success(params)
|
||||
|
||||
def test_terms_of_service(self):
|
||||
params = dict(self.minimal_params)
|
||||
|
||||
def assert_terms_of_service_error(expected_error):
|
||||
"""
|
||||
Assert that requesting account creation results in the expected
|
||||
error
|
||||
"""
|
||||
self.assert_error(params, "terms_of_service", expected_error)
|
||||
|
||||
# Missing
|
||||
del params["terms_of_service"]
|
||||
assert_terms_of_service_error("You must accept the terms of service.")
|
||||
|
||||
# Empty, invalid
|
||||
for terms_of_service in ["", "false", "not_boolean"]:
|
||||
params["terms_of_service"] = terms_of_service
|
||||
assert_terms_of_service_error("You must accept the terms of service.")
|
||||
|
||||
# True
|
||||
params["terms_of_service"] = "tRUe"
|
||||
self.assert_success(params)
|
||||
|
||||
@ddt.data(
|
||||
("level_of_education", 1, "A level of education is required"),
|
||||
("gender", 1, "Your gender is required"),
|
||||
("year_of_birth", 2, "Your year of birth is required"),
|
||||
("mailing_address", 2, "Your mailing address is required"),
|
||||
("goals", 2, "A description of your goals is required"),
|
||||
("city", 2, "A city is required"),
|
||||
("country", 2, "A country is required"),
|
||||
("custom_field", 2, "You are missing one or more required fields")
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_extra_fields(self, field, min_length, expected_error):
|
||||
params = dict(self.minimal_params)
|
||||
|
||||
def assert_extra_field_error():
|
||||
"""
|
||||
Assert that requesting account creation results in the expected
|
||||
error
|
||||
"""
|
||||
self.assert_error(params, field, expected_error)
|
||||
|
||||
with override_settings(REGISTRATION_EXTRA_FIELDS={field: "required"}):
|
||||
# Missing
|
||||
assert_extra_field_error()
|
||||
|
||||
# Empty
|
||||
params[field] = ""
|
||||
assert_extra_field_error()
|
||||
|
||||
# Too short
|
||||
if min_length > 1:
|
||||
params[field] = "a"
|
||||
assert_extra_field_error()
|
||||
|
||||
|
||||
@mock.patch.dict("student.models.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
@mock.patch("openedx.core.djangoapps.django_comment_common.comment_client.User.base_url", TEST_CS_URL)
|
||||
@mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request",
|
||||
return_value=mock.Mock(status_code=200, text='{}')
|
||||
)
|
||||
class TestCreateCommentsServiceUser(TransactionTestCase):
|
||||
""" Tests for creating comments service user. """
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateCommentsServiceUser, self).setUp()
|
||||
self.username = "test_user"
|
||||
self.url = reverse("create_account")
|
||||
self.params = {
|
||||
"username": self.username,
|
||||
"email": "test@example.org",
|
||||
"password": "testpass",
|
||||
"name": "Test User",
|
||||
"honor_code": "true",
|
||||
"terms_of_service": "true",
|
||||
}
|
||||
|
||||
config = ForumsConfig.current()
|
||||
config.enabled = True
|
||||
config.save()
|
||||
|
||||
def test_cs_user_created(self, request):
|
||||
"If user account creation succeeds, we should create a comments service user"
|
||||
response = self.client.post(self.url, self.params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(request.called)
|
||||
args, kwargs = request.call_args
|
||||
self.assertEqual(args[0], 'put')
|
||||
self.assertTrue(args[1].startswith(TEST_CS_URL))
|
||||
self.assertEqual(kwargs['data']['username'], self.params['username'])
|
||||
|
||||
@mock.patch("student.models.Registration.register", side_effect=Exception)
|
||||
def test_cs_user_not_created(self, register, request):
|
||||
"If user account creation fails, we should not create a comments service user"
|
||||
try:
|
||||
self.client.post(self.url, self.params)
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
with self.assertRaises(User.DoesNotExist):
|
||||
User.objects.get(username=self.username)
|
||||
self.assertTrue(register.called)
|
||||
self.assertFalse(request.called)
|
||||
|
||||
|
||||
class TestUnicodeUsername(TestCase):
|
||||
"""
|
||||
Test for Unicode usernames which is an optional feature.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestUnicodeUsername, self).setUp()
|
||||
self.url = reverse('create_account')
|
||||
|
||||
# The word below reads "Omar II", in Arabic. It also contains a space and
|
||||
# an Eastern Arabic Number another option is to use the Esperanto fake
|
||||
# language but this was used instead to test non-western letters.
|
||||
self.username = u'عمر ٢'
|
||||
|
||||
self.url_params = {
|
||||
'username': self.username,
|
||||
'email': 'unicode_user@example.com',
|
||||
"password": "testpass",
|
||||
'name': 'unicode_user',
|
||||
'terms_of_service': 'true',
|
||||
'honor_code': 'true',
|
||||
}
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_UNICODE_USERNAME': False})
|
||||
def test_with_feature_disabled(self):
|
||||
"""
|
||||
Ensures backward-compatible defaults.
|
||||
"""
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
|
||||
self.assertEquals(response.status_code, 400)
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEquals(USERNAME_INVALID_CHARS_ASCII, obj['value'])
|
||||
|
||||
with self.assertRaises(User.DoesNotExist):
|
||||
User.objects.get(email=self.url_params['email'])
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_UNICODE_USERNAME': True})
|
||||
def test_with_feature_enabled(self):
|
||||
response = self.client.post(self.url, self.url_params)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
self.assertTrue(User.objects.get(email=self.url_params['email']))
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_UNICODE_USERNAME': True})
|
||||
def test_special_chars_with_feature_enabled(self):
|
||||
"""
|
||||
Ensures that special chars are still prevented.
|
||||
"""
|
||||
|
||||
invalid_params = self.url_params.copy()
|
||||
invalid_params['username'] = '**john**'
|
||||
|
||||
response = self.client.post(self.url, invalid_params)
|
||||
self.assertEquals(response.status_code, 400)
|
||||
|
||||
obj = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEquals(USERNAME_INVALID_CHARS_UNICODE, obj['value'])
|
||||
|
||||
with self.assertRaises(User.DoesNotExist):
|
||||
User.objects.get(email=self.url_params['email'])
|
||||
Reference in New Issue
Block a user