[java-oidc-common] branch main updated: JCOMOIDC-28 - Support for metadata policies
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Dec 3 05:46:26 UTC 2021
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=31c9ada50fcdb7df02c8ec8efca7f186ce1a8841
The following commit(s) were added to refs/heads/main by this push:
new 31c9ada JCOMOIDC-28 - Support for metadata policies
31c9ada is described below
commit 31c9ada50fcdb7df02c8ec8efca7f186ce1a8841
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Dec 3 07:45:17 2021 +0200
JCOMOIDC-28 - Support for metadata policies
https://shibboleth.atlassian.net/browse/JCOMOIDC-28
---
.../oidc/metadata/policy/MetadataPolicy.java | 350 +++++++++++++++
.../metadata/policy/MetadataPolicyResolver.java | 34 ++
.../oidc/metadata/policy/package-info.java | 22 +
.../policy/impl/DefaultMetadataPolicyEnforcer.java | 196 +++++++++
.../impl/DefaultMetadataPolicyValidator.java | 167 ++++++++
.../metadata/policy/impl/MetadataPolicyHelper.java | 62 +++
.../policy/impl/OIDCMetadataPolicyResolver.java | 52 +++
.../oidc/metadata/policy/impl/package-info.java | 21 +
.../impl/DefaultMetadataPolicyEnforcerTest.java | 471 +++++++++++++++++++++
.../impl/DefaultMetadataPolicyValidatorTest.java | 282 ++++++++++++
.../impl/OIDCMetadataPolicyResolverTest.java | 152 +++++++
.../oidc/metadata/impl/metadata-policy1.json | 21 +
12 files changed, 1830 insertions(+)
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
new file mode 100644
index 0000000..6d31860
--- /dev/null
+++ b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/MetadataPolicy.java
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.metadata.policy;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The metadata policy for a single claim. It exploits the operators defined in the 5.1 of OIDC federation spec
+ * (draft 17 for time being): https://openid.net/specs/openid-connect-federation-1_0.html#rfc.section.5.1
+ */
+public class MetadataPolicy {
+
+ /** The (forced) value for the claim. */
+ @JsonProperty("value") private Object forcedValue;
+
+ /** The value(s) to be added for the claim. */
+ @JsonProperty("add") private Object add;
+
+ /** The default value for the claim to be used in case none is specified. */
+ @JsonProperty("default") private Object defaultValue;
+
+ /** The list of values from which the claim must be one of. */
+ @JsonProperty("one_of") private List<Object> oneOfValues;
+
+ /** The list of value(s) from which the claim must be subset of. */
+ @JsonProperty("subset_of") private List<Object> subsetOfValues;
+
+ /** The list of value(s) from which the claim must be superset of. */
+ @JsonProperty("superset_of") private List<Object> supersetOfValues;
+
+ /** The flag indicating that the claim must have a value. */
+ @JsonProperty("essential") private Boolean essential;
+
+ /** The regular expression that the claim value must meet. */
+ @JsonProperty("regexp") private String regexp;
+
+ /**
+ * Get the (forced) value for the claim.
+ *
+ * @return The (forced) value for the claim.
+ */
+ public Object getValue() {
+ return forcedValue;
+ }
+
+ /**
+ * Set the (forced) value for the claim.
+ *
+ * @param value What to set.
+ */
+ public void setValue(final Object value) {
+ this.forcedValue = value;
+ }
+
+ /**
+ * Get the value(s) to be added for the claim.
+ *
+ * @return The value(s) to be added for the claim.
+ */
+ public Object getAdd() {
+ return add;
+ }
+
+ /**
+ * Set the value(s) to be added for the claim.
+ *
+ * @param value What to set.
+ */
+ public void setAdd(final Object value) {
+ this.add = value;
+ }
+
+ /**
+ * Get the default value for the claim to be used in case none is specified.
+ *
+ * @return The default value for the claim to be used in case none is specified.
+ */
+ public Object getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * Set the default value for the claim to be used in case none is specified.
+ *
+ * @param value What to set.
+ */
+ public void setDefaultValue(final Object value) {
+ this.defaultValue = value;
+ }
+
+ /**
+ * Get the list of values from which the claim must be one of.
+ *
+ * @return The list of values from which the claim must be one of.
+ */
+ public List<Object> getOneOfValues() {
+ return oneOfValues;
+ }
+
+ /**
+ * Set the list of values from which the claim must be one of.
+ *
+ * @param values What to set.
+ */
+ public void setOneOfValues(final List<Object> values) {
+ this.oneOfValues = values;
+ }
+
+ /**
+ * Get the list of value(s) from which the claim must be subset of.
+ *
+ * @return The list of value(s) from which the claim must be subset of.
+ */
+ public List<Object> getSubsetOfValues() {
+ return subsetOfValues;
+ }
+
+ /**
+ * Set the list of value(s) from which the claim must be subset of.
+ *
+ * @param values What to set.
+ */
+ public void setSubsetOfValues(final List<Object> values) {
+ this.subsetOfValues = values;
+ }
+
+ /**
+ * Get the list of value(s) from which the claim must be superset of.
+ *
+ * @return The list of value(s) from which the claim must be superset of.
+ */
+ public List<Object> getSupersetOfValues() {
+ return supersetOfValues;
+ }
+
+ /**
+ * Set the list of value(s) from which the claim must be superset of.
+ *
+ * @param values What to set.
+ */
+ public void setSupersetOfValues(final List<Object> values) {
+ this.supersetOfValues = values;
+ }
+
+ /**
+ * Get the flag indicating that the claim must have a value.
+ *
+ * @return The flag indicating that the claim must have a value.
+ */
+ public Boolean getEssential() {
+ return essential;
+ }
+
+ /**
+ * Is the flag indicating that the claim must have a value enabled.
+ *
+ * @return true if enabled, false otherwise.
+ */
+ public boolean isEssential() {
+ return essential == null ? false : essential.booleanValue();
+ }
+
+ /**
+ * Set the flag indicating that the claim must have a value.
+ *
+ * @param isEssential What to set.
+ */
+ public void setEssential(final Boolean isEssential) {
+ this.essential = isEssential;
+ }
+
+ /**
+ * Get the regular expression that the claim value must meet.
+ *
+ * @return The regular expression that the claim value must meet.
+ */
+ public String getRegexp() {
+ return regexp;
+ }
+
+ /**
+ * Set the regular expression that the claim value must meet.
+ *
+ * @param value What to set.
+ */
+ public void setRegexp(final String value) {
+ regexp = value;
+ }
+
+ /**
+ * Builder class for the {@link MetadataPolicy} objects.
+ */
+ public static class Builder {
+
+ /** The (forced) value for the claim. */
+ private Object forcedValue;
+
+ /** The value(s) to be added for the claim. */
+ private Object add;
+
+ /** The default value for the claim to be used in case none is specified. */
+ private Object defaultValue;
+
+ /** The list of values from which the claim must be one of. */
+ private List<Object> oneOfValues;
+
+ /** The list of value(s) from which the claim must be subset of. */
+ private List<Object> subsetOfValues;
+
+ /** The list of value(s) from which the claim must be superset of. */
+ private List<Object> supersetOfValues;
+
+ /** The flag indicating that the claim must have a value. */
+ private Boolean essential;
+
+ /** The regular expression that the claim value must meet. */
+ private String regexp;
+
+ /**
+ * Constructor.
+ */
+ public Builder() {
+
+ }
+
+ /**
+ * Set the (forced) value for the claim.
+ *
+ * @param value What to set.
+ * @return The builder object with the new value set.
+ */
+ public Builder withValue(final Object value) {
+ this.forcedValue = value;
+ return this;
+ }
+
+ /**
+ * Set the value(s) to be added for the claim.
+ *
+ * @param value What to set.
+ * @return The builder object with the new value set.
+ */
+ public Builder withAdd(final Object value) {
+ this.add = value;
+ return this;
+ }
+
+ /**
+ * Set the default value for the claim to be used in case none is specified.
+ *
+ * @param value What to set.
+ * @return The builder object with the new value set.
+ */
+ public Builder withDefaultValue(final Object value) {
+ this.defaultValue = value;
+ return this;
+ }
+
+ /**
+ * Set the list of values from which the claim must be one of.
+ *
+ * @param values What to set.
+ * @return The builder object with the new value set.
+ */
+ public Builder withOneOfValues(final List<Object> values) {
+ this.oneOfValues = values;
+ return this;
+ }
+
+ /**
+ * Set the list of value(s) from which the claim must be subset of.
+ *
+ * @param values What to set.
+ * @return The builder object with the new value set.
+ */
+ public Builder withSubsetOfValues(final List<Object> values) {
+ this.subsetOfValues = values;
+ return this;
+ }
+
+ /**
+ * Set the list of value(s) from which the claim must be superset of.
+ *
+ * @param values What to set.
+ * @return The builder object with the new value set.
+ */
+ public Builder withSupersetOfValues(final List<Object> values) {
+ this.supersetOfValues = values;
+ return this;
+ }
+
+ /**
+ * Set the flag indicating that the claim must have a value.
+ *
+ * @param isEssential What to set.
+ * @return The builder object with the new value set.
+ */
+ public Builder withEssential(final Boolean isEssential) {
+ this.essential = isEssential;
+ return this;
+ }
+
+ /**
+ * Set the regular expression that the claim value must meet.
+ *
+ * @param value What to set.
+ * @return The builder object with the new value set.
+ */
+ public Builder withRegexp(final String value) {
+ this.regexp = value;
+ return this;
+ }
+
+ /**
+ * Build the metadata policy corresponding to the current builder state.
+ *
+ * @return The metadata policy corresponding to the current builder state.
+ */
+ public MetadataPolicy build() {
+ final MetadataPolicy policy = new MetadataPolicy();
+ policy.setValue(this.forcedValue);
+ policy.setAdd(this.add);
+ policy.setDefaultValue(this.defaultValue);
+ policy.setOneOfValues(this.oneOfValues);
+ policy.setSubsetOfValues(this.subsetOfValues);
+ policy.setSupersetOfValues(this.supersetOfValues);
+ policy.setEssential(this.essential);
+ policy.setRegexp(this.regexp);
+ return policy;
+ }
+ }
+
+}
diff --git a/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/MetadataPolicyResolver.java b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/MetadataPolicyResolver.java
new file mode 100644
index 0000000..7a0c068
--- /dev/null
+++ b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/MetadataPolicyResolver.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.metadata.policy;
+
+import java.util.Map;
+
+import net.shibboleth.utilities.java.support.component.IdentifiedComponent;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.Resolver;
+
+/**
+ * A resolver that is capable of resolving map of metadata policies (in the entry values as {@link MetadataPolicy}
+ * objects) which meet certain supplied criteria. The keys in the map refer to the claims for which the metadata
+ * policy is related to.
+ */
+public interface MetadataPolicyResolver extends Resolver<Map<String, MetadataPolicy>, CriteriaSet>,
+ IdentifiedComponent {
+
+}
diff --git a/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/package-info.java b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/package-info.java
new file mode 100644
index 0000000..75bf9ac
--- /dev/null
+++ b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/policy/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ *
+ * Metadata policy API.
+ */
+package net.shibboleth.oidc.metadata.policy;
\ No newline at end of file
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
new file mode 100644
index 0000000..c50a1b3
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcer.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.metadata.policy.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
+
+/**
+ * <p>A function that applies the given {@link MetadataPolicy} to the given object. The input is given as a {@link
+ * Pair} of the object and the policy. The policy is applied to the incoming object in the following way, as specified
+ * in the OIDC federation federation specification 1.0 (draft 17 / September 2021):</p>
+ *
+ * <ul>
+ * <li>If there is a value operator in the policy, apply that and you are done.</li>
+ * <li>Add whatever value is specified in an add operator.</li>
+ * <li>If the parameter still has no value apply the default if there is one.</li>
+ * <li>Do the essential check. If essential is missing as an operator essential is to be treated as if set to false.
+ * If essential is defined to be true, then the claim MUST have a value by now. Otherwise applying the operator MUST
+ * fail.</li>
+ * <li>Do the other checks. Verified that the value is one_of or that the values are subset_of/superset_of. If the
+ * parameter values do not fall within the allowed boundaries, applying the operator MUST fail.</li>
+ * </ul>
+ *
+ * <p>In addition to the checks above, we also support regular expression validation.</p>
+ *
+ * <p>The function returns a {@link Pair} of the object for which the value modifiers of the metadata policy have
+ * 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 Function<Pair<Object, MetadataPolicy>, Pair<Object, Boolean>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultMetadataPolicyEnforcer.class);
+
+ /** {@inheritDoc} */
+ @Override @Nonnull public Pair<Object, Boolean> apply(@Nonnull final Pair<Object, MetadataPolicy> pair) {
+ final Object candidate = pair.getFirst();
+ final MetadataPolicy policy = pair.getSecond();
+ if (policy == null) {
+ return new Pair<>(candidate, Boolean.TRUE);
+ }
+ final Object value = policy.getValue();
+ if (value != null) {
+ return new Pair<>(value, Boolean.TRUE);
+ }
+ final Object result;
+ final Object add = policy.getAdd();
+
+ if (add != null) {
+ try {
+ result = applyAddOperator(candidate, add);
+ } catch (final ConstraintViolationException e) {
+ log.warn("Could not add values to candidate: {}", e.getMessage());
+ return new Pair<>(candidate, Boolean.FALSE);
+ }
+ } else {
+ result = candidate == null ? policy.getDefaultValue() : candidate;
+ }
+
+ final boolean validation = doValueChecks(result, policy);
+ return new Pair<>(result, Boolean.valueOf(validation));
+ }
+
+ /**
+ * Applies the given add value modifier for the given candidate and returns the result of the operation.
+ *
+ * @param candidate The candidate for which the add operation is applied.
+ * @param add The value(s) to be added to the claim.
+ * @return An object containing the candidate for which the add operation has been applied.
+ * @throws ConstraintViolationException If the add operator is not compliant with the given candidate.
+ */
+ @Nonnull protected Object applyAddOperator(@Nullable final Object candidate, @Nonnull final Object add)
+ throws ConstraintViolationException {
+ if (candidate != null) {
+ if (candidate instanceof List) {
+ final List<Object> list = new ArrayList<>((List<?>)candidate);
+ if (add instanceof Collection) {
+ list.addAll((Collection<?>) add);
+ } else {
+ list.add(add);
+ }
+ return list;
+ } else {
+ if (add instanceof Collection) {
+ final Collection<?> collection = (Collection<?>) add;
+ if (collection.size() > 1 || !collection.contains(candidate)) {
+ throw new ConstraintViolationException("The array-values in add (" + add +
+ ") not compliant with the single existing value " + candidate);
+ }
+ }
+ if (!add.equals(candidate)) {
+ throw new ConstraintViolationException("The single-value in add (" + add +
+ ") does not match with the single existing value " + candidate);
+ }
+ return candidate;
+ }
+ }
+ if (add instanceof Collection) {
+ return List.copyOf((Collection<?>) add);
+ }
+ return add;
+ }
+
+ /**
+ * Runs the value check operators for the candidate.
+ *
+ * @param candidate The candidate to be verified.
+ * @param policy The metadata policy whose value check operators are used.
+ * @return true if the candidate is compliant with the metadata policy, false otherwise.
+ */
+ protected boolean doValueChecks(@Nullable final Object candidate, @Nonnull final MetadataPolicy policy) {
+ boolean validation = true;
+
+ if (candidate != null) {
+ final List<Object> oneOfValues = policy.getOneOfValues();
+ if (oneOfValues != null && !oneOfValues.contains(candidate)) {
+ log.warn("The candidate {} does not contain a value required by one_of {}", candidate, oneOfValues);
+ validation = false;
+ }
+ final List<Object> subsetOfValues = policy.getSubsetOfValues();
+ if (subsetOfValues != null && !MetadataPolicyHelper.isSubsetOfValues(candidate, subsetOfValues)) {
+ log.warn("The candidate {} is not a subset as required by subset_of {}", candidate, subsetOfValues);
+ validation = false;
+ }
+ final List<Object> supersetOfValues = policy.getSupersetOfValues();
+ if (supersetOfValues != null && !MetadataPolicyHelper.isSupersetOfValues(candidate, supersetOfValues)) {
+ log.warn("The candidate {} is not a superset as required by superset_of {}", candidate,
+ supersetOfValues);
+ validation = false;
+ }
+ if (!verifyRegexp(candidate, policy.getRegexp())) {
+ validation = false;
+ }
+ } else {
+ if (policy.isEssential()) {
+ log.warn("No value even though essential is set to true");
+ validation = false;
+ }
+ }
+ return validation;
+ }
+
+ /**
+ * Verifies that the given candidate meets the regular expression.
+ *
+ * @param candidate The candidate to be verified.
+ * @param regexp The regular expression.
+ * @return true if the candidate is compliant with regex, false otherwise.
+ */
+ protected boolean verifyRegexp(@Nonnull final Object candidate, @Nullable final String regexp) {
+ if (regexp != null) {
+ if (candidate instanceof List) {
+ for (final Object item : (List<?>) candidate) {
+ if (!Pattern.matches(regexp, item.toString())) {
+ log.warn("One of candidate values {} does not match the regex {}", item, regexp);
+ return false;
+ }
+ }
+ } else {
+ if (!Pattern.matches(regexp, candidate.toString())) {
+ log.warn("The candidate value {} does not match the regex {}", candidate, regexp);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
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
new file mode 100644
index 0000000..f58388f
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidator.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.metadata.policy.impl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.utilities.java.support.collection.Pair;
+
+/**
+ * <p>
+ * A function that verifies that the map of {@link MetadataPolicy} entries meets the restrictions defined in the
+ * OIDC federation specification 1.0 (draft 17 / September 2021):
+ * </p>
+ *
+ * <p>
+ * A policy entry can contain one or more operators. Not all operators are allowed to appear together in a policy
+ * entry.
+ * </p>
+ *
+ * <ul>
+ * <li>subset_of and superset_of applies to parameters that can have more than one value (for instance, contacts)
+ * while one_of applies to parameters that can only have one value (for instance, id_token_signed_response_alg). This
+ * means that one_of cannot appear beside subset_of/ superset_of in a policy entry.</li>
+ * <li>value overrides everything else. So having value together with any other operator (except for essential) does
+ * not make sense.</li>
+ * <li>If subset_of and superset_of both appear as operators, then the list of values in subset_of MUST be a superset
+ * of the values in superset_of.<li>
+ * <li>If add appears in a policy entry together with subset_of then the value/values of add MUST be a subset of
+ * subset_of.<li>
+ * <li>If add appears in a policy entry together with superset_of then the values of add MUST be a superset of
+ * superset_of.<li>
+ * <li>If default appears in a policy entry together with subset_of then the values of default MUST be a subset of
+ * subset_of.<li>
+ * <li>If default appears in a policy entry together with superset_of then the values of default MUST be a superset of
+ * superset_of.<li>
+ * <li>If add appears in a policy entry together with one_of then the value of add MUST be a member of one_of.<li>
+ * <li>If default appears in a policy entry together with one_of then the value default MUST be a member of one_of.
+ * </li>
+ * </ul>
+ */
+public class DefaultMetadataPolicyValidator implements Predicate<Map<String, MetadataPolicy>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultMetadataPolicyValidator.class);
+
+
+ /** {@inheritDoc} */
+ // Checkstyle: CyclomaticComplexity OFF
+ @Override
+ public boolean test(@Nullable final Map<String, MetadataPolicy> map) {
+ boolean result = true;
+ if (map == null || map.isEmpty()) {
+ return true;
+ }
+ for (final String claim : map.keySet()) {
+ final MetadataPolicy policy = map.get(claim);
+ final List<Object> oneOfValues = policy.getOneOfValues();
+ final List<Object> subsetOfValues = policy.getSubsetOfValues();
+ final List<Object> supersetOfValues = policy.getSupersetOfValues();
+ if ((subsetOfValues != null || supersetOfValues != null) && oneOfValues != null) {
+ log.warn("Claim {}: one_of cannot be set when superset_of and/or subset_of is set", claim);
+ result = false;
+ }
+
+ final Object value = policy.getValue();
+ final Object add = policy.getAdd();
+ final Object defaultValue = policy.getDefaultValue();
+ final String regexp = policy.getRegexp();
+
+ if (value != null) {
+ if (add != null || defaultValue != null || oneOfValues != null || regexp != null
+ || subsetOfValues != null || supersetOfValues != null) {
+ log.warn("Claim {}: value is set together with modifiers or value checks (other than essential)",
+ claim);
+ result = false;
+ }
+ }
+ if (supersetOfValues != null && subsetOfValues != null) {
+ if (!subsetOfValues.containsAll(supersetOfValues)) {
+ log.warn("Claim {}: subset_of and superset_of set, but subset_of is not a superset of superset_of",
+ claim);
+ result = false;
+ }
+ }
+
+ if (!verifyValue(claim, new Pair<>("add", add), subsetOfValues, supersetOfValues, oneOfValues)) {
+ result = false;
+ }
+
+ if (!verifyValue(claim, new Pair<>("default", defaultValue), subsetOfValues, supersetOfValues,
+ oneOfValues)) {
+ result = false;
+ }
+
+ }
+ return result;
+ }
+ // Checkstyle: CyclomaticComplexity ON
+
+ /**
+ * <p>
+ * Verifies the value against the following rules:
+ * </p>
+ *
+ * <ul>
+ * <li>If it appears in a policy entry together with subset_of then the value/values of add MUST be a subset of
+ * subset_of.</li>
+ * <li>If it appears in a policy entry together with superset_of then the values of add MUST be a superset of
+ * superset_of.</li>
+ * <li>If it appears in a policy entry together with one_of then the value of add MUST be a member of one_of.</li>
+ * </ul>
+ *
+ * @param claim The claim whose metadata policy is being verified, used in logging.
+ * @param value The value to be verified against the rules.
+ * @param subsetOfValues The contents of subset_of.
+ * @param supersetOfValues The contents of superset_of.
+ * @param oneOfValues The contents of one_of.
+ * @return true if the value meets the rules, false otherwise.
+ */
+ protected boolean verifyValue(final String claim, final Pair<String, Object> value,
+ final List<Object> subsetOfValues, final List<Object> supersetOfValues, final List<Object> oneOfValues) {
+ boolean result = true;
+ final String valueId = value.getFirst();
+ final Object valueObject = value.getSecond();
+ if (valueObject != null) {
+ if (subsetOfValues != null && !MetadataPolicyHelper.isSubsetOfValues(valueObject, subsetOfValues)) {
+ log.warn("Claim {}: {} contains values that are not subset of values in subset_of", claim, valueId);
+ result = false;
+ }
+ if (supersetOfValues != null && !MetadataPolicyHelper.isSupersetOfValues(valueObject, supersetOfValues)) {
+ log.warn("Claim {}: {} contains values that are not superset of values in superset_of", claim,
+ valueId);
+ result = false;
+ }
+ if (oneOfValues != null && !oneOfValues.contains(valueObject)) {
+ log.warn("Claim {}: {} contains value that is not included in one_of", claim, valueId);
+ result = false;
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/MetadataPolicyHelper.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/MetadataPolicyHelper.java
new file mode 100644
index 0000000..7d2b678
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/MetadataPolicyHelper.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.metadata.policy.impl;
+
+import java.util.Collection;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Static utility methods related to metadata policies.
+ */
+public final class MetadataPolicyHelper {
+
+ /**
+ * Constructor.
+ */
+ private MetadataPolicyHelper() {
+ // no op
+ }
+
+ /**
+ * Checks if the given candidate is a superset of the given values.
+ *
+ * @param candidate The candidate to be checked. May not be null.
+ * @param values The values to be checked. May not be null.
+ * @return true if the candidate is a superset of the values or they are equal, false otherwise.
+ */
+ public static boolean isSupersetOfValues(@Nonnull final Object candidate, @Nonnull final Collection<?> values) {
+ return candidate instanceof Collection
+ ? ((Collection<?>) candidate).containsAll(values)
+ : values.size() == 1 && values.contains(candidate);
+ }
+
+ /**
+ * Checks if the given candiate is a subset of the given values.
+ *
+ * @param candidate The candidate to be checked. May not be null.
+ * @param values The values to be checked. May not be null.
+ * @return true if the candidate is a subset of the values or they are equal, false otherwise.
+ */
+ public static boolean isSubsetOfValues(@Nonnull final Object candidate, @Nonnull final Collection<?> values) {
+ return candidate instanceof Collection
+ ? values.containsAll((Collection<?>) candidate)
+ : values.contains(candidate);
+ }
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/OIDCMetadataPolicyResolver.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/OIDCMetadataPolicyResolver.java
new file mode 100644
index 0000000..44569ed
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/OIDCMetadataPolicyResolver.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.metadata.policy.impl;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.impl.AbstractOIDCMetadataResolver;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicyResolver;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * Concrete resolver implementation for metadata policy resolution.
+ */
+public class OIDCMetadataPolicyResolver extends AbstractOIDCMetadataResolver<String, Map<String, MetadataPolicy>>
+ implements MetadataPolicyResolver {
+
+ /**
+ * Constructor.
+ *
+ * @param metadataCache the cache to hold metadata.
+ */
+ public OIDCMetadataPolicyResolver(
+ @Nonnull final MetadataCache<Map<String, MetadataPolicy>> metadataCache) {
+ super(metadataCache);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void initMetadataResolver() throws ComponentInitializationException {
+ // Do nothing
+ }
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/package-info.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/package-info.java
new file mode 100644
index 0000000..8640f86
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/policy/impl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Metadata policy implementation.
+ */
+package net.shibboleth.oidc.metadata.policy.impl;
\ No newline at end of file
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
new file mode 100644
index 0000000..410ab6b
--- /dev/null
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyEnforcerTest.java
@@ -0,0 +1,471 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.metadata.policy.impl;
+
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.utilities.java.support.collection.Pair;
+
+/**
+ * Unit tests for {@link DefaultMetadataPolicyEnforcer}.
+ */
+public class DefaultMetadataPolicyEnforcerTest {
+
+ DefaultMetadataPolicyEnforcer applier;
+
+ @BeforeMethod
+ public void init() {
+ applier = new DefaultMetadataPolicyEnforcer();
+ }
+
+ @Test
+ public void apply_whenValueSetToNull_resultIsValueWithTrue() {
+ final String stringValue = "mockValue";
+ assertResultEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withValue(stringValue).build())),
+ stringValue);
+ final List<String> listValue = List.of("value1", "value2");
+ assertResultEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withValue(listValue).build())),
+ listValue);
+ final int intValue = 123;
+ assertResultEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withValue(intValue).build())),
+ intValue);
+ final boolean booleanValue = true;
+ assertResultEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withValue(booleanValue).build())),
+ booleanValue);
+ }
+
+ @Test
+ public void apply_whenValueSetToSomething_resultIsValueWithTrue() {
+ final String stringValue = "mockValue";
+ assertResultEquals(
+ applier.apply(new Pair<>(4321, new MetadataPolicy.Builder().withValue(stringValue).build())),
+ stringValue);
+ final List<String> listValue = List.of("value1", "value2");
+ assertResultEquals(
+ applier.apply(new Pair<>("mock", new MetadataPolicy.Builder().withValue(listValue).build())),
+ listValue);
+ final int intValue = 123;
+ assertResultEquals(
+ applier.apply(new Pair<>(false, new MetadataPolicy.Builder().withValue(intValue).build())),
+ intValue);
+ final boolean booleanValue = true;
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of("1"), new MetadataPolicy.Builder().withValue(booleanValue).build())),
+ booleanValue);
+ }
+
+ @Test
+ public void apply_whenAddToNullCandidate_resultIsSameAsWithValue() {
+ final String stringValue = "mockValue";
+ System.out.println("" + applier.apply(new Pair<>(null, new MetadataPolicy.Builder()
+ .withAdd(stringValue).build())));
+ Assert.assertEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withAdd(stringValue).build())),
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withValue(stringValue).build())));
+ final List<String> listValue = List.of("value1", "value2");
+ Assert.assertEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withAdd(listValue).build())),
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withValue(listValue).build())));
+ final int intValue = 123;
+ Assert.assertEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withAdd(intValue).build())),
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withValue(intValue).build())));
+ final boolean booleanValue = true;
+ Assert.assertEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withAdd(booleanValue).build())),
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withValue(booleanValue).build())));
+
+ }
+
+ @Test
+ public void apply_whenAddToSameExistingCandidate_resultIsSameWithTrue() {
+ assertResultEquals(
+ applier.apply(new Pair<>("existing", new MetadataPolicy.Builder().withAdd("existing").build())),
+ "existing");
+ assertResultEquals(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withAdd(123).build())),
+ 123);
+ assertResultEquals(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withAdd(true).build())),
+ true);
+ }
+
+ @Test
+ public void apply_whenAddToDifferentSingleExistingCandidate_resultIsFalse() {
+ assertResultFalse(
+ applier.apply(new Pair<>("existing", new MetadataPolicy.Builder().withAdd("another").build())));
+ assertResultFalse(
+ applier.apply(new Pair<>("existing", new MetadataPolicy.Builder().withAdd(List.of("another"))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withAdd(321).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withAdd(false).build())));
+ }
+
+ @Test
+ public void apply_whenAddToListCandidate_resultIsMergedWithTrue() {
+ final List<String> listValue = List.of("value1", "value2");
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of("value0"),
+ new MetadataPolicy.Builder().withAdd(listValue).build())),
+ List.of("value0", "value1", "value2"));
+ final List<Integer> intValues = List.of(123);
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(321), new MetadataPolicy.Builder().withAdd(intValues).build())),
+ List.of(321, 123));
+ final List<Boolean> booleanValues = List.of(true);
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(false), new MetadataPolicy.Builder().withAdd(booleanValues).build())),
+ List.of(false, true));
+ }
+
+ @Test
+ public void apply_whenNoCandidateAndDefaultSet_resultIsDefaultWithTrue() {
+ assertResultEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withDefaultValue("default").build())),
+ "default");
+ assertResultEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withDefaultValue(List.of("default"))
+ .build())),
+ List.of("default"));
+ assertResultEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withDefaultValue(123).build())),
+ 123);
+ assertResultEquals(
+ applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withDefaultValue(true).build())),
+ true);
+ }
+
+ @Test
+ public void apply_whenCandidateAndDefaultSet_resultIsCandidateWithTrue() {
+ assertResultEquals(
+ applier.apply(new Pair<>("existing", new MetadataPolicy.Builder()
+ .withDefaultValue("default").build())),
+ "existing");
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of("existing"), new MetadataPolicy.Builder()
+ .withDefaultValue(List.of("default")).build())),
+ List.of("existing"));
+ assertResultEquals(
+ applier.apply(new Pair<>(321, new MetadataPolicy.Builder().withDefaultValue(123).build())),
+ 321);
+ assertResultEquals(
+ applier.apply(new Pair<>(false, new MetadataPolicy.Builder().withDefaultValue(true).build())),
+ false);
+ }
+
+ @Test
+ public void apply_whenEssentialAndNoValue_resultIsFalse() {
+ assertResultFalse(applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withEssential(true).build())));
+ }
+
+ @Test
+ public void apply_whenNonEssentialAndNoValue_resultIsNullWithTrue() {
+ // default is that essential=false
+ assertResultEquals(applier.apply(new Pair<>(null, new MetadataPolicy.Builder().build())),
+ null);
+ assertResultEquals(applier.apply(new Pair<>(null, new MetadataPolicy.Builder().withEssential(false).build())),
+ null);
+ }
+
+ @Test
+ public void apply_whenOneOfMeetsValue_resultIsCandidateWithTrue() {
+ assertResultEquals(
+ applier.apply(new Pair<>("existing", new MetadataPolicy.Builder().withOneOfValues(List.of("existing"))
+ .build())),
+ "existing");
+ assertResultEquals(
+ applier.apply(new Pair<>("existing", new MetadataPolicy.Builder().withOneOfValues(List.of("existing",
+ "another")).build())),
+ "existing");
+ assertResultEquals(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withOneOfValues(List.of(123))
+ .build())),
+ 123);
+ assertResultEquals(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withOneOfValues(List.of(123, 321))
+ .build())),
+ 123);
+ assertResultEquals(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withOneOfValues(List.of(true))
+ .build())),
+ true);
+ assertResultEquals(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withOneOfValues(List.of(true, false))
+ .build())),
+ true);
+ }
+
+ @Test
+ public void apply_whenOneOfNotMeetingValue_resultIsFalse() {
+ assertResultFalse(
+ applier.apply(new Pair<>("not", new MetadataPolicy.Builder().withOneOfValues(List.of("existing"))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>("not", new MetadataPolicy.Builder().withOneOfValues(List.of("existing",
+ "another")).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withOneOfValues(List.of(321))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withOneOfValues(List.of(321, 322))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withOneOfValues(List.of(false))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(false, new MetadataPolicy.Builder().withOneOfValues(List.of(true))
+ .build())));
+ }
+
+ @Test
+ public void apply_whenSubsetOfMeetsValue_resultIsCandidateWithTrue() {
+ // single values
+ assertResultEquals(
+ applier.apply(new Pair<>("existing", new MetadataPolicy.Builder().withSubsetOfValues(List.of(
+ "existing")).build())),
+ "existing");
+ assertResultEquals(
+ applier.apply(new Pair<>("existing", new MetadataPolicy.Builder().withSubsetOfValues(List.of(
+ "existing", "another")).build())),
+ "existing");
+ assertResultEquals(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withSubsetOfValues(List.of(123))
+ .build())),
+ 123);
+ assertResultEquals(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withSubsetOfValues(List.of(123, 321))
+ .build())),
+ 123);
+ assertResultEquals(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withSubsetOfValues(List.of(true))
+ .build())),
+ true);
+ assertResultEquals(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withSubsetOfValues(List.of(true, false))
+ .build())),
+ true);
+
+ // list of values
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of("existing", "another"),
+ new MetadataPolicy.Builder().withSubsetOfValues(List.of("existing", "another")).build())),
+ List.of("existing", "another"));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of("existing", "another"),
+ new MetadataPolicy.Builder().withSubsetOfValues(List.of(
+ "existing", "another", "yet_another")).build())),
+ List.of("existing", "another"));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(123, 321), new MetadataPolicy.Builder()
+ .withSubsetOfValues(List.of(123, 321)).build())),
+ List.of(123, 321));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(123, 321), new MetadataPolicy.Builder()
+ .withSubsetOfValues(List.of(123, 321, 213)).build())),
+ List.of(123, 321));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(true), new MetadataPolicy.Builder().withSubsetOfValues(List.of(true))
+ .build())),
+ List.of(true));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(true, false), new MetadataPolicy.Builder()
+ .withSubsetOfValues(List.of(true, false)).build())),
+ List.of(true, false));
+ }
+
+ @Test
+ public void apply_whenSubsetOfNotMeetingValue_resultIsFalse() {
+ // single values
+ assertResultFalse(
+ applier.apply(new Pair<>("not", new MetadataPolicy.Builder().withSubsetOfValues(List.of(
+ "existing")).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>("not", new MetadataPolicy.Builder().withSubsetOfValues(List.of(
+ "existing", "another")).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(321, new MetadataPolicy.Builder().withSubsetOfValues(List.of(123))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withSubsetOfValues(List.of(223, 321))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withSubsetOfValues(List.of(false))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(false, new MetadataPolicy.Builder().withSubsetOfValues(List.of(true))
+ .build())));
+
+ // list of values
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of("not"),
+ new MetadataPolicy.Builder().withSubsetOfValues(List.of("existing", "another")).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of("not", "neither"),
+ new MetadataPolicy.Builder().withSubsetOfValues(List.of(
+ "existing", "another", "yet_another")).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of(123), new MetadataPolicy.Builder()
+ .withSubsetOfValues(List.of(321, 432)).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of(123, 321), new MetadataPolicy.Builder()
+ .withSubsetOfValues(List.of(123, 213)).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of(true), new MetadataPolicy.Builder()
+ .withSubsetOfValues(List.of(false)).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of(false), new MetadataPolicy.Builder()
+ .withSubsetOfValues(List.of(true)).build())));
+ }
+
+ @Test
+ public void apply_whenSupersetOfMeetsValue_resultIsCandidateWithTrue() {
+ // single values
+ assertResultEquals(
+ applier.apply(new Pair<>("existing", new MetadataPolicy.Builder().withSupersetOfValues(List.of(
+ "existing")).build())),
+ "existing");
+ assertResultEquals(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withSupersetOfValues(List.of(123))
+ .build())),
+ 123);
+ assertResultEquals(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withSupersetOfValues(List.of(true))
+ .build())),
+ true);
+ assertResultEquals(
+ applier.apply(new Pair<>(false, new MetadataPolicy.Builder().withSupersetOfValues(List.of(false))
+ .build())),
+ false);
+
+ // list of values
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of("existing", "another"),
+ new MetadataPolicy.Builder().withSupersetOfValues(List.of("existing", "another")).build())),
+ List.of("existing", "another"));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of("existing", "another"),
+ new MetadataPolicy.Builder().withSupersetOfValues(List.of(
+ "existing")).build())),
+ List.of("existing", "another"));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(123, 321), new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of(123, 321)).build())),
+ List.of(123, 321));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(123, 321, 213), new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of(123)).build())),
+ List.of(123, 321, 213));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(true), new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of(true)).build())),
+ List.of(true));
+ assertResultEquals(
+ applier.apply(new Pair<>(List.of(true, false), new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of(false)).build())),
+ List.of(true, false));
+ }
+
+ @Test
+ public void apply_whenSupersetOfNotMeetingValue_resultIsFalse() {
+ // single values
+ assertResultFalse(
+ applier.apply(new Pair<>("not", new MetadataPolicy.Builder().withSupersetOfValues(List.of(
+ "existing")).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>("not", new MetadataPolicy.Builder().withSupersetOfValues(List.of(
+ "existing", "another")).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(321, new MetadataPolicy.Builder().withSupersetOfValues(List.of(123))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(123, new MetadataPolicy.Builder().withSupersetOfValues(List.of(223, 321))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(true, new MetadataPolicy.Builder().withSupersetOfValues(List.of(false))
+ .build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(false, new MetadataPolicy.Builder().withSupersetOfValues(List.of(true))
+ .build())));
+
+ // list of values
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of("not"),
+ new MetadataPolicy.Builder().withSupersetOfValues(List.of("not", "another")).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of("not", "neither"),
+ new MetadataPolicy.Builder().withSupersetOfValues(List.of(
+ "not", "neither", "another")).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of(123), new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of(123, 321)).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of(123, 321), new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of(123, 321, 213)).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of(true), new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of(false)).build())));
+ assertResultFalse(
+ applier.apply(new Pair<>(List.of(false), new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of(true, false)).build())));
+ }
+
+ @Test
+ public void apply_whenRegexMeetsValue_resultIsCandidateWithTrue() {
+ final String candidate = "https://sub.example.org/cb";
+ assertResultEquals(applier.apply(new Pair<>(candidate,
+ new MetadataPolicy.Builder().withRegexp(".*").build())), candidate);
+ assertResultEquals(applier.apply(new Pair<>(candidate,
+ new MetadataPolicy.Builder().withRegexp("^https:\\/\\/(?:([^.]+)\\.)?example\\.org\\/(.*)").build())),
+ candidate);
+ final List<String> candidates = List.of(candidate + "1", candidate + "2");
+ assertResultEquals(applier.apply(new Pair<>(candidates,
+ new MetadataPolicy.Builder().withRegexp("^https:\\/\\/(?:([^.]+)\\.)?example\\.org\\/(.*)").build())),
+ candidates);
+ }
+
+ @Test
+ public void apply_whenRegexNotMeetingValue_resultIsFalse() {
+ final String regex = "^https:\\/\\/(?:([^.]+)\\.)?example\\.org\\/(.*)";
+ assertResultFalse(applier.apply(new Pair<>("http://example.org/cb",
+ new MetadataPolicy.Builder().withRegexp(regex).build())));
+ assertResultFalse(applier.apply(new Pair<>("https://example.com/cb",
+ new MetadataPolicy.Builder().withRegexp(regex).build())));
+ assertResultFalse(applier.apply(new Pair<>(List.of("https://sub.example.org/cb", "https://sub.example.com/cb"),
+ new MetadataPolicy.Builder().withRegexp(regex).build())));
+ }
+
+ public static void assertResultEquals(final Pair<Object, Boolean> pair, final Object expected) {
+ Assert.assertTrue(pair.getSecond());
+ Assert.assertEquals(pair.getFirst(), expected);
+ }
+
+ public static void assertResultFalse(final Pair<Object, Boolean> pair) {
+ Assert.assertFalse(pair.getSecond());
+ }
+}
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
new file mode 100644
index 0000000..cf30170
--- /dev/null
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/DefaultMetadataPolicyValidatorTest.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.metadata.policy.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+
+/**
+ * Unit tests for {@link DefaultMetadataPolicyValidator}.
+ */
+public class DefaultMetadataPolicyValidatorTest {
+
+ DefaultMetadataPolicyValidator validator;
+
+ @BeforeMethod
+ public void init() {
+ validator = new DefaultMetadataPolicyValidator();
+ }
+
+ @Test
+ public void test_whenOneOfAndSupersetOfHaveBeenSet_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withOneOfValues(List.of("must"))
+ .withSupersetOfValues(List.of("must"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenOneOfAndSubsetOfHaveBeenSet_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withOneOfValues(List.of("must"))
+ .withSubsetOfValues(List.of("must"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenValueAndAddIsSet_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withValue("mockValue")
+ .withAdd("another")
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenValueAndDefaultSet_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withValue("mockValue")
+ .withDefaultValue("mockValue")
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenValueAndOneOfSet_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withValue("mockValue")
+ .withOneOfValues(List.of("mockValue"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenValueAndSubsetOfSet_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withValue("mockValue")
+ .withSubsetOfValues(List.of("mockValue"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenValueAndSupersetOfSet_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withValue("mockValue")
+ .withSupersetOfValues(List.of("mockValue"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenValueAndRegexpSet_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withValue("mockValue")
+ .withRegexp(".*")
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenValueAndEssentialSet_shouldReturnTrue() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withValue("mockValue")
+ .withEssential(true)
+ .build());
+ Assert.assertTrue(validator.test(map));
+ }
+
+ @Test
+ public void test_whenValueAndNonEssentialSet_shouldReturnTrue() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withValue("mockValue")
+ .withEssential(false)
+ .build());
+ Assert.assertTrue(validator.test(map));
+ }
+
+
+ @Test
+ public void test_whenSubsetAndSupersetOfSet_subsetOfNotBeingSuperset_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of("mockValue", "mockValue2"))
+ .withSubsetOfValues(List.of("mockValue"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenSubsetAndSupersetOfSet_subsetOfMatchingSupersetOf_shouldReturnTrue() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withSupersetOfValues(List.of("mockValue", "mockValue2"))
+ .withSubsetOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertTrue(validator.test(map));
+ }
+
+ @Test
+ public void test_whenSubsetOfAndAdd_singleAddNotBeingInSubset_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withAdd("mockValue")
+ .withSubsetOfValues(List.of("mockValue2", "mockValue3"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenSubsetOfAndAdd_listAddNotBeingInSubset_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withAdd(List.of("mockValue"))
+ .withSubsetOfValues(List.of("mockValue2", "mockValue3"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenSupersetOfAndAdd_singleAddNotBeingInSuperset_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withAdd("mockValue")
+ .withSupersetOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenSupersetOfAndAdd_listAddNotBeingInSuperset_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withAdd(List.of("mockValue"))
+ .withSupersetOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenSubsetOfAndDefault_singleDefaultNotBeingInSubset_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withDefaultValue("mockValue")
+ .withSubsetOfValues(List.of("mockValue2", "mockValue3"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenSubsetOfAndDefault_listDefaultNotBeingInSubset_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withDefaultValue(List.of("mockValue"))
+ .withSubsetOfValues(List.of("mockValue2", "mockValue3"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenSupersetOfAndDefault_singleDefaultNotBeingInSuperset_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withDefaultValue("mockValue")
+ .withSupersetOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenSupersetOfAndDefault_listDefaultNotBeingInSuperset_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withDefaultValue(List.of("mockValue"))
+ .withSupersetOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+ }
+
+ @Test
+ public void test_whenOneOfAndAdd_singleAddInOneOf_shouldReturnTrue() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withAdd("mockValue")
+ .withOneOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertTrue(validator.test(map));
+
+ }
+
+ @Test
+ public void test_whenOneOfAndAdd_singleAddNotInOneOf_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withAdd("mockValue3")
+ .withOneOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+
+ }
+
+ @Test
+ public void test_whenOneOfAndAdd_listAddInOneOf_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withAdd(List.of("mockValue"))
+ .withOneOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+
+ }
+
+ @Test
+ public void test_whenOneOfAndDefault_singleDefaultInOneOf_shouldReturnTrue() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withDefaultValue("mockValue")
+ .withOneOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertTrue(validator.test(map));
+
+ }
+
+ @Test
+ public void test_whenOneOfAndDefault_singleDefaultNotInOneOf_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withDefaultValue("mockValue3")
+ .withOneOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertFalse(validator.test(map));
+
+ }
+
+ @Test
+ public void test_whenOneOfAndDefault_listDefaultInOneOf_shouldReturnFalse() {
+ final Map<String, MetadataPolicy> map = Map.of("mockClaim", new MetadataPolicy.Builder()
+ .withDefaultValue(List.of("mockValue"))
+ .withOneOfValues(List.of("mockValue", "mockValue2"))
+ .build());
+ Assert.assertFalse(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
new file mode 100644
index 0000000..5ae1209
--- /dev/null
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/policy/impl/OIDCMetadataPolicyResolverTest.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.metadata.policy.impl;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCache;
+import net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilder;
+import net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilderSpec;
+import net.shibboleth.oidc.metadata.cache.impl.DefaultFileLoadingStrategy;
+import net.shibboleth.oidc.metadata.cache.impl.DefaultJSONMapParsingStrategy;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+/**
+ * Unit tests for {@link OIDCMetadataPolicyResolver}.
+ */
+public class OIDCMetadataPolicyResolverTest {
+
+ OIDCMetadataPolicyResolver resolver;
+
+ public void initTest(final String filename) throws Exception {
+ resolver = new OIDCMetadataPolicyResolver(buildCache(filename));
+ resolver.setId("mockId");
+ resolver.initialize();
+ }
+
+ protected BatchMetadataCache<String, Map<String, MetadataPolicy>> buildCache(String filename)
+ throws IOException, ComponentInitializationException {
+ final Resource file = filename == null || filename.isEmpty()? null : new ClassPathResource(filename);
+
+ BatchMetadataCacheBuilderSpec<String, Map<String, MetadataPolicy>> spec =
+ new BatchMetadataCacheBuilderSpec<String, Map<String, MetadataPolicy>>();
+ var builder = new BatchMetadataCacheBuilder.Builder<String, Map<String, MetadataPolicy>>();
+ spec.setLoadingStrategy(new DefaultFileLoadingStrategy(file));
+ spec.setParsingStrategy(new DefaultJSONMapParsingStrategy<>(MetadataPolicy.class));
+ spec.setMinRefreshDelay(Duration.ofMinutes(5));
+ spec.setMaxRefreshDelay(Duration.ofMinutes(10));
+ spec.setSourceMetadataExpiryStrategy(m -> Instant.now().plus(Duration.ofMinutes(5)));
+ spec.setCriteriaToIdentifierStrategy(crit -> "id");
+ spec.setIdentifierExtractionStrategy(crit -> "id");
+
+ spec.setMatchRequired(true);
+
+ spec.setRefreshDelayFactor(0.75f);
+ spec.setMetadataFilterStrategy((metadata, context) -> metadata);
+ return builder.build(spec);
+ }
+
+ @Test
+ public void testFound() throws Exception {
+ initTest("/net/shibboleth/oidc/metadata/impl/metadata-policy1.json");
+ 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);
+
+ final MetadataPolicy grantTypes = client.get("grant_types");
+ Assert.assertNotNull(grantTypes);
+ Assert.assertEquals(grantTypes.getOneOfValues(), List.of("authorization_code", "implicit"));
+ Assert.assertNull(grantTypes.getAdd());
+ Assert.assertNull(grantTypes.getDefaultValue());
+ Assert.assertNull(grantTypes.getValue());
+ Assert.assertFalse(grantTypes.isEssential());
+ Assert.assertNull(grantTypes.getRegexp());
+ Assert.assertNull(grantTypes.getSubsetOfValues());
+ Assert.assertNull(grantTypes.getSupersetOfValues());
+
+ final MetadataPolicy clientName = client.get("client_name");
+ Assert.assertNotNull(clientName);
+ Assert.assertNull(clientName.getAdd());
+ Assert.assertEquals(clientName.getDefaultValue(), "A known test application");
+ Assert.assertNull(clientName.getValue());
+ Assert.assertFalse(clientName.isEssential());
+ Assert.assertNull(clientName.getOneOfValues());
+ Assert.assertNull(clientName.getRegexp());
+ Assert.assertNull(clientName.getSubsetOfValues());
+ Assert.assertNull(clientName.getSupersetOfValues());
+
+ final MetadataPolicy organizationName = client.get("organization_name");
+ Assert.assertNotNull(organizationName);
+ Assert.assertNull(organizationName.getAdd());
+ Assert.assertNull(organizationName.getDefaultValue());
+ Assert.assertEquals(organizationName.getValue(), "A trusted organization");
+ Assert.assertFalse(organizationName.isEssential());
+ Assert.assertNull(organizationName.getOneOfValues());
+ Assert.assertNull(organizationName.getRegexp());
+ Assert.assertNull(organizationName.getSubsetOfValues());
+ Assert.assertNull(organizationName.getSupersetOfValues());
+
+ final MetadataPolicy redirectUris = client.get("redirect_uris");
+
+ Assert.assertNotNull(redirectUris);
+ Assert.assertNull(redirectUris.getAdd());
+ Assert.assertNull(redirectUris.getDefaultValue());
+ Assert.assertNull(redirectUris.getValue());
+ Assert.assertTrue(redirectUris.isEssential());
+ Assert.assertNull(redirectUris.getOneOfValues());
+ Assert.assertEquals(redirectUris.getRegexp(), "^https:\\/\\/(?:([^.]+).)?example.org\\/(.*)");
+ Assert.assertNull(redirectUris.getSubsetOfValues());
+ Assert.assertNull(redirectUris.getSupersetOfValues());
+
+ final MetadataPolicy algValuesSupported = client.get("id_token_signing_alg_values_supported");
+ Assert.assertNotNull(algValuesSupported);
+ Assert.assertNull(algValuesSupported.getAdd());
+ Assert.assertNull(algValuesSupported.getDefaultValue());
+ Assert.assertNull(algValuesSupported.getValue());
+ Assert.assertFalse(algValuesSupported.isEssential());
+ Assert.assertNull(algValuesSupported.getOneOfValues());
+ Assert.assertNull(algValuesSupported.getRegexp());
+ Assert.assertEquals(algValuesSupported.getSubsetOfValues(), List.of("RS256", "RS384", "RS512"));
+ Assert.assertNull(algValuesSupported.getSupersetOfValues());
+
+ final MetadataPolicy scopes = client.get("scopes");
+ Assert.assertNotNull(scopes);
+ Assert.assertNull(scopes.getAdd());
+ Assert.assertNull(scopes.getDefaultValue());
+ Assert.assertNull(scopes.getValue());
+ Assert.assertFalse(scopes.isEssential());
+ Assert.assertNull(scopes.getOneOfValues());
+ Assert.assertNull(scopes.getRegexp());
+ Assert.assertEquals(scopes.getSubsetOfValues(), List.of("openid", "profile", "email", "phone"));
+ Assert.assertNull(scopes.getSupersetOfValues());
+
+ }
+}
\ No newline at end of file
diff --git a/oidc-common-metadata-impl/src/test/resources/net/shibboleth/oidc/metadata/impl/metadata-policy1.json b/oidc-common-metadata-impl/src/test/resources/net/shibboleth/oidc/metadata/impl/metadata-policy1.json
new file mode 100644
index 0000000..52907aa
--- /dev/null
+++ b/oidc-common-metadata-impl/src/test/resources/net/shibboleth/oidc/metadata/impl/metadata-policy1.json
@@ -0,0 +1,21 @@
+{
+ "grant_types": {
+ "one_of": ["authorization_code","implicit"]
+ },
+ "client_name": {
+ "default": "A known test application"
+ },
+ "organization_name": {
+ "value": "A trusted organization"
+ },
+ "redirect_uris": {
+ "regexp": "^https:\/\/(?:([^.]+).)?example.org\/(.*)",
+ "essential": true
+ },
+ "id_token_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