[java-plugin-shibd-oidc] branch dev/JSHIBDOIDC-28 updated: WIP: RP-initiated logout
Codeberg
noreply at shibboleth.net
Fri Jul 10 08:41:58 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/JSHIBDOIDC-28
in repository java-plugin-shibd-oidc.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/a64e7c1aceb73fc9ac3d03cb264b0e5c7cf6bb73
The following commit(s) were added to refs/heads/dev/JSHIBDOIDC-28 by this push:
new a64e7c1 WIP: RP-initiated logout
a64e7c1 is described below
commit a64e7c1aceb73fc9ac3d03cb264b0e5c7cf6bb73
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jul 10 09:41:46 2026 +0100
WIP: RP-initiated logout
- finished the general flow and agent response
---
sp-oidc-api/pom.xml | 23 +++-
.../navigate/PostLogoutRedirectLookupStrategy.java | 93 +++++++++++++++++
.../logic/IDTokenHintActivationCondition.java | 64 ++++++++++++
.../logic/LogoutHintActivationCondition.java | 67 ++++++++++++
.../PostLogoutRedirectUriActivationCondition.java | 64 ++++++++++++
...tAgentAndRelyingPartyContextLookupFunction.java | 31 +++++-
.../navigate/IDTokenHintLookupStrategy.java | 11 +-
.../context/navigate/LogoutHintLookupStrategy.java | 109 +++++++++++++++++++
.../logic/AbstractLogoutLookupStrategyTest.java | 88 ++++++++++++++++
.../logic/LogoutHintActivationConditionTest.java | 71 +++++++++++++
.../PostLogoutRedirectLookupStrategyTest.java | 89 ++++++++++++++++
.../navigate/IDTokenHintLookupStrategyTest.java | 89 ++++++++++++++++
.../navigate/LogoutHintLookupStrategyTest.java | 116 +++++++++++++++++++++
.../net/shibboleth/sp/oidc/testing/TestHelper.java | 19 ++++
.../flows/sp/logout/initiator/oidc/oidc-beans.xml | 38 +++++--
.../net/shibboleth/sp/service/agent/postconfig.xml | 17 +--
.../shibboleth/idp/module/conf/sp/oidc.properties | 9 ++
.../sp/oidc/flows/OIDCLogoutInitiatorFlowTest.java | 22 +++-
18 files changed, 982 insertions(+), 38 deletions(-)
diff --git a/sp-oidc-api/pom.xml b/sp-oidc-api/pom.xml
index 7e6db4c..a5833ab 100644
--- a/sp-oidc-api/pom.xml
+++ b/sp-oidc-api/pom.xml
@@ -80,8 +80,26 @@ copyright/license information. -->
<artifactId>oidc-common-metadata-api</artifactId>
<scope>provided</scope>
</dependency>
+
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
<!-- Test Dependencies -->
+
+ <dependency>
+ <groupId>${shibd.groupId}</groupId>
+ <artifactId>sp-server-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-session-api</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.mockito</groupId>
@@ -98,11 +116,6 @@ copyright/license information. -->
<artifactId>idp-testing</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>jakarta.servlet</groupId>
- <artifactId>jakarta.servlet-api</artifactId>
- <scope>test</scope>
- </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/config/navigate/PostLogoutRedirectLookupStrategy.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/config/navigate/PostLogoutRedirectLookupStrategy.java
new file mode 100644
index 0000000..071b0e6
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/config/navigate/PostLogoutRedirectLookupStrategy.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.config.navigate;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.CodingErrorAction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
+import net.shibboleth.sp.oidc.profile.context.navigate.AbstractAgentAndRelyingPartyContextLookupFunction;
+import net.shibboleth.sp.profile.SPConstants;
+
+/**
+ * Retrieve the 'post_logout_redirect_uri' parameter from the {@link SPConstants#TARGET} in the {@link DDF} if present.
+ * A {@code null} is returned on failure to locate or convert the {@link SPConstants#TARGET}.
+ */
+public class PostLogoutRedirectLookupStrategy extends AbstractAgentAndRelyingPartyContextLookupFunction<URI> {
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PostLogoutRedirectLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public URI apply(final MessageContext messageCtx) {
+
+ final DDF input = getDDF(messageCtx, DDFDirection.INPUT);
+ if (input != null) {
+
+ final Agent agent = getAgent(messageCtx);
+ if (agent == null) {
+ return null;
+ }
+
+ final byte[] target = input.getmember(SPConstants.TARGET).unsafe_string();
+ if (target == null) {
+ // Do not report this, if it is not in the DDF we just ignore it
+ return null;
+ }
+
+ final var decoder = agent.getCharacterEncoding().newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT);
+
+ // Try and decode the data as best we can.
+ final String decodedTarget = RemotedHttpServletRequest.decodeUnsafeString(target, decoder, null);
+ if (decodedTarget == null) {
+ log.warn("Failure decoding target resource byte array using encoding: {}", decoder.charset().name());
+ return null;
+ }
+
+ return convertToUri(decodedTarget);
+
+ }
+ return null;
+ }
+
+ /**
+ * Convert the string URI to a {@link URI}, returning null on failure.
+ *
+ * @param uri the URI to convert
+ * @return the converted URI or null.
+ */
+ @Nullable private URI convertToUri(@Nonnull final String uri) {
+ try {
+ return new URI(uri);
+ } catch (final URISyntaxException e) {
+ log.warn("Failed to convert target '{}' to URI", uri, e);
+ return null;
+ }
+ }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/logic/IDTokenHintActivationCondition.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/logic/IDTokenHintActivationCondition.java
new file mode 100644
index 0000000..87d6baa
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/logic/IDTokenHintActivationCondition.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.context.logic;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.profile.config.OIDCLogoutProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.logic.messaging.AbstractRelyingPartyPredicate;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Determine if the id_token_hint parameter should be used based on the relying party configuration setting.
+ */
+public class IDTokenHintActivationCondition extends AbstractRelyingPartyPredicate {
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(IDTokenHintActivationCondition.class);
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(final MessageContext mc) {
+
+ final RelyingPartyContext rpc = getRelyingPartyContext(mc);
+
+ final ParentProfileRequestContextLookup<MessageContext> lookup = new ParentProfileRequestContextLookup<>();
+ final ProfileRequestContext prc = lookup.apply(mc);
+
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof final OIDCLogoutProfileConfiguration config) {
+ final boolean idTokenHintRequired = config.isRequireIdTokenHint(prc);
+ if (idTokenHintRequired) {
+ log.trace("The id_token_hint parameter is enabled");
+ return true;
+ } else {
+ log.trace("The id_token_hint parameter is not enabled");
+ return false;
+ }
+ }
+ }
+ log.trace("Unable to determine if the id_token_hint parameter is required and will be disabled");
+ return false;
+ }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/logic/LogoutHintActivationCondition.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/logic/LogoutHintActivationCondition.java
new file mode 100644
index 0000000..5f60ebe
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/logic/LogoutHintActivationCondition.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.context.logic;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.profile.core.OIDCLogoutRequest;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Activation condition which returns true only if the id_token_hint parameter has not been set.
+ */
+public class LogoutHintActivationCondition implements Predicate<MessageContext> {
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(LogoutHintActivationCondition.class);
+
+ /** Strategy used to locate the {@link OIDCLogoutRequest}. */
+ @NonnullAfterInit private final Function<MessageContext, OIDCLogoutRequest> logoutRequestLookupStrategy;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param strategy the logout request lookup strategy
+ */
+ public LogoutHintActivationCondition(@Nonnull @ParameterName(name="logoutRequestLookupStrategy") final
+ Function<MessageContext, OIDCLogoutRequest> strategy) {
+ logoutRequestLookupStrategy = Constraint.isNotNull(strategy, "LogoutRequestLookupStrategy can not be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(final MessageContext mc) {
+ final OIDCLogoutRequest logoutRequest = logoutRequestLookupStrategy.apply(mc);
+ if (logoutRequest == null || logoutRequest.getIdTokenHint() != null) {
+ // Can't be set if there is no in progress logout request.
+ log.trace("id_token_hint already set, logout_hint is not enabled");
+ return false;
+ }
+ // If there is no id_token_hint, we can try to set the logout_hint
+ log.trace("id_token_hint not set, logout_hint is enabled");
+ return true;
+ }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/logic/PostLogoutRedirectUriActivationCondition.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/logic/PostLogoutRedirectUriActivationCondition.java
new file mode 100644
index 0000000..1d6cc0d
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/logic/PostLogoutRedirectUriActivationCondition.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.context.logic;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.profile.config.OIDCLogoutProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.logic.messaging.AbstractRelyingPartyPredicate;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Determine if the post_logout_redirect_uri parameter should be set based on relying party configuration settings.
+ */
+public class PostLogoutRedirectUriActivationCondition extends AbstractRelyingPartyPredicate {
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PostLogoutRedirectUriActivationCondition.class);
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(final MessageContext mc) {
+
+ final RelyingPartyContext rpc = getRelyingPartyContext(mc);
+
+ final ParentProfileRequestContextLookup<MessageContext> lookup = new ParentProfileRequestContextLookup<>();
+ final ProfileRequestContext prc = lookup.apply(mc);
+
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof final OIDCLogoutProfileConfiguration config) {
+ final boolean postLogoutRedirectEnabled = config.isIncludePostLogoutRedirectUri(prc);
+ if (postLogoutRedirectEnabled) {
+ log.trace("The post_logout_redirect_uri parameter is enabled");
+ return true;
+ } else {
+ log.trace("The post_logout_redirect_uri parameter is not enabled");
+ return false;
+ }
+ }
+ }
+ log.trace("Unable to determine if the post_logout_redirect_uri parameter is enabled and will be disabled");
+ return false;
+ }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/AbstractAgentAndRelyingPartyContextLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/AbstractAgentAndRelyingPartyContextLookupFunction.java
index 09c84c7..203f92b 100644
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/AbstractAgentAndRelyingPartyContextLookupFunction.java
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/AbstractAgentAndRelyingPartyContextLookupFunction.java
@@ -26,6 +26,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+import net.shibboleth.oidc.profile.config.OIDCLogoutProfileConfiguration;
import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.profile.context.navigate.messaging.AbstractRelyingPartyLookupFunction;
import net.shibboleth.shared.logic.Constraint;
@@ -97,7 +98,7 @@ public abstract class AbstractAgentAndRelyingPartyContextLookupFunction<ResultTy
*
* @param input message context
*
- * @return the agent
+ * @return the agent, or {@code null} if unavailable
*/
@Nullable protected Agent getAgent(@Nullable final MessageContext input) {
final AgentRequestContext arc = agentRequestContextLookupStrategy.apply(input);
@@ -112,7 +113,7 @@ public abstract class AbstractAgentAndRelyingPartyContextLookupFunction<ResultTy
*
* @param input the message context
*
- * @return the agent
+ * @return the application, or {@code null} if unavailable
*/
@Nullable protected Application getApplication(@Nullable final MessageContext input) {
final AgentRequestContext arc = agentRequestContextLookupStrategy.apply(input);
@@ -126,9 +127,9 @@ public abstract class AbstractAgentAndRelyingPartyContextLookupFunction<ResultTy
* Gets the {@link DDF} for this request, either the input from the agent or the output for the agent.
*
* @param input the message context input
- * @param direction either an input DDF, or output
+ * @param direction whether to return the input or output DDF
*
- * @return the input or output DDF
+ * @return the requested DDF, or {@code null} if unavailable
*/
@Nullable protected DDF getDDF(@Nullable final MessageContext input, @Nonnull final DDFDirection direction) {
final AgentRequestContext arc = agentRequestContextLookupStrategy.apply(input);
@@ -150,7 +151,7 @@ public abstract class AbstractAgentAndRelyingPartyContextLookupFunction<ResultTy
* @param feature the feature to check
* @param messageCtx the message context to extract the profile configuration from
*
- * @return true iff the feature is disallowed, false otherwise
+ * @return {@code true} if the feature is disallowed, {@code false} otherwise
*/
protected boolean isFeatureDisallowed(final int feature, @Nullable final MessageContext messageCtx) {
@@ -183,4 +184,24 @@ public abstract class AbstractAgentAndRelyingPartyContextLookupFunction<ResultTy
return null;
}
+
+ /**
+ * Extracts the {@link OIDCLogoutProfileConfiguration} from the given {@link MessageContext}.
+ *
+ * @param messageContext the current {@link MessageContext}, may be {@code null}
+ *
+ * @return the resolved {@link OIDCLogoutProfileConfiguration}, or {@code null} if
+ * unavailable
+ */
+ @Nullable protected OIDCLogoutProfileConfiguration
+ getOIDCLogoutProfileConfiguration(@Nullable final MessageContext messageContext) {
+
+ final RelyingPartyContext rpc = getRelyingPartyContext(messageContext);
+ if (rpc != null && rpc.getProfileConfig() instanceof final
+ OIDCLogoutProfileConfiguration config) {
+ return config;
+ }
+ return null;
+
+ }
}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategy.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategy.java
index 5bf0c56..d9c4e5f 100644
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategy.java
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategy.java
@@ -32,7 +32,7 @@ import net.shibboleth.sp.oidc.context.OIDCLogoutContext;
/**
* A lookup strategy that locates the ID Token, used as a hint in a logout request, from the {@link OIDCLogoutContext}.
*/
-public class IDTokenHintLookupStrategy implements Function<MessageContext, JWT> {
+public class IDTokenHintLookupStrategy extends AbstractAgentAndRelyingPartyContextLookupFunction<JWT> {
/** Lookup function for parent {@link ProfileRequestContext}. */
@Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
@@ -52,16 +52,17 @@ public class IDTokenHintLookupStrategy implements Function<MessageContext, JWT>
public IDTokenHintLookupStrategy(@ParameterName(name = "oidcLogoutContextLookupStrategy")
@Nonnull final Function<ProfileRequestContext, OIDCLogoutContext> strategy) {
logoutContextLookupStrategy = Constraint.isNotNull(strategy,
- "StateDataContextLookupStrategy can not be null");
+ "OIDC logout context lookup strategy can not be null");
}
@Override
- @Nullable public JWT apply(@Nullable final MessageContext prc) {
- if (prc == null) {
+ @Nullable public JWT apply(@Nullable final MessageContext messageContext) {
+ if (messageContext == null) {
return null;
}
- final OIDCLogoutContext logoutContext = logoutContextLookupStrategy.apply(PRC_LOOKUP.apply(prc));
+
+ final OIDCLogoutContext logoutContext = logoutContextLookupStrategy.apply(PRC_LOOKUP.apply(messageContext));
if (logoutContext != null && logoutContext.getIdToken() != null) {
return logoutContext.getIdToken();
}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/LogoutHintLookupStrategy.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/LogoutHintLookupStrategy.java
new file mode 100644
index 0000000..78f17e9
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/context/navigate/LogoutHintLookupStrategy.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.context.navigate;
+
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.profile.config.OIDCLogoutProfileConfiguration;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.oidc.context.OIDCLogoutContext;
+
+/**
+ * A strategy to set the logout_hint from a claim inside the ID Token. The claim to extract is found from the profile
+ * configuration. If the claim can not be found, {@code null} is returned.
+ */
+public class LogoutHintLookupStrategy extends AbstractAgentAndRelyingPartyContextLookupFunction<String> {
+
+ /** Lookup function for parent {@link ProfileRequestContext}. */
+ @Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
+ = new ParentProfileRequestContextLookup<>();
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(LogoutHintLookupStrategy.class);
+
+ /**
+ * Strategy used to locate the {@link OIDCLogoutContext}.
+ */
+ @Nonnull
+ private final Function<ProfileRequestContext, OIDCLogoutContext> logoutContextLookupStrategy;
+
+ /**
+ * Constructor.
+ *
+ * @param strategy strategy to find the logout context
+ */
+ public LogoutHintLookupStrategy(@ParameterName(name = "oidcLogoutContextLookupStrategy")
+ @Nonnull final Function<ProfileRequestContext, OIDCLogoutContext> strategy) {
+ logoutContextLookupStrategy = Constraint.isNotNull(strategy,
+ "oidcLogoutContextLookupStrategy can not be null");
+ }
+
+
+ @Override
+ @Nullable public String apply(@Nullable final MessageContext messageContext) {
+ if (messageContext == null) {
+ return null;
+ }
+ final OIDCLogoutProfileConfiguration rpConfig =
+ getOIDCLogoutProfileConfiguration(messageContext);
+ if (rpConfig == null) {
+ return null;
+ }
+ final String claimName = rpConfig.getLogoutHintClaim(PRC_LOOKUP.apply(messageContext));
+
+ if (claimName == null) {
+ // No claim to use, nothing to do
+ log.trace("No claim defined, can not set logout_hint");
+ return null;
+ }
+
+ final OIDCLogoutContext logoutContext = logoutContextLookupStrategy.apply(PRC_LOOKUP.apply(messageContext));
+ final JWT idToken = logoutContext != null ? logoutContext.getIdToken() : null;
+ if (idToken != null) {
+ try {
+ final JWTClaimsSet claims = idToken.getJWTClaimsSet();
+ if (claims != null) {
+ final String claim = claims.getStringClaim(claimName);
+ if (claim == null) {
+ log.trace("Unable to find claim '{}', can not set logout_hint", claimName);
+ }
+ return claim;
+ }
+ } catch (final ParseException e) {
+ log.trace("Unable to parse JWT for claim '{}', can not set logout_hint", claimName, e);
+ return null;
+ }
+ } else {
+ log.trace("Unable to find ID Token to extract claim '{}', can not set logout_hint", claimName);
+ }
+ return null;
+
+ }
+
+}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/logic/AbstractLogoutLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/logic/AbstractLogoutLookupStrategyTest.java
new file mode 100644
index 0000000..5b671cc
--- /dev/null
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/logic/AbstractLogoutLookupStrategyTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.context.logic;
+
+import javax.annotation.Nonnull;
+
+import org.mockito.Mockito;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.annotations.BeforeMethod;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.oidc.profile.config.impl.DefaultOIDCLogoutConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.context.AgentRequestContext;
+import net.shibboleth.sp.ddf.DDF;
+
+/**
+ * An abstract test class for lookup functions that work to build authentication requests.
+ */
+public abstract class AbstractLogoutLookupStrategyTest {
+
+ protected RequestContext rc;
+ protected MessageContext mc;
+ protected AuthenticationContext ac;
+ protected ProfileRequestContext prc;
+ protected RelyingPartyContext rpc;
+ protected AgentRequestContext arc;
+ protected RelyingPartyContext partyContext;
+ protected DefaultOIDCLogoutConfiguration rpConfig;
+ protected Agent agent;
+
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ rc = new RequestContextBuilder().buildRequestContext();
+ prc = new WebflowRequestContextProfileRequestContextLookup().apply(rc);
+ ac = prc.ensureSubcontext(AuthenticationContext.class);
+ mc = new MessageContext();
+ prc.setOutboundMessageContext(mc);
+ arc = new AgentRequestContext();
+ prc.addSubcontext(arc);
+
+ agent = Mockito.mock(Agent.class);
+ arc.setAgent(agent);
+
+ partyContext = prc.ensureSubcontext(RelyingPartyContext.class);
+ rpConfig = new DefaultOIDCLogoutConfiguration();
+ partyContext.setProfileConfig(rpConfig);
+ prc.addSubcontext(partyContext);
+ }
+
+ /**
+ * Return the relying party context.
+ *
+ * @return return the relying party configuration, or throw an exception if it is null
+ */
+ @Nonnull protected RelyingPartyContext getRelyingPartyContext(){
+ return Constraint.isNotNull(partyContext, "RelyingPartyContext is null");
+ }
+
+ /**
+ * Add the {@link DDF} to the agent request context.
+ *
+ * @param input the {@link DDF} to add.
+ */
+ protected void addDDFToAgentRequestContext(final DDF input) {
+ arc.setInput(input);
+ }
+
+}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/logic/LogoutHintActivationConditionTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/logic/LogoutHintActivationConditionTest.java
new file mode 100644
index 0000000..6ca6769
--- /dev/null
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/logic/LogoutHintActivationConditionTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.context.logic;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.oidc.profile.core.OIDCLogoutRequest;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Tests for {@link LogoutHintActivationCondition}.
+ */
+public class LogoutHintActivationConditionTest extends AbstractLogoutLookupStrategyTest {
+
+ /** Condition to test. */
+ private LogoutHintActivationCondition condition;
+
+ @Override
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ super.setUp();
+ }
+
+ @Test
+ public void testNullLogoutRequest() {
+ condition = new LogoutHintActivationCondition(mc -> null);
+
+ assertFalse(condition.test(mc));
+ }
+
+ @Test
+ public void testIdTokenHintAlreadyPresent() throws URISyntaxException {
+ final OIDCLogoutRequest logoutRequest = new OIDCLogoutRequest(new URI("https://op.example.org/end_session"));
+ logoutRequest.setIdTokenHint(Mockito.mock(JWT.class));
+
+ condition = new LogoutHintActivationCondition(mc -> logoutRequest);
+
+ assertFalse(condition.test(mc));
+ }
+
+ @Test
+ public void testIdTokenHintNotPresent() throws URISyntaxException {
+ final OIDCLogoutRequest logoutRequest = new OIDCLogoutRequest(new URI("https://op.example.org/end_session"));
+
+ condition = new LogoutHintActivationCondition(mc -> logoutRequest);
+
+ assertTrue(condition.test(mc));
+ }
+}
\ No newline at end of file
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/logic/PostLogoutRedirectLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/logic/PostLogoutRedirectLookupStrategyTest.java
new file mode 100644
index 0000000..561b28e
--- /dev/null
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/logic/PostLogoutRedirectLookupStrategyTest.java
@@ -0,0 +1,89 @@
+package net.shibboleth.sp.oidc.profile.context.logic;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.testng.Assert.assertNull;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.oidc.profile.config.navigate.PostLogoutRedirectLookupStrategy;
+import net.shibboleth.sp.profile.SPConstants;
+
+/**
+ * Tests for {@link PostLogoutRedirectLookupStrategy}.
+ */
+public class PostLogoutRedirectLookupStrategyTest extends AbstractLogoutLookupStrategyTest {
+
+ /** The strategy to test.*/
+ private PostLogoutRedirectLookupStrategy strategy;
+
+ /** The DDF to store input parameters.*/
+ private DDF ddf;
+
+ @Override
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ super.setUp();
+ strategy = new PostLogoutRedirectLookupStrategy();
+
+ when(agent.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8);
+
+ }
+
+ @Test
+ public void testNoDDF() {
+ final URI redirect = strategy.apply(mc);
+ assertNull(redirect);
+ }
+
+ @Test
+ public void testNoTarget() {
+ ddf = new DDF(null).structure();
+ addDDFToAgentRequestContext(ddf);
+ final URI redirect = strategy.apply(mc);
+ assertNull(redirect);
+ }
+
+ @Test
+ public void testWithTarget() {
+ ddf = new DDF(null).structure();
+ ddf.addmember(SPConstants.TARGET).unsafe_string("https://sp.example.org/".getBytes(StandardCharsets.UTF_8));
+ addDDFToAgentRequestContext(ddf);
+ final URI redirect = strategy.apply(mc);
+ assertNotNull(redirect);
+ assert redirect != null;
+ assertEquals(redirect.toString(), "https://sp.example.org/");
+ }
+
+ @Test
+ public void testWithMalformedTarget() {
+ ddf = new DDF(null).structure();
+ ddf.addmember(SPConstants.TARGET).unsafe_string("https sp.example.org/".getBytes(StandardCharsets.UTF_8));
+ addDDFToAgentRequestContext(ddf);
+ final URI redirect = strategy.apply(mc);
+ assertNull(redirect);
+ }
+
+}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategyTest.java
new file mode 100644
index 0000000..f3897e0
--- /dev/null
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/navigate/IDTokenHintLookupStrategyTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.context.navigate;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.nio.charset.StandardCharsets;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.oidc.context.OIDCLogoutContext;
+import net.shibboleth.sp.oidc.profile.context.logic.AbstractLogoutLookupStrategyTest;
+import net.shibboleth.sp.oidc.testing.TestHelper;
+
+/**
+ * Tests for {@link IDTokenHintLookupStrategy}.
+ */
+public class IDTokenHintLookupStrategyTest extends AbstractLogoutLookupStrategyTest {
+
+ /** The strategy to test.*/
+ private IDTokenHintLookupStrategy strategy;
+
+ /** Test token. */
+ private JWT idToken;
+
+
+ @Override
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ super.setUp();
+
+ final OIDCLogoutContext logoutContext = new OIDCLogoutContext();
+ idToken = TestHelper.createSignedIDTokenWithNoSignature("http://op.example.org", "jdoe");
+ logoutContext.setIdToken(idToken);
+ strategy = new IDTokenHintLookupStrategy(prc -> logoutContext);
+
+ when(agent.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8);
+
+ }
+
+ @Test
+ public void testNullMessageContext() {
+ assertNull(strategy.apply(null));
+ }
+
+ @Test
+ public void testReturnsIDToken() {
+ assertNotNull(strategy.apply(mc));
+ assertEquals(strategy.apply(mc), idToken);
+ }
+
+ @Test
+ public void testNoIDToken() {
+ final OIDCLogoutContext logoutContext = new OIDCLogoutContext();
+
+ strategy = new IDTokenHintLookupStrategy(prc -> logoutContext);
+
+ assertNull(strategy.apply(mc));
+ }
+
+ @Test
+ public void testNoLogoutContext() {
+ strategy = new IDTokenHintLookupStrategy(prc -> null);
+
+ assertNull(strategy.apply(mc));
+ }
+
+
+}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/navigate/LogoutHintLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/navigate/LogoutHintLookupStrategyTest.java
new file mode 100644
index 0000000..a6ecaa3
--- /dev/null
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/context/navigate/LogoutHintLookupStrategyTest.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.context.navigate;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.sp.oidc.context.OIDCLogoutContext;
+import net.shibboleth.sp.oidc.profile.context.logic.AbstractLogoutLookupStrategyTest;
+import net.shibboleth.sp.oidc.testing.TestHelper;
+
+/**
+ * Tests for {@link LogoutHintLookupStrategy}.
+ */
+public class LogoutHintLookupStrategyTest extends AbstractLogoutLookupStrategyTest {
+
+ /** Strategy under test. */
+ private LogoutHintLookupStrategy strategy;
+
+ /** Test token. */
+ private JWT idToken;
+
+ @Override
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ super.setUp();
+ idToken = TestHelper.createSignedIDTokenWithNoSignature("http://op.example.org", "jdoe");
+ }
+
+ @Test
+ public void testNullMessageContext() {
+ strategy = new LogoutHintLookupStrategy(prc -> null);
+
+ assertNull(strategy.apply(null));
+ }
+
+ @Test
+ public void testNoProfileConfiguration() {
+ strategy = new LogoutHintLookupStrategy(prc -> null);
+
+ assertNull(strategy.apply(mc));
+ }
+
+ @Test
+ public void testNoClaimConfigured() {
+ rpConfig.setLogoutHintClaim("sub");
+ strategy = new LogoutHintLookupStrategy(prc -> new OIDCLogoutContext());
+
+ assertNull(strategy.apply(mc));
+ }
+
+ @Test
+ public void testNoLogoutContext() {
+ rpConfig.setLogoutHintClaim("sub");
+ strategy = new LogoutHintLookupStrategy(prc -> null);
+
+ assertNull(strategy.apply(mc));
+ }
+
+ @Test
+ public void testNoIDToken() {
+ rpConfig.setLogoutHintClaim("sub");
+ final OIDCLogoutContext logoutContext = new OIDCLogoutContext();
+ strategy = new LogoutHintLookupStrategy(prc -> logoutContext);
+
+ assertNull(strategy.apply(mc));
+ }
+
+ @Test
+ public void testClaimFound() {
+ rpConfig.setLogoutHintClaim("sub");
+ final OIDCLogoutContext logoutContext = new OIDCLogoutContext();
+ logoutContext.setIdToken(idToken);
+
+ strategy = new LogoutHintLookupStrategy(prc -> logoutContext);
+
+ assertEquals(strategy.apply(mc), "jdoe");
+ }
+
+ @Test
+ public void testClaimMissing() {
+
+ rpConfig.setLogoutHintClaim("email");
+ final OIDCLogoutContext logoutContext = new OIDCLogoutContext();
+ logoutContext.setIdToken(idToken);
+
+ strategy = new LogoutHintLookupStrategy(prc -> logoutContext);
+
+ assertNull(strategy.apply(mc));
+ }
+
+ @Test(expectedExceptions = ConstraintViolationException.class)
+ public void testNullLookupStrategy() {
+ new LogoutHintLookupStrategy(null);
+ }
+
+}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
index 7a91ee2..0b8e957 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
@@ -234,6 +234,25 @@ public final class TestHelper {
return createJWT(claims, JWSAlgorithm.HS256, null, null, macCredential, null);
}
+ /**
+ * Simplified method to create a signed JWT (Id Token) but leave it in a to-be-signed state. There is not signature.
+ * Useful for tests that only need the JWT structure and claims set.
+ *
+ * @param issuer the issuer
+ * @param subject the subject
+ *
+ * @return a to-be-signed Signed JWT
+ */
+ public static JWT createSignedIDTokenWithNoSignature(@Nullable final String issuer, @Nullable final String subject){
+ final JWTClaimsSet claims = new JWTClaimsSet.Builder()
+ .issuer(issuer)
+ .subject(subject)
+ .expirationTime(new Date(System.currentTimeMillis() + 60000))
+ .build();
+ // No actual signature is computed here.
+ return new SignedJWT(new JWSHeader(JWSAlgorithm.HS256),claims);
+ }
+
/**
* Create a JWT from the given payload. The JWT can either be plain, or signed and encrypted. If encrypted, it must
* be signed.
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml
index 05e2077..c8638ab 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/oidc/oidc-beans.xml
@@ -40,8 +40,6 @@
</property>
</bean>
- <!-- TODO, these should be optional via RP profile config switched, but we do not have a LogoutProfile config
- for the RP -->
<bean id="BuildLogoutRequest" parent="WebFlowOutboundMessageHandlerAdaptor" scope="prototype">
<constructor-arg>
<bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
@@ -49,19 +47,36 @@
<list>
<bean id="AddIDTokenHint" scope="prototype" parent="LogoutRequestHandler"
class="net.shibboleth.oidc.profile.messaging.handler.impl.AddIDTokenHintHandler">
+ <property name="activationCondition">
+ <bean id="idTokenHintActivationCondition"
+ class=" net.shibboleth.sp.oidc.profile.context.logic.IDTokenHintActivationCondition"
+ scope="prototype"/>
+ </property>
<property name="parameterValueLookupStrategy">
<bean class="net.shibboleth.sp.oidc.profile.context.navigate.IDTokenHintLookupStrategy"
c:oidcLogoutContextLookupStrategy-ref="shibboleth.ChildLookup.LogoutContext"
scope="prototype" />
</property>
</bean>
- <!-- Should this be conditional on the ID Token Hint being present? -->
+ <!-- Is not added if the id_token_hint was set in the first step as the id_token already contains the hinted claims-->
<bean id="AddLogoutHint" scope="prototype" parent="LogoutRequestHandler"
class="net.shibboleth.oidc.profile.messaging.handler.impl.AddLogoutHintHandler">
- <!-- <property name="parameterValueLookupStrategy">
- <bean class="net.shibboleth.sp.oidc.profile.config.navigate.ResponseTypeLookupStrategy"
+ <property name="parameterValueLookupStrategy">
+ <bean class="net.shibboleth.sp.oidc.profile.context.navigate.LogoutHintLookupStrategy"
+ c:oidcLogoutContextLookupStrategy-ref="shibboleth.ChildLookup.LogoutContext"
scope="prototype" />
- </property> -->
+ </property>
+ <property name="activationCondition">
+ <!-- Only activate if the ID Token Hint was not been set -->
+ <bean id="logoutHintActivationCondition"
+ class=" net.shibboleth.sp.oidc.profile.context.logic.LogoutHintActivationCondition"
+ scope="prototype">
+ <constructor-arg>
+ <bean class="net.shibboleth.sp.oidc.messaging.context.navigate.LogoutRequestFromMessageContextLookupFunction"
+ scope="prototype"/>
+ </constructor-arg>
+ </bean>
+ </property>
</bean>
<bean id="AddClientID" scope="prototype" parent="LogoutRequestHandler"
class="net.shibboleth.oidc.profile.messaging.handler.impl.AddClientIDHandler">
@@ -73,10 +88,15 @@
</bean>
<bean id="AddPostLogoutRedirectURI" scope="prototype" parent="LogoutRequestHandler"
class="net.shibboleth.oidc.profile.messaging.handler.impl.AddPostLogoutRedirectURIHandler">
- <!-- <property name="parameterValueLookupStrategy">
- <bean class="net.shibboleth.sp.oidc.profile.config.navigate.ResponseTypeLookupStrategy"
+ <property name="activationCondition">
+ <bean id="idTokenHintActivationCondition"
+ class=" net.shibboleth.sp.oidc.profile.context.logic.PostLogoutRedirectUriActivationCondition"
+ scope="prototype"/>
+ </property>
+ <property name="parameterValueLookupStrategy">
+ <bean class="net.shibboleth.sp.oidc.profile.config.navigate.PostLogoutRedirectLookupStrategy"
scope="prototype" />
- </property> -->
+ </property>
</bean>
<bean id="AddUILocales" scope="prototype" parent="LogoutRequestHandler"
class="net.shibboleth.oidc.profile.messaging.handler.impl.AddLogoutUILocalesHandler">
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
index 5590049..6b38143 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
@@ -108,20 +108,11 @@
</bean>
<bean id="OIDC.Logout" parent="AbstractOIDCProfile" lazy-init="true"
- class="net.shibboleth.oidc.profile.config.impl.DefaultOIDCLogoutConfiguration"/>
+ class="net.shibboleth.oidc.profile.config.impl.DefaultOIDCLogoutConfiguration"
+ p:logoutHintClaim="%{sp.oidc.logout.logoutHintClaim:sid}"
+ p:requireIdTokenHint="%{sp.oidc.logout.includeIdTokenHint:true}"
+ p:includePostLogoutRedirectUri="%{sp.oidc.logout.includePostLogoutRedirectUri:false}"/>
- <!-- TODO, Which of these profile options do we need? -->
- <!--
- p:issuer-ref="shibboleth.oidc.issuer"
- p:logoutHintMatchingStrategy-ref="%{idp.oidc.logout.logoutHintMatchingStrategy:DefaultLogoutHintMatchingPredicate}"
- p:securityConfiguration-ref="%{idp.security.oidc.logout.config:shibboleth.oidc.logout.DefaultSecurityConfiguration}"
- p:preferFrontChannel="%{idp.oidc.logout.preferFrontChannel:true}"
- p:frontChannelSuccess="%{idp.oidc.logout.frontChannelSuccess:false}"
- p:revokeTokens="%{idp.oidc.logout.revokeTokens:true}"
- p:requireIdTokenHint="%{idp.oidc.logout.requireIdTokenHint:true}"
- p:encryptionOptional="%{idp.oidc.logout.encryptionOptional:true}"
- p:ignoreInvalidPostLogoutRedirectUri="%{idp.oidc.logout.ignoreInvalidPostLogoutRedirectUri:false}" -->
-
<util:constant id="OIDC.SSO.FEATURE_ESSENTIAL_ACR_REQUEST"
static-field="net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration.FEATURE_ESSENTIAL_ACR_REQUEST"/>
diff --git a/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties b/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
index d132139..b2ee9bc 100644
--- a/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
+++ b/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
@@ -38,6 +38,15 @@
#sp.oidc.requestobject.encrypted = false
#sp.oidc.requestobject.signed = true
+# Logout settings
+
+# Whether to enable the id_token_hint parameter
+#sp.oidc.logout.includeIdTokenHint = true
+# Whether to enable the post_logout_redirect_uri parameter if the target is present in the request from the Agent
+#sp.oidc.logout.includePostLogoutRedirectUri = false
+# Default logout_hint claim is the session-identifier, if it exists. Note this will not be used if sp.oidc.logout.enableIdTokenHint is enabled
+#sp.oidc.logout.logoutHintClaim = sid
+
# Global Token Validation Settings
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCLogoutInitiatorFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCLogoutInitiatorFlowTest.java
index 672dbf7..301b420 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCLogoutInitiatorFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCLogoutInitiatorFlowTest.java
@@ -52,6 +52,7 @@ import net.shibboleth.sp.flows.AbstractSPFlowTest;
import net.shibboleth.sp.oidc.profile.impl.PrepareAgentResponse;
import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.oidc.testing.TestHelper;
+import net.shibboleth.sp.profile.SPConstants;
/**
* Unit test for the OIDC-SP RP-Initiated logout flow.
@@ -147,9 +148,28 @@ public class OIDCLogoutInitiatorFlowTest extends AbstractSPFlowTest {
assertFlowExecutionOutcome(result.getOutcome());
final var output = assertOutputMessageSuccess(result);
assert output != null;
- System.out.println("test output: " + output.toString());
+ System.out.println("test output: " + output.toString());
+ }
+
+ /* Test with a Target input, which should form the post_logout_redirect_uri.*/
+ @Test
+ public void testRPInitiated_WithTarget() throws Exception {
+ setDefaultAuth();
+ final DDF input =
+ TestHelper.buildLogoutInitiatorDDFInput(sealedIdToken, PrepareAgentResponse.ID_TOKEN_PARAM);
+ // Add target
+ input.addmember(SPConstants.TARGET).unsafe_string("https://sp.example.org/logout-end".getBytes(StandardCharsets.UTF_8));
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
+
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ final var output = assertOutputMessageSuccess(result);
+ assert output != null;
+ System.out.println("test output: " + output.toString());
}
/**
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list