From cc713a127aca7a07a238f69543fa0455bf8c08e3 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Tue, 16 Apr 2019 10:40:19 +0200 Subject: [PATCH 1/3] views: handle authentication level increase requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit utils: add function to check if a user has a session role Session roles are attributed at SAML login and stored in the HTTP session. They are a subset of all the roles a user is a member of. Il faudra réussir à rendre ce commit générique en hardcodant pas 'role' et même en ne parlant pas de niveau d'authentification. --- README | 13 +++++++++++++ mellon/app_settings.py | 1 + mellon/exceptions.py | 4 ++++ mellon/utils.py | 15 +++++++++++++++ mellon/views.py | 10 +++++++++- 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 mellon/exceptions.py diff --git a/README b/README index a06e1e9..43227f0 100644 --- a/README +++ b/README @@ -216,6 +216,19 @@ value means everything is authorized. Authentication class reference must be obtained from your identity provider but SHOULD come from the SAML 2.0 specification. +MELLON_AUTH_LEVELS_MAPPING +-------------------------- + +When working with an idp which provides authentication levels, this should be a +mapping from the authentication class references the idp provides to their +respective authentication level. Default is {}. Ex.:: + + MELLON_AUTH_LEVELS_MAPPING = { + 'https://entrouvert.org/auth-level/1': 1, + 'https://entrouvert.org/auth-level/2': 2, + 'https://entrouvert.org/auth-level/3': 3, + } + MELLON_GROUP_ATTRIBUTE ---------------------- diff --git a/mellon/app_settings.py b/mellon/app_settings.py index ae095bd..f0286b0 100644 --- a/mellon/app_settings.py +++ b/mellon/app_settings.py @@ -40,6 +40,7 @@ class AppSettings(object): 'ARTIFACT_RESOLVE_TIMEOUT': 10.0, 'LOGIN_HINTS': [], 'SIGNATURE_METHOD': 'RSA-SHA256', + 'AUTH_LEVELS_MAPPING': {}, } @property diff --git a/mellon/exceptions.py b/mellon/exceptions.py new file mode 100644 index 0000000..a74fc99 --- /dev/null +++ b/mellon/exceptions.py @@ -0,0 +1,4 @@ +class RoleNotInSession(Exception): + + def __init__(self, value): + self.value = value diff --git a/mellon/utils.py b/mellon/utils.py index 6462f81..4f1ded1 100644 --- a/mellon/utils.py +++ b/mellon/utils.py @@ -6,6 +6,7 @@ import isodate from xml.parsers import expat from django.contrib import auth +from django.contrib.auth.models import Group from django.core.urlresolvers import reverse from django.template.loader import render_to_string from django.utils.timezone import make_aware, now, make_naive, is_aware, get_default_timezone @@ -14,6 +15,7 @@ from django.utils.six.moves.urllib.parse import urlparse import lasso from . import app_settings +from .exceptions import RoleNotInSession def create_metadata(request): @@ -271,3 +273,16 @@ def get_local_path(request, url): if request.META.get('SCRIPT_NAME'): path = path[len(request.META['SCRIPT_NAME']):] return path + + +def user_has_role(request, role_id): + try: + group = request.user.groups.get(id=role_id) + except Group.DoesNotExist: + return False + role = getattr(group, 'role') + if not role: + return True + if role.uuid in request.session['role_uuids']: + return True + raise RoleNotInSession(role.auth_level) diff --git a/mellon/views.py b/mellon/views.py index 5a39adf..244c453 100644 --- a/mellon/views.py +++ b/mellon/views.py @@ -375,6 +375,7 @@ class LoginView(ProfileMixin, LogMixin, View): request, is_passive=request.GET.get('passive') == '1') next_url = check_next_url(self.request, request.GET.get(REDIRECT_FIELD_NAME)) + requested_auth_level = request.GET.get('auth_level') idp = self.get_idp(request) if idp is None: return HttpResponseBadRequest('no idp found') @@ -394,7 +395,14 @@ class LoginView(ProfileMixin, LogMixin, View): authn_request.isPassive = True # configure requested AuthnClassRef authn_classref = utils.get_setting(idp, 'AUTHN_CLASSREF') - if authn_classref: + authn_classref_levels = utils.get_setting(idp, 'AUTH_LEVELS_MAPPING') + if requested_auth_level and authn_classref_levels: + authn_classref = tuple(cr for cr, lvl in authn_classref_levels.items() + if lvl == int(requested_auth_level)) + req_authncontext = lasso.Samlp2RequestedAuthnContext() + authn_request.requestedAuthnContext = req_authncontext + req_authncontext.authnContextClassRef = authn_classref + elif authn_classref: authn_classref = tuple([str(x) for x in authn_classref]) req_authncontext = lasso.Samlp2RequestedAuthnContext() authn_request.requestedAuthnContext = req_authncontext -- 2.20.1