[java-plugin-shibd-saml] branch main updated: Add asynch detection and preliminary NotOnOrAfter support.

Codeberg noreply at shibboleth.net
Tue May 26 13:36:38 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-plugin-shibd-saml.

View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-saml/commit/8f38f73d686a948b180a40446f4708ced951803e

The following commit(s) were added to refs/heads/main by this push:
     new 8f38f73  Add asynch detection and preliminary NotOnOrAfter support.
8f38f73 is described below

commit 8f38f73d686a948b180a40446f4708ced951803e
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue May 26 09:36:18 2026 -0400

    Add asynch detection and preliminary NotOnOrAfter support.
---
 .../saml2/profile/impl/ProcessLogoutRequest.java   | 36 ++++++++++++++-
 .../profile/impl/ProcessLogoutRequestTest.java     | 52 ++++++++++++++++++++++
 2 files changed, 86 insertions(+), 2 deletions(-)

diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequest.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequest.java
index 58fef83..b18eff6 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequest.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequest.java
@@ -16,6 +16,7 @@ package net.shibboleth.sp.saml.saml2.profile.impl;
 
 import java.io.IOException;
 import java.io.StringReader;
+import java.time.Instant;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
@@ -31,6 +32,8 @@ import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.saml.common.binding.SAMLBindingSupport;
+import org.opensaml.saml.ext.saml2aslo.Asynchronous;
+import org.opensaml.saml.saml2.core.Extensions;
 import org.opensaml.saml.saml2.core.Issuer;
 import org.opensaml.saml.saml2.core.LogoutRequest;
 import org.opensaml.saml.saml2.core.NameID;
@@ -230,6 +233,19 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
             return;
         }
         
+        // TODO: in principle we're supposed to store the logout message for as long as it lasts
+        // and reject assertions that match for that period of time, so we would need storage
+        // for that.
+        
+        // Check expiration.
+        final Instant expires = logoutRequest.getNotOnOrAfter();
+        if (expires != null && !expires.isAfter(Instant.now())) {
+            log.info("{} Logout request has already expired", getLogPrefix());
+            addToken(profileRequestContext, issuer, false);
+            return;
+        }
+        
+        // Unpack our session data for comparison.
         final DDF input = agentRequestContext.getInput();
         final String pickled = input != null ?
                 input.getmember(ConsumerConstants.SESSION_OPAQUE).getmember(PrepareAgentResponse.NAMEID_PARAM).string()
@@ -239,7 +255,7 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
             addToken(profileRequestContext, issuer, false);
             return;
         }
-                
+        
         // Decode NameID from session data from Agent and unpack the buried information.
         NameID sessionNameID = null;
         try {
@@ -332,6 +348,16 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
         return false;
     }
     
