From 6a8bdf21f48f15a99522d5825cb5fd1210650543 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Fri, 10 Sep 2021 16:55:04 +0200 Subject: [PATCH] idp_saml2: set sessionNotOnOrAfter to half the current session duration (#56865) --- src/authentic2/idp/saml/saml2_endpoints.py | 11 ++++++++--- tests/test_idp_saml2.py | 11 +++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/authentic2/idp/saml/saml2_endpoints.py b/src/authentic2/idp/saml/saml2_endpoints.py index fe1cb7ce..e23f0c82 100644 --- a/src/authentic2/idp/saml/saml2_endpoints.py +++ b/src/authentic2/idp/saml/saml2_endpoints.py @@ -51,6 +51,7 @@ from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbid from django.shortcuts import redirect, render from django.urls import reverse from django.utils.encoding import force_bytes, force_str, force_text +from django.utils.timezone import utc from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_noop as N_ from django.views.decorators.cache import never_cache @@ -411,6 +412,7 @@ def build_assertion(request, login, provider, nid_format='transient'): """ entity_id = get_entity_id(request) now = datetime.datetime.utcnow() + timezone_now = now.replace(tzinfo=utc) logger.debug('NameIDFormat is %s', nid_format) # 1 minute ago notBefore = now - datetime.timedelta(0, app_settings.SECONDS_TOLERANCE) @@ -453,10 +455,13 @@ def build_assertion(request, login, provider, nid_format='transient'): ) assertion = login.assertion assertion.conditions.notOnOrAfter = notOnOrAfter.isoformat() + 'Z' - # Set SessionNotOnOrAfter to expiry date of the current session, so we are sure no session on - # service providers can outlive the IdP session. + # Set SessionNotOnOrAfter to half of the expire duration of the current + # session, so we are sure no session on service providers can outlive the + # IdP session but people are asked to reauthenticate before the end of the + # IdP session to prolongate it. expiry_date = request.session.get_expiry_date() - assertion.authnStatement[0].sessionNotOnOrAfter = datetime_to_xs_datetime(expiry_date) + session_not_on_or_after = timezone_now + (expiry_date - timezone_now) * 0.5 + assertion.authnStatement[0].sessionNotOnOrAfter = datetime_to_xs_datetime(session_not_on_or_after) logger.debug('assertion building in progress %s', force_text(assertion.dump())) fill_assertion(request, login.request, assertion, login.remoteProviderId, nid_format) # Save federation and new session diff --git a/tests/test_idp_saml2.py b/tests/test_idp_saml2.py index 37f8771b..e0687eeb 100644 --- a/tests/test_idp_saml2.py +++ b/tests/test_idp_saml2.py @@ -388,10 +388,13 @@ class Scenario: assertion = login.assertion session_not_on_or_after = login.assertion.authnStatement[0].sessionNotOnOrAfter assert session_not_on_or_after is not None - assert ( - datetime.datetime.strptime(session_not_on_or_after, '%Y-%m-%dT%H:%M:%SZ') - > datetime.datetime.utcnow() - ) + sp_session_expiry_date = datetime.datetime.strptime(session_not_on_or_after, '%Y-%m-%dT%H:%M:%SZ') + utc_now = datetime.datetime.utcnow() + assert sp_session_expiry_date > utc_now + # check session duration on SP is shorter than on IdP + local_session_expiry_date = self.app.session.get_expiry_date().replace(tzinfo=None) + assert (sp_session_expiry_date - utc_now) < 0.6 * (local_session_expiry_date - utc_now) + assertion_xml = assertion.exportToXml() namespaces = { 'saml': lasso.SAML2_ASSERTION_HREF, -- 2.32.0.rc0