From 0d013b00f92b31dd54ee92cf31bf7dcd74f4f15b Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Tue, 16 Apr 2019 10:14:23 +0200 Subject: [PATCH 3/3] idp_saml: handle authentication level increase request Because we don't tell SP which role grants a user superuser status, we have to let them request a special 'staff' value instead of a role uuid. --- src/authentic2/idp/saml/saml2_endpoints.py | 58 ++++++++++++++++------ 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/src/authentic2/idp/saml/saml2_endpoints.py b/src/authentic2/idp/saml/saml2_endpoints.py index 5bc0405d..6cbfbf4b 100644 --- a/src/authentic2/idp/saml/saml2_endpoints.py +++ b/src/authentic2/idp/saml/saml2_endpoints.py @@ -59,7 +59,9 @@ from django.utils.six.moves.urllib.parse import quote, urlencode from django.contrib.auth import load_backend from django.shortcuts import render, redirect from django.contrib import messages +from django.db.models import Min +from django_rbac.utils import get_role_model import authentic2.views as a2_views from authentic2.saml.models import ( @@ -101,6 +103,7 @@ from . import app_settings User = get_user_model() +Role = get_role_model() logger = logging.getLogger(__name__) @@ -539,16 +542,20 @@ def sso(request): return sso_after_process_request(request, login, nid_format=nid_format) -def need_login(request, login, nid_format, service): +def need_login(request, login, nid_format, service, auth_level=None): """Redirect to the login page with a nonce parameter to verify later that the login form was submitted """ nonce = login.request.id or get_nonce() save_key_values(nonce, login.dump(), False, nid_format) - next_url = make_url(continue_sso, params={NONCE_FIELD_NAME: nonce}) + params = { + NONCE_FIELD_NAME: nonce + } + next_url = make_url(continue_sso, params=params) + if auth_level: + params['auth_level'] = auth_level logger.debug('redirect to login page with next url %s', next_url) - return login_require(request, next_url=next_url, params={NONCE_FIELD_NAME: nonce}, - service=service) + return login_require(request, next_url=next_url, params=params, service=service) def get_url_with_nonce(request, function, nonce): @@ -645,21 +652,44 @@ def sso_after_process_request(request, login, consent_obtained=False, did_auth = find_authentication_event(request, nonce) is not None force_authn = login.request.forceAuthn passive = login.request.isPassive + service = LibertyServiceProvider.objects.get( + liberty_provider__entity_id=login.remoteProviderId).liberty_provider + + prefix = 'https://entrouvert.com/authn-class-ref/role-uuid/' # TODO setting + requested_auth_level = 1 + if login.request.requestedAuthnContext: + authn_classrefs = login.request.requestedAuthnContext.authnContextClassRef + roles = Role.objects.for_user(request.user, annotate=True) + role_uuids = set() + for classref in authn_classrefs: + if classref.startswith(prefix): + role_uuid = classref[len(prefix):] + if role_uuid == 'staff': + role_uuids.update(service.roles.values_list('uuid', flat=True)) + else: + role_uuids.add(role_uuid) + requested_roles = roles.filter(uuid__in=role_uuids) + # TODO if empty, fail with error 'user do not have requested roles' + requested_auth_level = \ + requested_roles.aggregate(min_lvl=Min('needed_auth_level'))['min_lvl'] logger.debug('NameIDFormat is %s', nid_format) logger.debug('nonce is %s', nonce) # check if user is authorized through this service - service = LibertyServiceProvider.objects.get( - liberty_provider__entity_id=login.remoteProviderId).liberty_provider - - if not passive and \ - (user.is_anonymous() or (force_authn and not did_auth)): - logger.debug('login required') - return need_login(request, login, nid_format, service) - - # No user is authenticated and passive is True, deny request - if passive and user.is_anonymous(): + current_auth_level = request.session.get('auth_level', 1) + if not passive: + if not user.is_anonymous() and requested_auth_level > current_auth_level: + logger.debug('authentication level increase required') + requested_auth_level = current_auth_level + 1 # progressively increase auth level + return need_login(request, login, nid_format, service, requested_auth_level) + if user.is_anonymous() or (force_authn and not did_auth): + logger.debug('login required') + return need_login(request, login, nid_format, service) + + # No user is authenticated or authentication level is too low and passive + # is True, deny request + if passive and (user.is_anonymous() or requested_auth_level > current_auth_level): logger.debug('no user connected and passive request, returning NoPassive') set_saml2_response_responder_status_code(login.response, lasso.SAML2_STATUS_CODE_NO_PASSIVE) return finish_sso(request, login) -- 2.20.1