[java-idp-oidc] 12/14: JOIDC-13 - Support for OIDC Logout

Henri Mikkonen henri.mikkonen at iki.fi
Wed Jan 3 14:07:19 UTC 2024


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

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=43d4824251dfd012adbb2f4040a58cabe81e1196

commit 43d4824251dfd012adbb2f4040a58cabe81e1196
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Dec 1 08:52:13 2023 +0200

    JOIDC-13 - Support for OIDC Logout
    
    https://shibboleth.atlassian.net/browse/JOIDC-13
    
    Fine-tuning and unit tests for the RP-intiated logout actions.
---
 .../impl/PopulateRpInitiatedLogoutContext.java     |   5 +
 .../impl/ProcessRpInitiatedLogoutRequest.java      |  21 +-
 .../impl/BaseOIDCRpInitiatedLogoutActionTest.java  |  74 +++++++
 .../impl/FormRpInitiatedLogoutResponseTest.java    |  81 ++++++++
 ...pInitiatedLogoutResponseMessageContextTest.java |  47 +++++
 .../impl/PopulateRpInitiatedLogoutContextTest.java | 215 +++++++++++++++++++++
 .../impl/ProcessRpInitiatedLogoutRequestTest.java  | 202 +++++++++++++++++++
 .../profile/impl/ValidateIdTokenHintTest.java      |  80 ++++++++
 .../impl/ValidatePostLogoutRedirectURITest.java    |  86 +++++++++
 9 files changed, 803 insertions(+), 8 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PopulateRpInitiatedLogoutContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PopulateRpInitiatedLogoutContext.java
index a8eeac65..b3246944 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PopulateRpInitiatedLogoutContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PopulateRpInitiatedLogoutContext.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
 import com.google.common.base.Predicates;
 import com.nimbusds.jwt.EncryptedJWT;
 import com.nimbusds.jwt.JWT;
+import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.oauth2.sdk.id.State;
 
 import net.shibboleth.oidc.profile.core.OidcEventIds;
@@ -91,6 +92,10 @@ public class PopulateRpInitiatedLogoutContext extends AbstractOIDCRpInitiatedLog
         }
         getRpInitiatedLogoutContext().setLogoutHint(StringSupport.trimOrNull(getLogoutRequest().getLogoutHint()));
         getRpInitiatedLogoutContext().setPostLogoutRedirectUri(getLogoutRequest().getPostLogoutRedirectionURI());
