[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