Moves external_auth from common to openedx/core.
This commit is contained in:
@@ -10,8 +10,11 @@ from django.conf import settings
|
||||
|
||||
from edxmako.shortcuts import render_to_response
|
||||
|
||||
from external_auth.views import (ssl_login_shortcut, ssl_get_cert_from_request,
|
||||
redirect_with_get)
|
||||
from openedx.core.djangoapps.external_auth.views import (
|
||||
ssl_login_shortcut,
|
||||
ssl_get_cert_from_request,
|
||||
redirect_with_get,
|
||||
)
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
|
||||
__all__ = ['signup', 'login_page', 'howitworks']
|
||||
|
||||
@@ -817,7 +817,7 @@ INSTALLED_APPS = (
|
||||
'contentstore',
|
||||
'contentserver',
|
||||
'course_creators',
|
||||
'external_auth',
|
||||
'openedx.core.djangoapps.external_auth',
|
||||
'student', # misleading name due to sharing with lms
|
||||
'openedx.core.djangoapps.course_groups', # not used in cms (yet), but tests run
|
||||
'openedx.core.djangoapps.coursetalk', # not used in cms (yet), but tests run
|
||||
|
||||
@@ -153,7 +153,7 @@ if settings.FEATURES.get('ENABLE_SERVICE_STATUS'):
|
||||
|
||||
if settings.FEATURES.get('AUTH_USE_CAS'):
|
||||
urlpatterns += (
|
||||
url(r'^cas-auth/login/$', 'external_auth.views.cas_login', name="cas-login"),
|
||||
url(r'^cas-auth/login/$', 'openedx.core.djangoapps.external_auth.views.cas_login', name="cas-login"),
|
||||
url(r'^cas-auth/logout/$', 'django_cas.views.logout', {'next_page': '/'}, name="cas-logout"),
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import mock
|
||||
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
from notification_prefs import NOTIFICATION_PREF_KEY
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
import student
|
||||
from student.models import UserAttribute
|
||||
from student.views import REGISTRATION_AFFILIATE_ID
|
||||
|
||||
@@ -16,7 +16,7 @@ import httpretty
|
||||
from mock import patch
|
||||
from social.apps.django_app.default.models import UserSocialAuth
|
||||
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory, RegistrationFactory, UserProfileFactory
|
||||
from student.views import login_oauth_token
|
||||
|
||||
@@ -11,7 +11,7 @@ from importlib import import_module
|
||||
from django.test.utils import override_settings
|
||||
from django.conf import settings
|
||||
from mock import patch
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from student.views import create_account
|
||||
|
||||
|
||||
|
||||
@@ -78,9 +78,9 @@ from courseware.access import has_access
|
||||
|
||||
from django_comment_common.models import Role
|
||||
|
||||
from external_auth.models import ExternalAuthMap
|
||||
import external_auth.views
|
||||
from external_auth.login_and_register import (
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
import openedx.core.djangoapps.external_auth.views
|
||||
from openedx.core.djangoapps.external_auth.login_and_register import (
|
||||
login as external_auth_login,
|
||||
register as external_auth_register
|
||||
)
|
||||
@@ -470,7 +470,9 @@ def register_user(request, extra_context=None):
|
||||
if extra_context is not None:
|
||||
context.update(extra_context)
|
||||
|
||||
if context.get("extauth_domain", '').startswith(external_auth.views.SHIBBOLETH_DOMAIN_PREFIX):
|
||||
if context.get("extauth_domain", '').startswith(
|
||||
openedx.core.djangoapps.external_auth.views.SHIBBOLETH_DOMAIN_PREFIX
|
||||
):
|
||||
return render_to_response('register-shib.html', context)
|
||||
|
||||
# If third-party auth is enabled, prepopulate the form with data from the
|
||||
@@ -1195,7 +1197,7 @@ def login_user(request, error=""): # pylint: disable=too-many-statements,unused
|
||||
if settings.FEATURES.get('AUTH_USE_SHIB') and user:
|
||||
try:
|
||||
eamap = ExternalAuthMap.objects.get(user=user)
|
||||
if eamap.external_domain.startswith(external_auth.views.SHIBBOLETH_DOMAIN_PREFIX):
|
||||
if eamap.external_domain.startswith(openedx.core.djangoapps.external_auth.views.SHIBBOLETH_DOMAIN_PREFIX):
|
||||
return JsonResponse({
|
||||
"success": False,
|
||||
"redirect": reverse('shib-login'),
|
||||
@@ -1637,9 +1639,7 @@ def create_account_with_params(request, params):
|
||||
not settings.FEATURES.get("AUTH_USE_SHIB") or
|
||||
not settings.FEATURES.get("SHIB_DISABLE_TOS") or
|
||||
not do_external_auth or
|
||||
not eamap.external_domain.startswith(
|
||||
external_auth.views.SHIBBOLETH_DOMAIN_PREFIX
|
||||
)
|
||||
not eamap.external_domain.startswith(openedx.core.djangoapps.external_auth.views.SHIBBOLETH_DOMAIN_PREFIX)
|
||||
)
|
||||
|
||||
form = AccountCreationForm(
|
||||
|
||||
@@ -101,7 +101,7 @@ MOCK_MODULES = [
|
||||
'openid',
|
||||
'openid.store',
|
||||
'openid.store.interface',
|
||||
'external_auth.views',
|
||||
'openedx.core.djangoapps.external_auth.views',
|
||||
'mail_utils',
|
||||
'ratelimitbackend.backends',
|
||||
'social.apps.django_app.default',
|
||||
|
||||
@@ -61,7 +61,7 @@ def index(request):
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
if settings.FEATURES.get('AUTH_USE_CERTIFICATES'):
|
||||
from external_auth.views import ssl_login
|
||||
from openedx.core.djangoapps.external_auth.views import ssl_login
|
||||
# Set next URL to dashboard if it isn't set to avoid
|
||||
# caching a redirect to / that causes a redirect loop on logout
|
||||
if not request.GET.get('next'):
|
||||
|
||||
@@ -33,7 +33,7 @@ from xmodule.x_module import XModule
|
||||
from xmodule.split_test_module import get_split_user_partitions
|
||||
from xmodule.partitions.partitions import NoSuchUserPartitionError, NoSuchUserPartitionGroupError
|
||||
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from courseware.masquerade import get_masquerade_role, is_masquerading_as_student
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from student import auth
|
||||
|
||||
@@ -34,8 +34,8 @@ import dashboard.git_import as git_import
|
||||
from dashboard.git_import import GitImportError
|
||||
from student.roles import CourseStaffRole, CourseInstructorRole
|
||||
from dashboard.models import CourseImportLog
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from external_auth.views import generate_password
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangoapps.external_auth.views import generate_password
|
||||
from student.models import CourseEnrollment, UserProfile, Registration
|
||||
import track.views
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
@@ -15,7 +15,7 @@ import readline
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from student.models import UserProfile, Registration
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from django.contrib.auth.models import User, Group
|
||||
from pytz import UTC
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from edxmako.shortcuts import render_to_response
|
||||
import pytz
|
||||
|
||||
from commerce.models import CommerceConfiguration
|
||||
from external_auth.login_and_register import (
|
||||
from openedx.core.djangoapps.external_auth.login_and_register import (
|
||||
login as external_auth_login,
|
||||
register as external_auth_register
|
||||
)
|
||||
|
||||
@@ -1917,7 +1917,7 @@ INSTALLED_APPS = (
|
||||
'support',
|
||||
|
||||
# External auth (OpenID, shib)
|
||||
'external_auth',
|
||||
'openedx.core.djangoapps.external_auth',
|
||||
'django_openid_auth',
|
||||
|
||||
# django-oauth2-provider (deprecated)
|
||||
|
||||
34
lms/urls.py
34
lms/urls.py
@@ -803,27 +803,31 @@ if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
|
||||
if settings.FEATURES.get('AUTH_USE_OPENID'):
|
||||
urlpatterns += (
|
||||
url(r'^openid/login/$', 'django_openid_auth.views.login_begin', name='openid-login'),
|
||||
url(r'^openid/complete/$', 'external_auth.views.openid_login_complete', name='openid-complete'),
|
||||
url(
|
||||
r'^openid/complete/$',
|
||||
'openedx.core.djangoapps.external_auth.views.openid_login_complete',
|
||||
name='openid-complete',
|
||||
),
|
||||
url(r'^openid/logo.gif$', 'django_openid_auth.views.logo', name='openid-logo'),
|
||||
)
|
||||
|
||||
if settings.FEATURES.get('AUTH_USE_SHIB'):
|
||||
urlpatterns += (
|
||||
url(r'^shib-login/$', 'external_auth.views.shib_login', name='shib-login'),
|
||||
url(r'^shib-login/$', 'openedx.core.djangoapps.external_auth.views.shib_login', name='shib-login'),
|
||||
)
|
||||
|
||||
if settings.FEATURES.get('AUTH_USE_CAS'):
|
||||
urlpatterns += (
|
||||
url(r'^cas-auth/login/$', 'external_auth.views.cas_login', name="cas-login"),
|
||||
url(r'^cas-auth/login/$', 'openedx.core.djangoapps.external_auth.views.cas_login', name="cas-login"),
|
||||
url(r'^cas-auth/logout/$', 'django_cas.views.logout', {'next_page': '/'}, name="cas-logout"),
|
||||
)
|
||||
|
||||
if settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD'):
|
||||
urlpatterns += (
|
||||
url(r'^course_specific_login/{}/$'.format(settings.COURSE_ID_PATTERN),
|
||||
'external_auth.views.course_specific_login', name='course-specific-login'),
|
||||
'openedx.core.djangoapps.external_auth.views.course_specific_login', name='course-specific-login'),
|
||||
url(r'^course_specific_register/{}/$'.format(settings.COURSE_ID_PATTERN),
|
||||
'external_auth.views.course_specific_register', name='course-specific-register'),
|
||||
'openedx.core.djangoapps.external_auth.views.course_specific_register', name='course-specific-register'),
|
||||
|
||||
)
|
||||
|
||||
@@ -846,14 +850,26 @@ urlpatterns += (
|
||||
|
||||
if settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'):
|
||||
urlpatterns += (
|
||||
url(r'^openid/provider/login/$', 'external_auth.views.provider_login', name='openid-provider-login'),
|
||||
url(
|
||||
r'^openid/provider/login/$',
|
||||
'openedx.core.djangoapps.external_auth.views.provider_login',
|
||||
name='openid-provider-login',
|
||||
),
|
||||
url(
|
||||
r'^openid/provider/login/(?:.+)$',
|
||||
'external_auth.views.provider_identity',
|
||||
'openedx.core.djangoapps.external_auth.views.provider_identity',
|
||||
name='openid-provider-login-identity'
|
||||
),
|
||||
url(r'^openid/provider/identity/$', 'external_auth.views.provider_identity', name='openid-provider-identity'),
|
||||
url(r'^openid/provider/xrds/$', 'external_auth.views.provider_xrds', name='openid-provider-xrds')
|
||||
url(
|
||||
r'^openid/provider/identity/$',
|
||||
'openedx.core.djangoapps.external_auth.views.provider_identity',
|
||||
name='openid-provider-identity',
|
||||
),
|
||||
url(
|
||||
r'^openid/provider/xrds/$',
|
||||
'openedx.core.djangoapps.external_auth.views.provider_xrds',
|
||||
name='openid-provider-xrds',
|
||||
),
|
||||
)
|
||||
|
||||
if settings.FEATURES.get('ENABLE_OAUTH2_PROVIDER'):
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
django admin pages for courseware model
|
||||
'''
|
||||
|
||||
from external_auth.models import *
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from ratelimitbackend import admin
|
||||
|
||||
|
||||
class ExternalAuthMapAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin model for ExternalAuthMap
|
||||
"""
|
||||
search_fields = ['external_id', 'user__username']
|
||||
date_hierarchy = 'dtcreated'
|
||||
|
||||
@@ -18,19 +18,28 @@ log = logging.getLogger('DjangoOpenIDStore')
|
||||
|
||||
|
||||
def get_url_key(server_url):
|
||||
key = ASSOCIATIONS_KEY_PREFIX + server_url
|
||||
return key
|
||||
"""
|
||||
Returns the URL key for the given server_url.
|
||||
"""
|
||||
return ASSOCIATIONS_KEY_PREFIX + server_url
|
||||
|
||||
|
||||
def get_nonce_key(server_url, timestamp, salt):
|
||||
key = '{prefix}{url}.{ts}.{salt}'.format(prefix=NONCE_KEY_PREFIX,
|
||||
url=server_url,
|
||||
ts=timestamp,
|
||||
salt=salt)
|
||||
return key
|
||||
"""
|
||||
Returns the nonce for the given parameters.
|
||||
"""
|
||||
return '{prefix}{url}.{ts}.{salt}'.format(
|
||||
prefix=NONCE_KEY_PREFIX,
|
||||
url=server_url,
|
||||
ts=timestamp,
|
||||
salt=salt,
|
||||
)
|
||||
|
||||
|
||||
class DjangoOpenIDStore(OpenIDStore):
|
||||
"""
|
||||
django implementation of OpenIDStore.
|
||||
"""
|
||||
def __init__(self):
|
||||
log.info('DjangoStore cache:' + str(cache.__class__))
|
||||
|
||||
@@ -7,7 +7,7 @@ import re
|
||||
from django.conf import settings
|
||||
from django.shortcuts import redirect
|
||||
from django.core.urlresolvers import reverse
|
||||
import external_auth.views
|
||||
import openedx.core.djangoapps.external_auth.views
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
@@ -56,11 +56,14 @@ def login(request):
|
||||
# is not handling the request.
|
||||
response = None
|
||||
|
||||
if settings.FEATURES['AUTH_USE_CERTIFICATES'] and external_auth.views.ssl_get_cert_from_request(request):
|
||||
if (
|
||||
settings.FEATURES['AUTH_USE_CERTIFICATES'] and
|
||||
openedx.core.djangoapps.external_auth.views.ssl_get_cert_from_request(request)
|
||||
):
|
||||
# SSL login doesn't require a view, so redirect
|
||||
# branding and allow that to process the login if it
|
||||
# is enabled and the header is in the request.
|
||||
response = external_auth.views.redirect_with_get('root', request.GET)
|
||||
response = openedx.core.djangoapps.external_auth.views.redirect_with_get('root', request.GET)
|
||||
elif settings.FEATURES.get('AUTH_USE_CAS'):
|
||||
# If CAS is enabled, redirect auth handling to there
|
||||
response = redirect(reverse('cas-login'))
|
||||
@@ -69,7 +72,10 @@ def login(request):
|
||||
if redirect_to:
|
||||
course_id = _parse_course_id_from_string(redirect_to)
|
||||
if course_id and _get_course_enrollment_domain(course_id):
|
||||
response = external_auth.views.course_specific_login(request, course_id.to_deprecated_string())
|
||||
response = openedx.core.djangoapps.external_auth.views.course_specific_login(
|
||||
request,
|
||||
course_id.to_deprecated_string(),
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -88,5 +94,5 @@ def register(request):
|
||||
if settings.FEATURES.get('AUTH_USE_CERTIFICATES_IMMEDIATE_SIGNUP'):
|
||||
# Redirect to branding to process their certificate if SSL is enabled
|
||||
# and registration is disabled.
|
||||
response = external_auth.views.redirect_with_get('root', request.GET)
|
||||
response = openedx.core.djangoapps.external_auth.views.redirect_with_get('root', request.GET)
|
||||
return response
|
||||
@@ -6,7 +6,7 @@ file and check it in at the same time as your model changes. To do that,
|
||||
|
||||
1. Go to the edx-platform dir
|
||||
2. ./manage.py lms schemamigration student --auto description_of_your_change
|
||||
3. Add the migration file created in edx-platform/common/djangoapps/external_auth/migrations/
|
||||
3. Add the migration file created in edx-platform/openedx/core/djangoapps/external_auth/migrations/
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
@@ -14,6 +14,9 @@ from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class ExternalAuthMap(models.Model):
|
||||
"""
|
||||
Model class for external auth.
|
||||
"""
|
||||
class Meta(object):
|
||||
app_label = "external_auth"
|
||||
unique_together = (('external_id', 'external_domain'), )
|
||||
@@ -29,5 +32,4 @@ class ExternalAuthMap(models.Model):
|
||||
dtsignup = models.DateTimeField('signup date', null=True) # set after signup
|
||||
|
||||
def __unicode__(self):
|
||||
s = "[%s] = (%s / %s)" % (self.external_id, self.external_name, self.external_email)
|
||||
return s
|
||||
return "[%s] = (%s / %s)" % (self.external_id, self.external_name, self.external_email)
|
||||
@@ -2,7 +2,7 @@
|
||||
Tests for utility functions in external_auth module
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from external_auth.views import _safe_postlogin_redirect
|
||||
from openedx.core.djangoapps.external_auth.views import _safe_postlogin_redirect
|
||||
|
||||
|
||||
class ExternalAuthHelperFnTest(TestCase):
|
||||
@@ -17,7 +17,7 @@ from django.test.client import RequestFactory
|
||||
from unittest import skipUnless
|
||||
|
||||
from student.tests.factories import UserFactory
|
||||
from external_auth.views import provider_login
|
||||
from openedx.core.djangoapps.external_auth.views import provider_login
|
||||
|
||||
|
||||
class MyFetcher(HTTPFetcher):
|
||||
@@ -130,27 +130,53 @@ class OpenIdProviderTest(TestCase):
|
||||
self.assertEqual(resp.status_code, code,
|
||||
"got code {0} for url '{1}'. Expected code {2}"
|
||||
.format(resp.status_code, url, code))
|
||||
self.assertContains(resp, '<input name="openid.mode" type="hidden" value="checkid_setup" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ns" type="hidden" value="http://specs.openid.net/auth/2.0" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.identity" type="hidden" value="http://specs.openid.net/auth/2.0/identifier_select" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.claimed_id" type="hidden" value="http://specs.openid.net/auth/2.0/identifier_select" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ns.ax" type="hidden" value="http://openid.net/srv/ax/1.0" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.mode" type="hidden" value="fetch_request" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.required" type="hidden" value="email,fullname,old_email,firstname,old_nickname,lastname,old_fullname,nickname" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.type.fullname" type="hidden" value="http://axschema.org/namePerson" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.type.lastname" type="hidden" value="http://axschema.org/namePerson/last" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.type.firstname" type="hidden" value="http://axschema.org/namePerson/first" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.type.nickname" type="hidden" value="http://axschema.org/namePerson/friendly" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.type.email" type="hidden" value="http://axschema.org/contact/email" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.type.old_email" type="hidden" value="http://schema.openid.net/contact/email" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.type.old_nickname" type="hidden" value="http://schema.openid.net/namePerson/friendly" />', html=True)
|
||||
self.assertContains(resp, '<input name="openid.ax.type.old_fullname" type="hidden" value="http://schema.openid.net/namePerson" />', html=True)
|
||||
self.assertContains(resp, '<input type="submit" value="Continue" />', html=True)
|
||||
# this should work on the server:
|
||||
self.assertContains(resp, '<input name="openid.realm" type="hidden" value="http://testserver/" />', html=True)
|
||||
for expected_input in (
|
||||
'<input name="openid.ns" type="hidden" value="http://specs.openid.net/auth/2.0" />',
|
||||
|
||||
'<input name="openid.ns.ax" type="hidden" value="http://openid.net/srv/ax/1.0" />',
|
||||
|
||||
'<input name="openid.ax.type.fullname" type="hidden" value="http://axschema.org/namePerson" />',
|
||||
|
||||
'<input type="submit" value="Continue" />',
|
||||
|
||||
'<input name="openid.ax.type.email" type="hidden" value="http://axschema.org/contact/email" />',
|
||||
|
||||
'<input name="openid.ax.type.lastname" '
|
||||
'type="hidden" value="http://axschema.org/namePerson/last" />',
|
||||
|
||||
'<input name="openid.ax.type.firstname" '
|
||||
'type="hidden" value="http://axschema.org/namePerson/first" />',
|
||||
|
||||
'<input name="openid.ax.required" type="hidden" '
|
||||
'value="email,fullname,old_email,firstname,old_nickname,lastname,old_fullname,nickname" />',
|
||||
|
||||
'<input name="openid.ax.type.nickname" '
|
||||
'type="hidden" value="http://axschema.org/namePerson/friendly" />',
|
||||
|
||||
'<input name="openid.ax.type.old_email" '
|
||||
'type="hidden" value="http://schema.openid.net/contact/email" />',
|
||||
|
||||
'<input name="openid.ax.type.old_nickname" '
|
||||
'type="hidden" value="http://schema.openid.net/namePerson/friendly" />',
|
||||
|
||||
'<input name="openid.ax.type.old_fullname" '
|
||||
'type="hidden" value="http://schema.openid.net/namePerson" />',
|
||||
|
||||
'<input name="openid.identity" '
|
||||
'type="hidden" value="http://specs.openid.net/auth/2.0/identifier_select" />',
|
||||
|
||||
'<input name="openid.claimed_id" '
|
||||
'type="hidden" value="http://specs.openid.net/auth/2.0/identifier_select" />',
|
||||
|
||||
# should work on the test server as well
|
||||
'<input name="openid.realm" '
|
||||
'type="hidden" value="http://testserver/" />',
|
||||
):
|
||||
self.assertContains(resp, expected_input, html=True)
|
||||
|
||||
# not included here are elements that will vary from run to run:
|
||||
# <input name="openid.return_to" type="hidden" value="http://testserver/openid/complete/?janrain_nonce=2013-01-23T06%3A20%3A17ZaN7j6H" />
|
||||
# <input name="openid.return_to" type="hidden"
|
||||
# value="http://testserver/openid/complete/?janrain_nonce=2013-01-23T06%3A20%3A17ZaN7j6H" />
|
||||
# <input name="openid.assoc_handle" type="hidden" value="{HMAC-SHA1}{50ff8120}{rh87+Q==}" />
|
||||
|
||||
def attempt_login(self, expected_code, login_method='POST', **kwargs):
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#pylint: disable=no-member
|
||||
"""
|
||||
Tests for Shibboleth Authentication
|
||||
@jbau
|
||||
@@ -14,8 +15,8 @@ from django.test.utils import override_settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from importlib import import_module
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from external_auth.views import (
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangoapps.external_auth.views import (
|
||||
shib_login, course_specific_login, course_specific_register, _flatten_to_ascii
|
||||
)
|
||||
from mock import patch
|
||||
@@ -125,6 +126,7 @@ class ShibSPTest(CacheIsolationTestCase):
|
||||
of an existing user that already has an ExternalAuthMap causes an error (403)
|
||||
* shib credentials that do not match an existing ExternalAuthMap causes the registration form to appear
|
||||
"""
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
user_w_map = UserFactory.create(email='withmap@stanford.edu')
|
||||
extauth = ExternalAuthMap(external_id='withmap@stanford.edu',
|
||||
@@ -155,7 +157,7 @@ class ShibSPTest(CacheIsolationTestCase):
|
||||
for remote_user in remote_users:
|
||||
|
||||
self.client.logout()
|
||||
with patch('external_auth.views.AUDIT_LOG') as mock_audit_log:
|
||||
with patch('openedx.core.djangoapps.external_auth.views.AUDIT_LOG') as mock_audit_log:
|
||||
response = self.client.get(
|
||||
reverse('shib-login'),
|
||||
**{
|
||||
@@ -214,7 +216,7 @@ class ShibSPTest(CacheIsolationTestCase):
|
||||
# no audit logging calls
|
||||
self.assertEquals(len(audit_log_calls), 0)
|
||||
|
||||
def _base_test_extauth_auto_activate_user_with_flag(self, log_user_string="inactive@stanford.edu"):
|
||||
def _test_auto_activate_user_with_flag(self, log_user_string="inactive@stanford.edu"):
|
||||
"""
|
||||
Tests that FEATURES['BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'] means extauth automatically
|
||||
linked users, activates them, and logs them in
|
||||
@@ -231,7 +233,7 @@ class ShibSPTest(CacheIsolationTestCase):
|
||||
})
|
||||
|
||||
request.user = AnonymousUser()
|
||||
with patch('external_auth.views.AUDIT_LOG') as mock_audit_log:
|
||||
with patch('openedx.core.djangoapps.external_auth.views.AUDIT_LOG') as mock_audit_log:
|
||||
response = shib_login(request)
|
||||
audit_log_calls = mock_audit_log.method_calls
|
||||
# reload user from db, since the view function works via db side-effects
|
||||
@@ -256,7 +258,7 @@ class ShibSPTest(CacheIsolationTestCase):
|
||||
"""
|
||||
Wrapper to run base_test_extauth_auto_activate_user_with_flag with {'SQUELCH_PII_IN_LOGS': False}
|
||||
"""
|
||||
self._base_test_extauth_auto_activate_user_with_flag(log_user_string="inactive@stanford.edu")
|
||||
self._test_auto_activate_user_with_flag(log_user_string="inactive@stanford.edu")
|
||||
|
||||
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
|
||||
@patch.dict(settings.FEATURES, {'BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH': True, 'SQUELCH_PII_IN_LOGS': True})
|
||||
@@ -264,7 +266,7 @@ class ShibSPTest(CacheIsolationTestCase):
|
||||
"""
|
||||
Wrapper to run base_test_extauth_auto_activate_user_with_flag with {'SQUELCH_PII_IN_LOGS': True}
|
||||
"""
|
||||
self._base_test_extauth_auto_activate_user_with_flag(log_user_string="user.id: 1")
|
||||
self._test_auto_activate_user_with_flag(log_user_string="user.id: 1")
|
||||
|
||||
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
|
||||
@data(*gen_all_identities())
|
||||
@@ -279,11 +281,11 @@ class ShibSPTest(CacheIsolationTestCase):
|
||||
response = client.get(path='/shib-login/', data={}, follow=False, **identity)
|
||||
|
||||
self.assertEquals(response.status_code, 200)
|
||||
mail_input_HTML = '<input class="" id="email" type="email" name="email"'
|
||||
mail_input_html = '<input class="" id="email" type="email" name="email"'
|
||||
if not identity.get('mail'):
|
||||
self.assertContains(response, mail_input_HTML)
|
||||
self.assertContains(response, mail_input_html)
|
||||
else:
|
||||
self.assertNotContains(response, mail_input_HTML)
|
||||
self.assertNotContains(response, mail_input_html)
|
||||
sn_empty = not identity.get('sn')
|
||||
given_name_empty = not identity.get('givenName')
|
||||
displayname_empty = not identity.get('displayName')
|
||||
@@ -2,6 +2,7 @@
|
||||
Provides unit tests for SSL based authentication portions
|
||||
of the external_auth app.
|
||||
"""
|
||||
# pylint: disable=no-member
|
||||
import copy
|
||||
import unittest
|
||||
|
||||
@@ -14,10 +15,10 @@ from django.core.urlresolvers import reverse
|
||||
from django.test.client import Client
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from external_auth.models import ExternalAuthMap
|
||||
import external_auth.views
|
||||
from mock import Mock, patch
|
||||
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
import openedx.core.djangoapps.external_auth.views as external_auth_views
|
||||
from student.models import CourseEnrollment
|
||||
from student.roles import CourseStaffRole
|
||||
from student.tests.factories import UserFactory
|
||||
@@ -87,7 +88,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
redirects them to the signup page.
|
||||
"""
|
||||
with self._create_ssl_request('/') as request:
|
||||
response = external_auth.views.ssl_login(request)
|
||||
response = external_auth_views.ssl_login(request)
|
||||
|
||||
# Response should contain template for signup form, eamap should have user, and internal
|
||||
# auth should not have a user
|
||||
@@ -127,7 +128,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
and the user is redirected to slash.
|
||||
"""
|
||||
with self._create_ssl_request('/') as request:
|
||||
external_auth.views.ssl_login(request)
|
||||
external_auth_views.ssl_login(request)
|
||||
|
||||
# Assert our user exists in both eamap and Users, and that we are logged in
|
||||
try:
|
||||
@@ -250,7 +251,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
# Create account, break internal password, and activate account
|
||||
|
||||
with self._create_ssl_request('/') as request:
|
||||
external_auth.views.ssl_login(request)
|
||||
external_auth_views.ssl_login(request)
|
||||
user = User.objects.get(email=self.USER_EMAIL)
|
||||
user.set_password('not autogenerated')
|
||||
user.is_active = True
|
||||
@@ -267,7 +268,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
def test_ssl_decorator_no_certs(self):
|
||||
"""Make sure no external auth happens without SSL enabled"""
|
||||
|
||||
dec_mock = external_auth.views.ssl_login_shortcut(self.mock)
|
||||
dec_mock = external_auth_views.ssl_login_shortcut(self.mock)
|
||||
|
||||
with self._create_normal_request(self.MOCK_URL) as request:
|
||||
request.user = AnonymousUser()
|
||||
@@ -282,7 +283,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
def test_ssl_login_decorator(self):
|
||||
"""Create mock function to test ssl login decorator"""
|
||||
|
||||
dec_mock = external_auth.views.ssl_login_shortcut(self.mock)
|
||||
dec_mock = external_auth_views.ssl_login_shortcut(self.mock)
|
||||
|
||||
# Test that anonymous without cert doesn't create authmap
|
||||
with self._create_normal_request(self.MOCK_URL) as request:
|
||||
@@ -312,7 +313,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
will bypass registration and call retfun.
|
||||
"""
|
||||
|
||||
dec_mock = external_auth.views.ssl_login_shortcut(self.mock)
|
||||
dec_mock = external_auth_views.ssl_login_shortcut(self.mock)
|
||||
with self._create_ssl_request(self.MOCK_URL) as request:
|
||||
dec_mock(request)
|
||||
|
||||
@@ -343,7 +344,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
)
|
||||
|
||||
with self._create_ssl_request('/') as request:
|
||||
external_auth.views.ssl_login(request)
|
||||
external_auth_views.ssl_login(request)
|
||||
user = User.objects.get(email=self.USER_EMAIL)
|
||||
CourseEnrollment.enroll(user, course.id)
|
||||
course_private_url = '/courses/MITx/999/Robot_Super_Course/courseware'
|
||||
@@ -374,7 +375,7 @@ class SSLClientTest(ModuleStoreTestCase):
|
||||
)
|
||||
|
||||
with self._create_ssl_request('/') as request:
|
||||
external_auth.views.ssl_login(request)
|
||||
external_auth_views.ssl_login(request)
|
||||
user = User.objects.get(email=self.USER_EMAIL)
|
||||
CourseEnrollment.enroll(user, course.id)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
"""
|
||||
External Auth Views
|
||||
"""
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
@@ -9,8 +12,8 @@ import unicodedata
|
||||
import urllib
|
||||
|
||||
from textwrap import dedent
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from external_auth.djangostore import DjangoOpenIDStore
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangoapps.external_auth.djangostore import DjangoOpenIDStore
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, login
|
||||
@@ -31,11 +34,7 @@ from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from edxmako.shortcuts import render_to_response, render_to_string
|
||||
try:
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
except ImportError:
|
||||
from django.contrib.csrf.middleware import csrf_exempt
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
||||
|
||||
import django_openid_auth.views as openid_views
|
||||
from django_openid_auth import auth as openid_auth
|
||||
@@ -62,7 +61,7 @@ OPENID_DOMAIN_PREFIX = settings.OPENID_DOMAIN_PREFIX
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def default_render_failure(request,
|
||||
def default_render_failure(request, # pylint: disable=unused-argument
|
||||
message,
|
||||
status=403,
|
||||
template_name='extauth_failure.html',
|
||||
@@ -90,7 +89,7 @@ def generate_password(length=12, chars=string.letters + string.digits):
|
||||
|
||||
@csrf_exempt
|
||||
def openid_login_complete(request,
|
||||
redirect_field_name=REDIRECT_FIELD_NAME,
|
||||
redirect_field_name=REDIRECT_FIELD_NAME, # pylint: disable=unused-argument
|
||||
render_failure=None):
|
||||
"""Complete the openid login process"""
|
||||
|
||||
@@ -104,7 +103,7 @@ def openid_login_complete(request,
|
||||
if openid_response.status == SUCCESS:
|
||||
external_id = openid_response.identity_url
|
||||
oid_backend = openid_auth.OpenIDBackend()
|
||||
details = oid_backend._extract_user_details(openid_response)
|
||||
details = oid_backend._extract_user_details(openid_response) # pylint: disable=protected-access
|
||||
|
||||
log.debug('openid success, details=%s', details)
|
||||
|
||||
@@ -134,6 +133,7 @@ def _external_login_or_signup(request,
|
||||
fullname,
|
||||
retfun=None):
|
||||
"""Generic external auth login or signup"""
|
||||
# pylint: disable=too-many-statements
|
||||
# see if we have a map from this external_id to an edX username
|
||||
try:
|
||||
eamap = ExternalAuthMap.objects.get(external_id=external_id,
|
||||
@@ -300,15 +300,16 @@ def _signup(request, eamap, retfun=None):
|
||||
# but this only affects username, not fullname
|
||||
username = re.sub(r'\s', '', _flatten_to_ascii(eamap.external_name), flags=re.UNICODE)
|
||||
|
||||
context = {'has_extauth_info': True,
|
||||
'show_signup_immediately': True,
|
||||
'extauth_domain': eamap.external_domain,
|
||||
'extauth_id': eamap.external_id,
|
||||
'extauth_email': eamap.external_email,
|
||||
'extauth_username': username,
|
||||
'extauth_name': eamap.external_name,
|
||||
'ask_for_tos': True,
|
||||
}
|
||||
context = {
|
||||
'has_extauth_info': True,
|
||||
'show_signup_immediately': True,
|
||||
'extauth_domain': eamap.external_domain,
|
||||
'extauth_id': eamap.external_id,
|
||||
'extauth_email': eamap.external_email,
|
||||
'extauth_username': username,
|
||||
'extauth_name': eamap.external_name,
|
||||
'ask_for_tos': True,
|
||||
}
|
||||
|
||||
# Some openEdX instances can't have terms of service for shib users, like
|
||||
# according to Stanford's Office of General Counsel
|
||||
@@ -343,17 +344,17 @@ def _ssl_dn_extract_info(dn_string):
|
||||
full name from the SSL DN string. Return (user,email,fullname) if
|
||||
successful, and None otherwise.
|
||||
"""
|
||||
ss = re.search('/emailAddress=(.*)@([^/]+)', dn_string)
|
||||
if ss:
|
||||
user = ss.group(1)
|
||||
email = "%s@%s" % (user, ss.group(2))
|
||||
search_string = re.search('/emailAddress=(.*)@([^/]+)', dn_string)
|
||||
if search_string:
|
||||
user = search_string.group(1)
|
||||
email = "%s@%s" % (user, search_string.group(2))
|
||||
else:
|
||||
return None
|
||||
ss = re.search('/CN=([^/]+)/', dn_string)
|
||||
if ss:
|
||||
fullname = ss.group(1)
|
||||
raise ValueError
|
||||
search_string = re.search('/CN=([^/]+)/', dn_string)
|
||||
if search_string:
|
||||
fullname = search_string.group(1)
|
||||
else:
|
||||
return None
|
||||
raise ValueError
|
||||
return (user, email, fullname)
|
||||
|
||||
|
||||
@@ -370,14 +371,14 @@ def ssl_get_cert_from_request(request):
|
||||
if not cert:
|
||||
try:
|
||||
# try the direct apache2 SSL key
|
||||
cert = request._req.subprocess_env.get(certkey, '')
|
||||
except Exception:
|
||||
cert = request._req.subprocess_env.get(certkey, '') # pylint: disable=protected-access
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return ''
|
||||
|
||||
return cert
|
||||
|
||||
|
||||
def ssl_login_shortcut(fn):
|
||||
def ssl_login_shortcut(func):
|
||||
"""
|
||||
Python function decorator for login procedures, to allow direct login
|
||||
based on existing ExternalAuth record and MIT ssl certificate.
|
||||
@@ -390,19 +391,19 @@ def ssl_login_shortcut(fn):
|
||||
"""
|
||||
|
||||
if not settings.FEATURES['AUTH_USE_CERTIFICATES']:
|
||||
return fn(*args, **kwargs)
|
||||
return func(*args, **kwargs)
|
||||
request = args[0]
|
||||
|
||||
if request.user and request.user.is_authenticated(): # don't re-authenticate
|
||||
return fn(*args, **kwargs)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
cert = ssl_get_cert_from_request(request)
|
||||
if not cert: # no certificate information - show normal login window
|
||||
return fn(*args, **kwargs)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
def retfun():
|
||||
"""Wrap function again for call by _external_login_or_signup"""
|
||||
return fn(*args, **kwargs)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
(_user, email, fullname) = _ssl_dn_extract_info(cert)
|
||||
return _external_login_or_signup(
|
||||
@@ -565,9 +566,9 @@ def course_specific_login(request, course_id):
|
||||
|
||||
# now the dispatching conditionals. Only shib for now
|
||||
if (
|
||||
settings.FEATURES.get('AUTH_USE_SHIB') and
|
||||
course.enrollment_domain and
|
||||
course.enrollment_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX)
|
||||
settings.FEATURES.get('AUTH_USE_SHIB') and
|
||||
course.enrollment_domain and
|
||||
course.enrollment_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX)
|
||||
):
|
||||
return redirect_with_get('shib-login', request.GET)
|
||||
|
||||
@@ -589,9 +590,9 @@ def course_specific_register(request, course_id):
|
||||
|
||||
# now the dispatching conditionals. Only shib for now
|
||||
if (
|
||||
settings.FEATURES.get('AUTH_USE_SHIB') and
|
||||
course.enrollment_domain and
|
||||
course.enrollment_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX)
|
||||
settings.FEATURES.get('AUTH_USE_SHIB') and
|
||||
course.enrollment_domain and
|
||||
course.enrollment_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX)
|
||||
):
|
||||
# shib-login takes care of both registration and login flows
|
||||
return redirect_with_get('shib-login', request.GET)
|
||||
@@ -634,6 +635,9 @@ def get_xrds_url(resource, request):
|
||||
|
||||
|
||||
def add_openid_simple_registration(request, response, data):
|
||||
"""
|
||||
Add simple registration fields to the response if requested.
|
||||
"""
|
||||
sreg_data = {}
|
||||
sreg_request = sreg.SRegRequest.fromOpenIDRequest(request)
|
||||
sreg_fields = sreg_request.allRequestedFields()
|
||||
@@ -655,6 +659,9 @@ def add_openid_simple_registration(request, response, data):
|
||||
|
||||
|
||||
def add_openid_attribute_exchange(request, response, data):
|
||||
"""
|
||||
Add attribute exchange fields to the response if requested.
|
||||
"""
|
||||
try:
|
||||
ax_request = ax.FetchRequest.fromOpenIDRequest(request)
|
||||
except ax.AXError:
|
||||
@@ -691,8 +698,8 @@ def provider_respond(server, request, response, data):
|
||||
http_response.status_code = webresponse.code
|
||||
|
||||
# add OpenID headers to response
|
||||
for k, v in webresponse.headers.iteritems():
|
||||
http_response[k] = v
|
||||
for key, val in webresponse.headers.iteritems():
|
||||
http_response[key] = val
|
||||
|
||||
return http_response
|
||||
|
||||
@@ -744,7 +751,7 @@ def provider_login(request):
|
||||
"""
|
||||
OpenID login endpoint
|
||||
"""
|
||||
|
||||
# pylint: disable=too-many-statements
|
||||
# make and validate endpoint
|
||||
endpoint = get_xrds_url('login', request)
|
||||
if not endpoint:
|
||||
Reference in New Issue
Block a user