diff --git a/common/djangoapps/third_party_auth/tests/testutil.py b/common/djangoapps/third_party_auth/tests/testutil.py
index 1befeb0d29..8479aa6346 100644
--- a/common/djangoapps/third_party_auth/tests/testutil.py
+++ b/common/djangoapps/third_party_auth/tests/testutil.py
@@ -279,7 +279,8 @@ def simulate_running_pipeline(pipeline_target, backend, email=None, fullname=Non
pipeline_data = {
"backend": backend,
"kwargs": {
- "details": kwargs
+ "details": kwargs,
+ "response": kwargs.get("response", {})
}
}
diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py
index e73abb227b..f88a362953 100644
--- a/lms/djangoapps/student_account/test/test_views.py
+++ b/lms/djangoapps/student_account/test/test_views.py
@@ -12,13 +12,17 @@ import pytest
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
+from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.middleware import MessageMiddleware
+from django.contrib.sessions.middleware import SessionMiddleware
from django.core import mail
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.urlresolvers import reverse
from django.http import HttpRequest
from django.test import TestCase
+from django.test.client import RequestFactory
from django.test.utils import override_settings
+from django.utils.translation import ugettext as _
from edx_oauth2_provider.tests.factories import AccessTokenFactory, ClientFactory, RefreshTokenFactory
from edx_rest_api_client import exceptions
from http.cookies import SimpleCookie
@@ -33,6 +37,7 @@ from course_modes.models import CourseMode
from lms.djangoapps.commerce.models import CommerceConfiguration
from lms.djangoapps.commerce.tests import factories
from lms.djangoapps.commerce.tests.mocks import mock_get_orders
+from lms.djangoapps.student_account.views import login_and_registration_form
from openedx.core.djangoapps.oauth_dispatch.tests import factories as dot_factories
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
@@ -40,6 +45,7 @@ from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme_context
from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account
from openedx.core.djangolib.js_utils import dump_js_escaped_json
+from openedx.core.djangolib.markup import HTML, Text
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from student.tests.factories import UserFactory
from student_account.views import account_settings_context, get_user_orders
@@ -452,6 +458,111 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
expected_ec
)
+ def _configure_testshib_provider(self, provider_name, idp_slug):
+ """
+ Enable and configure the TestShib SAML IdP as a third_party_auth provider.
+ """
+ kwargs = {}
+ kwargs.setdefault('name', provider_name)
+ kwargs.setdefault('enabled', True)
+ kwargs.setdefault('visible', True)
+ kwargs.setdefault('idp_slug', idp_slug)
+ kwargs.setdefault('entity_id', 'https://idp.testshib.org/idp/shibboleth')
+ kwargs.setdefault('metadata_source', 'https://mock.testshib.org/metadata/testshib-providers.xml')
+ kwargs.setdefault('icon_class', 'fa-university')
+ kwargs.setdefault('attr_email', 'dummy-email-attr')
+ kwargs.setdefault('max_session_length', None)
+ self.configure_saml_provider(**kwargs)
+
+ @mock.patch('django.conf.settings.MESSAGE_STORAGE', 'django.contrib.messages.storage.cookie.CookieStorage')
+ @mock.patch('lms.djangoapps.student_account.views.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
+ def test_saml_auth_with_error(
+ self,
+ url_name,
+ current_backend,
+ current_provider,
+ expected_enterprise_customer_mock_attrs,
+ enterprise_customer_mock,
+ ):
+ params = []
+ request = RequestFactory().get(reverse(url_name), params, HTTP_ACCEPT='text/html')
+ SessionMiddleware().process_request(request)
+ request.user = AnonymousUser()
+
+ 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
+ dummy_error_message = 'Authentication failed: SAML login failed ' \
+ '["invalid_response"] [SAML Response must contain 1 assertion]'
+
+ # Add error message for error in auth pipeline
+ MessageMiddleware().process_request(request)
+ messages.error(request, dummy_error_message, extra_tags='social-auth')
+
+ # Simulate a running pipeline
+ pipeline_response = {
+ 'response': {
+ 'idp_name': dummy_idp
+ }
+ }
+ pipeline_target = 'student_account.views.third_party_auth.pipeline'
+ with simulate_running_pipeline(pipeline_target, current_backend, **pipeline_response):
+ with mock.patch('edxmako.request_context.get_current_request', return_value=request):
+ response = login_and_registration_form(request)
+
+ expected_error_message = Text(_(
+ u'We are sorry, you are not authorized to access {platform_name} via this channel. '
+ u'Please contact your {enterprise} administrator in order to access {platform_name} '
+ u'or contact {edx_support_link}.{line_break}'
+ u'{line_break}'
+ u'Error Details:{line_break}{error_message}')
+ ).format(
+ platform_name=settings.PLATFORM_NAME,
+ enterprise=enterprise_customer_data['name'],
+ error_message=dummy_error_message,
+ edx_support_link=HTML(
+ '{support_url_name}'
+ ).format(
+ edx_support_url=settings.SUPPORT_SITE_LINK,
+ support_url_name=_('edX Support'),
+ ),
+ line_break=HTML('
')
+ )
+ self._assert_saml_auth_data_with_error(
+ response,
+ current_backend,
+ current_provider,
+ expected_error_message
+ )
+
def test_hinted_login(self):
params = [("next", "/courses/something/?tpa_hint=oa2-google-oauth2")]
response = self.client.get(reverse('signin_user'), params, HTTP_ACCEPT="text/html")
@@ -650,7 +761,32 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
expected_data = '"third_party_auth": {auth_info}'.format(
auth_info=auth_info
)
+ self.assertContains(response, expected_data)
+ def _assert_saml_auth_data_with_error(
+ self, response, current_backend, current_provider, expected_error_message
+ ):
+ """
+ Verify that third party auth info is rendered correctly in a DOM data attribute.
+ """
+ finish_auth_url = None
+ if current_backend:
+ finish_auth_url = reverse('social:complete', kwargs={'backend': current_backend}) + '?'
+
+ auth_info = {
+ 'currentProvider': current_provider,
+ 'providers': [],
+ 'secondaryProviders': [],
+ 'finishAuthUrl': finish_auth_url,
+ 'errorMessage': expected_error_message,
+ 'registerFormSubmitButtonText': 'Create Account',
+ 'syncLearnerProfileData': False,
+ }
+ auth_info = dump_js_escaped_json(auth_info)
+
+ expected_data = '"third_party_auth": {auth_info}'.format(
+ auth_info=auth_info
+ )
self.assertContains(response, expected_data)
def _third_party_login_url(self, backend_name, auth_entry, login_params):
diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py
index a03c8ec834..3a6fcc62b7 100644
--- a/lms/djangoapps/student_account/views.py
+++ b/lms/djangoapps/student_account/views.py
@@ -41,6 +41,7 @@ from openedx.core.lib.edx_api_utils import get_edx_api_data
from openedx.core.lib.time_zone_utils import TIME_ZONE_CHOICES
from openedx.features.enterprise_support.api import enterprise_customer_for_request, get_enterprise_learner_data
from student.cookies import set_experiments_is_enterprise_cookie
+from openedx.features.enterprise_support.utils import update_third_party_auth_context_for_enterprise
from student.helpers import destroy_oauth_tokens, get_next_url_for_login_page
from student.models import UserProfile
from student.views import register_user as old_register_view
@@ -388,6 +389,8 @@ def _third_party_auth_context(request, redirect_to, tpa_hint=None):
context['errorMessage'] = _(unicode(msg)) # pylint: disable=translation-of-non-string
break
+ context = update_third_party_auth_context_for_enterprise(context, enterprise_customer)
+
return context
diff --git a/openedx/features/enterprise_support/utils.py b/openedx/features/enterprise_support/utils.py
index 614f027d9a..8878f2da04 100644
--- a/openedx/features/enterprise_support/utils.py
+++ b/openedx/features/enterprise_support/utils.py
@@ -3,6 +3,10 @@ from __future__ import unicode_literals
import hashlib
import six
+from django.conf import settings
+from django.utils.translation import ugettext as _
+from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
+from openedx.core.djangolib.markup import HTML, Text
def get_cache_key(**kwargs):
@@ -27,3 +31,40 @@ def get_cache_key(**kwargs):
key = '__'.join(['{}:{}'.format(item, value) for item, value in six.iteritems(kwargs)])
return hashlib.md5(key).hexdigest()
+
+
+def update_third_party_auth_context_for_enterprise(context, enterprise_customer=None):
+ """
+ Return updated context of third party auth with modified for enterprise.
+
+ Arguments:
+ context (dict): Context for third party auth providers and auth pipeline.
+ enterprise_customer (dict): data for enterprise customer
+
+ Returns:
+ context (dict): Updated context of third party auth with modified
+ `errorMessage`.
+ """
+ if enterprise_customer and context['errorMessage']:
+ context['errorMessage'] = Text(_(
+ u'We are sorry, you are not authorized to access {platform_name} via this channel. '
+ u'Please contact your {enterprise} administrator in order to access {platform_name} '
+ u'or contact {edx_support_link}.{line_break}'
+ u'{line_break}'
+ u'Error Details:{line_break}{error_message}')
+ ).format(
+ platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
+ enterprise=enterprise_customer['name'],
+ error_message=context['errorMessage'],
+ edx_support_link=HTML(
+ '{support_url_name}'
+ ).format(
+ edx_support_url=configuration_helpers.get_value(
+ 'SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK
+ ),
+ support_url_name=_('edX Support'),
+ ),
+ line_break=HTML('
')
+ )
+
+ return context