[java-idp-oidc] branch dev/JOIDC-222 updated: JOIDC-222 - Support for OpenID Federation

Henri Mikkonen henri.mikkonen at iki.fi
Wed Apr 23 09:44:44 UTC 2025


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

hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.

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

The following commit(s) were added to refs/heads/dev/JOIDC-222 by this push:
     new f0324c3d JOIDC-222 - Support for OpenID Federation
f0324c3d is described below

commit f0324c3df50f3ff780481978253e7263485ad1a7
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Apr 23 12:43:07 2025 +0300

    JOIDC-222 - Support for OpenID Federation
    
    https://shibboleth.atlassian.net/browse/JOIDC-222
    
    Switched explicit registration flow to use entity ID as client ID.
    - Possibly existing records are replaced by the new metadata
    - Refactored StoreClientInformation
---
 .../op/oidfed/profile/impl/GenerateClientID.java   | 140 +++++++++++++++++++++
 .../op/profile/impl/StoreClientInformation.java    |  63 +++-------
 ...faultClientInformationReplacementCondition.java |  58 +++++++++
 .../idp/flows/oidc/register/register-beans.xml     |   3 +
 .../idp/flows/oidfed/register/register-beans.xml   |   9 +-
 .../profile/flow/oidfed/RegistrationFlowTest.java  |  63 +++++-----
 6 files changed, 255 insertions(+), 81 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/GenerateClientID.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/GenerateClientID.java