+    /**
+     * Gets whether the request contains the {@link Asynchronous} extension.
+     * 
+     * @return true iff the request contains the extension
+     */
+    private boolean isAsynchronous() {
+        final Extensions exts = logoutRequest.getExtensions();
+        return exts != null && !exts.getUnknownXMLObjects(Asynchronous.DEFAULT_ELEMENT_NAME).isEmpty();
+    }
+    
     /**
      * Generate token for Agent based on logout request and add to output.
      * 
@@ -346,11 +372,17 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
         
         output.addmember(MATCHED_PARAM).integer(matched ? 1 : 0);
         
+        if (isAsynchronous()) {
+            log.debug("{} Logout request contained Asynchronous extension, skipping token generation for Agent",
+                    getLogPrefix());
+            return;
+        }
+        
         final SAMLStateData state = new SAMLStateData();
         
         state.setRequestID(logoutRequest.getID());
         state.setAuthenticationAuthority(issuer != null ? issuer.getValue() : null);
-        // We have no need for this field's usual purpose so this is a simple way to store off RelayState.
+        // We have no need for this field's usual purpose so this is a simple way to save RelayState from the IdP.
         state.setResource(SAMLBindingSupport.getRelayState(profileRequestContext.ensureInboundMessageContext()));
         
         try {
diff --git a/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequestTest.java b/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequestTest.java
index be836dc..3c124ba 100644
--- a/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequestTest.java
+++ b/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequestTest.java
@@ -16,6 +16,7 @@ package net.shibboleth.sp.saml.saml2.profile.impl;
 
 
 import java.io.IOException;
+import java.time.Instant;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -24,9 +25,12 @@ import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.saml.common.SAMLObjectBuilder;
 import org.opensaml.saml.common.binding.SAMLBindingSupport;
+import org.opensaml.saml.ext.saml2aslo.Asynchronous;
+import org.opensaml.saml.saml2.core.Extensions;
 import org.opensaml.saml.saml2.core.Issuer;
 import org.opensaml.saml.saml2.core.LogoutRequest;
 import org.opensaml.saml.saml2.core.LogoutResponse;
+import org.opensaml.saml.saml2.core.RequestAbstractType;
 import org.opensaml.saml.saml2.core.SessionIndex;
 import org.opensaml.saml.saml2.testing.SAML2ActionTestingSupport;
 import org.springframework.core.io.ClassPathResource;
@@ -182,6 +186,25 @@ public class ProcessLogoutRequestTest extends BaseApplicationActionTest {
         validateToken(output.getmember(ProcessLogoutRequest.TOKEN_PARAM).string());
     }
     
+    @Test
+    public void testExpired() throws ComponentInitializationException, IOException {
+        ((LogoutRequest) prc.ensureInboundMessageContext().ensureMessage()).setNotOnOrAfter(Instant.now().minusSeconds(300));
+
+        final DDF input = new DDF(null).structure();
+        input.addmember(ConsumerConstants.SESSION_OPAQUE).addmember(PrepareAgentResponse.NAMEID_PARAM).string(
+                "<NameID xmlns='urn:oasis:names:tc:SAML:2.0:assertion' SPProvidedID='"
+                        + ActionTestingSupport.INBOUND_MSG_ISSUER + "'>jdoe</NameID>");
+        arc.setInput(input);
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        final DDF output = arc.getOutput();
+        assert output != null;
+        Assert.assertEquals(output.getmember(ProcessLogoutRequest.MATCHED_PARAM).integer(), 0);
+        validateToken(output.getmember(ProcessLogoutRequest.TOKEN_PARAM).string());
+    }
+    
     @Test
     public void testNoSessionData() throws ComponentInitializationException, IOException {
         final Event event = action.execute(src);
@@ -307,6 +330,35 @@ public class ProcessLogoutRequestTest extends BaseApplicationActionTest {
         Assert.assertEquals(output.getmember(ProcessLogoutRequest.MATCHED_PARAM).integer(), 1);
         validateToken(output.getmember(ProcessLogoutRequest.TOKEN_PARAM).string());
     }
+    
+    @Test
+    public void testMatchAsynch() throws ComponentInitializationException, IOException {
+        final DDF input = new DDF(null).structure();
+        input.addmember(ConsumerConstants.SESSION_OPAQUE).addmember(PrepareAgentResponse.NAMEID_PARAM).string(
+                "<NameID xmlns='urn:oasis:names:tc:SAML:2.0:assertion' SPProvidedID='"
+                        + ActionTestingSupport.INBOUND_MSG_ISSUER + "'>jdoe</NameID>");
+        arc.setInput(input);
+
+        final SAMLObjectBuilder<Extensions> extsBuilder = (SAMLObjectBuilder<Extensions>)
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<Extensions>ensureBuilder(
+                        Extensions.DEFAULT_ELEMENT_NAME);
+        final SAMLObjectBuilder<Asynchronous> asyncBuilder = (SAMLObjectBuilder<Asynchronous>)
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<Asynchronous>ensureBuilder(
+                        Asynchronous.DEFAULT_ELEMENT_NAME);
+        
+        final Extensions exts = extsBuilder.buildObject();
+        exts.getUnknownXMLObjects().add(asyncBuilder.buildObject());
+        ((RequestAbstractType) prc.ensureInboundMessageContext().ensureMessage()).setExtensions(exts);
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        final DDF output = arc.getOutput();
+        assert output != null;
+        Assert.assertEquals(output.getmember(ProcessLogoutRequest.MATCHED_PARAM).integer(), 1);
+        Assert.assertTrue(output.getmember(ProcessLogoutRequest.TOKEN_PARAM).isnull());
+    }
+    
 
     /**
      * Adds mock request content to the inbound {@link MessageContext).

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


More information about the commits mailing list