[java-idp-oidc] branch main updated: Allow client_id claim to pass through access token into reg flow.

Scott Cantor cantor.2 at osu.edu
Wed Mar 9 22:05:45 UTC 2022


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

scantor 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=08e580665a09fa36d9f41cdea9a3068d8feefcd9

The following commit(s) were added to refs/heads/main by this push:
     new 08e58066 Allow client_id claim to pass through access token into reg flow.
08e58066 is described below

commit 08e580665a09fa36d9f41cdea9a3068d8feefcd9
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Mar 9 17:04:14 2022 -0500

    Allow client_id claim to pass through access token into reg flow.
---
 .../OIDCClientRegistrationTokenClaimsContext.java  |  25 +--
 ...gistrationTokenClaimsContextLookupFunction.java |   2 +-
 ...istrationTokenMetadataPolicyLookupFunction.java |  10 +-
 .../op/token/support/RegistrationClaimsSet.java    | 231 +++++++++++++--------
 .../oidc/op/profile/impl/GenerateClientID.java     |  76 +++++--
 ...nitializeRegistrationMetadataPolicyContext.java |  11 +-
 .../impl/ValidateRegistrationAccessToken.java      |  41 ++--
 .../idp/flows/oidc/register/register-beans.xml     |  37 ++--
 .../oidc/op/profile/flow/RegistrationFlowTest.java | 110 ++++++----
 9 files changed, 328 insertions(+), 215 deletions(-)

diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationTokenClaimsContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationTokenClaimsContext.java
index 62e94440..db8383e6 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationTokenClaimsContext.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationTokenClaimsContext.java
@@ -17,9 +17,11 @@
 
 package net.shibboleth.idp.plugin.oidc.op.messaging.context;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.context.MessageContext;
 
 import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
 
@@ -27,29 +29,27 @@ import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
  * Subcontext carrying information on the claims included in the initial access token used for accessing the dynamic
  * client registration endpoint.
  * 
- * This context appears as a subcontext of the (inbound) {@link org.opensaml.messaging.context.MessageContext}.
+ * This context appears as a subcontext of the (inbound) {@link MessageContext}.
  * 
  * @since 3.1.0
  */
-public class OIDCClientRegistrationTokenClaimsContext extends BaseContext {
+public final class OIDCClientRegistrationTokenClaimsContext extends BaseContext {
 
     /** The claims set in the initial access token. */
     @Nullable private RegistrationClaimsSet claimsSet;
 
-    /**
-     * Constructor.
-     */
-    public OIDCClientRegistrationTokenClaimsContext() {
-        // no op
-    }
-
     /**
      * Set the claims set in the initial access token.
      * 
-     * @param set What to set.
+     * @param set What to set
+     * 
+     * @return this context
      */
-    public void setClaimsSet(@Nullable final RegistrationClaimsSet set) {
+    @Nonnull public OIDCClientRegistrationTokenClaimsContext setClaimsSet(
+            @Nullable final RegistrationClaimsSet set) {
         claimsSet = set;
+        
+        return this;
     }
 
     /**
@@ -60,4 +60,5 @@ public class OIDCClientRegistrationTokenClaimsContext extends BaseContext {
     @Nullable public RegistrationClaimsSet getClaimsSet() {
         return claimsSet;
     }
-}
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction.java
index 382b7eaa..85ed056d 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction.java
@@ -37,6 +37,6 @@ public class DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction
         if (input == null || input.getInboundMessageContext() == null) {
             return null;
         }
-        return input.getInboundMessageContext().getSubcontext(OIDCClientRegistrationTokenClaimsContext.class, false);
+        return input.getInboundMessageContext().getSubcontext(OIDCClientRegistrationTokenClaimsContext.class);
     }
 }
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction.java
index d41bc52f..ed800b32 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction.java
@@ -18,7 +18,6 @@
 package net.shibboleth.idp.plugin.oidc.op.profile.context.navigate;
 
 import java.util.Map;
-import java.util.function.Function;
 
 import javax.annotation.Nullable;
 
@@ -37,15 +36,14 @@ public class DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction impl
     ContextDataLookupFunction<ProfileRequestContext, Map<String, MetadataPolicy>> {
 
     /** {@inheritDoc} */
-    @Override @Nullable
-    public Map<String, MetadataPolicy> apply(@Nullable ProfileRequestContext profileRequestContext) {
-        final Function<ProfileRequestContext, OIDCClientRegistrationTokenClaimsContext> regTokenCtxLookup =
-                new DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction();
-        final OIDCClientRegistrationTokenClaimsContext regTokenCtx = regTokenCtxLookup.apply(profileRequestContext);
+    @Nullable public Map<String, MetadataPolicy> apply(@Nullable final ProfileRequestContext profileRequestContext) {
+        final OIDCClientRegistrationTokenClaimsContext regTokenCtx =
+                new DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction().apply(profileRequestContext);
         if (regTokenCtx == null) {
             return null;
         }
         final RegistrationClaimsSet claimsSet = regTokenCtx.getClaimsSet();
         return claimsSet == null ? null : claimsSet.getMetadata();
     }
+
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java
index 172298ee..2c732127 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java
@@ -21,6 +21,7 @@ import java.time.Instant;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -30,6 +31,12 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonSetter;
 
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 /**
  * Class wrapping claims for the initial registration access token.
@@ -44,47 +51,51 @@ public final class RegistrationClaimsSet {
 
     /** Identifier for the token. */
     @JsonProperty("jti")
-    private String jti;    
+    @Nullable @NotEmpty private String jti;
 
     /** Type of the token. */
     @JsonProperty("type")
-    private String keyType;
+    @Nullable @NotEmpty private String keyType;
     
     /** OP issuer. */
     @JsonProperty("iss")
-    private String issuer;
+    @Nullable @NotEmpty private String issuer;
     
     /** Expiration time of the token. */
     @JsonProperty("exp")
-    private Instant expiration;
+    @Nullable private Instant expiration;
     
     /** Issuance time of the token. */
     @JsonProperty("iat")
-    private Instant issuedAt;
+    @Nullable private Instant issuedAt;
     
     /** The principal who issued the token. */
     @JsonProperty("prncpl")
-    private String principal;
+    @Nullable @NotEmpty private String principal;
     
     /** Authentication context class reference value of the performed authentication. */
     @JsonProperty("acr")
-    private String authContext;
+    @Nullable @NotEmpty private String authContext;
     
     /** Authentication time of the performed authentication. */
     @JsonProperty("auth_time")
-    private Instant authTime;
+    @Nullable private Instant authTime;
     
     /** Allowed metadata values to be issued with the token. */
     @JsonProperty("metadata")
-    private Map<String, MetadataPolicy> metadata;
+    @Nullable @NonnullElements private Map<String,MetadataPolicy> metadata;
     
     /** Relying party identifier. */
     @JsonProperty("rp_id")
-    private String relyingPartyId;
-    
+    @Nullable @NotEmpty private String relyingPartyId;
+
+    /** Client identifier. */
+    @JsonProperty("client_id")
+    @Nullable @NotEmpty private String clientId;
+
     /** Flag to signal one-time use of the token. */
     @JsonProperty("onetime")
-    private Boolean onetime;
+    @Nullable private Boolean onetime;
 
     /**
      * Constructor.
@@ -97,7 +108,7 @@ public final class RegistrationClaimsSet {
      * 
      * @return The identifier for the token.
      */
-    public String getJti() {
+    @Nullable @NotEmpty public String getJti() {
         return jti;
     }
 
@@ -106,8 +117,8 @@ public final class RegistrationClaimsSet {
      * 
      * @param id What to set.
      */
-    public void setJti(final String id) {
-        this.jti = id;
+    public void setJti(@Nullable @NotEmpty final String id) {
+        jti = StringSupport.trimOrNull(id);
     }
 
     /**
@@ -115,7 +126,7 @@ public final class RegistrationClaimsSet {
      * 
      * @return The type of the token.
      */
-    public String getKeyType() {
+    @Nullable @NotEmpty public String getKeyType() {
         return keyType;
     }
 
@@ -124,8 +135,8 @@ public final class RegistrationClaimsSet {
      * 
      * @param type What to set.
      */
-    public void setKeyType(final String type) {
-        this.keyType = type;
+    public void setKeyType(@Nullable @NotEmpty final String type) {
+        keyType = type;
     }
 
     /**
@@ -133,7 +144,7 @@ public final class RegistrationClaimsSet {
      * 
      * @return The issuer of the token.
      */
-    public String getIssuer() {
+    @Nullable @NotEmpty public String getIssuer() {
         return issuer;
     }
 
@@ -142,8 +153,8 @@ public final class RegistrationClaimsSet {
      * 
      * @param iss What to set.
      */
-    public void setIssuer(final String iss) {
-        this.issuer = iss;
+    public void setIssuer(@Nullable @NotEmpty final String iss) {
+        issuer =  StringSupport.trimOrNull(iss);
     }
 
     /**
@@ -151,7 +162,7 @@ public final class RegistrationClaimsSet {
      * 
      * @return The expiration time of the token.
      */
-    public Instant getExpiration() {
+    @Nullable public Instant getExpiration() {
         return expiration;
     }
 
@@ -160,8 +171,8 @@ public final class RegistrationClaimsSet {
      * 
      * @param exp What to set.
      */
-    public void setExpiration(final Instant exp) {
-        this.expiration = exp;
+    public void setExpiration(@Nullable final Instant exp) {
+        expiration = exp;
     }
 
     /**
@@ -170,8 +181,8 @@ public final class RegistrationClaimsSet {
      * @return The expiration time of the token using epoch seconds.
      */
     @JsonGetter("exp")
-    public long getExpirationEpoch() {
-        return expiration.getEpochSecond();
+    @NonNegative public long getExpirationEpoch() {
+        return expiration != null ? expiration.getEpochSecond() : 0;
     }
 
     /**
@@ -180,8 +191,8 @@ public final class RegistrationClaimsSet {
      * @param exp What to set.
      */
     @JsonSetter("exp")
-    public void setExpirationEpoch(final long exp) {
-        this.expiration = Instant.ofEpochSecond(exp);
+    public void setExpirationEpoch(@NonNegative final long exp) {
+        expiration = Instant.ofEpochSecond(exp);
     }
 
     /**
@@ -189,7 +200,7 @@ public final class RegistrationClaimsSet {
      * 
      * @return The issuance time of the token.
      */
-    public Instant getIssuedAt() {
+    @Nullable public Instant getIssuedAt() {
         return issuedAt;
     }
 
@@ -198,8 +209,8 @@ public final class RegistrationClaimsSet {
      * 
      * @param iat What to set.
      */
-    public void setIssuedAt(final Instant iat) {
-        this.issuedAt = iat;
+    public void setIssuedAt(@Nullable final Instant iat) {
+        issuedAt = iat;
     }
 
     /**
@@ -208,8 +219,8 @@ public final class RegistrationClaimsSet {
      * @param iat What to set.
      */
     @JsonSetter("iat")
-    public void setIssuedAtEpoch(final long iat) {
-        this.issuedAt = Instant.ofEpochSecond(iat);
+    public void setIssuedAtEpoch(@NonNegative final long iat) {
+        issuedAt = Instant.ofEpochSecond(iat);
     }
 
     /**
@@ -218,8 +229,8 @@ public final class RegistrationClaimsSet {
      * @return The issuance time of the token using epoch seconds.
      */
     @JsonGetter("iat")
-    public long getIssuedAtEpoch() {
-        return issuedAt.getEpochSecond();
+    @NonNegative public long getIssuedAtEpoch() {
+        return issuedAt != null ? issuedAt.getEpochSecond() : 0;
     }
 
     /**
@@ -227,17 +238,17 @@ public final class RegistrationClaimsSet {
      * 
      * @return The principal who issued the token.
      */
-    public String getPrincipal() {
+    @Nullable @NotEmpty public String getPrincipal() {
         return principal;
     }
     
     /**
      * Set the principal who issued the token.
      * 
-     * @param prncpl What to set.
+     * @param prin What to set.
      */
-    public void setPrincipal(final String prncpl) {
-        this.principal = prncpl;
+    public void setPrincipal(@Nullable @NotEmpty final String prin) {
+        principal = prin;
     }
 
     /**
@@ -245,7 +256,7 @@ public final class RegistrationClaimsSet {
      * 
      * @return The authentication context class reference value of the performed authentication.
      */
-    public String getAuthContext() {
+    @Nullable @NotEmpty public String getAuthContext() {
         return authContext;
     }
 
@@ -254,8 +265,8 @@ public final class RegistrationClaimsSet {
      * 
      * @param acr What to set.
      */
-    public void setAuthContext(final String acr) {
-        this.authContext = acr;
+    public void setAuthContext(@Nullable @NotEmpty final String acr) {
+        authContext = StringSupport.trimOrNull(acr);
     }
 
     /**
@@ -263,7 +274,7 @@ public final class RegistrationClaimsSet {
      * 
      * @return The authentication time of the performed authentication.
      */
-    public Instant getAuthTime() {
+    @Nullable public Instant getAuthTime() {
         return authTime;
     }
 
@@ -272,8 +283,8 @@ public final class RegistrationClaimsSet {
      * 
      * @param time What to set.
      */
-    public void setAuthTime(final Instant time) {
-        this.authTime = time;
+    public void setAuthTime(@Nullable final Instant time) {
+        authTime = time;
     }
     
     /**
@@ -281,7 +292,7 @@ public final class RegistrationClaimsSet {
      * 
      * @return The allowed metadata values to be issued with the token.
      */
-    public Map<String, MetadataPolicy> getMetadata() {
+    @Nullable @NonnullElements @NotLive @Unmodifiable public Map<String, MetadataPolicy> getMetadata() {
         return metadata;
     }
 
@@ -290,8 +301,8 @@ public final class RegistrationClaimsSet {
      * 
      * @param data What to set.
      */
-    public void setMetadata(final Map<String, MetadataPolicy> data) {
-        this.metadata = data;
+    public void setMetadata(@Nullable @NonnullElements final Map<String, MetadataPolicy> data) {
+        metadata = data != null ? Map.copyOf(data) : null;
     }
     
     /**
@@ -299,19 +310,41 @@ public final class RegistrationClaimsSet {
      * 
      * @return The relying party identifier.
      */
-    public String getRelyingPartyId() {
+    @Nullable @NotEmpty public String getRelyingPartyId() {
         return relyingPartyId;
     }
     
     /**
      * Set the relying party identifier.
      * 
-     * @param rpId What to set.
+     * @param id id to set
+     */
+    public void setRelyingPartyId(@Nullable @NotEmpty final String id) {
+        relyingPartyId = StringSupport.trimOrNull(id);
+    }
+
+    /**
+     * Get the client identifier.
+     * 
+     * <p>This is may or may not be the same as the relying party identifier,
+     * and if set is a signal to the registration flow to use this value rather than
+     * a generated one.</p>
+     * 
+     * @return client identifier.
      */
-    public void setRelyingPartyId(final String rpId) {
-        this.relyingPartyId = rpId;
+    @Nullable @NotEmpty public String getClientId() {
+        return clientId;
     }
     
+    /**
+     * Set the client identifier.
+     * 
+     * @param id id to set
+     */
+    public void setClientId(@Nullable @NotEmpty final String id) {
+        clientId = StringSupport.trimOrNull(id);
+    }
+
     /**
      * Get the flag to signal one-time use of the token.
      * 
@@ -326,8 +359,8 @@ public final class RegistrationClaimsSet {
      * 
      * @param flag What to set.
      */
-    public void setOnetime(final Boolean flag) {
-        this.onetime = flag;
+    public void setOnetime(@Nullable final Boolean flag) {
+        onetime = flag;
     }
 
     /**
@@ -336,46 +369,49 @@ public final class RegistrationClaimsSet {
     public static class Builder {
         
         /** Identifier for the token. */
-        private String jti;
+        @Nullable @NotEmpty private String jti;
         
         /** Type of the token. */
-        private String keyType;
+        @Nullable @NotEmpty private String keyType;
         
         /** Issuer of the token. */
-        private String issuer;
+        @Nullable @NotEmpty private String issuer;
         
         /** Expiration time of the token. */
-        private Instant expiration;
+        @Nullable private Instant expiration;
         
         /** Issuance time of the token. */
-        private Instant issuedAt;
+        @Nullable private Instant issuedAt;
         
         /** The principal who issued the token. */
-        private String principal;
+        @Nullable @NotEmpty private String principal;
         
         /** The authentication context class reference value of the performed authentication. */
-        private String authContext;
+        @Nullable @NotEmpty private String authContext;
         
         /** The authentication time of the performed authentication. */
-        private Instant authTime;
+        @Nullable private Instant authTime;
         
         /** Allowed metadata values to be issued with the token. */
-        private Map<String, MetadataPolicy> metadata;
+        @Nullable @NonnullElements private Map<String,MetadataPolicy> metadata;
         
         /** Relying party identifier. */
-        private String relyingPartyId;
+        @Nullable @NotEmpty private String relyingPartyId;
+
+        /** Client identifier. */
+        @Nullable @NotEmpty private String clientId;
 
         /** Flag to signal one-time use of the token. */
-        private Boolean onetime;
+        @Nullable private Boolean onetime;
 
         /**
          * Constructor.
          *
          * @param id The identifier for the token.
          */
-        public Builder(final String id) {
-            this.jti = id;
-            this.keyType = VALUE_TYPE_RT;
+        public Builder(@Nullable @NotEmpty final String id) {
+            jti = id;
+            keyType = VALUE_TYPE_RT;
         }
         
         /**
@@ -383,8 +419,8 @@ public final class RegistrationClaimsSet {
          * @param type What to set.
          * @return The builder instance.
          */
-        public Builder withType(final String type) {
-            this.keyType = type;
+        public Builder withType(@Nullable @NotEmpty final String type) {
+            keyType = type;
             return this;
         }
 
@@ -393,8 +429,8 @@ public final class RegistrationClaimsSet {
          * @param iss What to set.
          * @return The builder instance.
          */
-        public Builder withIssuer(final String iss) {
-            this.issuer = iss;
+        public Builder withIssuer(@Nullable @NotEmpty final String iss) {
+            issuer = iss;
             return this;
         }
         
@@ -403,8 +439,8 @@ public final class RegistrationClaimsSet {
          * @param exp What to set.
          * @return The builder instance.
          */
-        public Builder withExpiration(final Instant exp) {
-            this.expiration = exp;
+        public Builder withExpiration(@Nullable final Instant exp) {
+            expiration = exp;
             return this;
         }
         
@@ -413,8 +449,8 @@ public final class RegistrationClaimsSet {
          * @param iat What to set.
          * @return The builder instance.
          */
-        public Builder withIssuedAt(final Instant iat) {
-            this.issuedAt = iat;
+        public Builder withIssuedAt(@Nullable final Instant iat) {
+            issuedAt = iat;
             return this;
         }
         
@@ -423,8 +459,8 @@ public final class RegistrationClaimsSet {
          * @param prncpl What to set.
          * @return The builder instance.
          */
-        public Builder withPrincipal(final String prncpl) {
-            this.principal = prncpl;
+        public Builder withPrincipal(@Nullable @NotEmpty final String prncpl) {
+            principal = prncpl;
             return this;
         }
         
@@ -433,8 +469,8 @@ public final class RegistrationClaimsSet {
          * @param acr What to set.
          * @return The builder instance.
          */
-        public Builder withAcr(final String acr) {
-            this.authContext = acr;
+        public Builder withAcr(@Nullable @NotEmpty final String acr) {
+            authContext = acr;
             return this;
         }
         
@@ -443,8 +479,8 @@ public final class RegistrationClaimsSet {
          * @param time What to set.
          * @return The builder instance.
          */
-        public Builder withAuthTime(final Instant time) {
-            this.authTime = time;
+        public Builder withAuthTime(@Nullable final Instant time) {
+            authTime = time;
             return this;
         }
         
@@ -453,28 +489,38 @@ public final class RegistrationClaimsSet {
          * @param data What to set.
          * @return The builder instance.
          */
-        public Builder withMetadata(final Map<String, MetadataPolicy> data) {
-            this.metadata = data;
+        public Builder withMetadata(@Nullable @NonnullElements final Map<String, MetadataPolicy> data) {
+            metadata = data != null ? Map.copyOf(data) : null;
             return this;
         }
         
         /**
          * Set the relying party identifier.
-         * @param rpId What to set.
+         * @param id What to set.
          * @return The builder instance.
          */
-        public Builder withRelyingPartyId(final String rpId) {
-            this.relyingPartyId = rpId;
+        public Builder withRelyingPartyId(@Nullable @NotEmpty final String id) {
+            relyingPartyId = id;
             return this;
         }
-        
+
+        /**
+         * Set the client identifier.
+         * @param id What to set
+         * @return The builder instance.
+         */
+        public Builder withClientId(@Nullable @NotEmpty final String id) {
+            clientId = id;
+            return this;
+        }
+
         /**
          * Set the flag to signal one-time use of the token.
          * @param flag What to set.
          * @return The builder instance.
          */
-        public Builder withOnetime(final Boolean flag) {
-            this.onetime = flag;
+        public Builder withOnetime(@Nullable final Boolean flag) {
+            onetime = flag;
             return this;
         }
         
@@ -482,7 +528,7 @@ public final class RegistrationClaimsSet {
          * Build the claims set object.
          * @return The claims set object.
          */
-        public RegistrationClaimsSet build() {
+        @Nonnull public RegistrationClaimsSet build() {
             final RegistrationClaimsSet claimsSet = new RegistrationClaimsSet();
             claimsSet.setJti(jti);
             claimsSet.setKeyType(keyType);
@@ -494,9 +540,10 @@ public final class RegistrationClaimsSet {
             claimsSet.setAuthTime(authTime);
             claimsSet.setMetadata(metadata);
             claimsSet.setRelyingPartyId(relyingPartyId);
+            claimsSet.setClientId(clientId);
             claimsSet.setOnetime(onetime);
             return claimsSet;
         }   
     }
 
-}
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/GenerateClientID.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/GenerateClientID.java
index c8c30563..081e553f 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/GenerateClientID.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/GenerateClientID.java
@@ -27,10 +27,13 @@ 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 org.slf4j.LoggerFactory;
 
 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.profile.context.navigate.DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.config.ProfileConfiguration;
@@ -40,36 +43,46 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.security.IdentifierGenerationStrategy;
 
 /**
- * Creates a new client ID with the {@link IdentifierGenerationStrategy} attached to the profile configuration. The
- * client ID is included to the {@link OIDCClientRegistrationResponseContext}.
+ * Creates the client ID for the registration.
+ * 
+ * <p>If an access token was used, it may contain the client_id to use. Otherwise uses
+ * the {@link IdentifierGenerationStrategy} attached to the profile configuration. The
+ * client ID is included to the {@link OIDCClientRegistrationResponseContext}.</p>
  */
 public class GenerateClientID extends AbstractProfileAction {
 
     /** Class logger. */
-    @Nonnull
-    private final Logger log = LoggerFactory.getLogger(GenerateClientID.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(GenerateClientID.class);
     
     /**
      * Strategy used to locate the {@link RelyingPartyContext} associated with a given {@link ProfileRequestContext}.
      */
     @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
 
-    /**
-     * Strategy used to locate the {@link OIDCClientRegistrationResponseContext} associated with a given 
-     * {@link MessageContext}.
-     */
-    @Nonnull private Function<MessageContext,OIDCClientRegistrationResponseContext> oidcResponseContextLookupStrategy;
+    /** Strategy used to locate the {@link OIDCClientRegistrationResponseContext} associated with a given request. */
+    @Nonnull private Function<ProfileRequestContext,OIDCClientRegistrationResponseContext>
+        oidcResponseContextLookupStrategy;
 
+    /** Strategy used to locate the {@link OIDCClientRegistrationTokenClaimsContext} associated with the request. */
+    @Nonnull private Function<ProfileRequestContext,OIDCClientRegistrationTokenClaimsContext>
+        registrationTokenContextLookupStrategy;
+    
     /** The RelyingPartyContext to operate on. */
     @Nullable private RelyingPartyContext rpCtx;
-    
+
     /** The OIDCClientRegistrationResponseContext to create the client ID to. */
     @Nullable private OIDCClientRegistrationResponseContext oidcResponseCtx;
+
+    /** The OIDCClientRegistrationTokenClaimsContext from which to optionally obtain client ID. */
+    @Nullable private OIDCClientRegistrationTokenClaimsContext registrationTokenCtx;
     
     /** Constructor. */
     public GenerateClientID() {
         relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
-        oidcResponseContextLookupStrategy = new ChildContextLookup<>(OIDCClientRegistrationResponseContext.class);
+        oidcResponseContextLookupStrategy = new ChildContextLookup<>(
+                OIDCClientRegistrationResponseContext.class).compose(
+                        new OutboundMessageContextLookup());
+        registrationTokenContextLookupStrategy = new DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction();
     }
 
     /**
@@ -95,13 +108,28 @@ public class GenerateClientID extends AbstractProfileAction {
      *         given {@link MessageContext}
      */
     public void setOidcResponseContextLookupStrategy(
-            @Nonnull final Function<MessageContext,OIDCClientRegistrationResponseContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext,OIDCClientRegistrationResponseContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
         oidcResponseContextLookupStrategy = Constraint.isNotNull(strategy,
                 "OIDCClientRegistrationResponseContext lookup strategy cannot be null");
     }
 
+
+    /**
+     * Set the strategy used to locate the {@link OIDCClientRegistrationTokenClaimsContext} associated with a given
+     * request.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setRegistrationTokenContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,OIDCClientRegistrationTokenClaimsContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        registrationTokenContextLookupStrategy = Constraint.isNotNull(strategy,
+                "OIDCClientRegistrationTokenClaimsContext lookup strategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -129,13 +157,18 @@ public class GenerateClientID extends AbstractProfileAction {
             return false;            
         }
 
-        oidcResponseCtx = oidcResponseContextLookupStrategy.apply(profileRequestContext.getOutboundMessageContext());
+        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;            
         }
+        
+        registrationTokenCtx = registrationTokenContextLookupStrategy.apply(profileRequestContext);
+        if (registrationTokenCtx != null && registrationTokenCtx.getClaimsSet() == null) {
+            registrationTokenCtx = null;
+        }
 
         return true;
     }
@@ -147,8 +180,19 @@ public class GenerateClientID extends AbstractProfileAction {
         final IdentifierGenerationStrategy idGenerator =
                 profileConfig.getSecurityConfiguration(profileRequestContext).getIdGenerator();
         
-        final String clientId = idGenerator.generateIdentifier();
+        String clientId = null;
+        if (registrationTokenCtx != null) {
+            clientId = registrationTokenCtx.getClaimsSet().getClientId();
+        }
+        
+        if (clientId != null) {
+            log.debug("{} Using client_id supplied by access token: {}", getLogPrefix(), clientId);
+        } else {
+            clientId = idGenerator.generateIdentifier();
+            log.debug("{} Created a new client ID: {}", getLogPrefix(), clientId);
+        }
+        
         oidcResponseCtx.setClientId(clientId);
-        log.debug("{} Created a new client ID {}", getLogPrefix(), clientId);
     }
-}
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java
index a6b15be9..e16faf96 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java
@@ -28,6 +28,7 @@ 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.InboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,7 +63,7 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
         tokenMetadataPolicyLookupStrategy;
     
     /** The strategy used to create or locate the metadata policy context. */
-    @NonnullAfterInit private Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext>
+    @NonnullAfterInit private Function<ProfileRequestContext,OIDCClientRegistrationMetadataPolicyContext>
         registrationPolicyContextCreationStrategy;
     
     /** The strategy used for merging profile and token based metadata policies. */
@@ -82,7 +83,8 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
         profileMetadataPolicyLookupStrategy = new RegistrationMetadataPolicyLookupFunction();
         tokenMetadataPolicyLookupStrategy = new DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction();
         registrationPolicyContextCreationStrategy = 
-                new ChildContextLookup<>(OIDCClientRegistrationMetadataPolicyContext.class, true);
+                new ChildContextLookup<>(OIDCClientRegistrationMetadataPolicyContext.class, true).compose(
+                        new InboundMessageContextLookup());
         metadataPolicyMergingStrategy = new DefaultMetadataPolicyMergingStrategy();
         metadataPolicyValidationStrategy = new DefaultMetadataPolicyValidator();
     }
@@ -119,7 +121,7 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
      * @param strategy What to set.
      */
     public void setRegistrationPolicyContextCreationStrategy(
-            @Nonnull final Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext,OIDCClientRegistrationMetadataPolicyContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
         registrationPolicyContextCreationStrategy = Constraint.isNotNull(strategy,
@@ -158,8 +160,7 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
             return false;
         }
 
-        metadataPolicyContext = 
-                registrationPolicyContextCreationStrategy.apply(profileRequestContext.getInboundMessageContext());
+        metadataPolicyContext = registrationPolicyContextCreationStrategy.apply(profileRequestContext);
         if (metadataPolicyContext == null) {
             log.error("{} The registration metadata policy context could not be created, invalid profile context",
                     getLogPrefix());
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java
index 2f57507e..083f908a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java
@@ -23,11 +23,11 @@ import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 
-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.InboundMessageContextLookup;
 import org.opensaml.storage.RevocationCache;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -45,7 +45,6 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterI
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
 import net.shibboleth.utilities.java.support.security.DataSealer;
 import net.shibboleth.utilities.java.support.security.DataSealerException;
 
@@ -60,10 +59,10 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
     @Nonnull private Logger log = LoggerFactory.getLogger(ValidateRegistrationAccessToken.class);
     
     /** Strategy that will return a {@link RelyingPartyContext}. */
-    @Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextLookupStrategy;
+    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
     
     /** Strategy to create or return a {@link OIDCClientRegistrationTokenClaimsContext}. */
-    @Nonnull private Function<MessageContext, OIDCClientRegistrationTokenClaimsContext>
+    @Nonnull private Function<ProfileRequestContext,OIDCClientRegistrationTokenClaimsContext>
         registrationClaimsContextCreationStrategy;
 
     /** Data sealer for unwrapping authorization code. */
@@ -77,9 +76,6 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
     
     /** The relying party context to operate on. */
     private RelyingPartyContext relyingPartyContext;
-    
-    /** The registration token claims context to operate on. */
-    private OIDCClientRegistrationTokenClaimsContext registrationClaimsContext;
 
     /** The registration access token to be validated. */
     private String accessToken;
@@ -88,9 +84,10 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
      * Constructor.
      */
     public ValidateRegistrationAccessToken() {
-        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class, false);
+        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
         registrationClaimsContextCreationStrategy = 
-                new ChildContextLookup<>(OIDCClientRegistrationTokenClaimsContext.class, true);
+                new ChildContextLookup<>(OIDCClientRegistrationTokenClaimsContext.class, true).compose(
+                        new InboundMessageContextLookup());
     }
     
     /**
@@ -112,7 +109,7 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
      * @param strategy creation strategy
      */
     public void setRegistrationClaimsContextCreationStrategy(
-            @Nonnull final Function<MessageContext, OIDCClientRegistrationTokenClaimsContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext,OIDCClientRegistrationTokenClaimsContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
         registrationClaimsContextCreationStrategy = Constraint.isNotNull(strategy,
@@ -190,14 +187,6 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
         }
         accessToken = getRequest().getAccessToken().getValue();
         
-        registrationClaimsContext = 
-                registrationClaimsContextCreationStrategy.apply(profileRequestContext.getInboundMessageContext());
-        if (registrationClaimsContext == null) {
-            log.error("{} The registration token claims context could not be created, invalid profile context",
-                    getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-        }
         return true;
     }
     
@@ -228,7 +217,7 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
             return;
         }
-        final String relyingPartyId = StringSupport.trimOrNull(claimsSet.getRelyingPartyId());
+        final String relyingPartyId = claimsSet.getRelyingPartyId();
         if (relyingPartyId == null) {
             log.error("{} registration access token {} didn't contain relying party identifier", getLogPrefix(),
                     claimsSet.getJti());
@@ -249,8 +238,20 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
                 return;
             }
         }
+        
+        final OIDCClientRegistrationTokenClaimsContext registrationClaimsContext =
+                registrationClaimsContextCreationStrategy.apply(profileRequestContext);
+        if (registrationClaimsContext == null) {
+            log.error("{} The registration token claims context could not be created, invalid profile context",
+                    getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return;
+        }
+        
+        registrationClaimsContext.setClaimsSet(claimsSet);
+        
         relyingPartyContext.setVerified(true);
         relyingPartyContext.setRelyingPartyId(relyingPartyId);
-        registrationClaimsContext.setClaimsSet(claimsSet);
     }
+
 }
\ No newline at end of file
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 754a5013..0058a9e2 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
@@ -6,18 +6,22 @@
     xmlns:util="http://www.springframework.org/schema/util"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
-                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
-                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
     default-init-method="initialize" default-destroy-method="destroy">
     
     <bean id="shibboleth.oidc.profileId" class="java.lang.String"
         c:_0="#{T(net.shibboleth.oidc.profile.config.OIDCDynamicRegistrationConfiguration).PROFILE_ID}" />
 
-    <bean id="shibboleth.oidc.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.oidcdynreg:OIDC.Registration}" />
+    <bean id="shibboleth.oidc.loggingId" class="java.lang.String"
+        c:_0="%{idp.service.logging.oidcdynreg:OIDC.Registration}" />
 
-    <bean id="DecodeMessage"
-        class="org.opensaml.profile.action.impl.DecodeMessage"
+    <bean id="InitializeOutboundMessageContext"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeOutboundRegistrationResponseMessageContext"
         scope="prototype">
+    </bean>
+
+    <bean id="DecodeMessage" class="org.opensaml.profile.action.impl.DecodeMessage" scope="prototype">
         <constructor-arg>
             <bean
                 class="net.shibboleth.idp.plugin.oidc.op.decoding.impl.OIDCClientRegistrationRequestDecoder"
@@ -31,7 +35,14 @@
         p:revocationCache-ref="shibboleth.oidc.RevocationCache"
         p:sealer-ref="#{'%{idp.oidc.dynreg.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
         p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
-        
+
+    <bean id="SelectRelyingPartyConfiguration"
+        class="net.shibboleth.idp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype"
+        p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
+
+    <bean id="SelectProfileConfiguration"
+        class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype" />
+
     <bean id="ValidateRegistrationRequestMetadata"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationRequestMetadata" scope="prototype" />
         
@@ -154,20 +165,6 @@
         </property>
     </bean>
 
-    <bean id="SelectRelyingPartyConfiguration"
-        class="net.shibboleth.idp.profile.impl.SelectRelyingPartyConfiguration"
-        scope="prototype"
-        p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
-
-    <bean id="SelectProfileConfiguration"
-        class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration"
-        scope="prototype" />
-
-    <bean id="InitializeOutboundMessageContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeOutboundRegistrationResponseMessageContext"
-        scope="prototype">
-    </bean>
-
     <bean id="BuildErrorResponseFromEvent"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.BuildRegistrationErrorResponseFromEvent"
         scope="prototype"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
index c3472714..69e5292a 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
@@ -18,24 +18,22 @@
 package net.shibboleth.idp.plugin.oidc.op.profile.flow;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.security.NoSuchAlgorithmException;
 import java.time.Instant;
 
 import org.opensaml.storage.StorageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.webflow.core.FlowException;
 import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.nimbusds.langtag.LangTag;
 import com.nimbusds.langtag.LangTagException;
 import com.nimbusds.oauth2.sdk.GrantType;
-import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformationResponse;
@@ -45,8 +43,6 @@ import net.minidev.json.JSONObject;
 import net.minidev.json.parser.JSONParser;
 import net.shibboleth.idp.plugin.oidc.op.profile.impl.BaseOIDCResponseActionTest;
 import net.shibboleth.oidc.metadata.impl.BaseStorageServiceClientInformationComponent;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.security.DataSealerException;
 
 /**
  * Some tests for the dynamic registration flow.
@@ -55,7 +51,10 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
     
     public static final String FLOW_ID = "oidc/register";
     
-    String redirectUri = "https://example.org/cb";
+    private final String redirectUri = "https://example.org/cb";
+    
+    private String rpId;
+    private String clientId;
     
     @Autowired
     @Qualifier("shibboleth.StorageService")
@@ -64,9 +63,22 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
     public RegistrationFlowTest() {
         super(FLOW_ID);
     }
+    
+    @BeforeMethod
+    public void setUp() {
+        rpId = "mockRpId";
+        clientId = null;
+    }
+    
+    @AfterMethod
+    public void tearDown() throws IOException {
+        if (clientId != null) {
+            storageService.delete(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId);
+        }
+    }
 
     @Test
-    public void testInvalidMessage() throws UnsupportedEncodingException, ParseException {
+    public void testInvalidMessage() throws Exception {
         setJsonRequest("POST", "{ \"test\":not_json");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, "invalid_client_metadata");
@@ -87,17 +99,18 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
     }
 
     @Test
-    public void testUnauthenticated_success() throws ParseException, IOException, net.minidev.json.parser.ParseException {
+    public void testUnauthenticated_success() throws Exception {
         setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"] }");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        OIDCClientInformationResponse parsedResponse = parseSuccessResponse(result, OIDCClientInformationResponse.class);
-        OIDCClientInformation clientInfo = parsedResponse.getOIDCClientInformation();
-        OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
-        String record = storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, 
+        final OIDCClientInformationResponse parsedResponse =
+                parseSuccessResponse(result, OIDCClientInformationResponse.class);
+        final OIDCClientInformation clientInfo = parsedResponse.getOIDCClientInformation();
+        final OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
+        final String record = storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, 
                 clientInfo.getID().toString()).getValue();
         Assert.assertNotNull(record);
-        JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);
-        OIDCClientInformation storedInfo = OIDCClientInformation.parse((JSONObject) parser.parse(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());
@@ -105,7 +118,7 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
     }
 
     @Test
-    public void testAccessToken_nonCompliantWithProfilePolicy1() throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+    public void testAccessToken_nonCompliantWithProfilePolicy1() throws Exception {
         setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"] }");
         request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://invalid.domain.org/cb\"]")
                 .toAuthorizationHeader());
@@ -114,7 +127,7 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
     }
 
     @Test
-    public void testAccessToken_nonCompliantWithProfilePolicy2() throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+    public void testAccessToken_nonCompliantWithProfilePolicy2() throws Exception {
         setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"], \"id_token_signed_response_alg\":\"HS256\" }");
         request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://example.org/cb\"]")
                 .toAuthorizationHeader());
@@ -123,77 +136,91 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
     }
 
     @Test
-    public void testAccessToken_success() throws ParseException, IOException, net.minidev.json.parser.ParseException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException, FlowException {
+    public void testAccessToken_success() throws Exception {
         setJsonRequest("POST", buildRequestMessage(redirectUri));
         request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://example.org/cb\"]")
                 .toAuthorizationHeader());
-        assertSuccessfulResponse(flowExecutor.launchExecution(FLOW_ID, null, externalContext));
+        assertSuccessfulResponse(flowExecutor.launchExecution(FLOW_ID, null, externalContext), null);
     }
 
     @Test
-    public void testAccessToken_successCustomClaimIgnored() throws ParseException, IOException, net.minidev.json.parser.ParseException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException, FlowException {
+    public void testAccessToken_success_withClientID() throws Exception {
+        setJsonRequest("POST", buildRequestMessage(redirectUri));
+        clientId = "https://example.org";
+        request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://example.org/cb\"]")
+                .toAuthorizationHeader());
+        assertSuccessfulResponse(flowExecutor.launchExecution(FLOW_ID, null, externalContext), clientId);
+    }
+
+    @Test
+    public void testAccessToken_successCustomClaimIgnored() throws Exception {
         setJsonRequest("POST", buildRequestMessage(redirectUri, "\"customClaim\":\"customValue\""));
         request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://example.org/cb\"]")
                 .toAuthorizationHeader());
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertSuccessfulResponse(result);
+        assertSuccessfulResponse(result, null);
         final OIDCClientInformationResponse parsedResponse = 
                 parseSuccessResponse(result, OIDCClientInformationResponse.class);
         Assert.assertNull(parsedResponse.getOIDCClientInformation().getOIDCMetadata().getCustomField("customClaim"));
     }
 
     @Test
-    public void testAccessToken_successCustomClaimInPolicyAdded() throws ParseException, IOException, net.minidev.json.parser.ParseException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException, FlowException {
+    public void testAccessToken_successCustomClaimInPolicyAdded() throws Exception {
         setJsonRequest("POST", buildRequestMessage(redirectUri, "\"customClaim\":\"customValue\""));
         request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://example.org/cb\"]", new String[] {"customClaim"})
                 .toAuthorizationHeader());
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertSuccessfulResponse(result);
+        assertSuccessfulResponse(result, null);
         final OIDCClientInformationResponse parsedResponse = 
                 parseSuccessResponse(result, OIDCClientInformationResponse.class);
         Assert.assertEquals(parsedResponse.getOIDCClientInformation().getOIDCMetadata().getCustomField("customClaim"), "customValue");
     }
 
     @Test
-    public void testAccessToken_noPolicyNoRedirectUri() throws NoSuchAlgorithmException, DataSealerException,
-            ComponentInitializationException {
+    public void testAccessToken_noPolicyNoRedirectUri() throws Exception {
         setJsonRequest("POST", "{ \"test\":false }");
-        request.addHeader("Authorization", buildRegistrationAccessToken("mockDynRegClientNoProfilePolicy",
-                (String) null, (String[]) null).toAuthorizationHeader());
+        rpId = "mockDynRegClientNoProfilePolicy";
+        request.addHeader("Authorization", buildRegistrationAccessToken((String) null,
+                (String[]) null).toAuthorizationHeader());
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, "invalid_redirect_uri");
     }
 
     @Test
-    public void testAccessToken_nonDefaultPolicyActive_failsWhenIncompatibleRequest()
-            throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+    public void testAccessToken_nonDefaultPolicyActive_failsWhenIncompatibleRequest() throws Exception {
         // the policy for mockDynRegClientAnotherProfilePolicy shouldn't accept other grant_types than implicit
         setJsonRequest("POST", buildRequestMessage(redirectUri, "\"grant_types\":[\"authorization_code\"]"));
-        request.addHeader("Authorization", buildRegistrationAccessToken("mockDynRegClientAnotherProfilePolicy",
-                (String) null, (String[]) null).toAuthorizationHeader());
+        rpId = "mockDynRegClientAnotherProfilePolicy";
+        request.addHeader("Authorization", buildRegistrationAccessToken((String) null,
+                (String[]) null).toAuthorizationHeader());
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, "invalid_client_metadata");
     }
 
     @Test
-    public void testAccessToken_nonDefaultPolicyActive_successWithCompatibleRequest()
-            throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException, ParseException, IOException, net.minidev.json.parser.ParseException {
+    public void testAccessToken_nonDefaultPolicyActive_successWithCompatibleRequest() throws Exception {
         setJsonRequest("POST", buildRequestMessage(redirectUri, "\"grant_types\":[\"implicit\"]"));
-        request.addHeader("Authorization", buildRegistrationAccessToken("mockDynRegClientAnotherProfilePolicy",
-                (String) null, (String[]) null).toAuthorizationHeader());
+        rpId = "mockDynRegClientAnotherProfilePolicy";
+        request.addHeader("Authorization", buildRegistrationAccessToken((String) null,
+                (String[]) null).toAuthorizationHeader());
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertSuccessfulResponse(result);
+        assertSuccessfulResponse(result, null);
         final OIDCClientInformationResponse parsedResponse = 
                 parseSuccessResponse(result, OIDCClientInformationResponse.class);
         Assert.assertTrue(parsedResponse.getOIDCClientInformation().getOIDCMetadata().getGrantTypes().contains(GrantType.IMPLICIT));
     }
 
-    protected void assertSuccessfulResponse(final FlowExecutionResult result) throws IOException, ParseException, net.minidev.json.parser.ParseException {
+    protected void assertSuccessfulResponse(final FlowExecutionResult result, final String clientId) throws Exception {
         final OIDCClientInformationResponse parsedResponse =
                 parseSuccessResponse(result, OIDCClientInformationResponse.class);
         final OIDCClientInformation clientInfo = OIDCClientInformation.parse(
                 parsedResponse.getOIDCClientInformation().toJSONObject());
         final OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
+        if (clientId != null) {
+            Assert.assertEquals(clientId, clientInfo.getID().getValue());
+        } else {
+            Assert.assertNotEquals(clientId, clientInfo.getID().getValue());
+        }
         final String record = storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, 
                 clientInfo.getID().toString()).getValue();
         Assert.assertNotNull(record);
@@ -208,17 +235,13 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
         try {
             Assert.assertEquals(metadata.getPolicyURI(LangTag.parse("fi")), new URI("https://policy.org/finnish"));
             Assert.assertEquals(metadata.getPolicyURI(LangTag.parse("en")), new URI("https://policy.org/english"));
-        } catch (LangTagException | URISyntaxException e) {
+        } catch (final LangTagException | URISyntaxException e) {
             Assert.fail();
         }
         Assert.assertEquals(storedMetadata.getPolicyURIEntries(), metadata.getPolicyURIEntries());
     }
 
-    protected BearerAccessToken buildRegistrationAccessToken(final String redirectUriSubset, final String... additionalPolicyClaims) throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
-        return buildRegistrationAccessToken("mockRpId", redirectUriSubset, additionalPolicyClaims);
-    }
-
-    protected BearerAccessToken buildRegistrationAccessToken(final String rpId, final String redirectUriSubset, final String... additionalPolicyClaims) throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+    protected BearerAccessToken buildRegistrationAccessToken(final String redirectUriSubset, final String... additionalPolicyClaims) throws Exception {
         final StringBuilder metadata = new StringBuilder();
         if (additionalPolicyClaims != null) {
             for (int i = 0; i < additionalPolicyClaims.length; i++) {
@@ -242,6 +265,7 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
                 "\"iat\":" + Instant.now().getEpochSecond() + "," +
                 "\"jti\":\"" + idGenerator.generateIdentifier() + "\"," + 
                 "\"rp_id\":\"" + rpId + "\"," + 
+                (clientId != null ? ("\"client_id\":\"" + clientId + "\",") : "") + 
                 "\"metadata\":" + (metadata.length() == 0 ? "null" : "{" + metadata.toString()) + "}" +
                 "}";
         return new BearerAccessToken(BaseOIDCResponseActionTest.initializeDataSealer().wrap(json,

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


More information about the commits mailing list