[java-idp-oidc] branch dev/JOIDC-13 updated: JOIDC-13 - Support for OIDC Logout
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Dec 1 06:52:56 UTC 2023
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch dev/JOIDC-13
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=c40aa92bbab3ff6d66ac3a549a64aa1d6e4cbf1e
The following commit(s) were added to refs/heads/dev/JOIDC-13 by this push:
new c40aa92b JOIDC-13 - Support for OIDC Logout
c40aa92b is described below
commit c40aa92bbab3ff6d66ac3a549a64aa1d6e4cbf1e
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