new file mode 100644
index 00000000..6ee91f5c
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/GenerateClientID.java
@@ -0,0 +1,140 @@
+/*
+ * 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.oidfed.profile.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+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.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationResponseContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+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;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Reuses the entity ID as the client ID for the registration.
+ */
+public class GenerateClientID extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(GenerateClientID.class);
+    
+    /** Strategy used to locate the {@link OIDCClientRegistrationResponseContext} associated with a given request. */
+    @Nonnull private Function<ProfileRequestContext,OIDCClientRegistrationResponseContext>
+        oidcResponseContextLookupStrategy;
+
+    /** Strategy used to obtain the client id value for authorize/token request. */
+    @NonnullAfterInit private Function<MessageContext, ClientID> clientIDLookupStrategy;
+
+    /** The OIDCClientRegistrationResponseContext to create the client ID to. */
+    @Nullable private OIDCClientRegistrationResponseContext oidcResponseCtx;
+
+    /** The entity id to be used as client id. */
+    @NonnullBeforeExec private String entityId;
+
+    /** Constructor. */
+    public GenerateClientID() {
+        final Function<ProfileRequestContext,OIDCClientRegistrationResponseContext> orcls =
+                new ChildContextLookup<>(OIDCClientRegistrationResponseContext.class).compose(
+                        new OutboundMessageContextLookup());
+        assert orcls != null;
+        oidcResponseContextLookupStrategy = orcls;
+    }
+
+    /**
+     * Set the strategy used to locate the {@link OIDCClientRegistrationResponseContext} associated with a given
+     * {@link MessageContext}.
+     * 
+     * @param strategy strategy used to locate the {@link OIDCClientRegistrationResponseContext} associated with a 
+     *         given {@link MessageContext}
+     */
+    public void setOidcResponseContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,OIDCClientRegistrationResponseContext> strategy) {
+        checkSetterPreconditions();
+        
+        oidcResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+                "OIDCClientRegistrationResponseContext lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to locate the client id of the request.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setClientIDLookupStrategy(@Nonnull final Function<MessageContext, ClientID> strategy) {
+        checkSetterPreconditions();
+        clientIDLookupStrategy =
+                Constraint.isNotNull(strategy, "ClientIDLookupStrategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (clientIDLookupStrategy == null) {
+            throw new ComponentInitializationException("ClientIDLookupStrategy cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        oidcResponseCtx = oidcResponseContextLookupStrategy.apply(profileRequestContext);
+        if (oidcResponseCtx == null) {
+            log.debug("{} No OIDC client registration response context associated with this profile request", 
+                    getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;            
+        }
+
+        final ClientID clientId = clientIDLookupStrategy.apply(profileRequestContext.getInboundMessageContext());
+        if (clientId == null || clientId.getValue() == null) {
+            log.error("{} No client ID could be resolved via inbound message context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;            
+        }
+        entityId = clientId.getValue();
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        assert oidcResponseCtx != null;
+        oidcResponseCtx.setClientId(entityId);
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/StoreClientInformation.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/StoreClientInformation.java
index b74e4457..52716dd6 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/StoreClientInformation.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/StoreClientInformation.java
@@ -17,6 +17,7 @@ package net.shibboleth.idp.plugin.oidc.op.profile.impl;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.function.Function;
+import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -30,10 +31,8 @@ import com.nimbusds.oauth2.sdk.client.ClientInformation;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
 
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationResponseContext;
-import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationTokenClaimsContext;
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.navigate.DefaultClientInformationLookupFunction;
-import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction;
-import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultClientInformationReplacementCondition;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.oidc.metadata.ClientInformationManager;
 import net.shibboleth.oidc.metadata.ClientInformationManagerException;
@@ -57,20 +56,16 @@ public class StoreClientInformation extends AbstractProfileAction {
     /** Strategy to obtain registration validity period policy. */
     @Nullable private Function<ProfileRequestContext,Duration> registrationValidityPeriodStrategy;
     
-    /** Strategy used to locate the {@link OIDCClientRegistrationTokenClaimsContext} associated with the request. */
-    @Nonnull private Function<ProfileRequestContext,OIDCClientRegistrationTokenClaimsContext>
-        registrationTokenContextLookupStrategy;
-
     /** Strategy used to locate {@link OIDCClientInformation} to be stored. */
     @Nonnull private Function<ProfileRequestContext,OIDCClientInformation> clientInformationLookupStrategy;
 
-    /** The OIDCClientRegistrationTokenClaimsContext from which to optionally obtain client ID. */
-    @Nullable private OIDCClientRegistrationTokenClaimsContext registrationTokenCtx;
-    
+    /** Condition used to determine if existing record should be replaced. */
+    @Nonnull private Predicate<ProfileRequestContext> replacementCondition;
+
     /** Constructor. */
     public StoreClientInformation() {
-        registrationTokenContextLookupStrategy = new DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction();
         clientInformationLookupStrategy = new DefaultClientInformationLookupFunction();
+        replacementCondition = new DefaultClientInformationReplacementCondition();
     }
     
     /**
@@ -102,30 +97,27 @@ public class StoreClientInformation extends AbstractProfileAction {
         ifInitializedThrowUnmodifiabledComponentException();
         clientInformationManager = Constraint.isNotNull(manager, "The client information manager cannot be null!");
     }
-    
+
     /**
-     * Set the strategy used to locate the {@link OIDCClientRegistrationTokenClaimsContext} associated with a given
-     * request.
+     * Set the strategy used to locate {@link OIDCClientInformation} to be stored.
      * 
      * @param strategy lookup strategy
      */
-    public void setRegistrationTokenContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,OIDCClientRegistrationTokenClaimsContext> strategy) {
-        ifInitializedThrowUnmodifiabledComponentException();
+    public void setClientInformationLookupStrategy(@Nonnull final Function<ProfileRequestContext,OIDCClientInformation> strategy) {
+        checkSetterPreconditions();
         
-        registrationTokenContextLookupStrategy = Constraint.isNotNull(strategy,
-                "OIDCClientRegistrationTokenClaimsContext lookup strategy cannot be null");
+        clientInformationLookupStrategy = Constraint.isNotNull(strategy, "Client information lookup strategy cannot be null");
     }
 
     /**
-     * Set the strategy used to locate {@link OIDCClientInformation} to be stored.
+     * Set the condition used to determine if existing record should be replaced.
      * 
-     * @param strategy lookup strategy
+     * @param condition replacement condition
      */
-    public void setClientInformationLookupStrategy(@Nonnull final Function<ProfileRequestContext,OIDCClientInformation> strategy) {
+    public void setReplacementCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
         checkSetterPreconditions();
         
-        clientInformationLookupStrategy = Constraint.isNotNull(strategy, "Client information lookup strategy cannot be null");
+        replacementCondition = Constraint.isNotNull(condition, "Replacement condition cannot be null");
     }
 
     /** {@inheritDoc} */
@@ -138,21 +130,6 @@ public class StoreClientInformation extends AbstractProfileAction {
         }
     }
 
-    /** {@inheritDoc} */
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-        
-        registrationTokenCtx = registrationTokenContextLookupStrategy.apply(profileRequestContext);
-        if (registrationTokenCtx != null && registrationTokenCtx.getClaimsSet() == null) {
-            registrationTokenCtx = null;
-        }
-        
-        return true;
-    }
-    
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -166,15 +143,7 @@ public class StoreClientInformation extends AbstractProfileAction {
         Duration lifetime = registrationValidityPeriodStrategy != null ?
                 registrationValidityPeriodStrategy.apply(profileRequestContext) : null;
 
-        final boolean replace;
-        if (registrationTokenCtx != null) {
-            final RegistrationClaimsSet claimsSet = registrationTokenCtx.getClaimsSet();
-            assert claimsSet != null;
-            replace = claimsSet.isReplacement();
-            
-        } else {
-            replace = false;
-        }
+        final boolean replace = replacementCondition.test(profileRequestContext);
         
         log.debug("{} Storing client information (replace = {})", getLogPrefix(), replace);
         
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultClientInformationReplacementCondition.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultClientInformationReplacementCondition.java
new file mode 100644
index 00000000..8c65a9d8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultClientInformationReplacementCondition.java
@@ -0,0 +1,58 @@
+/*
+ * 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.profile.logic;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationTokenClaimsContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
+
+/**
+ * Default condition that bases on {@link RegistrationClaimsSet#isReplacement()} if it's set in
+ * {@link OIDCClientRegistrationTokenClaimsContext}. Otherwise, false is returned.
+ */
+public class DefaultClientInformationReplacementCondition implements Predicate<ProfileRequestContext> {
+
+    /** Strategy used to locate the {@link OIDCClientRegistrationTokenClaimsContext} associated with the request. */
+    @Nonnull private Function<ProfileRequestContext,OIDCClientRegistrationTokenClaimsContext>
+        registrationTokenContextLookupStrategy;
+
+    /**
+     * Constructor.
+     */
+    public DefaultClientInformationReplacementCondition() {
+        registrationTokenContextLookupStrategy = new DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction();
+
+    }
+    /** {@inheritDoc} */
+    @Override
+    public boolean test(@Nullable final ProfileRequestContext profileRequestContext) {
+        final OIDCClientRegistrationTokenClaimsContext registrationTokenCtx =
+                registrationTokenContextLookupStrategy.apply(profileRequestContext);
+        if (registrationTokenCtx != null && registrationTokenCtx.getClaimsSet() != null) {
+            final RegistrationClaimsSet claimsSet = registrationTokenCtx.getClaimsSet();
+            assert claimsSet != null;
+            return claimsSet.isReplacement();
+        }
+        return false;
+    }
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
index 141cc452..11b19881 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
@@ -109,6 +109,9 @@
         <property name="registrationValidityPeriodStrategy">
             <bean class="net.shibboleth.oidc.profile.config.navigate.RegistrationValidityPeriodLookupFunction" />
         </property>
+        <property name="replacementCondition">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultClientInformationReplacementCondition" />
+        </property>
     </bean>
 
     <bean id="BuildErrorResponseFromEvent"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index 5a60fb0e..cc1e301a 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -174,11 +174,9 @@
     </bean>
 
     <bean id="GenerateClientID"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.GenerateClientID"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.GenerateClientID"
         scope="prototype"
-        p:xmlSafeIdentifier="%{idp.oidc.xmlSafeIdentifiers:true}"
-        p:relyingPartyContextLookupStrategy-ref="ExplicitRegistrationRelyingPartyCreationStrategy"
-        p:identifierGeneratorLookupStrategy="#{getObject('shibboleth.oidc.dynreg.ClientIDGenerationStrategy') ?: getObject('shibboleth.oidc.DefaultIdentifierGenerationStrategy')}"/>
+        p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy" />
 
     <bean id="GenerateClientSecret"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.GenerateClientSecret" scope="prototype"
@@ -218,7 +216,8 @@
 
     <bean id="StoreClientInformation"
             class="net.shibboleth.idp.plugin.oidc.op.profile.impl.StoreClientInformation" scope="prototype"
-            p:clientInformationManager-ref="#{'%{idp.oidfed.expreg.clientInformationManager:shibboleth.oidc.ClientInformationManager}'.trim()}">
+            p:clientInformationManager-ref="#{'%{idp.oidfed.expreg.clientInformationManager:shibboleth.oidc.ClientInformationManager}'.trim()}"
+            p:replacementCondition-ref="shibboleth.Conditions.TRUE">
         <property name="registrationValidityPeriodStrategy">
             <bean parent="shibboleth.Functions.Expression"
                 c:expression="T(java.time.Duration).between(T(java.time.Instant).now(), #input.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext)).getSelectedMetadataExpiration())" />
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
index 22cd0b55..e048dabf 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed;
 
+import java.io.IOException;
 import java.util.List;
 
 import org.opensaml.storage.StorageRecord;
@@ -24,6 +25,7 @@ import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
 import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatementClaimsSet;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
@@ -78,31 +80,18 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         configureMockHttpClient(clientId);
         setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        final ExplicitClientRegistrationResponse parsedResponse =
-                parseSuccessResponse(result, ExplicitClientRegistrationResponse.class);
-        final EntityStatement entityStatement = parsedResponse.getEntityStatement();
-        final EntityStatementClaimsSet statementClaims = entityStatement.getClaimsSet();
-        Assert.assertEquals(statementClaims.getAuthorityHints().stream().map(id -> id.getValue()).toList(),
-                List.of(anchorId));
-        Assert.assertEquals(statementClaims.getClaim("trust_anchor"), anchorId);
-        final OIDCClientInformation clientInfo = entityStatement.getClaimsSet().getRPInformation();
-        final OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
-        final String providedClientId = clientInfo.getID().getValue();
-        assert providedClientId != null;
-        assert storageService != null;
-        final StorageRecord<String> storageRecord =
-                storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, providedClientId);
-        Assert.assertNotNull(storageRecord, "Record with clientId " + providedClientId + " was null");
-        assert storageRecord != null;
-        final String record = storageRecord.getValue();
-        Assert.assertNotNull(record);
-        final JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);
-        final OIDCClientInformation storedInfo = OIDCClientInformation.parse((JSONObject) parser.parse(record));
-        Assert.assertEquals(storedInfo.getID(), clientInfo.getID());
-        Assert.assertEquals(storedInfo.getSecret(), clientInfo.getSecret());
-        Assert.assertEquals(storedInfo.getOIDCMetadata().getRedirectionURIStrings(),
-                metadata.getRedirectionURIStrings());
-        Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
+        assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+    }
+
+    @Test
+    public void testValidEntityConfiguration_repeat() throws Exception {
+        final String clientId = uniqueClientId();
+        for (int i = 0; i < 2; i++) {
+            configureMockHttpClient(clientId);
+            setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+            final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+            assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        }
     }
 
     @Test
@@ -112,9 +101,24 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
                 subordinateStatement(clientId) + "\", \"" + trustedAnchorConfiguration() + "\"]";
         setRequest("POST", trustChain, "application/trust-chain+json");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        final ExplicitClientRegistrationResponse parsedResponse =
-                parseSuccessResponse(result, ExplicitClientRegistrationResponse.class);
-        final EntityStatement entityStatement = parsedResponse.getEntityStatement();
+        assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+    }
+
+    @Test
+    public void testValidTrustChain_repeat() throws Exception {
+        final String clientId = uniqueClientId();
+        final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+                subordinateStatement(clientId) + "\", \"" + trustedAnchorConfiguration() + "\"]";
+        for (int i = 0; i < 2; i++) {
+            setRequest("POST", trustChain, "application/trust-chain+json");
+            final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+            assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        }
+    }
+
+    protected void assertResponseStatement(final ExplicitClientRegistrationResponse response,
+            final String expectedClientId) throws IOException, ParseException, net.minidev.json.parser.ParseException {
+        final EntityStatement entityStatement = response.getEntityStatement();
         final EntityStatementClaimsSet statementClaims = entityStatement.getClaimsSet();
         Assert.assertEquals(statementClaims.getAuthorityHints().stream().map(id -> id.getValue()).toList(),
                 List.of(anchorId));
@@ -123,6 +127,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         final OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
         final String providedClientId = clientInfo.getID().getValue();
         assert providedClientId != null;
+        Assert.assertEquals(providedClientId, expectedClientId);
         assert storageService != null;
         final StorageRecord<String> storageRecord =
                 storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, providedClientId);
@@ -139,5 +144,5 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
         
     }
-
+    
 }

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


More information about the commits mailing list