[java-identity-provider] branch dev/logout-enh updated: IDP-1825 - Extend SAML2 Session storage with knowledge of logout support

Scott Cantor cantor.2 at osu.edu
Fri Jun 4 13:26:45 UTC 2021


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch dev/logout-enh
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=934499f497186926f403f0a2f0d9ca7b8d591769

The following commit(s) were added to refs/heads/dev/logout-enh by this push:
       new  934499f49 IDP-1825 - Extend SAML2 Session storage with knowledge of logout support
934499f49 is described below

commit 934499f497186926f403f0a2f0d9ca7b8d591769
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Jun 4 09:26:42 2021 -0400

    IDP-1825 - Extend SAML2 Session storage with knowledge of logout support
    
    https://issues.shibboleth.net/jira/browse/IDP-1825
---
 .../idp/cas/session/impl/CASSPSession.java         |  7 +++++
 idp-conf/src/test/resources/conf/idp.properties    |  2 +-
 .../idp/saml/session/SAML2SPSession.java           | 34 ++++++++++++++++++++++
 .../idp/saml/session/SAML2SPSessionTest.java       | 17 ++++++-----
 .../impl/SAML2SPSessionCreationStrategy.java       | 17 ++++++++++-
 .../session/impl/SAML2SPSessionSerializer.java     |  9 ++++--
 .../impl/PrepareInboundMessageContextTest.java     |  2 +-
 .../session/impl/SAML2SPSessionSerializerTest.java |  3 +-
 .../idp/saml/impl/session/saml2SPSession.json      |  2 +-
 .../session/LogoutPropagationFlowDescriptor.java   | 20 +++++++++++++
 .../net/shibboleth/idp/session/SPSessionEx.java    | 12 ++++++++
 .../LogoutPropagationFlowDescriptorSelector.java   |  3 +-
 12 files changed, 112 insertions(+), 16 deletions(-)

diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/session/impl/CASSPSession.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/session/impl/CASSPSession.java
index 4ef038554..30ce273f5 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/session/impl/CASSPSession.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/session/impl/CASSPSession.java
@@ -66,6 +66,7 @@ public class CASSPSession extends BasicSPSession implements SPSessionEx {
         return ticket;
     }
 
