improve handling of authentication errors

All errors are logged as warnings, status message and status codes are
logged, and if DEBUG is True, shown to the user.
This commit is contained in:
Benjamin Dauvergne 2014-05-28 20:19:54 +02:00
parent e1a962608c
commit bcb055263b
4 changed files with 83 additions and 28 deletions

13
README
View File

@ -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.

View File

@ -21,6 +21,8 @@ class AppSettings(object):
'AUTHN_CLASSREF': (),
'GROUP_ATTRIBUTE': None,
'CREATE_GROUP': True,
'ERROR_URL': None,
'ERROR_REDIRECT_AFTER_TIMEOUT': 120,
}
@property

View File

@ -1,24 +1,38 @@
{% extends "base.html" %}
{% load i18n %}
{% block extra_scripts %}
{% if error_redirect_after_timeout %}
<meta http-equiv="refresh" content="{{ error_redirect_after_timeout }};url={{ next_url }}"
{% endif %}
{% endblock
{% block content %}
<div id="mellon-authentication-failure" class="mellon-message">
<h2>{% trans "Authentication failed" %}</h2>
<p>
{% blocktrans %}The authentication has failed, you can return to
the <a href="{{ next_url }}">last page</a> you where.
{% endblocktrans %}
<h2 class="mellon-message-header">{% trans "Authentication failed" %}</h2>
<p class="mellon-message-body">
{% blocktrans %}The authentication has failed.{% endblocktrans %}
</p>
<dl id="mellon-authentication-failure-details">
<dt>{% trans "Issuer" %}</dt>
<dd>{{ issuer }}</dd>
{% if status_message %}
<dt>{% trans "Message" %}</dt>
<dd>{{ status_message }}</dd>
{% endif %}
<dt>{% trans "Codes" %}</dt>
<dd><tt>{{ status_codes|join:", " }}</tt></dd>
</dl>
<p class="mellon-message-continue">
<a class="mellon-link" href="{{ next_url }}">{% trans "Continue" %}</a>
</p>
{% if debug %}
<dl id="mellon-authentication-failure-details">
<dt class="mellon-issuer">{% trans "Issuer" %}</dt>
<dd class="mellon-issuer"><tt>{{ issuer }}</tt></dd>
{% if status_message %}
<dt class="mellon-status-message">{% trans "Message" %}</dt>
<dd class="mellon-status-message">{{ status_message }}</dd>
{% endif %}
{% if status_codes %}
<dt class="mellon-status-codes">{% trans "Codes" %}</dt>
<dd class="mellon-status-codes"><tt>{{ status_codes|join:", " }}</tt></dd>
{% endif %}
{% if relaystate %}
<dt class="mellon-relaystate">{% trans "Relaystate" %}</dt>
<dd class="mellon-relaystate"><tt>{{ relaystate }}</tt></dd>
{% endif %}
</dl>
{% endif %}
</div>
{% endblock %}

View File

@ -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