Merge pull request #20295 from edx/mroytman/EDUCATOR-4229-lms-redirection-idp
add LMS endpoint to redirect to identity provider
This commit is contained in:
30
common/djangoapps/third_party_auth/tests/test_saml.py
Normal file
30
common/djangoapps/third_party_auth/tests/test_saml.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Unit tests for third_party_auth SAML auth providers
|
||||
"""
|
||||
|
||||
import mock
|
||||
|
||||
from third_party_auth.tests.testutil import SAMLTestCase
|
||||
from third_party_auth.saml import EdXSAMLIdentityProvider, get_saml_idp_class
|
||||
from third_party_auth.tests.data.saml_identity_provider_mock_data import mock_conf, mock_attributes,\
|
||||
expected_user_details
|
||||
|
||||
|
||||
class TestEdXSAMLIdentityProvider(SAMLTestCase):
|
||||
"""
|
||||
Test EdXSAMLIdentityProvider.
|
||||
"""
|
||||
@mock.patch('third_party_auth.saml.log')
|
||||
def test_get_saml_idp_class_with_fake_identifier(self, log_mock):
|
||||
error_mock = log_mock.error
|
||||
idp_class = get_saml_idp_class('fake_idp_class_option')
|
||||
error_mock.assert_called_once_with(
|
||||
u'%s is not a valid EdXSAMLIdentityProvider subclass; using EdXSAMLIdentityProvider base class.',
|
||||
'fake_idp_class_option'
|
||||
)
|
||||
self.assertIs(idp_class, EdXSAMLIdentityProvider)
|
||||
|
||||
def test_get_user_details(self):
|
||||
""" test get_attr and get_user_details of EdXSAMLIdentityProvider"""
|
||||
edx_saml_identity_provider = EdXSAMLIdentityProvider('demo', **mock_conf)
|
||||
self.assertEqual(edx_saml_identity_provider.get_user_details(mock_attributes), expected_user_details)
|
||||
@@ -6,9 +6,11 @@ import unittest
|
||||
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from lxml import etree
|
||||
from onelogin.saml2.errors import OneLogin_Saml2_Error
|
||||
|
||||
from third_party_auth import pipeline
|
||||
# Define some XML namespaces:
|
||||
from third_party_auth.tasks import SAML_XML_NS
|
||||
|
||||
@@ -53,8 +55,8 @@ class SAMLMetadataTest(SAMLTestCase):
|
||||
self.enable_saml(
|
||||
other_config_str=(
|
||||
'{'
|
||||
'"TECHNICAL_CONTACT": {"givenName": "Jane Tech", "emailAddress": "jane@example.com"},' # pylint: disable=unicode-format-string,line-too-long
|
||||
'"SUPPORT_CONTACT": {"givenName": "Joe Support", "emailAddress": "joe@example.com"}' # pylint: disable=unicode-format-string,line-too-long
|
||||
'"TECHNICAL_CONTACT": {"givenName": "Jane Tech", "emailAddress": "jane@example.com"},' # pylint: disable=unicode-format-string
|
||||
'"SUPPORT_CONTACT": {"givenName": "Joe Support", "emailAddress": "joe@example.com"}' # pylint: disable=unicode-format-string
|
||||
'}'
|
||||
)
|
||||
)
|
||||
@@ -153,3 +155,42 @@ class SAMLAuthTest(SAMLTestCase):
|
||||
self.enable_saml(enabled=False)
|
||||
response = self.client.get(self.LOGIN_URL)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
@unittest.skipUnless(AUTH_FEATURE_ENABLED, AUTH_FEATURES_KEY + ' not enabled')
|
||||
class IdPRedirectViewTest(SAMLTestCase):
|
||||
"""
|
||||
Test IdPRedirectView.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(IdPRedirectViewTest, self).setUp()
|
||||
|
||||
self.enable_saml()
|
||||
self.configure_saml_provider(
|
||||
name="Test",
|
||||
slug="test",
|
||||
enabled=True,
|
||||
)
|
||||
|
||||
def test_with_valid_provider_slug(self):
|
||||
endpoint_url = self.get_idp_redirect_url('saml-test')
|
||||
expected_url = pipeline.get_login_url('saml-test', pipeline.AUTH_ENTRY_LOGIN, reverse('dashboard'))
|
||||
|
||||
response = self.client.get(endpoint_url)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, expected_url)
|
||||
|
||||
def test_with_invalid_provider_slug(self):
|
||||
endpoint_url = self.get_idp_redirect_url('saml-test-invalid')
|
||||
|
||||
response = self.client.get(endpoint_url)
|
||||
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@staticmethod
|
||||
def get_idp_redirect_url(provider_slug, next_destination=None):
|
||||
return '{idp_redirect_url}?{next_destination}'.format(
|
||||
idp_redirect_url=reverse('idp_redirect', kwargs={'provider_slug': provider_slug}),
|
||||
next_destination=next_destination,
|
||||
)
|
||||
|
||||
@@ -25,9 +25,6 @@ from third_party_auth.models import (
|
||||
SAMLConfiguration,
|
||||
SAMLProviderConfig
|
||||
)
|
||||
from third_party_auth.saml import EdXSAMLIdentityProvider, get_saml_idp_class
|
||||
from third_party_auth.tests.data.saml_identity_provider_mock_data import mock_conf, mock_attributes,\
|
||||
expected_user_details
|
||||
|
||||
AUTH_FEATURES_KEY = 'ENABLE_THIRD_PARTY_AUTH'
|
||||
AUTH_FEATURE_ENABLED = AUTH_FEATURES_KEY in settings.FEATURES
|
||||
@@ -217,21 +214,6 @@ class SAMLTestCase(TestCase):
|
||||
kwargs.setdefault('entity_id', "https://saml.example.none")
|
||||
super(SAMLTestCase, self).enable_saml(**kwargs)
|
||||
|
||||
@mock.patch('third_party_auth.saml.log')
|
||||
def test_get_saml_idp_class_with_fake_identifier(self, log_mock):
|
||||
error_mock = log_mock.error
|
||||
idp_class = get_saml_idp_class('fake_idp_class_option')
|
||||
error_mock.assert_called_once_with(
|
||||
u'%s is not a valid EdXSAMLIdentityProvider subclass; using EdXSAMLIdentityProvider base class.',
|
||||
'fake_idp_class_option'
|
||||
)
|
||||
self.assertIs(idp_class, EdXSAMLIdentityProvider)
|
||||
|
||||
def test_get_user_details(self):
|
||||
""" test get_attr and get_user_details of EdXSAMLIdentityProvider"""
|
||||
edx_smal_identity_provider = EdXSAMLIdentityProvider('demo', **mock_conf)
|
||||
self.assertEqual(edx_smal_identity_provider.get_user_details(mock_attributes), expected_user_details)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def simulate_running_pipeline(pipeline_target, backend, email=None, fullname=None, username=None, **kwargs):
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
from django.conf.urls import include, url
|
||||
|
||||
from .views import inactive_user_view, lti_login_and_complete_view, post_to_custom_auth_form, saml_metadata_view
|
||||
from .views import (inactive_user_view, lti_login_and_complete_view,
|
||||
post_to_custom_auth_form, saml_metadata_view, IdPRedirectView)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^auth/inactive', inactive_user_view, name="third_party_inactive_redirect"),
|
||||
url(r'^auth/custom_auth_entry', post_to_custom_auth_form, name='tpa_post_to_custom_auth_form'),
|
||||
url(r'^auth/saml/metadata.xml', saml_metadata_view),
|
||||
url(r'^auth/login/(?P<backend>lti)/$', lti_login_and_complete_view),
|
||||
url(r'^auth/idp_redirect/(?P<provider_slug>[\w-]+)', IdPRedirectView.as_view(), name="idp_redirect"),
|
||||
url(r'^auth/', include('social_django.urls', namespace='social')),
|
||||
]
|
||||
|
||||
@@ -3,13 +3,15 @@ Extra views required for SSO
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.http import Http404, HttpResponse, HttpResponseNotAllowed, HttpResponseServerError
|
||||
from django.http import Http404, HttpResponse, HttpResponseNotAllowed, HttpResponseServerError, HttpResponseNotFound
|
||||
from django.shortcuts import redirect, render
|
||||
from django.views.generic.base import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from social_django.utils import load_strategy, load_backend, psa
|
||||
from social_django.views import complete
|
||||
from social_core.utils import setting_name
|
||||
|
||||
from student.helpers import get_next_url_for_login_page
|
||||
from student.models import UserProfile
|
||||
from student.views import compose_and_send_activation_email
|
||||
import third_party_auth
|
||||
@@ -110,3 +112,41 @@ def post_to_custom_auth_form(request):
|
||||
'hmac': pipeline_data['hmac'],
|
||||
}
|
||||
return render(request, 'third_party_auth/post_custom_auth_entry.html', data)
|
||||
|
||||
|
||||
class IdPRedirectView(View):
|
||||
"""
|
||||
Redirect to an IdP's login page if the IdP exists; otherwise, return a 404.
|
||||
|
||||
Example usage:
|
||||
|
||||
GET auth/idp_redirect/saml-default
|
||||
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Return either a redirect to the login page of an identity provider that
|
||||
corresponds to the provider_slug keyword argument or a 404 if the
|
||||
provider_slug does not correspond to an identity provider.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
|
||||
Keyword Args:
|
||||
provider_slug (str): a slug corresponding to a configured identity provider
|
||||
|
||||
Returns:
|
||||
HttpResponse: 302 to a provider's login url if the provider_slug kwarg matches an identity provider
|
||||
HttpResponse: 404 if the provider_slug kwarg does not match an identity provider
|
||||
"""
|
||||
# this gets the url to redirect to after login/registration/third_party_auth
|
||||
# it also handles checking the safety of the redirect url (next query parameter)
|
||||
# it checks against settings.LOGIN_REDIRECT_WHITELIST, so be sure to add the url
|
||||
# to this setting
|
||||
next_destination_url = get_next_url_for_login_page(request)
|
||||
|
||||
try:
|
||||
url = pipeline.get_login_url(kwargs['provider_slug'], pipeline.AUTH_ENTRY_LOGIN, next_destination_url)
|
||||
return redirect(url)
|
||||
except ValueError:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
Reference in New Issue
Block a user