[java-idp-oidc] branch main updated: JOIDC-61 Support metadata policies in the dyn. reg. profile configuration

Henri Mikkonen henri.mikkonen at iki.fi
Fri Jan 7 13:30:13 UTC 2022


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

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

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

The following commit(s) were added to refs/heads/main by this push:
     new 70e41bb8 JOIDC-61 Support metadata policies in the dyn. reg. profile configuration
70e41bb8 is described below

commit 70e41bb8fbb6aad340228afc417f34586ba5b48a
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Jan 7 15:25:37 2022 +0200

    JOIDC-61 Support metadata policies in the dyn. reg. profile configuration
    
    https://shibboleth.atlassian.net/browse/JOIDC-61
    
    Initial, still incomplete support for metadata policies with dynamic
    client registration. Some remarks:
    
    - RegistrationClaimsSet overlaps with the other claims
    set classes in the same package, but is still independent.
    
    - ValidateRegistrationRequestMetadata doesn't store the enforced request
    metadata anywhere yet.
    
    - Default metadata policy resolver (via file) doesn't handle non-existing
    files yet: flow tests work as the file exists, but real deployment doesn't.
---
 idp-oidc-extension-api/pom.xml                     |  15 +
 ...IDCClientRegistrationMetadataPolicyContext.java |  64 ++++
 .../OIDCClientRegistrationTokenClaimsContext.java  |  63 +++
 ...gistrationTokenClaimsContextLookupFunction.java |  42 ++
 ...istrationTokenMetadataPolicyLookupFunction.java |  51 +++
 .../oidc/op/storage/RevocationCacheContexts.java   |  10 +-
 .../op/token/support/RegistrationClaimsSet.java    | 424 +++++++++++++++++++++
 ...nitializeRegistrationMetadataPolicyContext.java | 207 ++++++++++
 .../impl/ValidateRegistrationAccessToken.java      | 242 ++++++++++++
 .../impl/ValidateRegistrationRequestMetadata.java  | 180 +++++++++
 .../DefaultMetadataPolicyMergingStrategy.java      | 104 +++++
 .../idp/flows/oidc/register/register-beans.xml     |  16 +
 .../idp/flows/oidc/register/register-flow.xml      |   3 +
 .../idp/service/relying-party/postconfig.xml       |  74 +++-
 .../idp/plugin/oidc/op/conf/oidc.properties        |   4 +
 .../oidc/op/profile/flow/RegistrationFlowTest.java |  72 +++-
 .../src/test/resources/conf/metadata-policy1.json  |  21 +
 .../src/test/resources/conf/oidc.properties        |   2 +
 .../src/test/resources/conf/relying-party.xml      |   1 +
 19 files changed, 1590 insertions(+), 5 deletions(-)

diff --git a/idp-oidc-extension-api/pom.xml b/idp-oidc-extension-api/pom.xml
index 2736f711..59be2ebd 100644
--- a/idp-oidc-extension-api/pom.xml
+++ b/idp-oidc-extension-api/pom.xml
@@ -58,6 +58,11 @@
             <artifactId>opensaml-xmlsec-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>net.shibboleth.oidc</groupId>
+            <artifactId>oidc-common-metadata-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>com.nimbusds</groupId>
             <artifactId>oauth2-oidc-sdk</artifactId>
