From c0721a6230748a54666a97f3b14f98398901a03d Mon Sep 17 00:00:00 2001
From: Jaime Perez Crespo <jaime.perez@uninett.no>
Date: Wed, 16 Mar 2016 13:50:42 +0100
Subject: [PATCH] Be graceful if a SAML assertion does not contain a NameID. Do
 not set it in the state array, and set logout as "saml1" to avoid SLO, since
 SLO requires NameIDs.

---
 modules/saml/www/sp/saml2-acs.php | 45 ++++++++++++++++++++++---------
 1 file changed, 32 insertions(+), 13 deletions(-)

diff --git a/modules/saml/www/sp/saml2-acs.php b/modules/saml/www/sp/saml2-acs.php
index e0e93415c..611ea0b08 100644
--- a/modules/saml/www/sp/saml2-acs.php
+++ b/modules/saml/www/sp/saml2-acs.php
@@ -186,23 +186,42 @@ if ($expire !== null) {
     $logoutExpire = time() + 24 * 60 * 60;
 }
 
-// register this session in the logout store
-sspmod_saml_SP_LogoutStore::addSession($sourceId, $nameId, $sessionIndex, $logoutExpire);
-
-// we need to save the NameID and SessionIndex for logout
-$logoutState = array(
-    'saml:logout:Type'         => 'saml2',
-    'saml:logout:IdP'          => $idp,
-    'saml:logout:NameID'       => $nameId,
-    'saml:logout:SessionIndex' => $sessionIndex,
-);
+if (!empty($nameId)) {
+    // register this session in the logout store
+    sspmod_saml_SP_LogoutStore::addSession($sourceId, $nameId, $sessionIndex, $logoutExpire);
+
+    // we need to save the NameID and SessionIndex for logout
+    $logoutState = array(
+        'saml:logout:Type'         => 'saml2',
+        'saml:logout:IdP'          => $idp,
+        'saml:logout:NameID'       => $nameId,
+        'saml:logout:SessionIndex' => $sessionIndex,
+    );
+
+    $state['saml:sp:NameID'] = $nameId; // no need to mark it as persistent, it already is
+} else {
+    /*
+     * No NameID provided, we can't logout from this IdP!
+     *
+     * Even though interoperability profiles "require" a NameID, the SAML 2.0 standard does not require it to be present
+     * in assertions. That way, we could have a Subject with only a SubjectConfirmation, or even no Subject element at
+     * all.
+     *
+     * In case we receive a SAML assertion with no NameID, we can be graceful and continue, but we won't be able to
+     * perform a Single Logout since the SAML logout profile mandates the use of a NameID to identify the individual we
+     * want to be logged out. In order to minimize the impact of this, we keep logout state information (without saving
+     * it to the store), marking the IdP as SAML 1.0, which does not implement logout. Then we can safely log the user
+     * out from the local session, skipping Single Logout upstream to the IdP.
+     */
+    $logoutState = array(
+        'saml:logout:Type'         => 'saml1',
+    );
+}
+
 $state['LogoutState'] = $logoutState;
 $state['saml:AuthenticatingAuthority'] = $authenticatingAuthority;
 $state['saml:AuthenticatingAuthority'][] = $idp;
 $state['PersistentAuthData'][] = 'saml:AuthenticatingAuthority';
-
-$state['saml:sp:NameID'] = $nameId;
-$state['PersistentAuthData'][] = 'saml:sp:NameID';
 $state['saml:sp:SessionIndex'] = $sessionIndex;
 $state['PersistentAuthData'][] = 'saml:sp:SessionIndex';
 $state['saml:sp:AuthnContext'] = $assertion->getAuthnContext();
-- 
GitLab