[java-oidc-common] branch main updated: JCOMOIDC-176 - Add logout request handlers

Codeberg noreply at shibboleth.net
Fri Jul 3 13:48:23 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-oidc-common.

View the commit online:
https://codeberg.org/Shibboleth/java-oidc-common/commit/b03a000471defa02ee3165bd0a40804740c8861c

The following commit(s) were added to refs/heads/main by this push:
     new b03a0004 JCOMOIDC-176 - Add logout request handlers
b03a0004 is described below

commit b03a000471defa02ee3165bd0a40804740c8861c
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jul 3 14:48:12 2026 +0100

    JCOMOIDC-176 - Add logout request handlers
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-176
---
 ...tLogoutRequestParameterValueMessageHandler.java | 204 +++++++++++++++++++++
 .../messaging/handler/impl/AddClientIDHandler.java |  52 ++++++
 .../handler/impl/AddIDTokenHintHandler.java        |  91 +++++++++
 .../handler/impl/AddLogoutHintHandler.java         |  54 ++++++
 .../handler/impl/AddLogoutUILocalesHandler.java    |  68 +++++++
 .../impl/AddPostLogoutRedirectURIHandler.java      |  55 ++++++
 6 files changed, 524 insertions(+)

diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AbstractLogoutRequestParameterValueMessageHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AbstractLogoutRequestParameterValueMessageHandler.java
new file mode 100644
index 00000000..a3094b44
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AbstractLogoutRequestParameterValueMessageHandler.java
@@ -0,0 +1,204 @@
+/*
+ * 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.oidc.profile.messaging.handler.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.core.OIDCLogoutRequest;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+
+/** 
+ * Base class for message handlers that process and apply values of OpenID Connect logout requests.
+ * 
+ * <p>
+ * This abstract class provides common functionality for locating:
+ * </p>
+ * <ul>
+ *   <li>the {@link OIDCLogoutRequest} associated with the current
+ *       {@link MessageContext},</li>
+ *   <li>the {@link OIDCProviderMetadata} describing the peer OpenID Provider,</li>
+ *   <li>and the parameter value to be extracted and validated against
+ *       the expected Java type.</li>
+ * </ul>
+ * 
+ * @param <T> the logout request parameter value type
+ *
+ * TODO: move to commons
+ */
+public abstract class AbstractLogoutRequestParameterValueMessageHandler<T> extends AbstractMessageHandler {
+    
+    /** Lookup function for parent ProfileRequestContext. */
+    @Nonnull protected static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
+        = new ParentProfileRequestContextLookup<>();
+    
+    /** Strategy used to locate the {@link OIDCLogoutRequest}.  */
+    @NonnullAfterInit private Function<MessageContext, OIDCLogoutRequest> logoutRequestLookupStrategy;
+    
+    /** Lookup strategy to locate the OpenID Provider metadata to use.*/
+    @NonnullAfterInit private Function<MessageContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+    
+    /** Lookup strategy for parameter value. */
+    @Nullable private Function<MessageContext,T> parameterValueLookupStrategy;
+    
+    /** The logout request parameter value type.*/
+    @Nonnull private final Class<T> type;
+    
+    /** The stashed {@link OIDCLogoutRequest}.*/
+    @NonnullBeforeExec private OIDCLogoutRequest logoutRequest;  
+    
+    /** The stashed OpenID Provider metadata .*/
+    @NonnullBeforeExec private OIDCProviderMetadata providerMetadata;
+    
+    
+    /**
+     * Constructor.
+     * 
+     * @param valueType type of value returned by handler
+     */
+    protected AbstractLogoutRequestParameterValueMessageHandler(@Nonnull final Class<T> valueType) {
+        type = Constraint.isNotNull(valueType, "Logout request parameter value type cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (logoutRequestLookupStrategy == null) {
+            throw new ComponentInitializationException("LogoutRequestLookupStrategy cannot be null");
+        }
+        if (providerMetadataLookupStrategy == null) {
+            throw new ComponentInitializationException("ProviderMetadataLookupStrategy cannot be null");
+        }
+    }
+    
+    /**
+     * Get the logout request.
+     * 
+     * @return the logout request
+     */
+    @NonnullBeforeExec protected OIDCLogoutRequest getLogoutRequest() {
+        return logoutRequest;
+    }
+    
+    /**
+     * Set the lookup strategy to locate the OpenID providers metadata.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setProviderMetadataLookupStrategy(
+            @Nonnull final Function<MessageContext, OIDCProviderMetadataContext> strategy) {
+        checkSetterPreconditions();
+        
+        providerMetadataLookupStrategy = 
+                Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+    }
+    
+    /**
+     * Returns the OpenID Provider metadata. Should never be {@code null} after
+     * {@code doPreExecute} has been called.
+     * 
+     * @return The provider metadata context.
+     */
+    @NonnullBeforeExec protected OIDCProviderMetadata getProviderMetadata() {
+        return providerMetadata;
+    }
+    
+    /**
+     * Set the parameter value lookup strategy used to find the value to set onto the logout request.
+     * 
+     * @param strategy The parameter value lookup strategy to set.
+     */
+    public void setParameterValueLookupStrategy(@Nonnull final Function<MessageContext, T> strategy) {
+        checkSetterPreconditions();
+        parameterValueLookupStrategy = Constraint.isNotNull(strategy,
+                "ParameterValueLookupStrategy can not be null");
+    }
+    
+    /**
+     * Retrieves the parameter value or configuration options from the configured lookup strategy, 
+     * verifying at runtime that the result matches the type expected by the subclass.
+     *  
+     * @param context the message context to pass to the lookup function
+     * 
+     * @return the parameter value
+     * 
+     * @throws MessageHandlerException if the value is not the expected type
+     */
+    @Nullable protected T getParameterValue(@Nonnull final MessageContext context) 
+            throws MessageHandlerException {
+        final var localParameterValueLookupStrategy = parameterValueLookupStrategy;
+        if (localParameterValueLookupStrategy == null) {
+            return null;
+        }
+        final Object value = localParameterValueLookupStrategy.apply(context);
+        if (value == null) {
+            return null;
+        }
+        if (type.isInstance(value)) {
+            return type.cast(value);
+        }
+        throw new MessageHandlerException("Logout request parameter value lookup returned the "
+                + "wrong value type");
+    }
+    
+    /**
+     * Set the strategy used to locate the {@link OIDCLogoutRequest} to use. 
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setLogoutRequestLookupStrategy(
+            @Nonnull final Function<MessageContext, OIDCLogoutRequest> strategy) {
+        checkSetterPreconditions();
+        
+        logoutRequestLookupStrategy =
+                Constraint.isNotNull(strategy, "LogoutContext lookup strategy cannot be null");
+    }
+    
+    @Override
+    protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        logoutRequest = logoutRequestLookupStrategy.apply(messageContext);
+        if (logoutRequest == null) {
+            throw new MessageHandlerException("OIDC logout request is null");
+        }
+        final OIDCProviderMetadataContext providerMetadataContext = 
+                providerMetadataLookupStrategy.apply(messageContext);
+        if (providerMetadataContext == null) {
+            throw new MessageHandlerException("No provider metadata context found for peer");
+        }
+        providerMetadata = providerMetadataContext.getProviderInformation();
+        if (providerMetadata == null) {
+            throw new MessageHandlerException("No provider metadata found for peer");
+        }
+        
+        return super.doPreInvoke(messageContext);
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddClientIDHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddClientIDHandler.java
new file mode 100644
index 00000000..dd9707bf
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddClientIDHandler.java
@@ -0,0 +1,52 @@
+/*
+ * 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.oidc.profile.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A message handler that populates the client_id parameter into the logout request.
+ */
+public class AddClientIDHandler extends AbstractLogoutRequestParameterValueMessageHandler<String> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddClientIDHandler.class);
+    
+    /** Constructor.*/
+    public AddClientIDHandler() {
+        super(String.class);
+    }    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final String clientId = getParameterValue(messageContext);
+        if (StringSupport.trimOrNull(clientId) == null) {
+            return;
+        }
+        getLogoutRequest().setClientID(new ClientID(clientId));
+        log.trace("{}: Set client_id to '{}'", getLogPrefix(), clientId);          
+    }
+}
\ No newline at end of file
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddIDTokenHintHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddIDTokenHintHandler.java
new file mode 100644
index 00000000..dae52338
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddIDTokenHintHandler.java
@@ -0,0 +1,91 @@
+/*
+ * 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.oidc.profile.messaging.handler.impl;
+
+import java.text.ParseException;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the id_token_hint parameter into the logout request.
+ */
+public class AddIDTokenHintHandler extends AbstractLogoutRequestParameterValueMessageHandler<JWT> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddIDTokenHintHandler.class);
+    
+    /** Constructor.*/
+    public AddIDTokenHintHandler() {
+        super(JWT.class);
+    }    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final JWT idTokenHint = getParameterValue(messageContext);
+        if (idTokenHint == null) {
+            return;
+        }
+        if (idTokenHint instanceof EncryptedJWT) {
+            log.trace("{}: ID Token is still encrypted, possibly for the wrong target, this maybe ignored by "
+                    + "the OP", getLogPrefix());
+        }
+        getLogoutRequest().setIdTokenHint(idTokenHint);
+        if (log.isTraceEnabled()) {
+            log.trace("{}: Set id_token_hint for subject '{}'", getLogPrefix(), 
+                    getSubjectOrDefault(idTokenHint,"not-available"));
+        }
+        
+    }
+    
+    /**
+     * Get the subject from the id_token, else return the default value.
+     * 
+     * @param idToken the id_token to extract the subject from
+     * @param defaultSubject the default subject to return if the subject can not be extracted
+     * 
+     * @return the subject, either from the id_token or the default value
+     */
+    private String getSubjectOrDefault(final JWT idToken, final String defaultSubject) {
+
+        if (idToken instanceof EncryptedJWT) {
+            return defaultSubject;
+        }
+
+        try {
+            final JWTClaimsSet claims = idToken.getJWTClaimsSet();
+            if (claims != null) {
+                return claims.getSubject() != null ? claims.getSubject() : defaultSubject;
+            }
+        } catch (final ParseException e) {
+            // Ignore
+        }
+        return defaultSubject;
+        
+    }
+
+
+}
\ No newline at end of file
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddLogoutHintHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddLogoutHintHandler.java
new file mode 100644
index 00000000..125b3f77
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddLogoutHintHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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.oidc.profile.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A message handler that populates the logout_hint parameter into the logout request.
+ */
+public class AddLogoutHintHandler extends AbstractLogoutRequestParameterValueMessageHandler<String> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddLogoutHintHandler.class);
+    
+    /** Constructor.*/
+    public AddLogoutHintHandler() {
+        super(String.class);
+    }    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final String logoutHint = getParameterValue(messageContext);
+        if (StringSupport.trimOrNull(logoutHint) == null) {
+            return;
+        }
+        getLogoutRequest().setLogoutHint(logoutHint);
+        log.trace("{}: Set logout_hint to '{}'", getLogPrefix(), logoutHint);
+        
+    }
+    
+
+
+}
\ No newline at end of file
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddLogoutUILocalesHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddLogoutUILocalesHandler.java
new file mode 100644
index 00000000..578c5f8a
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddLogoutUILocalesHandler.java
@@ -0,0 +1,68 @@
+/*
+ * 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.oidc.profile.messaging.handler.impl;
+
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.langtag.LangTag;
+import com.nimbusds.langtag.LangTagException;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the client_id parameter into the logout request.
+ */
+public class AddLogoutUILocalesHandler extends AbstractLogoutRequestParameterValueMessageHandler<List<String>> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddLogoutUILocalesHandler.class);
+
+    /** Constructor.*/
+    public AddLogoutUILocalesHandler() {
+        super((Class)List.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        final List<String> locales = getParameterValue(messageContext);
+        if (locales != null && !locales.isEmpty()) {
+            if (log.isTraceEnabled()) {
+                log.trace("{} Setting 'ui_locales={}'", getLogPrefix(), locales);
+            }
+            final List<LangTag> uiLocals = locales.stream().map(tag -> {
+                try {
+                    return LangTag.parse(tag);
+                } catch (final LangTagException e) {
+                    log.warn("Can not parse language tag '{}'", tag);
+                }
+                return null;
+            }).filter(Objects::nonNull).toList();
+            
+            getLogoutRequest().setUiLocales(uiLocals);            
+        }
+    }
+    
+
+
+}
\ No newline at end of file
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddPostLogoutRedirectURIHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddPostLogoutRedirectURIHandler.java
new file mode 100644
index 00000000..4c91b553
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddPostLogoutRedirectURIHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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.oidc.profile.messaging.handler.impl;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A message handler that populates the client_id parameter into the logout request.
+ */
+public class AddPostLogoutRedirectURIHandler extends AbstractLogoutRequestParameterValueMessageHandler<URI> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddPostLogoutRedirectURIHandler.class);
+    
+    /** Constructor.*/
+    public AddPostLogoutRedirectURIHandler() {
+        super(URI.class);
+    }    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        final URI postRedirectUri = getParameterValue(messageContext);
+        if (postRedirectUri == null) {
+            return;
+        }
+        getLogoutRequest().setPostLogoutRedirectURI(postRedirectUri);
+        log.trace("{}: Set post_logout_redirect_uri to '{}'", getLogPrefix(), postRedirectUri);
+        
+    }
+    
+
+
+}
\ No newline at end of file

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


More information about the commits mailing list