Commit 2112e7f8 by Nimisha Asthagiri Committed by GitHub

Merge pull request #13659 from edx/common_cleanup/external_auth

Moves external_auth from common to openedx/core
parents 84bdd859 fd49f88e
......@@ -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']
......
......@@ -818,7 +818,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)
......
......@@ -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):
......
# -*- 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)
......
"""
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:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment