Commit 4b6694a4 by Tommy MacWilliam Committed by Carlos Andrés Rocha

OpenID provider implementation

- endpoint supports both SReg and AX
- identity taken from edX username
- sreg fullname and ax http://axschema.org/namePerson taken from edX
  name
- sreg email and ax http://axschema.org/contact/email taken from edX
  email
parent 1c176d36
......@@ -11,9 +11,12 @@ from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, login
from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from student.models import UserProfile
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.utils.http import urlquote
from django.shortcuts import render_to_response
from django.shortcuts import redirect
from django.template import RequestContext
......@@ -29,6 +32,14 @@ from django_openid_auth import auth as openid_auth
from openid.consumer.consumer import (Consumer, SUCCESS, CANCEL, FAILURE)
import django_openid_auth.views as openid_views
from openid.server.server import Server, ProtocolError, CheckIDRequest, EncodingError
from openid.server.trustroot import verifyReturnTo
from openid.store.filestore import FileOpenIDStore
from openid.yadis.discover import DiscoveryFailure
from openid.consumer.discover import OPENID_IDP_2_0_TYPE
from openid.extensions import ax, sreg
from openid.fetchers import HTTPFetchingError
import student.views as student_views
log = logging.getLogger("mitx.external_auth")
......@@ -218,3 +229,197 @@ def edXauth_ssl_login(request):
email=email,
fullname=fullname,
retfun = functools.partial(student_views.index, request))
def get_dict_for_openid(data):
"""
Return a dictionary suitable for the OpenID library
"""
return dict((k, v) for k, v in data.iteritems())
def get_xrds_url(resource, request):
"""
Return the XRDS url for a resource
"""
location = request.META['HTTP_HOST'] + '/openid/provider/' + resource + '/'
if request.is_secure():
url = 'https://' + location
else:
url = 'http://' + location
return url
def provider_respond(server, request, response, data):
"""
Respond to an OpenID request
"""
# get simple registration request
sreg_data = {}
sreg_request = sreg.SRegRequest.fromOpenIDRequest(request)
sreg_fields = sreg_request.allRequestedFields()
# if consumer requested simple registration fields, add them
if sreg_fields:
for field in sreg_fields:
if field == 'email' and 'email' in data:
sreg_data['email'] = data['email']
elif field == 'fullname' and 'fullname' in data:
sreg_data['fullname'] = data['fullname']
# construct sreg response
sreg_response = sreg.SRegResponse.extractResponse(sreg_request, sreg_data)
sreg_response.toMessage(response.fields)
# get attribute exchange request
try:
ax_request = ax.FetchRequest.fromOpenIDRequest(request)
except ax.AXError:
pass
else:
ax_response = ax.FetchResponse()
# if consumer requested attribute exchange fields, add them
if ax_request and ax_request.requested_attributes:
for type_uri in ax_request.requested_attributes.iterkeys():
if type_uri == 'http://axschema.org/contact/email' and 'email' in data:
ax_response.addValue('http://axschema.org/contact/email', data['email'])
elif type_uri == 'http://axschema.org/namePerson' and 'fullname' in data:
ax_response.addValue('http://axschema.org/namePerson', data['fullname']);
# construct ax response
ax_response.toMessage(response.fields)
# create http response from OpenID response
webresponse = server.encodeResponse(response)
http_response = HttpResponse(webresponse.body)
http_response.status_code = webresponse.code
# add OpenID headers to response
for k, v in webresponse.headers.iteritems():
http_response[k] = v
return http_response
@csrf_exempt
def provider_login(request):
"""
OpenID login endpoint
"""
# initialize store and server
endpoint = get_xrds_url('login', request)
store = FileOpenIDStore('/tmp/openid_provider')
server = Server(store, endpoint)
# handle OpenID request
query = get_dict_for_openid(request.GET or request.POST)
error = False
if 'openid.mode' in request.GET or 'openid.mode' in request.POST:
# decode request
openid_request = server.decodeRequest(query)
# checkid_immediate not supported, require user interaction
if openid_request.mode == 'checkid_immediate':
return provider_respond(server, openid_request, openid_request.answer(false), {})
# checkid_setup, so display login page
elif openid_request.mode == 'checkid_setup':
if openid_request.idSelect():
# remember request and original path
request.session['openid_request'] = {
'request': openid_request,
'url': request.get_full_path()
}
# user failed login on previous attempt
if 'openid_error' in request.session:
error = True
del request.session['openid_error']
# OpenID response
else:
return provider_respond(server, openid_request, server.handleRequest(openid_request), {})
# handle login
if request.method == 'POST' and 'openid_request' in request.session:
# get OpenID request from session
openid_request = request.session['openid_request']
del request.session['openid_request']
# check if user with given email exists
email = request.POST['email']
password = request.POST['password']
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
request.session['openid_error'] = True
log.warning("Login failed - Unknown user email: {0}".format(email))
return HttpResponseRedirect(openid_request['url'])
# attempt to authenticate user
username = user.username
user = authenticate(username=username, password=password)
if user is None:
request.session['openid_error'] = True
log.warning("Login failed - password for {0} is invalid".format(email))
return HttpResponseRedirect(openid_request['url'])
# authentication succeeded, so log user in
if user is not None and user.is_active:
# remove error from session since login succeeded
if 'openid_error' in request.session:
del request.session['openid_error']
# fullname field comes from user profile
profile = UserProfile.objects.get(user=user)
# redirect user to return_to location
response = openid_request['request'].answer(True, None, endpoint + urlquote(user.username))
return provider_respond(server, openid_request['request'], response, {
'fullname': profile.name,
'email': user.email
})
request.session['openid_error'] = True
log.warning("Login failed - Account not active for user {0}".format(username))
return HttpResponseRedirect(openid_request['url'])
# display login page
response = render_to_response('provider_login.html', {
'error': error
})
# custom XRDS header necessary for discovery process
response['X-XRDS-Location'] = get_xrds_url('xrds', request)
return response
def provider_identity(request):
"""
XRDS for identity discovery
"""
response = render_to_response('identity.xml', {
'url': get_xrds_url('login', request)
}, mimetype='text/xml')
# custom XRDS header necessary for discovery process
response['X-XRDS-Location'] = get_xrds_url('identity', request)
return response
def provider_xrds(request):
"""
XRDS for endpoint discovery
"""
response = render_to_response('xrds.xml', {
'url': get_xrds_url('login', request)
}, mimetype='text/xml')
# custom XRDS header necessary for discovery process
response['X-XRDS-Location'] = get_xrds_url('xrds', request)
return response
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
<XRD>
<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
<Type>http://openid.net/signon/1.1</Type>
<URI>${url}</URI>
</Service>
</XRD>
</xrds:XRDS>
<%inherit file="main.html" />
<%namespace name='static' file='static_content.html'/>
<style>
.openid-login {
display: block;
position: relative;
left: 0;
margin: 100px auto;
top: 0;
}
</style>
<section id="login-modal" class="modal login-modal openid-login">
<div class="inner-wrapper">
<header>
<h2>Log In</h2>
<hr>
</header>
<form id="login_form" class="login_form" method="post" action="/openid/provider/login/">
%if error:
<div id="login_error" class="modal-form-error" style="display: block;">Email or password is incorrect.</div>
%endif
<label>E-mail</label>
<input type="text" name="email" placeholder="E-mail" tabindex="1" />
<label>Password</label>
<input type="password" name="password" placeholder="Password" tabindex="2" />
<div class="submit">
<input name="submit" type="submit" value="Access My Courses" tabindex="3" />
</div>
</form>
</div>
</section>
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
<XRD>
<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/server</Type>
<Type>http://openid.net/sreg/1.0</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<URI>${url}</URI>
</Service>
</XRD>
</xrds:XRDS>
......@@ -217,6 +217,10 @@ if settings.MITX_FEATURES.get('AUTH_USE_OPENID'):
url(r'^openid/login/$', 'django_openid_auth.views.login_begin', name='openid-login'),
url(r'^openid/complete/$', 'external_auth.views.edXauth_openid_login_complete', name='openid-complete'),
url(r'^openid/logo.gif$', 'django_openid_auth.views.logo', name='openid-logo'),
url(r'^openid/provider/login/$', 'external_auth.views.provider_login', name='openid-provider-login'),
url(r'^openid/provider/login/(?:[\w%\. ]+)$', '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')
)
if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
......
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