[java-plugin-shibd-saml] branch main updated: Capture SP identity into token in front-half of logout-consumer flow.

Codeberg noreply at shibboleth.net
Tue Jun 2 21:06:53 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/c978a8fbdd6c0bd57e5f3bfa29251ffd9e003552

The following commit(s) were added to refs/heads/main by this push:
     new c978a8f  Capture SP identity into token in front-half of logout-consumer flow.
c978a8f is described below

commit c978a8fbdd6c0bd57e5f3bfa29251ffd9e003552
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue Jun 2 12:31:24 2026 -0400

    Capture SP identity into token in front-half of logout-consumer flow.
---
 .../flows/saml2/SAML2LogoutConsumerFlowTest.java   |  5 ++-
 .../saml2/SAML2LogoutConsumerTokenFlowTest.java    |  1 +
 .../saml/saml2/profile/impl/AddLogoutResponse.java | 44 +++-------------------
 .../saml2/profile/impl/ProcessLogoutRequest.java   | 29 ++++++++------
 .../profile/impl/ProcessLogoutTokenRequest.java    | 10 +++--
 .../profile/impl/ProcessLogoutRequestTest.java     |  3 +-
 .../profile/impl/ProcessLogoutRespoonseTest.java   |  2 -
 7 files changed, 36 insertions(+), 58 deletions(-)

