[java-idp-oidc] branch main updated: JOIDC-196 - Enhance metadata and unregistered client policy config options

Henri Mikkonen henri.mikkonen at iki.fi
Thu Feb 15 11:28:12 UTC 2024


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=efbb3313dd26b94f83c1d97ba86710ff23f9c14d

The following commit(s) were added to refs/heads/main by this push:
     new efbb3313 JOIDC-196 - Enhance metadata and unregistered client policy config options
efbb3313 is described below

commit efbb3313dd26b94f83c1d97ba86710ff23f9c14d
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Feb 15 13:22:15 2024 +0200

    JOIDC-196 - Enhance metadata and unregistered client policy config options
    
    https://shibboleth.atlassian.net/browse/JOIDC-196
    
    - The default map of custom metadata policy operators: shibboleth.oidc.DefaultMetadataPolicyCustomOperators
      - contains the 'bean' operator in addition to the standard ones
    
    - The configuration beans for the unregistered client policies:
      - shibboleth.oidc.DefaultUnregisteredClientPolicyValidator - can be replaced via shibboleth.oidc.UnregisteredClientPolicyValidator
      - shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer - can be replaced via shibboleth.oidc.UnregisteredClientPolicyEnforcer
      - The default map of policy operators can be replaced via shibboleth.oidc.UnregisteredClientPolicyCustomOperators
    
    - The configuration beans for the metadata policies used in dynamic client registration:
      - shibboleth.oidc.dynreg.DefaultMetadataPolicyValidator - can be replaced via shibboleth.oidc.dynreg.MetadataPolicyValidator
      - shibboleth.oidc.dynreg.DefaultMetadataPolicyEnforcer - can be replaced via shibboleth.oidc.dynreg.MetadataPolicyEnforcer
      - The default map of policy operators can be replaced via shibboleth.oidc.dynreg.MetadataPolicyCustomOperators
    
    - Refactored ValidateResponseType to apply policy enforcer directly to the requested value
      - Deprecated DefaultUnregisteredClientResponseTypesLookupFunction
    
    TODO: custom operators with registration access token + merging with the profile policy
---
 .../oauth2/profile/impl/ValidateRedirectURI.java   |  18 +++-
 .../oauth2/profile/impl/ValidateResponseType.java  |  88 +++++++++++-----
 ...nitializeRegistrationMetadataPolicyContext.java |  21 ++--
 ...registeredClientAllowedScopeLookupFunction.java |  30 +++++-
 .../DefaultUnregisteredClientPolicyValidator.java  |  41 ++++----
 ...egisteredClientResponseTypesLookupFunction.java |   2 +
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  31 +++++-
 .../oauth2/introspection/introspection-beans.xml   |   3 +-
 .../flows/oauth2/revocation/revocation-beans.xml   |   3 +-
 .../idp/flows/oidc/authorize/authorize-beans.xml   |  14 +--
 .../idp/flows/oidc/register/register-beans.xml     |   7 +-
 .../idp/flows/oidc/token/token-beans.xml           |   6 +-
 .../idp/flows/oidc/userinfo/userinfo-beans.xml     |   3 +-
 .../idp/service/relying-party/postconfig.xml       |   3 -
 .../profile/impl/ValidateRedirectURITest.java      |   2 +
 .../profile/impl/ValidateResponseTypeTest.java     |   8 +-
 .../AlwaysFalseCustomMetadataPolicyOperator.java   |  33 ++++++
 .../AlwaysTrueCustomMetadataPolicyOperator.java    |  33 ++++++
 .../oidc/op/profile/flow/RegistrationFlowTest.java |  20 ++++
 ...steredClientAllowedScopeLookupFunctionTest.java |  32 ++++++
 ...faultUnregisteredClientPolicyValidatorTest.java | 115 ++++++++++++++++++++-
 .../net/shibboleth/idp/module/conf/global.xml      |   4 +
 .../idp/module/conf/metadata-policy-failing.json   |   5 +
 .../module/conf/metadata-policy-invalidbean.json   |   5 +
 .../shibboleth/idp/module/conf/relying-party.xml   |  24 +++++
 .../idp/module/conf/unregistered-policy.json       |   3 +-
 26 files changed, 470 insertions(+), 84 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRedirectURI.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRedirectURI.java
index c6d3b719..69b47553 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRedirectURI.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRedirectURI.java
@@ -31,12 +31,14 @@ import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequest
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultValidRedirectUrisLookupFunction;
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 import net.shibboleth.oidc.metadata.policy.UnregisteredClientPolicy;
-import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer;
 import net.shibboleth.oidc.profile.config.navigate.UnregisteredClientPolicyLookupFunction;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
 
 /**
  * Action that validates redirect uri is expected. Validated redirect uri is stored to response context.
@@ -60,7 +62,7 @@ public class ValidateRedirectURI extends AbstractOAuthAuthorizationResponseActio
         unregisteredClientPolicyLookupStrategy;
 
     /** Enforcer function for validating redirect URI against the configured policy. */
-    @Nonnull private BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> unregisteredClientPolicyEnforcer;
+    @NonnullAfterInit private BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> unregisteredClientPolicyEnforcer;
 
     /** Whether to require redirect uri value in the request also when only single value is registered. */
     private boolean requireRequestedValue = true;
@@ -73,7 +75,6 @@ public class ValidateRedirectURI extends AbstractOAuthAuthorizationResponseActio
         validRedirectURIsLookupStrategy = new DefaultValidRedirectUrisLookupFunction();
         registeredRedirectURIsLookupStrategy = new DefaultValidRedirectUrisLookupFunction();
         unregisteredClientPolicyLookupStrategy = new UnregisteredClientPolicyLookupFunction();
-        unregisteredClientPolicyEnforcer = new DefaultMetadataPolicyEnforcer();
     }
 
     /**
@@ -145,6 +146,15 @@ public class ValidateRedirectURI extends AbstractOAuthAuthorizationResponseActio
         unregisteredClientPolicyEnforcer = Constraint.isNotNull(enforcer, "Unregistered client policy cannot be null");
     }
 
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (unregisteredClientPolicyEnforcer == null) {
+            throw new ConstraintViolationException("Unregistered client policy enforcer cannot be null");
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -205,7 +215,7 @@ public class ValidateRedirectURI extends AbstractOAuthAuthorizationResponseActio
      * @param profileRequestContext profile request context
      * @param validRedirectionURIs set of valid redirection uris
      */
