[java-opensaml] branch main updated: IDP-1870: Detect duplicate entityIDs when ingesting metadata
Brent Putman
putmanb at georgetown.edu
Fri Nov 19 04:29:39 UTC 2021
This is an automated email from the git hooks/post-receive script.
putmanb pushed a commit to branch main
in repository java-opensaml.
View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=4f8b4c77b747cb5c70eb904d0a9bcb6b2b3082f0
The following commit(s) were added to refs/heads/main by this push:
new 4f8b4c77b IDP-1870: Detect duplicate entityIDs when ingesting metadata
4f8b4c77b is described below
commit 4f8b4c77b747cb5c70eb904d0a9bcb6b2b3082f0
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Thu Nov 18 21:36:48 2021 -0500
IDP-1870: Detect duplicate entityIDs when ingesting metadata
---
.../entity/DetectDuplicateEntityIDsCriterion.java | 80 +++++++++++++
.../resolver/ChainingMetadataResolver.java | 130 ++++++++++++++++++++-
.../resolver/DetectDuplicateEntityIDs.java | 37 ++++++
3 files changed, 246 insertions(+), 1 deletion(-)
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/criteria/entity/DetectDuplicateEntityIDsCriterion.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/criteria/entity/DetectDuplicateEntityIDsCriterion.java
new file mode 100644
index 000000000..0d8d4fbb1
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/criteria/entity/DetectDuplicateEntityIDsCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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 org.opensaml.saml.metadata.criteria.entity;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.saml.metadata.resolver.DetectDuplicateEntityIDs;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.Criterion;
+
+/**
+ * Criterion which holds an instance of {@link DetectDuplicateEntityIDs}.
+ */
+public class DetectDuplicateEntityIDsCriterion implements Criterion {
+
+ /** The configured duplicate detection value. **/
+ @Nonnull private DetectDuplicateEntityIDs value;
+
+ /**
+ * Constructor.
+ *
+ * @param detect the value used for duplicate detection
+ */
+ public DetectDuplicateEntityIDsCriterion(@Nonnull final DetectDuplicateEntityIDs detect) {
+ value = Constraint.isNotNull(detect, "DetectDuplicateEntityIDs was null");
+ }
+
+ /**
+ * Get the configured value.
+ *
+ * @return the criterion value
+ */
+ @Nonnull public DetectDuplicateEntityIDs getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other instanceof DetectDuplicateEntityIDsCriterion) {
+ return Objects.equals(this.value, ((DetectDuplicateEntityIDsCriterion)other).value);
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("value", value).toString();
+ }
+
+}
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/resolver/ChainingMetadataResolver.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/resolver/ChainingMetadataResolver.java
index 89e970449..e6048cc8b 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/resolver/ChainingMetadataResolver.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/resolver/ChainingMetadataResolver.java
@@ -21,6 +21,10 @@ import java.time.Instant;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -34,6 +38,7 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
+import org.opensaml.saml.metadata.criteria.entity.DetectDuplicateEntityIDsCriterion;
import org.opensaml.saml.metadata.resolver.filter.MetadataFilter;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.slf4j.Logger;
@@ -53,10 +58,14 @@ public class ChainingMetadataResolver extends AbstractIdentifiableInitializableC
/** Registered resolvers. */
@Nonnull @NonnullElements private List<MetadataResolver> resolvers;
+
+ /** Strategy for detecting duplicate entityIDs across resolvers. */
+ @Nonnull private DetectDuplicateEntityIDs detectDuplicateEntityIDs;
/** Constructor. */
public ChainingMetadataResolver() {
resolvers = Collections.emptyList();
+ detectDuplicateEntityIDs = DetectDuplicateEntityIDs.Off;
}
/**
@@ -88,6 +97,24 @@ public class ChainingMetadataResolver extends AbstractIdentifiableInitializableC
resolvers = List.copyOf(newResolvers);
}
+ /**
+ * Get the strategy for detecting duplicate entityIDs across resolvers.
+ *
+ * @return the configured strategy
+ */
+ @Nonnull public DetectDuplicateEntityIDs getDetectDuplicateEntityIDs() {
+ return detectDuplicateEntityIDs;
+ }
+
+ /**
+ * Set the strategy for detecting duplicate entityIDs across resolvers.
+ *
+ * @param strategy the strategy to configure
+ */
+ public void setDetectDuplicateEntityIDs(@Nullable final DetectDuplicateEntityIDs strategy) {
+ detectDuplicateEntityIDs = strategy != null ? strategy : DetectDuplicateEntityIDs.Off;
+ }
+
/** {@inheritDoc} */
@Override public boolean isRequireValidMetadata() {
log.warn("Attempt to access unsupported requireValidMetadata property on ChainingMetadataResolver");
@@ -129,12 +156,34 @@ public class ChainingMetadataResolver extends AbstractIdentifiableInitializableC
@Override
@Nonnull public Iterable<EntityDescriptor> resolve(@Nullable final CriteriaSet criteria) throws ResolverException {
ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+
+ DetectDuplicateEntityIDs detectDuplicates = getDetectDuplicateEntityIDs();
+ if (criteria.contains(DetectDuplicateEntityIDsCriterion.class)) {
+ detectDuplicates = criteria.get(DetectDuplicateEntityIDsCriterion.class).getValue();
+ }
+ log.trace("Effecgive DetectDuplicateEntityIDs value is: {}", detectDuplicates);
+ Iterable<EntityDescriptor> result = null;
+ Set<String> resultEntityIDs = null;
for (final MetadataResolver resolver : resolvers) {
try {
+ if (result != null) {
+ detectDuplicateEntityIDs(resolver, criteria, resultEntityIDs, detectDuplicates);
+ continue;
+ }
+
final Iterable<EntityDescriptor> descriptors = resolver.resolve(criteria);
if (descriptors != null && descriptors.iterator().hasNext()) {
- return descriptors;
+ if (detectDuplicates == DetectDuplicateEntityIDs.Off) {
+ log.trace("Resolved EntityDescriptor(s) from '{}', duplicate detection disabled, returning",
+ resolver.getId());
+ return descriptors;
+ }
+
+ log.trace("Resolved EntityDescriptor(s) from '{}', duplicate detection enabled, continuing",
+ resolver.getId());
+ result = descriptors;
+ resultEntityIDs = collectEntityIDs(result);
}
} catch (final ResolverException e) {
log.warn("Error retrieving metadata from resolver of type {}, proceeding to next resolver",
@@ -143,9 +192,88 @@ public class ChainingMetadataResolver extends AbstractIdentifiableInitializableC
}
}
+ if (result != null) {
+ return result;
+ }
return Collections.emptyList();
}
+ /**
+ * Perform duplicate entityID detection.
+ *
+ * @param resolver the metadata resolver over which to perform duplicate detection
+ * @param criteria the current criteria set
+ * @param resultEntityIDs the set of entityIDs contained in the effective results to be returned
+ * @param detectDuplicates the effective strategy for duplicate detection
+ */
+ // Checkstyle: CyclomaticComplexity OFF
+ private void detectDuplicateEntityIDs(final @Nonnull MetadataResolver resolver,
+ final @Nonnull CriteriaSet criteria,
+ final @Nullable Set<String> resultEntityIDs,
+ final @Nonnull DetectDuplicateEntityIDs detectDuplicates) {
+
+ if (resultEntityIDs == null || resultEntityIDs.isEmpty()) {
+ return;
+ }
+
+ switch(detectDuplicates) {
+ case Off:
+ return;
+ case Batch:
+ if (!BatchMetadataResolver.class.isInstance(resolver)) {
+ return;
+ }
+ break;
+ case Dynamic:
+ if (!DynamicMetadataResolver.class.isInstance(resolver)) {
+ return;
+ }
+ break;
+ case All:
+ break;
+ default:
+ log.warn("Saw unknown DetectDuplicateEntityIDs value, can not process: {}", detectDuplicates);
+ return;
+ }
+
+ log.trace("Performing duplicate entityID detection on resolver '{} of type {}",
+ resolver.getId(), resolver.getClass().getName());
+
+ try {
+ final Iterable<EntityDescriptor> descriptors = resolver.resolve(criteria);
+ if (descriptors != null && descriptors.iterator().hasNext()) {
+ final Set<String> descriptorsEnitityIDs = collectEntityIDs(descriptors);
+
+ final Set<String> duplicates = resultEntityIDs.stream()
+ .filter(descriptorsEnitityIDs::contains)
+ .collect(Collectors.toSet());
+
+ if (!duplicates.isEmpty()) {
+ log.warn("MetadataResolver '{}' contained duplicate entityIDs relative to the returned results: {}",
+ resolver.getId(), duplicates);
+ }
+ }
+ } catch (final ResolverException e) {
+ log.warn("During duplicate detection, error retrieving metadata from resolver '{}' of type {}",
+ resolver.getId(), resolver.getClass().getName(), e);
+ }
+
+ }
+ // Checkstyle: CyclomaticComplexity ON
+
+ /**
+ * Collect the unique entityIDs from the supplied iterable of entity descriptors.
+ *
+ * @param descriptors
+ * @return the unique entityIDs from the supplied descriptors
+ */
+ private Set<String> collectEntityIDs(final @Nonnull Iterable<EntityDescriptor> descriptors) {
+ return StreamSupport.stream(descriptors.spliterator(), false)
+ .map(EntityDescriptor::getEntityID)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ }
+
/** {@inheritDoc} */
public void clear() throws ResolverException {
for (final MetadataResolver resolver : resolvers) {
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/resolver/DetectDuplicateEntityIDs.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/resolver/DetectDuplicateEntityIDs.java
new file mode 100644
index 000000000..b95b1bc80
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/metadata/resolver/DetectDuplicateEntityIDs.java
@@ -0,0 +1,37 @@
+/*
+ * 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 org.opensaml.saml.metadata.resolver;
+
+/**
+ * Strategy for performing detection of duplicate entityIDs across metadata resolvers.
+ */
+public enum DetectDuplicateEntityIDs {
+
+ /** No duplicate detection. */
+ Off,
+
+ /** Detect only with batch metadata resolvers. */
+ Batch,
+
+ /** Detect only with dynamic metadata resolvers. */
+ Dynamic,
+
+ /** Detect with all metadata resolvers. */
+ All;
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list