[java-oidc-common] branch main updated: JCOMOIDC-96 - Support custom/additional metadata policy operators

Henri Mikkonen henri.mikkonen at iki.fi
Fri Jan 19 12:41:10 UTC 2024


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

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

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

The following commit(s) were added to refs/heads/main by this push:
     new ed435c1  JCOMOIDC-96 - Support custom/additional metadata policy operators
ed435c1 is described below

commit ed435c1aac06078cfe7af080e6bf363ca8a96afa
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Jan 19 14:38:07 2024 +0200

    JCOMOIDC-96 - Support custom/additional metadata policy operators
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-96
    
    The MetadataPolicy -class now include a map of custom metadata policy operators.
    The default policy enforcer and validator functions be wired with a map of
    CustomMetadataPolicyOperator, key describing the operator name used within the
    metadata policies. An example implementation BeanMetadataPolicyOperator can be used
    for using a bean that implements the CustomMetadataPolicyOperator interface as a
    custom metadata policy operator.
---
 .../oidc/metadata/policy/MetadataPolicy.java       |  57 +++++++-
 .../policy/impl/BeanMetadataPolicyOperator.java    |  69 ++++++++++
 .../policy/impl/CustomMetadataPolicyOperator.java  |  47 +++++++
 .../policy/impl/DefaultMetadataPolicyEnforcer.java |  52 +++++++-
 .../impl/DefaultMetadataPolicyValidator.java       |  38 +++++-
 .../impl/BeanMetadataPolicyOperatorTest.java       | 144 +++++++++++++++++++++
 .../impl/DefaultMetadataPolicyEnforcerTest.java    |  23 ++++
 .../impl/DefaultMetadataPolicyValidatorTest.java   |  32 +++++
 .../impl/OIDCMetadataPolicyResolverTest.java       |  81 +++++++++++-
 .../metadata/impl/metadata-policy1-custom.json     |  22 ++++
 10 files changed, 552 insertions(+), 13 deletions(-)

diff --git a/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/MetadataPolicy.java b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/MetadataPolicy.java
index 0801f84..55c39b4 100644
--- a/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/MetadataPolicy.java
+++ b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/MetadataPolicy.java
@@ -14,8 +14,12 @@
 
 package net.shibboleth.oidc.metadata.policy;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.MoreObjects;
 