@@ -68,6 +73,16 @@
             <artifactId>jakarta.mail-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
         <!-- Test Dependencies -->
         <dependency>
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationMetadataPolicyContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationMetadataPolicyContext.java
new file mode 100644
index 00000000..987b3afc
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationMetadataPolicyContext.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.messaging.context;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+
+/**
+ * Subcontext carrying information on the metadata policy related to the dynamic client registration.
+ * 
+ * This context appears as a subcontext of the (inbound) {@link org.opensaml.messaging.context.MessageContext}.
+ * 
+ * @since 3.1.0
+ */
+public class OIDCClientRegistrationMetadataPolicyContext extends BaseContext {
+    
+    /** The metadata policy related to dynamic client registration. */
+    private Map<String, MetadataPolicy> metadataPolicy;
+
+    /**
+     * Constructor.
+     */
+    public OIDCClientRegistrationMetadataPolicyContext() {
+        // no op
+    }
+
+    /**
+     * Set the metadata policy related to dynamic client registration.
+     * 
+     * @param policy What to set.
+     */
+    public void setMetadataPolicy(@Nullable final Map<String, MetadataPolicy> policy) {
+        metadataPolicy = policy;
+    }
+
+    /**
+     * Get the metadata policy related to dynamic client registration.
+     * 
+     * @return The metadata policy related to dynamic client registration.
+     */
+    @Nullable public Map<String, MetadataPolicy> getMetadataPolicy() {
+        return metadataPolicy;
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 00000000..62e94440
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationTokenClaimsContext.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.messaging.context;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+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}.
+ * 
+ * @since 3.1.0
+ */
+public 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.
+     */
+    public void setClaimsSet(@Nullable final RegistrationClaimsSet set) {
+        claimsSet = set;
+    }
+
+    /**
+     * Get the claims set in the initial access token.
+     * 
+     * @return The claims set in the initial access token.
+     */
+    @Nullable public RegistrationClaimsSet getClaimsSet() {
+        return claimsSet;
+    }
+}
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
new file mode 100644
index 00000000..382b7eaa
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.context.navigate;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationTokenClaimsContext;
+
+/**
+ * A function that returns a {@link OIDCClientRegistrationTokenClaimsContext} from the inbound message context, located
+ * as a subcontext for the profile request context.
+ */
+public class DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction
+    implements ContextDataLookupFunction<ProfileRequestContext, OIDCClientRegistrationTokenClaimsContext> {
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public OIDCClientRegistrationTokenClaimsContext apply(@Nullable final ProfileRequestContext input) {
+        if (input == null || input.getInboundMessageContext() == null) {
+            return null;
+        }
+        return input.getInboundMessageContext().getSubcontext(OIDCClientRegistrationTokenClaimsContext.class, false);
+    }
+}
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
new file mode 100644
index 00000000..d41bc52f
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.context.navigate;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationTokenClaimsContext;
+import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+
+/**
+ * A function that returns a map of metadata policies from the {@link RegistrationClaimsSet}. The claims set is fetched
+ * via {@link DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction}.
+ */
+public class DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction implements
+    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);
+        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/storage/RevocationCacheContexts.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/storage/RevocationCacheContexts.java
index 742e7172..6416ed8e 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/storage/RevocationCacheContexts.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/storage/RevocationCacheContexts.java
@@ -25,12 +25,20 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 public final class RevocationCacheContexts {
 
     /**
-     * ID of context for revoking authorization codes.
+     * ID of context for revoking authorization codes (and access/refresh tokens based on the authorization codes).
      */
     @Nonnull
     @NotEmpty
     public static final String AUTHORIZATION_CODE = RevocationCacheContexts.class.getName() + ".AUTHORIZATION_CODE";
 
+    /**
+     * ID of context for revoking access tokens issued for the dynamic client registration.
+     */
+    @Nonnull
+    @NotEmpty
+    public static final String REGISTRATION_ACCESS_TOKEN = RevocationCacheContexts.class.getName()
+        + ".REGISTRATION_ACCESS_TOKEN";
+
     /** Private constructor. */
     private RevocationCacheContexts() {
         
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
new file mode 100644
index 00000000..54bbb5b4
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java
@@ -0,0 +1,424 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.token.support;
+
+import java.time.Instant;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+
+/**
+ * Class wrapping claims for the initial registration access token.
+ */
+public class RegistrationClaimsSet {
+    
+    /** Value of initial registration access token claims set type. */
+    private static final String VALUE_TYPE_RT = "rt";
+    
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(RegistrationClaimsSet.class);
+
+    /** Identifier for the token. */
+    @JsonProperty("jti")
+    private String jti;    
+
+    /** Type of the token. */
+    @JsonProperty("type")
+    private String keyType;
+    
+    /** OP issuer. */
+    @JsonProperty("iss")
+    private String issuer;
+    
+    /** Expiration time of the token. */
+    @JsonProperty("exp")
+    private Instant expiration;
+    
+    /** Issuance time of the token. */
+    @JsonProperty("iat")
+    private Instant issuedAt;
+    
+    /** The principal who issued the token. */
+    @JsonProperty("prncpl")
+    private String principal;
+    
+    /** Authentication context class reference value of the performed authentication. */
+    @JsonProperty("acr")
+    private String authContext;
+    
+    /** Authentication time of the performed authentication. */
+    @JsonProperty("auth_time")
+    private Instant authTime;
+    
+    /** Allowed metadata values to be issued with the token. */
+    @JsonProperty("metadata")
+    private Map<String, MetadataPolicy> metadata;
+    
+    /** Relying party identifier. */
+    @JsonProperty("rp_id")
+    private String relyingPartyId;
+
+    /**
+     * Constructor.
+     */
+    private RegistrationClaimsSet() {
+    }
+    
+    /**
+     * Get the identifier for the token.
+     * 
+     * @return The identifier for the token.
+     */
+    public String getJti() {
+        return jti;
+    }
+
+    /**
+     * Set the identifier for the token.
+     * 
+     * @param id What to set.
+     */
+    public void setJti(final String id) {
+        this.jti = id;
+    }
+
+    /**
+     * Get the type of the token.
+     * 
+     * @return The type of the token.
+     */
+    public String getKeyType() {
+        return keyType;
+    }
+
+    /**
+     * Set the type of the token.
+     * 
+     * @param type What to set.
+     */
+    public void setKeyType(final String type) {
+        this.keyType = type;
+    }
+
+    /**
+     * Get the issuer of the token.
+     * 
+     * @return The issuer of the token.
+     */
+    public String getIssuer() {
+        return issuer;
+    }
+
+    /**
+     * Set the issuer of the token.
+     * 
+     * @param iss What to set.
+     */
+    public void setIssuer(final String iss) {
+        this.issuer = iss;
+    }
+
+    /**
+     * Get the expiration time of the token.
+     * 
+     * @return The expiration time of the token.
+     */
+    public Instant getExpiration() {
+        return expiration;
+    }
+
+    /**
+     * Set the expiration time of the token.
+     * 
+     * @param exp What to set.
+     */
+    public void setExpiration(final Instant exp) {
+        this.expiration = exp;
+    }
+
+    /**
+     * Get the issuance time of the token.
+     * 
+     * @return The issuance time of the token.
+     */
+    public Instant getIssuedAt() {
+        return issuedAt;
+    }
+
+    /**
+     * Set the issuance time of the token.
+     * 
+     * @param iat What to set.
+     */
+    public void setIssuedAt(final Instant iat) {
+        this.issuedAt = iat;
+    }
+    
+    /**
+     * Get the principal who issued the token.
+     * 
+     * @return The principal who issued the token.
+     */
+    public String getPrincipal() {
+        return principal;
+    }
+    
+    /**
+     * Set the principal who issued the token.
+     * 
+     * @param prncpl What to set.
+     */
+    public void setPrincipal(final String prncpl) {
+        this.principal = prncpl;
+    }
+
+    /**
+     * Get the authentication context class reference value of the performed authentication.
+     * 
+     * @return The authentication context class reference value of the performed authentication.
+     */
+    public String getAuthContext() {
+        return authContext;
+    }
+
+    /**
+     * Set the authentication context class reference value of the performed authentication.
+     * 
+     * @param acr What to set.
+     */
+    public void setAuthContext(final String acr) {
+        this.authContext = acr;
+    }
+
+    /**
+     * Get the authentication time of the performed authentication.
+     * 
+     * @return The authentication time of the performed authentication.
+     */
+    public Instant getAuthTime() {
+        return authTime;
+    }
+
+    /**
+     * Set the authentication time of the performed authentication.
+     * 
+     * @param time What to set.
+     */
+    public void setAuthTime(final Instant time) {
+        this.authTime = time;
+    }
+    
+    /**
+     * Get the allowed metadata values to be issued with the token.
+     * 
+     * @return The allowed metadata values to be issued with the token.
+     */
+    public Map<String, MetadataPolicy> getMetadata() {
+        return metadata;
+    }
+
+    /**
+     * Set the allowed metadata values to be issued with the token.
+     * 
+     * @param data What to set.
+     */
+    public void setMetadata(final Map<String, MetadataPolicy> data) {
+        this.metadata = data;
+    }
+    
+    /**
+     * Get the relying party identifier.
+     * 
+     * @return The relying party identifier.
+     */
+    public String getRelyingPartyId() {
+        return relyingPartyId;
+    }
+    
+    /**
+     * Set the relying party identifier.
+     * 
+     * @param rpId What to set.
+     */
+    public void setRelyingPartyId(final String rpId) {
+        this.relyingPartyId = rpId;
+    }
+
+    /**
+     * The builder for {@link RegistrationClaimsSet}.
+     */
+    public static class Builder {
+        
+        /** Identifier for the token. */
+        private String jti;
+        
+        /** Type of the token. */
+        private String keyType;
+        
+        /** Issuer of the token. */
+        private String issuer;
+        
+        /** Expiration time of the token. */
+        private Instant expiration;
+        
+        /** Issuance time of the token. */
+        private Instant issuedAt;
+        
+        /** The principal who issued the token. */
+        private String principal;
+        
+        /** The authentication context class reference value of the performed authentication. */
+        private String authContext;
+        
+        /** The authentication time of the performed authentication. */
+        private Instant authTime;
+        
+        /** Allowed metadata values to be issued with the token. */
+        private Map<String, MetadataPolicy> metadata;
+        
+        /** Relying party identifier. */
+        private String relyingPartyId;
+        
+        /**
+         * Constructor.
+         *
+         * @param id The identifier for the token.
+         */
+        public Builder(final String id) {
+            this.jti = id;
+            this.keyType = VALUE_TYPE_RT;
+        }
+        
+        /**
+         * Sets the type of the token.
+         * @param type What to set.
+         * @return The builder instance.
+         */
+        public Builder withType(final String type) {
+            this.keyType = type;
+            return this;
+        }
+
+        /**
+         * Sets the issuer of the token.
+         * @param iss What to set.
+         * @return The builder instance.
+         */
+        public Builder withIssuer(final String iss) {
+            this.issuer = iss;
+            return this;
+        }
+        
+        /**
+         * Set the expiration time of the token.
+         * @param exp What to set.
+         * @return The builder instance.
+         */
+        public Builder withExpiration(final Instant exp) {
+            this.expiration = exp;
+            return this;
+        }
+        
+        /**
+         * Set the issuance time of the token.
+         * @param iat What to set.
+         * @return The builder instance.
+         */
+        public Builder withIssuedAt(final Instant iat) {
+            this.issuedAt = iat;
+            return this;
+        }
+        
+        /**
+         * Set the principal who issued the token.
+         * @param prncpl What to set.
+         * @return The builder instance.
+         */
+        public Builder withPrincipal(final String prncpl) {
+            this.principal = prncpl;
+            return this;
+        }
+        
+        /**
+         * Set the authentication context class reference value of the performed authentication.
+         * @param acr What to set.
+         * @return The builder instance.
+         */
+        public Builder withAcr(final String acr) {
+            this.authContext = acr;
+            return this;
+        }
+        
+        /**
+         * Set the authentication time of the performed authentication.
+         * @param time What to set.
+         * @return The builder instance.
+         */
+        public Builder withAuthTime(final Instant time) {
+            this.authTime = time;
+            return this;
+        }
+        
+        /**
+         * Set the allowed metadata values to be issued with the token.
+         * @param data What to set.
+         * @return The builder instance.
+         */
+        public Builder withMetadata(final Map<String, MetadataPolicy> data) {
+            this.metadata = data;
+            return this;
+        }
+        
+        /**
+         * Set the relying party identifier.
+         * @param rpId What to set.
+         * @return The builder instance.
+         */
+        public Builder withRelyingPartyId(final String rpId) {
+            this.relyingPartyId = rpId;
+            return this;
+        }
+        
+        /**
+         * Build the claims set object.
+         * @return The claims set object.
+         */
+        public RegistrationClaimsSet build() {
+            final RegistrationClaimsSet claimsSet = new RegistrationClaimsSet();
+            claimsSet.setJti(jti);
+            claimsSet.setKeyType(keyType);
+            claimsSet.setIssuer(issuer);
+            claimsSet.setExpiration(expiration);
+            claimsSet.setIssuedAt(issuedAt);
+            claimsSet.setPrincipal(principal);
+            claimsSet.setAuthContext(authContext);
+            claimsSet.setAuthTime(authTime);
+            claimsSet.setMetadata(metadata);
+            claimsSet.setRelyingPartyId(relyingPartyId);
+            return claimsSet;
+        }   
+    }
+
+}
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
new file mode 100644
index 00000000..a6b15be9
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.impl;
+
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationMetadataPolicyContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataPolicyMergingStrategy;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyValidator;
+import net.shibboleth.oidc.profile.config.navigate.RegistrationMetadataPolicyLookupFunction;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Initializes the {@link OIDCClientRegistrationMetadataPolicyContext} and attaches it as a subcontext for the incoming
+ * {@link MessageContext}. The context contains the merged metadata policy, containing the policy items defined in the
+ * profile configuration and initial registration access token (if involved).
+ */
+public class InitializeRegistrationMetadataPolicyContext extends AbstractProfileAction {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(InitializeRegistrationMetadataPolicyContext.class);
+
+    /** The strategy used to locate the request metadata policy configured for the profile. */
+    @NonnullAfterInit private Function<ProfileRequestContext, Map<String, MetadataPolicy>>
+        profileMetadataPolicyLookupStrategy;
+
+    /** The strategy used to locate the request metadata policy encoded in the access token. */
+    @NonnullAfterInit private Function<ProfileRequestContext, Map<String, MetadataPolicy>>
+        tokenMetadataPolicyLookupStrategy;
+    
+    /** The strategy used to create or locate the metadata policy context. */
+    @NonnullAfterInit private Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext>
+        registrationPolicyContextCreationStrategy;
+    
+    /** The strategy used for merging profile and token based metadata policies. */
+    @NonnullAfterInit private Function<Pair<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>>,
+        Pair<Map<String, MetadataPolicy>, Boolean>> metadataPolicyMergingStrategy;
+    
+    /** The strategy used for validating token and merged metadata policies. */
+    @NonnullAfterInit private Predicate<Map<String, MetadataPolicy>> metadataPolicyValidationStrategy;
+    
+    /** The metadata policy context to operate on. */
+    private OIDCClientRegistrationMetadataPolicyContext metadataPolicyContext;
+
+    /**
+     * Constructor.
+     */
+    public InitializeRegistrationMetadataPolicyContext() {
+        profileMetadataPolicyLookupStrategy = new RegistrationMetadataPolicyLookupFunction();
+        tokenMetadataPolicyLookupStrategy = new DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction();
+        registrationPolicyContextCreationStrategy = 
+                new ChildContextLookup<>(OIDCClientRegistrationMetadataPolicyContext.class, true);
+        metadataPolicyMergingStrategy = new DefaultMetadataPolicyMergingStrategy();
+        metadataPolicyValidationStrategy = new DefaultMetadataPolicyValidator();
+    }
+
+    /**
+     * Set the strategy used to locate the request metadata policy configured for the profile.
+     * 
+     * @param strategy What to set.
+     */
+    public void setProfileMetadataPolicyLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, Map<String, MetadataPolicy>> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        profileMetadataPolicyLookupStrategy = Constraint.isNotNull(strategy,
+                "Profile metadata policy lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to locate the request metadata policy encoded in the access token.
+     * 
+     * @param strategy What to set.
+     */
+    public void setTokenMetadataPolicyLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, Map<String, MetadataPolicy>> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        tokenMetadataPolicyLookupStrategy = Constraint.isNotNull(strategy,
+                "Token metadata policy lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to create or return the {@link OIDCClientRegistrationMetadataPolicyContext}.
+     * 
+     * @param strategy What to set.
+     */
+    public void setRegistrationPolicyContextCreationStrategy(
+            @Nonnull final Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        registrationPolicyContextCreationStrategy = Constraint.isNotNull(strategy,
+                "OIDCClientRegistrationMetadataPolicyContext creation strategy cannot be null");
+    }
+    
+    /**
+     * Set the strategy used for merging profile and token based metadata policies.
+     * 
+     * @param strategy What to set.
+     */
+    public void setMetadataPolicyMergingStrategy(@Nonnull final Function<Pair<Map<String, MetadataPolicy>,
+            Map<String, MetadataPolicy>>, Pair<Map<String, MetadataPolicy>, Boolean>> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        metadataPolicyMergingStrategy = Constraint.isNotNull(strategy,
+                "Metadata policy merging strategy cannot be null");
+    }
+    
+    /**
+     * Set the strategy used for validating token and merged metadata policies.
+     * 
+     * @param strategy What to set.
+     */
+    public void setMetadataPolicyValidationStrategy(@Nonnull final Predicate<Map<String, MetadataPolicy>> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        metadataPolicyValidationStrategy = Constraint.isNotNull(strategy,
+                "Metadata policy validation strategy cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        metadataPolicyContext = 
+                registrationPolicyContextCreationStrategy.apply(profileRequestContext.getInboundMessageContext());
+        if (metadataPolicyContext == null) {
+            log.error("{} The registration metadata policy context could not be created, invalid profile context",
+                    getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        final Map<String, MetadataPolicy> profileMetadataPolicy =
+                profileMetadataPolicyLookupStrategy.apply(profileRequestContext);
+        
+        final Map<String, MetadataPolicy> tokenMetadataPolicy =
+                tokenMetadataPolicyLookupStrategy.apply(profileRequestContext);
+        
+        if (!metadataPolicyValidationStrategy.test(tokenMetadataPolicy)) {
+            log.warn("{} The token metadata policy is not valid", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+            return;            
+        }
+        
+        final Pair<Map<String, MetadataPolicy>, Boolean> mergedResult =
+                metadataPolicyMergingStrategy.apply(new Pair<>(profileMetadataPolicy, tokenMetadataPolicy));
+        
+        if (!mergedResult.getSecond()) {
+            log.warn("{} The metadata policies from profile and token could not be merged", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+            return;
+        }
+        
+        final Map<String, MetadataPolicy> mergedPolicy = mergedResult.getFirst();
+        if (!metadataPolicyValidationStrategy.test(mergedPolicy)) {
+            log.warn("{} The merged metadata policy is not valid", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+            return;
+        }
+        
+        metadataPolicyContext.setMetadataPolicy(mergedPolicy);
+    }
+}
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
new file mode 100644
index 00000000..342e4a8e
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.impl;
+
+import java.time.Instant;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientRegistrationRequest;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationTokenClaimsContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds;
+import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCache;
+import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
+import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+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;
+
+/**
+ * Action that validates registration access token is a valid one. Token is valid if it is successfully unwrapped,
+ * parsed as access token, is not expired, has not been revoked and contains relying party identifier. Validated
+ * token is stored to under incoming message context {@link OIDCClientRegistrationTokenClaimsContext#getClaimsSet()}.
+ */
+public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<OIDCClientRegistrationRequest> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(ValidateRegistrationAccessToken.class);
+    
+    /** Strategy that will return a {@link RelyingPartyContext}. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextLookupStrategy;
+    
+    /** Strategy to create or return a {@link OIDCClientRegistrationTokenClaimsContext}. */
+    @Nonnull private Function<MessageContext, OIDCClientRegistrationTokenClaimsContext>
+        registrationClaimsContextCreationStrategy;
+
+    /** Data sealer for unwrapping authorization code. */
+    @NonnullAfterInit private DataSealer dataSealer;
+
+    /** Message revocation cache instance to use. */
+    @NonnullAfterInit private RevocationCache revocationCache;
+    
+    /** JSON object mapper. */
+    @NonnullAfterInit private ObjectMapper objectMapper;
+    
+    /** 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;
+    
+    /**
+     * Constructor.
+     */
+    public ValidateRegistrationAccessToken() {
+        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class, false);
+        registrationClaimsContextCreationStrategy = 
+                new ChildContextLookup<>(OIDCClientRegistrationTokenClaimsContext.class, true);
+    }
+    
+    /**
+     * Set the strategy used to return the {@link RelyingPartyContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setRelyingPartyContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        relyingPartyContextLookupStrategy =
+                Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+    }
+    
+    /**
+     * Set the strategy used to create or return the {@link OIDCClientRegistrationTokenClaimsContext}.
+     * 
+     * @param strategy creation strategy
+     */
+    public void setRegistrationClaimsContextCreationStrategy(
+            @Nonnull final Function<MessageContext, OIDCClientRegistrationTokenClaimsContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        registrationClaimsContextCreationStrategy = Constraint.isNotNull(strategy,
+                "OIDCClientRegistrationTokenClaimsContext creation strategy cannot be null");
+    }
+    
+    /**
+     * Set the data sealer for handling access token.
+     * 
+     * @param sealer data sealer.
+     */
+    public void setSealer(@Nonnull final DataSealer sealer) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        dataSealer = Constraint.isNotNull(sealer, "Data sealer cannot be null");
+    }
+    
+    /**
+     * Set the JSON {@link ObjectMapper}.
+     * 
+     * @param mapper object mapper
+     */
+    public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
+    }
+
+    /**
+     * Set the revocation cache instance to use.
+     * 
+     * @param cache The revocationCache to set.
+     */
+    public void setRevocationCache(@Nonnull final RevocationCache cache) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        revocationCache = Constraint.isNotNull(cache, "RevocationCache cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (dataSealer == null) {
+            throw new ComponentInitializationException("DataSealer cannot be null");
+        }
+        
+        if (objectMapper == null) {
+            throw new ComponentInitializationException("ObjectMapper cannot be null");
+        }
+        
+        if (revocationCache == null) {
+            throw new ComponentInitializationException("RevocationCache cannot be null");
+        }
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        relyingPartyContext = relyingPartyContextLookupStrategy.apply(profileRequestContext);
+        if (relyingPartyContext == null) {
+            log.error("{} No relying party context found, invalid profile context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+
+        if (getRequest().getAccessToken() == null || getRequest().getAccessToken().getValue() == null) {
+            log.debug("{} No access token in the request, nothing to do", getLogPrefix());
+            return false;
+        }
+        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;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final RegistrationClaimsSet claimsSet;
+        try {
+            final String unwrapped = dataSealer.unwrap(accessToken);
+            log.debug("{} access token unwrapped into {}", getLogPrefix(), unwrapped);
+            claimsSet = objectMapper.readValue(unwrapped, RegistrationClaimsSet.class);
+        } catch (final DataSealerException | JsonProcessingException e) {
+            log.error("{} Decoding access token failed: {}", getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+
+        log.debug("{} registration access token decoded into {}", getLogPrefix(), claimsSet);
+
+        if (Instant.now().isAfter(claimsSet.getExpiration())) {
+            log.error("{} registration access token exp is in the past {}", getLogPrefix(), claimsSet.getExpiration());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+        if (revocationCache.isRevoked(RevocationCacheContexts.REGISTRATION_ACCESS_TOKEN, claimsSet.getJti())) {
+            log.error("{} registration access token {} has been revoked", getLogPrefix(),
+                    claimsSet.getJti());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+        final String relyingPartyId = StringSupport.trimOrNull(claimsSet.getRelyingPartyId());
+        if (relyingPartyId == null) {
+            log.error("{} registration access token {} didn't contain relying party identifier", getLogPrefix(),
+                    claimsSet.getJti());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+        log.debug("{} registration access token {} successfully validated", getLogPrefix(), claimsSet.getJti());
+        relyingPartyContext.setVerified(true);
+        relyingPartyContext.setRelyingPartyId(relyingPartyId);
+        registrationClaimsContext.setClaimsSet(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/ValidateRegistrationRequestMetadata.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationRequestMetadata.java
new file mode 100644
index 00000000..7883fb2d
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationRequestMetadata.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.impl;
+
+import java.util.Map;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientRegistrationRequest;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationMetadataPolicyContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Validates the incoming dynamic client registration request against the metadata policy stored in the
+ * {@link OIDCClientRegistrationMetadataPolicyContext}.
+ * 
+ * TODO: where to store the result
+ */
+public class ValidateRegistrationRequestMetadata extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(ValidateRegistrationRequestMetadata.class);
+    
+    /** Strategy that will return {@link OIDCClientRegistrationMetadataPolicyContext}. */
+    @NonnullAfterInit private Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext>
+        registrationMetadataPolicyContextLookupStrategy;
+    
+    /** Function used for enforcing the metadata policy. */
+    @NonnullAfterInit private Function<Pair<Object, MetadataPolicy>, Pair<Object, Boolean>> metadataPolicyEnforcer;
+
+    /** The OIDCClientRegistrationRequest to validate. */
+    @Nullable private OIDCClientRegistrationRequest request;
+
+    /** The metadata policy used for validation. */
+    private Map<String, MetadataPolicy> metadataPolicy;   
+    
+    /**
+     * Constructor.
+     */
+    public ValidateRegistrationRequestMetadata() {
+        registrationMetadataPolicyContextLookupStrategy = 
+                new ChildContextLookup<>(OIDCClientRegistrationMetadataPolicyContext.class, false);
+
+        metadataPolicyEnforcer = new DefaultMetadataPolicyEnforcer();
+    }
+    
+    /**
+     * Set the strategy that will return {@link OIDCClientRegistrationMetadataPolicyContext}.
+     * 
+     * @param strategy Strategy that will return {@link OIDCClientRegistrationMetadataPolicyContext}.
+     */
+    public void setRegistrationMetadataPolicyContextLookupStrategy(
+            @Nonnull final Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        registrationMetadataPolicyContextLookupStrategy = Constraint.isNotNull(strategy,
+                "Registration metadata policy context lookup strategy cannot be null");
+    }
+ 
+    /**
+     * Set the function used for enforcing the metadata policy.
+     * 
+     * @param function Function used for enforcing the metadata policy.
+     */
+    public void setMetadataPolicyEnforcer(
+            @Nonnull final Function<Pair<Object, MetadataPolicy>, Pair<Object, Boolean>> function) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        metadataPolicyEnforcer = Constraint.isNotNull(function, "The metadata policy enforcer cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        final MessageContext messageContext = profileRequestContext.getInboundMessageContext();
+
+        if (messageContext == null) {
+            log.debug("{} No inbound message context associated with this profile request", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        final Object message = messageContext.getMessage();
+        if (message == null || !(message instanceof OIDCClientRegistrationRequest)) {
+            log.debug("{} No inbound message associated with this profile request", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;                        
+        }
+        request = (OIDCClientRegistrationRequest) message;
+
+        
+        final OIDCClientRegistrationMetadataPolicyContext registrationMetadataPolicyContext =
+                registrationMetadataPolicyContextLookupStrategy.apply(messageContext);
+
+        if (registrationMetadataPolicyContext == null) {
+            log.debug("{} No metadata policy context associated with this request", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;                        
+        }
+        metadataPolicy = registrationMetadataPolicyContext.getMetadataPolicy();
+        if (metadataPolicy == null || metadataPolicy.isEmpty()) {
+            log.debug("{} No metadata policy found, nothing to do", getLogPrefix());
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        log.debug("{} Metadata policy to be validated: {}", getLogPrefix(), metadataPolicy);
+        boolean compliant = true;
+
+        for (final String claim : metadataPolicy.keySet()) {
+            log.trace("{} Analysing claim {}", getLogPrefix(), claim);
+            final JSONObject requestMetadata = request.getOIDCClientMetadata().toJSONObject();
+            if (requestMetadata.containsKey(claim)) {
+                final Object value = requestMetadata.get(claim);
+                log.debug("{} The claim {} set in policy included in the request", getLogPrefix(), claim);
+                final MetadataPolicy policy = metadataPolicy.get(claim);
+                final Pair<Object, MetadataPolicy> candidate = new Pair<>(value, policy);
+                final Pair<Object, Boolean> result = metadataPolicyEnforcer.apply(candidate);
+                
+                if (!result.getSecond()) {
+                    log.warn("{} the metadata claim {} is not compliant with the policy", getLogPrefix(), claim);
+                    compliant = false;
+                } else {
+                    log.trace("{} validation result is OK for claim {}", getLogPrefix(), claim);
+                }
+            }
+            
+            //TODO: store the result
+        }
+        
+        if (!compliant) {
+            log.warn("{} The requested metadata is not compliant with the policy", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+        }
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java
new file mode 100644
index 00000000..8822e6a8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.MetadataPolicyHelper;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
+
+/**
+ * <p>A function that merges two maps of metadata policies according to the rules specified in the OIDC federation spec
+ * (draft 17), section 5.1.3.1:</p>
+ * <ul>
+ * <li>
+ * subset_of: The result of merging the values of two subset_of operators is the intersection of the operator values.
+ * </li>
+ * <li>
+ * one_of: The result of merging the values of two one_of operators is the intersection of the operator values.
+ * </li>
+ * <li>
+ * superset_of: The result of merging the values of two superset_of operators is the union of the operator values.
+ * </li>
+ * <li>
+ * add: The result of merging the values of two add operators is the union of the values.
+ * </li>
+ * <li>
+ * value: Merging two value operators is NOT allowed unless the two operator values are equal.
+ * </li>
+ * <li>
+ * default: Merging two default operators is NOT allowed unless the two operator values are equal.
+ * </li>
+ * <li>
+ * essential: If a superior has specified essential=true, then a subordinate cannot change that. If a superior has
+ * specified essential=false, then a subordinate is allowed to change that to essential=true. If a superior has not
+ * specified essential, then a subordinate can set essential to true or false.
+ * </li>
+ * <p>The function returns a pair of map of merged metadata policies and a boolean indicating if the merging operators
+ * in the policies were compliant.
+ * </ul>
+ */
+public class DefaultMetadataPolicyMergingStrategy implements
+    Function<Pair<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>>,
+        Pair<Map<String, MetadataPolicy>, Boolean>> {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(DefaultMetadataPolicyMergingStrategy.class);
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public Pair<Map<String, MetadataPolicy>, Boolean> apply(
+            @Nullable final Pair<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>> input) {
+        final Map<String, MetadataPolicy> first = input.getFirst();
+        final Map<String, MetadataPolicy> second = input.getSecond();
+        if (first == null) {
+            return new Pair<>(second, Boolean.TRUE);
+        } else if (second == null || second.isEmpty()) {
+            return new Pair<>(first, Boolean.TRUE);
+        }
+        final Set<String> combinedKeys = Stream.concat(first.keySet().stream(), 
+                second.keySet().stream()).collect(Collectors.toSet());
+        final Map<String, MetadataPolicy> result = new HashMap<>();
+        boolean valid = true;
+        for (final String key : combinedKeys) {
+            try {
+                result.put(key, MetadataPolicyHelper.mergeMetadataPolicies(first.get(key), second.get(key)));
+            } catch (final ConstraintViolationException e) {
+                log.warn("Incompatible metadata policies for claim '{}' that cannot be merged.", key);
+                valid = false;
+            }
+        }
+        return new Pair<>(result, Boolean.valueOf(valid));
+    }
+
+}
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 0f8f398c..1a305cd0 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
@@ -24,6 +24,19 @@
         </constructor-arg>
     </bean>
 
+    <bean id="ValidateRegistrationAccessToken"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationAccessToken" scope="prototype"
+        p:revocationCache-ref="shibboleth.RevocationCache"
+        p:sealer-ref="#{'%{idp.oidc.dynreg.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+        p:objectMapper-ref="shibboleth.JSONObjectMapper" />
+        
+    <bean id="ValidateRegistrationRequestMetadata"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationRequestMetadata" scope="prototype" />
+        
+    <bean id="InitializeRegistrationMetadataPolicyContext"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeRegistrationMetadataPolicyContext"
+        scope="prototype" />
+
     <bean id="InitializeRelyingPartyContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeUnverifiedRelyingPartyContext"
         scope="prototype" />
@@ -172,6 +185,9 @@
                 <entry
                     key="#{T(org.opensaml.profile.action.EventIds).UNABLE_TO_DECODE}"
                     value="#{T(com.nimbusds.oauth2.sdk.client.RegistrationError).INVALID_CLIENT_METADATA}" />
+                <entry
+                    key="#{T(org.opensaml.profile.action.EventIds).INVALID_MESSAGE}"
+                    value="#{T(com.nimbusds.oauth2.sdk.client.RegistrationError).INVALID_CLIENT_METADATA}" />
             </map>
         </property>
     </bean>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-flow.xml
index 0727f8e7..14bc0bb6 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-flow.xml
@@ -16,6 +16,7 @@
         <evaluate expression="DecodeMessage" />
         <evaluate expression="PostDecodePopulateAuditContext" />
         <evaluate expression="InitializeRelyingPartyContext" />
+        <evaluate expression="ValidateRegistrationAccessToken" />
         <evaluate expression="SelectRelyingPartyConfiguration" />
         <evaluate expression="SelectProfileConfiguration" />
         <evaluate expression="'proceed'" />
@@ -24,6 +25,8 @@
     </action-state>
 
     <action-state id="OutboundContextsAndSecurityParameters">
+        <evaluate expression="InitializeRegistrationMetadataPolicyContext" />
+        <evaluate expression="ValidateRegistrationRequestMetadata" />
         <evaluate expression="CheckRedirectURIs" />
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="BuildResponse" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 913f806b..d1d4e920 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -51,8 +51,9 @@
         p:registrationValidityPeriod="%{idp.oidc.dynreg.defaultRegistrationValidity:PT24H}"
         p:secretExpirationPeriod="%{idp.oidc.dynreg.defaultSecretExpiration:P12M}"
         p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
-        p:claimsValidator-ref="DefaultJWTClaimsValidator" />
-        
+        p:claimsValidator-ref="DefaultJWTClaimsValidator"
+        p:metadataPolicyLookupStrategy-ref="shibboleth.oidc.dynreg.DefaultMetadataPolicyLookupStrategy" />
+    
     <bean id="OIDC.Configuration" parent="AbstractOIDCProfile" lazy-init="true"
         class="net.shibboleth.oidc.profile.config.OIDCProviderInformationConfiguration"
         p:issuer-ref="issuer" />
@@ -315,6 +316,11 @@
             <bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="secretExpirationPeriod"
                 p:defaultValue="%{idp.oidc.dynreg.defaultSecretExpiration:P12M}" />
         </property>
+        <property name="metadataPolicyLookupStrategy">
+            <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="metadataPolicy"
+                p:propertyType="#{T(java.util.Map)}"
+                p:defaultValue-ref="%{idp.oidc.dynreg.defaultMetadataPolicy:shibboleth.oidc.dynreg.DefaultMetadataPolicy}" />
+        </property>
     </bean>
     
     <bean id="OIDC.Configuration.MDDriven" parent="AbstractMDDrivenOIDCProfile" lazy-init="true"
@@ -636,5 +642,67 @@
     <!--  Encryption keys to publish. -->    
     <bean id="shibboleth.oidc.PublishKeySetRequestObjectDecryptionConfiguration" parent="shibboleth.BasicEncryptionConfiguration"
         p:keyTransportEncryptionCredentials="#{getObject('shibboleth.oidc.EncryptionCredentialsToPublish') ?: getObject('shibboleth.oidc.EncryptionCredentials')}"/>
-    
+
+    <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyLookupStrategy"
+        class="net.shibboleth.oidc.profile.config.navigate.ResolverBasedRegistrationMetadataPolicyLookupFunction"
+        p:metadataPolicyResolver-ref="shibboleth.oidc.dynreg.DefaultMetadataPolicyResolver" />
+
+    <bean id="shibboleth.oidc.dynreg.BaseMetadataPolicyResolver" abstract="true"
+        class="net.shibboleth.oidc.metadata.policy.impl.OIDCMetadataPolicyResolver" />
+
+    <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyResolver"
+        parent="shibboleth.oidc.dynreg.BaseMetadataPolicyResolver">
+        <constructor-arg>
+            <bean parent="shibboleth.oidc.dynreg.BatchCacheBuilder">
+                <constructor-arg>
+                    <bean p:cacheId="DefaultPolicyFileCache" parent="shibboleth.oidc.dynreg.BatchMetadataCacheBuilderSpec">
+                        <property name="loadingStrategy">
+                            <bean class="net.shibboleth.oidc.metadata.cache.impl.DefaultFileLoadingStrategy"
+                                c:metadata="%{idp.oidc.dynreg.defaultMetadataPolicyFile:}" />
+                        </property>
+                    </bean>
+                </constructor-arg>
+            </bean>
+        </constructor-arg>
+    </bean>
+
+    <!--  Batch OIDC provider cache builder factory and template specification -->
+    <bean id="batchCacheFactory" class="net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilder$Builder"/>
+
+    <bean id="shibboleth.oidc.dynreg.BatchCacheBuilder" factory-bean="batchCacheFactory" factory-method="build"
+        abstract="true"/>
+
+    <bean class="net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilderSpec" 
+        id="shibboleth.oidc.dynreg.BatchMetadataCacheBuilderSpec" abstract="true" 
+        p:parsingStrategy-ref="shibboleth.oidc.dynreg.DefaultJSONMapParsingStrategy"
+        p:criteriaToIdentifierStrategy-ref="shibboleth.oidc.dynreg.DefaultMetadataCriteriaToIdentifierStrategy"
+        p:sourceMetadataExpiryStrategy-ref="shibboleth.oidc.dynreg.DefaultExpirationTimeStrategy"
+        p:identifierExtractionStrategy-ref="shibboleth.oidc.dynreg.DefaultMetadataPolicyIdentifierExtractionStrategy"
+        p:metadataValidPredicate-ref="shibboleth.oidc.dynreg.DefaultMetadataPolicyValidator"/>
+
+    <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyValidator"
+        class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyValidator" />
+
+    <bean id="shibboleth.oidc.dynreg.DefaultJSONMapParsingStrategy"
+        class="net.shibboleth.oidc.metadata.cache.impl.DefaultJSONMapParsingStrategy"
+        c:valueClass="net.shibboleth.oidc.metadata.policy.MetadataPolicy"/>
+
+    <bean id="shibboleth.oidc.dynreg.DefaultMetadataCriteriaToIdentifierStrategy"
+       parent="shibboleth.Functions.Constant" c:target="DefaultMetadataPolicy" />
+
+    <bean id="shibboleth.oidc.dynreg.DefaultExpirationTimeStrategy"
+        class="net.shibboleth.oidc.metadata.cache.impl.DefaultSourceMetadataExpirationTimeStrategy" c:duration="PT10M"/>
+
+    <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyIdentifierExtractionStrategy"
+       parent="shibboleth.Functions.Constant" c:target="DefaultMetadataPolicy" />
+
+    <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicy"
+            factory-bean="shibboleth.oidc.dynreg.DefaultMetadataPolicyResolver"
+            factory-method="resolveSingle">
+        <constructor-arg>
+            <!-- Empty criteria set is assumed to be ok for the resolver -->
+            <bean class="net.shibboleth.utilities.java.support.resolver.CriteriaSet" />
+        </constructor-arg>
+    </bean>
+
 </beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
index f3f23600..387f3ec9 100644
--- a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
@@ -16,6 +16,10 @@ idp.oidc.issuer = https://your.issuer.example.org
 #idp.oidc.dynreg.allowNoneForRequestSigning = true
 # Bean to determine whether dynamic registration should validate the remote JWK set if it's defined in the request
 #idp.oidc.dynreg.validateRemoteJwks = shibboleth.Conditions.TRUE
+# Full path to the file containing default metadata policy used for dynamic client registration
+#idp.oidc.dynreg.defaultMetadataPolicyFile = 
+# Bean to determine the default metadata policy used for dynamic client registration
+#idp.oidc.dynreg.defaultMetadataPolicy = shibboleth.oidc.dynreg.DefaultMetadataPolicy
 
 # Storage for storing remote jwk sets.
 #idp.oidc.jwk.StorageService = shibboleth.StorageService
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 d25d10d4..3c9b1810 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
@@ -19,6 +19,8 @@ package net.shibboleth.idp.plugin.oidc.op.profile.flow;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
 
 import org.opensaml.storage.StorageService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -28,13 +30,17 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 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;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 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.
@@ -68,7 +74,21 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
     }
     
     @Test
-    public void testSuccess() throws ParseException, IOException, net.minidev.json.parser.ParseException {
+    public void testUnauthenticated_nonCompliantWithProfilePolicy1() {
+        setJsonRequest("POST", "{ \"redirect_uris\":[\"https://not.compliant.org/cb\"] }");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_client_metadata");
+    }
+
+    @Test
+    public void testUnauthenticated_nonCompliantWithProfilePolicy2() {
+        setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"], \"id_token_signed_response_alg\":\"HS256\" }");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_client_metadata");
+    }
+
+    @Test
+    public void testUnauthenticated_success() throws ParseException, IOException, net.minidev.json.parser.ParseException {
         setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"] }");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         OIDCClientInformationResponse parsedResponse = parseSuccessResponse(result, OIDCClientInformationResponse.class);
@@ -85,4 +105,54 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
         Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
     }
 
+    @Test
+    public void testAccessToken_nonCompliantWithProfilePolicy1() throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+        setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"] }");
+        request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://invalid.domain.org/cb\"]")
+                .toAuthorizationHeader());
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_client_metadata");
+    }
+
+    @Test
+    public void testAccessToken_nonCompliantWithProfilePolicy2() throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+        setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"], \"id_token_signed_response_alg\":\"HS256\" }");
+        request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://example.org/cb\"]")
+                .toAuthorizationHeader());
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_client_metadata");
+    }
+
+    @Test
+    public void testAccessToken_success() throws ParseException, IOException, net.minidev.json.parser.ParseException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+        setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"] }");
+        request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://example.org/cb\"]")
+                .toAuthorizationHeader());
+        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, 
+                clientInfo.getID().toString()).getValue();
+        Assert.assertNotNull(record);
+        JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);
+        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));
+    }
+
+    protected BearerAccessToken buildRegistrationAccessToken(final String redirectUriSubset) throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+        final String json = "{" +
+                "\"prncpl\":\"jdoe\"," +
+                "\"type\":\"rat\"," +
+                "\"exp\":" + Instant.now().plusSeconds(30).getEpochSecond() + "," +
+                "\"iat\":" + Instant.now().getEpochSecond() + "," +
+                "\"jti\":\"" + idGenerator.generateIdentifier() + "\"," + 
+                "\"rp_id\":\"mockRpId\"," + 
+                "\"metadata\":{\"redirect_uris\":{\"subset_of\":" + redirectUriSubset + "}}}";
+        return new BearerAccessToken(BaseOIDCResponseActionTest.initializeDataSealer().wrap(json,
+                Instant.now().plusSeconds(30)));
+    }
 }
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/metadata-policy1.json b/idp-oidc-extension-impl/src/test/resources/conf/metadata-policy1.json
new file mode 100644
index 00000000..c5945dfb
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/conf/metadata-policy1.json
@@ -0,0 +1,21 @@
+{
+    "grant_types": {
+        "one_of": ["authorization_code","implicit"]
+    },
+    "client_name": {
+        "default": "A known test application"
+    },
+    "organization_name": {
+        "value": "A trusted organization"
+    },
+    "redirect_uris": {
+        "regexp": "^https:\/\/(?:([^.]+).)?example.org\/(.*)",
+        "essential": true
+    },
+    "id_token_signed_response_alg": {
+        "subset_of": ["RS256", "RS384", "RS512"]
+    },
+    "scopes": {
+        "subset_of": ["openid", "profile", "email", "phone"]
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties b/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties
index 8dd2e620..0d00d608 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties
@@ -5,3 +5,5 @@ idp.oidc.issuer = https://op.example.org
 idp.oidc.subject.sourceAttribute = uid
 idp.oidc.subject.salt = isfd07fsddfs70sdf9d99s8
 idp.oidc.discovery.template = src/test/resources/conf/openid-configuration.json
+
+idp.oidc.dynreg.defaultMetadataPolicyFile = src/test/resources/conf/metadata-policy1.json
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
index 4de25ce5..5eb74533 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
@@ -46,6 +46,7 @@
                 <ref bean="OIDC.SSO.MDDriven" />
                 <ref bean="OIDC.Token.MDDriven" />
                 <ref bean="OIDC.UserInfo.MDDriven" />
+                <ref bean="OIDC.Registration.MDDriven" />
                 <ref bean="OAUTH2.Revocation.MDDriven" />
                 <ref bean="OAUTH2.Introspection.MDDriven" />
             </list>

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


More information about the commits mailing list