-    protected void handleNullRequestedURI(final ProfileRequestContext profileRequestContext,
+    protected void handleNullRequestedURI(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull @NotEmpty final Set<URI> validRedirectionURIs) {
         if (requireRequestedValue) {
             log.warn("{} Redirection URI of the request not located for verification", getLogPrefix());
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateResponseType.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateResponseType.java
index 21fc6523..a44bbebe 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateResponseType.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateResponseType.java
@@ -14,7 +14,9 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
 
+import java.util.Map;
 import java.util.Set;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -23,18 +25,23 @@ import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
 import com.nimbusds.oauth2.sdk.ResponseType;
 
-import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultUnregisteredClientResponseTypesLookupFunction;
+import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestResponseTypeLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultValidResponseTypesLookupFunction;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.UnregisteredClientPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer;
+import net.shibboleth.oidc.profile.config.navigate.UnregisteredClientPolicyLookupFunction;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
- * An action that validates the requested response_type is (1) registered to the requesting RP and (2) valid for the
- * protocol used for request.
+ * An action that validates the requested response_type is (1) registered to the requesting RP (or accepted in the
+ * policy for unregistered clients) and (2) valid for the protocol used for request.
  */
 public class ValidateResponseType extends AbstractOAuthAuthorizationResponseAction {
 
@@ -42,14 +49,17 @@ public class ValidateResponseType extends AbstractOAuthAuthorizationResponseActi
     @Nonnull private Logger log = LoggerFactory.getLogger(ValidateResponseType.class);
 
     /** Lookup strategy for fetching the requested response type. */
-    @NonnullAfterInit private Function<ProfileRequestContext, ResponseType> requestedResponseTypeLookupStrategy;
+    @Nonnull private Function<ProfileRequestContext, ResponseType> requestedResponseTypeLookupStrategy;
 
     /** Lookup strategy for fetching the valid response types matching the request message. */
-    @NonnullAfterInit private Function<ProfileRequestContext, Set<ResponseType>> validResponseTypesLookupStrategy;
+    @Nonnull private Function<ProfileRequestContext, Set<ResponseType>> validResponseTypesLookupStrategy;
+
+    /** Strategy used to locate the unregistered client policy configured for the profile. */
+    @Nonnull private Function<ProfileRequestContext, Map<String, UnregisteredClientPolicy>>
+        unregisteredClientPolicyLookupStrategy;
 
-    /** Lookup strategy for fetching the valid response types for unregistered clients. */
-    @NonnullAfterInit private Function<ProfileRequestContext, Set<ResponseType>>
-        unregisteredClientValidResponseTypesLookupStrategy;
+    /** Enforcer function for validating response type against the configured policy. */
+    @Nonnull private BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> unregisteredClientPolicyEnforcer;
 
     /**
      * Constructor.
@@ -57,8 +67,8 @@ public class ValidateResponseType extends AbstractOAuthAuthorizationResponseActi
     public ValidateResponseType() {
         requestedResponseTypeLookupStrategy = new DefaultRequestResponseTypeLookupFunction();
         validResponseTypesLookupStrategy = new DefaultValidResponseTypesLookupFunction();
-        unregisteredClientValidResponseTypesLookupStrategy =
-                new DefaultUnregisteredClientResponseTypesLookupFunction();
+        unregisteredClientPolicyLookupStrategy = new UnregisteredClientPolicyLookupFunction();
+        unregisteredClientPolicyEnforcer = new DefaultMetadataPolicyEnforcer();
     }
 
     /**
@@ -84,30 +94,60 @@ public class ValidateResponseType extends AbstractOAuthAuthorizationResponseActi
     }
 
     /**
-     * Set the lookup strategy for fetching the valid response types for unregistered clients.
+     * Set the strategy used to locate the unregistered client policy configured for the profile.
      * 
-     * @param strategy What to set.
+     * @param strategy lookup strategy
      */
-    public void setUnregisteredClientValidResponseTypesLookupStrategy(@Nonnull final
-            Function<ProfileRequestContext, Set<ResponseType>> strategy) {
+    public void setUnregisteredClientPolicyLookupStrategy(
+            final Function<ProfileRequestContext, Map<String, UnregisteredClientPolicy>> strategy) {
         checkSetterPreconditions();
-        unregisteredClientValidResponseTypesLookupStrategy = Constraint.isNotNull(strategy,
-                "Validation strategy cannot be null");
+
+        unregisteredClientPolicyLookupStrategy = Constraint.isNotNull(strategy,
+                "Unregistered client policy lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the enforcer function for validating response type against the configured policy.
+     * 
+     * @param enforcer policy enforcer
+     */
+    public void setUnregisteredClientPolicyEnforcer(
+            final BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> enforcer) {
+        checkSetterPreconditions();
+
+        unregisteredClientPolicyEnforcer = Constraint.isNotNull(enforcer, "Unregistered client policy cannot be null");
     }
 
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
-        final Set<ResponseType> registeredTypes = getMetadataContext() != null
-                    ? getMetadataContext().getClientInformation().getMetadata().getResponseTypes()
-                            : unregisteredClientValidResponseTypesLookupStrategy.apply(profileRequestContext);
-
         final ResponseType requestedType = requestedResponseTypeLookupStrategy.apply(profileRequestContext);
 
-        if (registeredTypes == null || registeredTypes.isEmpty() || !registeredTypes.contains(requestedType)) {
-            log.warn("{} The response type {} is not registered for this RP", getLogPrefix(), requestedType);
-            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_RESPONSE_TYPE);
+        if (getMetadataContext() != null) {
+            final Set<ResponseType> registeredTypes =
+                    getMetadataContext().getClientInformation().getMetadata().getResponseTypes();
+            if (registeredTypes == null || registeredTypes.isEmpty() || !registeredTypes.contains(requestedType)) {
+                log.warn("{} The response type {} is not registered for this RP", getLogPrefix(), requestedType);
+                ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_RESPONSE_TYPE);
+                return;
+            }
+        } else {
+            final Map<String, UnregisteredClientPolicy> policies =
+                    unregisteredClientPolicyLookupStrategy.apply(profileRequestContext);
+            if (policies == null || policies.isEmpty()) {
+                log.warn("{} No policy was resolved even though the RP was not registered", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+                return;
+            }
+            final Pair<Object, Boolean> enforcerResult =
+                    unregisteredClientPolicyEnforcer.apply(requestedType == null ? "" : requestedType.toString(),
+                            policies.get("response_type"));
+            if (!enforcerResult.getSecond()) {
+                log.warn("{} The response type {} is not compliant with the policy", getLogPrefix(), requestedType);
+                ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_RESPONSE_TYPE);
+                return;
+            }
         }
 
         final Set<ResponseType> validTypes = validResponseTypesLookupStrategy.apply(profileRequestContext);
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 af3fff85..1f650623 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
@@ -35,10 +35,10 @@ import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCCli
 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.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
@@ -52,19 +52,19 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
     @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>>
+    @Nonnull 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>>
+    @Nonnull private Function<ProfileRequestContext, Map<String, MetadataPolicy>>
         tokenMetadataPolicyLookupStrategy;
     
     /** The strategy used to create or locate the metadata policy context. */
-    @NonnullAfterInit private Function<ProfileRequestContext,OIDCClientRegistrationMetadataPolicyContext>
+    @Nonnull private Function<ProfileRequestContext,OIDCClientRegistrationMetadataPolicyContext>
         registrationPolicyContextCreationStrategy;
     
     /** The strategy used for merging profile and token based metadata policies. */
-    @NonnullAfterInit private BiFunction<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>,
+    @Nonnull private BiFunction<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>,
         Pair<Map<String, MetadataPolicy>, Boolean>> metadataPolicyMergingStrategy;
     
     /** The strategy used for validating token and merged metadata policies. */
@@ -83,7 +83,6 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
                 new ChildContextLookup<>(OIDCClientRegistrationMetadataPolicyContext.class, true).compose(
                         new InboundMessageContextLookup());
         metadataPolicyMergingStrategy = new DefaultMetadataPolicyMergingStrategy();
-        metadataPolicyValidationStrategy = new DefaultMetadataPolicyValidator();
     }
 
     /**
@@ -149,7 +148,15 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
         metadataPolicyValidationStrategy = Constraint.isNotNull(strategy,
                 "Metadata policy validation strategy cannot be null");
     }
-    
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        if (metadataPolicyValidationStrategy == null) {
+            throw new ComponentInitializationException("Metadata policy validation strategy cannot be null");
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientAllowedScopeLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientAllowedScopeLookupFunction.java
index c2f23003..cae215ab 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientAllowedScopeLookupFunction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientAllowedScopeLookupFunction.java
@@ -16,6 +16,7 @@ package net.shibboleth.idp.plugin.oidc.op.profile.logic;
 
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -27,8 +28,9 @@ import com.nimbusds.oauth2.sdk.Scope;
 
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 import net.shibboleth.oidc.metadata.policy.UnregisteredClientPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer;
 import net.shibboleth.oidc.profile.config.navigate.UnregisteredClientPolicyLookupFunction;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
@@ -40,14 +42,18 @@ import net.shibboleth.shared.logic.Constraint;
 public class DefaultUnregisteredClientAllowedScopeLookupFunction implements Function<ProfileRequestContext, Scope> {
 
     /** Strategy used to locate the unregistered client policy. */
-    @NonnullAfterInit private Function<ProfileRequestContext, Map<String, UnregisteredClientPolicy>>
+    @Nonnull private Function<ProfileRequestContext, Map<String, UnregisteredClientPolicy>>
         unregisteredClientPolicyLookupStrategy;
 
+    /** Enforcer function for fetching allowed scope via the configured policy. */
+    @Nonnull private BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> unregisteredClientPolicyEnforcer;
+
     /**
      * Constructor.
      */
     public DefaultUnregisteredClientAllowedScopeLookupFunction() {
         unregisteredClientPolicyLookupStrategy = new UnregisteredClientPolicyLookupFunction();
+        unregisteredClientPolicyEnforcer = new DefaultMetadataPolicyEnforcer();
     }
     
     /**
@@ -60,18 +66,32 @@ public class DefaultUnregisteredClientAllowedScopeLookupFunction implements Func
         unregisteredClientPolicyLookupStrategy = Constraint.isNotNull(strategy,
                 "Unregistered client policy lookup strategy cannot be null");
     }
-    
+
+    /**
+     * Set the enforcer function for fetching the allowed scope via the configured policy.
+     * 
+     * @param enforcer policy enforcer
+     */
+    public void setUnregisteredClientPolicyEnforcer(
+            final BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> enforcer) {
+        unregisteredClientPolicyEnforcer = Constraint.isNotNull(enforcer, "Unregistered client policy cannot be null");
+    }
+
     @Override @Nonnull
     public Scope apply(@Nullable final ProfileRequestContext input) {
         final Map<String, UnregisteredClientPolicy> policies = unregisteredClientPolicyLookupStrategy.apply(input);
         if (policies == null || policies.isEmpty()) {
             return new Scope();
         }
-        return Optional.ofNullable((MetadataPolicy) policies.get("scope"))
-                .map(policy -> policy.getValue())
+        final Scope scope = Optional.ofNullable((MetadataPolicy) policies.get("scope"))
+                .map(policy -> unregisteredClientPolicyEnforcer.apply(null, policy))
+                .filter(value -> value != null)
+                .filter(value -> value.getSecond())
+                .map(value -> value.getFirst())
                 .filter(value -> value != null)
                 .map(value -> value.toString())
                 .map(Scope::parse)
                 .orElse(new Scope());
+        return scope == null ? new Scope() : scope;
     }
 }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientPolicyValidator.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientPolicyValidator.java
index 4737d435..0f201f09 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientPolicyValidator.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientPolicyValidator.java
@@ -32,9 +32,9 @@ import net.shibboleth.shared.component.AbstractInitializableComponent;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
- * Default validator for the unregistered client policies. Scope must be specified vie value -operator. Response types
- * must be specified via one_of -operator. Client ID and redirect URIs must be specified via one_of or regexp
- * -operators.
+ * Default validator for the unregistered client policies. Scope must be specified via value and/or custom operators.
+ * Response types must be specified via one_of and/or custom operators. Client ID and redirect URIs must be specified
+ * via one_of, regexp or custom operators.
  * 
  * @since 3.0.0
  */
@@ -98,16 +98,16 @@ public class DefaultUnregisteredClientPolicyValidator extends AbstractInitializa
                     break;
                 case "redirect_uri":
                     if (!Optional.ofNullable(policy)
-                            .filter(responseType -> preChecksForMandatoryKey(responseType, "redirect_uri"))
-                            .filter(responseType -> verifyRedirectUriPolicy(responseType))
+                            .filter(redirectUri -> preChecksForMandatoryKey(redirectUri, "redirect_uri"))
+                            .filter(redirectUri -> verifyRedirectUriPolicy(redirectUri))
                             .isPresent()) {
                         return false;
                     }
                     break;
                 case "client_id":
                     if (!Optional.ofNullable(policy)
-                            .filter(responseType -> preChecksForMandatoryKey(responseType, "client_id"))
-                            .filter(responseType -> verifyClientIdPolicy(responseType))
+                            .filter(clientId -> preChecksForMandatoryKey(clientId, "client_id"))
+                            .filter(clientId -> verifyClientIdPolicy(clientId))
                             .isPresent()) {
                         return false;
                     }
@@ -126,12 +126,12 @@ public class DefaultUnregisteredClientPolicyValidator extends AbstractInitializa
      * @return true if verified, false otherwise.
      */
     protected boolean verifyScopePolicy(@Nonnull final MetadataPolicy policy) {
-        if (policy.getOneOfValues() != null || policy.getRegexp() != null || policy.getValue() == null) {
-            log.error("Only 'value' should be used together with 'scope' in the policy");
+        if (policy.getOneOfValues() != null || policy.getRegexp() != null) {
+            log.error("Only 'value' or custom operators should be used together with 'scope' in the policy");
             return false;
         }
-        if (!(policy.getValue() instanceof String)) {
-            log.error("Only string values can be specified for 'scope' in the policy");
+        if (!(policy.getValue() instanceof String) && policy.getCustomOperators().isEmpty() ) {
+            log.error("Only string 'value' or custom operators can be specified for 'scope' in the policy");
             return false;
         }
         return true;
@@ -143,12 +143,13 @@ public class DefaultUnregisteredClientPolicyValidator extends AbstractInitializa
      * @return true if verified, false otherwise.
      */
     protected boolean verifyResponseTypePolicy(@Nonnull final MetadataPolicy policy) {
-        if (policy.getRegexp() != null || policy.getValue() != null || policy.getOneOfValues() == null) {
-            log.error("Only 'one_of' should be used together with 'response_type' in the policy");
+        if (policy.getRegexp() != null || policy.getValue() != null) {
+            log.error("Only 'one_of' or custom operators should be used together with 'response_type' in the policy");
             return false;
         }
-        if (policy.getOneOfValues().isEmpty()) {
-            log.error("No 'one_of' values specified for 'response_type' in the policy");
+        if ((policy.getOneOfValues() == null || policy.getOneOfValues().isEmpty())
+                && policy.getCustomOperators().isEmpty()) {
+            log.error("No 'one_of' or custom operator values specified for 'response_type' in the policy");
             return false;
         }
         return true;
@@ -164,8 +165,9 @@ public class DefaultUnregisteredClientPolicyValidator extends AbstractInitializa
             log.error("'value' cannot be specified for 'redirect_uri' in the policy");
             return false;
         }
-        if ((policy.getOneOfValues() == null || policy.getOneOfValues().isEmpty()) && policy.getRegexp() == null) {
-            log.error("No 'one_of' or 'regexp' values specified for 'redirect_uri' in the policy");
+        if ((policy.getOneOfValues() == null || policy.getOneOfValues().isEmpty()) && policy.getRegexp() == null
+                && policy.getCustomOperators().isEmpty()) {
+            log.error("No 'one_of' or 'regexp' or custom operator values specified for 'redirect_uri' in the policy");
             return false;
         }
         return true;
@@ -181,8 +183,9 @@ public class DefaultUnregisteredClientPolicyValidator extends AbstractInitializa
             log.error("'value' cannot be specified for 'client_id' in the policy");
             return false;
         }
-        if ((policy.getOneOfValues() == null || policy.getOneOfValues().isEmpty()) && policy.getRegexp() == null) {
-            log.error("No 'one_of' or 'regexp' values specified for 'client_id' in the policy");
+        if ((policy.getOneOfValues() == null || policy.getOneOfValues().isEmpty()) && policy.getRegexp() == null
+                && policy.getCustomOperators().isEmpty()) {
+            log.error("No 'one_of' or 'regexp' or custom operator values specified for 'client_id' in the policy");
             return false;
         }
         return true;
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientResponseTypesLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientResponseTypesLookupFunction.java
index 8b223561..26dbc593 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientResponseTypesLookupFunction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientResponseTypesLookupFunction.java
@@ -38,7 +38,9 @@ import net.shibboleth.shared.primitive.StringSupport;
  * Default function for fetching allowed response types via unregistered client policy.
  * 
  * @since 3.0.0
+ * @deprecated The response type validation is handled against the policy in the SWF action
  */
+ at Deprecated(forRemoval = true)
 public class DefaultUnregisteredClientResponseTypesLookupFunction extends AbstractInitializableComponent
         implements Function<ProfileRequestContext, Set<ResponseType>> {
     
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 0e158895..919dbf7f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -233,7 +233,8 @@
     <bean id="shibboleth.oidc.DefaultAllowedScopeStrategy"
         class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultAllowedScopeLookupFunction">
         <property name="unregisteredClientAllowedScopeLookupStrategy">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultUnregisteredClientAllowedScopeLookupFunction">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultUnregisteredClientAllowedScopeLookupFunction"
+                p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}">
                 <property name="unregisteredClientPolicyLookupStrategy">
                     <bean class="net.shibboleth.oidc.profile.config.navigate.UnregisteredClientPolicyLookupFunction" />
                 </property>
@@ -705,10 +706,15 @@
         p:criteriaToIdentifierStrategy-ref="shibboleth.oidc.UnregisteredDefaultMetadataCriteriaToIdentifierStrategy"
         p:sourceMetadataExpiryStrategy-ref="shibboleth.oidc.UnregisteredDefaultExpirationTimeStrategy"
         p:identifierExtractionStrategy-ref="shibboleth.oidc.DefaultUnregisteredClientPolicyIdentifierExtractionStrategy"
-        p:metadataValidPredicate-ref="shibboleth.oidc.DefaultUnregisteredClientPolicyValidator"/>
+        p:metadataValidPredicate="#{getObject('shibboleth.oidc.UnregisteredClientPolicyValidator') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyValidator')}"/>
 
     <bean id="shibboleth.oidc.DefaultUnregisteredClientPolicyValidator"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultUnregisteredClientPolicyValidator" />
+        class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultUnregisteredClientPolicyValidator">
+        <property name="syntaxValidator">
+            <bean class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyValidator"
+                p:customMetadataPolicyOperators="#{getObject('shibboleth.oidc.UnregisteredClientPolicyCustomOperators') ?: getObject('shibboleth.oidc.DefaultMetadataPolicyCustomOperators')}"/>
+        </property>
+    </bean>
 
     <bean id="shibboleth.oidc.UnregisteredDefaultJSONMapParsingStrategy"
         class="net.shibboleth.oidc.metadata.cache.impl.DefaultJSONMapParsingStrategy"
@@ -752,4 +758,23 @@
         </constructor-arg>
     </bean>
 
+    <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyValidator"
+        class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyValidator"
+        p:customMetadataPolicyOperators="#{getObject('shibboleth.oidc.dynreg.MetadataPolicyCustomOperators') ?: getObject('shibboleth.oidc.DefaultMetadataPolicyCustomOperators')}"/>
+
+    <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyEnforcer"
+        class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer"
+        p:customMetadataPolicyOperators="#{getObject('shibboleth.oidc.dynreg.MetadataPolicyCustomOperators') ?: getObject('shibboleth.oidc.DefaultMetadataPolicyCustomOperators')}"/>
+
+    <bean id="shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer"
+        class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer"
+        p:customMetadataPolicyOperators="#{getObject('shibboleth.oidc.UnregisteredClientPolicyCustomOperators') ?: getObject('shibboleth.oidc.DefaultMetadataPolicyCustomOperators')}"/>
+
+    <util:map id="shibboleth.oidc.DefaultMetadataPolicyCustomOperators"
+        value-type="net.shibboleth.oidc.metadata.policy.impl.CustomMetadataPolicyOperator">
+        <entry key="bean">
+            <bean class="net.shibboleth.oidc.metadata.policy.impl.BeanMetadataPolicyOperator" />
+        </entry>
+    </util:map>
+
 </beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
index 7a64d20d..ddd835d9 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
@@ -34,7 +34,8 @@
     <bean id="ValidateClientIDAgainstPolicy"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateClientIDAgainstPolicy"
         p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
-        scope="prototype" />
+        scope="prototype"
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
 
     <bean id="ProcessTokenForIntrospection"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ProcessTokenForIntrospection" scope="prototype"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
index 6872ef61..28236519 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
@@ -34,7 +34,8 @@
     <bean id="ValidateClientIDAgainstPolicy"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateClientIDAgainstPolicy"
         p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
-        scope="prototype" />
+        scope="prototype"
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
 
     <bean id="ProcessTokenForRevocation"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ProcessTokenForRevocation" scope="prototype"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 51fc17f5..e2ce0c4f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -75,7 +75,8 @@
     <bean id="ValidateClientIDAgainstPolicy"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateClientIDAgainstPolicy"
         p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
-        scope="prototype" />
+        scope="prototype"
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
 
     <bean id="ValidateAuthorizationRequestType"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateAuthorizationRequestType"
@@ -310,14 +311,13 @@
     </util:list>
 
     <bean id="ValidateRedirectURI" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateRedirectURI"
-        scope="prototype" p:requireRequestedValue="true" />
+        scope="prototype"
+        p:requireRequestedValue="true"
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
 
     <bean id="ValidateResponseType" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateResponseType"
-        scope="prototype">
-        <property name="unregisteredClientValidResponseTypesLookupStrategy">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultUnregisteredClientResponseTypesLookupFunction" />
-        </property>
-    </bean>        
+        scope="prototype"
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
 
     <bean id="ValidateCodeChallenge" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateCodeChallenge"
         scope="prototype" />
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 fd228730..48fe8013 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
@@ -53,10 +53,13 @@
 
     <bean id="InitializeRegistrationMetadataPolicyContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeRegistrationMetadataPolicyContext"
-        scope="prototype" />
+        scope="prototype"
+        p:metadataPolicyValidationStrategy="#{getObject('shibboleth.oidc.dynreg.MetadataPolicyValidator') ?: getObject('shibboleth.oidc.dynreg.DefaultMetadataPolicyValidator')}" />
 
     <bean id="ValidateRegistrationRequestMetadata"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationRequestMetadata" scope="prototype" />
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationRequestMetadata"
+        scope="prototype"
+        p:metadataPolicyEnforcer="#{getObject('shibboleth.oidc.dynreg.MetadataPolicyEnforcer') ?: getObject('shibboleth.oidc.dynreg.DefaultMetadataPolicyEnforcer')}"/>
         
     <bean id="CheckRedirectURIs"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.CheckRedirectURIs"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 056d204b..a6667518 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -43,7 +43,8 @@
     <bean id="ValidateClientIDAgainstPolicy"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateClientIDAgainstPolicy"
         p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
-        scope="prototype" />
+        scope="prototype"
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
 
     <bean id="ValidateGrantType" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrantType"
         scope="prototype" />
@@ -86,7 +87,8 @@
         p:activationCondition-ref="AuthorizationCodeGrantCondition"
         p:redirectURILookupStrategy-ref="shibboleth.TokenRequestRedirectURILookupStrategy"
         p:validRedirectURIsLookupStrategy-ref="shibboleth.TokenRequestValidRequestUrisLookupStrategy"
-        p:requireRequestedValue="false" />
+        p:requireRequestedValue="false"
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
 
     <bean id="shibboleth.TokenRequestRedirectURILookupStrategy"
         class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestRedirectURILookupFunction" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
index 5ca1ec48..87445bf6 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
@@ -45,7 +45,8 @@
     <bean id="ValidateClientIDAgainstPolicy"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateClientIDAgainstPolicy"
         p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
-        scope="prototype" />
+        scope="prototype"
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
 
     <bean id="ValidateAccessToken"
         class="net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl.ValidateAccessToken" scope="prototype" />
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 b64bec47..94e42913 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
@@ -772,9 +772,6 @@
         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"/>
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRedirectURITest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRedirectURITest.java
index d9946619..4d5c7fb4 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRedirectURITest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRedirectURITest.java
@@ -25,6 +25,7 @@ import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 import net.shibboleth.oidc.metadata.policy.UnregisteredClientPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.shared.component.ComponentInitializationException;
 
@@ -66,6 +67,7 @@ public class ValidateRedirectURITest extends BaseOIDCResponseActionTest {
         if (policies != null) {
           action.setUnregisteredClientPolicyLookupStrategy(prc -> policies);
         }
+        action.setUnregisteredClientPolicyEnforcer(new DefaultMetadataPolicyEnforcer());
         action.initialize();
     }
 
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateResponseTypeTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateResponseTypeTest.java
index 6772563b..b8ea993f 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateResponseTypeTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateResponseTypeTest.java
@@ -17,11 +17,14 @@ package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 import net.shibboleth.idp.plugin.oidc.op.profile.impl.BaseOIDCResponseActionTest;
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.UnregisteredClientPolicy;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.shared.component.ComponentInitializationException;
 
@@ -83,7 +86,10 @@ public class ValidateResponseTypeTest extends BaseOIDCResponseActionTest {
 
     private void initPolicy(final Set<ResponseType> responseTypes) {
         action = new ValidateResponseType();
-        action.setUnregisteredClientValidResponseTypesLookupStrategy(prc -> responseTypes);
+        action.setUnregisteredClientPolicyLookupStrategy(prc -> Map.of("response_type", 
+                new UnregisteredClientPolicy(new MetadataPolicy.Builder()
+                        .withOneOfValues(responseTypes.stream().map(rt -> (Object) rt.toString()).toList())
+                        .build())));
         try {
             action.initialize();
         } catch (ComponentInitializationException e) {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AlwaysFalseCustomMetadataPolicyOperator.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AlwaysFalseCustomMetadataPolicyOperator.java
new file mode 100644
index 00000000..396d846e
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AlwaysFalseCustomMetadataPolicyOperator.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.flow;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.CustomMetadataPolicyOperator;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+public class AlwaysFalseCustomMetadataPolicyOperator implements CustomMetadataPolicyOperator {
+
+    @Override
+    public boolean validate(MetadataPolicy policy) {
+        return false;
+    }
+
+    @Override
+    public Object apply(Object inputValue, MetadataPolicy policy) throws ConstraintViolationException {
+        throw new ConstraintViolationException("Cannot accept " + inputValue);
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AlwaysTrueCustomMetadataPolicyOperator.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AlwaysTrueCustomMetadataPolicyOperator.java
new file mode 100644
index 00000000..5f0234bc
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AlwaysTrueCustomMetadataPolicyOperator.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.flow;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.CustomMetadataPolicyOperator;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+public class AlwaysTrueCustomMetadataPolicyOperator implements CustomMetadataPolicyOperator {
+
+    @Override
+    public boolean validate(MetadataPolicy policy) {
+        return true;
+    }
+
+    @Override
+    public Object apply(Object inputValue, MetadataPolicy policy) throws ConstraintViolationException {
+        return inputValue;
+    }
+
+}
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 bdbafc2d..6a34b070 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
@@ -251,6 +251,26 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
         Assert.assertTrue(parsedResponse.getOIDCClientInformation().getOIDCMetadata().getGrantTypes().contains(GrantType.IMPLICIT));
     }
 
+    @Test
+    public void testAccessToken_nonDefaultPolicyActive_failsWhenAlwaysFailingPolicy() throws Exception {
+        setJsonRequest("POST", buildRequestMessage(redirectUri, "\"grant_types\":[\"authorization_code\"]"));
+        rpId = "alwaysFailingMetadataPolicy";
+        request.addHeader("Authorization", buildRegistrationAccessToken(false, (String) null,
+                (String[]) null).toAuthorizationHeader());
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_client_metadata");
+    }
+
+    @Test
+    public void testAccessToken_nonDefaultPolicyActive_failsWhenInvalidBeanInPolicy() throws Exception {
+        setJsonRequest("POST", buildRequestMessage(redirectUri, "\"grant_types\":[\"authorization_code\"]"));
+        rpId = "invalidBeanMetadataPolicy";
+        request.addHeader("Authorization", buildRegistrationAccessToken(false, (String) null,
+                (String[]) null).toAuthorizationHeader());
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_client_metadata");
+    }
+
     protected void assertSuccessfulResponse(final FlowExecutionResult result, final String clientId) throws Exception {
         final OIDCClientInformationResponse parsedResponse =
                 parseSuccessResponse(result, OIDCClientInformationResponse.class);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientAllowedScopeLookupFunctionTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientAllowedScopeLookupFunctionTest.java
index a70ef7f8..dd83d04f 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientAllowedScopeLookupFunctionTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientAllowedScopeLookupFunctionTest.java
@@ -27,6 +27,10 @@ import com.nimbusds.openid.connect.sdk.OIDCScopeValue;
 
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 import net.shibboleth.oidc.metadata.policy.UnregisteredClientPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.CustomMetadataPolicyOperator;
+import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.ConstraintViolationException;
 
 /**
  * Unit tests for {@link DefaultUnregisteredClientAllowedScopeLookupFunction}.
@@ -38,6 +42,22 @@ public class DefaultUnregisteredClientAllowedScopeLookupFunctionTest {
     public void setup(final Map<String, UnregisteredClientPolicy> policies) {
         function = new DefaultUnregisteredClientAllowedScopeLookupFunction();
         function.setUnregisteredClientPolicyLookupStrategy(prc -> policies);
+        final DefaultMetadataPolicyEnforcer enforcer = new DefaultMetadataPolicyEnforcer();
+        enforcer.setCustomMetadataPolicyOperators(CollectionSupport.singletonMap(
+                "customOperator", new CustomMetadataPolicyOperator() {
+
+            @Override
+            public boolean validate(MetadataPolicy policy) {
+                return true;
+            }
+
+            @Override
+            public Object apply(Object inputValue, MetadataPolicy policy) throws ConstraintViolationException {
+                return "openid email";
+            }
+            
+        }));
+        function.setUnregisteredClientPolicyEnforcer(enforcer);
     }
 
     @Test
@@ -76,4 +96,16 @@ public class DefaultUnregisteredClientAllowedScopeLookupFunctionTest {
         Assert.assertTrue(result.contains(OIDCScopeValue.OPENID));
         Assert.assertTrue(result.contains(OIDCScopeValue.PROFILE));
     }
+
+    @Test
+    public void testWithScopeCustomOperator() {
+        setup(Map.of("scope", new UnregisteredClientPolicy(new MetadataPolicy.Builder()
+                .withCustomOperator("customOperator", "nomatter").build())));
+        final Scope result = function.apply(new ProfileRequestContext());
+        Assert.assertNotNull(result);
+        Assert.assertEquals(result.size(), 2);
+        Assert.assertTrue(result.contains(OIDCScopeValue.OPENID));
+        Assert.assertTrue(result.contains(OIDCScopeValue.EMAIL));
+    }
+
 }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientPolicyValidatorTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientPolicyValidatorTest.java
index 05cb6080..28b8c006 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientPolicyValidatorTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultUnregisteredClientPolicyValidatorTest.java
@@ -18,23 +18,35 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.mockito.Mockito;
 import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 import net.shibboleth.oidc.metadata.policy.UnregisteredClientPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.CustomMetadataPolicyOperator;
+import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyValidator;
 
 /**
  * Unit tests for {@link DefaultUnregisteredClientPolicyValidator}.
  */
 public class DefaultUnregisteredClientPolicyValidatorTest {
     
-    DefaultUnregisteredClientPolicyValidator predicate = new DefaultUnregisteredClientPolicyValidator();
+    DefaultUnregisteredClientPolicyValidator predicate;
     
-    public void setup(final Map<String, UnregisteredClientPolicy> policies) {
-        
+    @BeforeMethod
+    public void init() {
+        predicate = new DefaultUnregisteredClientPolicyValidator();
     }
 
+    public void setupOperators(final Map<String, CustomMetadataPolicyOperator> operators) {
+        final DefaultMetadataPolicyValidator syntaxValidator = new DefaultMetadataPolicyValidator();
+        assert operators != null;
+        syntaxValidator.setCustomMetadataPolicyOperators(operators);
+        predicate.setSyntaxValidator(syntaxValidator);
+    }
+    
     @Test
     public void testNull() {
         Assert.assertFalse(predicate.test(null));
@@ -50,6 +62,25 @@ public class DefaultUnregisteredClientPolicyValidatorTest {
         Assert.assertTrue(predicate.test(validPolicies()));
     }
 
+    @Test
+    public void testValidWithValidCustomOperators() {
+        setupOperators(Map.of("mockOperator", mockOperator(true)));
+        Assert.assertTrue(predicate.test(validWithCustomOperators()));
+    }
+
+    @Test
+    public void testCustomOperatorsFailingValidation() {
+        setupOperators(Map.of("mockOperator", mockOperator(false)));
+        Assert.assertFalse(predicate.test(validWithCustomOperators()));
+    }
+
+    @SuppressWarnings("null")
+    protected CustomMetadataPolicyOperator mockOperator(final boolean validationResult) {
+        CustomMetadataPolicyOperator operator = Mockito.mock(CustomMetadataPolicyOperator.class);
+        Mockito.when(operator.validate(Mockito.any())).thenReturn(validationResult);
+        return operator;
+    }
+
     @Test
     public void testMissingScope() {
         Map<String, UnregisteredClientPolicy> map = validPolicies();
@@ -64,6 +95,22 @@ public class DefaultUnregisteredClientPolicyValidatorTest {
         Assert.assertFalse(predicate.test(map));
     }
 
+    @Test
+    public void testScopeSuccessWithCustomOperator() {
+        setupOperators(Map.of("mockOperator", mockOperator(true)));
+        Map<String, UnregisteredClientPolicy> map = validPolicies();
+        map.put("scope", customOperator());
+        Assert.assertTrue(predicate.test(map));
+    }
+
+    @Test
+    public void testScopeFailureWithCustomOperator() {
+        setupOperators(Map.of("mockOperator", mockOperator(false)));
+        Map<String, UnregisteredClientPolicy> map = validPolicies();
+        map.put("scope", customOperator());
+        Assert.assertFalse(predicate.test(map));
+    }
+
     @Test
     public void testMissingResponseType() {
         Map<String, UnregisteredClientPolicy> map = validPolicies();
@@ -78,6 +125,22 @@ public class DefaultUnregisteredClientPolicyValidatorTest {
         Assert.assertFalse(predicate.test(map));
     }
 
+    @Test
+    public void testResponseTypeSuccessWithCustomOperator() {
+        setupOperators(Map.of("mockOperator", mockOperator(true)));
+        Map<String, UnregisteredClientPolicy> map = validPolicies();
+        map.put("response_type", customOperator());
+        Assert.assertTrue(predicate.test(map));
+    }
+
+    @Test
+    public void testResponseTypeFailureWithCustomOperator() {
+        setupOperators(Map.of("mockOperator", mockOperator(false)));
+        Map<String, UnregisteredClientPolicy> map = validPolicies();
+        map.put("response_type", customOperator());
+        Assert.assertFalse(predicate.test(map));
+    }
+
     @Test
     public void testMissingRedirectUri() {
         Map<String, UnregisteredClientPolicy> map = validPolicies();
@@ -92,6 +155,22 @@ public class DefaultUnregisteredClientPolicyValidatorTest {
         Assert.assertFalse(predicate.test(map));
     }
 
+    @Test
+    public void testRedirectUriSuccessWithCustomOperator() {
+        setupOperators(Map.of("mockOperator", mockOperator(true)));
+        Map<String, UnregisteredClientPolicy> map = validPolicies();
+        map.put("redirect_uri", customOperator());
+        Assert.assertTrue(predicate.test(map));
+    }
+
+    @Test
+    public void testRedirectUriFailureWithCustomOperator() {
+        setupOperators(Map.of("mockOperator", mockOperator(false)));
+        Map<String, UnregisteredClientPolicy> map = validPolicies();
+        map.put("redirect_uri", customOperator());
+        Assert.assertFalse(predicate.test(map));
+    }
+
     @Test
     public void testMissingClientId() {
         Map<String, UnregisteredClientPolicy> map = validPolicies();
@@ -106,6 +185,22 @@ public class DefaultUnregisteredClientPolicyValidatorTest {
         Assert.assertFalse(predicate.test(map));
     }
 
+    @Test
+    public void testClientIdSuccessWithCustomOperator() {
+        setupOperators(Map.of("mockOperator", mockOperator(true)));
+        Map<String, UnregisteredClientPolicy> map = validPolicies();
+        map.put("client_id", customOperator());
+        Assert.assertTrue(predicate.test(map));
+    }
+
+    @Test
+    public void testClientIdFailureWithCustomOperator() {
+        setupOperators(Map.of("mockOperator", mockOperator(false)));
+        Map<String, UnregisteredClientPolicy> map = validPolicies();
+        map.put("client_id", customOperator());
+        Assert.assertFalse(predicate.test(map));
+    }
+
     protected Map<String, UnregisteredClientPolicy> validPolicies() {
         final Map<String, UnregisteredClientPolicy> map = new HashMap<>( Map.of("scope", validScope(),
                 "response_type", validResponseType(),
@@ -114,6 +209,20 @@ public class DefaultUnregisteredClientPolicyValidatorTest {
         return map;
     }
 
+    protected Map<String, UnregisteredClientPolicy> validWithCustomOperators() {
+        final Map<String, UnregisteredClientPolicy> map = new HashMap<>( Map.of("scope", customOperator(),
+                "response_type", customOperator(),
+                "redirect_uri", customOperator(),
+                "client_id", customOperator()));
+        return map;        
+    }
+
+    protected UnregisteredClientPolicy customOperator() {
+        return new UnregisteredClientPolicy(new MetadataPolicy.Builder()
+                .withCustomOperator("mockOperator", "mockValue")
+                .build());
+    }
+    
     protected UnregisteredClientPolicy validScope() {
         return new UnregisteredClientPolicy(new MetadataPolicy.Builder().withValue("openid profile").build());
     }
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
index 6ae6e990..707f855f 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
@@ -102,4 +102,8 @@
         </property>
     </bean>
 
+    <bean id="alwaysTruePolicyOperator" class="net.shibboleth.idp.plugin.oidc.op.profile.flow.AlwaysTrueCustomMetadataPolicyOperator" />
+
+    <bean id="alwaysFalsePolicyOperator" class="net.shibboleth.idp.plugin.oidc.op.profile.flow.AlwaysFalseCustomMetadataPolicyOperator" />
+
 </beans>
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/metadata-policy-failing.json b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/metadata-policy-failing.json
new file mode 100644
index 00000000..53013be1
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/metadata-policy-failing.json
@@ -0,0 +1,5 @@
+{
+    "grant_types": {
+        "bean": "alwaysFalsePolicyOperator"
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/metadata-policy-invalidbean.json b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/metadata-policy-invalidbean.json
new file mode 100644
index 00000000..274b4bb6
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/metadata-policy-invalidbean.json
@@ -0,0 +1,5 @@
+{
+    "grant_types": {
+        "bean": "thisBeanNotFound"
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index 89255cec..1d96c81f 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -302,6 +302,20 @@
                 </list>
             </property>
         </bean>
+        <bean parent="RelyingPartyByName" c:relyingPartyIds="alwaysFailingMetadataPolicy">
+            <property name="profileConfigurations">
+                <list>
+                    <bean parent="OIDC.Registration" p:metadataPolicyLookupStrategy-ref="shibboleth.oidc.dynreg.AlwaysFailingMetadataPolicyLookupStrategy"/>
+                </list>
+            </property>
+        </bean>
+        <bean parent="RelyingPartyByName" c:relyingPartyIds="invalidBeanMetadataPolicy">
+            <property name="profileConfigurations">
+                <list>
+                    <bean parent="OIDC.Registration" p:metadataPolicyLookupStrategy-ref="shibboleth.oidc.dynreg.InvalidBeanMetadataPolicyLookupStrategy"/>
+                </list>
+            </property>
+        </bean>
     </util:list>
 
     <bean id="shibboleth.oidc.dynreg.AnotherMetadataPolicyLookupStrategy"
@@ -309,4 +323,14 @@
         c:resource="classpath:net/shibboleth/idp/module/conf/metadata-policy2.json"
         c:id="AnotherProfileMetadataPolicyCache" />
 
+    <bean id="shibboleth.oidc.dynreg.AlwaysFailingMetadataPolicyLookupStrategy"
+        parent="shibboleth.oidc.dynreg.MetadataPolicyLookupStrategy"
+        c:resource="classpath:net/shibboleth/idp/module/conf/metadata-policy-failing.json"
+        c:id="AlwaysFailingUnregisteredClientPolicyCache" />
+
+    <bean id="shibboleth.oidc.dynreg.InvalidBeanMetadataPolicyLookupStrategy"
+        parent="shibboleth.oidc.dynreg.MetadataPolicyLookupStrategy"
+        c:resource="classpath:net/shibboleth/idp/module/conf/metadata-policy-invalidbean.json"
+        c:id="InvalidBeanUnregisteredClientPolicyCache" />
+
 </beans>
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/unregistered-policy.json b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/unregistered-policy.json
index 6ac45565..dd896844 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/unregistered-policy.json
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/unregistered-policy.json
@@ -6,7 +6,8 @@
         "value": "openid info"
     },
     "redirect_uri": {
-        "regexp": "^https:\/\/(?:([^.]+).)?example.org\/(.*)"
+        "regexp": "^https:\/\/(?:([^.]+).)?example.org\/(.*)",
+        "bean": "alwaysTruePolicyOperator"
     },
     "response_type": {
         "one_of": ["code"]

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


More information about the commits mailing list