diff --git a/README b/README index 9c2f825..0ddff95 100644 --- a/README +++ b/README @@ -33,6 +33,7 @@ Add mellon urls to your urls:: If SAML 2.0 should be your only authentication method you can define `mellon_login` as you main `LOGIN_URL`:: LOGIN_URL = 'mellon_login' + LOGOUT_URL = 'mellon_logout' Yout metadata will be downloadable through HTTP on diff --git a/mellon/utils.py b/mellon/utils.py index 52d455c..c9b91de 100644 --- a/mellon/utils.py +++ b/mellon/utils.py @@ -111,3 +111,22 @@ def get_values(saml_attributes, name): def get_parameter(idp, name): return idp.get(name) or getattr(app_settings, name) + +def create_logout(request): + server = create_server(request) + mellon_session = request.session.get('mellon_session', {}) + entity_id = mellon_session.get('issuer') + session_index = mellon_session.get('session_index') + name_id_format = mellon_session.get('name_id_format') + name_id_content = mellon_session.get('name_id_content') + session_dump = render_to_string('mellon/session_dump.xml', { + 'entity_id': entity_id, + 'session_index': session_index, + 'name_id_format': name_id_format, + 'name_id_content': name_id_content, + }) + logout = lasso.Logout(server) + if not app_settings.PRIVATE_KEY: + logout.setSignatureHint(lasso.PROFILE_SIGNATURE_HINT_FORBID) + logout.setSessionFromDump(session_dump) + return logout diff --git a/mellon/views.py b/mellon/views.py index 833f71c..89d1436 100644 --- a/mellon/views.py +++ b/mellon/views.py @@ -5,7 +5,8 @@ from django.http import HttpResponseBadRequest, HttpResponseRedirect, HttpRespon from django.contrib import auth from django.conf import settings from django.views.decorators.csrf import csrf_exempt -from django.shortcuts import render +from django.shortcuts import render, redirect +from django.utils.http import same_origin import lasso @@ -120,10 +121,76 @@ class LoginView(View): login = csrf_exempt(LoginView.as_view()) class LogoutView(View): - pass + def get(self, request): + if 'SAMLRequest' in request.GET: + return self.idp_logout(request) + elif 'SAMLResponse' in request.GET: + return self.sp_logout_response(request) + else: + return self.sp_logout_request(request) + + def idp_logout(self, request): + '''Handle logout request emitted by the IdP''' + logout = utils.create_logout(request) + try: + logout.processRequestMsg(request.META['QUERY_STRING']) + except lasso.Error, e: + return HttpResponseBadRequest('error processing logout request: %r' % e) + try: + logout.validateRequest() + except lasso.Error, e: + log.warning('error validating logout request: %r' % e) + issuer = request.session.get('mellon_session', {}).get('issuer') + if issuer == logout.remoteProviderId: + auth.logout(request) + try: + logout.buildResponseMsg() + except lasso.Error, e: + return HttpResponseBadRequest('error processing logout request: %r' % e) + return HttpResponseRedirect(logout.msgUrl) + + def sp_logout_request(self, request): + '''Launch a logout request to the identity provider''' + next_url = request.GET.get('next') or settings.LOGIN_REDIRECT_URL + referer = request.META.get('HTTP_REFERER') + if not referer or same_origin(referer, request.build_absolute_uri()): + if request.user.is_authenticated(): + issuer = request.session.get('mellon_session', {}).get('issuer') + if issuer: + logout = utils.create_logout(request) + try: + logout.initRequest(issuer, lasso.HTTP_METHOD_REDIRECT) + logout.msgRelayState = next_url + logout.buildRequestMsg() + except lasso.Error, e: + log.error('unable to initiate a logout request %r', e) + else: + return HttpResponseRedirect(logout.msgUrl) + auth.logout(request) + else: + log.warning('logout refused referer %r is not of the ' + 'same origin', referer) + return HttpResponseRedirect(next_url) + + def sp_logout_response(self, request): + '''Launch a logout request to the identity provider''' + if 'SAMLResponse' not in request.GET: + return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL) + logout = utils.create_logout(request) + try: + logout.processResponseMsg(request.GET['SAMLResponse']) + except lasso.Error, e: + log.error('unable to process a logout response %r', e) + return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL) + next_url = logout.msgRelayState + if next_url and same_origin(next_url, request.build_absolute_uri()): + return redirect(next_url) + return redirect(settings.LOGIN_REDIRECT_URL) + logout = LogoutView.as_view() + def metadata(request): metadata = utils.create_metadata(request) return HttpResponse(metadata, content_type='text/xml')