[java-idp-oidc] 11/14: JOIDC-13 - Support for OIDC Logout
Henri Mikkonen
henri.mikkonen at iki.fi
Wed Jan 3 14:07:18 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=412d81a1e81a9a64de907895a7a8e12218599d95
commit 412d81a1e81a9a64de907895a7a8e12218599d95
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Nov 24 18:17:37 2023 +0200
JOIDC-13 - Support for OIDC Logout
https://shibboleth.atlassian.net/browse/JOIDC-13
Improved the logout propagation action hierarchy and added unit tests.
---
.../impl/AbstractOIDCLogoutPropagationAction.java | 66 +++++++-
.../impl/ExecuteBackChannelLogoutRequest.java | 55 +-----
.../impl/FormOutboundLogoutRequestMessage.java | 39 +----
.../impl/PrepareBackChannelLogoutRequest.java | 57 +++----
.../op/logout/profile/impl/RevokeTokenChain.java | 44 +----
.../profile/impl/SetFrontChannelLogoutSuccess.java | 58 +------
...OIDCBackChannelLogoutPropagationActionTest.java | 51 ++++++
.../impl/BaseOIDCLogoutPropagationActionTest.java | 98 +++++++++++
.../impl/ExecuteBackChannelLogoutRequestTest.java | 185 +++++++++++++++++++++
.../impl/FormOutboundLogoutRequestMessageTest.java | 111 +++++++++++++
.../impl/PrepareBackChannelLogoutRequestTest.java | 81 +++++++++
.../logout/profile/impl/RevokeTokenChainTest.java | 108 ++++++++++++
.../impl/SetFrontChannelLogoutSuccessTest.java | 47 ++++++
13 files changed, 788 insertions(+), 212 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/AbstractOIDCLogoutPropagationAction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/AbstractOIDCLogoutPropagationAction.java
index e36d8e26..2be84f24 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/AbstractOIDCLogoutPropagationAction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/AbstractOIDCLogoutPropagationAction.java
@@ -16,13 +16,19 @@ package net.shibboleth.idp.plugin.oidc.op.logout.profile.impl;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCLogoutPropagationContext;
import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCResponseAction;
+import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
+import net.shibboleth.idp.session.context.LogoutPropagationContext;
+import net.shibboleth.shared.logic.Constraint;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.function.Function;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -34,9 +40,36 @@ public class AbstractOIDCLogoutPropagationAction extends AbstractOIDCResponseAct
/** Class logger. */
@Nonnull private static final Logger log = LoggerFactory.getLogger(AbstractOIDCLogoutPropagationAction.class);
+ /** The lookup strategy for the logout propagation context. */
+ @Nonnull private Function<ProfileRequestContext, LogoutPropagationContext> logoutPropagationContextLookupStrategy;
+
/** OIDC logout propagation context. */
@Nullable private OIDCLogoutPropagationContext oidcLogoutContext;
+ /** The logout propagation context to operate on. */
+ @Nullable private LogoutPropagationContext logoutPropagationContext;
+
+ /** The OIDC RP session containing the data for logout propagation. */
+ @Nullable private OIDCRPSession oidcRPSession;
+
+ /**
+ * Constructor.
+ */
+ public AbstractOIDCLogoutPropagationAction() {
+ logoutPropagationContextLookupStrategy = new ChildContextLookup<>(LogoutPropagationContext.class);
+ }
+
+ /**
+ * Set the lookup strategy for the logout propagation context.
+ * @param strategy What to set
+ */
+ public void setLogoutPropagationContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, LogoutPropagationContext> strategy) {
+ checkSetterPreconditions();
+ logoutPropagationContextLookupStrategy = Constraint.isNotNull(strategy,
+ "LogoutPropagationContext lookup strategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull ProfileRequestContext profileRequestContext) {
@@ -51,6 +84,22 @@ public class AbstractOIDCLogoutPropagationAction extends AbstractOIDCResponseAct
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
+
+ logoutPropagationContext = logoutPropagationContextLookupStrategy.apply(profileRequestContext);
+ if (logoutPropagationContext == null) {
+ log.error("{} No logout propagation context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ if (logoutPropagationContext.getSession() instanceof OIDCRPSession rpSession) {
+ oidcRPSession = rpSession;
+ } else {
+ log.error("{} Logout propagation context did not contain a OIDCRPSession", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
return true;
}
@@ -58,8 +107,23 @@ public class AbstractOIDCLogoutPropagationAction extends AbstractOIDCResponseAct
* Get the OIDC logout propagation context.
* @return The OIDC logout propagation context.
*/
- @Nullable public OIDCLogoutPropagationContext getOidcLogoutContext() {
+ @Nullable protected OIDCLogoutPropagationContext getOidcLogoutContext() {
return oidcLogoutContext;
}
+ /**
+ * Get the protocol-independent logout propagation context.
+ * @return The protocol-independent logout propagation context.
+ */
+ @Nullable protected LogoutPropagationContext getLogoutPropagationContext() {
+ return logoutPropagationContext;
+ }
+
+ /**
+ * Get the OIDC RP session.
+ * @return The OIDC RP session.
+ */
+ @Nullable protected OIDCRPSession getOidcRPSession() {
+ return oidcRPSession;
+ }
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ExecuteBackChannelLogoutRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ExecuteBackChannelLogoutRequest.java
index 20d39129..da7a232f 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ExecuteBackChannelLogoutRequest.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ExecuteBackChannelLogoutRequest.java
@@ -18,7 +18,6 @@ import com.nimbusds.common.contenttype.ContentType;
import com.nimbusds.jwt.JWT;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
-import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
import net.shibboleth.idp.session.context.LogoutPropagationContext;
import net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
@@ -35,7 +34,6 @@ import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.message.BasicNameValuePair;
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
@@ -69,18 +67,9 @@ public class ExecuteBackChannelLogoutRequest extends AbstractOIDCBackChannelLogo
/** HTTP client security parameters. */
@Nullable private HttpClientSecurityParameters httpClientSecurityParameters;
- /** The lookup strategy for the logout propagation context. */
- @Nonnull private Function<ProfileRequestContext, LogoutPropagationContext> logoutPropagationContextLookupStrategy;
-
/** The lookup strategy for back-channel logout endpoint. */
@Nonnull private final Function<OIDCClientInformation, String> oidcBackChannelLogoutURILookupFunction;
- /** The logout propagation context to operate on. */
- @Nullable private LogoutPropagationContext logoutPropagationContext;
-
- /** The OIDC RP session resolved from the logout propagation context. */
- @Nullable private OIDCRPSession oidcRPSession;
-
/** The back-channel logout URI for which to send the back-channel logout request. */
@Nullable private URI backChannelLogoutURI;
@@ -93,7 +82,6 @@ public class ExecuteBackChannelLogoutRequest extends AbstractOIDCBackChannelLogo
public ExecuteBackChannelLogoutRequest() {
oidcBackChannelLogoutURILookupFunction =
new ClientInformationStringValueLookupFunction("backchannel_logout_uri");
- logoutPropagationContextLookupStrategy = new ChildContextLookup<>(LogoutPropagationContext.class);
}
/**
@@ -114,17 +102,6 @@ public class ExecuteBackChannelLogoutRequest extends AbstractOIDCBackChannelLogo
httpClientSecurityParameters = params;
}
- /**
- * Set the lookup strategy for logout propagation context.
- * @param strategy What to set
- */
- public void setLogoutPropagationContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, LogoutPropagationContext> strategy) {
- checkSetterPreconditions();
- logoutPropagationContextLookupStrategy = Constraint.isNotNull(strategy,
- "LogoutPropagationContext lookup strategy cannot be null");
- }
-
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -139,24 +116,6 @@ public class ExecuteBackChannelLogoutRequest extends AbstractOIDCBackChannelLogo
return false;
}
- logoutPropagationContext = logoutPropagationContextLookupStrategy.apply(profileRequestContext);
- if (logoutPropagationContext == null) {
- log.error("{} No bclogout propagation context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- } else if (!(logoutPropagationContext.getSession() instanceof OIDCRPSession)) {
- log.error("{} Logout propagation context did not contain a OIDCRPSession", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- }
-
- oidcRPSession = (OIDCRPSession) logoutPropagationContext.getSession();
- if (oidcRPSession == null) {
- log.error("{} No OIDCRPSession available", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- }
-
if (getMetadataContext() == null) {
log.error("{} No OIDC metadata context available", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
@@ -193,9 +152,9 @@ public class ExecuteBackChannelLogoutRequest extends AbstractOIDCBackChannelLogo
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- final String relyingPartyId = oidcRPSession.getId();
- final String subject = oidcRPSession.getSubject();
- final String sessionId = oidcRPSession.getSessionIdentifier();
+ final String relyingPartyId = getOidcRPSession().getId();
+ final String subject = getOidcRPSession().getSubject();
+ final String sessionId = getOidcRPSession().getSessionIdentifier();
final HttpPost httpRequest = new HttpPost(backChannelLogoutURI);
@@ -215,24 +174,24 @@ public class ExecuteBackChannelLogoutRequest extends AbstractOIDCBackChannelLogo
if (response.getCode() == HttpStatus.SC_OK || response.getCode() == HttpStatus.SC_NO_CONTENT) {
if (!getOidcLogoutContext().hasRevocationError()) {
- logoutPropagationContext.setResult(LogoutPropagationContext.Result.Success);
+ getLogoutPropagationContext().setResult(LogoutPropagationContext.Result.Success);
log.debug("{} back-channel logout for rp '{}', sub '{}' and sid '{}' succeeded.", getLogPrefix(),
relyingPartyId, subject, sessionId);
} else {
- logoutPropagationContext.setResult(LogoutPropagationContext.Result.Failure);
+ getLogoutPropagationContext().setResult(LogoutPropagationContext.Result.Failure);
log.debug("{} back-channel logout for rp '{}', sub '{}' and sid '{}' succeeded, but overall " +
"result remains 'failure' due to previous error",
getLogPrefix(), relyingPartyId, subject, sessionId);
}
} else {
- logoutPropagationContext.setResult(LogoutPropagationContext.Result.Failure);
+ getLogoutPropagationContext().setResult(LogoutPropagationContext.Result.Failure);
log.error("{} back-channel logout for rp '{}', sub '{}' and sid '{}' failed. HTTP code: {}",
getLogPrefix(), relyingPartyId, subject, sessionId, response.getCode());
}
} catch (final IOException | URISyntaxException e) {
log.error("{} back-channel logout for rp '{}' with token '{}' failed.", getLogPrefix(), relyingPartyId,
logoutToken.serialize(), e);
- logoutPropagationContext.setResult(LogoutPropagationContext.Result.Failure);
+ getLogoutPropagationContext().setResult(LogoutPropagationContext.Result.Failure);
}
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormOutboundLogoutRequestMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormOutboundLogoutRequestMessage.java
index 3f1be412..d57fd6ad 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormOutboundLogoutRequestMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormOutboundLogoutRequestMessage.java
@@ -20,9 +20,7 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,10 +28,7 @@ import org.slf4j.LoggerFactory;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.idp.plugin.oidc.op.messaging.impl.FrontChannelLogoutPropagationResponse;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCResponseAction;
-import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
import net.shibboleth.idp.profile.IdPEventIds;
-import net.shibboleth.idp.session.context.LogoutPropagationContext;
import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.StringSupport;
@@ -42,7 +37,7 @@ import net.shibboleth.shared.primitive.StringSupport;
* Action that forms outbound message based on request and response context. Formed message is set to
* {@link ProfileRequestContext#getOutboundMessageContext()}.
*/
-public class FormOutboundLogoutRequestMessage extends AbstractOIDCResponseAction {
+public class FormOutboundLogoutRequestMessage extends AbstractOIDCLogoutPropagationAction {
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(FormOutboundLogoutRequestMessage.class);
@@ -50,15 +45,9 @@ public class FormOutboundLogoutRequestMessage extends AbstractOIDCResponseAction
/** Strategy used to obtain the response issuer value. */
@Nonnull private Function<ProfileRequestContext, String> issuerLookupStrategy;
- /** The lookup strategy for the logout propagation context. */
- @Nonnull private Function<ProfileRequestContext, LogoutPropagationContext> logoutPropagationContextLookupStrategy;
-
/** Issuer value to included in the response message, if configured to be included. */
@Nullable private String issuer;
- /** The logout propagation context to operate on. */
- @Nullable private LogoutPropagationContext logoutPropagationContext;
-
/** The RP metadata where the front-channel logout related data is fetched. */
@Nullable private OIDCClientInformation clientInformation;
@@ -70,7 +59,6 @@ public class FormOutboundLogoutRequestMessage extends AbstractOIDCResponseAction
*/
public FormOutboundLogoutRequestMessage() {
issuerLookupStrategy = new IssuerLookupFunction();
- logoutPropagationContextLookupStrategy = new ChildContextLookup<>(LogoutPropagationContext.class);
}
/**
@@ -83,18 +71,6 @@ public class FormOutboundLogoutRequestMessage extends AbstractOIDCResponseAction
issuerLookupStrategy = Constraint.isNotNull(strategy, "IssuerLookupStrategy lookup strategy cannot be null");
}
- /**
- * Set the strategy used to locate logout propagation context to use.
- *
- * @param strategy What to set
- */
- public void setLogoutPropagationContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, LogoutPropagationContext> strategy) {
- checkSetterPreconditions();
- logoutPropagationContextLookupStrategy = Constraint.isNotNull(strategy,
- "LogoutPropagationContext lookup strategy cannot be null");
- }
-
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -109,17 +85,6 @@ public class FormOutboundLogoutRequestMessage extends AbstractOIDCResponseAction
return false;
}
- logoutPropagationContext = logoutPropagationContextLookupStrategy.apply(profileRequestContext);
- if (logoutPropagationContext == null) {
- log.error("{} No bclogout propagation context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- } else if (!(logoutPropagationContext.getSession() instanceof OIDCRPSession)) {
- log.error("{} Logout propagation context did not contain a OIDCRPSession", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- }
-
if (getMetadataContext() == null || getMetadataContext().getClientInformation() == null) {
log.error("{} Could not find OIDC metadata", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
@@ -144,7 +109,7 @@ public class FormOutboundLogoutRequestMessage extends AbstractOIDCResponseAction
final FrontChannelLogoutPropagationResponse response;
if (clientInformation.getOIDCMetadata().requiresFrontChannelLogoutSession()) {
response = new FrontChannelLogoutPropagationResponse(frontChannelLogoutUri.toString(), issuer,
- ((OIDCRPSession) logoutPropagationContext.getSession()).getSessionIdentifier());
+ getOidcRPSession().getSessionIdentifier());
} else {
response = new FrontChannelLogoutPropagationResponse(frontChannelLogoutUri.toString());
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PrepareBackChannelLogoutRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PrepareBackChannelLogoutRequest.java
index 93f98e00..4fe46c9d 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PrepareBackChannelLogoutRequest.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PrepareBackChannelLogoutRequest.java
@@ -22,13 +22,11 @@ import com.nimbusds.openid.connect.sdk.claims.LogoutTokenClaimsSet;
import com.nimbusds.openid.connect.sdk.claims.SessionID;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCBackChannelLogoutPropagationContext;
-import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
-import net.shibboleth.idp.session.context.LogoutPropagationContext;
import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+import net.shibboleth.shared.security.impl.SecureRandomIdentifierGenerationStrategy;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
@@ -36,6 +34,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
@@ -51,29 +51,29 @@ public class PrepareBackChannelLogoutRequest extends AbstractOIDCBackChannelLogo
/** Class logger. */
@Nonnull private static final Logger log = LoggerFactory.getLogger(PrepareBackChannelLogoutRequest.class);
- /** The lookup strategy for fetching logout propagation context containing the OIDC RP session. */
- @Nonnull private Function<ProfileRequestContext, LogoutPropagationContext> logoutPropagationContextLookupStrategy;
+ /** Strategy used to locate the {@link IdentifierGenerationStrategy} to use. */
+ @Nonnull private Function<ProfileRequestContext,IdentifierGenerationStrategy> idGeneratorLookupStrategy;
- /** The OIDC RP session containing the data for back-channel logout. */
- @Nullable private OIDCRPSession oidcRPSession;
+ /** The generator to use. */
+ @Nullable private IdentifierGenerationStrategy idGenerator;
/**
* Constructor.
*/
public PrepareBackChannelLogoutRequest() {
- logoutPropagationContextLookupStrategy = new ChildContextLookup<>(LogoutPropagationContext.class);
+ idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
}
/**
- * Set the lookup strategy for fetching logout propagation context containing the OIDC RP session.
+ * Set the strategy used to locate the {@link IdentifierGenerationStrategy} to use.
*
- * @param strategy What to set
+ * @param strategy lookup strategy
*/
- public void setLogoutPropagationContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, LogoutPropagationContext> strategy) {
+ public void setIdentifierGeneratorLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,IdentifierGenerationStrategy> strategy) {
checkSetterPreconditions();
- logoutPropagationContextLookupStrategy = Constraint.isNotNull(strategy,
- "LogoutPropagationContext lookup strategy cannot be null");
+ idGeneratorLookupStrategy =
+ Constraint.isNotNull(strategy, "Identifier generation strategy cannot be null");
}
/** {@inheritDoc} */
@@ -83,32 +83,25 @@ public class PrepareBackChannelLogoutRequest extends AbstractOIDCBackChannelLogo
return false;
}
- final LogoutPropagationContext logoutPropagationContext =
- logoutPropagationContextLookupStrategy.apply(profileRequestContext);
- if (logoutPropagationContext == null) {
- log.error("{} No bclogout propagation context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- } else if (!(logoutPropagationContext.getSession() instanceof OIDCRPSession)) {
- log.error("{} Logout propagation context did not contain a OIDCRPSession", getLogPrefix());
+ idGenerator = idGeneratorLookupStrategy.apply(profileRequestContext);
+ if (idGenerator == null) {
+ log.error("{} No identifier generation strategy", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return false;
}
- oidcRPSession = (OIDCRPSession) logoutPropagationContext.getSession();
-
return true;
}
/** {@inheritDoc} */
@Override
- protected void doExecute(@NotNull ProfileRequestContext profileRequestContext) {
- final Issuer iss = new Issuer(oidcRPSession.getIssuer());
- final Subject sub = new Subject(oidcRPSession.getSubject());
- final List<Audience> aud = Collections.singletonList(new Audience(oidcRPSession.getId()));
+ protected void doExecute(@Nonnull ProfileRequestContext profileRequestContext) {
+ final Issuer iss = new Issuer(getOidcRPSession().getIssuer());
+ final Subject sub = new Subject(getOidcRPSession().getSubject());
+ final List<Audience> aud = Collections.singletonList(new Audience(getOidcRPSession().getId()));
final Date iat = Calendar.getInstance().getTime();
- final JWTID jti = new JWTID(oidcRPSession.getRootTokenIdentifier());
- final SessionID sid = new SessionID(oidcRPSession.getSessionIdentifier());
+ final JWTID jti = new JWTID(idGenerator.generateIdentifier());
+ final SessionID sid = new SessionID(getOidcRPSession().getSessionIdentifier());
final LogoutTokenClaimsSet logoutTokenClaimsSet = new LogoutTokenClaimsSet(iss, sub, aud, iat, jti, sid);
getOidcBackChannelLogoutContext().setLogoutTokenClaimsSet(logoutTokenClaimsSet);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/RevokeTokenChain.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/RevokeTokenChain.java
index 4e8c88d2..6c7b922d 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/RevokeTokenChain.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/RevokeTokenChain.java
@@ -15,24 +15,20 @@
package net.shibboleth.idp.plugin.oidc.op.logout.profile.impl;
import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultChainRevocationLifetimeLookupStrategy;
-import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
import net.shibboleth.idp.profile.IdPEventIds;
-import net.shibboleth.idp.session.context.LogoutPropagationContext;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.logic.Constraint;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.storage.RevocationCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
import java.time.Duration;
import java.util.function.Function;
@@ -47,9 +43,6 @@ public class RevokeTokenChain extends AbstractOIDCLogoutPropagationAction {
/** Token revocation cache instance to use. */
@NonnullAfterInit private RevocationCache revocationCache;
- /** Lookup function to logout propagation context. */
- @Nonnull private Function<ProfileRequestContext, LogoutPropagationContext> logoutPropagationContextLookupStrategy;
-
/** Lookup function to supply chain revocation lifetime. */
@Nonnull private Function<ProfileRequestContext,Duration> chainRevocationLifetimeLookupStrategy;
@@ -64,7 +57,6 @@ public class RevokeTokenChain extends AbstractOIDCLogoutPropagationAction {
*/
public RevokeTokenChain() {
chainRevocationLifetimeLookupStrategy = new DefaultChainRevocationLifetimeLookupStrategy();
- logoutPropagationContextLookupStrategy = new ChildContextLookup<>(LogoutPropagationContext.class);
}
/**
@@ -73,7 +65,7 @@ public class RevokeTokenChain extends AbstractOIDCLogoutPropagationAction {
* @param cache What to set.
*/
public void setRevocationCache(@Nonnull final RevocationCache cache) {
- ifInitializedThrowUnmodifiabledComponentException();
+ checkSetterPreconditions();
revocationCache = Constraint.isNotNull(cache, "RevocationCache cannot be null");
}
@@ -84,19 +76,8 @@ public class RevokeTokenChain extends AbstractOIDCLogoutPropagationAction {
*/
public void setChainRevocationLifetimeLookupStrategy(
@Nullable final Function<ProfileRequestContext,Duration> strategy) {
- chainRevocationLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
- }
-
- /**
- * Set the lookup strategy for logout propagation context.
- *
- * @param strategy What to set
- */
- public void setLogoutPropagationContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, LogoutPropagationContext> strategy) {
checkSetterPreconditions();
- logoutPropagationContextLookupStrategy = Constraint.isNotNull(strategy,
- "LogoutPropagationContext lookup strategy cannot be null");
+ chainRevocationLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
}
/**
@@ -115,20 +96,7 @@ public class RevokeTokenChain extends AbstractOIDCLogoutPropagationAction {
return false;
}
- final LogoutPropagationContext logoutPropagationContext =
- logoutPropagationContextLookupStrategy.apply(profileRequestContext);
- if (logoutPropagationContext == null) {
- log.error("{} No bclogout propagation context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- }
- if (!(logoutPropagationContext.getSession() instanceof OIDCRPSession)) {
- log.error("{} Logout propagation context did not contain a OIDCRPSession", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- }
-
- rootTokenIdentifier = ((OIDCRPSession) logoutPropagationContext.getSession()).getRootTokenIdentifier();
+ rootTokenIdentifier = getOidcRPSession().getRootTokenIdentifier();
if (rootTokenIdentifier == null) {
log.debug("{} OIDCRPSession context did not contain a root jti, nothing to revoke.", getLogPrefix());
return false;
@@ -141,7 +109,7 @@ public class RevokeTokenChain extends AbstractOIDCLogoutPropagationAction {
* {@inheritDoc}
*/
@Override
- protected void doExecute(@NotNull ProfileRequestContext profileRequestContext) {
+ protected void doExecute(@Nonnull ProfileRequestContext profileRequestContext) {
if (revocationCache.revoke(
RevocationCacheContexts.AUTHORIZATION_CODE, rootTokenIdentifier, revocationLifetime)) {
getOidcLogoutContext().setRevocationError(false);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/SetFrontChannelLogoutSuccess.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/SetFrontChannelLogoutSuccess.java
index f80a2d30..9b14d804 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/SetFrontChannelLogoutSuccess.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/SetFrontChannelLogoutSuccess.java
@@ -14,81 +14,27 @@
package net.shibboleth.idp.plugin.oidc.op.logout.profile.impl;
-import java.util.function.Function;
-
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCResponseAction;
-import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
import net.shibboleth.idp.session.context.LogoutPropagationContext;
import net.shibboleth.idp.session.context.LogoutPropagationContext.Result;
-import net.shibboleth.shared.logic.Constraint;
/**
* Action that sets the {@link LogoutPropagationContext#setResult(Result)} as 'success'.
*/
-public class SetFrontChannelLogoutSuccess extends AbstractOIDCResponseAction {
+public class SetFrontChannelLogoutSuccess extends AbstractOIDCLogoutPropagationAction {
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(SetFrontChannelLogoutSuccess.class);
- /** The lookup strategy for the logout propagation context. */
- @Nonnull private Function<ProfileRequestContext, LogoutPropagationContext> logoutPropagationContextLookupStrategy;
-
- /** The logout propagation context to operate on. */
- @Nullable private LogoutPropagationContext logoutPropagationContext;
-
- /**
- * Constructor.
- */
- public SetFrontChannelLogoutSuccess() {
- logoutPropagationContextLookupStrategy = new ChildContextLookup<>(LogoutPropagationContext.class);
- }
-
- /**
- * Set the lookup strategy for the logout propagation context.
- * @param strategy What to set
- */
- public void setLogoutPropagationContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, LogoutPropagationContext> strategy) {
- checkSetterPreconditions();
- logoutPropagationContextLookupStrategy = Constraint.isNotNull(strategy,
- "LogoutPropagationContext lookup strategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- logoutPropagationContext = logoutPropagationContextLookupStrategy.apply(profileRequestContext);
- if (logoutPropagationContext == null) {
- log.error("{} No bclogout propagation context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- } else if (!(logoutPropagationContext.getSession() instanceof OIDCRPSession)) {
- log.error("{} Logout propagation context did not contain a OIDCRPSession", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- }
-
- return true;
- }
-
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
log.debug("{} Set front-channel logout propagation result 'success'", getLogPrefix());
- logoutPropagationContext.setResult(Result.Success);
+ getLogoutPropagationContext().setResult(Result.Success);
}
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/BaseOIDCBackChannelLogoutPropagationActionTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/BaseOIDCBackChannelLogoutPropagationActionTest.java
new file mode 100644
index 00000000..f61588df
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/BaseOIDCBackChannelLogoutPropagationActionTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.action.EventIds;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCBackChannelLogoutPropagationContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCLogoutPropagationContext;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+
+/**
+ * Base unit test class for actions extending {@link AbstractOIDCBackChannelLogoutPropagationAction}.
+ *
+ * @param <T>
+ */
+public abstract class BaseOIDCBackChannelLogoutPropagationActionTest<T extends AbstractOIDCBackChannelLogoutPropagationAction>
+ extends BaseOIDCLogoutPropagationActionTest<AbstractOIDCBackChannelLogoutPropagationAction> {
+
+ protected OIDCBackChannelLogoutPropagationContext backChannelLogoutPropagationContext;
+
+ @BeforeMethod
+ protected void setup() throws Exception {
+ super.setup();
+ backChannelLogoutPropagationContext = new OIDCBackChannelLogoutPropagationContext();
+ profileRequestContext.getOutboundMessageContext().getSubcontext(OIDCLogoutPropagationContext.class)
+ .addSubcontext(backChannelLogoutPropagationContext);
+ }
+
+ @Test
+ public void testMissingOidcBackChannelLogoutPropagationContext() {
+ action = constructAction();
+ profileRequestContext.getOutboundMessageContext().getSubcontext(OIDCLogoutPropagationContext.class)
+ .removeSubcontext(OIDCBackChannelLogoutPropagationContext.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/BaseOIDCLogoutPropagationActionTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/BaseOIDCLogoutPropagationActionTest.java
new file mode 100644
index 00000000..9c004f79
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/BaseOIDCLogoutPropagationActionTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.time.Instant;
+
+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 net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCLogoutPropagationContext;
+import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
+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.idp.session.context.LogoutPropagationContext;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+
+/**
+ * Base unit test class for actions extending {@link AbstractOIDCLogoutPropagationAction}.
+ *
+ * @param <T>
+ */
+public abstract class BaseOIDCLogoutPropagationActionTest<T extends AbstractOIDCLogoutPropagationAction> {
+
+ protected T action;
+ protected RequestContext requestContext;
+ protected ProfileRequestContext profileRequestContext;
+ protected LogoutPropagationContext logoutPropagationContext;
+ protected OIDCLogoutPropagationContext oidcLogoutPropagationContext;
+ protected OIDCRPSession oidcRpSession;
+ protected OIDCMetadataContext oidcMetadataContext;
+ protected OIDCAuthenticationResponseContext oidcResponseContext;
+
+ 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);
+ oidcResponseContext = new OIDCAuthenticationResponseContext();
+ profileRequestContext.getOutboundMessageContext().addSubcontext(oidcResponseContext);
+ oidcLogoutPropagationContext = new OIDCLogoutPropagationContext();
+ profileRequestContext.getOutboundMessageContext().addSubcontext(oidcLogoutPropagationContext);
+ logoutPropagationContext = new LogoutPropagationContext();
+ oidcRpSession = new OIDCRPSession.Builder()
+ .creationInstant(Instant.now())
+ .expirationInstant(Instant.now().plusSeconds(60))
+ .issuer("mockIssuer")
+ .rootTokenIdentifier("mockRootTokenIdentifier")
+ .serviceId("mockClientId")
+ .sessionIdentifier("mockSessionIdentifier")
+ .subject("mockSubject")
+ .supportLogoutPropagation(true)
+ .build();
+ logoutPropagationContext.setSession(oidcRpSession);
+ profileRequestContext.addSubcontext(logoutPropagationContext);
+ }
+
+ @Test
+ public void testMissingOidcLogoutPropagationContext() {
+ action = constructAction();
+ profileRequestContext.getOutboundMessageContext().removeSubcontext(OIDCLogoutPropagationContext.class);
+ ActionTestingSupport.assertEvent(action.execute(requestContext), EventIds.INVALID_MSG_CTX);
+ }
+
+ @Test
+ public void testMissingLogoutPropagationContext() {
+ action = constructAction();
+ profileRequestContext.removeSubcontext(LogoutPropagationContext.class);
+ ActionTestingSupport.assertEvent(action.execute(requestContext), EventIds.INVALID_PROFILE_CTX);
+ }
+
+ public void testMissingOidcRpSession() {
+ action = constructAction();
+ logoutPropagationContext.setSession(null);
+ ActionTestingSupport.assertEvent(action.execute(requestContext), EventIds.INVALID_PROFILE_CTX);
+
+ }
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ExecuteBackChannelLogoutRequestTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ExecuteBackChannelLogoutRequestTest.java
new file mode 100644
index 00000000..bfee9222
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ExecuteBackChannelLogoutRequestTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.mockito.Mockito;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.security.credential.Credential;
+import org.springframework.core.io.ClassPathResource;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+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.idp.session.context.LogoutPropagationContext;
+import net.shibboleth.oidc.security.credential.BasicJWKCredentialFactoryBean;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link ExecuteBackChannelLogoutRequest}.
+ */
+public class ExecuteBackChannelLogoutRequestTest
+ extends BaseOIDCBackChannelLogoutPropagationActionTest<ExecuteBackChannelLogoutRequest> {
+
+ HttpClient mockClient = Mockito.mock(HttpClient.class);
+ Credential credential;
+
+ @BeforeClass
+ protected void setupCredential() {
+ final BasicJWKCredentialFactoryBean factory = new BasicJWKCredentialFactoryBean();
+ factory.setResource(new ClassPathResource("credentials/idp-signing-rs.jwk"));
+ try {
+ factory.afterPropertiesSet();
+ credential = factory.getObject();
+ } catch (Exception e) {
+ Assert.fail("Could not setup the signing credential", e);
+ }
+
+ }
+ protected ExecuteBackChannelLogoutRequest constructAction() {
+ final ExecuteBackChannelLogoutRequest action = new ExecuteBackChannelLogoutRequest();
+ action.setHttpClient(mockClient);
+ try {
+ action.initialize();
+ } catch (final ComponentInitializationException e) {
+ Assert.fail();
+ }
+ return action;
+ }
+
+ @Test
+ public void testMissingBackChannelLogoutUri() {
+ ActionTestingSupport.assertEvent(action.execute(requestContext), EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test
+ public void testMissingBackChannelLogoutToken() {
+ populateMetadataContext();
+ ActionTestingSupport.assertEvent(action.execute(requestContext), EventIds.INVALID_MSG_CTX);
+ }
+
+ @Test
+ public void testHttpClientThrowingException() throws IOException {
+ populateMetadataContext();
+ populateResponseContext();
+ Mockito.when(mockClient.executeOpen(Mockito.any(), (ClassicHttpRequest)Mockito.any(),
+ Mockito.any())).thenThrow(new IOException("mock"));
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertEquals(logoutPropagationContext.getResult(), LogoutPropagationContext.Result.Failure);
+ }
+
+ @Test
+ public void testHttpClientReturningOK_noRevocationError() throws IOException {
+ populateMetadataContext();
+ populateResponseContext();
+ initializeMockClient(HttpStatus.SC_OK);
+ oidcLogoutPropagationContext.setRevocationError(false);
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertEquals(logoutPropagationContext.getResult(), LogoutPropagationContext.Result.Success);
+ }
+
+ @Test
+ public void testHttpClientReturningOK_withRevocationError() throws IOException {
+ populateMetadataContext();
+ populateResponseContext();
+ initializeMockClient(HttpStatus.SC_OK);
+ oidcLogoutPropagationContext.setRevocationError(true);
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertEquals(logoutPropagationContext.getResult(), LogoutPropagationContext.Result.Failure);
+ }
+
+ @Test
+ public void testHttpClientReturningNoContent_noRevocationError() throws IOException {
+ populateMetadataContext();
+ populateResponseContext();
+ initializeMockClient(HttpStatus.SC_NO_CONTENT);
+ oidcLogoutPropagationContext.setRevocationError(false);
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertEquals(logoutPropagationContext.getResult(), LogoutPropagationContext.Result.Success);
+ }
+
+ @Test
+ public void testHttpClientReturningNoContent_withRevocationError() throws IOException {
+ populateMetadataContext();
+ populateResponseContext();
+ initializeMockClient(HttpStatus.SC_NO_CONTENT);
+ oidcLogoutPropagationContext.setRevocationError(true);
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertEquals(logoutPropagationContext.getResult(), LogoutPropagationContext.Result.Failure);
+ }
+
+ @Test
+ public void testHttpClientReturningUnexpectedCode() throws IOException {
+ populateMetadataContext();
+ populateResponseContext();
+ initializeMockClient(HttpStatus.SC_BAD_GATEWAY);
+ oidcLogoutPropagationContext.setRevocationError(false);
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertEquals(logoutPropagationContext.getResult(), LogoutPropagationContext.Result.Failure);
+ }
+
+ protected void populateMetadataContext() {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ try {
+ metadata.setBackChannelLogoutURI(new URI("https://example.org/backchannel"));
+ } catch (URISyntaxException e) {
+ Assert.fail();
+ }
+ oidcMetadataContext.setClientInformation(new OIDCClientInformation(new ClientID("mockClientId"), metadata));
+ }
+
+ protected void populateResponseContext() {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().audience("mockAudience").issuer("mockIssuer").build();
+ final SignedJWT jwt = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("id").build(),
+ claimsSet);
+ try {
+ jwt.sign(new RSASSASigner(credential.getPrivateKey()));
+ } catch (JOSEException e) {
+ Assert.fail("Could not sign the JWT", e);
+ }
+ oidcResponseContext.setProcessedToken(jwt);
+ }
+
+ protected void initializeMockClient(final int statusCode) {
+ final ClassicHttpResponse httpResponse = Mockito.mock(ClassicHttpResponse.class);
+ Mockito.when(httpResponse.getCode()).thenReturn(statusCode);
+ try {
+ Mockito.when(mockClient.executeOpen(Mockito.any(), (ClassicHttpRequest)Mockito.any(),
+ (HttpContext)Mockito.any())).thenReturn(httpResponse);
+ } catch (IOException e) {
+ Assert.fail("Could not initialize the mock HTTP client", e);
+ }
+
+ }
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormOutboundLogoutRequestMessageTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormOutboundLogoutRequestMessageTest.java
new file mode 100644
index 00000000..bf876a5f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/FormOutboundLogoutRequestMessageTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.function.Function;
+
+import org.mockito.Mockito;
+import org.opensaml.profile.context.ProfileRequestContext;
+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.plugin.oidc.op.messaging.impl.FrontChannelLogoutPropagationResponse;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link FormOutboundLogoutRequestMessage}.
+ */
+public class FormOutboundLogoutRequestMessageTest
+ extends BaseOIDCLogoutPropagationActionTest<FormOutboundLogoutRequestMessage> {
+
+ @SuppressWarnings("unchecked")
+ Function<ProfileRequestContext, String> issuerLookupStrategy = Mockito.mock(Function.class);
+
+ String frontChannelUri = "https://example.org/frontchannel";
+
+ @Override
+ protected FormOutboundLogoutRequestMessage constructAction() {
+ final FormOutboundLogoutRequestMessage action = new FormOutboundLogoutRequestMessage();
+ action.setIssuerLookupStrategy(issuerLookupStrategy);
+ try {
+ action.initialize();
+ } catch (final ComponentInitializationException e) {
+ Assert.fail();
+ }
+ return action;
+ }
+
+ @Test
+ public void testWithNoIssuer() {
+ Mockito.when(issuerLookupStrategy.apply(Mockito.any())).thenReturn(null);
+ ActionTestingSupport.assertEvent(action.execute(requestContext), IdPEventIds.INVALID_RELYING_PARTY_CTX);
+ }
+
+ @Test
+ public void testWithNoMetadata() {
+ Mockito.when(issuerLookupStrategy.apply(Mockito.any())).thenReturn("mockIssuer");
+ ActionTestingSupport.assertEvent(action.execute(requestContext), IdPEventIds.INVALID_RELYING_PARTY_CTX);
+ }
+
+ @Test
+ public void testSuccessWithoutSessionDetails() {
+ populateMetadataContext(false);
+ Mockito.when(issuerLookupStrategy.apply(Mockito.any())).thenReturn("mockIssuer");
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ final Object response = profileRequestContext.getOutboundMessageContext().getMessage();
+ Assert.assertNotNull(response);
+ Assert.assertTrue(response instanceof FrontChannelLogoutPropagationResponse);
+ final FrontChannelLogoutPropagationResponse logoutResponse = (FrontChannelLogoutPropagationResponse) response;
+ Assert.assertTrue(logoutResponse.indicatesSuccess());
+ Assert.assertEquals(logoutResponse.toHTTPResponse().getLocation().toString(), frontChannelUri);
+ }
+
+ @Test
+ public void testSuccessWithSessionDetails() {
+ populateMetadataContext(true);
+ Mockito.when(issuerLookupStrategy.apply(Mockito.any())).thenReturn("mockIssuer");
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ final Object response = profileRequestContext.getOutboundMessageContext().getMessage();
+ Assert.assertNotNull(response);
+ Assert.assertTrue(response instanceof FrontChannelLogoutPropagationResponse);
+ final FrontChannelLogoutPropagationResponse logoutResponse = (FrontChannelLogoutPropagationResponse) response;
+ Assert.assertTrue(logoutResponse.indicatesSuccess());
+ final String uri = logoutResponse.toHTTPResponse().getLocation().toString();
+ Assert.assertTrue(uri.startsWith(frontChannelUri));
+ Assert.assertTrue(uri.contains("iss=mockIssuer"));
+ Assert.assertTrue(uri.contains("sid=mockSessionIdentifier"));
+
+ }
+
+ protected void populateMetadataContext(final boolean addSession) {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ try {
+ metadata.setFrontChannelLogoutURI(new URI(frontChannelUri));
+ metadata.requiresFrontChannelLogoutSession(addSession);
+ } catch (URISyntaxException e) {
+ Assert.fail();
+ }
+ oidcMetadataContext.setClientInformation(new OIDCClientInformation(new ClientID("mockClientId"), metadata));
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PrepareBackChannelLogoutRequestTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PrepareBackChannelLogoutRequestTest.java
new file mode 100644
index 00000000..e2ca1568
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/PrepareBackChannelLogoutRequestTest.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 org.opensaml.profile.action.EventIds;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.id.Audience;
+import com.nimbusds.openid.connect.sdk.claims.LogoutTokenClaimsSet;
+
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+
+/**
+ * Unit tests for {@link PrepareBackChannelLogoutRequest}.
+ */
+public class PrepareBackChannelLogoutRequestTest
+ extends BaseOIDCBackChannelLogoutPropagationActionTest<PrepareBackChannelLogoutRequest> {
+
+ @Override
+ protected PrepareBackChannelLogoutRequest constructAction() {
+ return constructAction(new IdentifierGenerationStrategy() {
+
+ @Override
+ public String generateIdentifier() {
+ return "mockId";
+ }
+
+ @Override
+ public String generateIdentifier(boolean xmlSafe) {
+ return "mockId";
+ }
+
+ });
+ }
+
+ protected PrepareBackChannelLogoutRequest constructAction(final IdentifierGenerationStrategy strategy) {
+ final PrepareBackChannelLogoutRequest action = new PrepareBackChannelLogoutRequest();
+ action.setIdentifierGeneratorLookupStrategy(prc -> strategy);
+ try {
+ action.initialize();
+ } catch (final ComponentInitializationException e) {
+ Assert.fail();
+ }
+ return action;
+ }
+
+ @Test
+ public void testMissingIdGenerationStrategy() {
+ action = constructAction(null);
+ ActionTestingSupport.assertEvent(action.execute(requestContext), EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test
+ public void testSuccess() {
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ final LogoutTokenClaimsSet claimsSet = backChannelLogoutPropagationContext.getLogoutTokenClaimsSet();
+ Assert.assertNotNull(claimsSet);
+ Assert.assertEquals(claimsSet.getAudience().size(), 1);
+ Assert.assertTrue(claimsSet.getAudience().contains(new Audience(oidcRpSession.getId())));
+ Assert.assertEquals(claimsSet.getIssuer().getValue(), "mockIssuer");
+ Assert.assertEquals(claimsSet.getSubject().getValue(), "mockSubject");
+ Assert.assertEquals(claimsSet.getSessionID().getValue(), "mockSessionIdentifier");
+ Assert.assertEquals(claimsSet.getJWTID().getValue(), "mockId");
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/RevokeTokenChainTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/RevokeTokenChainTest.java
new file mode 100644
index 00000000..086abd70
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/RevokeTokenChainTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.time.Duration;
+import java.util.function.Function;
+
+import org.mockito.Mockito;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.storage.RevocationCache;
+import org.opensaml.storage.impl.MemoryStorageService;
+import org.opensaml.storage.impl.StorageServiceRevocationCache;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link RevokeTokenChain}.
+ */
+public class RevokeTokenChainTest extends BaseOIDCLogoutPropagationActionTest<RevokeTokenChain> {
+
+ MemoryStorageService storageService;
+ StorageServiceRevocationCache revocationCache;
+ @SuppressWarnings("unchecked")
+ Function<ProfileRequestContext,Duration> revocationLifetimeStrategy = Mockito.mock(Function.class);
+
+ @Override
+ protected RevokeTokenChain constructAction() {
+ try {
+ storageService = new MemoryStorageService();
+ storageService.setId("test");
+ storageService.initialize();
+
+ revocationCache = new StorageServiceRevocationCache();
+ revocationCache.setId("mockCache");
+ revocationCache.setEntryExpiration(Duration.ofHours(1));
+ revocationCache.setStorage(storageService);
+ revocationCache.initialize();
+ } catch (final ComponentInitializationException e) {
+ Assert.fail();
+ }
+ return constructAction(revocationCache);
+ }
+
+ protected RevokeTokenChain constructAction(final RevocationCache revocationCache) {
+ final RevokeTokenChain action = new RevokeTokenChain();
+ action.setChainRevocationLifetimeLookupStrategy(prc -> Duration.ofHours(2));
+ action.setRevocationCache(revocationCache);
+ action.setChainRevocationLifetimeLookupStrategy(revocationLifetimeStrategy);
+ try {
+ action.initialize();
+ } catch (final ComponentInitializationException e) {
+ Assert.fail();
+ }
+ return action;
+ }
+
+ @Test
+ public void testWithNullLifetime() {
+ Mockito.when(revocationLifetimeStrategy.apply(Mockito.any())).thenReturn(null);
+ ActionTestingSupport.assertEvent(action.execute(requestContext), IdPEventIds.INVALID_PROFILE_CONFIG);
+ }
+
+ @Test
+ public void testWithZeroLifetime() {
+ Mockito.when(revocationLifetimeStrategy.apply(Mockito.any())).thenReturn(Duration.ZERO);
+ ActionTestingSupport.assertEvent(action.execute(requestContext), IdPEventIds.INVALID_PROFILE_CONFIG);
+ }
+
+ @Test
+ public void testWithPositiveLifetime() {
+ Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE,
+ "mockRootTokenIdentifier"));
+ Mockito.when(revocationLifetimeStrategy.apply(Mockito.any())).thenReturn(Duration.ofHours(2));
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertFalse(oidcLogoutPropagationContext.hasRevocationError());
+ Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE,
+ "mockRootTokenIdentifier"));
+ }
+
+ @Test
+ public void testWithPositiveLifetimeRevocationFailed() {
+ RevocationCache revocationCache = Mockito.mock(RevocationCache.class);
+ Mockito.when(revocationCache.revoke(Mockito.anyString(), Mockito.anyString(), Mockito.any(Duration.class)))
+ .thenReturn(false);
+ action = constructAction(revocationCache);
+ Mockito.when(revocationLifetimeStrategy.apply(Mockito.any())).thenReturn(Duration.ofHours(2));
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertTrue(oidcLogoutPropagationContext.hasRevocationError());
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/SetFrontChannelLogoutSuccessTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/SetFrontChannelLogoutSuccessTest.java
new file mode 100644
index 00000000..87c0ff14
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/SetFrontChannelLogoutSuccessTest.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.testng.Assert;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.session.context.LogoutPropagationContext;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link SetFrontChannelLogoutSuccess}.
+ */
+public class SetFrontChannelLogoutSuccessTest
+ extends BaseOIDCLogoutPropagationActionTest<SetFrontChannelLogoutSuccess> {
+
+ @Override
+ protected SetFrontChannelLogoutSuccess constructAction() {
+ final SetFrontChannelLogoutSuccess action = new SetFrontChannelLogoutSuccess();
+ try {
+ action.initialize();
+ } catch (final ComponentInitializationException e) {
+ Assert.fail();
+ }
+ return action;
+ }
+
+ @Test
+ public void test() {
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertEquals(logoutPropagationContext.getResult(), LogoutPropagationContext.Result.Success);
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list