@@ -49,6 +53,9 @@ public class MetadataPolicy {
     /** The regular expression that the claim value must meet. */
     @JsonProperty("regexp") private String regexp;
 
+    /** The map of any other operators not directly mapped. */
+    private final Map<String, Object> customOperators = new HashMap<>();
+
     /**
      * Get the (forced) value for the claim.
      * 
@@ -201,7 +208,32 @@ public class MetadataPolicy {
     public void setRegexp(final String value) {
         regexp = value;
     }
-    
+
+    /**
+     * Get the map of custom operators.
+     * 
+     * @return The map of any other operators not directly mapped.
+     * 
+     * @since 3.1.0
+     */
+    @JsonAnyGetter
+    public Map<String, Object> getCustomOperators() {
+        return customOperators;
+    }
+
+    /**
+     * Add a custom operator to the map of custom operators.
+     * 
+     * @param name The name of the custom operator.
+     * @param value The value of the custom operator.
+     * 
+     * @since 3.1.0
+     */
+    @JsonAnySetter
+    public void setCustomOperator(final String name, final Object value) {
+        customOperators.put(name, value);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -245,12 +277,15 @@ public class MetadataPolicy {
        
         /** The regular expression that the claim value must meet. */
         private String regexp;
-        
+
+        /** The map of any other operators not directly mapped. */
+        private final Map<String, Object> customOperators;
+
         /**
          * Constructor.
          */
         public Builder() {
-            
+            this.customOperators = new HashMap<>();
         }
 
         /**
@@ -341,6 +376,19 @@ public class MetadataPolicy {
             return this;
         }
 
+        /**
+         * Add a custom operator to the map of custom operators.
+         * 
+         * @param name The name of the custom operator.
+         * @param value The value of the custom operator.
+         * 
+         * @since 3.1.0
+         */
+        public Builder withCustomOperator(final String name, final Object value) {
+            this.customOperators.put(name, value);
+            return this;
+        }
+
         /**
          * Build the metadata policy corresponding to the current builder state.
          * 
@@ -356,6 +404,9 @@ public class MetadataPolicy {
             policy.setSupersetOfValues(this.supersetOfValues);
             policy.setEssential(this.essential);
             policy.setRegexp(this.regexp);
+            for (final String key : this.customOperators.keySet()) {
+                policy.setCustomOperator(key, this.customOperators.get(key));
+            }
             return policy;
         }
     }
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/BeanMetadataPolicyOperator.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/BeanMetadataPolicyOperator.java
new file mode 100644
index 0000000..b8f182b
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/BeanMetadataPolicyOperator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.oidc.metadata.policy.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A custom metadata policy operator that fetches a bean from {@link ApplicationContext} and uses it for as
+ * {@link CustomMetadataPolicyOperator}.
+ */
+public class BeanMetadataPolicyOperator implements CustomMetadataPolicyOperator, ApplicationContextAware {
+
+    /** The operator name to be used within {@link MetadataPolicy} custom operators. */
+    public static final String BEAN_OPERATOR_NAME = "bean";
+
+    /** The application context from where to fetch the custom policy operators. */
+    private ApplicationContext applicationContext;
+
+    @Override
+    public boolean validate(@Nonnull final MetadataPolicy policy) {
+        return true;
+    }
+
+    @Override
+    public Object apply(final @Nullable Object inputValue, final @Nonnull MetadataPolicy policy)
+            throws ConstraintViolationException {
+        Constraint.isNotNull(inputValue, "input value (bean ID) cannot be null");
+        Constraint.isNotNull(policy, "metadata policy cannot be null");
+        final Object rawBeanId = policy.getCustomOperators().get(BEAN_OPERATOR_NAME);
+        if (rawBeanId instanceof String beanId) {
+            try {
+                final CustomMetadataPolicyOperator operator = 
+                        applicationContext.getBean(beanId, CustomMetadataPolicyOperator.class);
+                return operator.apply(inputValue, policy);
+            } catch (final BeansException e) {
+                throw new ConstraintViolationException("Could not wire a compatible bean " + beanId);
+            }
+        } else {
+            throw new ConstraintViolationException("Could not parse the bean name from policy");
+        }
+    }
+
+    @Override
+    public void setApplicationContext(@Nonnull final ApplicationContext context) throws BeansException {
+        applicationContext = context;
+    }
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/CustomMetadataPolicyOperator.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/CustomMetadataPolicyOperator.java
new file mode 100644
index 0000000..40a873e
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/CustomMetadataPolicyOperator.java
@@ -0,0 +1,47 @@
+/*
+ * 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.oidc.metadata.policy.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * Interface describing a custom policy operator to be used with {@link MetadataPolicy} objects.
+ */
+public interface CustomMetadataPolicyOperator {
+
+    /**
+     * Validate whether the operator is compatible with the other operators in the policy.
+     * 
+     * @param policy The metadata policy to be used by the custom policy operator.
+     * @return true if 
+     */
+    public boolean validate(@Nonnull final MetadataPolicy policy);
+    
+    /**
+     * Apply the operator for the given input that has the given metadata policy attached.
+     * 
+     * @param inputValue The value to be used by the custom policy operator.
+     * @param policy The metadata policy to be used by the custom policy operator.
+     * @return The value returned by the custom policy operator.
+     * @throws ConstraintViolationException If the value-check of the custom operator fails.
+     */
+    @Nullable public Object apply(@Nullable final Object inputValue, @Nonnull final MetadataPolicy policy)
+            throws ConstraintViolationException;
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcer.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcer.java
index f0dfe7c..d5ce8d1 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcer.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcer.java
@@ -17,6 +17,7 @@ package net.shibboleth.oidc.metadata.policy.impl;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.function.BiFunction;
 import java.util.regex.Pattern;
 
@@ -28,6 +29,8 @@ import org.slf4j.Logger;
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.ConstraintViolationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -53,11 +56,31 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * been applied to, and a flag indicating if the object was compatible with the value checks of the metadata policy.
  * </p>
  */
-public class DefaultMetadataPolicyEnforcer implements BiFunction<Object,MetadataPolicy,Pair<Object,Boolean>> {
+public class DefaultMetadataPolicyEnforcer extends AbstractInitializableComponent
+    implements BiFunction<Object,MetadataPolicy,Pair<Object,Boolean>> {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultMetadataPolicyEnforcer.class);
 
+    /** Map of custom metadata policy operators that are applied after the standard operators. */
+    @Nonnull private Map<String, CustomMetadataPolicyOperator> customOperators;
+
+    /**
+     * Constructor.
+     */
+    public DefaultMetadataPolicyEnforcer() {
+        customOperators = CollectionSupport.emptyMap();
+    }
+
+    /**
+     * Set the map of custom metadata policy operators that are applied after the standard operators.
+     * @param operators What to set
+     */
+    public void setCustomMetadataPolicyOperators(@Nonnull final Map<String, CustomMetadataPolicyOperator> operators) {
+        checkSetterPreconditions();
+        customOperators = Constraint.isNotNull(operators, "Map of custom operators cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Override
     @Nonnull public Pair<Object, Boolean> apply(@Nullable final Object candidate, 
@@ -84,9 +107,32 @@ public class DefaultMetadataPolicyEnforcer implements BiFunction<Object,Metadata
         }
         
         final boolean validation = doValueChecks(result, policy);
-        return new Pair<>(result, Boolean.valueOf(validation));
+        return customOperators.isEmpty() || !validation
+                ? new Pair<>(result, Boolean.valueOf(validation))
+                        : applyCustomOperators(result, policy);
     }
 
+    /**
+     * Applies the custom operators attached to this enforcer.
+     * 
+     * @param initialResult The result after standard operators have been applied
+     * @param policy The metadata policy to be applied to the initial result
+     * @return a {@link Pair} as required to return by this enforcer function
+     */
+    @Nonnull protected Pair<Object, Boolean> applyCustomOperators(@Nullable final Object initialResult,
+            @Nonnull final MetadataPolicy policy) {
+        Object result = initialResult;
+        for (final String customOperator : customOperators.keySet()) {
+            try {
+                result = customOperators.get(customOperator).apply(result, policy);
+            } catch (final ConstraintViolationException e) {
+                log.debug("The custom operator {} did not accept the value {}", customOperator, result);
+                return new Pair<>(result, Boolean.FALSE);
+            }
+        }
+        return new Pair<>(result, Boolean.TRUE);
+    }
+    
     /**
      * Applies the given add value modifier for the given candidate and returns the result of the operation.
      * 
@@ -147,7 +193,7 @@ public class DefaultMetadataPolicyEnforcer implements BiFunction<Object,Metadata
                         log.warn("The candidate {} contains multiple values, not compatible with one_of", candidate);
                         validation = false;
                     } else {
-                        if (oneOfValues != null && !MetadataPolicyHelper.isSubsetOfValues(candidate, oneOfValues)) {
+                        if (!MetadataPolicyHelper.isSubsetOfValues(candidate, oneOfValues)) {
                             log.warn("The candidate {} is not compatible with one_of {}", candidate, oneOfValues);
                             validation = false;
                         }
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidator.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidator.java
index 9a25c8c..1d4a345 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidator.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidator.java
@@ -24,7 +24,10 @@ import javax.annotation.Nullable;
 import org.slf4j.Logger;
 
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
@@ -59,11 +62,30 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * </li>
  * </ul>
  */
-public class DefaultMetadataPolicyValidator implements Predicate<Map<String, MetadataPolicy>> {
+public class DefaultMetadataPolicyValidator extends AbstractInitializableComponent
+    implements Predicate<Map<String, MetadataPolicy>> {
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultMetadataPolicyValidator.class);
-    
+
+    /** Map of custom metadata policy operators that are applied after the standard operators. */
+    @Nonnull private Map<String, CustomMetadataPolicyOperator> customOperators;
+
+    /**
+     * Constructor.
+     */
+    public DefaultMetadataPolicyValidator() {
+        customOperators = CollectionSupport.emptyMap();
+    }
+
+    /**
+     * Set the map of custom metadata policy operators that are applied after the standard operators.
+     * @param operators What to set
+     */
+    public void setCustomMetadataPolicyOperators(@Nonnull final Map<String, CustomMetadataPolicyOperator> operators) {
+        checkSetterPreconditions();
+        customOperators = Constraint.isNotNull(operators, "Map of custom operators cannot be null");
+    }
 
     /** {@inheritDoc} */
  // Checkstyle: CyclomaticComplexity OFF
@@ -113,6 +135,18 @@ public class DefaultMetadataPolicyValidator implements Predicate<Map<String, Met
                 result = false;
             }
 
+            if (!policy.getCustomOperators().isEmpty()) {
+                for (final String operatorName : policy.getCustomOperators().keySet()) {
+                    final CustomMetadataPolicyOperator customOperator = customOperators.get(operatorName);
+                    if (customOperator == null) {
+                        log.warn("Claim {}: could not find a custom operator {}", claim, operatorName);
+                        result = false;
+                    } else if (!customOperator.validate(policy)) {
+                        log.warn("Claimn {}: policy is not compatible with the custom operator {}", claim, operatorName);
+                        result = false;
+                    }
+                }
+            }
         }
         return result;
     }
diff --git a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/BeanMetadataPolicyOperatorTest.java b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/BeanMetadataPolicyOperatorTest.java
new file mode 100644
index 0000000..a879bb2
--- /dev/null
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/BeanMetadataPolicyOperatorTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.oidc.metadata.policy.impl;
+
+import javax.annotation.Nonnull;
+
+import org.mockito.Mockito;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * Unit tests for {@link BeanMetadataPolicyOperator}.
+ */
+ at SuppressWarnings("null")
+public class BeanMetadataPolicyOperatorTest {
+
+    BeanMetadataPolicyOperator operator;
+    ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class);
+
+    @BeforeMethod
+    public void init() {
+        operator = new BeanMetadataPolicyOperator();
+        operator.setApplicationContext(applicationContext);
+    }
+
+    @Test
+    public void testSame() {
+        Mockito.when(applicationContext.getBean(Mockito.any(), Mockito.eq(CustomMetadataPolicyOperator.class)))
+            .thenReturn(alwaysSameOperator());
+        Assert.assertEquals(operator.apply("mockValue", metadataPolicy("alwaysSame")), "mockValue");
+    }
+
+    @Test
+    public void testChanged() {
+        Mockito.when(applicationContext.getBean(Mockito.any(), Mockito.eq(CustomMetadataPolicyOperator.class)))
+            .thenReturn(alwaysChangedOperator());
+        Assert.assertEquals(operator.apply("changedValue", metadataPolicy("alwaysChanged")), "changedValue");
+    }
+
+    @Test(expectedExceptions = { ConstraintViolationException.class })
+    public void testBeanFailing() {
+        Mockito.when(applicationContext.getBean(Mockito.any(), Mockito.eq(CustomMetadataPolicyOperator.class)))
+            .thenReturn(alwaysFailingOperator());
+        operator.apply("changedValue", metadataPolicy("alwaysChanged"));
+    }
+
+    @Test(expectedExceptions = { ConstraintViolationException.class })
+    public void testNotFound() {
+        Mockito.doThrow(CustomBeansException.class)
+            .when(applicationContext).getBean(Mockito.any(), Mockito.eq(CustomMetadataPolicyOperator.class));
+        operator.apply("changedValue", metadataPolicy("alwaysChanged"));
+    }
+
+    @Test(expectedExceptions = { ConstraintViolationException.class })
+    public void testNullId() {
+        Mockito.doThrow(CustomBeansException.class)
+            .when(applicationContext).getBean(Mockito.any(), Mockito.eq(CustomMetadataPolicyOperator.class));
+        operator.apply("changedValue", metadataPolicy(null));
+    }
+
+    @Test(expectedExceptions = { ConstraintViolationException.class })
+    public void testNonStringId() {
+        Mockito.doThrow(CustomBeansException.class)
+            .when(applicationContext).getBean(Mockito.any(), Mockito.eq(CustomMetadataPolicyOperator.class));
+        operator.apply("changedValue", metadataPolicy(1L));
+    }
+
+    @Nonnull protected MetadataPolicy metadataPolicy(final Object beanId) {
+        return new MetadataPolicy.Builder()
+                .withCustomOperator(BeanMetadataPolicyOperator.BEAN_OPERATOR_NAME, beanId)
+                .build();
+    }
+    protected CustomMetadataPolicyOperator alwaysSameOperator() {
+        return new CustomMetadataPolicyOperator() {
+
+            @Override
+            public Object apply(Object inputValue, MetadataPolicy policy) throws ConstraintViolationException {
+                return inputValue;
+            }
+
+            @Override
+            public boolean validate(MetadataPolicy policy) {
+                return true;
+            }
+        };
+    }
+
+    protected CustomMetadataPolicyOperator alwaysChangedOperator() {
+        return new CustomMetadataPolicyOperator() {
+
+            @Override
+            public Object apply(Object inputValue, MetadataPolicy policy) throws ConstraintViolationException {
+                return "changedValue";
+            }
+
+            @Override
+            public boolean validate(MetadataPolicy policy) {
+                return true;
+            }
+        };
+    }
+
+    protected CustomMetadataPolicyOperator alwaysFailingOperator() {
+        return new CustomMetadataPolicyOperator() {
+
+            @Override
+            public Object apply(Object inputValue, MetadataPolicy policy) throws ConstraintViolationException {
+                throw new ConstraintViolationException("mock");
+            }
+
+            @Override
+            public boolean validate(MetadataPolicy policy) {
+                return true;
+            }
+        };
+    }
+
+    @SuppressWarnings("serial")
+    class CustomBeansException extends BeansException {
+
+        public CustomBeansException(String msg) {
+            super(msg);
+        }
+        
+    }
+}
diff --git a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcerTest.java b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcerTest.java
index 7995b56..a1fec01 100644
--- a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcerTest.java
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcerTest.java
@@ -16,12 +16,15 @@ package net.shibboleth.oidc.metadata.policy.impl;
 
 import java.util.List;
 
+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.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.logic.ConstraintViolationException;
 
 /**
  * Unit tests for {@link DefaultMetadataPolicyEnforcer}.
@@ -500,6 +503,26 @@ public class DefaultMetadataPolicyEnforcerTest {
                 new MetadataPolicy.Builder().withRegexp(regex).build()));
     }
 
+    @Test
+    public void apply_whenCustomOperatorAcceptsValue_resultIsCandidateWithTrue() {
+        final String candidate = "expectdValue";
+        CustomMetadataPolicyOperator customOperator = Mockito.mock(CustomMetadataPolicyOperator.class);
+        Mockito.when(customOperator.apply(Mockito.any(), Mockito.any())).thenReturn(candidate);
+        applier.setCustomMetadataPolicyOperators(CollectionSupport.singletonMap("customOperator", customOperator));
+        assertResultEquals(applier.apply(candidate,
+                new MetadataPolicy.Builder().withCustomOperator("customOperator", "mockId").build()), candidate);
+    }
+
+    @Test
+    public void apply_whenCustomOperatorDoesNotAcceptValue_resultIsFalse() {
+        final String candidate = "expectdValue";
+        CustomMetadataPolicyOperator customOperator = Mockito.mock(CustomMetadataPolicyOperator.class);
+        Mockito.doThrow(ConstraintViolationException.class).when(customOperator).apply(Mockito.any(), Mockito.any());
+        applier.setCustomMetadataPolicyOperators(CollectionSupport.singletonMap("customOperator", customOperator));
+        assertResultFalse(applier.apply(candidate,
+                new MetadataPolicy.Builder().withCustomOperator("customOperator", "mockId").build()));
+    }
+
     public static void assertResultEquals(final Pair<Object, Boolean> pair, final Object expected) {
         final Boolean flag = pair.getSecond(); 
         Assert.assertTrue(flag != null && flag);
diff --git a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidatorTest.java b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidatorTest.java
index a3acc6a..cd2c264 100644
--- a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidatorTest.java
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidatorTest.java
@@ -17,11 +17,13 @@ package net.shibboleth.oidc.metadata.policy.impl;
 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.shared.collection.CollectionSupport;
 
 /**
  * Unit tests for {@link DefaultMetadataPolicyValidator}.
@@ -277,4 +279,34 @@ public class DefaultMetadataPolicyValidatorTest {
         
     }
 
+    @Test
+    public void test_whenCustomOperatorMissing_shouldReturnFalse() {
+        final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+                .withCustomOperator("customOperator", "obsoleteValue")
+                .build());
+        Assert.assertFalse(validator.test(map));
+    }
+
+    @Test
+    public void test_whenCustomOperatorExists_noOtherPolicyItems_shouldReturnFalse_customValidationFails() {
+        final CustomMetadataPolicyOperator operator = Mockito.mock(CustomMetadataPolicyOperator.class);
+        Mockito.when(operator.validate(Mockito.any())).thenReturn(false);
+        validator.setCustomMetadataPolicyOperators(CollectionSupport.singletonMap("customOperator", operator));
+        final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+                .withCustomOperator("customOperator", "obsoleteValue")
+                .build());
+        Assert.assertFalse(validator.test(map));
+    }
+
+    @Test
+    public void test_whenCustomOperatorExists_noOtherPolicyItems_shouldReturnTrue() {
+        final CustomMetadataPolicyOperator operator = Mockito.mock(CustomMetadataPolicyOperator.class);
+        Mockito.when(operator.validate(Mockito.any())).thenReturn(true);
+        validator.setCustomMetadataPolicyOperators(CollectionSupport.singletonMap("customOperator", operator));
+        final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+                .withCustomOperator("customOperator", "obsoleteValue")
+                .build());
+        Assert.assertTrue(validator.test(map));
+    }
+
 }
diff --git a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/OIDCMetadataPolicyResolverTest.java b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/OIDCMetadataPolicyResolverTest.java
index bdf7463..315500f 100644
--- a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/OIDCMetadataPolicyResolverTest.java
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/OIDCMetadataPolicyResolverTest.java
@@ -33,7 +33,9 @@ import net.shibboleth.oidc.metadata.cache.impl.DefaultJSONMapParsingStrategy;
 import net.shibboleth.oidc.metadata.cache.impl.DefaultSourceMetadataExpirationTimeStrategy;
 import net.shibboleth.oidc.metadata.cache.impl.MetadataCacheBuilder;
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.ConstraintViolationException;
 import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
@@ -45,12 +47,17 @@ public class OIDCMetadataPolicyResolverTest {
     OIDCMetadataPolicyResolver resolver;
     
     public void initTest(final String filename) throws Exception {
-        resolver = new OIDCMetadataPolicyResolver(buildCache(filename));
+        initTest(filename, null);
+    }
+
+    public void initTest(final String filename, final CustomMetadataPolicyOperator customOperator) throws Exception {
+        resolver = new OIDCMetadataPolicyResolver(buildCache(filename, customOperator));
         resolver.setId("mockId");
         resolver.initialize();
     }
-    
-    protected BatchMetadataCache<String, Map<String, MetadataPolicy>> buildCache(final String filename)
+
+    protected BatchMetadataCache<String, Map<String, MetadataPolicy>> buildCache(final String filename,
+            final CustomMetadataPolicyOperator customOperator)
             throws IOException, ComponentInitializationException {
         final Resource file = filename == null || filename.isEmpty()? null : new ClassPathResource(filename);
 
@@ -64,7 +71,11 @@ public class OIDCMetadataPolicyResolverTest {
         spec.setSourceMetadataExpiryStrategy(new DefaultSourceMetadataExpirationTimeStrategy(Duration.ofMinutes(5)));
         spec.setCriteriaToIdentifierStrategy(crit -> "id");
         spec.setIdentifierExtractionStrategy(crit -> "id");
-        spec.setMetadataValidPredicate(new DefaultMetadataPolicyValidator());
+        final DefaultMetadataPolicyValidator policyValidator = new DefaultMetadataPolicyValidator();
+        if (customOperator != null) {
+            policyValidator.setCustomMetadataPolicyOperators(CollectionSupport.singletonMap("bean", customOperator));
+        }
+        spec.setMetadataValidPredicate(policyValidator);
        
         spec.setMatchRequired(true);
 
@@ -98,7 +109,67 @@ public class OIDCMetadataPolicyResolverTest {
         Assert.assertTrue(iter.hasNext());
         final Map<String, MetadataPolicy> client = iter.next();
         Assert.assertEquals(client.size(), 6);
-        
+        assertStandardOperators(client);
+    }
+
+    @Test
+    public void testFoundWithCustomFoundAndValid() throws Exception {
+        final CustomMetadataPolicyOperator customOperator = new CustomMetadataPolicyOperator() {
+
+            @Override
+            public boolean validate(final MetadataPolicy policy) {
+                return true;
+            }
+
+            @Override
+            public Object apply(final Object inputValue, final MetadataPolicy policy)
+                    throws ConstraintViolationException {
+                return null;
+            }
+            
+        };
+        initTest("/net/shibboleth/oidc/metadata/impl/metadata-policy1-custom.json", customOperator);
+        final Iterator<Map<String, MetadataPolicy>> iter = resolver.resolve(new CriteriaSet()).iterator();
+        Assert.assertTrue(iter.hasNext());
+        final Map<String, MetadataPolicy> client = iter.next();
+        Assert.assertEquals(client.size(), 6);
+        assertStandardOperators(client);
+        final MetadataPolicy clientName = client.get("client_name");
+        Assert.assertFalse(clientName.getCustomOperators().isEmpty());
+        final Object beanOperator = clientName.getCustomOperators().get("bean");
+        Assert.assertTrue(beanOperator != null);
+        Assert.assertEquals(beanOperator, "customMetadataPolicyOperator");
+    }
+
+    @Test
+    public void testFoundWithCustomFoundButNotValid() throws Exception {
+        final CustomMetadataPolicyOperator customOperator = new CustomMetadataPolicyOperator() {
+
+            @Override
+            public boolean validate(final MetadataPolicy policy) {
+                return false;
+            }
+
+            @Override
+            public Object apply(final Object inputValue, final MetadataPolicy policy)
+                    throws ConstraintViolationException {
+                return null;
+            }
+            
+        };
+        initTest("/net/shibboleth/oidc/metadata/impl/metadata-policy1-custom.json", customOperator);
+        final Iterator<Map<String, MetadataPolicy>> iter = resolver.resolve(new CriteriaSet()).iterator();
+        Assert.assertFalse(iter.hasNext());
+    }
+
+    @Test
+    public void testFoundWithCustomNotFound() throws Exception {
+        initTest("/net/shibboleth/oidc/metadata/impl/metadata-policy1-custom.json");
+        final Iterator<Map<String, MetadataPolicy>> iter = resolver.resolve(new CriteriaSet()).iterator();
+        Assert.assertFalse(iter.hasNext());
+    }
+
+    protected void assertStandardOperators(final Map<String, MetadataPolicy> client) {
         final MetadataPolicy grantTypes = client.get("grant_types");
         Assert.assertNotNull(grantTypes);
         Assert.assertEquals(grantTypes.getOneOfValues(), List.of("authorization_code", "implicit"));
diff --git a/oidc-common-metadata-impl/src/test/resources/net/shibboleth/oidc/metadata/impl/metadata-policy1-custom.json b/oidc-common-metadata-impl/src/test/resources/net/shibboleth/oidc/metadata/impl/metadata-policy1-custom.json
new file mode 100644
index 0000000..e7b7be9
--- /dev/null
+++ b/oidc-common-metadata-impl/src/test/resources/net/shibboleth/oidc/metadata/impl/metadata-policy1-custom.json
@@ -0,0 +1,22 @@
+{
+    "grant_types": {
+        "one_of": ["authorization_code","implicit"]
+    },
+    "client_name": {
+        "default": "A known test application",
+        "bean": "customMetadataPolicyOperator"
+    },
+    "organization_name": {
+        "value": "A trusted organization"
+    },
+    "redirect_uris": {
+        "regexp": "^https:\/\/(?:([^.]+).)?example.org\/(.*)",
+        "essential": true
+    },
+    "id_token_signing_alg_values_supported": {
+        "subset_of": ["RS256", "RS384", "RS512"]
+    },
+    "scopes": {
+        "subset_of": ["openid", "profile", "email", "phone"]
+    }
+}
\ No newline at end of file

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


More information about the commits mailing list