[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