diff --git a/common/djangoapps/external_auth/tests/__init__.py b/common/djangoapps/external_auth/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/djangoapps/external_auth/tests/test_openid_provider.py b/common/djangoapps/external_auth/tests/test_openid_provider.py
new file mode 100644
index 0000000000..9c522f88b4
--- /dev/null
+++ b/common/djangoapps/external_auth/tests/test_openid_provider.py
@@ -0,0 +1,209 @@
+'''
+Created on Jan 18, 2013
+
+@author: brian
+'''
+import openid
+from openid.fetchers import HTTPFetcher, HTTPResponse
+from urlparse import parse_qs
+
+from django.conf import settings
+from django.test import TestCase, LiveServerTestCase
+# from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+from django.test.client import RequestFactory
+
+class MyFetcher(HTTPFetcher):
+ """A fetcher that uses server-internal calls for performing HTTP
+ requests.
+ """
+
+ def __init__(self, client):
+ """@param client: A test client object"""
+
+ super(MyFetcher, self).__init__()
+ self.client = client
+
+ def fetch(self, url, body=None, headers=None):
+ """Perform an HTTP request
+
+ @raises Exception: Any exception that can be raised by Django
+
+ @see: C{L{HTTPFetcher.fetch}}
+ """
+ if body:
+ # method = 'POST'
+ # undo the URL encoding of the POST arguments
+ data = parse_qs(body)
+ response = self.client.post(url, data)
+ else:
+ # method = 'GET'
+ data = {}
+ if headers and 'Accept' in headers:
+ data['CONTENT_TYPE'] = headers['Accept']
+ response = self.client.get(url, data)
+
+ # Translate the test client response to the fetcher's HTTP response abstraction
+ content = response.content
+ final_url = url
+ response_headers = {}
+ if 'Content-Type' in response:
+ response_headers['content-type'] = response['Content-Type']
+ if 'X-XRDS-Location' in response:
+ response_headers['x-xrds-location'] = response['X-XRDS-Location']
+ status = response.status_code
+
+ return HTTPResponse(
+ body=content,
+ final_url=final_url,
+ headers=response_headers,
+ status=status,
+ )
+
+class OpenIdProviderTest(TestCase):
+
+# def setUp(self):
+# username = 'viewtest'
+# email = 'view@test.com'
+# password = 'foo'
+# user = User.objects.create_user(username, email, password)
+
+ def testBeginLoginWithXrdsUrl(self):
+ # skip the test if openid is not enabled (as in cms.envs.test):
+ if not settings.MITX_FEATURES.get('AUTH_USE_OPENID') or not settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'):
+ return
+
+ # the provider URL must be converted to an absolute URL in order to be
+ # used as an openid provider.
+ provider_url = reverse('openid-provider-xrds')
+ factory = RequestFactory()
+ request = factory.request()
+ abs_provider_url = request.build_absolute_uri(location = provider_url)
+
+ # In order for this absolute URL to work (i.e. to get xrds, then authentication)
+ # in the test environment, we either need a live server that works with the default
+ # fetcher (i.e. urlopen2), or a test server that is reached through a custom fetcher.
+ # Here we do the latter:
+ fetcher = MyFetcher(self.client)
+ openid.fetchers.setDefaultFetcher(fetcher, wrap_exceptions=False)
+
+ # now we can begin the login process by invoking a local openid client,
+ # with a pointer to the (also-local) openid provider:
+ with self.settings(OPENID_SSO_SERVER_URL = abs_provider_url):
+ url = reverse('openid-login')
+ resp = self.client.post(url)
+ code = 200
+ self.assertEqual(resp.status_code, code,
+ "got code {0} for url '{1}'. Expected code {2}"
+ .format(resp.status_code, url, code))
+
+ def testBeginLoginWithLoginUrl(self):
+ # skip the test if openid is not enabled (as in cms.envs.test):
+ if not settings.MITX_FEATURES.get('AUTH_USE_OPENID') or not settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'):
+ return
+
+ # the provider URL must be converted to an absolute URL in order to be
+ # used as an openid provider.
+ provider_url = reverse('openid-provider-login')
+ factory = RequestFactory()
+ request = factory.request()
+ abs_provider_url = request.build_absolute_uri(location = provider_url)
+
+ # In order for this absolute URL to work (i.e. to get xrds, then authentication)
+ # in the test environment, we either need a live server that works with the default
+ # fetcher (i.e. urlopen2), or a test server that is reached through a custom fetcher.
+ # Here we do the latter:
+ fetcher = MyFetcher(self.client)
+ openid.fetchers.setDefaultFetcher(fetcher, wrap_exceptions=False)
+
+ # now we can begin the login process by invoking a local openid client,
+ # with a pointer to the (also-local) openid provider:
+ with self.settings(OPENID_SSO_SERVER_URL = abs_provider_url):
+ url = reverse('openid-login')
+ resp = self.client.post(url)
+ code = 200
+ self.assertEqual(resp.status_code, code,
+ "got code {0} for url '{1}'. Expected code {2}"
+ .format(resp.status_code, url, code))
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ self.assertContains(resp, '', html=True)
+ # this should work on the server:
+ self.assertContains(resp, '', html=True)
+
+ # not included here are elements that will vary from run to run:
+ #
+ #
+
+
+ def testOpenIdSetup(self):
+ if not settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'):
+ return
+ url = reverse('openid-provider-login')
+ post_args = {
+ "openid.mode" : "checkid_setup",
+ "openid.return_to" : "http://testserver/openid/complete/?janrain_nonce=2013-01-23T06%3A20%3A17ZaN7j6H",
+ "openid.assoc_handle" : "{HMAC-SHA1}{50ff8120}{rh87+Q==}",
+ "openid.claimed_id" : "http://specs.openid.net/auth/2.0/identifier_select",
+ "openid.ns" : "http://specs.openid.net/auth/2.0",
+ "openid.realm" : "http://testserver/",
+ "openid.identity" : "http://specs.openid.net/auth/2.0/identifier_select",
+ "openid.ns.ax" : "http://openid.net/srv/ax/1.0",
+ "openid.ax.mode" : "fetch_request",
+ "openid.ax.required" : "email,fullname,old_email,firstname,old_nickname,lastname,old_fullname,nickname",
+ "openid.ax.type.fullname" : "http://axschema.org/namePerson",
+ "openid.ax.type.lastname" : "http://axschema.org/namePerson/last",
+ "openid.ax.type.firstname" : "http://axschema.org/namePerson/first",
+ "openid.ax.type.nickname" : "http://axschema.org/namePerson/friendly",
+ "openid.ax.type.email" : "http://axschema.org/contact/email",
+ "openid.ax.type.old_email" : "http://schema.openid.net/contact/email",
+ "openid.ax.type.old_nickname" : "http://schema.openid.net/namePerson/friendly",
+ "openid.ax.type.old_fullname" : "http://schema.openid.net/namePerson",
+ }
+ resp = self.client.post(url, post_args)
+ code = 200
+ self.assertEqual(resp.status_code, code,
+ "got code {0} for url '{1}'. Expected code {2}"
+ .format(resp.status_code, url, code))
+
+
+# In order for this absolute URL to work (i.e. to get xrds, then authentication)
+# in the test environment, we either need a live server that works with the default
+# fetcher (i.e. urlopen2), or a test server that is reached through a custom fetcher.
+# Here we do the former.
+class OpenIdProviderLiveServerTest(LiveServerTestCase):
+
+ def testBeginLogin(self):
+ # skip the test if openid is not enabled (as in cms.envs.test):
+ if not settings.MITX_FEATURES.get('AUTH_USE_OPENID') or not settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'):
+ return
+
+ # the provider URL must be converted to an absolute URL in order to be
+ # used as an openid provider.
+ provider_url = reverse('openid-provider-xrds')
+ factory = RequestFactory()
+ request = factory.request()
+ abs_provider_url = request.build_absolute_uri(location = provider_url)
+
+ # now we can begin the login process by invoking a local openid client,
+ # with a pointer to the (also-local) openid provider:
+ with self.settings(OPENID_SSO_SERVER_URL = abs_provider_url):
+ url = reverse('openid-login')
+ resp = self.client.post(url)
+ code = 200
+ self.assertEqual(resp.status_code, code,
+ "got code {0} for url '{1}'. Expected code {2}"
+ .format(resp.status_code, url, code))
diff --git a/common/djangoapps/external_auth/views.py b/common/djangoapps/external_auth/views.py
index d456150cf7..d557b33e9c 100644
--- a/common/djangoapps/external_auth/views.py
+++ b/common/djangoapps/external_auth/views.py
@@ -438,7 +438,9 @@ def provider_login(request):
store = DjangoOpenIDStore()
server = Server(store, endpoint)
- # handle OpenID request
+ # first check to see if the request is an OpenID request.
+ # If so, the client will have specified an 'openid.mode' as part
+ # of the request.
querydict = dict(request.REQUEST.items())
error = False
if 'openid.mode' in request.GET or 'openid.mode' in request.POST:
@@ -458,6 +460,8 @@ def provider_login(request):
openid_request.answer(False), {})
# checkid_setup, so display login page
+ # (by falling through to the provider_login at the
+ # bottom of this method).
elif openid_request.mode == 'checkid_setup':
if openid_request.idSelect():
# remember request and original path
@@ -476,8 +480,10 @@ def provider_login(request):
return provider_respond(server, openid_request,
server.handleRequest(openid_request), {})
- # handle login
- if request.method == 'POST' and 'openid_setup' in request.session:
+ # handle login redirection: these are also sent to this view function,
+ # but are distinguished by lacking the openid mode. We also know that
+ # they are posts, because they come from the popup
+ elif request.method == 'POST' and 'openid_setup' in request.session:
# get OpenID request from session
openid_setup = request.session['openid_setup']
openid_request = openid_setup['request']
@@ -489,6 +495,8 @@ def provider_login(request):
return default_render_failure(request, "Invalid OpenID trust root")
# check if user with given email exists
+ # Failure is redirected to this method (by using the original URL),
+ # which will bring up the login dialog.
email = request.POST.get('email', None)
try:
user = User.objects.get(email=email)
@@ -498,7 +506,8 @@ def provider_login(request):
log.warning(msg)
return HttpResponseRedirect(openid_request_url)
- # attempt to authenticate user
+ # attempt to authenticate user (but not actually log them in...)
+ # Failure is again redirected to the login dialog.
username = user.username
password = request.POST.get('password', None)
user = authenticate(username=username, password=password)
@@ -509,7 +518,8 @@ def provider_login(request):
log.warning(msg)
return HttpResponseRedirect(openid_request_url)
- # authentication succeeded, so log user in
+ # authentication succeeded, so fetch user information
+ # that was requested
if user is not None and user.is_active:
# remove error from session since login succeeded
if 'openid_error' in request.session:
@@ -534,13 +544,19 @@ def provider_login(request):
# break the CS50 client. Temporarily we will be returning
# username filling in for fullname in addition to username
# as sreg nickname.
+
+ # Note too that this is hardcoded, and not really responding to
+ # the extensions that were registered in the first place.
results = {
'nickname': user.username,
'email': user.email,
'fullname': user.username
}
+
+ # the request succeeded:
return provider_respond(server, openid_request, response, results)
+ # the account is not active, so redirect back to the login page:
request.session['openid_error'] = True
msg = "Login failed - Account not active for user {0}".format(username)
log.warning(msg)
@@ -559,7 +575,7 @@ def provider_login(request):
'return_to': return_to
})
- # custom XRDS header necessary for discovery process
+ # add custom XRDS header necessary for discovery process
response['X-XRDS-Location'] = get_xrds_url('xrds', request)
return response
diff --git a/lms/envs/test.py b/lms/envs/test.py
index 71d11d0652..8b546549eb 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -125,8 +125,15 @@ SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
################################## OPENID ######################################
MITX_FEATURES['AUTH_USE_OPENID'] = True
MITX_FEATURES['AUTH_USE_OPENID_PROVIDER'] = True
+
+OPENID_CREATE_USERS = False
+OPENID_UPDATE_DETAILS_FROM_SREG = True
+OPENID_USE_AS_ADMIN_LOGIN = False
OPENID_PROVIDER_TRUSTED_ROOTS = ['*']
+INSTALLED_APPS += ('external_auth',)
+INSTALLED_APPS += ('django_openid_auth',)
+
############################ STATIC FILES #############################
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
MEDIA_ROOT = TEST_ROOT / "uploads"