diff --git a/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerFlowTest.java b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerFlowTest.java
index df929a0..e1e07e6 100644
--- a/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerFlowTest.java
+++ b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerFlowTest.java
@@ -125,6 +125,9 @@ public class SAML2LogoutConsumerFlowTest extends AbstractSPFlowTest {
     /** Flow ID. */
     @Nonnull @NotEmpty public static final String FLOW_ID = "sp/logout-consumer";
 
+    /** Issuer. */
+    @Nonnull @NotEmpty public static final String SP_ENTITY_ID = "https://testsp.example.org";
+        
     /** Issuer. */
     @Nonnull @NotEmpty public static final String ISSUER = "https://idp.example.org";
 
@@ -361,7 +364,6 @@ public class SAML2LogoutConsumerFlowTest extends AbstractSPFlowTest {
         validateError(EventIds.INVALID_MESSAGE);
     }
     
-    
     /**
      * Test LogoutResponse flow with a successful status.
      * 
@@ -446,6 +448,7 @@ public class SAML2LogoutConsumerFlowTest extends AbstractSPFlowTest {
         final Issuer issuer = request.getIssuer();
         assert issuer != null;
         Assert.assertEquals(stateData.getAuthenticationAuthority(), issuer.getValue());
+        Assert.assertEquals(stateData.getIssuer(), SP_ENTITY_ID);
         Assert.assertEquals(stateData.getRequestID(), request.getID());
         Assert.assertEquals(stateData.getResource(), SAMLBindingSupport.getRelayState(prc.ensureInboundMessageContext()));
         
diff --git a/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerTokenFlowTest.java b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerTokenFlowTest.java
index afe5557..922877c 100644
--- a/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerTokenFlowTest.java
+++ b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerTokenFlowTest.java
@@ -406,6 +406,7 @@ public class SAML2LogoutConsumerTokenFlowTest extends AbstractSPFlowTest {
         
         final SAMLStateData data = new SAMLStateData();
         data.setAuthenticationAuthority(entityID);
+        data.setIssuer(SP_ENTITY_ID);
         data.setRequestID(requestID);
         data.setResource(relayState);
         
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddLogoutResponse.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddLogoutResponse.java
index 237b112..591e9bf 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddLogoutResponse.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddLogoutResponse.java
@@ -18,12 +18,10 @@ import java.time.Instant;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.profile.config.navigate.IdentifierGenerationStrategyLookupFunction;
 import net.shibboleth.profile.context.RelyingPartyContext;
-import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.security.IdentifierGenerationStrategy;
@@ -31,17 +29,11 @@ import net.shibboleth.sp.context.StateDataContext;
 import net.shibboleth.sp.ddf.DDF;
 import net.shibboleth.sp.profile.AbstractApplicationAction;
 import net.shibboleth.sp.profile.ConsumerConstants;
-import net.shibboleth.sp.profile.SPConstants;
 import net.shibboleth.sp.saml.saml2.SAMLStateData;
-import net.shibboleth.sp.saml.saml2.context.SAMLLogoutContext;
 import net.shibboleth.sp.saml.saml2.profile.config.SingleLogoutProfileConfiguration;
-import net.shibboleth.sp.state.StateData;
 
 import org.opensaml.core.xml.XMLObjectBuilderFactory;
 import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
-import org.opensaml.core.xml.io.MarshallingException;
-import org.opensaml.core.xml.io.UnmarshallingException;
-import org.opensaml.core.xml.util.XMLObjectSupport;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
@@ -50,13 +42,8 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.saml.common.SAMLObjectBuilder;
 import org.opensaml.saml.common.SAMLVersion;
 import org.opensaml.saml.common.binding.SAMLBindingSupport;
-import org.opensaml.saml.ext.saml2aslo.Asynchronous;
-import org.opensaml.saml.saml2.core.LogoutRequest;
 import org.opensaml.saml.saml2.core.LogoutResponse;
-import org.opensaml.saml.saml2.core.Extensions;
 import org.opensaml.saml.saml2.core.Issuer;
-import org.opensaml.saml.saml2.core.NameID;
-import org.opensaml.saml.saml2.core.SessionIndex;
 import org.opensaml.saml.saml2.core.Status;
 import org.opensaml.saml.saml2.core.StatusCode;
 import org.slf4j.Logger;
@@ -94,9 +81,6 @@ public class AddLogoutResponse extends AbstractApplicationAction {
     /** Strategy used to locate the {@link StateDataContext} to read from. */
     @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextLookupStrategy;
     
-    /** Strategy used to obtain the request issuer value. */
-    @Nullable private Function<ProfileRequestContext,String> issuerLookupStrategy;
-    
     /** The generator to use. */
     @NonnullBeforeExec private IdentifierGenerationStrategy idGenerator;
     
@@ -106,15 +90,11 @@ public class AddLogoutResponse extends AbstractApplicationAction {
     /** Cached state data object to read from. */
     @NonnullBeforeExec private SAMLStateData stateData;
     
-    /** EntityID to populate into Issuer element. */
-    @Nullable private String issuerId;
-    
     /** Constructor. */
     public AddLogoutResponse() {
         // Default strategy is a 16-byte secure random source.
         idGeneratorLookupStrategy = new IdentifierGenerationStrategyLookupFunction();
         stateDataContextLookupStrategy = new ChildContextLookup<>(StateDataContext.class);
-        issuerLookupStrategy = new IssuerLookupFunction();
     }
         
     /**
@@ -150,16 +130,6 @@ public class AddLogoutResponse extends AbstractApplicationAction {
         stateDataContextLookupStrategy =
                 Constraint.isNotNull(strategy, "StateDataContext lookup strategy cannot be null");
     }
-    
-    /**
-     * Set the strategy used to locate the issuer value to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setIssuerLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
-        checkSetterPreconditions();
-        issuerLookupStrategy = strategy;
-    }
 
     /** {@inheritDoc} */
     @Override
@@ -205,10 +175,6 @@ public class AddLogoutResponse extends AbstractApplicationAction {
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
-        
-        if (issuerLookupStrategy != null) {
-            issuerId = issuerLookupStrategy.apply(profileRequestContext);
-        }
 
         outboundMessageCtx.setMessage(null);
         
@@ -237,15 +203,15 @@ public class AddLogoutResponse extends AbstractApplicationAction {
         object.setVersion(SAMLVersion.VERSION_20);
         object.setInResponseTo(stateData.getRequestID());
         
-        if (issuerId != null) {
-            log.debug("{} Setting Issuer to {}", getLogPrefix(), issuerId);
+        if (stateData.getIssuer() != null) {
+            log.debug("{} Setting Issuer to {}", getLogPrefix(), stateData.getIssuer());
             final SAMLObjectBuilder<Issuer> issuerBuilder =
                     (SAMLObjectBuilder<Issuer>) bf.<Issuer>ensureBuilder(Issuer.DEFAULT_ELEMENT_NAME);
             final Issuer issuer = issuerBuilder.buildObject();
-            issuer.setValue(issuerId);
+            issuer.setValue(stateData.getIssuer());
             object.setIssuer(issuer);
         } else {
-            log.debug("{} No issuer value available, leaving Issuer unset", getLogPrefix());
+            log.warn("{} No issuer value available, leaving Issuer unset", getLogPrefix());
         }
         
         final Status status = statusBuilder.buildObject();
@@ -269,7 +235,7 @@ public class AddLogoutResponse extends AbstractApplicationAction {
         SAMLBindingSupport.setRelayState(profileRequestContext.ensureOutboundMessageContext(), stateData.getResource());
         
         log.info("{} Generated LogoutResponse with ID {} from {} in response to LogoutRequest with ID {}",
-                getLogPrefix(), object.getID(), issuerId, stateData.getRequestID());
+                getLogPrefix(), object.getID(), stateData.getIssuer(), stateData.getRequestID());
     }
     
 }
\ No newline at end of file
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 d0e5070..a91f4b5 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
@@ -14,8 +14,6 @@
 
 package net.shibboleth.sp.saml.saml2.profile.impl;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
 import java.io.IOException;
 import java.time.Instant;
 import java.util.Collection;
@@ -26,9 +24,7 @@ import java.util.function.Function;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import org.opensaml.core.xml.XMLObject;
 import org.opensaml.core.xml.io.UnmarshallingException;
-import org.opensaml.core.xml.util.XMLObjectSupport;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -59,7 +55,6 @@ import net.shibboleth.saml.saml2.profile.config.navigate.QualifiedNameIDFormatsL
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.DecodingException;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
@@ -105,10 +100,13 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
     
     /** Logout request message. */
     @NonnullBeforeExec private LogoutRequest logoutRequest;
-    
+        
     /** Output message. */
     @NonnullBeforeExec private DDF output;
     
+    /** Local identity of Agent saved for use. */
+    @Nullable private String localIssuer;
+    
     /** Constructor. */
     public ProcessLogoutRequest() {
         qualifiedNameIDFormatsLookupStrategy = new QualifiedNameIDFormatsLookupFunction();
@@ -117,8 +115,11 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
         
         // Note these are reversed from the IdP as we are the SP here, so the
         // NameQualifier would be the request issuer and the SPNameQualifier is us.
+        // The latter is also cached off as the eventual "issuer" of the
+        // {@link LogoutResponse} if any, to capture into the token sent back.
         assertingPartyLookupStrategy = new RelyingPartyIdLookupFunction();
         relyingPartyLookupStrategy = new IssuerLookupFunction();
+        
     }
     
     /**
@@ -210,7 +211,9 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
             return false;
         }
         
+        localIssuer = relyingPartyLookupStrategy.apply(profileRequestContext);
         qualifiedNameIDFormats = new HashSet<>(qualifiedNameIDFormatsLookupStrategy.apply(profileRequestContext));
+        
         return true;
     }
 
@@ -316,8 +319,7 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
                 || qualifiedNameIDFormats.contains(sessionNameID.getFormat())) {
             
             if (!SAML2ObjectSupport.areNameIDsEquivalent(sessionNameID, requestedNameID,
-                    assertingPartyLookupStrategy.apply(profileRequestContext),
-                    relyingPartyLookupStrategy.apply(profileRequestContext))) {
+                    assertingPartyLookupStrategy.apply(profileRequestContext), localIssuer)) {
                 log.info("{} LogoutRequest's NameID did not strongly match session", getLogPrefix());
                 return false;
             }
@@ -360,10 +362,10 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
      * <p>The token tracks the request ID and the issuer.</p>
      * 
      * @param profileRequestContext profile request context
-     * @param issuer issuer of request
+     * @param authority issuer of request
      * @param matched matched signal
      */
-    private void addToken(@Nonnull final ProfileRequestContext profileRequestContext, @Nullable final Issuer issuer,
+    private void addToken(@Nonnull final ProfileRequestContext profileRequestContext, @Nullable final Issuer authority,
             final boolean matched) {
         
         output.addmember(ConsumerConstants.MATCHED_PARAM).integer(matched ? 1 : 0);
@@ -377,7 +379,12 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
         final SAMLStateData state = new SAMLStateData();
         
         state.setRequestID(logoutRequest.getID());
-        state.setAuthenticationAuthority(issuer != null ? issuer.getValue() : null);
+        state.setAuthenticationAuthority(authority != null ? authority.getValue() : null);
+        
+        // This is stored so as to allow the value to depend on the properties of the original HTTP request
+        // to the Agent being processed here so that it is unnecessary later on.
+        state.setIssuer(localIssuer);
+        
         // 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()));
         
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutTokenRequest.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutTokenRequest.java
index a8a91ff..d4f90f5 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutTokenRequest.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutTokenRequest.java
@@ -144,9 +144,9 @@ public class ProcessLogoutTokenRequest extends AbstractApplicationAction {
         }
         
         final String token = input.getmember(ConsumerConstants.TOKEN_PARAM).string();
-        if (token == null || !token.startsWith(ProcessLogoutRequest.TOKEN_PREFIX)) {
+        if (token == null) {
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
-            log.error("{} '{}' parameter missing or invalid", getLogPrefix(), ConsumerConstants.TOKEN_PARAM);
+            log.error("{} '{}' parameter missing", getLogPrefix(), ConsumerConstants.TOKEN_PARAM);
             return false;
         }
         
@@ -154,8 +154,12 @@ public class ProcessLogoutTokenRequest extends AbstractApplicationAction {
             stateData =
                     stateManager.recoverFromStateToken(ensureAgent(), ensureApplication(), token, SAMLStateData.class);
         } catch (final IOException e) {
-            ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_DECODE);
             log.error("{} Error decoding '{}' parameter", getLogPrefix(), ConsumerConstants.TOKEN_PARAM, e);
+        }
+
+        if (stateData == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_DECODE);
+            log.error("{} Unable to decode '{}' parameter", getLogPrefix(), ConsumerConstants.TOKEN_PARAM);
             return false;
         }
         
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 43b2c1a..13506d9 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
@@ -47,7 +47,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
-import net.shibboleth.profile.context.RelyingPartyContext;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.resource.Resource;
 import net.shibboleth.shared.security.DataSealer;
@@ -130,7 +129,6 @@ public class ProcessLogoutRequestTest extends BaseApplicationActionTest {
     @BeforeMethod
     public void setUp() throws ComponentInitializationException {
         super.beforeMethod();
-        prc.removeSubcontext(RelyingPartyContext.class);
         
         action = new ProcessLogoutRequest();
         action.setParserPool(parserPool);
@@ -408,6 +406,7 @@ public class ProcessLogoutRequestTest extends BaseApplicationActionTest {
         final Issuer issuer = request.getIssuer();
         assert issuer != null;
         Assert.assertEquals(data.getAuthenticationAuthority(), issuer.getValue());
+        Assert.assertEquals(data.getIssuer(), ActionTestingSupport.OUTBOUND_MSG_ISSUER);
         Assert.assertEquals(data.getRequestID(), request.getID());
         Assert.assertEquals(data.getResource(), RELAY_STATE);
     }
diff --git a/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRespoonseTest.java b/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRespoonseTest.java
index 1e86f40..31506fc 100644
--- a/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRespoonseTest.java
+++ b/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRespoonseTest.java
@@ -37,7 +37,6 @@ import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
-import net.shibboleth.profile.context.RelyingPartyContext;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.sp.context.StateDataContext;
@@ -66,7 +65,6 @@ public class ProcessLogoutRespoonseTest extends BaseApplicationActionTest {
     @BeforeMethod
     public void setUp() throws ComponentInitializationException {
         super.beforeMethod();
-        prc.removeSubcontext(RelyingPartyContext.class);
         
         action = new ProcessLogoutResponse();
         action.initialize();

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


More information about the commits mailing list