2014-04-28 14:33:04 +02:00
|
|
|
import logging
|
2015-05-18 14:22:22 +02:00
|
|
|
import requests
|
2016-02-26 13:30:50 +01:00
|
|
|
import lasso
|
2016-03-18 15:48:29 +01:00
|
|
|
import uuid
|
2015-06-10 11:47:00 +02:00
|
|
|
from requests.exceptions import RequestException
|
2014-04-28 14:33:04 +02:00
|
|
|
|
|
|
|
from django.views.generic import View
|
|
|
|
from django.http import HttpResponseBadRequest, HttpResponseRedirect, HttpResponse
|
|
|
|
from django.contrib import auth
|
|
|
|
from django.conf import settings
|
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
2016-03-18 15:48:29 +01:00
|
|
|
from django.shortcuts import render, resolve_url
|
2016-02-26 15:57:45 +01:00
|
|
|
from django.utils.http import urlencode
|
2016-03-18 15:48:29 +01:00
|
|
|
from django.contrib.auth import REDIRECT_FIELD_NAME
|
2016-02-26 13:28:49 +01:00
|
|
|
|
2016-02-26 15:59:34 +01:00
|
|
|
from . import app_settings, utils
|
2014-04-28 14:33:04 +02:00
|
|
|
|
|
|
|
|
2015-06-25 11:26:50 +02:00
|
|
|
lasso.setFlag('thin-sessions')
|
|
|
|
|
2016-03-18 15:48:29 +01:00
|
|
|
|
2014-11-24 10:35:01 +01:00
|
|
|
class LogMixin(object):
|
|
|
|
"""Initialize a module logger in new objects"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.log = logging.getLogger(__name__)
|
|
|
|
super(LogMixin, self).__init__(*args, **kwargs)
|
|
|
|
|
2016-03-18 15:48:29 +01:00
|
|
|
|
|
|
|
class ProfileMixin(object):
|
|
|
|
profile = None
|
|
|
|
|
|
|
|
def set_next_url(self, next_url):
|
|
|
|
if not next_url:
|
|
|
|
return
|
|
|
|
if not utils.is_nonnull(next_url):
|
|
|
|
self.log.warning('next parameter ignored, as it contains null characters')
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
next_url.encode('ascii')
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
self.log.warning('next parameter ignored, as is\'s not an ASCII string')
|
|
|
|
return
|
|
|
|
if not utils.same_origin(next_url, self.request.build_absolute_uri()):
|
|
|
|
self.log.warning('next parameter ignored as it is not of the same origin')
|
|
|
|
return
|
|
|
|
self.set_state('next_url', next_url)
|
|
|
|
|
|
|
|
def set_state(self, name, value):
|
|
|
|
assert self.profile
|
|
|
|
relay_state = self.get_relay_state(create=True)
|
|
|
|
self.request.session['mellon_%s_%s' % (name, relay_state)] = value
|
|
|
|
|
|
|
|
def get_state(self, name, default=None):
|
|
|
|
if self.profile:
|
|
|
|
relay_state = self.get_relay_state()
|
|
|
|
key = 'mellon_%s_%s' % (name, relay_state)
|
|
|
|
return self.request.session.get(key, default)
|
|
|
|
return default
|
|
|
|
|
|
|
|
def get_relay_state(self, create=False):
|
|
|
|
if self.profile and self.profile.msgRelayState:
|
|
|
|
try:
|
|
|
|
return uuid.UUID(self.profile.msgRelayState)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
if create:
|
|
|
|
assert self.profile
|
|
|
|
self.profile.msgRelayState = str(uuid.uuid4())
|
|
|
|
return self.profile.msgRelayState
|
|
|
|
|
|
|
|
def get_next_url(self, default=None):
|
|
|
|
return self.get_state('next_url', default=default)
|
|
|
|
|
|
|
|
|
|
|
|
class LoginView(ProfileMixin, LogMixin, View):
|
2014-04-28 14:33:04 +02:00
|
|
|
def get_idp(self, request):
|
2016-02-26 15:57:45 +01:00
|
|
|
entity_id = request.POST.get('entityID') or request.GET.get('entityID')
|
2014-04-28 14:33:04 +02:00
|
|
|
if not entity_id:
|
2016-02-26 13:29:49 +01:00
|
|
|
for idp in utils.get_idps():
|
|
|
|
return idp
|
|
|
|
else:
|
|
|
|
return None
|
2014-04-28 14:33:04 +02:00
|
|
|
else:
|
2015-02-13 18:03:47 +01:00
|
|
|
return utils.get_idp(entity_id)
|
2014-04-28 14:33:04 +02:00
|
|
|
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
'''Assertion consumer'''
|
|
|
|
if 'SAMLResponse' not in request.POST:
|
|
|
|
return self.get(request, *args, **kwargs)
|
2016-02-26 12:03:32 +01:00
|
|
|
if not utils.is_nonnull(request.POST['SAMLResponse']):
|
|
|
|
return HttpResponseBadRequest('SAMLResponse contains a null character')
|
2016-03-18 15:48:29 +01:00
|
|
|
self.profile = login = utils.create_login(request)
|
2014-05-28 20:19:54 +02:00
|
|
|
idp_message = None
|
|
|
|
status_codes = []
|
2016-02-26 12:03:32 +01:00
|
|
|
# prevent null characters in SAMLResponse
|
2014-04-28 14:33:04 +02:00
|
|
|
try:
|
|
|
|
login.processAuthnResponseMsg(request.POST['SAMLResponse'])
|
|
|
|
login.acceptSso()
|
2014-05-28 20:19:54 +02:00
|
|
|
except lasso.ProfileCannotVerifySignatureError:
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.warning('SAML authentication failed: signature validation failed for %r',
|
2016-04-06 01:04:34 +02:00
|
|
|
login.remoteProviderId)
|
2014-05-28 20:19:54 +02:00
|
|
|
except lasso.ParamError:
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.exception('lasso param error')
|
2015-07-21 14:06:54 +02:00
|
|
|
except (lasso.LoginStatusNotSuccessError,
|
|
|
|
lasso.ProfileStatusNotSuccessError,
|
|
|
|
lasso.ProfileRequestDeniedError):
|
2014-05-02 15:58:29 +02:00
|
|
|
status = login.response.status
|
|
|
|
a = status
|
|
|
|
while a.statusCode:
|
|
|
|
status_codes.append(a.statusCode.value)
|
|
|
|
a = a.statusCode
|
2014-05-28 20:19:54 +02:00
|
|
|
args = ['SAML authentication failed: status is not success codes: %r', status_codes]
|
2014-05-02 15:58:29 +02:00
|
|
|
if status.statusMessage:
|
2014-05-28 20:19:54 +02:00
|
|
|
idp_message = status.statusMessage.decode('utf-8')
|
|
|
|
args[0] += ' message: %r'
|
|
|
|
args.append(status.statusMessage)
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.warning(*args)
|
2014-04-28 14:33:04 +02:00
|
|
|
except lasso.Error, e:
|
2016-04-06 01:04:34 +02:00
|
|
|
return HttpResponseBadRequest('error processing the authentication response: %r' % e)
|
2014-05-28 20:19:54 +02:00
|
|
|
else:
|
2016-02-26 12:03:32 +01:00
|
|
|
if 'RelayState' in request.POST and utils.is_nonnull(request.POST['RelayState']):
|
2015-01-30 11:30:09 +01:00
|
|
|
login.msgRelayState = request.POST['RelayState']
|
2015-03-19 17:34:32 +01:00
|
|
|
return self.sso_success(request, login)
|
|
|
|
return self.sso_failure(request, login, idp_message, status_codes)
|
2014-05-28 20:19:54 +02:00
|
|
|
|
2015-03-19 17:34:32 +01:00
|
|
|
def sso_failure(self, request, login, idp_message, status_codes):
|
2014-05-28 20:19:54 +02:00
|
|
|
'''show error message to user after a login failure'''
|
2015-05-18 14:46:16 +02:00
|
|
|
idp = utils.get_idp(login.remoteProviderId)
|
2014-08-05 17:55:29 +02:00
|
|
|
error_url = utils.get_setting(idp, 'ERROR_URL')
|
|
|
|
error_redirect_after_timeout = utils.get_setting(idp, 'ERROR_REDIRECT_AFTER_TIMEOUT')
|
2014-05-28 20:19:54 +02:00
|
|
|
if error_url:
|
|
|
|
error_url = resolve_url(error_url)
|
|
|
|
next_url = error_url or login.msgRelayState or resolve_url(settings.LOGIN_REDIRECT_URL)
|
2016-04-06 01:04:34 +02:00
|
|
|
return render(request, 'mellon/authentication_failed.html',
|
|
|
|
{
|
|
|
|
'debug': settings.DEBUG,
|
|
|
|
'idp_message': idp_message,
|
|
|
|
'status_codes': status_codes,
|
|
|
|
'issuer': login.remoteProviderId,
|
|
|
|
'next_url': next_url,
|
|
|
|
'error_url': error_url,
|
|
|
|
'relaystate': login.msgRelayState,
|
|
|
|
'error_redirect_after_timeout': error_redirect_after_timeout,
|
|
|
|
})
|
2014-05-28 20:19:54 +02:00
|
|
|
|
2015-03-19 17:34:32 +01:00
|
|
|
def sso_success(self, request, login):
|
2014-04-28 14:33:04 +02:00
|
|
|
attributes = {}
|
|
|
|
attribute_statements = login.assertion.attributeStatement
|
|
|
|
for ats in attribute_statements:
|
|
|
|
for at in ats.attribute:
|
|
|
|
values = attributes.setdefault(at.name, [])
|
|
|
|
for value in at.attributeValue:
|
|
|
|
content = [any.exportToXml() for any in value.any]
|
|
|
|
content = ''.join(content)
|
|
|
|
values.append(content.decode('utf8'))
|
2014-12-09 10:30:32 +01:00
|
|
|
attributes['issuer'] = login.remoteProviderId
|
2014-08-05 16:09:54 +02:00
|
|
|
if login.nameIdentifier:
|
|
|
|
name_id = login.nameIdentifier
|
2016-04-06 01:04:34 +02:00
|
|
|
name_id_format = unicode(name_id.format
|
|
|
|
or lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED)
|
2014-08-05 16:09:54 +02:00
|
|
|
attributes.update({
|
2014-11-24 10:35:39 +01:00
|
|
|
'name_id_content': name_id.content.decode('utf8'),
|
2016-04-06 01:04:34 +02:00
|
|
|
'name_id_format': name_id_format
|
2014-08-05 16:09:54 +02:00
|
|
|
})
|
2015-06-25 11:25:57 +02:00
|
|
|
if name_id.nameQualifier:
|
|
|
|
attributes['name_id_name_qualifier'] = unicode(name_id.nameQualifier)
|
|
|
|
if name_id.spNameQualifier:
|
|
|
|
attributes['name_id_sp_name_qualifier'] = unicode(name_id.spNameQualifier)
|
2014-04-28 14:33:04 +02:00
|
|
|
authn_statement = login.assertion.authnStatement[0]
|
|
|
|
if authn_statement.authnInstant:
|
|
|
|
attributes['authn_instant'] = utils.iso8601_to_datetime(authn_statement.authnInstant)
|
|
|
|
if authn_statement.sessionNotOnOrAfter:
|
2016-04-06 01:04:34 +02:00
|
|
|
attributes['session_not_on_or_after'] = utils.iso8601_to_datetime(
|
|
|
|
authn_statement.sessionNotOnOrAfter)
|
2014-04-28 14:33:04 +02:00
|
|
|
if authn_statement.sessionIndex:
|
|
|
|
attributes['session_index'] = authn_statement.sessionIndex
|
|
|
|
attributes['authn_context_class_ref'] = ()
|
|
|
|
if authn_statement.authnContext:
|
|
|
|
authn_context = authn_statement.authnContext
|
|
|
|
if authn_context.authnContextClassRef:
|
|
|
|
attributes['authn_context_class_ref'] = \
|
|
|
|
authn_context.authnContextClassRef
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.debug('trying to authenticate with attributes %r', attributes)
|
2015-03-19 17:34:32 +01:00
|
|
|
return self.authenticate(request, login, attributes)
|
|
|
|
|
|
|
|
def authenticate(self, request, login, attributes):
|
2014-04-28 14:33:04 +02:00
|
|
|
user = auth.authenticate(saml_attributes=attributes)
|
2016-03-18 15:48:29 +01:00
|
|
|
next_url = self.get_next_url(default=resolve_url(settings.LOGIN_REDIRECT_URL))
|
2014-04-28 14:33:04 +02:00
|
|
|
if user is not None:
|
|
|
|
if user.is_active:
|
|
|
|
auth.login(request, user)
|
2016-04-06 01:04:34 +02:00
|
|
|
self.log.info('user %r (NameID is %r) logged in using SAML', unicode(user),
|
|
|
|
attributes['name_id_content'])
|
2014-04-28 14:33:04 +02:00
|
|
|
request.session['mellon_session'] = utils.flatten_datetime(attributes)
|
2016-01-13 17:41:13 +01:00
|
|
|
if ('session_not_on_or_after' in attributes and
|
2016-04-06 01:04:34 +02:00
|
|
|
not settings.SESSION_EXPIRE_AT_BROWSER_CLOSE):
|
|
|
|
request.session.set_expiry(
|
|
|
|
utils.get_seconds_expiry(
|
|
|
|
attributes['session_not_on_or_after']))
|
2014-04-28 14:33:04 +02:00
|
|
|
else:
|
|
|
|
return render(request, 'mellon/inactive_user.html', {
|
|
|
|
'user': user,
|
|
|
|
'saml_attributes': attributes})
|
|
|
|
else:
|
2016-04-06 01:04:34 +02:00
|
|
|
return render(request, 'mellon/user_not_found.html',
|
|
|
|
{'saml_attributes': attributes})
|
2015-02-05 18:26:29 +01:00
|
|
|
request.session['lasso_session_dump'] = login.session.dump()
|
2016-03-18 15:48:29 +01:00
|
|
|
|
2015-09-25 15:38:13 +02:00
|
|
|
return HttpResponseRedirect(next_url)
|
2014-04-28 14:33:04 +02:00
|
|
|
|
2015-05-18 14:22:22 +02:00
|
|
|
def continue_sso_artifact_get(self, request):
|
|
|
|
idp_message = None
|
|
|
|
status_codes = []
|
|
|
|
|
2016-03-18 15:48:29 +01:00
|
|
|
self.profile = login = utils.create_login(request)
|
2015-06-10 11:47:00 +02:00
|
|
|
try:
|
|
|
|
login.initRequest(request.META['QUERY_STRING'], lasso.HTTP_METHOD_ARTIFACT_GET)
|
2016-03-11 15:17:20 +01:00
|
|
|
except lasso.ProfileInvalidArtifactError:
|
|
|
|
self.log.warning(u'artifact is malformed %r', request.GET['SAMLart'])
|
|
|
|
return HttpResponseBadRequest(u'artifact is malformed %r' % request.GET['SAMLart'])
|
2015-06-10 11:47:00 +02:00
|
|
|
except lasso.ServerProviderNotFoundError:
|
|
|
|
self.log.warning('no entity id found for artifact %s',
|
|
|
|
request.GET['SAMLart'])
|
|
|
|
return HttpResponseBadRequest(
|
|
|
|
'no entity id found for this artifact %r' %
|
|
|
|
request.GET['SAMLart'])
|
|
|
|
idp = utils.get_idp(login.remoteProviderId)
|
|
|
|
if not idp:
|
|
|
|
self.log.warning('entity id %r is unknown', login.remoteProviderId)
|
|
|
|
return HttpResponseBadRequest(
|
|
|
|
'entity id %r is unknown' % login.remoteProviderId)
|
|
|
|
verify_ssl_certificate = utils.get_setting(
|
|
|
|
idp, 'VERIFY_SSL_CERTIFICATE')
|
|
|
|
login.buildRequestMsg()
|
|
|
|
try:
|
|
|
|
result = requests.post(login.msgUrl, data=login.msgBody,
|
2016-04-06 01:04:34 +02:00
|
|
|
headers={'content-type': 'text/xml'},
|
|
|
|
verify=verify_ssl_certificate)
|
2015-06-10 11:47:00 +02:00
|
|
|
except RequestException, e:
|
|
|
|
self.log.warning('unable to reach %r: %s', login.msgUrl, e)
|
|
|
|
return HttpResponseBadRequest('unable to reach %r: %s' % (login.msgUrl, e))
|
2015-05-18 14:22:22 +02:00
|
|
|
if result.status_code != 200:
|
2016-04-06 01:04:34 +02:00
|
|
|
self.log.warning('SAML authentication failed: IdP returned %s when given artifact',
|
|
|
|
result.status_code)
|
2015-05-18 14:22:22 +02:00
|
|
|
return self.sso_failure(request, login, idp_message, status_codes)
|
|
|
|
|
|
|
|
try:
|
2015-06-11 16:37:59 +02:00
|
|
|
login.processResponseMsg(result.content)
|
2015-05-18 14:22:22 +02:00
|
|
|
login.acceptSso()
|
2016-03-11 15:23:09 +01:00
|
|
|
except lasso.ProfileInvalidMsgError:
|
|
|
|
self.log.warning('ArtifactResolveResponse is malformed %r' % result.content[:200])
|
|
|
|
if settings.DEBUG:
|
|
|
|
return HttpResponseBadRequest('ArtififactResolveResponse is malformed\n%r' %
|
|
|
|
result.content)
|
|
|
|
else:
|
|
|
|
return HttpResponseBadRequest('ArtififactResolveResponse is malformed')
|
2015-05-18 14:22:22 +02:00
|
|
|
except lasso.ProfileCannotVerifySignatureError:
|
|
|
|
self.log.warning('SAML authentication failed: signature validation failed for %r',
|
2016-04-06 01:04:34 +02:00
|
|
|
login.remoteProviderId)
|
2015-05-18 14:22:22 +02:00
|
|
|
except lasso.ParamError:
|
|
|
|
self.log.exception('lasso param error')
|
|
|
|
except (lasso.ProfileStatusNotSuccessError, lasso.ProfileRequestDeniedError):
|
|
|
|
status = login.response.status
|
|
|
|
a = status
|
|
|
|
while a.statusCode:
|
|
|
|
status_codes.append(a.statusCode.value)
|
|
|
|
a = a.statusCode
|
|
|
|
args = ['SAML authentication failed: status is not success codes: %r', status_codes]
|
|
|
|
if status.statusMessage:
|
|
|
|
idp_message = status.statusMessage.decode('utf-8')
|
|
|
|
args[0] += ' message: %r'
|
|
|
|
args.append(status.statusMessage)
|
|
|
|
self.log.warning(*args)
|
|
|
|
except lasso.Error, e:
|
2015-06-10 11:47:00 +02:00
|
|
|
self.log.exception('unexpected lasso error')
|
2016-04-06 01:04:34 +02:00
|
|
|
return HttpResponseBadRequest('error processing the authentication response: %r' % e)
|
2015-05-18 14:22:22 +02:00
|
|
|
else:
|
2016-02-26 12:03:32 +01:00
|
|
|
if 'RelayState' in request.GET and utils.is_nonnull(request.GET['RelayState']):
|
2015-05-18 14:22:22 +02:00
|
|
|
login.msgRelayState = request.GET['RelayState']
|
|
|
|
return self.sso_success(request, login)
|
|
|
|
return self.sso_failure(request, login, idp_message, status_codes)
|
|
|
|
|
2016-02-26 13:28:49 +01:00
|
|
|
def request_discovery_service(self, request, is_passive=False):
|
|
|
|
self_url = request.build_absolute_uri(request.path)
|
|
|
|
url = app_settings.DISCOVERY_SERVICE_URL
|
|
|
|
params = {
|
|
|
|
# prevent redirect loops with the discovery service
|
|
|
|
'nodisco': '1',
|
|
|
|
'return': self_url
|
|
|
|
}
|
|
|
|
if is_passive:
|
|
|
|
params['isPassive'] = 'true'
|
|
|
|
url += '?' + urlencode(params)
|
|
|
|
return HttpResponseRedirect(url)
|
|
|
|
|
2014-04-28 14:33:04 +02:00
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
'''Initialize login request'''
|
2015-05-18 14:22:22 +02:00
|
|
|
if 'SAMLart' in request.GET:
|
|
|
|
return self.continue_sso_artifact_get(request)
|
2016-02-26 13:28:49 +01:00
|
|
|
|
|
|
|
# redirect to discovery service if needed
|
|
|
|
if (not 'entityID' in request.GET
|
2016-04-06 01:04:34 +02:00
|
|
|
and not 'nodisco' in request.GET
|
|
|
|
and app_settings.DISCOVERY_SERVICE_URL):
|
2016-02-26 13:28:49 +01:00
|
|
|
return self.request_discovery_service(
|
|
|
|
request, is_passive=request.GET.get('passive') == '1')
|
|
|
|
|
2016-03-18 15:48:29 +01:00
|
|
|
next_url = request.GET.get(REDIRECT_FIELD_NAME)
|
2014-04-28 14:33:04 +02:00
|
|
|
idp = self.get_idp(request)
|
|
|
|
if idp is None:
|
2016-02-26 13:31:20 +01:00
|
|
|
return HttpResponseBadRequest('no idp found')
|
2016-03-18 15:48:29 +01:00
|
|
|
self.profile = login = utils.create_login(request)
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.debug('authenticating to %r', idp['ENTITY_ID'])
|
2014-04-28 14:33:04 +02:00
|
|
|
try:
|
2016-04-06 01:04:34 +02:00
|
|
|
login.initAuthnRequest(idp['ENTITY_ID'], lasso.HTTP_METHOD_REDIRECT)
|
2014-04-28 14:33:04 +02:00
|
|
|
authn_request = login.request
|
|
|
|
# configure NameID policy
|
|
|
|
policy = authn_request.nameIdPolicy
|
2015-02-13 18:03:47 +01:00
|
|
|
policy.allowCreate = utils.get_setting(idp, 'NAME_ID_POLICY_ALLOW_CREATE')
|
|
|
|
policy.format = utils.get_setting(idp, 'NAME_ID_POLICY_FORMAT')
|
|
|
|
force_authn = utils.get_setting(idp, 'FORCE_AUTHN')
|
2014-04-28 14:33:04 +02:00
|
|
|
if force_authn:
|
2015-04-29 23:38:22 +02:00
|
|
|
authn_request.forceAuthn = True
|
2014-04-28 14:33:04 +02:00
|
|
|
if request.GET.get('passive') == '1':
|
2015-04-29 23:38:22 +02:00
|
|
|
authn_request.isPassive = True
|
2014-04-28 14:33:04 +02:00
|
|
|
# configure requested AuthnClassRef
|
2015-02-13 18:03:47 +01:00
|
|
|
authn_classref = utils.get_setting(idp, 'AUTHN_CLASSREF')
|
2014-04-28 14:33:04 +02:00
|
|
|
if authn_classref:
|
2016-03-09 08:59:16 +01:00
|
|
|
req_authncontext = lasso.Samlp2RequestedAuthnContext()
|
2014-04-28 14:33:04 +02:00
|
|
|
authn_request.requestedAuthnContext = req_authncontext
|
2016-03-09 08:59:16 +01:00
|
|
|
req_authncontext.authnContextClassRef = tuple(authn_classref)
|
2016-03-18 15:48:29 +01:00
|
|
|
self.set_next_url(next_url)
|
2014-04-28 14:33:04 +02:00
|
|
|
login.buildAuthnRequestMsg()
|
|
|
|
except lasso.Error, e:
|
2016-04-06 01:04:34 +02:00
|
|
|
return HttpResponseBadRequest('error initializing the authentication request: %r' % e)
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.debug('sending authn request %r', authn_request.dump())
|
|
|
|
self.log.debug('to url %r', login.msgUrl)
|
2014-04-28 14:33:04 +02:00
|
|
|
return HttpResponseRedirect(login.msgUrl)
|
|
|
|
|
|
|
|
login = csrf_exempt(LoginView.as_view())
|
|
|
|
|
2016-04-06 01:04:34 +02:00
|
|
|
|
2016-03-18 15:48:29 +01:00
|
|
|
class LogoutView(ProfileMixin, LogMixin, View):
|
2014-05-02 11:48:05 +02:00
|
|
|
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'''
|
2016-03-18 15:48:29 +01:00
|
|
|
self.profile = logout = utils.create_logout(request)
|
2014-05-02 11:48:05 +02:00
|
|
|
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:
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.warning('error validating logout request: %r' % e)
|
2014-05-02 11:48:05 +02:00
|
|
|
issuer = request.session.get('mellon_session', {}).get('issuer')
|
|
|
|
if issuer == logout.remoteProviderId:
|
2016-04-06 01:04:34 +02:00
|
|
|
self.log.info(u'user logged out by IdP SLO request')
|
2015-06-25 11:26:37 +02:00
|
|
|
auth.logout(request)
|
2014-05-02 11:48:05 +02:00
|
|
|
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'''
|
2016-03-18 15:48:29 +01:00
|
|
|
next_url = request.GET.get(REDIRECT_FIELD_NAME)
|
2014-05-02 11:48:05 +02:00
|
|
|
referer = request.META.get('HTTP_REFERER')
|
2016-02-26 15:57:45 +01:00
|
|
|
if not referer or utils.same_origin(referer, request.build_absolute_uri()):
|
2014-05-02 11:48:05 +02:00
|
|
|
if request.user.is_authenticated():
|
2016-03-18 15:48:29 +01:00
|
|
|
logout = None
|
2014-05-02 15:58:48 +02:00
|
|
|
try:
|
|
|
|
issuer = request.session.get('mellon_session', {}).get('issuer')
|
|
|
|
if issuer:
|
2016-03-18 15:48:29 +01:00
|
|
|
self.profile = logout = utils.create_logout(request)
|
2014-05-02 15:58:48 +02:00
|
|
|
try:
|
2016-04-06 01:04:34 +02:00
|
|
|
if 'lasso_session_dump' in request.session:
|
|
|
|
logout.setSessionFromDump(request.session['lasso_session_dump'])
|
2015-02-05 18:26:29 +01:00
|
|
|
else:
|
|
|
|
self.log.error('unable to find lasso session dump')
|
2014-05-02 15:58:48 +02:00
|
|
|
logout.initRequest(issuer, lasso.HTTP_METHOD_REDIRECT)
|
|
|
|
logout.buildRequestMsg()
|
|
|
|
except lasso.Error, e:
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.error('unable to initiate a logout request %r', e)
|
2014-05-02 15:58:48 +02:00
|
|
|
else:
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.debug('sending LogoutRequest %r', logout.request.dump())
|
|
|
|
self.log.debug('to URL %r', logout.msgUrl)
|
2014-05-02 15:58:48 +02:00
|
|
|
return HttpResponseRedirect(logout.msgUrl)
|
|
|
|
finally:
|
2016-03-18 15:48:29 +01:00
|
|
|
auth.logout(request)
|
|
|
|
# set next_url after local logout, as the session is wiped by auth.logout
|
|
|
|
if logout:
|
|
|
|
self.set_next_url(next_url)
|
2016-04-06 01:04:34 +02:00
|
|
|
self.log.info(u'user logged out, SLO request sent to IdP')
|
2014-05-02 11:48:05 +02:00
|
|
|
else:
|
2016-04-06 01:04:34 +02:00
|
|
|
self.log.warning('logout refused referer %r is not of the same origin', referer)
|
2014-05-02 11:48:05 +02:00
|
|
|
return HttpResponseRedirect(next_url)
|
|
|
|
|
|
|
|
def sp_logout_response(self, request):
|
|
|
|
'''Launch a logout request to the identity provider'''
|
2016-03-18 15:48:29 +01:00
|
|
|
self.profile = logout = utils.create_logout(request)
|
2014-05-02 11:48:05 +02:00
|
|
|
try:
|
2014-05-02 15:59:05 +02:00
|
|
|
logout.processResponseMsg(request.META['QUERY_STRING'])
|
2014-05-02 11:48:05 +02:00
|
|
|
except lasso.Error, e:
|
2014-11-24 10:35:01 +01:00
|
|
|
self.log.error('unable to process a logout response %r', e)
|
2016-03-18 15:48:29 +01:00
|
|
|
return HttpResponseRedirect(resolve_url(settings.LOGIN_REDIRECT_URL))
|
|
|
|
next_url = self.get_next_url(default=resolve_url(settings.LOGIN_REDIRECT_URL))
|
|
|
|
return HttpResponseRedirect(next_url)
|
2014-05-02 11:48:05 +02:00
|
|
|
|
2014-04-28 14:33:04 +02:00
|
|
|
|
|
|
|
logout = LogoutView.as_view()
|
|
|
|
|
2014-05-02 11:48:05 +02:00
|
|
|
|
2014-04-28 14:33:04 +02:00
|
|
|
def metadata(request):
|
|
|
|
metadata = utils.create_metadata(request)
|
|
|
|
return HttpResponse(metadata, content_type='text/xml')
|