+    /** {@inheritDoc} */
     @Override
     public String getSPSessionKey() {
         return ticket;
@@ -75,6 +76,12 @@ public class CASSPSession extends BasicSPSession implements SPSessionEx {
     @Nullable @NotEmpty public String getProtocol() {
         return AbstractProtocolConfiguration.PROTOCOL_URI;
     }
+    
+    /** {@inheritDoc} */
+    @Override
+    public boolean supportsLogoutPropagation() {
+        return true;
+    }
 
     @Override
     public String toString() {
diff --git a/idp-conf/src/test/resources/conf/idp.properties b/idp-conf/src/test/resources/conf/idp.properties
index fb9507cdd..c6dd2a22b 100644
--- a/idp-conf/src/test/resources/conf/idp.properties
+++ b/idp-conf/src/test/resources/conf/idp.properties
@@ -164,7 +164,7 @@ idp.session.secondaryServiceIndex = true
 #idp.logout.authenticated = true
 
 # Whether to hide logout propagation status reporting.
-idp.logout.propagationHidden = true
+#idp.logout.propagationHidden = false
 
 # Bean to determine whether user should be allowed to cancel logout
 #idp.logout.promptUser=shibboleth.Conditions.FALSE
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/session/SAML2SPSession.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/session/SAML2SPSession.java
index c6834c268..af7730ab1 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/session/SAML2SPSession.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/session/SAML2SPSession.java
@@ -51,6 +51,9 @@ public class SAML2SPSession extends BasicSPSession implements SPSessionEx {
     /** The SessionIndex asserted to the SP. */
     @Nonnull @NotEmpty private final String sessionIndex;
     
+    /** Whether logout propagation is possible. */
+    private final boolean supportsLogoutPropagation;
+    
 // Checkstyle: ParameterNumber OFF
     /**
      * Constructor.
@@ -60,7 +63,10 @@ public class SAML2SPSession extends BasicSPSession implements SPSessionEx {
      * @param expiration expiration time of session
      * @param assertedNameID the NameID asserted to the SP
      * @param assertedIndex the SessionIndex asserted to the SP
+     * 
+     * @deprecated
      */
+    @Deprecated(since="4.2.0", forRemoval=true)
     public SAML2SPSession(@Nonnull @NotEmpty final String id, @Nonnull final Instant creation,
             @Nonnull final Instant expiration, @Nonnull final NameID assertedNameID,
             @Nonnull @NotEmpty final String assertedIndex) {
@@ -69,6 +75,28 @@ public class SAML2SPSession extends BasicSPSession implements SPSessionEx {
         nameID = Constraint.isNotNull(assertedNameID, "NameID cannot be null");
         sessionIndex = Constraint.isNotNull(StringSupport.trimOrNull(assertedIndex),
                 "SessionIndex cannot be null or empty");
+        supportsLogoutPropagation = true;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param id the identifier of the service associated with this session
+     * @param creation creation time of session
+     * @param expiration expiration time of session
+     * @param assertedNameID the NameID asserted to the SP
+     * @param assertedIndex the SessionIndex asserted to the SP
+     * @param supportsLogoutProp whether the session supports logout propagation
+     */
+    public SAML2SPSession(@Nonnull @NotEmpty final String id, @Nonnull final Instant creation,
+            @Nonnull final Instant expiration, @Nonnull final NameID assertedNameID,
+            @Nonnull @NotEmpty final String assertedIndex, final boolean supportsLogoutProp) {
+        super(id, creation, expiration);
+        
+        nameID = Constraint.isNotNull(assertedNameID, "NameID cannot be null");
+        sessionIndex = Constraint.isNotNull(StringSupport.trimOrNull(assertedIndex),
+                "SessionIndex cannot be null or empty");
+        supportsLogoutPropagation = supportsLogoutProp;
     }
 // Checkstyle: ParameterNumber ON
    
@@ -101,6 +129,12 @@ public class SAML2SPSession extends BasicSPSession implements SPSessionEx {
         return SAMLConstants.SAML20P_NS;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public boolean supportsLogoutPropagation() {
+        return supportsLogoutPropagation;
+    }
+    
     /** {@inheritDoc} */
     @Override
     public int hashCode() {
diff --git a/idp-saml-api/src/test/java/net/shibboleth/idp/saml/session/SAML2SPSessionTest.java b/idp-saml-api/src/test/java/net/shibboleth/idp/saml/session/SAML2SPSessionTest.java
index 424961982..43ef13a73 100644
--- a/idp-saml-api/src/test/java/net/shibboleth/idp/saml/session/SAML2SPSessionTest.java
+++ b/idp-saml-api/src/test/java/net/shibboleth/idp/saml/session/SAML2SPSessionTest.java
@@ -46,58 +46,59 @@ public class SAML2SPSessionTest extends OpenSAMLInitBaseTestCase {
         Thread.sleep(50);
 
         SAML2SPSession session = new SAML2SPSession("test", Instant.now(),
-                Instant.now().plusSeconds(60), nameID, "1234567890");
+                Instant.now().plusSeconds(60), nameID, "1234567890", false);
         Assert.assertEquals(session.getId(), "test");
         Assert.assertTrue(session.getCreationInstant().isAfter(start));
         Assert.assertTrue(session.getExpirationInstant().isAfter(session.getCreationInstant()));
         Assert.assertSame(session.getNameID(), nameID);
         Assert.assertEquals(session.getSessionIndex(), "1234567890");
         Assert.assertEquals(session.getSPSessionKey(), "joe at example.org");
+        Assert.assertFalse(session.supportsLogoutPropagation());
 
         try {
-            new SAML2SPSession(null, Instant.ofEpochMilli(0), Instant.ofEpochMilli(0), null, null);
+            new SAML2SPSession(null, Instant.ofEpochMilli(0), Instant.ofEpochMilli(0), null, null, true);
             Assert.fail();
         } catch (ConstraintViolationException e) {
 
         }
 
         try {
-            new SAML2SPSession("", Instant.ofEpochMilli(0), Instant.ofEpochMilli(0), null, null);
+            new SAML2SPSession("", Instant.ofEpochMilli(0), Instant.ofEpochMilli(0), null, null, true);
             Assert.fail();
         } catch (ConstraintViolationException e) {
 
         }
 
         try {
-            new SAML2SPSession("  ", Instant.ofEpochMilli(0), Instant.ofEpochMilli(0), null, null);
+            new SAML2SPSession("  ", Instant.ofEpochMilli(0), Instant.ofEpochMilli(0), null, null, true);
             Assert.fail();
         } catch (ConstraintViolationException e) {
 
         }
 
         try {
-            new SAML2SPSession("foo", Instant.ofEpochMilli(0), Instant.ofEpochMilli(0), null, null);
+            new SAML2SPSession("foo", Instant.ofEpochMilli(0), Instant.ofEpochMilli(0), null, null, true);
             Assert.fail();
         } catch (ConstraintViolationException e) {
 
         }
 
         try {
-            new SAML2SPSession("foo", start, Instant.ofEpochMilli(0), null, null);
+            new SAML2SPSession("foo", start, Instant.ofEpochMilli(0), null, null, true);
             Assert.fail();
         } catch (ConstraintViolationException e) {
 
         }
 
         try {
-            new SAML2SPSession("foo", start, start, null, null);
+            new SAML2SPSession("foo", start, start, null, null, true);
             Assert.fail();
         } catch (ConstraintViolationException e) {
 
         }
 
         try {
-            new SAML2SPSession("foo", start, start, nameID, null);
+            new SAML2SPSession("foo", start, start, nameID, null, true);
             Assert.fail();
         } catch (ConstraintViolationException e) {
 
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionCreationStrategy.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionCreationStrategy.java
index 2253766af..c38fb2438 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionCreationStrategy.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionCreationStrategy.java
@@ -28,9 +28,12 @@ import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.context.navigate.MessageLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
 import org.opensaml.saml.saml2.core.Assertion;
 import org.opensaml.saml.saml2.core.AuthnStatement;
 import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -131,8 +134,20 @@ public class SAML2SPSessionCreationStrategy implements Function<ProfileRequestCo
             expiration = now.plus(sessionLifetime);
         }
         
+        // Do a basic check for outbound logout capability to the SP based on metadata.
+        // This may optimize out subsequent need to process the session for propagation.
+        boolean supportLogoutPropagation = false;
+        if (rpCtx.getRelyingPartyIdContextTree() instanceof SAMLPeerEntityContext) {
+            final SAMLMetadataContext mdCtx =
+                    rpCtx.getRelyingPartyIdContextTree().getSubcontext(SAMLMetadataContext.class);
+            if (mdCtx != null && mdCtx.getRoleDescriptor() instanceof SPSSODescriptor) {
+                supportLogoutPropagation =
+                        !((SPSSODescriptor) mdCtx.getRoleDescriptor()).getSingleLogoutServices().isEmpty();
+            }
+        }
+        
         return new SAML2SPSession(issuer, now, expiration, result.getFirst().getSubject().getNameID(),
-                result.getSecond().getSessionIndex());
+                result.getSecond().getSessionIndex(), supportLogoutPropagation);
     }
 
     /**
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionSerializer.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionSerializer.java
index f3a7374d0..b175ed819 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionSerializer.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionSerializer.java
@@ -59,7 +59,10 @@ public class SAML2SPSessionSerializer extends AbstractSPSessionSerializer {
 
     /** Field name of SessionIndex. */
     @Nonnull @NotEmpty private static final String SESSION_INDEX_FIELD = "ix";
-
+    
+    /** Field name of Single Logout indicator. */
+    @Nonnull @NotEmpty private static final String LOGOUT_PROP_FIELD = "slo";
+    
     /** DOM configuration parameters used by LSSerializer to exclude XML declaration. */
     @Nonnull private static final Map<String, Object> NO_XML_DECL_PARAMS;
     
@@ -98,6 +101,7 @@ public class SAML2SPSessionSerializer extends AbstractSPSessionSerializer {
             generator.write(NAMEID_FIELD, SerializeSupport.nodeToString(
                     XMLObjectSupport.marshall(saml2Session.getNameID()), NO_XML_DECL_PARAMS));
             generator.write(SESSION_INDEX_FIELD, saml2Session.getSessionIndex());
+            generator.write(LOGOUT_PROP_FIELD, saml2Session.supportsLogoutPropagation());
         } catch (final MarshallingException e) {
             throw new XMLRuntimeException("Error marshalling and serializing NameID", e);
         }
@@ -110,11 +114,12 @@ public class SAML2SPSessionSerializer extends AbstractSPSessionSerializer {
         
         final String rawNameID = obj.getString(NAMEID_FIELD);
         final String sessionIndex = obj.getString(SESSION_INDEX_FIELD);
+        final boolean supportsLogoutProp = obj.getBoolean(LOGOUT_PROP_FIELD, true);
         
         try {
             final XMLObject nameID = XMLObjectSupport.unmarshallFromReader(parserPool, new StringReader(rawNameID));
             if (nameID instanceof NameID) {
-                return new SAML2SPSession(id, creation, expiration, (NameID) nameID, sessionIndex);
+                return new SAML2SPSession(id, creation, expiration, (NameID) nameID, sessionIndex, supportsLogoutProp);
             }
             throw new IOException("XMLObject stored in NameID field was not a NameID");
         } catch (final XMLParserException | UnmarshallingException e) {
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/session/impl/PrepareInboundMessageContextTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/session/impl/PrepareInboundMessageContextTest.java
index 3580ce5ac..1b86677fb 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/session/impl/PrepareInboundMessageContextTest.java
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/session/impl/PrepareInboundMessageContextTest.java
@@ -52,7 +52,7 @@ public class PrepareInboundMessageContextTest extends OpenSAMLInitBaseTestCase {
         prc.setInboundMessageContext(null);
                
         final SAML2SPSession session = new SAML2SPSession("https://sp.example.org", Instant.now(),
-                Instant.now().plusSeconds(1800), SAML2ActionTestingSupport.buildNameID("jdoe"), "foo");
+                Instant.now().plusSeconds(1800), SAML2ActionTestingSupport.buildNameID("jdoe"), "foo", true);
         prc.getSubcontext(LogoutPropagationContext.class, true).setSession(session);
         
         action = new PrepareInboundMessageContext();
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionSerializerTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionSerializerTest.java
index 7637d58c8..4be5e07e2 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionSerializerTest.java
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/session/impl/SAML2SPSessionSerializerTest.java
@@ -88,7 +88,7 @@ public class SAML2SPSessionSerializerTest extends OpenSAMLInitBaseTestCase {
         NameID nameID = (NameID) XMLObjectSupport.buildXMLObject(NameID.DEFAULT_ELEMENT_NAME);
         nameID.setValue("joe at example.org");
         
-        SAML2SPSession session = new SAML2SPSession("test", INSTANT, exp, nameID, SESSION_INDEX);
+        SAML2SPSession session = new SAML2SPSession("test", INSTANT, exp, nameID, SESSION_INDEX, false);
         
         String s = serializer.serialize(session);
         String s2 = fileToString(DATAPATH + "saml2SPSession.json");
@@ -101,6 +101,7 @@ public class SAML2SPSessionSerializerTest extends OpenSAMLInitBaseTestCase {
         Assert.assertEquals(session.getExpirationInstant(), session2.getExpirationInstant());
         Assert.assertEquals(session.getNameID().getValue(), session2.getNameID().getValue());
         Assert.assertEquals(session.getSessionIndex(), session2.getSessionIndex());
+        Assert.assertFalse(session2.supportsLogoutPropagation());
     }
     
     private String fileToString(String pathname) throws URISyntaxException, IOException {
diff --git a/idp-saml-impl/src/test/resources/net/shibboleth/idp/saml/impl/session/saml2SPSession.json b/idp-saml-impl/src/test/resources/net/shibboleth/idp/saml/impl/session/saml2SPSession.json
index 47284787e..ac2ebee03 100644
--- a/idp-saml-impl/src/test/resources/net/shibboleth/idp/saml/impl/session/saml2SPSession.json
+++ b/idp-saml-impl/src/test/resources/net/shibboleth/idp/saml/impl/session/saml2SPSession.json
@@ -1 +1 @@
-{"id":"test","ts":1378827849463,"nam":"<saml2:NameID xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">joe at example.org</saml2:NameID>","ix":"1234567890"}
\ No newline at end of file
+{"id":"test","ts":1378827849463,"nam":"<saml2:NameID xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">joe at example.org</saml2:NameID>","ix":"1234567890","slo":false}
\ No newline at end of file
diff --git a/idp-session-api/src/main/java/net/shibboleth/idp/session/LogoutPropagationFlowDescriptor.java b/idp-session-api/src/main/java/net/shibboleth/idp/session/LogoutPropagationFlowDescriptor.java
index c435b2f61..d5db0c615 100644
--- a/idp-session-api/src/main/java/net/shibboleth/idp/session/LogoutPropagationFlowDescriptor.java
+++ b/idp-session-api/src/main/java/net/shibboleth/idp/session/LogoutPropagationFlowDescriptor.java
@@ -56,6 +56,26 @@ public class LogoutPropagationFlowDescriptor extends AbstractIdentifiableInitial
     @Nonnull public Class<? extends SPSession> getSessionType() {
         return sessionType;
     }
+    
+    /**
+     * Test an input session to determine if this flow supports it.
+     * 
+     * @param session input session
+     * 
+     * @return true iff this flow should be used to propagate a logout to the corresponding SP
+     */
+    @SuppressWarnings("removal")
+    public boolean isSupported(@Nonnull final SPSession session) {
+        if (sessionType.isInstance(session)) {
+            if (session instanceof SPSessionEx) {
+                return ((SPSessionEx) session).supportsLogoutPropagation();
+            }
+            
+            return true;
+        }
+        
+        return false;
+    }
 
     /** {@inheritDoc} */
     @Override
diff --git a/idp-session-api/src/main/java/net/shibboleth/idp/session/SPSessionEx.java b/idp-session-api/src/main/java/net/shibboleth/idp/session/SPSessionEx.java
index 31dd3ef88..86c436620 100644
--- a/idp-session-api/src/main/java/net/shibboleth/idp/session/SPSessionEx.java
+++ b/idp-session-api/src/main/java/net/shibboleth/idp/session/SPSessionEx.java
@@ -38,4 +38,16 @@ public interface SPSessionEx extends SPSession {
      * @return a protocol constant
      */
     @Nullable @NotEmpty String getProtocol();
+    
+    /**
+     * Gets whether the session allows for logout propagation.
+     * 
+     * @return whether the session allows for logout propagation
+     * 
+     * @since 4.2.0
+     */
+    default boolean supportsLogoutPropagation() {
+        return false;
+    }
+    
 }
\ No newline at end of file
diff --git a/idp-session-api/src/main/java/net/shibboleth/idp/session/logic/LogoutPropagationFlowDescriptorSelector.java b/idp-session-api/src/main/java/net/shibboleth/idp/session/logic/LogoutPropagationFlowDescriptorSelector.java
index f87257540..9276f1637 100644
--- a/idp-session-api/src/main/java/net/shibboleth/idp/session/logic/LogoutPropagationFlowDescriptorSelector.java
+++ b/idp-session-api/src/main/java/net/shibboleth/idp/session/logic/LogoutPropagationFlowDescriptorSelector.java
@@ -49,8 +49,9 @@ public class LogoutPropagationFlowDescriptorSelector implements Function<SPSessi
 
     /** {@inheritDoc} */
     @Nullable public LogoutPropagationFlowDescriptor apply(@Nullable final SPSession input) {
+
         for (final LogoutPropagationFlowDescriptor flowDescriptor : availableFlows) {
-            if (flowDescriptor.getSessionType().isInstance(input)) {
+            if (flowDescriptor.isSupported(input)) {
                 return flowDescriptor;
             }
         }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list