diff --git a/README b/README
index 0ddff95..49092d7 100644
--- a/README
+++ b/README
@@ -216,3 +216,16 @@ MELLON_CREATE_GROUP
-------------------
Whether to create group or only assign existing groups, default is True.
+
+MELLON_ERROR_URL
+================
+
+URL for the continue link when authentication fails, default is
+None. If not ERROR_URL is None, the RelayState is used. If there is
+no RelayState, the LOGIN_REDIRECT_URL, which defaults to /, is used.
+
+MELLON_ERROR_REDIRECT_AFTER_TIMEOUT
+===================================
+
+Timeout in seconds before automatically redirecting the user to the
+continue URL when authentication has failed. Default is 120 seconds.
diff --git a/mellon/app_settings.py b/mellon/app_settings.py
index ed071e4..4460b02 100644
--- a/mellon/app_settings.py
+++ b/mellon/app_settings.py
@@ -21,6 +21,8 @@ class AppSettings(object):
'AUTHN_CLASSREF': (),
'GROUP_ATTRIBUTE': None,
'CREATE_GROUP': True,
+ 'ERROR_URL': None,
+ 'ERROR_REDIRECT_AFTER_TIMEOUT': 120,
}
@property
diff --git a/mellon/templates/mellon/authentication_failed.html b/mellon/templates/mellon/authentication_failed.html
index 10ddc14..f86b5f1 100644
--- a/mellon/templates/mellon/authentication_failed.html
+++ b/mellon/templates/mellon/authentication_failed.html
@@ -1,24 +1,38 @@
{% extends "base.html" %}
{% load i18n %}
+{% block extra_scripts %}
+ {% if error_redirect_after_timeout %}
+
-
{% trans "Authentication failed" %}
-
- {% blocktrans %}The authentication has failed, you can return to
- the last page you where.
- {% endblocktrans %}
+
+
+ {% blocktrans %}The authentication has failed.{% endblocktrans %}
-
- - {% trans "Issuer" %}
- - {{ issuer }}
- {% if status_message %}
- - {% trans "Message" %}
- - {{ status_message }}
- {% endif %}
- - {% trans "Codes" %}
- - {{ status_codes|join:", " }}
-
+
+ {% trans "Continue" %}
+
+ {% if debug %}
+
+ - {% trans "Issuer" %}
+ - {{ issuer }}
+ {% if status_message %}
+ - {% trans "Message" %}
+ - {{ status_message }}
+ {% endif %}
+ {% if status_codes %}
+ - {% trans "Codes" %}
+ - {{ status_codes|join:", " }}
+ {% endif %}
+ {% if relaystate %}
+ - {% trans "Relaystate" %}
+ - {{ relaystate }}
+ {% endif %}
+
+ {% endif %}
{% endblock %}
diff --git a/mellon/views.py b/mellon/views.py
index 00e574a..923a335 100644
--- a/mellon/views.py
+++ b/mellon/views.py
@@ -7,6 +7,8 @@ from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render, redirect, resolve_url
from django.utils.http import same_origin
+from django.utils.translation import ugettext as _
+from django.contrib import messages
import lasso
@@ -29,31 +31,55 @@ class LoginView(View):
if 'SAMLResponse' not in request.POST:
return self.get(request, *args, **kwargs)
login = utils.create_login(request)
+ idp_message = None
+ status_codes = []
try:
login.processAuthnResponseMsg(request.POST['SAMLResponse'])
login.acceptSso()
- except lasso.ProfileStatusNotSuccessError, e:
- status_codes = []
+ except lasso.ProfileCannotVerifySignatureError:
+ log.warning('SAML authentication failed: signature validation failed for %r',
+ login.remoteProviderId)
+ except lasso.ParamError:
+ 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
- log.warning('SAML authentication failed, codes: %r', status_codes)
+ args = ['SAML authentication failed: status is not success codes: %r', status_codes]
if status.statusMessage:
- log.warning('SAML authentication failed, message: %r',
- status.statusMessage)
- next_url = login.msgRelayState or \
- resolve_url(settings.LOGIN_REDIRECT_URL)
- return render(request, 'mellon/authentication_failed.html', {
- 'status_message': status.statusMessage,
- 'status_codes': status_codes,
- 'issuer': login.remoteProviderId,
- 'next_url': next_url,
- })
+ idp_message = status.statusMessage.decode('utf-8')
+ args[0] += ' message: %r'
+ args.append(status.statusMessage)
+ log.warning(*args)
except lasso.Error, e:
return HttpResponseBadRequest('error processing the authentication '
'response: %r' % e)
+ else:
+ return self.login_success(request, login)
+ return self.login_failure(request, login, idp_message, status_codes)
+
+ def login_failure(self, request, login, idp_message, status_codes):
+ '''show error message to user after a login failure'''
+ idp = self.get_idp(request)
+ error_url = utils.get_parameter(idp, 'ERROR_URL')
+ error_redirect_after_timeout = utils.get_parameter(idp, 'ERROR_REDIRECT_AFTER_TIMEOUT')
+ if error_url:
+ error_url = resolve_url(error_url)
+ next_url = error_url or login.msgRelayState or resolve_url(settings.LOGIN_REDIRECT_URL)
+ 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,
+ })
+
+ def login_success(self, request, login):
name_id = login.nameIdentifier
attributes = {}
attribute_statements = login.assertion.attributeStatement