From f840774169386d688aea874708f3fd531d7aa180 Mon Sep 17 00:00:00 2001 From: Nimisha Asthagiri <nasthagiri@edx.org> Date: Sat, 22 Dec 2018 22:08:28 -0500 Subject: [PATCH] Fix Studio SSO ARCH-329 --- .../djangoapps/third_party_auth/settings.py | 21 +++++++------ .../third_party_auth/tests/specs/base.py | 30 ++++++++++++++----- .../tests/specs/test_testshib.py | 2 +- .../third_party_auth/tests/test_settings.py | 9 ++++-- lms/envs/devstack_docker.py | 1 + 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/common/djangoapps/third_party_auth/settings.py b/common/djangoapps/third_party_auth/settings.py index 632780842f4..4b0410d002d 100644 --- a/common/djangoapps/third_party_auth/settings.py +++ b/common/djangoapps/third_party_auth/settings.py @@ -12,23 +12,18 @@ If true, it: from openedx.features.enterprise_support.api import insert_enterprise_pipeline_elements -_FIELDS_STORED_IN_SESSION = ['auth_entry', 'next'] -_MIDDLEWARE_CLASSES = ['third_party_auth.middleware.ExceptionMiddleware'] -_SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/dashboard' -_SOCIAL_AUTH_AZUREAD_OAUTH2_AUTH_EXTRA_ARGUMENTS = { - 'msafed': 0 -} - def apply_settings(django_settings): """Set provider-independent settings.""" # Whitelisted URL query parameters retrained in the pipeline session. # Params not in this whitelist will be silently dropped. - django_settings.FIELDS_STORED_IN_SESSION = _FIELDS_STORED_IN_SESSION + django_settings.FIELDS_STORED_IN_SESSION = ['auth_entry', 'next'] # Inject exception middleware to make redirects fire. - django_settings.MIDDLEWARE_CLASSES.extend(_MIDDLEWARE_CLASSES) + django_settings.MIDDLEWARE_CLASSES.extend( + ['third_party_auth.middleware.ExceptionMiddleware'] + ) # Where to send the user if there's an error during social authentication # and we cannot send them to a more specific URL @@ -36,10 +31,14 @@ def apply_settings(django_settings): django_settings.SOCIAL_AUTH_LOGIN_ERROR_URL = '/' # Where to send the user once social authentication is successful. - django_settings.SOCIAL_AUTH_LOGIN_REDIRECT_URL = _SOCIAL_AUTH_LOGIN_REDIRECT_URL + django_settings.SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/dashboard' + + # Disable sanitizing of redirect urls in social-auth since the platform + # already does its own sanitization via the LOGIN_REDIRECT_WHITELIST setting. + django_settings.SOCIAL_AUTH_SANITIZE_REDIRECTS = False # Adding extra key value pair in the url query string for microsoft as per request - django_settings.SOCIAL_AUTH_AZUREAD_OAUTH2_AUTH_EXTRA_ARGUMENTS = _SOCIAL_AUTH_AZUREAD_OAUTH2_AUTH_EXTRA_ARGUMENTS + django_settings.SOCIAL_AUTH_AZUREAD_OAUTH2_AUTH_EXTRA_ARGUMENTS = {'msafed': 0} # Inject our customized auth pipeline. All auth backends must work with # this pipeline. diff --git a/common/djangoapps/third_party_auth/tests/specs/base.py b/common/djangoapps/third_party_auth/tests/specs/base.py index 0a01fddc174..190e8edfe5a 100644 --- a/common/djangoapps/third_party_auth/tests/specs/base.py +++ b/common/djangoapps/third_party_auth/tests/specs/base.py @@ -161,12 +161,14 @@ class HelperMixin(object): """Makes sure the given request is running an auth pipeline.""" self.assertTrue(pipeline.running(request)) - def assert_redirect_to_dashboard_looks_correct(self, response): - """Asserts a response would redirect to /dashboard.""" + def assert_redirect_after_pipeline_completes(self, response, expected_redirect_url=None): + """Asserts a response would redirect to the expected_redirect_url or SOCIAL_AUTH_LOGIN_REDIRECT_URL.""" self.assertEqual(302, response.status_code) # NOTE: Ideally we should use assertRedirects(), however it errors out due to the hostname, testserver, # not being properly set. This may be an issue with the call made by PSA, but we are not certain. - self.assertTrue(response.get('Location').endswith(django_settings.SOCIAL_AUTH_LOGIN_REDIRECT_URL)) + self.assertTrue(response.get('Location').endswith( + expected_redirect_url or django_settings.SOCIAL_AUTH_LOGIN_REDIRECT_URL, + )) def assert_redirect_to_login_looks_correct(self, response): """Asserts a response would redirect to /login.""" @@ -560,7 +562,7 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin): self.set_logged_in_cookies(request) # Fire off the auth pipeline to link. - self.assert_redirect_to_dashboard_looks_correct( + self.assert_redirect_after_pipeline_completes( actions.do_complete( request.backend, social_views._do_login, # pylint: disable=protected-access @@ -606,7 +608,7 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin): self.assert_social_auth_exists_for_user(request.user, strategy) # Fire off the disconnect pipeline to unlink. - self.assert_redirect_to_dashboard_looks_correct( + self.assert_redirect_after_pipeline_completes( actions.do_disconnect( request.backend, request.user, @@ -725,7 +727,7 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin): # Set the cookie and try again self.set_logged_in_cookies(request) - self.assert_redirect_to_dashboard_looks_correct( + self.assert_redirect_after_pipeline_completes( actions.do_complete(request.backend, social_views._do_login, user=user, request=request)) self.assert_account_settings_context_looks_correct(account_settings_context(request)) @@ -765,6 +767,20 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin): self.assert_first_party_auth_trumps_third_party_auth( email='user@example.com', password=u'password', success=True) + def test_pipeline_redirects_to_requested_url(self): + requested_redirect_url = 'foo' # something different from '/dashboard' + request, strategy = self.get_request_and_strategy(redirect_uri='social:complete') + strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy)) + request.session[pipeline.AUTH_REDIRECT_KEY] = requested_redirect_url + + user = self.create_user_models_for_existing_account(strategy, 'user@foo.com', 'password', self.get_username()) + self.set_logged_in_cookies(request) + + self.assert_redirect_after_pipeline_completes( + actions.do_complete(request.backend, social_views._do_login, user=user, request=request), # pylint: disable=protected-access + requested_redirect_url, + ) + def test_full_pipeline_succeeds_registering_new_account(self): # First, create, the request and strategy that store pipeline state. # Mock out wire traffic. @@ -831,7 +847,7 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin): # Set the cookie and try again self.set_logged_in_cookies(request) - self.assert_redirect_to_dashboard_looks_correct( + self.assert_redirect_after_pipeline_completes( actions.do_complete(strategy.request.backend, social_views._do_login, user=created_user, request=request)) # Now the user has been redirected to the dashboard. Their third party account should now be linked. self.assert_social_auth_exists_for_user(created_user, strategy) diff --git a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py index c81823d3d96..e195e3efd21 100644 --- a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py +++ b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py @@ -212,7 +212,7 @@ class TestShibIntegrationTest(SamlIntegrationTestUtilities, IntegrationTestMixin ) # Fire off the disconnect pipeline to unlink. - self.assert_redirect_to_dashboard_looks_correct( + self.assert_redirect_after_pipeline_completes( actions.do_disconnect( request.backend, user, diff --git a/common/djangoapps/third_party_auth/tests/test_settings.py b/common/djangoapps/third_party_auth/tests/test_settings.py index b08472bff0f..e36c3c8b9dd 100644 --- a/common/djangoapps/third_party_auth/tests/test_settings.py +++ b/common/djangoapps/third_party_auth/tests/test_settings.py @@ -38,12 +38,11 @@ class SettingsUnitTest(testutil.TestCase): def test_apply_settings_adds_exception_middleware(self): settings.apply_settings(self.settings) - for middleware_name in settings._MIDDLEWARE_CLASSES: - self.assertIn(middleware_name, self.settings.MIDDLEWARE_CLASSES) + self.assertIn('third_party_auth.middleware.ExceptionMiddleware', self.settings.MIDDLEWARE_CLASSES) def test_apply_settings_adds_fields_stored_in_session(self): settings.apply_settings(self.settings) - self.assertEqual(settings._FIELDS_STORED_IN_SESSION, self.settings.FIELDS_STORED_IN_SESSION) + self.assertEqual(['auth_entry', 'next'], self.settings.FIELDS_STORED_IN_SESSION) @unittest.skipUnless(testutil.AUTH_FEATURE_ENABLED, testutil.AUTH_FEATURES_KEY + ' not enabled') def test_apply_settings_enables_no_providers_by_default(self): @@ -56,3 +55,7 @@ class SettingsUnitTest(testutil.TestCase): # bad in prod. settings.apply_settings(self.settings) self.assertFalse(self.settings.SOCIAL_AUTH_RAISE_EXCEPTIONS) + + def test_apply_settings_turns_off_redirect_sanitization(self): + settings.apply_settings(self.settings) + self.assertFalse(self.settings.SOCIAL_AUTH_SANITIZE_REDIRECTS) diff --git a/lms/envs/devstack_docker.py b/lms/envs/devstack_docker.py index 98650770f04..28b97fe049c 100644 --- a/lms/envs/devstack_docker.py +++ b/lms/envs/devstack_docker.py @@ -14,6 +14,7 @@ CMS_BASE = 'localhost:18010' SITE_NAME = LMS_BASE LMS_ROOT_URL = 'http://{}'.format(LMS_BASE) LMS_INTERNAL_ROOT_URL = LMS_ROOT_URL +LOGIN_REDIRECT_WHITELIST = [CMS_BASE] ECOMMERCE_PUBLIC_URL_ROOT = 'http://localhost:18130' ECOMMERCE_API_URL = 'http://edx.devstack.ecommerce:18130/api/v2' -- GitLab