This completes the work started in https://github.com/edx/edx-platform/pull/19453 to use the LMS login and registration for Studio, rather than Studio providing its own implementation. LMS login/registration are being used for the following reasons: 1. LMS logistration properly handles all SSO integrations. 2. A single logistration is simpler to maintain and understand. 3. Allows Studio to work more like all other IDAs that use LMS logistration. The original switch to use LMS logistration for Studio also added the toggle `DISABLE_STUDIO_SSO_OVER_LMS` to provide the community some additional time for switching. This commit removes this toggle, which at this point means all deployments will use the LMS logistration. This change requires sharing cookies across LMS and Studio. Should that prove to be a problem for certain Open edX instances, there are discussions of possible alternative solutions. See https://github.com/edx/edx-platform/pull/19845#issuecomment-559154256 Detailed changes: * Fix some Studio links that still went to old Studio signin and signup. * Remove DISABLE_STUDIO_SSO_OVER_LMS feature toggle. * Remove old studio signin and signup pages and templates. * Fix url name "login", which had different meanings for Studio and LMS. * Use the following settings: LOGIN_URL, FRONTEND_LOGIN_URL, FRONTEND_LOGOUT_URL, and FRONTEND_REGISTER_URL. * Redirect /signin and /signup to the LMS logistration. * Add custom metric `uses_pattern_library`. * Add custom metric `student_activate_account`. * Add Django Settings to allow /signin, /signup, and /login_post to be disabled once ready. This work also relates to ARCH-218 and DEPR-6. ARCH-1253
116 lines
5.5 KiB
Python
116 lines
5.5 KiB
Python
""" Test Student helpers """
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import logging
|
|
|
|
import ddt
|
|
from django.conf import settings
|
|
from django.contrib.sessions.middleware import SessionMiddleware
|
|
from django.test import TestCase
|
|
from django.test.client import RequestFactory
|
|
from django.test.utils import override_settings
|
|
from mock import patch
|
|
from testfixtures import LogCapture
|
|
|
|
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration_context
|
|
from student.helpers import get_next_url_for_login_page
|
|
|
|
LOGGER_NAME = "student.helpers"
|
|
|
|
|
|
@ddt.ddt
|
|
class TestLoginHelper(TestCase):
|
|
"""Test login helper methods."""
|
|
static_url = settings.STATIC_URL
|
|
|
|
def setUp(self):
|
|
super(TestLoginHelper, self).setUp()
|
|
self.request = RequestFactory()
|
|
|
|
@staticmethod
|
|
def _add_session(request):
|
|
"""Annotate the request object with a session"""
|
|
middleware = SessionMiddleware()
|
|
middleware.process_request(request)
|
|
request.session.save()
|
|
|
|
@ddt.data(
|
|
(logging.WARNING, "WARNING", "https://www.amazon.com", "text/html", None,
|
|
"Unsafe redirect parameter detected after login page: 'https://www.amazon.com'"),
|
|
(logging.WARNING, "WARNING", "testserver/edx.org/images/logo", "text/html", None,
|
|
"Redirect to theme content detected after login page: 'testserver/edx.org/images/logo'"),
|
|
(logging.INFO, "INFO", "favicon.ico", "image/*", "test/agent",
|
|
"Redirect to non html content 'image/*' detected from 'test/agent' after login page: 'favicon.ico'"),
|
|
(logging.WARNING, "WARNING", "https://www.test.com/test.jpg", "image/*", None,
|
|
"Unsafe redirect parameter detected after login page: 'https://www.test.com/test.jpg'"),
|
|
(logging.INFO, "INFO", static_url + "dummy.png", "image/*", "test/agent",
|
|
"Redirect to non html content 'image/*' detected from 'test/agent' after login page: '" + static_url +
|
|
"dummy.png" + "'"),
|
|
(logging.WARNING, "WARNING", "test.png", "text/html", None,
|
|
"Redirect to url path with specified filed type 'image/png' not allowed: 'test.png'"),
|
|
(logging.WARNING, "WARNING", static_url + "dummy.png", "text/html", None,
|
|
"Redirect to url path with specified filed type 'image/png' not allowed: '" + static_url + "dummy.png" + "'"),
|
|
)
|
|
@ddt.unpack
|
|
def test_next_failures(self, log_level, log_name, unsafe_url, http_accept, user_agent, expected_log):
|
|
""" Test unsafe next parameter """
|
|
with LogCapture(LOGGER_NAME, level=log_level) as logger:
|
|
req = self.request.get(settings.LOGIN_URL + "?next={url}".format(url=unsafe_url))
|
|
req.META["HTTP_ACCEPT"] = http_accept # pylint: disable=no-member
|
|
req.META["HTTP_USER_AGENT"] = user_agent # pylint: disable=no-member
|
|
get_next_url_for_login_page(req)
|
|
logger.check(
|
|
(LOGGER_NAME, log_name, expected_log)
|
|
)
|
|
|
|
@ddt.data(
|
|
('/dashboard', 'testserver'),
|
|
('https://edx.org/courses', 'edx.org'),
|
|
('https://test.edx.org/courses', 'edx.org'),
|
|
('https://test2.edx.org/courses', 'edx.org'),
|
|
)
|
|
@ddt.unpack
|
|
@override_settings(LOGIN_REDIRECT_WHITELIST=['test.edx.org', 'test2.edx.org'])
|
|
def test_safe_next(self, next_url, host):
|
|
""" Test safe next parameter """
|
|
req = self.request.get(settings.LOGIN_URL + "?next={url}".format(url=next_url), HTTP_HOST=host)
|
|
req.META["HTTP_ACCEPT"] = "text/html" # pylint: disable=no-member
|
|
next_page = get_next_url_for_login_page(req)
|
|
self.assertEqual(next_page, next_url)
|
|
|
|
@patch('student.helpers.third_party_auth.pipeline.get')
|
|
@ddt.data(
|
|
# Test requests outside the TPA pipeline - tpa_hint should be added.
|
|
(None, '/dashboard', '/dashboard', False),
|
|
('', '/dashboard', '/dashboard', False),
|
|
('', '/dashboard?tpa_hint=oa2-google-oauth2', '/dashboard?tpa_hint=oa2-google-oauth2', False),
|
|
('saml-idp', '/dashboard', '/dashboard?tpa_hint=saml-idp', False),
|
|
# THIRD_PARTY_AUTH_HINT can be overridden via the query string
|
|
('saml-idp', '/dashboard?tpa_hint=oa2-google-oauth2', '/dashboard?tpa_hint=oa2-google-oauth2', False),
|
|
|
|
# Test requests inside the TPA pipeline - tpa_hint should not be added, preventing infinite loop.
|
|
(None, '/dashboard', '/dashboard', True),
|
|
('', '/dashboard', '/dashboard', True),
|
|
('', '/dashboard?tpa_hint=oa2-google-oauth2', '/dashboard?tpa_hint=oa2-google-oauth2', True),
|
|
('saml-idp', '/dashboard', '/dashboard', True),
|
|
# OK to leave tpa_hint overrides in place.
|
|
('saml-idp', '/dashboard?tpa_hint=oa2-google-oauth2', '/dashboard?tpa_hint=oa2-google-oauth2', True),
|
|
)
|
|
@ddt.unpack
|
|
def test_third_party_auth_hint(self, tpa_hint, next_url, expected_url, running_pipeline, mock_running_pipeline):
|
|
mock_running_pipeline.return_value = running_pipeline
|
|
|
|
def validate_login():
|
|
req = self.request.get(settings.LOGIN_URL + "?next={url}".format(url=next_url))
|
|
req.META["HTTP_ACCEPT"] = "text/html" # pylint: disable=no-member
|
|
self._add_session(req)
|
|
next_page = get_next_url_for_login_page(req)
|
|
self.assertEqual(next_page, expected_url)
|
|
|
|
with override_settings(FEATURES=dict(settings.FEATURES, THIRD_PARTY_AUTH_HINT=tpa_hint)):
|
|
validate_login()
|
|
|
|
with with_site_configuration_context(configuration=dict(THIRD_PARTY_AUTH_HINT=tpa_hint)):
|
|
validate_login()
|