+        final ClientID clientId = getLogoutRequest().getClientID();
+        if (clientId != null) {
+            getRpInitiatedLogoutContext().setRequestedClientId(clientId.getValue());
+        }
         final State state = getLogoutRequest().getState();
         if (state != null) {
             getRpInitiatedLogoutContext().setState(state.getValue());
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ProcessRpInitiatedLogoutRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ProcessRpInitiatedLogoutRequest.java
index cf3a8b7c..0dfaf454 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ProcessRpInitiatedLogoutRequest.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ProcessRpInitiatedLogoutRequest.java
@@ -29,19 +29,19 @@ import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
-import org.opensaml.saml.saml2.core.LogoutRequest;
 import org.slf4j.Logger;
 
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.LogoutRequest;
 
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 import net.shibboleth.idp.authn.context.SubjectContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCRpInitiatedLogoutContext;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.LogoutRequestClientIDLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
 import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
-import net.shibboleth.idp.saml.session.SAML2SPSession;
 import net.shibboleth.idp.session.IdPSession;
 import net.shibboleth.idp.session.SPSession;
 import net.shibboleth.idp.session.SessionResolver;
@@ -59,9 +59,10 @@ import net.shibboleth.shared.resolver.CriteriaSet;
 import net.shibboleth.shared.resolver.ResolverException;
 
 /**
- * Profile action that processes a {@link LogoutRequest} by resolving matching sessions, and destroys them,
- * populating the associated {@link SPSession} objects (excepting the one initiating the logout) into a
- * {@link LogoutContext}.
+ * Profile action that processes {@link LogoutRequest} and {@link OIDCRpInitiatedLogoutContext} by resolving matching
+ * sessions and populating the associated {@link SPSession} objects into a {@link LogoutContext}. The session that
+ * initiated the process is included in the context only if {@link OIDCRPSession#supportsLogoutPropagation()} 
+ * returns true.
  * 
  * <p>A {@link SubjectContext} is also populated. If and only if a single {@link IdPSession} is resolved,
  * a {@link SessionContext} is also populated.</p>
@@ -274,9 +275,13 @@ public class ProcessRpInitiatedLogoutRequest extends AbstractOIDCRpInitiatedLogo
                 
                 for (final SPSession spSession : session.getSPSessions()) {
                     assert spSession!=null;
-                    if (!sessionMatches(profileRequestContext, spSession)) {
+                    if (!sessionMatches(profileRequestContext, spSession) ||
+                            ((OIDCRPSession) spSession).supportsLogoutPropagation()) {
                         logoutCtx.getSessionMap().put(spSession.getId(), spSession);
                         logoutCtx.getKeyedSessionMap().put(Integer.toString(count++), spSession);
+                    } else {
+                        log.debug("{} Skipping {} when popoulating the logout context", getLogPrefix(),
+                                spSession.getId());
                     }
                 }
             }
@@ -299,7 +304,7 @@ public class ProcessRpInitiatedLogoutRequest extends AbstractOIDCRpInitiatedLogo
 // Checkstyle: CyclomaticComplexity|ReturnCount ON
 
     /**
-     * Check if the session contains a {@link SAML2SPSession} with the appropriate service ID and SessionIndex.
+     * Check if the session contains a {@link OIDCRPPSession} with the appropriate service ID and SessionIndex.
      * 
      * @param profileRequestContext current profile request context
      * @param session {@link IdPSession} to check
@@ -321,7 +326,7 @@ public class ProcessRpInitiatedLogoutRequest extends AbstractOIDCRpInitiatedLogo
     }
 
     /**
-     * Check if the {@link SPSession} has the appropriate service ID and SessionIndex.
+     * Check if the {@link OIDCRPPSession} has the appropriate service ID and SessionIndex.
      * 
      * @param profileRequestContext current profile request context
      * @param session {@link SPSession} to check
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/BaseOIDCRpInitiatedLogoutActionTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/BaseOIDCRpInitiatedLogoutActionTest.java
new file mode 100644
index 00000000..c134e1f4
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/BaseOIDCRpInitiatedLogoutActionTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.idp.plugin.oidc.op.logout.profile.impl;
+
+import java.net.URI;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.openid.connect.sdk.LogoutRequest;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCRpInitiatedLogoutContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+
+/**
+ * Base unit test for actions extending {@link AbstractOIDCRpInitiatedLogoutAction}.
+ */
+public abstract class BaseOIDCRpInitiatedLogoutActionTest<T extends AbstractOIDCRpInitiatedLogoutAction> {
+
+    protected T action;
+    protected RequestContext requestContext;
+    protected ProfileRequestContext profileRequestContext;
+    protected OIDCMetadataContext oidcMetadataContext;
+    protected OIDCRpInitiatedLogoutContext rpInitiatedLogoutContext;
+    protected LogoutRequest logoutRequest;
+
+    protected abstract T constructAction();
+
+    @BeforeMethod
+    protected void setup() throws Exception {
+        action = constructAction();
+        requestContext = new RequestContextBuilder().buildRequestContext();
+        profileRequestContext = new WebflowRequestContextProfileRequestContextLookup().apply(requestContext);
+        oidcMetadataContext = new OIDCMetadataContext();
+        profileRequestContext.getInboundMessageContext().addSubcontext(oidcMetadataContext);
+        rpInitiatedLogoutContext = new OIDCRpInitiatedLogoutContext();
+        profileRequestContext.getOutboundMessageContext().addSubcontext(rpInitiatedLogoutContext);
+        logoutRequest = new LogoutRequest(new URI("https://example.org/"));
+        profileRequestContext.getInboundMessageContext().setMessage(logoutRequest);
+    }
+
+    @Test
+    public void testMissingOidcMetadataContext() {
+        action = constructAction();
+        profileRequestContext.getInboundMessageContext().removeSubcontext(OIDCMetadataContext.class);
+        ActionTestingSupport.assertEvent(action.execute(requestContext), EventIds.INVALID_MSG_CTX);
+    }
+
+    @Test
+    public void testMissingRpInitiatedLogoutContext() {
+        action = constructAction();
+        profileRequestContext.getOutboundMessageContext().removeSubcontext(OIDCRpInitiatedLogoutContext.class);
+        ActionTestingSupport.assertEvent(action.execute(requestContext), EventIds.INVALID_MSG_CTX);
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormRpInitiatedLogoutResponseTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormRpInitiatedLogoutResponseTest.java
new file mode 100644
index 00000000..0641e9b5
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormRpInitiatedLogoutResponseTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.idp.plugin.oidc.op.logout.profile.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.impl.RpInitiatedLogoutResponse;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link FormRpInitiatedLogoutResponse}.
+ */
+public class FormRpInitiatedLogoutResponseTest
+        extends BaseOIDCRpInitiatedLogoutActionTest<FormRpInitiatedLogoutResponse> {
+
+    @Override
+    protected FormRpInitiatedLogoutResponse constructAction() {
+        final FormRpInitiatedLogoutResponse action = new FormRpInitiatedLogoutResponse();
+        try {
+            action.initialize();
+        } catch (final ComponentInitializationException e) {
+            Assert.fail();
+        }
+        return action;
+    }
+
+    @Test
+    public void testWithoutPostLogoutRedirectUri() {
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        final RpInitiatedLogoutResponse response = parseLogoutResponse();
+        Assert.assertTrue(response.indicatesSuccess());
+        Assert.assertEquals(response.toHTTPResponse().getStatusCode(), 200);
+    }
+
+    @Test
+    public void testWithtPostLogoutRedirectUriNoState() throws URISyntaxException {
+        final URI uri = new URI("https://example.org/postLogout");
+        rpInitiatedLogoutContext.setPostLogoutRedirectUri(uri);
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        final RpInitiatedLogoutResponse response = parseLogoutResponse();
+        Assert.assertTrue(response.indicatesSuccess());
+        Assert.assertEquals(response.toHTTPResponse().getStatusCode(), 302);
+        Assert.assertEquals(response.toHTTPResponse().getLocation(), uri);
+    }
+
+    @Test
+    public void testWithtPostLogoutRedirectUriAndState() throws URISyntaxException {
+        final URI uri = new URI("https://example.org/postLogout");
+        rpInitiatedLogoutContext.setPostLogoutRedirectUri(uri);
+        rpInitiatedLogoutContext.setState("mockState");
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        final RpInitiatedLogoutResponse response = parseLogoutResponse();
+        Assert.assertTrue(response.indicatesSuccess());
+        Assert.assertEquals(response.toHTTPResponse().getStatusCode(), 302);
+        Assert.assertEquals(response.toHTTPResponse().getLocation(), new URI(uri.toString() + "?state=mockState"));
+    }
+
+    protected RpInitiatedLogoutResponse parseLogoutResponse() {
+        final Object responseObject = profileRequestContext.getOutboundMessageContext().getMessage();
+        Assert.assertNotNull(responseObject);
+        Assert.assertTrue(responseObject instanceof RpInitiatedLogoutResponse);
+        return (RpInitiatedLogoutResponse) responseObject;
+    }
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/InitializeOutboundRpInitiatedLogoutResponseMessageContextTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/InitializeOutboundRpInitiatedLogoutResponseMessageContextTest.java
new file mode 100644
index 00000000..8039334b
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/InitializeOutboundRpInitiatedLogoutResponseMessageContextTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.idp.plugin.oidc.op.logout.profile.impl;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCRpInitiatedLogoutContext;
+
+/**
+ * Unit tests for {@link InitializeOutboundRpInitiatedLogoutResponseMessageContext}.
+ */
+public class InitializeOutboundRpInitiatedLogoutResponseMessageContextTest {
+
+    protected InitializeOutboundRpInitiatedLogoutResponseMessageContext action;
+    protected ProfileRequestContext profileRequestContext;
+
+    @BeforeMethod
+    protected void setup() throws Exception {
+        profileRequestContext = new ProfileRequestContext();
+        action = new InitializeOutboundRpInitiatedLogoutResponseMessageContext();
+        action.initialize();
+    }
+
+    @Test
+    public void testSuccess() {
+        Assert.assertNull(profileRequestContext.getOutboundMessageContext());
+        action.execute(profileRequestContext);
+        Assert.assertNotNull(profileRequestContext.getOutboundMessageContext());
+        Assert.assertNotNull(profileRequestContext.getOutboundMessageContext()
+                .getSubcontext(OIDCRpInitiatedLogoutContext.class));
+    }
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PopulateRpInitiatedLogoutContextTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PopulateRpInitiatedLogoutContextTest.java
new file mode 100644
index 00000000..947a4b0f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PopulateRpInitiatedLogoutContextTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.idp.plugin.oidc.op.logout.profile.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEHeader;
+import com.nimbusds.jose.JWEObject;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.crypto.AESEncrypter;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.LogoutRequest;
+
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.impl.support.TestCredentialHelper;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link PopulateRpInitiatedLogoutContext}.
+ */
+public class PopulateRpInitiatedLogoutContextTest
+        extends BaseOIDCRpInitiatedLogoutActionTest<PopulateRpInitiatedLogoutContext> {
+
+    RSAPrivateKey rsaPrivateKey;
+    RSAPublicKey rsaPublicKey;
+
+    @BeforeClass
+    public void initKeys() throws NoSuchAlgorithmException {
+        final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+        keyGen.initialize(2048);
+        final KeyPair keyPair = keyGen.genKeyPair();
+        rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+        rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+    }
+
+    @Override
+    protected PopulateRpInitiatedLogoutContext constructAction() {
+        return constructAction(false);
+    }
+
+    protected PopulateRpInitiatedLogoutContext constructAction(final boolean enforceIdTokenHint) {
+        final PopulateRpInitiatedLogoutContext action = new PopulateRpInitiatedLogoutContext();
+        action.setIdTokenHintEnforcedPredicate(prc -> enforceIdTokenHint);
+        try {
+            action.initialize();
+        } catch (final ComponentInitializationException e) {
+            Assert.fail();
+        }
+        return action;
+    }
+
+    @Test
+    public void testNoParams() throws URISyntaxException {
+        profileRequestContext.getInboundMessageContext().setMessage(new LogoutRequest(new URI("https://example.org")));
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        Assert.assertNull(rpInitiatedLogoutContext.getLogoutHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedClientId());
+        Assert.assertNull(rpInitiatedLogoutContext.getState());
+        Assert.assertNull(rpInitiatedLogoutContext.getPostLogoutRedirectUri());
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedIdTokenHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getProcessedIdTokenHint());
+    }
+
+    @Test
+    public void testWithClientId() throws URISyntaxException {
+        profileRequestContext.getInboundMessageContext().setMessage(new LogoutRequest(
+                new URI("https://example.org"), null, null, new ClientID("mockClientId"), null, null, null));
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        Assert.assertNull(rpInitiatedLogoutContext.getLogoutHint());
+        Assert.assertEquals(rpInitiatedLogoutContext.getRequestedClientId(), "mockClientId");
+        Assert.assertNull(rpInitiatedLogoutContext.getState());
+        Assert.assertNull(rpInitiatedLogoutContext.getPostLogoutRedirectUri());
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedIdTokenHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getProcessedIdTokenHint());
+    }
+
+    @Test
+    public void testWithUriNoState() throws URISyntaxException {
+        final URI postLogoutRedirectUri = new URI("https://rp.example.org/postLogout");
+        profileRequestContext.getInboundMessageContext().setMessage(new LogoutRequest(
+                new URI("https://example.org"), null, null, null, postLogoutRedirectUri, null, null));
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        Assert.assertNull(rpInitiatedLogoutContext.getLogoutHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedClientId());
+        Assert.assertNull(rpInitiatedLogoutContext.getState());
+        Assert.assertEquals(rpInitiatedLogoutContext.getPostLogoutRedirectUri(), postLogoutRedirectUri);
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedIdTokenHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getProcessedIdTokenHint());
+    }
+
+    @Test
+    public void testWithUriAndState() throws URISyntaxException {
+        final URI postLogoutRedirectUri = new URI("https://rp.example.org/postLogout");
+        profileRequestContext.getInboundMessageContext().setMessage(new LogoutRequest(
+                new URI("https://example.org"), null, null, null, postLogoutRedirectUri,
+                new State("mockState"), null));
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        Assert.assertNull(rpInitiatedLogoutContext.getLogoutHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedClientId());
+        Assert.assertEquals(rpInitiatedLogoutContext.getState(), "mockState");
+        Assert.assertEquals(rpInitiatedLogoutContext.getPostLogoutRedirectUri(), postLogoutRedirectUri);
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedIdTokenHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getProcessedIdTokenHint());
+    }
+
+    @Test
+    public void testWithLogoutHint() throws URISyntaxException {
+        profileRequestContext.getInboundMessageContext().setMessage(new LogoutRequest(
+                new URI("https://example.org"), null, "mockLogoutHint", null, null, null, null));
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        Assert.assertEquals(rpInitiatedLogoutContext.getLogoutHint(), "mockLogoutHint");
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedClientId());
+        Assert.assertNull(rpInitiatedLogoutContext.getState());
+        Assert.assertNull(rpInitiatedLogoutContext.getPostLogoutRedirectUri());
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedIdTokenHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getProcessedIdTokenHint());
+    }
+
+    @Test
+    public void testWithNonEncryptedIdTokenHint() throws URISyntaxException {
+        final SignedJWT jwt = buildSignedJWT();
+        profileRequestContext.getInboundMessageContext().setMessage(new LogoutRequest(
+                new URI("https://example.org"), jwt, null, null, null, null, null));
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        Assert.assertNull(rpInitiatedLogoutContext.getLogoutHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedClientId());
+        Assert.assertNull(rpInitiatedLogoutContext.getState());
+        Assert.assertNull(rpInitiatedLogoutContext.getPostLogoutRedirectUri());
+        Assert.assertEquals(rpInitiatedLogoutContext.getRequestedIdTokenHint(), jwt);
+        Assert.assertEquals(rpInitiatedLogoutContext.getProcessedIdTokenHint(), jwt);
+    }
+
+    @Test
+    public void testWithEncryptedIdTokenHint() throws URISyntaxException {
+        final EncryptedJWT jwt = buildEncryptedJWT();
+        profileRequestContext.getInboundMessageContext().setMessage(new LogoutRequest(
+                new URI("https://example.org"), jwt, null, null, null, null, null));
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        Assert.assertNull(rpInitiatedLogoutContext.getLogoutHint());
+        Assert.assertNull(rpInitiatedLogoutContext.getRequestedClientId());
+        Assert.assertNull(rpInitiatedLogoutContext.getState());
+        Assert.assertNull(rpInitiatedLogoutContext.getPostLogoutRedirectUri());
+        Assert.assertEquals(rpInitiatedLogoutContext.getRequestedIdTokenHint(), jwt);
+        Assert.assertNull(rpInitiatedLogoutContext.getProcessedIdTokenHint());
+    }
+
+    protected SignedJWT buildSignedJWT() {
+        final JWSSigner signer = new RSASSASigner(rsaPrivateKey);
+        final SignedJWT jwt = new SignedJWT(
+                new JWSHeader.Builder(new JWSAlgorithm("RS256")).build(),
+                new JWTClaimsSet.Builder().issuer("mockIssuer").build());
+        try {
+            jwt.sign(signer);
+        } catch (final JOSEException e) {
+            Assert.fail();
+        }
+        return jwt;
+    }
+
+    protected EncryptedJWT buildEncryptedJWT() {
+        final JWEObject jweObject = 
+                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.A256KW, EncryptionMethod.A256GCM)
+                .contentType("JWT")
+                .keyID("client_secret_credential")
+                .build(),
+                new Payload(buildSignedJWT()));
+        final JWKCredential cred;
+        try {
+            cred = TestCredentialHelper.createClientSecretCredential(
+                    "mockClientSecret1234567890mockClientSecret1234567890mockClientSecret1234567890")
+                    .toEncryptionCredential(JWEAlgorithm.A256KW, EncryptionMethod.A256GCM);
+            jweObject.encrypt(new AESEncrypter(cred.getSecretKey()));
+            return EncryptedJWT.parse(jweObject.serialize());
+        } catch (KeyException | JOSEException | ParseException e) {
+            Assert.fail();
+        }
+        return null;
+    }
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ProcessRpInitiatedLogoutRequestTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ProcessRpInitiatedLogoutRequestTest.java
new file mode 100644
index 00000000..14e99f87
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ProcessRpInitiatedLogoutRequestTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.idp.plugin.oidc.op.logout.profile.impl;
+
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.session.BasicSPSession;
+import net.shibboleth.idp.session.IdPSession;
+import net.shibboleth.idp.session.SPSession;
+import net.shibboleth.idp.session.SessionResolver;
+import net.shibboleth.idp.session.criterion.HttpServletRequestCriterion;
+import net.shibboleth.idp.session.criterion.SPSessionCriterion;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * Unit tests for {@link ProcessRpInitiatedLogoutRequest}.
+ */
+public class ProcessRpInitiatedLogoutRequestTest
+        extends BaseOIDCRpInitiatedLogoutActionTest<ProcessRpInitiatedLogoutRequest> {
+
+    SessionResolver sessionResolver = Mockito.mock(SessionResolver.class);
+    @SuppressWarnings("unchecked")
+    Function<ProfileRequestContext,BiPredicate<String,SPSession>> logoutHintPredicate = Mockito.mock(Function.class);
+    String clientId = "mockClientId";
+    String sessionId = "mockSessionId";
+    String issuer = "mockIssuer";
+    String subject = "mockSubject";
+
+    @Override
+    protected ProcessRpInitiatedLogoutRequest constructAction() {
+        return constructAction(null);
+    }
+
+    protected ProcessRpInitiatedLogoutRequest constructAction(final String clientId) {
+        final ProcessRpInitiatedLogoutRequest action = new ProcessRpInitiatedLogoutRequest();
+        action.setSessionResolver(sessionResolver);
+        action.setLogoutHintMatchingStrategyLookupStrategy(logoutHintPredicate);
+        if (clientId != null) {
+            action.setIssuerLookupStrategy(prc -> new ClientID(clientId));
+        }
+        try {
+            action.initialize();
+        } catch (final ComponentInitializationException e) {
+            Assert.fail("Could not initialize", e);
+        }
+        return action;
+    }
+
+    @Test
+    public void testNoSPSessions() throws ResolverException {
+        action = constructAction(clientId);
+        rpInitiatedLogoutContext.setProcessedIdTokenHint(mockJwtWithSessionId(sessionId));
+        IdPSession session = Mockito.mock(IdPSession.class);
+        initializeSessionResolver(session);
+        ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.SESSION_NOT_FOUND);
+    }
+
+    @Test
+    public void testNoOIDCRPSessions() throws ResolverException {
+        action = constructAction(clientId);
+        rpInitiatedLogoutContext.setProcessedIdTokenHint(mockJwtWithSessionId(sessionId));
+        IdPSession session = Mockito.mock(IdPSession.class);
+        Mockito.when(session.getSPSessions()).thenReturn(Set.of(new BasicSPSession("id", Instant.now(),
+                Instant.now().plusSeconds(600))));
+        initializeSessionResolver(session);
+        ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.SESSION_NOT_FOUND);
+    }
+
+    @Test
+    public void testNoMatchingClientIdInOIDCRPSessions() throws ResolverException {
+        action = constructAction(clientId);
+        rpInitiatedLogoutContext.setProcessedIdTokenHint(mockJwtWithSessionId(sessionId));
+        IdPSession session = Mockito.mock(IdPSession.class);
+        Mockito.when(session.getSPSessions()).thenReturn(Set.of(mockRpSession("wrongClientId", issuer, subject)));
+        initializeSessionResolver(session);
+        ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.SESSION_NOT_FOUND);
+    }
+
+    @Test
+    public void testNoMatchingSubjectInOIDCRPSessions() throws ResolverException {
+        action = constructAction(clientId);
+        rpInitiatedLogoutContext.setProcessedIdTokenHint(mockJwtWithSessionId(sessionId));
+        IdPSession session = Mockito.mock(IdPSession.class);
+        Mockito.when(session.getSPSessions()).thenReturn(Set.of(mockRpSession(clientId, issuer, "wrongSubject")));
+        initializeSessionResolver(session);
+        ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.SESSION_NOT_FOUND);
+    }
+
+    @Test
+    public void testSessionMatchedViaIdTokenHint() throws ResolverException {
+        action = constructAction(clientId);
+        rpInitiatedLogoutContext.setProcessedIdTokenHint(mockJwtWithSessionId(sessionId));
+        IdPSession session = Mockito.mock(IdPSession.class);
+        Mockito.when(session.getSPSessions()).thenReturn(Set.of(mockRpSession(clientId, issuer, subject)));
+        initializeSessionResolver(session);
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+    }
+
+    @Test
+    public void testSessionNotMatchedViaLogoutHint() throws ResolverException {
+        action = constructAction(clientId);
+        rpInitiatedLogoutContext.setLogoutHint(sessionId);
+        IdPSession session = Mockito.mock(IdPSession.class);
+        Mockito.when(logoutHintPredicate.apply(Mockito.any())).thenReturn((str, ses) -> false);
+        Mockito.when(session.getSPSessions()).thenReturn(Set.of(mockRpSession(clientId, issuer, subject)));
+        initializeSessionResolver(session);
+        ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.SESSION_NOT_FOUND);
+    }
+
+    @Test
+    public void testSessionMatchedViaLogoutHint() throws ResolverException {
+        action = constructAction(clientId);
+        rpInitiatedLogoutContext.setLogoutHint(sessionId);
+        IdPSession session = Mockito.mock(IdPSession.class);
+        Mockito.when(logoutHintPredicate.apply(Mockito.any())).thenReturn((str, ses) -> true);
+        Mockito.when(session.getSPSessions()).thenReturn(Set.of(mockRpSession(clientId, issuer, subject)));
+        initializeSessionResolver(session);
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+    }
+
+    protected OIDCRPSession mockRpSession(final String clientId, final String issuer, final String subject) {
+        return new OIDCRPSession.Builder()
+                .creationInstant(Instant.now())
+                .expirationInstant(Instant.now().plusSeconds(600))
+                .serviceId(clientId)
+                .sessionIdentifier(sessionId)
+                .issuer(issuer)
+                .subject(subject)
+                .build();
+ 
+    }
+    protected void initializeSessionResolver(final IdPSession session) {
+        try {
+            Mockito.when(sessionResolver.resolve(Mockito.argThat(new ArgumentMatcher<CriteriaSet>() {
+
+                @Override
+                public boolean matches(CriteriaSet set) {
+                    if (set != null) {
+                        final SPSessionCriterion sessionCriterion = set.get(SPSessionCriterion.class);
+                        if (sessionCriterion != null) {
+                            return clientId.equals(sessionCriterion.getServiceId())
+                                    && sessionId.equals(sessionCriterion.getSPSessionKey());
+                        }
+                        return set.get(HttpServletRequestCriterion.class) != null;
+                    } else {
+                        return false;
+                    }
+                }
+            }))).thenReturn(List.of(session));
+        } catch (ResolverException e) {
+            Assert.fail("Could not initilize mock session resolver", e);
+        }
+    }
+
+    protected JWT mockJwtWithSessionId(final String sessionId) {
+        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer("mockClientId")
+                .claim(TokenClaimsSet.KEY_SESSION_ID, sessionId)
+                .subject("mockSubject")
+                .build();
+        JWT jwt = Mockito.mock(JWT.class);
+        try {
+            Mockito.when(jwt.getJWTClaimsSet()).thenReturn(claimsSet);
+        } catch (ParseException e) {
+            Assert.fail();
+        }
+        return jwt;
+    }
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidateIdTokenHintTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidateIdTokenHintTest.java
new file mode 100644
index 00000000..5875cd4a
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidateIdTokenHintTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.idp.plugin.oidc.op.logout.profile.impl;
+
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link ValidateIdTokenHint}.
+ */
+public class ValidateIdTokenHintTest extends BaseOIDCRpInitiatedLogoutActionTest<ValidateIdTokenHint> {
+
+    ClaimsValidator validator = Mockito.mock(ClaimsValidator.class);
+
+    @Override
+    protected ValidateIdTokenHint constructAction() {
+        final ValidateIdTokenHint action = new ValidateIdTokenHint();
+        action.setClaimsValidator(validator);
+        try {
+            action.initialize();
+        } catch (ComponentInitializationException e) {
+            Assert.fail();
+        }
+        return action;
+    }
+    
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void testNoClaimsValidator() throws ComponentInitializationException {
+        final ValidateIdTokenHint action = new ValidateIdTokenHint();
+        action.initialize();
+    }
+
+    @Test
+    public void noIdTokenHint() {
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+    }
+
+    @Test
+    public void notSignedIdTokenHint() {
+        rpInitiatedLogoutContext.setProcessedIdTokenHint(Mockito.mock(PlainJWT.class));
+        ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.INVALID_ID_TOKEN_HINT);
+    }
+
+    @Test
+    public void notValidatedIdTokenHint() throws JWTValidationException {
+        Mockito.doThrow(JWTValidationException.class).when(validator).validate(Mockito.any(), Mockito.any());
+        rpInitiatedLogoutContext.setProcessedIdTokenHint(Mockito.mock(SignedJWT.class));
+        ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.INVALID_ID_TOKEN_HINT);
+    }
+
+    @Test
+    public void validatedIdTokenHint() throws JWTValidationException {
+        Mockito.doNothing().when(validator).validate(Mockito.any(), Mockito.any());
+        rpInitiatedLogoutContext.setProcessedIdTokenHint(Mockito.mock(SignedJWT.class));
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURITest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURITest.java
new file mode 100644
index 00000000..530d3e47
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURITest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.idp.plugin.oidc.op.logout.profile.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Set;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link ValidatePostLogoutRedirectURI}.
+ */
+public class ValidatePostLogoutRedirectURITest
+        extends BaseOIDCRpInitiatedLogoutActionTest<ValidatePostLogoutRedirectURI> {
+
+    @Override
+    protected ValidatePostLogoutRedirectURI constructAction() {
+        final ValidatePostLogoutRedirectURI action = new ValidatePostLogoutRedirectURI();
+        try {
+            action.initialize();
+        } catch (final ComponentInitializationException e) {
+            Assert.fail();
+        }
+        return action;
+    }
+    
+    @Test
+    public void noRequestedPostLogoutRedirectURis() {
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+    }
+
+    @Test
+    public void noRegisteredPostLogoutRedirectURis() {
+        rpInitiatedLogoutContext.setPostLogoutRedirectUri(mockPostLogoutRedirectUri());
+        ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.INVALID_REDIRECT_URI);
+    }
+
+    @Test
+    public void noMatchingRegisteredPostLogoutRedirectURis() throws URISyntaxException {
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setPostLogoutRedirectionURIs(Set.of(new URI("https://rp.example.com/notMatching")));
+        oidcMetadataContext.setClientInformation(new OIDCClientInformation(new ClientID("mockClientId"), metadata));
+        rpInitiatedLogoutContext.setPostLogoutRedirectUri(mockPostLogoutRedirectUri());
+        ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.INVALID_REDIRECT_URI);
+    }
+
+    @Test
+    public void matchingRegisteredPostLogoutRedirectURis() {
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setPostLogoutRedirectionURIs(Set.of(mockPostLogoutRedirectUri()));
+        oidcMetadataContext.setClientInformation(new OIDCClientInformation(new ClientID("mockClientId"), metadata));
+        rpInitiatedLogoutContext.setPostLogoutRedirectUri(mockPostLogoutRedirectUri());
+        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+    }
+
+    protected URI mockPostLogoutRedirectUri() {
+        try {
+            return new URI("https://rp.example.com/postLogout");
+        } catch (URISyntaxException e) {
+            Assert.fail();
+        }
+        return null;
+    }
+}

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


More information about the commits mailing list