[java-idp-oidc] 01/03: JOIDC-13 - Support for OIDC Logout

Henri Mikkonen henri.mikkonen at iki.fi
Fri Nov 24 16:18:25 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=bf558a6486d24ca720b66cc04970a5abcd89221a

commit bf558a6486d24ca720b66cc04970a5abcd89221a
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Nov 24 13:53:51 2023 +0200

    JOIDC-13 - Support for OIDC Logout
    
    https://shibboleth.atlassian.net/browse/JOIDC-13
    
    Refactored OIDCRPSession to be compatible with implicit flow (i.e. no authorization code involved)
    - Root token identifier is now optional
    
    Removed global bean 'shibboleth.oidc.OIDCRPSessionCreationStrategy' and restored using
    'DefaultSPSessionCreationStrategy' in authorize flow beans.
---
 .../idp/plugin/oidc/op/session/OIDCRPSession.java  |   6 +-
 .../op/logout/profile/impl/RevokeTokenChain.java   |   3 +-
 .../impl/OIDCRPSessionCreationStrategy.java        | 142 ++++++++--
 .../op/session/impl/OIDCRPSessionSerializer.java   |   4 +-
 .../META-INF/net.shibboleth.idp/postconfig.xml     |   4 -
 .../idp/flows/oidc/authorize/authorize-beans.xml   |   3 +-
 .../impl/OIDCRPSessionCreationStrategyTest.java    | 293 +++++++++++++++++++++
 7 files changed, 425 insertions(+), 30 deletions(-)

diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/OIDCRPSession.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/OIDCRPSession.java
index 33ab9d96..7021a0f8 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/OIDCRPSession.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/OIDCRPSession.java
@@ -37,7 +37,7 @@ public class OIDCRPSession extends BasicSPSession implements SPSession {
     @Nonnull @NotEmpty private final String issuer;
 
     /** The root token identifier. */
-    @Nonnull @NotEmpty private final String rootTokenIdentifier;
+    @Nullable private final String rootTokenIdentifier;
 
     /** The session identifier. */
     @Nonnull @NotEmpty private final String sessionIdentifier;
@@ -62,11 +62,11 @@ public class OIDCRPSession extends BasicSPSession implements SPSession {
      */
     private OIDCRPSession(@Nonnull @NotEmpty final String id, @Nonnull final Instant creation,
             @Nonnull final Instant expiration, @Nonnull @NotEmpty final String iss,
-            @Nonnull @NotEmpty final String rootJti, @Nonnull @NotEmpty final String sid,
+            @Nullable final String rootJti, @Nonnull @NotEmpty final String sid,
             @Nonnull @NotEmpty final String sub, final boolean supportsPropagation) {
         super(id, creation, expiration);
         issuer = Constraint.isNotEmpty(iss, "The issuer value cannot be empty");
-        rootTokenIdentifier = Constraint.isNotEmpty(rootJti, "The root token identifier cannot be empty");
+        rootTokenIdentifier = rootJti;
         sessionIdentifier = Constraint.isNotEmpty(sid, "The session identifier cannot be empty");
         subject = Constraint.isNotEmpty(sub, "The subject value cannot be empty");
         supportsLogoutPropagation = supportsPropagation;
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 0933bea3..4e8c88d2 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
@@ -130,8 +130,7 @@ public class RevokeTokenChain extends AbstractOIDCLogoutPropagationAction {
 
         rootTokenIdentifier = ((OIDCRPSession) logoutPropagationContext.getSession()).getRootTokenIdentifier();
         if (rootTokenIdentifier == null) {
-            log.error("{} OIDCRPSession context did not contain a root json web token identifier.", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            log.debug("{} OIDCRPSession context did not contain a root jti, nothing to revoke.", getLogPrefix());
             return false;
         }
 
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionCreationStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionCreationStrategy.java
index 9dc90880..0ee73b98 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionCreationStrategy.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionCreationStrategy.java
@@ -22,26 +22,32 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultClientIDLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.OIDCAuthenticationResponseContextLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
 import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
 import net.shibboleth.idp.session.SPSession;
 import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
  * A function to create a {@link OIDCRPSession} based on profile execution state.
  */
-public class OIDCRPSessionCreationStrategy implements Function<ProfileRequestContext, SPSession> {
+public class OIDCRPSessionCreationStrategy extends AbstractInitializableComponent
+        implements Function<ProfileRequestContext, SPSession> {
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(OIDCRPSessionCreationStrategy.class);
@@ -50,11 +56,14 @@ public class OIDCRPSessionCreationStrategy implements Function<ProfileRequestCon
     @Nonnull private final Duration sessionLifetime;
 
     /** Lookup strategy for OIDC metadata context. */
-    @Nonnull private final Function<ProfileRequestContext, OIDCMetadataContext> oidcMetadataContextLookupFunction;
+    @Nonnull private Function<ProfileRequestContext, OIDCMetadataContext> oidcMetadataContextLookupStrategy;
 
-    /** Lookup strategy for OIDC authenticatin response context. */
+    /** Lookup strategy for OIDC authentication response context. */
     @Nonnull
-    private final Function<ProfileRequestContext, OIDCAuthenticationResponseContext> oidcResponseContextLookupStrategy;
+    private Function<ProfileRequestContext, OIDCAuthenticationResponseContext> oidcResponseContextLookupStrategy;
+
+    /** Lookup strategy used to obtain the client id value. */
+    @Nonnull private Function<ProfileRequestContext, ClientID> clientIDLookupStrategy;
 
     /**
      * Constructor.
@@ -63,8 +72,39 @@ public class OIDCRPSessionCreationStrategy implements Function<ProfileRequestCon
      */
     public OIDCRPSessionCreationStrategy(@Nonnull final Duration lifetime) {
         sessionLifetime = Constraint.isNotNull(lifetime, "Lifetime cannot be null");
-        oidcMetadataContextLookupFunction = new DefaultOIDCMetadataContextLookupFunction();
+        oidcMetadataContextLookupStrategy = new DefaultOIDCMetadataContextLookupFunction();
         oidcResponseContextLookupStrategy = new OIDCAuthenticationResponseContextLookupFunction();
+        clientIDLookupStrategy = FunctionSupport.compose(new DefaultClientIDLookupFunction(),
+                new InboundMessageContextLookup());
+    }
+
+    /**
+     * Set the lookup strategy for OIDC metadata context.
+     * @param strategy lookup strategy
+     */
+    public void setOidcMetadataContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCMetadataContext> strategy) {
+        checkSetterPreconditions();
+        oidcMetadataContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the lookup strategy for OIDC authentication response context.
+     * @param strategy lookup strategy
+     */
+    public void setOidcResponseContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OIDCAuthenticationResponseContext> strategy) {
+        checkSetterPreconditions();
+        oidcResponseContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the lookup strategy used to obtain the client id value.
+     * @param strategy lookup strategy
+     */
+    public void setClientIDLookupStrategy(@Nonnull final Function<ProfileRequestContext, ClientID> strategy) {
+        checkSetterPreconditions();
+        clientIDLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
     }
 
     /** {@inheritDoc} */
@@ -78,34 +118,41 @@ public class OIDCRPSessionCreationStrategy implements Function<ProfileRequestCon
         }
 
         final TokenClaimsSet tokenClaimsSet = oidcAuthenticationResponseContext.getAuthorizationGrantClaimsSet();
-        if (tokenClaimsSet == null) {
-            log.debug("AuthorizationGrantClaimsSet cannot be null, no OIDCRPSession created.");
-            return null;
-        }
+        final IDTokenClaimsSet idTokenClaimsSet = oidcAuthenticationResponseContext.getIDToken();
 
-        final ClientID clientIdentifier = tokenClaimsSet.getClientID();
+        final ClientID clientIdentifier = clientIDLookupStrategy.apply(profileRequestContext);
         if (clientIdentifier == null || StringSupport.trimOrNull(clientIdentifier.getValue()) == null) {
             log.debug("ClientID/RelyingPartyId cannot be null, no OIDCRPSession created.");
             return null;
         }
 
         final Instant now = Instant.now();
-        final String issuer = tokenClaimsSet.getIssuer();
-
-        final String rootTokenIdentifier = tokenClaimsSet.getRootTokenIdentifier() != null 
-                ? tokenClaimsSet.getRootTokenIdentifier() : tokenClaimsSet.getID();
-        if (rootTokenIdentifier == null) {
-            log.debug("RootTokenIdentifier (root jti) cannot be null, no OIDCRPSession created.");
+        final String issuer = getIssuer(tokenClaimsSet, idTokenClaimsSet);
+        if (issuer == null) {
+            log.debug("Issuer cannot be null, no OIDCRPSession created.");
             return null;
         }
 
-        final String sessionIdentifier = tokenClaimsSet.getSessionIdentifier();
+        final String rootTokenIdentifier;
+        if (tokenClaimsSet != null) {
+            rootTokenIdentifier = tokenClaimsSet.getRootTokenIdentifier() != null 
+                    ? tokenClaimsSet.getRootTokenIdentifier() : tokenClaimsSet.getID();
+            if (rootTokenIdentifier == null) {
+                log.debug("RootTokenIdentifier (root jti) cannot be null in the authorization grant context");
+                return null;
+            }
+        } else {
+            log.debug("RootTokenIdentifier won't be set to OIDCRPSession");
+            rootTokenIdentifier = null;
+        }
+
+        final String sessionIdentifier = getSessionIdentifier(tokenClaimsSet, idTokenClaimsSet);
         if (sessionIdentifier == null) {
             log.debug("SessionIdentifier cannot be null, no OIDCRPSession created.");
             return null;
         }
 
-        final String subject = tokenClaimsSet.getSubject();
+        final String subject = getSubject(tokenClaimsSet, idTokenClaimsSet);
         if (subject == null) {
             log.debug("Subject cannot be null, no OIDCRPSession created.");
             return null;
@@ -125,6 +172,63 @@ public class OIDCRPSessionCreationStrategy implements Function<ProfileRequestCon
                 .build();
     }
 
+    /**
+     * Get session identifier from the given token claims set if found, and from the given ID token claims set if not.
+     * 
+     * @param tokenClaimsSet token claims set used as a primary source
+     * @param idTokenClaimsSet ID token claims set used as a secondary source
+     * @return session identifier value, or null if not found from neither of the sources
+     */
+    @Nullable
+    protected String getSessionIdentifier(@Nullable final TokenClaimsSet tokenClaimsSet,
+            @Nullable final IDTokenClaimsSet idTokenClaimsSet) {
+        if (tokenClaimsSet != null) {
+            return tokenClaimsSet.getSessionIdentifier();
+        }
+        if (idTokenClaimsSet != null && idTokenClaimsSet.getSessionID() != null) {
+            return idTokenClaimsSet.getSessionID().getValue();
+        }
+        return null;
+    }
+
+    /**
+     * Get issuer from the given token claims set if found, and from the given ID token claims set if not.
+     * 
+     * @param tokenClaimsSet token claims set used as a primary source
+     * @param idTokenClaimsSet ID token claims set used as a secondary source
+     * @return issuer value, or null if not found from neither of the sources
+     */
+    @Nullable
+    protected String getIssuer(@Nullable final TokenClaimsSet tokenClaimsSet,
+            @Nullable final IDTokenClaimsSet idTokenClaimsSet) {
+        if (tokenClaimsSet != null) {
+            return tokenClaimsSet.getIssuer();
+        }
+        if (idTokenClaimsSet != null && idTokenClaimsSet.getIssuer() != null) {
+            return idTokenClaimsSet.getIssuer().getValue();
+        }
+        return null;
+    }
+
+    /**
+     * Get subject from the given token claims set if found, and from the given ID token claims set if not.
+     * 
+     * @param tokenClaimsSet token claims set used as a primary source
+     * @param idTokenClaimsSet ID token claims set used as a secondary source
+     * @return subject value, or null if not found from neither of the sources
+     */
+    @Nullable
+    protected String getSubject(@Nullable final TokenClaimsSet tokenClaimsSet,
+            @Nullable final IDTokenClaimsSet idTokenClaimsSet) {
+        if (tokenClaimsSet != null) {
+            return tokenClaimsSet.getSubject();
+        }
+        if (idTokenClaimsSet != null && idTokenClaimsSet.getSubject() != null) {
+            return idTokenClaimsSet.getSubject().getValue();
+        }
+        return null;
+    }
+
     /**
      * Check if the {@link OIDCClientMetadata} attached to the given PRC contains a front- or back-channel logout URI.
      * 
@@ -132,7 +236,7 @@ public class OIDCRPSessionCreationStrategy implements Function<ProfileRequestCon
      * @return true iff the attached metadata contains a front- or back-channel logout URI
      */
     protected boolean isSupportsLogoutPropagation(@Nonnull final ProfileRequestContext profileRequestContext) {
-        final OIDCMetadataContext oidcMetadataContext = oidcMetadataContextLookupFunction.apply(profileRequestContext);
+        final OIDCMetadataContext oidcMetadataContext = oidcMetadataContextLookupStrategy.apply(profileRequestContext);
         if (oidcMetadataContext == null || oidcMetadataContext.getClientInformation() == null) {
             log.debug("No metadata found, does not support propagation");
             return false;
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionSerializer.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionSerializer.java
index ca62b077..14902ec0 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionSerializer.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionSerializer.java
@@ -54,7 +54,9 @@ public class OIDCRPSessionSerializer extends AbstractSPSessionSerializer {
         generator.write(TokenClaimsSet.KEY_SESSION_ID, oidcRpSession.getSessionIdentifier());
         generator.write(TokenClaimsSet.KEY_ISSUER, oidcRpSession.getIssuer());
         generator.write(TokenClaimsSet.KEY_SUBJECT, oidcRpSession.getSubject());
-        generator.write(TokenClaimsSet.KEY_ROOT_JTI, oidcRpSession.getRootTokenIdentifier());
+        if (oidcRpSession.getRootTokenIdentifier() != null) {
+            generator.write(TokenClaimsSet.KEY_ROOT_JTI, oidcRpSession.getRootTokenIdentifier());
+        }
         generator.write(LOGOUT_PROP_FIELD, oidcRpSession.supportsLogoutPropagation());
     }
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index b74e6067..0e158895 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -741,10 +741,6 @@
     <bean id="shibboleth.oidc.Conditions.MetadataValueEquals"
         class="net.shibboleth.idp.plugin.oidc.op.profile.logic.MetadataValueEqualsCondition" abstract="true" />
 
-    <bean id="shibboleth.oidc.OIDCRPSessionCreationStrategy"
-          class="net.shibboleth.idp.plugin.oidc.op.session.impl.OIDCRPSessionCreationStrategy"
-          c:lifetime="%{idp.session.defaultSPlifetime:PT2H}"/>
-
     <bean id="logoutprop/oidc" class="net.shibboleth.idp.session.LogoutPropagationFlowDescriptor"
           c:_0="net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession"/>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 4bfabc8f..5e4f05b8 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -877,7 +877,8 @@
             p:sessionManager-ref="shibboleth.SessionManager"
             p:SPSessionCreationStrategy-ref="%{idp.oidc.SPSessionCreationStrategy:DefaultSPSessionCreationStrategy}"/>
 
-    <bean id="DefaultSPSessionCreationStrategy" class="net.shibboleth.idp.saml.session.impl.SAML2SPSessionCreationStrategy"
+    <bean id="DefaultSPSessionCreationStrategy"
+        class="net.shibboleth.idp.plugin.oidc.op.session.impl.OIDCRPSessionCreationStrategy"
         c:lifetime="%{idp.session.defaultSPlifetime:PT2H}"/>
 
     <bean id="FormOutboundMessage"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionCreationStrategyTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionCreationStrategyTest.java
new file mode 100644
index 00000000..f7af3a34
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/session/impl/OIDCRPSessionCreationStrategyTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.session.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.oauth2.sdk.id.Audience;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.oauth2.sdk.id.Subject;
+import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.SessionID;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.session.OIDCRPSession;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.idp.session.SPSession;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link OIDCRPSessionCreationStrategy}.
+ */
+public class OIDCRPSessionCreationStrategyTest {
+
+    OIDCRPSessionCreationStrategy function;
+    
+    private String clientId = "mockClientId";
+    private String sessionIdentifier = "mockSid";
+    private String subject = "mockSubject";
+    private String issuer = "mockIssuer";
+    private String rootId = "mockRootId";
+    private String jwtId = "mockJwtId";
+
+    public void setup() {
+        setup(Duration.ofDays(1), null, null, null);
+    }
+
+    public void setup(final Duration lifetime, final OIDCMetadataContext metadataContext,
+            final OIDCAuthenticationResponseContext responseContext, final String clientId) {
+        function = new OIDCRPSessionCreationStrategy(lifetime);
+        if (metadataContext != null) {
+            function.setOidcMetadataContextLookupStrategy(prc -> metadataContext);
+        }
+        if (responseContext != null) {
+            function.setOidcResponseContextLookupStrategy(prc -> responseContext);
+        }
+        if (clientId != null) {
+            function.setClientIDLookupStrategy(prc -> new ClientID(clientId));
+        }
+        try {
+            function.initialize();
+        } catch (final ComponentInitializationException e) {
+            Assert.fail();
+        }
+    }
+
+    @Test
+    public void nullPRC_shouldReturnNull() {
+        setup();
+        Assert.assertNull(function.apply(null));
+    }
+
+    @Test
+    public void emptyPRC_shouldReturnNull() {
+        setup();
+        Assert.assertNull(function.apply(new ProfileRequestContext()));
+    }
+
+    @Test
+    public void withTokenClaimsSet_noIssuer_shouldReturnNull() {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .subject(subject)
+                .claim("sid", sessionIdentifier)
+                .claim("root_jti", rootId)
+                .build();
+        final TokenClaimsSet tokenClaimsSet = new TokenClaimsSet(claimsSet) { };
+        setup(Duration.ofDays(1), null, mockResponseContext(tokenClaimsSet, null), clientId);
+        Assert.assertNull(function.apply(new ProfileRequestContext()));
+    }
+
+    @Test
+    public void withTokenClaimsSet_noRootNorJti_shouldReturnNull() {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer(issuer)
+                .claim("sid", sessionIdentifier)
+                .subject(subject)
+                .build();
+        final TokenClaimsSet tokenClaimsSet = new TokenClaimsSet(claimsSet) { };
+        setup(Duration.ofDays(1), null, mockResponseContext(tokenClaimsSet, null), clientId);
+        Assert.assertNull(function.apply(new ProfileRequestContext()));
+    }
+
+    @Test
+    public void withTokenClaimsSet_noSubject_shouldReturnNull() {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer(issuer)
+                .claim("sid", sessionIdentifier)
+                .claim("root_jti", rootId)
+                .build();
+        final TokenClaimsSet tokenClaimsSet = new TokenClaimsSet(claimsSet) { };
+        setup(Duration.ofDays(1), null, mockResponseContext(tokenClaimsSet, null), clientId);
+        Assert.assertNull(function.apply(new ProfileRequestContext()));
+    }
+
+    @Test
+    public void withTokenClaimsSet_noSessionIdentifier_shouldReturnNull() {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer(issuer)
+                .subject(subject)
+                .jwtID(jwtId)
+                .build();
+        final TokenClaimsSet tokenClaimsSet = new TokenClaimsSet(claimsSet) { };
+        setup(Duration.ofDays(1), null, mockResponseContext(tokenClaimsSet, null), clientId);
+        Assert.assertNull(function.apply(new ProfileRequestContext()));
+    }
+
+    @Test
+    public void withIdTokenClaimsSet_noSessionIdentifier_shouldReturnNull() {
+        final IDTokenClaimsSet claimsSet = new IDTokenClaimsSet(new Issuer(issuer), new Subject(subject),
+                List.of(new Audience(clientId)), Date.from(Instant.now().plusSeconds(60)), Date.from(Instant.now()));
+        setup(Duration.ofDays(1), null, mockResponseContext(null, claimsSet), clientId);
+        Assert.assertNull(function.apply(new ProfileRequestContext()));
+    }
+
+    @Test
+    public void withValidTokenClaimsSet_noClientId_shouldReturnNull() {
+        setup(Duration.ofDays(1), null, mockResponseContext(validTokenClaimsSet(), null), null);
+        Assert.assertNull(function.apply(new ProfileRequestContext()));
+    }
+
+    @Test
+    public void withValidIdTokenClaimsSet_noClientId_shouldReturnNull() {
+        setup(Duration.ofDays(1), null, mockResponseContext(null, validIdTokenClaimsSet()), null);
+        Assert.assertNull(function.apply(new ProfileRequestContext()));
+    }
+    
+    @Test
+    public void withValidTokenClaimsSet_noMetadata_shouldReturnNoPropagationSession() {
+        setup(Duration.ofDays(1), null, mockResponseContext(validTokenClaimsSet(), null), clientId);
+        assertSession(function.apply(new ProfileRequestContext()), false, jwtId);
+    }
+
+    @Test
+    public void withValidIdTokenClaimsSet_noMetadata_shouldReturnNoPropagationSession() {
+        setup(Duration.ofDays(1), null, mockResponseContext(null, validIdTokenClaimsSet()), clientId);
+        assertSession(function.apply(new ProfileRequestContext()), false, null);
+    }
+
+    @Test
+    public void withValidTokenClaimsSet_noUrisInMetadata_shouldReturnNoPropagationSession() {
+        setup(Duration.ofDays(1), mockMetadataContext(false, false), mockResponseContext(validTokenClaimsSet(), null),
+                clientId);
+        assertSession(function.apply(new ProfileRequestContext()), false, jwtId);
+    }
+
+    @Test
+    public void withValidIdTokenClaimsSet_noUrisInMetadata_shouldReturnNoPropagationSession() {
+        setup(Duration.ofDays(1), mockMetadataContext(false, false), mockResponseContext(null, validIdTokenClaimsSet()),
+                clientId);
+        assertSession(function.apply(new ProfileRequestContext()), false, null);
+    }
+
+    @Test
+    public void withValidTokenClaimsSet_frontUriInMetadata_shouldReturnPropagationSession() {
+        setup(Duration.ofDays(1), mockMetadataContext(true, false), mockResponseContext(validTokenClaimsSet(), null),
+                clientId);
+        assertSession(function.apply(new ProfileRequestContext()), true, jwtId);
+    }
+
+    @Test
+    public void withValidIdTokenClaimsSet_frontUriInMetadata_shouldReturnPropagationSession() {
+        setup(Duration.ofDays(1), mockMetadataContext(true, false), mockResponseContext(null, validIdTokenClaimsSet()),
+                clientId);
+        assertSession(function.apply(new ProfileRequestContext()), true, null);
+    }
+
+    @Test
+    public void withValidTokenClaimsSet_backUriInMetadata_shouldReturnPropagationSession() {
+        setup(Duration.ofDays(1), mockMetadataContext(false, true), mockResponseContext(validTokenClaimsSet(), null),
+                clientId);
+        assertSession(function.apply(new ProfileRequestContext()), true, jwtId);
+    }
+
+    @Test
+    public void withValidIdTokenClaimsSet_backUriInMetadata_shouldReturnPropagationSession() {
+        setup(Duration.ofDays(1), mockMetadataContext(false, true), mockResponseContext(null, validIdTokenClaimsSet()),
+                clientId);
+        assertSession(function.apply(new ProfileRequestContext()), true, null);
+    }
+
+    @Test
+    public void withValidTokenClaimsSet_bothUrisInMetadata_shouldReturnPropagationSession() {
+        setup(Duration.ofDays(1), mockMetadataContext(true, true), mockResponseContext(validTokenClaimsSet(), null),
+                clientId);
+        assertSession(function.apply(new ProfileRequestContext()), true, jwtId);
+    }
+
+    @Test
+    public void withValidIdTokenClaimsSet_bothUrisInMetadata_shouldReturnPropagationSession() {
+        setup(Duration.ofDays(1), mockMetadataContext(true, true), mockResponseContext(null, validIdTokenClaimsSet()),
+                clientId);
+        assertSession(function.apply(new ProfileRequestContext()), true, null);
+    }
+
+    protected OIDCAuthenticationResponseContext mockResponseContext(final TokenClaimsSet tokenClaimsSet,
+            final IDTokenClaimsSet idTokenClaimsSet) {
+        final OIDCAuthenticationResponseContext responseContext = new OIDCAuthenticationResponseContext();
+        responseContext.setAuthorizationGrantClaimsSet(tokenClaimsSet);
+        responseContext.setIDToken(idTokenClaimsSet);
+        return responseContext;
+    }
+
+    protected OIDCMetadataContext mockMetadataContext(final boolean frontChannel, final boolean backChannel) {
+        final OIDCMetadataContext metadataContext = new OIDCMetadataContext();
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        if (frontChannel) {
+            try {
+                metadata.setFrontChannelLogoutURI(new URI("https://example.org/frontChannel"));
+            } catch (URISyntaxException e) {
+                Assert.fail();
+            }
+        }
+        if (backChannel) {
+            try {
+                metadata.setBackChannelLogoutURI(new URI("https://example.org/backChannel"));
+            } catch (URISyntaxException e) {
+                Assert.fail();
+            }
+        }
+        metadataContext.setClientInformation(new OIDCClientInformation(new ClientID(clientId), metadata));
+        return metadataContext;
+    }
+
+    protected TokenClaimsSet validTokenClaimsSet() {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer(issuer)
+                .claim("sid", sessionIdentifier)
+                .jwtID(jwtId)
+                .subject(subject)
+                .build();
+        return new TokenClaimsSet(claimsSet) { };
+    }
+
+    protected IDTokenClaimsSet validIdTokenClaimsSet() {
+        IDTokenClaimsSet claimsSet = new IDTokenClaimsSet(new Issuer(issuer), new Subject(subject),
+                List.of(new Audience(clientId)), Date.from(Instant.now().plusSeconds(60)), Date.from(Instant.now()));
+        claimsSet.setSessionID(new SessionID(sessionIdentifier));
+        return claimsSet;
+    }
+
+    protected void assertSession(final SPSession session, final boolean supportsPropagation, final String rootId) {
+        Assert.assertNotNull(session);
+        Assert.assertTrue(session instanceof OIDCRPSession);
+        final OIDCRPSession rpSession = (OIDCRPSession) session;
+        Assert.assertEquals(rpSession.getIssuer(), issuer);
+        Assert.assertEquals(rpSession.getSessionIdentifier(), sessionIdentifier);
+        Assert.assertEquals(rpSession.getSubject(), subject);
+        Assert.assertEquals(rpSession.supportsLogoutPropagation(), supportsPropagation);
+        Assert.assertEquals(rpSession.getRootTokenIdentifier(), rootId);
+        Assert.assertEquals(rpSession.getId(), clientId);
+        Assert.assertTrue(Duration.between(rpSession.getCreationInstant(), Instant.now()).toMillis()
+                < Duration.ofSeconds(2).toMillis());
+
+        Assert.assertTrue(Duration.between(rpSession.getExpirationInstant(), Instant.now().plus(Duration.ofDays(1)))
+                .toMillis() < Duration.ofSeconds(2).toMillis());
+        
+    }
+}

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


More information about the commits mailing list