diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py index f796ec4750..4ac5ceacdd 100644 --- a/lms/djangoapps/student_account/test/test_views.py +++ b/lms/djangoapps/student_account/test/test_views.py @@ -362,15 +362,16 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi self._assert_third_party_auth_data(response, None, None, [], None) @mock.patch('student_account.views.enterprise_customer_for_request') + @mock.patch('openedx.core.djangoapps.user_api.api.enterprise_customer_for_request') @ddt.data( - ("signin_user", None, None, None), - ("register_user", None, None, None), - ("signin_user", "google-oauth2", "Google", None), - ("register_user", "google-oauth2", "Google", None), - ("signin_user", "facebook", "Facebook", None), - ("register_user", "facebook", "Facebook", None), - ("signin_user", "dummy", "Dummy", None), - ("register_user", "dummy", "Dummy", None), + ("signin_user", None, None, None, False), + ("register_user", None, None, None, False), + ("signin_user", "google-oauth2", "Google", None, False), + ("register_user", "google-oauth2", "Google", None, False), + ("signin_user", "facebook", "Facebook", None, False), + ("register_user", "facebook", "Facebook", None, False), + ("signin_user", "dummy", "Dummy", None, False), + ("register_user", "dummy", "Dummy", None, False), ( "signin_user", "google-oauth2", @@ -379,7 +380,8 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi 'name': 'FakeName', 'logo': 'https://host.com/logo.jpg', 'welcome_msg': 'No message' - } + }, + True ) ) @ddt.unpack @@ -389,7 +391,9 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi current_backend, current_provider, expected_enterprise_customer_mock_attrs, - enterprise_customer_mock + add_user_details, + enterprise_customer_mock_1, + enterprise_customer_mock_2 ): params = [ ('course_id', 'course-v1:Org+Course+Run'), @@ -400,24 +404,26 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi ] if expected_enterprise_customer_mock_attrs: - expected_ec = mock.MagicMock( - branding_configuration=mock.MagicMock( - logo=mock.MagicMock( - url=expected_enterprise_customer_mock_attrs['logo'] - ), - welcome_message=expected_enterprise_customer_mock_attrs['welcome_msg'] - ) - ) - expected_ec.name = expected_enterprise_customer_mock_attrs['name'] + expected_ec = { + 'name': expected_enterprise_customer_mock_attrs['name'], + 'branding_configuration': { + 'logo': 'https://host.com/logo.jpg', + 'welcome_message': expected_enterprise_customer_mock_attrs['welcome_msg'] + } + } else: expected_ec = None - enterprise_customer_mock.return_value = expected_ec + email = None + if add_user_details: + email = 'test@test.com' + enterprise_customer_mock_1.return_value = expected_ec + enterprise_customer_mock_2.return_value = expected_ec # Simulate a running pipeline if current_backend is not None: pipeline_target = "student_account.views.third_party_auth.pipeline" - with simulate_running_pipeline(pipeline_target, current_backend): + with simulate_running_pipeline(pipeline_target, current_backend, email=email): response = self.client.get(reverse(url_name), params, HTTP_ACCEPT="text/html") # Do NOT simulate a running pipeline @@ -456,7 +462,8 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi current_backend, current_provider, expected_providers, - expected_ec + expected_ec, + add_user_details ) def _configure_testshib_provider(self, provider_name, idp_slug): @@ -477,16 +484,12 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi @mock.patch('django.conf.settings.MESSAGE_STORAGE', 'django.contrib.messages.storage.cookie.CookieStorage') @mock.patch('lms.djangoapps.student_account.views.enterprise_customer_for_request') + @mock.patch('openedx.core.djangoapps.user_api.api.enterprise_customer_for_request') @ddt.data( ( 'signin_user', 'tpa-saml', 'TestShib', - { - 'name': 'FakeName', - 'logo': 'https://host.com/logo.jpg', - 'welcome_msg': 'No message' - } ) ) @ddt.unpack @@ -495,8 +498,8 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi url_name, current_backend, current_provider, - expected_enterprise_customer_mock_attrs, - enterprise_customer_mock, + enterprise_customer_mock_1, + enterprise_customer_mock_2, ): params = [] request = RequestFactory().get(reverse(url_name), params, HTTP_ACCEPT='text/html') @@ -506,21 +509,13 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi self.enable_saml() dummy_idp = 'testshib' self._configure_testshib_provider(current_provider, dummy_idp) - expected_ec = mock.MagicMock( - branding_configuration=mock.MagicMock( - logo=mock.MagicMock( - url=expected_enterprise_customer_mock_attrs['logo'] - ), - welcome_message=expected_enterprise_customer_mock_attrs['welcome_msg'] - ) - ) - expected_ec.name = expected_enterprise_customer_mock_attrs['name'] enterprise_customer_data = { 'uuid': '72416e52-8c77-4860-9584-15e5b06220fb', 'name': 'Dummy Enterprise', 'identity_provider': dummy_idp, } - enterprise_customer_mock.return_value = enterprise_customer_data + enterprise_customer_mock_1.return_value = enterprise_customer_data + enterprise_customer_mock_2.return_value = enterprise_customer_data dummy_error_message = 'Authentication failed: SAML login failed ' \ '["invalid_response"] [SAML Response must contain 1 assertion]' @@ -739,7 +734,8 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi self.assertEqual(resp['X-Frame-Options'], 'ALLOW') - def _assert_third_party_auth_data(self, response, current_backend, current_provider, providers, expected_ec): + def _assert_third_party_auth_data(self, response, current_backend, current_provider, providers, expected_ec, + add_user_details=False): """Verify that third party auth info is rendered correctly in a DOM data attribute. """ finish_auth_url = None if current_backend: @@ -753,6 +749,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi "errorMessage": None, "registerFormSubmitButtonText": "Create Account", "syncLearnerProfileData": False, + "pipeline_user_details": {"email": "test@test.com"} if add_user_details else None } if expected_ec is not None: # If we set an EnterpriseCustomer, third-party auth providers ought to be hidden. @@ -782,6 +779,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi 'errorMessage': expected_error_message, 'registerFormSubmitButtonText': 'Create Account', 'syncLearnerProfileData': False, + 'pipeline_user_details': {'response': {'idp_name': 'testshib'}} } auth_info = dump_js_escaped_json(auth_info) diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py index c3200e6522..be08368bfb 100644 --- a/lms/djangoapps/student_account/views.py +++ b/lms/djangoapps/student_account/views.py @@ -250,6 +250,7 @@ def _third_party_auth_context(request, redirect_to, tpa_hint=None): "errorMessage": None, "registerFormSubmitButtonText": _("Create Account"), "syncLearnerProfileData": False, + "pipeline_user_details": None } if third_party_auth.is_enabled(): @@ -275,6 +276,9 @@ def _third_party_auth_context(request, redirect_to, tpa_hint=None): running_pipeline = pipeline.get(request) if running_pipeline is not None: current_provider = third_party_auth.provider.Registry.get_from_pipeline(running_pipeline) + user_details = running_pipeline['kwargs']['details'] + if user_details: + context['pipeline_user_details'] = user_details if current_provider is not None: context["currentProvider"] = current_provider.name @@ -309,7 +313,7 @@ def _get_form_descriptions(request): return { 'password_reset': get_password_reset_form().to_json(), - 'login': get_login_session_form().to_json(), + 'login': get_login_session_form(request).to_json(), 'registration': RegistrationFormFactory().get_registration_form(request).to_json() } diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js index c04a8bbe7b..45f385993b 100644 --- a/lms/static/js/student_account/views/AccessView.js +++ b/lms/static/js/student_account/views/AccessView.js @@ -75,6 +75,8 @@ this.passwordResetSupportUrl = options.password_reset_support_link; this.createAccountOption = options.account_creation_allowed; this.hideAuthWarnings = options.hide_auth_warnings || false; + this.pipelineUserDetails = options.third_party_auth.pipeline_user_details; + this.enterpriseName = options.enterprise_name || ''; // The login view listens for 'sync' events from the reset model this.resetModel = new PasswordResetModel({}, { @@ -133,7 +135,9 @@ supportURL: this.supportURL, passwordResetSupportUrl: this.passwordResetSupportUrl, createAccountOption: this.createAccountOption, - hideAuthWarnings: this.hideAuthWarnings + hideAuthWarnings: this.hideAuthWarnings, + pipelineUserDetails: this.pipelineUserDetails, + enterpriseName: this.enterpriseName }); // Listen for 'password-help' event to toggle sub-views diff --git a/lms/static/js/student_account/views/LoginView.js b/lms/static/js/student_account/views/LoginView.js index 0c954927d7..264e00258c 100644 --- a/lms/static/js/student_account/views/LoginView.js +++ b/lms/static/js/student_account/views/LoginView.js @@ -50,6 +50,8 @@ this.createAccountOption = data.createAccountOption; this.accountActivationMessages = data.accountActivationMessages; this.hideAuthWarnings = data.hideAuthWarnings; + this.pipelineUserDetails = data.pipelineUserDetails; + this.enterpriseName = data.enterpriseName; this.listenTo(this.model, 'sync', this.saveSuccess); this.listenTo(this.resetModel, 'sync', this.resetEmail); @@ -68,7 +70,9 @@ providers: this.providers, hasSecondaryProviders: this.hasSecondaryProviders, platformName: this.platformName, - createAccountOption: this.createAccountOption + createAccountOption: this.createAccountOption, + pipelineUserDetails: this.pipelineUserDetails, + enterpriseName: this.enterpriseName } })); diff --git a/lms/templates/student_account/form_field.underscore b/lms/templates/student_account/form_field.underscore index 4cba9a5cd1..2c4e42d7d2 100644 --- a/lms/templates/student_account/form_field.underscore +++ b/lms/templates/student_account/form_field.underscore @@ -91,6 +91,7 @@ <% } %> <% if ( restrictions.min_length ) { %> minlength="<%- restrictions.min_length %>"<% } %> <% if ( restrictions.max_length ) { %> maxlength="<%- restrictions.max_length %>"<% } %> + <% if ( restrictions.readonly ) { %> readonly <% } %> <% if ( required ) { %> required<% } %> <% if ( typeof errorMessages !== 'undefined' ) { _.each(errorMessages, function( msg, type ) {%> diff --git a/lms/templates/student_account/login.underscore b/lms/templates/student_account/login.underscore index e0829bf9cf..7b7252da94 100644 --- a/lms/templates/student_account/login.underscore +++ b/lms/templates/student_account/login.underscore @@ -1,14 +1,30 @@
+ <%- gettext("You already have an edX account with your {enterprise_name} email address.").replace(/{enterprise_name}/g, context.enterpriseName) %> + <% if (context.syncLearnerProfileData) { + %><%- gettext("Going forward, your account information will be updated and maintained by {enterprise_name}.").replace(/{enterprise_name}/g, context.enterpriseName) %> + <% } %> + <%- gettext("You can view your information or unlink from {enterprise_name} anytime in your Account Settings.").replace(/{enterprise_name}/g, context.enterpriseName) %> +
+<%- gettext("To continue learning with this account, sign in below.") %>
+<% } else { %> +