[java-metadata-aggregator] branch master updated: MDA-242 - Review for thread safety
Ian Young
ian at iay.org.uk
Tue Jul 14 10:57:02 UTC 2020
This is an automated email from the git hooks/post-receive script.
iay pushed a commit to branch master
in repository java-metadata-aggregator.
View the commit online:
http://git.shibboleth.net/view/?p=java-metadata-aggregator.git;a=commit;h=288b13145b98fb958dbbdd4499137b9097b91944
The following commit(s) were added to refs/heads/master by this push:
new 288b131 MDA-242 - Review for thread safety
288b131 is described below
commit 288b13145b98fb958dbbdd4499137b9097b91944
Author: Ian Young <ian at iay.org.uk>
AuthorDate: Tue Jul 14 11:56:56 2020 +0100
MDA-242 - Review for thread safety
https://issues.shibboleth.net/jira/browse/MDA-242
First tranche: mainly root, dom, dom.ds, dom.impl, dom.saml.
---
...bstractCompositeItemIdentificationStrategy.java | 6 +-
.../metadata/CollectionMergeStrategy.java | 14 +-
.../metadata/DeduplicatingItemIdMergeStrategy.java | 17 +-
.../FirstItemIdItemIdentificationStrategy.java | 5 +-
.../metadata/ItemCollectionSerializer.java | 6 +
.../metadata/ItemIdentificationStrategy.java | 14 +-
.../net/shibboleth/metadata/ItemSerializer.java | 6 +
.../metadata/SimpleCollectionMergeStrategy.java | 8 +-
.../metadata/SimpleItemCollectionFactory.java | 2 +
.../metadata/SimpleItemCollectionSerializer.java | 2 +
.../metadata/dom/AbstractDOMTraversalStage.java | 6 +-
.../metadata/dom/AbstractDOMValidationStage.java | 21 +-
.../metadata/dom/AbstractElementVisitingStage.java | 28 +-
.../dom/AbstractNamespacesStrippingStage.java | 5 +-
.../metadata/dom/AbstractXSLProcessingStage.java | 88 +++-
.../net/shibboleth/metadata/dom/AttrVisitor.java | 10 +-
.../shibboleth/metadata/dom/CRDetectionStage.java | 4 +-
.../net/shibboleth/metadata/dom/Container.java | 2 +-
.../metadata/dom/DOMElementSerializer.java | 3 -
.../metadata/dom/DOMFilesystemSourceStage.java | 74 +--
.../metadata/dom/DOMResourceSourceStage.java | 56 ++-
.../metadata/dom/DOMTraversalContext.java | 8 +
.../net/shibboleth/metadata/dom/ElementMaker.java | 8 +-
.../shibboleth/metadata/dom/ElementMatcher.java | 8 +-
.../metadata/dom/ElementStrippingStage.java | 22 +-
.../shibboleth/metadata/dom/ElementVisitor.java | 7 +
.../dom/ElementWhitespaceTrimmingStage.java | 2 +
.../metadata/dom/ElementsStrippingStage.java | 71 ++-
.../metadata/dom/EmptyContainerStrippingStage.java | 22 +-
.../metadata/dom/NamespaceStrippingStage.java | 8 +-
.../metadata/dom/NamespacesStrippingStage.java | 19 +-
.../net/shibboleth/metadata/dom/NodeVisitor.java | 12 +-
.../metadata/dom/SimpleDOMTraversalContext.java | 2 +-
.../metadata/dom/WhitespaceTrimmingVisitor.java | 2 +
.../metadata/dom/XMLSchemaValidationStage.java | 33 +-
.../metadata/dom/XMLSignatureSigningStage.java | 507 +++----------------
.../metadata/dom/XMLSignatureValidationStage.java | 110 +++--
.../metadata/dom/XPathFilteringStage.java | 12 +-
.../metadata/dom/XPathItemSelectionStrategy.java | 6 +-
.../metadata/dom/XSLValidationStage.java | 6 +-
.../metadata/dom/ds/X509ValidationStage.java | 15 +-
.../metadata/dom/impl/XMLSignatureSigner.java | 545 +++++++++++++++++++++
.../dom/{ => impl}/XMLSignatureValidator.java | 11 +-
.../impl/package-info.java} | 21 +-
.../dom/saml/AbstractSAMLTraversalStage.java | 9 +-
.../metadata/dom/saml/AttributeElementMaker.java | 4 +-
.../metadata/dom/saml/AttributeElementMatcher.java | 2 +
.../dom/saml/AttributeValueElementMaker.java | 4 +-
.../dom/saml/AttributeValueElementMatcher.java | 2 +
.../dom/saml/ContactPersonFilterStage.java | 27 +-
.../dom/saml/DiscoFeedCollectionSerializer.java | 51 +-
.../dom/saml/EntitiesDescriptorAssemblerStage.java | 40 +-
.../saml/EntitiesDescriptorDisassemblerStage.java | 4 +-
.../metadata/dom/saml/EntityFilterStage.java | 37 +-
.../metadata/dom/saml/EntityRoleFilterStage.java | 69 ++-
.../metadata/dom/saml/GenerateIdStage.java | 6 +-
.../dom/saml/PullUpCacheDurationStage.java | 18 +-
.../metadata/dom/saml/PullUpValidUntilStage.java | 14 +-
.../metadata/dom/saml/SAMLMetadataSupport.java | 2 +-
.../dom/saml/SAMLStringElementCheckingStage.java | 2 +
.../shibboleth/metadata/dom/saml/SAMLSupport.java | 3 +-
.../metadata/dom/saml/SetCacheDurationStage.java | 8 +-
.../metadata/dom/saml/SetValidUntilStage.java | 8 +-
.../metadata/dom/saml/ValidateValidUntilStage.java | 16 +-
.../dom/saml/mdui/IPHintValidationStage.java | 2 +-
.../metadata/pipeline/SimplePipeline.java | 5 +-
.../metadata/validate/AcceptAllValidator.java | 2 +
.../metadata/validate/BaseValidator.java | 3 +-
.../metadata/validate/RejectAllValidator.java | 2 +
.../shibboleth/metadata/validate/Validator.java | 10 +-
.../metadata/validate/ValidatorSequence.java | 14 +-
.../metadata/validate/x509/X509DSADetector.java | 3 +-
.../dom/AbstractDOMValidationStageTest.java | 2 +-
.../metadata/dom/XMLSignatureSigningStageTest.java | 16 +-
.../saml/SAMLStringElementCheckingStageTest.java | 6 +-
75 files changed, 1321 insertions(+), 904 deletions(-)
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/AbstractCompositeItemIdentificationStrategy.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/AbstractCompositeItemIdentificationStrategy.java
index 98639b2..6d9de2a 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/AbstractCompositeItemIdentificationStrategy.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/AbstractCompositeItemIdentificationStrategy.java
@@ -19,6 +19,7 @@ package net.shibboleth.metadata;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
@@ -42,6 +43,7 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
public abstract class AbstractCompositeItemIdentificationStrategy implements ItemIdentificationStrategy {
/** Identifier to use if an {@link Item} does not have an {@link ItemId}. Default value: "unidentified" */
+ @Nonnull @NotEmpty @GuardedBy("this")
private String noItemIdIdentifier = "unidentified";
/**
@@ -49,7 +51,7 @@ public abstract class AbstractCompositeItemIdentificationStrategy implements Ite
*
* @return identifier to use if an {@link Item} does not have an {@link ItemId}
*/
- public String getNoItemIdIdentifier() {
+ @Nonnull @NotEmpty public final synchronized String getNoItemIdIdentifier() {
return noItemIdIdentifier;
}
@@ -58,7 +60,7 @@ public abstract class AbstractCompositeItemIdentificationStrategy implements Ite
*
* @param identifier identifier to use if an {@link Item} does not have an {@link ItemId}
*/
- public void setNoItemIdIdentifier(@Nonnull @NotEmpty final String identifier) {
+ public synchronized void setNoItemIdIdentifier(@Nonnull @NotEmpty final String identifier) {
noItemIdIdentifier =
Constraint.isNotNull(StringSupport.trimOrNull(identifier), "Identifier can not be null or empty");
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/CollectionMergeStrategy.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/CollectionMergeStrategy.java
index d7a1163..e3988ff 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/CollectionMergeStrategy.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/CollectionMergeStrategy.java
@@ -21,10 +21,18 @@ import java.util.Collection;
import java.util.List;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-/** Strategy used to merge the results of each child pipeline in to the collection of Items given to this stage. */
+/**
+ * Strategy used to merge the results of each child pipeline in to the collection of Items given to this stage.
+ *
+ * <p>
+ * All implementations of this interface <strong>must</strong> be thread-safe.
+ * </p>
+ */
+ at ThreadSafe
public interface CollectionMergeStrategy {
/**
@@ -34,6 +42,6 @@ public interface CollectionMergeStrategy {
* @param sources collections of Items to be merged in to the target, never null not containing any null elements
* @param <T> type of data contained in the items
*/
- <T> void mergeCollection(@Nonnull @NonnullElements final Collection<Item<T>> target,
- @Nonnull @NonnullElements final List<Collection<Item<T>>> sources);
+ <T> void mergeCollection(@Nonnull @NonnullElements Collection<Item<T>> target,
+ @Nonnull @NonnullElements List<Collection<Item<T>>> sources);
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/DeduplicatingItemIdMergeStrategy.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/DeduplicatingItemIdMergeStrategy.java
index bdf817d..69cdfed 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/DeduplicatingItemIdMergeStrategy.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/DeduplicatingItemIdMergeStrategy.java
@@ -22,6 +22,7 @@ import java.util.HashSet;
import java.util.List;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -31,19 +32,19 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
* the same {@link ItemId} as source item. If the source item does not contain a {@link ItemId} it is always added to
* the target collection.
*/
+ at ThreadSafe
public class DeduplicatingItemIdMergeStrategy implements CollectionMergeStrategy {
- /** {@inheritDoc} */
- @Override public <T> void mergeCollection(@Nonnull @NonnullElements final Collection<Item<T>> target,
+ @Override
+ public <T> void mergeCollection(@Nonnull @NonnullElements final Collection<Item<T>> target,
@Nonnull @NonnullElements final List<Collection<Item<T>>> sources) {
Constraint.isNotNull(target, "Target collection can not be null");
Constraint.isNotNull(sources, "Source collections can not be null or empty");
- List<ItemId> itemIds;
final HashSet<ItemId> presentItemIds = new HashSet<>();
for (final Item<T> item : target) {
- itemIds = item.getItemMetadata().get(ItemId.class);
+ final var itemIds = item.getItemMetadata().get(ItemId.class);
if (itemIds != null) {
presentItemIds.addAll(itemIds);
}
@@ -67,16 +68,14 @@ public class DeduplicatingItemIdMergeStrategy implements CollectionMergeStrategy
private <T> void merge(@Nonnull @NonnullElements final HashSet<ItemId> presentItemIds,
@Nonnull @NonnullElements final Collection<Item<T>> target,
@Nonnull @NonnullElements final Collection<Item<T>> sourceItems) {
- boolean itemAlreadyPresent;
- List<ItemId> itemIds;
for (final Item<T> sourceItem : sourceItems) {
- itemIds = sourceItem.getItemMetadata().get(ItemId.class);
+ final var itemIds = sourceItem.getItemMetadata().get(ItemId.class);
if (itemIds == null || itemIds.isEmpty()) {
target.add(sourceItem);
continue;
}
- itemAlreadyPresent = false;
+ var itemAlreadyPresent = false;
for (final ItemId itemId : itemIds) {
if (presentItemIds.contains(itemId)) {
itemAlreadyPresent = true;
@@ -90,4 +89,4 @@ public class DeduplicatingItemIdMergeStrategy implements CollectionMergeStrategy
}
}
}
-}
\ No newline at end of file
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/FirstItemIdItemIdentificationStrategy.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/FirstItemIdItemIdentificationStrategy.java
index 17ab773..b8041bd 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/FirstItemIdItemIdentificationStrategy.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/FirstItemIdItemIdentificationStrategy.java
@@ -21,11 +21,13 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
/**
* Strategy that returns the first {@link ItemId} associated with an {@link Item} or, if no {@link ItemId} is
* associated with the item, a generic identifier is returned.
*/
+ at Immutable
public class FirstItemIdItemIdentificationStrategy extends AbstractCompositeItemIdentificationStrategy {
@Override
@@ -33,9 +35,8 @@ public class FirstItemIdItemIdentificationStrategy extends AbstractCompositeItem
final List<ItemId> itemIds = item.getItemMetadata().get(ItemId.class);
if (!itemIds.isEmpty()) {
return itemIds.get(0).getId();
- } else {
- return null;
}
+ return null;
}
@Override
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemCollectionSerializer.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemCollectionSerializer.java
index 648ba39..5d05467 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemCollectionSerializer.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemCollectionSerializer.java
@@ -28,8 +28,14 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElemen
/**
* Item collection serializers convert a {@link Collection} of {@link Item}s into an octet stream.
*
+ * <p>
* The caller is responsible for managing (opening, closing, etc.) the output stream.
+ * </p>
*
+ * <p>
+ * All implementations of this interface <strong>must</strong> be thread-safe.
+ * </p>
+ *
* @param <T> type of data contained in each item
*
* @since 0.9.0
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemIdentificationStrategy.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemIdentificationStrategy.java
index 1a3de26..aab7999 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemIdentificationStrategy.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemIdentificationStrategy.java
@@ -18,8 +18,16 @@
package net.shibboleth.metadata;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
-/** Strategy for determining a displayable identifier for a given {@link Item}. */
+/**
+ * Strategy for determining a displayable identifier for a given {@link Item}.
+ *
+ * <p>
+ * All implementations of this interface <strong>must</strong> be thread-safe.
+ * </p>
+ */
+ at ThreadSafe
public interface ItemIdentificationStrategy {
/**
@@ -27,8 +35,8 @@ public interface ItemIdentificationStrategy {
*
* @param item the item
*
- * @return the identifier, never null
+ * @return the identifier, never <code>null</code>
*/
- String getItemIdentifier(@Nonnull final Item<?> item);
+ @Nonnull String getItemIdentifier(@Nonnull Item<?> item);
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemSerializer.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemSerializer.java
index aff33e5..9ab6a6a 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemSerializer.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemSerializer.java
@@ -25,9 +25,15 @@ import javax.annotation.Nonnull;
/**
* Item serializers convert an {@link Item} into an octet stream.
*
+ * <p>
* The caller is responsible for managing (opening, closing, etc.) the output stream
* and orchestrating the serialization of collections of {@link Item}s.
+ * </p>
*
+ * <p>
+ * All implementations of this interface <strong>must</strong> be thread-safe.
+ * </p>
+ *
* @param <T> type of data contained in the item
*/
public interface ItemSerializer<T> {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleCollectionMergeStrategy.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleCollectionMergeStrategy.java
index a7a815f..059174b 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleCollectionMergeStrategy.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleCollectionMergeStrategy.java
@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.List;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -29,10 +30,11 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
* A {@link CollectionMergeStrategy} that adds the Item from each source, in order, by means of the
* {@link Collection#addAll(Collection)} method on the target.
*/
+ at Immutable
public class SimpleCollectionMergeStrategy implements CollectionMergeStrategy {
- /** {@inheritDoc} */
- @Override public <T> void mergeCollection(@Nonnull @NonnullElements final Collection<Item<T>> target,
+ @Override
+ public <T> void mergeCollection(@Nonnull @NonnullElements final Collection<Item<T>> target,
@Nonnull @NonnullElements final List<Collection<Item<T>>> sources) {
Constraint.isNotNull(target, "Target collection can not be null");
Constraint.isNotNull(sources, "Source collections can not be null or empty");
@@ -41,4 +43,4 @@ public class SimpleCollectionMergeStrategy implements CollectionMergeStrategy {
target.addAll(source);
}
}
-}
\ No newline at end of file
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleItemCollectionFactory.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleItemCollectionFactory.java
index ae6e161..86fd350 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleItemCollectionFactory.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleItemCollectionFactory.java
@@ -22,12 +22,14 @@ import java.util.Collection;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
/**
* Implementation {@link Supplier} that produces {@link ArrayList} instances.
*
* @param <T> the type of items the produced collection will contain
*/
+ at Immutable
public class SimpleItemCollectionFactory<T> implements Supplier<Collection<Item<T>>> {
@Override
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleItemCollectionSerializer.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleItemCollectionSerializer.java
index 75b82b6..920b419 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleItemCollectionSerializer.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/SimpleItemCollectionSerializer.java
@@ -22,6 +22,7 @@ import java.io.OutputStream;
import java.util.Collection;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
@@ -31,6 +32,7 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElemen
*
* @param <T> type of data contained in each item
*/
+ at Immutable
public class SimpleItemCollectionSerializer<T> implements ItemCollectionSerializer<T> {
/** The {@link ItemSerializer} to use on each {@link Item} in turn. */
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMTraversalStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMTraversalStage.java
index 4aff930..a4851dd 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMTraversalStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMTraversalStage.java
@@ -65,10 +65,12 @@ public abstract class AbstractDOMTraversalStage<C extends DOMTraversalContext>
* Indicates whether the visitor should be applied to a particular {@link Element}.
*
* @param element {@link Element} to which we may wish to apply the visitor
+ * @param context {@link DOMTraversalContext} implementation being used to manage the traversal
*
* @return <code>true</code> if the visitor should be applied to this {@link Element}.
*/
- protected abstract boolean applicable(@Nonnull final Element element);
+ protected abstract boolean applicable(@Nonnull final Element element,
+ @Nonnull final C context);
/**
* Visit a particular {@link Element}.
@@ -96,7 +98,7 @@ public abstract class AbstractDOMTraversalStage<C extends DOMTraversalContext>
for (final Element child : children) {
traverse(child, context);
}
- if (applicable(element)) {
+ if (applicable(element, context)) {
visit(element, context);
}
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMValidationStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMValidationStage.java
index f616f2e..8fafed1 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMValidationStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMValidationStage.java
@@ -20,6 +20,7 @@ package net.shibboleth.metadata.dom;
import java.util.List;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.metadata.pipeline.StageProcessingException;
import net.shibboleth.metadata.validate.Validator;
@@ -36,12 +37,21 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
*
* @since 0.9.0
*/
+ at ThreadSafe
public abstract class AbstractDOMValidationStage<V, C extends DOMTraversalContext>
extends AbstractDOMTraversalStage<C> {
- /** The validator sequence to apply. */
+ /**
+ * The validator sequence to apply.
+ *
+ * <p>
+ * Thread safety: as a <code>final</code> field, access to <code>validators</code>
+ * does not need to be synchronised for thread safety. The referenced object
+ * is itself thread-safe.
+ * </p>
+ */
@Nonnull @NonnullElements @Unmodifiable
- private ValidatorSequence<V> validators = new ValidatorSequence<>();
+ private final ValidatorSequence<V> validators = new ValidatorSequence<>();
/**
* Set the list of validators to apply to each item.
@@ -58,7 +68,7 @@ public abstract class AbstractDOMValidationStage<V, C extends DOMTraversalContex
* @return list of validators
*/
@Nonnull @NonnullElements @Unmodifiable
- public List<Validator<V>> getValidators() {
+ public final List<Validator<V>> getValidators() {
return validators.getValidators();
}
@@ -76,8 +86,11 @@ public abstract class AbstractDOMValidationStage<V, C extends DOMTraversalContex
@Override
protected void doDestroy() {
+ /*
+ * We can not discard the reference to the validators object,
+ * but we can destroy it.
+ */
validators.destroy();
- validators = null;
super.doDestroy();
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractElementVisitingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractElementVisitingStage.java
index 4d29547..9d2c83c 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractElementVisitingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractElementVisitingStage.java
@@ -21,6 +21,8 @@ import java.util.Collection;
import java.util.Set;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
import javax.xml.namespace.QName;
import org.w3c.dom.Element;
@@ -36,26 +38,20 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
*
* @since 0.9.0
*/
+ at ThreadSafe
public abstract class AbstractElementVisitingStage extends AbstractDOMTraversalStage<DOMTraversalContext> {
/** Collection of element names for those elements we will be visiting. */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private Set<QName> elementNames = Set.of();
- @Override
- protected void doDestroy() {
- elementNames = null;
-
- super.doDestroy();
- }
-
/**
* Gets the collection of element names to visit.
*
* @return collection of element names to visit.
*/
@Nonnull @NonnullElements @Unmodifiable
- public Collection<QName> getElementNames() {
+ public final synchronized Collection<QName> getElementNames() {
return elementNames;
}
@@ -64,7 +60,7 @@ public abstract class AbstractElementVisitingStage extends AbstractDOMTraversalS
*
* @param names collection of element names to visit.
*/
- public void setElementNames(@Nonnull @NonnullElements @Unmodifiable final Collection<QName> names) {
+ public synchronized void setElementNames(@Nonnull @NonnullElements @Unmodifiable final Collection<QName> names) {
throwSetterPreconditionExceptions();
Constraint.isNotNull(names, "elementNames may not be null");
elementNames = Set.copyOf(names);
@@ -77,16 +73,16 @@ public abstract class AbstractElementVisitingStage extends AbstractDOMTraversalS
*
* @param name {@link QName} for the element to be visited.
*/
- public void setElementName(@Nonnull final QName name) {
+ public synchronized void setElementName(@Nonnull final QName name) {
throwSetterPreconditionExceptions();
Constraint.isNotNull(name, "elementName may not be null");
elementNames = Set.of(name);
}
@Override
- protected boolean applicable(@Nonnull final Element e) {
+ protected boolean applicable(@Nonnull final Element e, @Nonnull final DOMTraversalContext context) {
final QName q = new QName(e.getNamespaceURI(), e.getLocalName());
- return elementNames.contains(q);
+ return getElementNames().contains(q);
}
@Override
@@ -94,4 +90,10 @@ public abstract class AbstractElementVisitingStage extends AbstractDOMTraversalS
return new SimpleDOMTraversalContext(item);
}
+ @Override
+ protected void doDestroy() {
+ elementNames = null;
+
+ super.doDestroy();
+ }
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractNamespacesStrippingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractNamespacesStrippingStage.java
index 3dd784a..74787dd 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractNamespacesStrippingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractNamespacesStrippingStage.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.XMLConstants;
@@ -61,11 +62,11 @@ public abstract class AbstractNamespacesStrippingStage extends AbstractIterating
* @param namespace potentially stripped namespace
* @return <code>true</code> if this namespace should be stripped
*/
- protected abstract boolean removingNamespace(final String namespace);
+ protected abstract boolean removingNamespace(@Nullable final String namespace);
@Override
protected void doExecute(@Nonnull final Item<Element> item) {
- final Element element = Constraint.isNotNull(item, "Item can not be null").unwrap();
+ final Element element = item.unwrap();
/*
* We can't, by definition, remove the document element from a {@link DOMElementItem},
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractXSLProcessingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractXSLProcessingStage.java
index 5f510f6..e8c8f97 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractXSLProcessingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractXSLProcessingStage.java
@@ -24,6 +24,8 @@ import java.util.Map.Entry;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Templates;
@@ -45,6 +47,7 @@ import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.WarningStatus;
import net.shibboleth.metadata.pipeline.AbstractStage;
import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -67,17 +70,28 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
private final Logger log = LoggerFactory.getLogger(AbstractXSLProcessingStage.class);
/** Resource that provides the XSL document. */
+ @NonnullAfterInit @GuardedBy("this")
private Resource xslResource;
- /** XSL template used to transform Elements. */
+ /**
+ * XSL template used to transform <code>Element</code>s.
+ *
+ * <p>
+ * A single shared <code>Templates</code> object is constructed from the
+ * <code>xslResource</code>, <code>transformAttributes</code>,
+ * <code>transformFeatures</code> and any <code>uriResolver</code>
+ * during initialisation of the stage.
+ * </p>
+ */
+ @NonnullAfterInit @GuardedBy("this")
private Templates xslTemplate;
/** Attributes set on the {@link Transformer} used by this stage. */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private Map<String, Object> transformAttributes = Map.of();
/** Features set on the {@link Transformer} used by this stage. */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private Map<String, Boolean> transformFeatures = Map.of();
/**
@@ -85,18 +99,19 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
*
* If not set, an empty collection.
*/
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private Map<String, Object> transformParameters = Map.of();
/** {@link URIResolver} to use in the transformer. Default value: <code>null</code>. */
- @Nullable private URIResolver uriResolver;
+ @Nullable @GuardedBy("this")
+ private URIResolver uriResolver;
/**
* Gets the resource that provides the XSL document.
*
* @return resource that provides the XSL document
*/
- @Nullable public Resource getXSLResource() {
+ @Nullable public final synchronized Resource getXSLResource() {
return xslResource;
}
@@ -110,13 +125,22 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
xslResource = Constraint.isNotNull(resource, "XSL resource can not be null");
}
+ /**
+ * Get the shared templates object.
+ *
+ * @return the shared templates object
+ */
+ @NonnullAfterInit private synchronized Templates getXSLTemplate() {
+ return xslTemplate;
+ }
+
/**
* Gets the unmodifiable collection of attributes used by the XSLT transformer.
*
* @return unmodifiable collection of attributes used by the XSLT transformer, never null nor containing null keys
*/
@Nonnull @NonnullElements @Unmodifiable
- public Map<String, Object> getTransformAttributes() {
+ public final synchronized Map<String, Object> getTransformAttributes() {
return transformAttributes;
}
@@ -137,7 +161,7 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
* @return unmodifiable collection of features used by the XSLT transformer, never null nor containing null keys
*/
@Nonnull @NonnullElements @Unmodifiable
- public Map<String, Boolean> getTransformFeatures() {
+ public final synchronized Map<String, Boolean> getTransformFeatures() {
return transformFeatures;
}
@@ -158,7 +182,7 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
* @return parameters used by the XSLT transformer, never null nor containing null keys
*/
@Nonnull @NonnullElements @Unmodifiable
- public Map<String, Object> getTransformParameters() {
+ public final synchronized Map<String, Object> getTransformParameters() {
return transformParameters;
}
@@ -178,7 +202,7 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
*
* @return the {@link URIResolver}, or <code>null</code>
*/
- @Nullable public URIResolver getURIResolver() {
+ @Nullable public final synchronized URIResolver getURIResolver() {
return uriResolver;
}
@@ -192,12 +216,24 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
uriResolver = resolver;
}
- /** {@inheritDoc} */
- @Override protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection)
+ @Override
+ protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection)
throws StageProcessingException {
try {
- final Transformer transformer = xslTemplate.newTransformer();
- for (final Map.Entry<String, Object> entry : transformParameters.entrySet()) {
+ /*
+ * Construct a per-execution Transformer from the shared template.
+ * Although a single Transformer (with parameters) could in principle be
+ * used sequentially by multiple executions, Transformers are not thread-safe
+ * and sharing one across threads would require locking against that instance
+ * for the whole duration of doExecute, or pooling.
+ *
+ * What we're doing here allows executions to overlap, at the cost of building
+ * a Transformer for each execution.
+ */
+ final Transformer transformer = getXSLTemplate().newTransformer();
+
+ // Set each of the transform's parameters
+ for (final Map.Entry<String, Object> entry : getTransformParameters().entrySet()) {
transformer.setParameter(entry.getKey(), entry.getValue());
}
@@ -232,8 +268,8 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
super.doDestroy();
}
- /** {@inheritDoc} */
- @Override protected void doInitialize() throws ComponentInitializationException {
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
if (xslResource == null) {
@@ -241,6 +277,11 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
+ ", XslResource must not be null");
}
+ /*
+ * Construct the shared (thread-safe) Templates instance. Note that because
+ * doInitialize() is called with the stage's monitor held, we can refer to
+ * fields directly and do not need to use getters.
+ */
try {
final TransformerFactory tfactory = TransformerFactory.newInstance();
@@ -290,6 +331,7 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
*
* <p>This listener works well in conjunction with <xsl:message>.</p>
*/
+ @Immutable
public class StatusInfoAppendingErrorListener implements ErrorListener {
/** Prefix used by messages that result in an {@link ErrorStatus}. */
@@ -302,7 +344,7 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
public static final String INFO_PREFIX = "[INFO]";
/** Item to which the status info will be appended. */
- private Item<?> item;
+ private final Item<?> item;
/**
* Constructor.
@@ -313,18 +355,18 @@ public abstract class AbstractXSLProcessingStage extends AbstractStage<Element>
item = receivingItem;
}
- /** {@inheritDoc} */
- @Override public void error(@Nonnull final TransformerException e) throws TransformerException {
+ @Override
+ public void error(@Nonnull final TransformerException e) throws TransformerException {
parseAndAppendStatusInfo(e);
}
- /** {@inheritDoc} */
- @Override public void fatalError(@Nonnull final TransformerException e) throws TransformerException {
+ @Override
+ public void fatalError(@Nonnull final TransformerException e) throws TransformerException {
parseAndAppendStatusInfo(e);
}
- /** {@inheritDoc} */
- @Override public void warning(@Nonnull final TransformerException e) throws TransformerException {
+ @Override
+ public void warning(@Nonnull final TransformerException e) throws TransformerException {
parseAndAppendStatusInfo(e);
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AttrVisitor.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AttrVisitor.java
index 833e24e..43ede88 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AttrVisitor.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AttrVisitor.java
@@ -19,6 +19,9 @@ package net.shibboleth.metadata.dom;
import net.shibboleth.metadata.Item;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
@@ -26,8 +29,13 @@ import org.w3c.dom.Element;
* Provides a variation of the Visitor pattern for performing operations on
* DOM attributes which are part of {@link Element} items.
*
+ * <p>
+ * All implementations of this interface <strong>must</strong> be thread-safe.
+ * </p>
+ *
* @since 0.9.0
*/
+ at ThreadSafe
public interface AttrVisitor {
/**
@@ -37,6 +45,6 @@ public interface AttrVisitor {
* @param visited the {@link Attr} being visited.
* @param item the {@link Item} which is the context for the visit.
*/
- void visitAttr(Attr visited, Item<Element> item);
+ void visitAttr(@Nonnull Attr visited, @Nonnull Item<Element> item);
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/CRDetectionStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/CRDetectionStage.java
index e960f32..6e27fb8 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/CRDetectionStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/CRDetectionStage.java
@@ -18,6 +18,7 @@
package net.shibboleth.metadata.dom;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
@@ -39,6 +40,7 @@ import net.shibboleth.metadata.pipeline.StageProcessingException;
*
* @see <a href="https://issues.shibboleth.net/jira/browse/SSPCPP-684">SSPCPP-684</a>
*/
+ at ThreadSafe
public class CRDetectionStage extends AbstractSAMLTraversalStage<CRDetectionStage.Context> {
/** Context class for this kind of traversal. */
@@ -78,7 +80,7 @@ public class CRDetectionStage extends AbstractSAMLTraversalStage<CRDetectionStag
private static final char CR = '\r';
@Override
- protected boolean applicable(final Element element) {
+ protected boolean applicable(@Nonnull final Element element, @Nonnull final Context context) {
// all Elements are applicable
return true;
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/Container.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/Container.java
index a81704a..6cfd1c5 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/Container.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/Container.java
@@ -55,7 +55,7 @@ public class Container {
* @param child child {@link Element} to add
* @return a new child {@link Container} representing the child {@link Element}
*/
- @Nonnull Container addChild(@Nonnull final Container parent, @Nonnull final Element child);
+ @Nonnull Container addChild(@Nonnull Container parent, @Nonnull Element child);
}
/**
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMElementSerializer.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMElementSerializer.java
index cd88797..457ba70 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMElementSerializer.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMElementSerializer.java
@@ -55,9 +55,6 @@ public class DOMElementSerializer implements ItemSerializer<Element>, ItemCollec
@Override
public void serialize(@Nonnull final Item<Element> item, @Nonnull final OutputStream output)
throws IOException {
- if (item == null) {
- return;
- }
final Element documentRoot = item.unwrap();
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMFilesystemSourceStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMFilesystemSourceStage.java
index f4bb1d8..184ff49 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMFilesystemSourceStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMFilesystemSourceStage.java
@@ -27,6 +27,7 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
@@ -38,6 +39,7 @@ import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.pipeline.AbstractStage;
import net.shibboleth.metadata.pipeline.StageProcessingException;
import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -59,29 +61,35 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
private final Logger log = LoggerFactory.getLogger(DOMFilesystemSourceStage.class);
/** Pool of DOM parsers used to parse the XML file in to a DOM. */
+ @NonnullAfterInit @GuardedBy("this")
private ParserPool parserPool;
/** The file path to the DOM material provided by this source. May be a file or a directory. */
+ @NonnullAfterInit @GuardedBy("this")
private File sourceFile;
/**
* Filter used to determine if a file should be included. This is only used if the {@link #sourceFile} is a
* directory.
*/
+ @Nullable @GuardedBy("this")
private FileFilter sourceFileFilter;
/**
* Whether or not directories are recursed if the given input file is a directory. Default value: <code>false</code>
*/
+ @GuardedBy("this")
private boolean recurseDirectories;
/** Whether the lack of source files is treated as an error. Default value: <code>false</code> */
+ @GuardedBy("this")
private boolean noSourceFilesAnError;
/**
* Whether an error parsing one source file causes this entire {@link net.shibboleth.metadata.pipeline.Stage} to
* fail, or just excludes the material from the offending source file. Default value: <code>true</code>
*/
+ @GuardedBy("this")
private boolean errorCausesSourceFailure = true;
/**
@@ -89,7 +97,7 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
*
* @return pool of DOM parsers used to parse the XML file in to a DOM
*/
- @Nullable public ParserPool getParserPool() {
+ @Nullable public final synchronized ParserPool getParserPool() {
return parserPool;
}
@@ -108,7 +116,7 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
*
* @return path to the DOM material provided by this source
*/
- @Nullable public File getSource() {
+ @Nullable public final synchronized File getSource() {
return sourceFile;
}
@@ -128,7 +136,7 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
*
* @return filter used to determine if a file, in a directory, should be treated as a source file, may be null
*/
- @Nullable public FileFilter getSourceFileFilter() {
+ @Nullable public final synchronized FileFilter getSourceFileFilter() {
return sourceFileFilter;
}
@@ -147,7 +155,7 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
*
* @return whether directories will be recursively searched for XML input files
*/
- public boolean getRecurseDirectories() {
+ public final synchronized boolean getRecurseDirectories() {
return recurseDirectories;
}
@@ -166,7 +174,7 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
*
* @return whether the lack of source files is considered an error
*/
- public boolean isNoSourceFilesAnError() {
+ public final synchronized boolean isNoSourceFilesAnError() {
return noSourceFilesAnError;
}
@@ -185,7 +193,7 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
*
* @return whether an error parsing a single file causes the source to fail
*/
- public boolean getErrorCausesSourceFailure() {
+ public final synchronized boolean getErrorCausesSourceFailure() {
return errorCausesSourceFailure;
}
@@ -199,28 +207,28 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
errorCausesSourceFailure = causesFailure;
}
- /** {@inheritDoc} */
- @Override protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection)
+ @Override
+ protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection)
throws StageProcessingException {
final ArrayList<File> sourceFiles = new ArrayList<>();
- if (sourceFile.isFile()) {
- sourceFiles.add(sourceFile);
+
+ final var sFile = getSource();
+ if (sFile.isFile()) {
+ sourceFiles.add(sFile);
} else {
- getSourceFiles(sourceFile, sourceFiles);
+ getSourceFiles(sFile, sourceFiles);
}
if (sourceFiles.isEmpty()) {
- if (!noSourceFilesAnError) {
- log.warn("stage {}: no input XML files in source path {}", getId(), sourceFile.getPath());
+ if (!isNoSourceFilesAnError()) {
+ log.warn("stage {}: no input XML files in source path {}", getId(), sFile.getPath());
return;
- } else {
- throw new StageProcessingException("stage " + getId() + ": no source file was available for parsing");
}
+ throw new StageProcessingException("stage " + getId() + ": no source file was available for parsing");
}
- DOMElementItem dme;
for (final File source : sourceFiles) {
- dme = processSourceFile(source);
+ final var dme = processSourceFile(source);
if (dme != null) {
itemCollection.add(dme);
}
@@ -235,10 +243,12 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
* @param input the source input file, never null
* @param collector the collector of XML input files
*/
- protected void
- getSourceFiles(@Nonnull final File input, @Nonnull @NonnullElements @Live final List<File> collector) {
+ protected void getSourceFiles(@Nonnull final File input,
+ @Nonnull @NonnullElements @Live final List<File> collector) {
+
if (input.isFile()) {
- if (sourceFileFilter == null || sourceFileFilter.accept(input)) {
+ final var filter = getSourceFileFilter();
+ if (filter == null || filter.accept(input)) {
collector.add(input);
}
return;
@@ -247,8 +257,9 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
// file must be a directory
final File[] files = input.listFiles();
if (files != null) {
+ final var recursing = getRecurseDirectories();
for (final File file : files) {
- if (file.isFile() || (file.isDirectory() && recurseDirectories)) {
+ if (file.isFile() || (file.isDirectory() && recursing)) {
getSourceFiles(file, collector);
}
}
@@ -272,17 +283,16 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
try {
log.debug("{} pipeline source parsing XML file {}", getId(), source.getPath());
xmlIn = new FileInputStream(source);
- final Document doc = parserPool.parse(xmlIn);
+ final Document doc = getParserPool().parse(xmlIn);
return new DOMElementItem(doc);
} catch (final Exception e) {
- if (errorCausesSourceFailure) {
+ if (getErrorCausesSourceFailure()) {
throw new StageProcessingException(getId() + " pipeline source unable to parse XML input file "
+ source.getPath(), e);
- } else {
- log.warn("{} pipeline source: unable to parse XML source file {}, ignoring it bad file", new Object[] {
- getId(), source.getPath(), e,});
- return null;
}
+ log.warn("{} pipeline source: unable to parse XML source file {}, ignoring it bad file", new Object[] {
+ getId(), source.getPath(), e,});
+ return null;
} finally {
try {
xmlIn.close();
@@ -292,8 +302,8 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
}
}
- /** {@inheritDoc} */
- @Override protected void doDestroy() {
+ @Override
+ protected void doDestroy() {
parserPool = null;
sourceFile = null;
sourceFileFilter = null;
@@ -301,8 +311,8 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
super.doDestroy();
}
- /** {@inheritDoc} */
- @Override protected void doInitialize() throws ComponentInitializationException {
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
if (parserPool == null) {
@@ -319,4 +329,4 @@ public class DOMFilesystemSourceStage extends AbstractStage<Element> {
+ sourceFile.getPath() + " can not be read");
}
}
-}
\ No newline at end of file
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMResourceSourceStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMResourceSourceStage.java
index 8967d7d..45708cc 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMResourceSourceStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMResourceSourceStage.java
@@ -23,6 +23,7 @@ import java.util.Collection;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
@@ -33,6 +34,7 @@ import org.w3c.dom.Element;
import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.pipeline.AbstractStage;
import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -56,15 +58,18 @@ public class DOMResourceSourceStage extends AbstractStage<Element> {
private final Logger log = LoggerFactory.getLogger(DOMResourceSourceStage.class);
/** Resource used to fetch remote XML document. */
+ @NonnullAfterInit @GuardedBy("this")
private Resource domResource;
/** Pool of parsers used to parse incoming DOM. */
+ @NonnullAfterInit @GuardedBy("this")
private ParserPool parserPool;
/**
* Whether an error parsing one source file causes this entire {@link net.shibboleth.metadata.pipeline.Stage} to
* fail, or just excludes the material from the offending source file. Default value: <code>true</code>
*/
+ @GuardedBy("this")
private boolean errorCausesSourceFailure = true;
/**
@@ -72,7 +77,7 @@ public class DOMResourceSourceStage extends AbstractStage<Element> {
*
* @return resource from which the XML document will be fetched
*/
- @Nullable public Resource getDOMResource() {
+ @Nullable public final synchronized Resource getDOMResource() {
return domResource;
}
@@ -91,7 +96,7 @@ public class DOMResourceSourceStage extends AbstractStage<Element> {
*
* @return pool of DOM parsers used to parse the XML file in to a DOM
*/
- @Nullable public ParserPool getParserPool() {
+ @Nullable public final synchronized ParserPool getParserPool() {
return parserPool;
}
@@ -110,7 +115,7 @@ public class DOMResourceSourceStage extends AbstractStage<Element> {
*
* @return whether an error reading and parsing the XML file causes this stage to fail
*/
- public boolean getErrorCausesSourceFailure() {
+ public final synchronized boolean getErrorCausesSourceFailure() {
return errorCausesSourceFailure;
}
@@ -124,22 +129,23 @@ public class DOMResourceSourceStage extends AbstractStage<Element> {
errorCausesSourceFailure = causesFailure;
}
- /** {@inheritDoc} */
- @Override protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection)
+ @Override
+ protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection)
throws StageProcessingException {
- log.debug("Attempting to fetch XML document from '{}'", domResource.getDescription());
+ final var resource = getDOMResource();
- try (InputStream ins = domResource.getInputStream()) {
- populateItemCollection(itemCollection, ins);
+ log.debug("Attempting to fetch XML document from '{}'", resource.getDescription());
+
+ try (InputStream ins = resource.getInputStream()) {
+ populateItemCollection(itemCollection, ins, resource);
} catch (final IOException e) {
- if (errorCausesSourceFailure) {
+ if (getErrorCausesSourceFailure()) {
throw new StageProcessingException("Error retrieving XML document from " +
- domResource.getDescription(), e);
- } else {
- log.warn("stage {}: unable to read in XML file", getId());
- log.debug("stage {}: HTTP resource exception", getId(), e);
+ resource.getDescription(), e);
}
+ log.warn("stage {}: unable to read in XML file", getId());
+ log.debug("stage {}: HTTP resource exception", getId(), e);
}
}
@@ -149,35 +155,35 @@ public class DOMResourceSourceStage extends AbstractStage<Element> {
*
* @param itemCollection collection to which the read in and parsed document element is added
* @param data XML input file
+ * @param resource the resource to read from
*
* @throws StageProcessingException thrown if there is a problem reading and parsing the response
*/
protected void populateItemCollection(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection,
- final InputStream data) throws StageProcessingException {
+ @Nonnull final InputStream data, @Nonnull final Resource resource) throws StageProcessingException {
try {
- log.debug("Parsing XML document retrieved from '{}'", domResource.getDescription());
- itemCollection.add(new DOMElementItem(parserPool.parse(data)));
+ log.debug("Parsing XML document retrieved from '{}'", resource.getDescription());
+ itemCollection.add(new DOMElementItem(getParserPool().parse(data)));
} catch (final XMLParserException e) {
- if (errorCausesSourceFailure) {
+ if (getErrorCausesSourceFailure()) {
throw new StageProcessingException(getId() + " unable to parse returned XML document " +
- domResource.getDescription(), e);
- } else {
- log.warn("stage {}: unable to parse XML document", getId());
- log.debug("stage {}: parsing exception", getId(), e);
+ resource.getDescription(), e);
}
+ log.warn("stage {}: unable to parse XML document", getId());
+ log.debug("stage {}: parsing exception", getId(), e);
}
}
- /** {@inheritDoc} */
- @Override protected void doDestroy() {
+ @Override
+ protected void doDestroy() {
domResource = null;
parserPool = null;
super.doDestroy();
}
- /** {@inheritDoc} */
- @Override protected void doInitialize() throws ComponentInitializationException {
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
if (parserPool == null) {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMTraversalContext.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMTraversalContext.java
index 66c4e0c..24a668e 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMTraversalContext.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/DOMTraversalContext.java
@@ -26,9 +26,17 @@ import net.shibboleth.metadata.Item;
/**
* The context for a particular DOM traversal.
*
+ * <p>
* Implementations may add additional fields and methods to the definition
* of a {@link DOMTraversalContext}, and may define {@link #end()} to perform
* operations at the end of the traversal.
+ * </p>
+ *
+ * <p>
+ * Note that classes implementing this interface will normally be used only by the
+ * thread in which they are created, so implementations are not required to
+ * be thread-safe.
+ * </p>
*/
public interface DOMTraversalContext {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMaker.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMaker.java
index d804ef8..b7989ce 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMaker.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMaker.java
@@ -20,19 +20,19 @@ package net.shibboleth.metadata.dom;
import java.util.function.Function;
import javax.annotation.Nonnull;
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
import javax.xml.namespace.QName;
-import net.shibboleth.utilities.java.support.xml.ElementSupport;
-
import org.w3c.dom.Element;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+
/**
* Basic maker class for {@link Element}s for use with the {@link Container} system.
*
* @since 0.10.0
*/
- at ThreadSafe
+ at Immutable
public class ElementMaker implements Function<Container, Element> {
/** Qualified name for the {@link Element} to be created. */
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMatcher.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMatcher.java
index a581a52..dc47fc4 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMatcher.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMatcher.java
@@ -21,19 +21,19 @@ import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
import javax.xml.namespace.QName;
-import net.shibboleth.utilities.java.support.xml.ElementSupport;
-
import org.w3c.dom.Element;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+
/**
* Basic matcher class for {@link Element}s for use with the {@link Container} system.
*
* @since 0.10.0
*/
- at ThreadSafe
+ at Immutable
public class ElementMatcher implements Predicate<Element> {
/** Element {@link QName} to match. */
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementStrippingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementStrippingStage.java
index ef721b2..ff452ac 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementStrippingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementStrippingStage.java
@@ -22,6 +22,7 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Element;
@@ -30,6 +31,7 @@ import org.w3c.dom.NodeList;
import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.pipeline.AbstractIteratingStage;
import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -42,9 +44,11 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
public class ElementStrippingStage extends AbstractIteratingStage<Element> {
/** Namespace of the element to strip. */
+ @NonnullAfterInit @NotEmpty @GuardedBy("this")
private String elementNamespace;
/** Name of the element to strip. */
+ @NonnullAfterInit @NotEmpty @GuardedBy("this")
private String elementName;
/**
@@ -52,7 +56,7 @@ public class ElementStrippingStage extends AbstractIteratingStage<Element> {
*
* @return namespace of the element to strip
*/
- @Nullable public String getElementNamespace() {
+ @Nullable public final synchronized String getElementNamespace() {
return elementNamespace;
}
@@ -61,7 +65,7 @@ public class ElementStrippingStage extends AbstractIteratingStage<Element> {
*
* @param namespace namespace of the element to strip
*/
- public void setElementNamespace(@Nonnull @NotEmpty final String namespace) {
+ public synchronized void setElementNamespace(@Nonnull @NotEmpty final String namespace) {
throwSetterPreconditionExceptions();
elementNamespace = Constraint.isNotNull(StringSupport.trimOrNull(namespace),
"target namespace can not be null or empty");
@@ -72,7 +76,7 @@ public class ElementStrippingStage extends AbstractIteratingStage<Element> {
*
* @return the name of the element to strip
*/
- @Nullable public String getElementName() {
+ @Nullable public final synchronized String getElementName() {
return elementName;
}
@@ -81,7 +85,7 @@ public class ElementStrippingStage extends AbstractIteratingStage<Element> {
*
* @param name the name of the element to strip
*/
- public void setElementName(@Nonnull @NotEmpty final String name) {
+ public synchronized void setElementName(@Nonnull @NotEmpty final String name) {
throwSetterPreconditionExceptions();
elementName = Constraint.isNotNull(StringSupport.trimOrNull(name),
"target element name can not be null or empty");
@@ -94,7 +98,7 @@ public class ElementStrippingStage extends AbstractIteratingStage<Element> {
// List all the matching descendant elements in this document in document order
// Note that this list will never include the document element itself
- final NodeList nodeList = docElement.getElementsByTagNameNS(elementNamespace, elementName);
+ final NodeList nodeList = docElement.getElementsByTagNameNS(getElementNamespace(), getElementName());
// Copy these into a list, because a NodeList can change length at any time
final int nNodes = nodeList.getLength();
@@ -109,16 +113,16 @@ public class ElementStrippingStage extends AbstractIteratingStage<Element> {
}
}
- /** {@inheritDoc} */
- @Override protected void doDestroy() {
+ @Override
+ protected void doDestroy() {
elementNamespace = null;
elementName = null;
super.doDestroy();
}
- /** {@inheritDoc} */
- @Override protected void doInitialize() throws ComponentInitializationException {
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
if (elementNamespace == null) {
throw new ComponentInitializationException("target namespace can not be null or empty");
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementVisitor.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementVisitor.java
index 84cd9d8..40a61e9 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementVisitor.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementVisitor.java
@@ -19,14 +19,21 @@ package net.shibboleth.metadata.dom;
import net.shibboleth.metadata.Item;
+import javax.annotation.concurrent.ThreadSafe;
+
import org.w3c.dom.Element;
/**
* Provides a variation of the Visitor pattern for performing operations on
* DOM elements which are part of {@link Element} items.
*
+ * <p>
+ * All implementations of this interface <strong>must</strong> be thread-safe.
+ * </p>
+ *
* @since 0.9.0
*/
+ at ThreadSafe
public interface ElementVisitor {
/**
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementWhitespaceTrimmingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementWhitespaceTrimmingStage.java
index 86d58d6..9dafae7 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementWhitespaceTrimmingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementWhitespaceTrimmingStage.java
@@ -18,6 +18,7 @@
package net.shibboleth.metadata.dom;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Element;
@@ -27,6 +28,7 @@ import org.w3c.dom.Element;
*
* @since 0.9.0
*/
+ at ThreadSafe
public class ElementWhitespaceTrimmingStage extends AbstractElementVisitingStage {
/** Visitor to apply to each visited element. */
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementsStrippingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementsStrippingStage.java
index 5eef774..4189079 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementsStrippingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementsStrippingStage.java
@@ -23,12 +23,13 @@ import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Element;
import net.shibboleth.metadata.Item;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
@@ -58,22 +59,63 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
@ThreadSafe
public class ElementsStrippingStage extends AbstractDOMTraversalStage<ElementsStrippingStage.Context> {
- /** Context class for this kind of traversal. */
- protected static class Context extends SimpleDOMTraversalContext {
+ /**
+ * Context class for this kind of traversal.
+ *
+ * <p>
+ * An instance of this class is passed around during the traversal.
+ * To reduce the number of synchronizations, it includes a copy of
+ * each of the guarded state variables from the stage class instance.
+ * </p>
+ */
+ protected static final class Context extends SimpleDOMTraversalContext {
/**
* List of {@link Element}s to be removed from the document at the
* end of the traversal.
*/
- private List<Element> elements = new ArrayList<>();
+ @Nonnull @NonnullElements
+ private final List<Element> elements = new ArrayList<>();
+
+ /** Namespace of the elements to strip. */
+ @Nonnull @NotEmpty
+ private final String elementNamespace;
+
+ /** Names of the elements to strip. */
+ @Nonnull @NonnullElements @Unmodifiable
+ private final Collection<String> elementNames;
+
+ /** Whether we are operating in a whitelisting mode (<code>false</code> by default). */
+ private final boolean whitelisting;
/**
* Constructor.
*
* @param contextItem the {@link Item} we are traversing
+ * @param namespace
+ * @param names
+ * @param wl
*/
- public Context(@Nonnull final Item<Element> contextItem) {
+ public Context(@Nonnull final Item<Element> contextItem,
+ @Nonnull @NotEmpty final String namespace,
+ @Nonnull @NonnullElements @Unmodifiable final Collection<String> names,
+ final boolean wl) {
super(contextItem);
+ elementNamespace = namespace;
+ elementNames = names;
+ whitelisting = wl;
+ }
+
+ protected final String getElementNamespace() {
+ return elementNamespace;
+ }
+
+ protected final Collection<String> getElementNames() {
+ return elementNames;
+ }
+
+ protected final boolean isWhitelisting() {
+ return whitelisting;
}
/**
@@ -94,10 +136,11 @@ public class ElementsStrippingStage extends AbstractDOMTraversalStage<ElementsSt
}
/** Namespace of the elements to strip. */
+ @NonnullAfterInit @NotEmpty @GuardedBy("this")
private String elementNamespace;
/** Names of the elements to strip. */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private Set<String> elementNames = Set.of();
/** Whether we are operating in a whitelisting mode (<code>false</code> by default). */
@@ -108,7 +151,7 @@ public class ElementsStrippingStage extends AbstractDOMTraversalStage<ElementsSt
*
* @return namespace of the elements to strip
*/
- @Nullable public String getElementNamespace() {
+ @NonnullAfterInit @NotEmpty public final synchronized String getElementNamespace() {
return elementNamespace;
}
@@ -129,7 +172,7 @@ public class ElementsStrippingStage extends AbstractDOMTraversalStage<ElementsSt
* @return the names of the elements to strip
*/
@Nonnull @NonnullElements @Unmodifiable
- public Collection<String> getElementNames() {
+ public final synchronized Collection<String> getElementNames() {
return elementNames;
}
@@ -138,7 +181,7 @@ public class ElementsStrippingStage extends AbstractDOMTraversalStage<ElementsSt
*
* @param names the names of the elements to strip
*/
- public void setElementNames(
+ public synchronized void setElementNames(
@Nonnull @NonnullElements @Unmodifiable @NotEmpty final Collection<String> names) {
throwSetterPreconditionExceptions();
elementNames = Set.copyOf(names);
@@ -165,14 +208,14 @@ public class ElementsStrippingStage extends AbstractDOMTraversalStage<ElementsSt
}
@Override
- protected boolean applicable(@Nonnull final Element element) {
+ protected boolean applicable(@Nonnull final Element element, @Nonnull final Context context) {
// ignore all elements not in the given namespace
- if (!elementNamespace.equals(element.getNamespaceURI())) {
+ if (!context.getElementNamespace().equals(element.getNamespaceURI())) {
return false;
}
// Whitelisting reverses the meaning of presence in the list
- return whitelisting ^ elementNames.contains(element.getLocalName());
+ return context.isWhitelisting() ^ context.getElementNames().contains(element.getLocalName());
}
@Override
@@ -198,8 +241,8 @@ public class ElementsStrippingStage extends AbstractDOMTraversalStage<ElementsSt
}
@Override
- protected Context buildContext(@Nonnull final Item<Element> item) {
- return new Context(item);
+ protected synchronized Context buildContext(@Nonnull final Item<Element> item) {
+ return new Context(item, getElementNamespace(), getElementNames(), isWhitelisting());
}
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/EmptyContainerStrippingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/EmptyContainerStrippingStage.java
index 50d045c..86c9cab 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/EmptyContainerStrippingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/EmptyContainerStrippingStage.java
@@ -19,6 +19,7 @@ package net.shibboleth.metadata.dom;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Element;
@@ -27,6 +28,7 @@ import org.w3c.dom.NodeList;
import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.pipeline.AbstractIteratingStage;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -40,9 +42,11 @@ import net.shibboleth.utilities.java.support.xml.ElementSupport;
public class EmptyContainerStrippingStage extends AbstractIteratingStage<Element> {
/** Namespace of the element to strip. */
+ @NonnullAfterInit @NotEmpty @GuardedBy("this")
private String elementNamespace;
/** Name of the element to strip. */
+ @NonnullAfterInit @NotEmpty @GuardedBy("this")
private String elementName;
/**
@@ -50,7 +54,7 @@ public class EmptyContainerStrippingStage extends AbstractIteratingStage<Element
*
* @return namespace of the element to strip
*/
- @Nullable public String getElementNamespace() {
+ @Nullable public final synchronized String getElementNamespace() {
return elementNamespace;
}
@@ -59,7 +63,7 @@ public class EmptyContainerStrippingStage extends AbstractIteratingStage<Element
*
* @param namespace namespace of the element to strip
*/
- public void setElementNamespace(@Nonnull @NotEmpty final String namespace) {
+ public synchronized void setElementNamespace(@Nonnull @NotEmpty final String namespace) {
throwSetterPreconditionExceptions();
elementNamespace = Constraint.isNotNull(StringSupport.trimOrNull(namespace),
"target namespace can not be null or empty");
@@ -70,7 +74,7 @@ public class EmptyContainerStrippingStage extends AbstractIteratingStage<Element
*
* @return the name of the element to strip
*/
- @Nullable public String getElementName() {
+ @Nullable public final synchronized String getElementName() {
return elementName;
}
@@ -79,7 +83,7 @@ public class EmptyContainerStrippingStage extends AbstractIteratingStage<Element
*
* @param name the name of the element to strip
*/
- public void setElementName(@Nonnull @NotEmpty final String name) {
+ public synchronized void setElementName(@Nonnull @NotEmpty final String name) {
throwSetterPreconditionExceptions();
elementName = Constraint.isNotNull(StringSupport.trimOrNull(name),
"target element name can not be null or empty");
@@ -102,7 +106,7 @@ public class EmptyContainerStrippingStage extends AbstractIteratingStage<Element
final Element element = item.unwrap();
// List all the relevant elements in this document in document order
- final NodeList extensionList = element.getElementsByTagNameNS(elementNamespace, elementName);
+ final NodeList extensionList = element.getElementsByTagNameNS(getElementNamespace(), getElementName());
// Process in reverse order so that, for example, Extensions inside Extensions are
// handled correctly.
@@ -114,16 +118,16 @@ public class EmptyContainerStrippingStage extends AbstractIteratingStage<Element
}
}
- /** {@inheritDoc} */
- @Override protected void doDestroy() {
+ @Override
+ protected void doDestroy() {
elementNamespace = null;
elementName = null;
super.doDestroy();
}
- /** {@inheritDoc} */
- @Override protected void doInitialize() throws ComponentInitializationException {
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
if (elementNamespace == null) {
throw new ComponentInitializationException("target namespace can not be null or empty");
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespaceStrippingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespaceStrippingStage.java
index 1174001..99fb9b8 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespaceStrippingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespaceStrippingStage.java
@@ -19,6 +19,7 @@ package net.shibboleth.metadata.dom;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
@@ -37,6 +38,7 @@ public class NamespaceStrippingStage extends AbstractNamespacesStrippingStage {
/**
* XML namespace to remove.
*/
+ @Nonnull @NotEmpty @GuardedBy("this")
private String namespace;
/**
@@ -44,7 +46,7 @@ public class NamespaceStrippingStage extends AbstractNamespacesStrippingStage {
*
* @return namespace URI
*/
- @Nullable public String getNamespace() {
+ @Nullable public final synchronized String getNamespace() {
return namespace;
}
@@ -53,7 +55,7 @@ public class NamespaceStrippingStage extends AbstractNamespacesStrippingStage {
*
* @param ns namespace URI as a string
*/
- public void setNamespace(@Nonnull @NotEmpty final String ns) {
+ public synchronized void setNamespace(@Nonnull @NotEmpty final String ns) {
throwSetterPreconditionExceptions();
namespace = Constraint.isNotNull(StringSupport.trimOrNull(ns),
"target namespace can not be null or empty");
@@ -61,7 +63,7 @@ public class NamespaceStrippingStage extends AbstractNamespacesStrippingStage {
@Override
protected boolean removingNamespace(final String ns) {
- return namespace.equals(ns);
+ return getNamespace().equals(ns);
}
@Override
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespacesStrippingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespacesStrippingStage.java
index b43f442..b56695e 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespacesStrippingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespacesStrippingStage.java
@@ -21,6 +21,8 @@ import java.util.Collection;
import java.util.Set;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
@@ -48,12 +50,13 @@ public class NamespacesStrippingStage extends AbstractNamespacesStrippingStage {
/**
* XML namespaces to whitelist or blacklist.
*/
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private Set<String> namespaces = Set.of();
/**
* Whether we are whitelisting or blacklisting (default: blacklisting).
*/
+ @GuardedBy("this")
private boolean whitelisting;
/**
@@ -62,7 +65,7 @@ public class NamespacesStrippingStage extends AbstractNamespacesStrippingStage {
* @return collection of namespaces being removed
*/
@Nonnull @NonnullElements @Unmodifiable
- public Collection<String> getNamespaces() {
+ public final synchronized Collection<String> getNamespaces() {
return namespaces;
}
@@ -71,7 +74,7 @@ public class NamespacesStrippingStage extends AbstractNamespacesStrippingStage {
*
* @param nss collection of namespaces
*/
- public void setNamespaces(@Nonnull @NonnullElements @Unmodifiable final Collection<String> nss) {
+ public synchronized void setNamespaces(@Nonnull @NonnullElements @Unmodifiable final Collection<String> nss) {
throwSetterPreconditionExceptions();
namespaces = Set.copyOf(nss);
}
@@ -81,7 +84,7 @@ public class NamespacesStrippingStage extends AbstractNamespacesStrippingStage {
*
* @return <code>true</code> for whitelisting, <code>false</code> for blacklisting (the default)
*/
- public boolean isWhitelisting() {
+ public final synchronized boolean isWhitelisting() {
return whitelisting;
}
@@ -90,20 +93,20 @@ public class NamespacesStrippingStage extends AbstractNamespacesStrippingStage {
*
* @param wl <code>true</code> for whitelisting, <code>false</code> for blacklisting
*/
- public void setWhitelisting(final boolean wl) {
+ public synchronized void setWhitelisting(final boolean wl) {
throwSetterPreconditionExceptions();
whitelisting = wl;
}
@Override
- protected boolean removingNamespace(final String namespace) {
+ protected boolean removingNamespace(@Nullable final String namespace) {
// Handle ineligible null element, for the default namespace case
if (namespace == null) {
- return whitelisting;
+ return isWhitelisting();
}
// Handle normal namespaces
- return whitelisting ^ namespaces.contains(namespace);
+ return isWhitelisting() ^ getNamespaces().contains(namespace);
}
@Override
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NodeVisitor.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NodeVisitor.java
index 10ff174..fb9c12d 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NodeVisitor.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NodeVisitor.java
@@ -19,6 +19,9 @@ package net.shibboleth.metadata.dom;
import net.shibboleth.metadata.Item;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -26,8 +29,13 @@ import org.w3c.dom.Node;
* Provides a variation of the Visitor pattern for performing operations on
* DOM nodes which are part of {@link Element} items.
*
+ * <p>
+ * All implementations of this interface <strong>must</strong> be thread-safe.
+ * </p>
+ *
* @since 0.9.0
*/
+ at ThreadSafe
public interface NodeVisitor {
/**
@@ -37,6 +45,6 @@ public interface NodeVisitor {
* @param visited the {@link Node} being visited.
* @param item the {@link Item} which is the context for the visit.
*/
- void visitNode(Node visited, Item<Element> item);
-
+ void visitNode(@Nonnull Node visited, @Nonnull Item<Element> item);
+
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/SimpleDOMTraversalContext.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/SimpleDOMTraversalContext.java
index 24b1f6a..efedfed 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/SimpleDOMTraversalContext.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/SimpleDOMTraversalContext.java
@@ -41,7 +41,7 @@ public class SimpleDOMTraversalContext implements DOMTraversalContext {
}
@Override
- public Item<Element> getItem() {
+ public final Item<Element> getItem() {
return item;
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/WhitespaceTrimmingVisitor.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/WhitespaceTrimmingVisitor.java
index 7899080..325da6d 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/WhitespaceTrimmingVisitor.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/WhitespaceTrimmingVisitor.java
@@ -18,6 +18,7 @@
package net.shibboleth.metadata.dom;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
@@ -31,6 +32,7 @@ import net.shibboleth.metadata.Item;
*
* @since 0.9.0
*/
+ at Immutable
class WhitespaceTrimmingVisitor implements NodeVisitor, ElementVisitor, AttrVisitor {
@Override
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSchemaValidationStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSchemaValidationStage.java
index a460ba8..b8c0958 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSchemaValidationStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSchemaValidationStage.java
@@ -22,6 +22,7 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
@@ -39,6 +40,7 @@ import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.WarningStatus;
import net.shibboleth.metadata.pipeline.AbstractIteratingStage;
import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -65,13 +67,15 @@ public class XMLSchemaValidationStage extends AbstractIteratingStage<Element> {
private final Logger log = LoggerFactory.getLogger(XMLSchemaValidationStage.class);
/** Collection of schema resources. */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private List<Resource> schemaResources = List.of();
/** Whether Elements are required to be schema valid. Default value: <code>true</code> */
+ @GuardedBy("this")
private boolean elementRequiredToBeSchemaValid = true;
- /** Schema used to validate Elements. */
+ /** Schema used to validate Elements. Built during initialization. */
+ @NonnullAfterInit @GuardedBy("this")
private Schema validationSchema;
/**
@@ -80,7 +84,7 @@ public class XMLSchemaValidationStage extends AbstractIteratingStage<Element> {
* @return unmodifiable list of schema resources against which Elements are validated
*/
@Nonnull @NonnullElements @Unmodifiable
- public List<Resource> getSchemaResources() {
+ public final synchronized List<Resource> getSchemaResources() {
return schemaResources;
}
@@ -100,7 +104,7 @@ public class XMLSchemaValidationStage extends AbstractIteratingStage<Element> {
*
* @return whether Elements are required to be schema valid
*/
- public boolean isElementRequiredToBeSchemaValid() {
+ public final synchronized boolean isElementRequiredToBeSchemaValid() {
return elementRequiredToBeSchemaValid;
}
@@ -114,18 +118,27 @@ public class XMLSchemaValidationStage extends AbstractIteratingStage<Element> {
elementRequiredToBeSchemaValid = isRequired;
}
+ /**
+ * Returns the shared validation schema built during initialization.
+ *
+ * @return the shared validation schema
+ */
+ private synchronized Schema getValidationSchema() {
+ return validationSchema;
+ }
+
@Override
protected void doExecute(@Nonnull final Item<Element> item) throws StageProcessingException {
log.debug("{} pipeline stage schema validating DOM Element collection elements", getId());
- final Validator validator = validationSchema.newValidator();
+ final Validator validator = getValidationSchema().newValidator();
try {
validator.validate(new DOMSource(item.unwrap()));
} catch (final Exception e) {
if (log.isDebugEnabled()) {
log.debug("DOM Element was not valid:\n{}", SerializeSupport.prettyPrintXML(item.unwrap()), e);
}
- if (elementRequiredToBeSchemaValid) {
+ if (isElementRequiredToBeSchemaValid()) {
item.getItemMetadata().put(new ErrorStatus(getId(), e.getMessage()));
} else {
item.getItemMetadata().put(new WarningStatus(getId(), e.getMessage()));
@@ -133,16 +146,16 @@ public class XMLSchemaValidationStage extends AbstractIteratingStage<Element> {
}
}
- /** {@inheritDoc} */
- @Override protected void doDestroy() {
+ @Override
+ protected void doDestroy() {
schemaResources = null;
validationSchema = null;
super.doDestroy();
}
- /** {@inheritDoc} */
- @Override protected void doInitialize() throws ComponentInitializationException {
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
if (schemaResources.isEmpty()) {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureSigningStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureSigningStage.java
index f2a09cd..3f04b67 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureSigningStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureSigningStage.java
@@ -17,60 +17,33 @@
package net.shibboleth.metadata.dom;
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Collection;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
-import javax.security.auth.x500.X500Principal;
-import javax.xml.crypto.XMLStructure;
-import javax.xml.crypto.dsig.CanonicalizationMethod;
-import javax.xml.crypto.dsig.DigestMethod;
-import javax.xml.crypto.dsig.Reference;
-import javax.xml.crypto.dsig.SignatureMethod;
-import javax.xml.crypto.dsig.SignedInfo;
-import javax.xml.crypto.dsig.Transform;
-import javax.xml.crypto.dsig.XMLSignContext;
-import javax.xml.crypto.dsig.XMLSignature;
-import javax.xml.crypto.dsig.XMLSignatureFactory;
-import javax.xml.crypto.dsig.dom.DOMSignContext;
-import javax.xml.crypto.dsig.keyinfo.KeyInfo;
-import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
-import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
-import javax.xml.crypto.dsig.spec.DigestMethodParameterSpec;
-import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec;
-import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.namespace.QName;
import org.apache.xml.security.Init;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.w3c.dom.Attr;
import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
import net.shibboleth.metadata.Item;
-import net.shibboleth.metadata.dom.ds.XMLDSIGSupport;
-import net.shibboleth.metadata.pipeline.AbstractIteratingStage;
+import net.shibboleth.metadata.dom.impl.XMLSignatureSigner;
+import net.shibboleth.metadata.pipeline.AbstractStage;
import net.shibboleth.metadata.pipeline.StageProcessingException;
-import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-import net.shibboleth.utilities.java.support.xml.ElementSupport;
-import net.shibboleth.utilities.java.support.xml.QNameSupport;
/**
* A pipeline stage that creates, and adds, an enveloped signature for each element in the given {@link DOMElementItem}
@@ -83,80 +56,72 @@ import net.shibboleth.utilities.java.support.xml.QNameSupport;
* </ul>
*/
@ThreadSafe
-public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
+public class XMLSignatureSigningStage extends AbstractStage<Element> {
/** The variant of SHA to use in the various signature algorithms. */
public static enum ShaVariant {
- SHA1, SHA256, SHA384, SHA512
+ /** 160-bit SHA-1. */
+ SHA1,
+ /** 256-bit SHA-2. */
+ SHA256,
+ /** 384-bit SHA-2. */
+ SHA384,
+ /** 512-bit SHA-2. */
+ SHA512
};
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(XMLSignatureSigningStage.class);
- /** Factory used to create XML signature objects. */
- private XMLSignatureFactory xmlSigFactory;
-
- /** Factory used to create KeyInfo objects. */
- private KeyInfoFactory keyInfoFactory;
-
/** SHA algorithm variant used in signature and digest algorithms. Default value: <code>ShaVariant.SHA256</code> */
+ @Nonnull @GuardedBy("this")
private ShaVariant shaVariant = ShaVariant.SHA256;
/** Private key used to sign data. */
+ @NonnullAfterInit @GuardedBy("this")
private PrivateKey privKey;
/** Public key associated with the given private key. */
+ @Nullable @GuardedBy("this")
private PublicKey publicKey;
/**
* Certificate chain, with end entity certificate as element 0, to be included with the signature. Default value:
* empty list
*/
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private List<X509Certificate> certificates = List.of();
/** CRLs to be included with the signature. Default value: empty list */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private List<X509CRL> crls = List.of();
- /** Signature algorithm used. */
- private String sigAlgo;
-
- /** Digest algorithm used. */
- private String digestAlgo;
-
/** Whether to use exclusive canonicalization. Default value: <code>true</code> */
- private boolean c14nExclusive = true;
+ @GuardedBy("this") private boolean c14nExclusive = true;
/** Whether to include comments in the canonicalized data. Default value: <code>false</code> */
- private boolean c14nWithComments;
+ @GuardedBy("this") private boolean c14nWithComments;
/** Whether to remove CR characters from generated signatures. Default value: <code>true</code>. */
- private boolean removingCRsFromSignature = true;
-
- /**
- * Canonicalization algorithm to use. This is determined from the {@link #c14nExclusive} and
- * {@link #c14nWithComments} properties.
- */
- private String c14nAlgo;
+ @GuardedBy("this") private boolean removingCRsFromSignature = true;
/** Inclusive prefix list used with exclusive canonicalization. Default value: empty list */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private List<String> inclusivePrefixList = List.of();
/**
* Names of attributes to treat as ID attributes for signature referencing. Default value: list containing the
* non-namespace-qualified attributes 'ID', 'Id', 'id'
*/
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private List<QName> idAttributeNames = List.of(new QName("ID"), new QName("id"), new QName("Id"));
/** Explicit names to associate with the given signing key. Default value: empty list */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private List<String> keyNames = List.of();
/** Whether key names should be included in the signature's KeyInfo. Default value: <code>true</code> */
- private boolean includeKeyNames = true;
+ @GuardedBy("this") private boolean includeKeyNames = true;
/**
* Whether the public key should be included in the signature's KeyInfo.
@@ -166,44 +131,37 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* Default value: <code>false</code>
*/
- private boolean includeKeyValue;
+ @GuardedBy("this") private boolean includeKeyValue;
/**
* Whether the end-entity certificate's subject name should be included in the signature's KeyInfo. Default value:
* <code>false</code>
*/
- private boolean includeX509SubjectName;
+ @GuardedBy("this") private boolean includeX509SubjectName;
/**
* Whether the certificates chain should be included in the signature's KeyInfo. Default value: <code>true</code>
*/
- private boolean includeX509Certificates = true;
+ @GuardedBy("this") private boolean includeX509Certificates = true;
/** Whether the CRLs should be included in the signature's KeyInfo. Default value: <code>false</code> */
- private boolean includeX509Crls;
+ @GuardedBy("this") private boolean includeX509Crls;
/**
* Whether the end-entity certificate's issuer and serial number should be included in the signature's KeyInfo.
* Default value: <code>false</code>
*/
- private boolean includeX509IssuerSerial;
+ @GuardedBy("this") private boolean includeX509IssuerSerial;
/** Whether to debug digest operations by logging the pre-digest data stream. Default value: <code>false</code> */
- private boolean debugPreDigest;
-
- /**
- * Constructor.
- */
- public XMLSignatureSigningStage() {
- shaVariant = ShaVariant.SHA256;
- }
+ @GuardedBy("this") private boolean debugPreDigest;
/**
* Gets the SHA algorithm variant used when computing the signature and digest.
*
* @return SHA algorithm variant used when computing the signature and digest
*/
- @Nonnull public ShaVariant getShaVariant() {
+ @Nonnull public final synchronized ShaVariant getShaVariant() {
return shaVariant;
}
@@ -222,7 +180,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return the privKey private key used to sign the content
*/
- @Nullable public PrivateKey getPrivateKey() {
+ @Nullable public final synchronized PrivateKey getPrivateKey() {
return privKey;
}
@@ -241,7 +199,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return public key associated with private key used to sign the content
*/
- @Nullable public PublicKey getPublicKey() {
+ @Nullable public final synchronized PublicKey getPublicKey() {
return publicKey;
}
@@ -262,7 +220,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
* @return certificates associated with the key used to sign the content
*/
@Nonnull @NonnullElements @Unmodifiable
- public List<X509Certificate> getCertificates() {
+ public final synchronized List<X509Certificate> getCertificates() {
return certificates;
}
@@ -284,7 +242,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
* @return CRLs associated with certificates
*/
@Nonnull @NonnullElements @Unmodifiable
- public List<X509CRL> getCrls() {
+ public final synchronized List<X509CRL> getCrls() {
return crls;
}
@@ -304,7 +262,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return whether exclusive canonicalization will be used
*/
- public boolean isC14nExclusive() {
+ public final synchronized boolean isC14nExclusive() {
return c14nExclusive;
}
@@ -323,7 +281,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return whether comments are canonicalized
*/
- public boolean isC14nWithComments() {
+ public final synchronized boolean isC14nWithComments() {
return c14nWithComments;
}
@@ -343,7 +301,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
* @return inclusive prefix list used during exclusive canonicalization
*/
@Nonnull @NonnullElements @Unmodifiable
- public List<String> getInclusivePrefixList() {
+ public final synchronized List<String> getInclusivePrefixList() {
return inclusivePrefixList;
}
@@ -364,7 +322,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
* @return names of the attributes treated as reference IDs
*/
@Nonnull @NonnullElements @Unmodifiable
- public List<QName> getIdAttributeNames() {
+ public final synchronized List<QName> getIdAttributeNames() {
return idAttributeNames;
}
@@ -387,7 +345,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
* @return explicit key names added to the KeyInfo
*/
@Nonnull @NonnullElements @Unmodifiable
- public List<String> getKeyNames() {
+ public final synchronized List<String> getKeyNames() {
return keyNames;
}
@@ -407,7 +365,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return whether key names are included in the KeyInfo
*/
- public boolean isIncludeKeyNames() {
+ public final synchronized boolean isIncludeKeyNames() {
return includeKeyNames;
}
@@ -426,7 +384,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return whether key values are included in the KeyInfo
*/
- public boolean isIncludeKeyValue() {
+ public final synchronized boolean isIncludeKeyValue() {
return includeKeyValue;
}
@@ -445,7 +403,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return whether end-entity certifcate's subject name is included in the KeyInfo
*/
- public boolean isIncludeX509SubjectName() {
+ public final synchronized boolean isIncludeX509SubjectName() {
return includeX509SubjectName;
}
@@ -464,7 +422,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return whether X509 certificates are included in the KeyInfo
*/
- public boolean isIncludeX509Certificates() {
+ public final synchronized boolean isIncludeX509Certificates() {
return includeX509Certificates;
}
@@ -483,7 +441,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return whether CRLs are included in the KeyInfo
*/
- public boolean isIncludeX509Crls() {
+ public final synchronized boolean isIncludeX509Crls() {
return includeX509Crls;
}
@@ -502,7 +460,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return whether the end-entity certificate's issuer and serial number are included in the KeyInfo
*/
- public boolean isIncludeX509IssuerSerial() {
+ public final synchronized boolean isIncludeX509IssuerSerial() {
return includeX509IssuerSerial;
}
@@ -521,7 +479,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
*
* @return whether logging of the pre-digest data stream is enabled
*/
- public boolean isDebugPreDigest() {
+ public final synchronized boolean isDebugPreDigest() {
return debugPreDigest;
}
@@ -535,30 +493,12 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
debugPreDigest = debug;
}
- /**
- * Gets the signature algorithm used when signing.
- *
- * @return signature algorithm used when signing
- */
- public String getSigAlgo() {
- return sigAlgo;
- }
-
- /**
- * Gets the digest algorithm used when signing.
- *
- * @return digest algorithm used when signing
- */
- public String getDigestAlgo() {
- return digestAlgo;
- }
-
/**
* Gets whether CR characters will be removed from generated signatures.
*
* @return <code>true</code> if CR characters will be removed from generated signatures.
*/
- public boolean isRemovingCRsFromSignature() {
+ public final synchronized boolean isRemovingCRsFromSignature() {
return removingCRsFromSignature;
}
@@ -572,314 +512,21 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
removingCRsFromSignature = newValue;
}
- /**
- * Remove any CRs from the text content of named child elements.
- *
- * @param signature The <code>Signature</code> element to process.
- * @param elementName The element name within the XML DSIG namespace to look for.
- */
- private void removeCRsFromNamedChildren(@Nonnull final Element signature, @Nonnull final String elementName) {
- final NodeList nodes = signature.getElementsByTagNameNS(XMLSignature.XMLNS, elementName);
- for (int i = 0; i < nodes.getLength(); i++) {
- final Node node = nodes.item(i);
- final String text = node.getTextContent();
- if (text.indexOf('\r') >= 0) {
- node.setTextContent(text.replaceAll("\\r", ""));
- }
- }
- }
-
@Override
- protected void doExecute(@Nonnull final Item<Element> item) throws StageProcessingException {
- final Element element = item.unwrap();
- final XMLSignature signature = xmlSigFactory.newXMLSignature(buildSignedInfo(element), buildKeyInfo());
- try {
- final XMLSignContext context = new DOMSignContext(privKey, element, element.getFirstChild());
-
- // Enable caching reference values if required for debugging.
- if (isDebugPreDigest() && log.isDebugEnabled()) {
- context.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
- }
-
- // Perform the signature operation
- signature.sign(context);
-
- // Remove any CRs from selected signature elements.
- if (isRemovingCRsFromSignature()) {
- final Element signatureElement = ElementSupport.getFirstChildElement(element,
- XMLDSIGSupport.SIGNATURE_NAME);
- removeCRsFromNamedChildren(signatureElement, "SignatureValue");
- removeCRsFromNamedChildren(signatureElement, "X509Certificate");
- }
-
- // Log the pre-digest data for debugging
- if (isDebugPreDigest() && log.isDebugEnabled()) {
- final Reference ref = signature.getSignedInfo().getReferences().get(0);
- final String preDigest = new String(ref.getDigestInputStream().readAllBytes(), StandardCharsets.UTF_8);
- log.debug("pre digest: {}", preDigest);
- }
- } catch (final Exception e) {
- log.error("Unable to create signature for element", e);
- throw new StageProcessingException("Unable to create signature for element", e);
- }
- }
-
- /**
- * Gets the descriptor of signed content.
- *
- * @param target the element that will be signed
- *
- * @return signed content descriptor
- *
- * @throws StageProcessingException thrown if there is a problem creating the signed content descriptor
- */
- @Nonnull protected SignedInfo buildSignedInfo(@Nonnull final Element target) throws StageProcessingException {
- C14NMethodParameterSpec c14nMethodSpec = null;
- if (c14nAlgo.startsWith(CanonicalizationMethod.EXCLUSIVE) && inclusivePrefixList != null
- && !inclusivePrefixList.isEmpty()) {
- c14nMethodSpec = new ExcC14NParameterSpec(inclusivePrefixList);
- }
-
- final CanonicalizationMethod c14nMethod;
- try {
- c14nMethod = xmlSigFactory.newCanonicalizationMethod(c14nAlgo, c14nMethodSpec);
- } catch (final Exception e) {
- final String errMsg = "Unable to create transform " + c14nAlgo;
- log.error(errMsg, e);
- throw new StageProcessingException(errMsg, e);
- }
-
- final SignatureMethod sigMethod;
- try {
- sigMethod = xmlSigFactory.newSignatureMethod(sigAlgo, null);
- } catch (final Exception e) {
- final String errMsg = "Unable to create signature method " + sigAlgo;
- log.error(errMsg, e);
- throw new StageProcessingException(errMsg, e);
- }
-
- final List<Reference> refs = Collections.singletonList(buildSignatureReference(target));
-
- return xmlSigFactory.newSignedInfo(c14nMethod, sigMethod, refs);
- }
-
- /**
- * Builds the references to the signed content.
- *
- * @param target the element to be signed
- *
- * @return reference to signed content
- *
- * @throws StageProcessingException thrown if there is a problem creating the reference to the element
- */
- @Nonnull protected Reference buildSignatureReference(@Nonnull final Element target)
+ protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection)
throws StageProcessingException {
- final String id = getElementId(target);
- final String refUri;
- if (id == null) {
- refUri = "";
- } else {
- refUri = "#" + id;
- }
-
- DigestMethod digestMethod = null;
- try {
- final DigestMethodParameterSpec digestMethodSpec = null;
- digestMethod = xmlSigFactory.newDigestMethod(digestAlgo, digestMethodSpec);
- } catch (final Exception e) {
- final String errMsg = "Unable to create digest method " + digestAlgo;
- log.error(errMsg, e);
- throw new StageProcessingException(errMsg, e);
- }
-
- TransformParameterSpec transformSpec;
- final ArrayList<Transform> transforms = new ArrayList<>();
-
- try {
- transformSpec = null;
- transforms.add(xmlSigFactory.newTransform(Transform.ENVELOPED, transformSpec));
- } catch (final Exception e) {
- final String errMsg = "Unable to create transform " + Transform.ENVELOPED;
- log.error(errMsg, e);
- throw new StageProcessingException(errMsg, e);
- }
-
- try {
- if (c14nAlgo.startsWith(CanonicalizationMethod.EXCLUSIVE) && inclusivePrefixList != null
- && !inclusivePrefixList.isEmpty()) {
- transformSpec = new ExcC14NParameterSpec(inclusivePrefixList);
- }
- transforms.add(xmlSigFactory.newTransform(c14nAlgo, transformSpec));
- } catch (final Exception e) {
- final String errMsg = "Unable to create transform " + c14nAlgo;
- log.error(errMsg, e);
- throw new StageProcessingException(errMsg, e);
- }
-
- return xmlSigFactory.newReference(refUri, digestMethod, transforms, null, null);
- }
-
- /**
- * Determines the ID for the element to be signed. To determine the ID first, all the element attributes are
- * inspected, if one matches the provided {@link #idAttributeNames} then the value of the attribute is used as the
- * ID value. If no ID attribute names are given, or none of the given ones match, and one or more of the attributes
- * is marked as an ID attribute (i.e. {@link Attr#isId()} is true), then the value of one of those attributes is
- * used.
- *
- * @param target an element to be referenced by the signature
- *
- * @return the ID value for the element, or null
- */
- @Nullable protected String getElementId(@Nonnull final Element target) {
- final NamedNodeMap attributes = target.getAttributes();
- if (attributes == null || attributes.getLength() < 1) {
- return null;
- }
-
- if (idAttributeNames != null && !idAttributeNames.isEmpty()) {
- for (int i = 0; i < attributes.getLength(); i++) {
- final Attr attribute = (Attr) attributes.item(i);
- if (idAttributeNames.contains(QNameSupport.getNodeQName(attribute))) {
- // mark the attribute as an ID attribute so that it can be referenced by the signature
- target.setIdAttributeNode(attribute, true);
- final String value = StringSupport.trimOrNull(attribute.getValue());
- if (value != null) {
- return value;
- }
- }
- }
- }
-
- for (int i = 0; i < attributes.getLength(); i++) {
- final Attr attribute = (Attr) attributes.item(i);
- if (attribute.isId()) {
- final String value = StringSupport.trimOrNull(attribute.getValue());
- if (value != null) {
- return value;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Builds the KeyInfo element to be included in the signature.
- *
- * @return KeyInfo element to be included in the signature
- *
- * @throws StageProcessingException thrown if there is a problem creating the KeyInfo descriptor
- */
- @Nullable protected KeyInfo buildKeyInfo() throws StageProcessingException {
- final List<XMLStructure> keyInfoItems = new ArrayList<>();
-
- addKeyNames(keyInfoItems);
- addKeyValue(keyInfoItems);
- addX509Data(keyInfoItems);
-
- if (keyInfoItems.isEmpty()) {
- return null;
- }
- return keyInfoFactory.newKeyInfo(keyInfoItems);
- }
-
- /**
- * Adds key names to the KeyInfo, if key names are to be included.
- *
- * @param keyInfoItems collector for KeyInfo children
- *
- * @throws StageProcessingException thrown if there is a problem creating the KeyName content
- */
- protected void addKeyNames(@Nonnull @NonnullElements @Live final List<XMLStructure> keyInfoItems)
- throws StageProcessingException {
- if (!includeKeyNames) {
- return;
- }
-
- if (keyNames != null && !keyNames.isEmpty()) {
- for (final String name : keyNames) {
- keyInfoItems.add(keyInfoFactory.newKeyName(name));
- }
- }
- }
-
- /**
- * Adds raw key values to the KeyInfo if key values are to be included.
- *
- * @param keyInfoItems collector for KeyInfo children
- *
- * @throws StageProcessingException thrown if there is a problem creating the KeyValue content
- */
- protected void addKeyValue(@Nonnull @NonnullElements @Live final List<XMLStructure> keyInfoItems)
- throws StageProcessingException {
- if (!includeKeyValue) {
- return;
- }
-
- PublicKey key = publicKey;
-
- // If we have no explicit public key, we can extract one from a certificate, if we have one.
- if (key == null && !certificates.isEmpty()) {
- key = certificates.get(0).getPublicKey();
- }
-
- if (key != null) {
- try {
- keyInfoItems.add(keyInfoFactory.newKeyValue(key));
- } catch (final Exception e) {
- log.error("Unable to create KeyValue", e);
- throw new StageProcessingException("Unable to create KeyValue", e);
- }
- }
- }
-
- /**
- * Adds X509 data (subject names, certificates, CRLs, and Issuer/Serial) set to be included, into the key info.
- *
- * @param keyInfoItems collector for KeyInfo children
- */
- protected void addX509Data(@Nonnull @NonnullElements @Live final List<XMLStructure> keyInfoItems) {
- final List<Object> x509Data = new ArrayList<>();
-
- if (certificates != null && !certificates.isEmpty()) {
- final X509Certificate endEntityCert = certificates.get(0);
-
- if (includeX509SubjectName) {
- final X500Principal subjectDn = endEntityCert.getSubjectX500Principal();
- x509Data.add(subjectDn.getName(X500Principal.RFC2253));
- }
-
- if (includeX509Certificates) {
- x509Data.addAll(certificates);
- }
-
- if (includeX509IssuerSerial) {
- final X500Principal issuerDn = endEntityCert.getIssuerX500Principal();
- final BigInteger serialNumber = endEntityCert.getSerialNumber();
- x509Data.add(keyInfoFactory.newX509IssuerSerial(issuerDn.getName(X500Principal.RFC2253), serialNumber));
- }
- }
-
- if (includeX509Crls && crls != null && !crls.isEmpty()) {
- x509Data.add(crls);
- }
-
- if (!x509Data.isEmpty()) {
- keyInfoItems.add(keyInfoFactory.newX509Data(x509Data));
+ final var signer = new XMLSignatureSigner(this, log);
+ for (final Item<Element> item : itemCollection) {
+ signer.sign(item);
}
}
@Override
protected void doDestroy() {
- xmlSigFactory = null;
- keyInfoFactory = null;
privKey = null;
publicKey = null;
certificates = null;
crls = null;
- sigAlgo = null;
- digestAlgo = null;
- c14nAlgo = null;
inclusivePrefixList = null;
idAttributeNames = null;
keyNames = null;
@@ -891,49 +538,7 @@ public class XMLSignatureSigningStage extends AbstractIteratingStage<Element> {
protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
- if (!Init.isInitialized()) {
- Init.init();
- }
-
- xmlSigFactory = XMLSignatureFactory.getInstance();
- keyInfoFactory = xmlSigFactory.getKeyInfoFactory();
-
- switch (shaVariant) {
- case SHA1:
- sigAlgo = SignatureMethod.RSA_SHA1;
- digestAlgo = DigestMethod.SHA1;
- break;
-
- case SHA384:
- sigAlgo = SignatureMethod.RSA_SHA384;
- digestAlgo = DigestMethod.SHA384;
- break;
-
- case SHA512:
- sigAlgo = SignatureMethod.RSA_SHA512;
- digestAlgo = DigestMethod.SHA512;
- break;
-
- case SHA256:
- default:
- sigAlgo = SignatureMethod.RSA_SHA256;
- digestAlgo = DigestMethod.SHA256;
- break;
- }
-
- if (c14nExclusive) {
- if (c14nWithComments) {
- c14nAlgo = CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS;
- } else {
- c14nAlgo = CanonicalizationMethod.EXCLUSIVE;
- }
- } else {
- if (c14nWithComments) {
- c14nAlgo = CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS;
- } else {
- c14nAlgo = CanonicalizationMethod.INCLUSIVE;
- }
- }
-
+ Init.init();
}
+
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureValidationStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureValidationStage.java
index 02ea65c..be6d7a5 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureValidationStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureValidationStage.java
@@ -24,6 +24,7 @@ import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.apache.xml.security.Init;
@@ -34,9 +35,10 @@ import org.w3c.dom.Element;
import net.shibboleth.metadata.ErrorStatus;
import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.WarningStatus;
-import net.shibboleth.metadata.dom.XMLSignatureValidator.ValidationException;
-import net.shibboleth.metadata.pipeline.AbstractIteratingStage;
-import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.metadata.dom.impl.XMLSignatureValidator;
+import net.shibboleth.metadata.dom.impl.XMLSignatureValidator.ValidationException;
+import net.shibboleth.metadata.pipeline.AbstractStage;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -60,41 +62,45 @@ import net.shibboleth.utilities.java.support.xml.SerializeSupport;
* valid and an Element signature is found to be invalid than an {@link WarningStatus} is set on the Element.
*/
@ThreadSafe
-public class XMLSignatureValidationStage extends AbstractIteratingStage<Element> {
+public class XMLSignatureValidationStage extends AbstractStage<Element> {
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(XMLSignatureValidationStage.class);
/** Whether Elements are required to be signed. */
+ @GuardedBy("this")
private boolean signatureRequired = true;
/** Whether the signature on a Elements is required to be valid. Default value: <code>true</code> */
+ @GuardedBy("this")
private boolean validSignatureRequired = true;
/** Certificate whose public key is used to verify the Element signature. */
+ @Nullable @GuardedBy("this")
private Certificate verificationCertificate;
/** Public key used to verify the Element signature. */
+ @NonnullAfterInit @GuardedBy("this")
private PublicKey verificationKey;
/** Set of blacklisted digest URIs. Default value: empty set. */
- @Nonnull @NonnullElements private Set<String> blacklistedDigests = Set.of();
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
+ private Set<String> blacklistedDigests = Set.of();
/** Set of blacklisted signature method URIs. Default value: empty set. */
- @Nonnull @NonnullElements private Set<String> blacklistedSignatureMethods = Set.of();
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
+ private Set<String> blacklistedSignatureMethods = Set.of();
/** Option to determine whether empty references are to be permitted. Default value: <code>true</code>. */
+ @GuardedBy("this")
private boolean permittingEmptyReferences = true;
-
- /** Validator used for all signatures validated by this stage instance. */
- private XMLSignatureValidator validator;
/**
* Gets whether the Element is required to be signed.
*
* @return whether the Element is required to be signed
*/
- public boolean isSignatureRequired() {
+ public final synchronized boolean isSignatureRequired() {
return signatureRequired;
}
@@ -113,7 +119,7 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
*
* @return whether the signature on a Element element is required to be valid
*/
- public boolean isValidSignatureRequired() {
+ public final synchronized boolean isValidSignatureRequired() {
return validSignatureRequired;
}
@@ -128,43 +134,43 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
}
/**
- * Gets the key used to verify the signature.
+ * Gets the certificate whose public key is used to verify the signed Element.
*
- * @return key used to verify the signature
+ * @return certificate whose public key is used to verify the signed Element
*/
- @Nullable public PublicKey getVerificationKey() {
- return verificationKey;
+ @Nullable public final synchronized Certificate getVerificationCertificate() {
+ return verificationCertificate;
}
/**
- * Sets the key used to verify the signature.
+ * Set the key, included in a certificate, used to verify the signature. This method will also set
+ * {@link #verificationKey} with the public key of the certificate.
*
- * @param key key used to verify the signature
+ * @param certificate certificate containing the key used to verify the signature
*/
- public synchronized void setVerificationKey(@Nonnull final PublicKey key) {
+ public synchronized void setVerificationCertificate(@Nonnull final Certificate certificate) {
throwSetterPreconditionExceptions();
- verificationKey = Constraint.isNotNull(key, "Public key can not be null");
+ verificationCertificate = Constraint.isNotNull(certificate, "Certificate can not be null");
+ verificationKey = verificationCertificate.getPublicKey();
}
/**
- * Gets the certificate whose public key is used to verify the signed Element.
+ * Gets the key used to verify the signature.
*
- * @return certificate whose public key is used to verify the signed Element
+ * @return key used to verify the signature
*/
- @Nullable public Certificate getVerificationCertificate() {
- return verificationCertificate;
+ @NonnullAfterInit public final synchronized PublicKey getVerificationKey() {
+ return verificationKey;
}
/**
- * Set the key, included in a certificate, used to verify the signature. This method will also set
- * {@link #verificationKey} with the public key of the certificate.
+ * Sets the key used to verify the signature.
*
- * @param certificate certificate containing the key used to verify the signature
+ * @param key key used to verify the signature
*/
- public synchronized void setVerificationCertificate(@Nonnull final Certificate certificate) {
+ public synchronized void setVerificationKey(@Nonnull final PublicKey key) {
throwSetterPreconditionExceptions();
- verificationCertificate = Constraint.isNotNull(certificate, "Certificate can not be null");
- verificationKey = verificationCertificate.getPublicKey();
+ verificationKey = Constraint.isNotNull(key, "Public key can not be null");
}
/**
@@ -172,7 +178,8 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
*
* @param identifiers collection of identifiers to be blacklisted
*/
- public void setBlacklistedDigests(@Nonnull @NonnullElements @Unmodifiable final Collection<String> identifiers) {
+ public synchronized void setBlacklistedDigests(
+ @Nonnull @NonnullElements @Unmodifiable final Collection<String> identifiers) {
throwSetterPreconditionExceptions();
blacklistedDigests = Set.copyOf(identifiers);
}
@@ -182,7 +189,7 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
*
* @return the set of blacklisted digest algorithm identifiers
*/
- @Nonnull @NonnullElements @Unmodifiable public Set<String> getBlacklistedDigests() {
+ @Nonnull @NonnullElements @Unmodifiable public final synchronized Set<String> getBlacklistedDigests() {
return blacklistedDigests;
}
@@ -191,7 +198,7 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
*
* @param identifiers collection of identifiers to be blacklisted
*/
- public void setBlacklistedSignatureMethods(
+ public synchronized void setBlacklistedSignatureMethods(
@Nonnull @NonnullElements @Unmodifiable final Collection<String> identifiers) {
throwSetterPreconditionExceptions();
blacklistedSignatureMethods = Set.copyOf(identifiers);
@@ -202,7 +209,7 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
*
* @return the set of blacklisted signature method identifiers
*/
- @Nonnull @NonnullElements @Unmodifiable public Set<String> getBlacklistedSignatureMethods() {
+ @Nonnull @NonnullElements @Unmodifiable public final synchronized Set<String> getBlacklistedSignatureMethods() {
return blacklistedSignatureMethods;
}
@@ -211,7 +218,7 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
*
* @return whether empty references are permitted
*/
- public boolean isPermittingEmptyReferences() {
+ public final synchronized boolean isPermittingEmptyReferences() {
return permittingEmptyReferences;
}
@@ -225,8 +232,13 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
permittingEmptyReferences = permit;
}
- @Override
- protected void doExecute(@Nonnull final Item<Element> item) throws StageProcessingException {
+ /**
+ * Validate an individual {@link Item} using the provided validator.
+ *
+ * @param item the {@link Item} to validate
+ * @param validator {@link XMLSignatureValidator} to use for the validation
+ */
+ protected void validateItem(@Nonnull final Item<Element> item, @Nonnull final XMLSignatureValidator validator) {
final Element docElement = item.unwrap();
@@ -235,7 +247,7 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
try {
signatureElement = validator.getSignatureElement(docElement);
if (signatureElement == null) {
- if (signatureRequired) {
+ if (isSignatureRequired()) {
log.debug("DOM Element was not signed and signature is required");
item.getItemMetadata().put(
new ErrorStatus(getId(), "DOM Element was not signed but signatures are required"));
@@ -260,7 +272,7 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
} catch (final ValidationException e) {
final String message = "element signature is invalid: " + e.getMessage();
log.debug("setting status: ", message);
- if (validSignatureRequired) {
+ if (isValidSignatureRequired()) {
item.getItemMetadata().put(new ErrorStatus(getId(), message));
} else {
item.getItemMetadata().put(new WarningStatus(getId(), message));
@@ -268,11 +280,22 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
}
}
+ @Override
+ protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection) {
+ // Create a single non-thread-safe validator
+ final var validator = new XMLSignatureValidator(getVerificationKey(),
+ getBlacklistedDigests(), getBlacklistedSignatureMethods(), isPermittingEmptyReferences());
+
+ // Use it to validate each item in turn
+ for (@Nonnull final var item : itemCollection) {
+ validateItem(item, validator);
+ }
+ }
+
@Override
protected void doDestroy() {
verificationCertificate = null;
verificationKey = null;
- validator = null;
blacklistedDigests = null;
blacklistedSignatureMethods = null;
@@ -283,16 +306,13 @@ public class XMLSignatureValidationStage extends AbstractIteratingStage<Element>
protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
+ Init.init();
+
if (verificationKey == null) {
throw new ComponentInitializationException("Unable to initialize " + getId()
+ ", no verification key was specified");
}
- validator = new XMLSignatureValidator(verificationKey,
- blacklistedDigests, blacklistedSignatureMethods, permittingEmptyReferences);
-
- if (!Init.isInitialized()) {
- Init.init();
- }
}
+
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XPathFilteringStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XPathFilteringStage.java
index bfcc53a..77559c8 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XPathFilteringStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XPathFilteringStage.java
@@ -22,6 +22,7 @@ import java.util.Iterator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
@@ -62,10 +63,11 @@ public class XPathFilteringStage extends AbstractStage<Element> {
private final Logger log = LoggerFactory.getLogger(XPathFilteringStage.class);
/** The XPath expression to execute on each {@link DOMElementItem}. */
+ @NonnullAfterInit @NotEmpty @GuardedBy("this")
private String xpathExpression;
/** The {@link NamespaceContext} to use in interpreting the XPath expression. */
- @Nonnull
+ @Nonnull @GuardedBy("this")
private NamespaceContext namespaceContext = new SimpleNamespaceContext();
/**
@@ -73,7 +75,7 @@ public class XPathFilteringStage extends AbstractStage<Element> {
*
* @return XPath expression to execute on each {@link DOMElementItem}
*/
- @NonnullAfterInit @NotEmpty public String getXPathExpression() {
+ @NonnullAfterInit @NotEmpty public final synchronized String getXPathExpression() {
return xpathExpression;
}
@@ -93,7 +95,7 @@ public class XPathFilteringStage extends AbstractStage<Element> {
*
* @return {@link NamespaceContext} to use in interpreting the XPath expression
*/
- @Nonnull public NamespaceContext getNamespaceContext() {
+ @Nonnull public final synchronized NamespaceContext getNamespaceContext() {
return namespaceContext;
}
@@ -116,11 +118,11 @@ public class XPathFilteringStage extends AbstractStage<Element> {
throws StageProcessingException {
final XPathFactory factory = XPathFactory.newInstance();
final XPath xpath = factory.newXPath();
- xpath.setNamespaceContext(namespaceContext);
+ xpath.setNamespaceContext(getNamespaceContext());
final XPathExpression compiledExpression;
try {
- compiledExpression = xpath.compile(xpathExpression);
+ compiledExpression = xpath.compile(getXPathExpression());
} catch (final XPathExpressionException e) {
// This should never occur, as we attempted the same operation at initialization time.
throw new StageProcessingException("error compiling XPath expression", e);
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XPathItemSelectionStrategy.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XPathItemSelectionStrategy.java
index ce3277a..26b1f3d 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XPathItemSelectionStrategy.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XPathItemSelectionStrategy.java
@@ -20,6 +20,7 @@ package net.shibboleth.metadata.dom;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
@@ -50,11 +51,9 @@ public class XPathItemSelectionStrategy implements Predicate<Item<Element>> {
* {@link XPathExpression} objects are reusable but are not thread-safe, so access to the compiled expression must
* be protected.
*/
+ @Nonnull @GuardedBy("this")
private final XPathExpression compiledExpression;
- /** The {@link NamespaceContext} to use in interpreting the XPath expression. */
- private final NamespaceContext namespaceContext;
-
/**
* Constructor.
*
@@ -64,6 +63,7 @@ public class XPathItemSelectionStrategy implements Predicate<Item<Element>> {
*/
public XPathItemSelectionStrategy(@Nonnull @NotEmpty final String expression,
@Nonnull final NamespaceContext context) throws XPathExpressionException {
+ final NamespaceContext namespaceContext;
if (context == null) {
namespaceContext = new SimpleNamespaceContext();
} else {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XSLValidationStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XSLValidationStage.java
index 203253d..0fcb020 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XSLValidationStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XSLValidationStage.java
@@ -42,8 +42,8 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElemen
@ThreadSafe
public class XSLValidationStage extends AbstractXSLProcessingStage {
- /** {@inheritDoc} */
- @Override protected void executeTransformer(@Nonnull final Transformer transformer,
+ @Override
+ protected void executeTransformer(@Nonnull final Transformer transformer,
@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection) throws StageProcessingException,
TransformerConfigurationException {
@@ -56,4 +56,4 @@ public class XSLValidationStage extends AbstractXSLProcessingStage {
throw new StageProcessingException("Unable to validate DOM Element", e);
}
}
-}
\ No newline at end of file
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ds/X509ValidationStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ds/X509ValidationStage.java
index efd10ed..98e53d8 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ds/X509ValidationStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ds/X509ValidationStage.java
@@ -25,6 +25,8 @@ import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
import javax.xml.crypto.dsig.XMLSignature;
import org.w3c.dom.Element;
@@ -33,6 +35,7 @@ import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.dom.AbstractDOMValidationStage;
import net.shibboleth.metadata.dom.SimpleDOMTraversalContext;
import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.codec.Base64Support;
import net.shibboleth.utilities.java.support.codec.DecodingException;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -44,6 +47,7 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
*
* @since 0.9.0
*/
+ at ThreadSafe
public class X509ValidationStage extends AbstractDOMValidationStage<X509Certificate, X509ValidationStage.Context> {
/** Context class for this kind of traversal. */
@@ -91,6 +95,7 @@ public class X509ValidationStage extends AbstractDOMValidationStage<X509Certific
}
/** Certificate factory to use to convert to X.509 certificates. */
+ @NonnullAfterInit @GuardedBy("this")
private CertificateFactory factory;
@Override
@@ -99,7 +104,7 @@ public class X509ValidationStage extends AbstractDOMValidationStage<X509Certific
}
@Override
- protected boolean applicable(@Nonnull final Element e) {
+ protected boolean applicable(@Nonnull final Element e, @Nonnull final Context context) {
return XMLSignature.XMLNS.equals(e.getNamespaceURI()) &&
"X509Certificate".equals(e.getLocalName());
}
@@ -110,8 +115,12 @@ public class X509ValidationStage extends AbstractDOMValidationStage<X509Certific
final String text = element.getTextContent();
try {
final byte[] data = Base64Support.decode(text);
- final X509Certificate cert =
- (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(data));
+
+ final X509Certificate cert;
+ synchronized (this) {
+ cert = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(data));
+ }
+
// only process each certificate once per item
if (!context.haveSeen(cert)) {
context.add(cert);
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/impl/XMLSignatureSigner.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/impl/XMLSignatureSigner.java
new file mode 100644
index 0000000..88df0d6
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/impl/XMLSignatureSigner.java
@@ -0,0 +1,545 @@
+/*
+ * 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.metadata.dom.impl;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.security.auth.x500.X500Principal;
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.SignatureMethod;
+import javax.xml.crypto.dsig.SignedInfo;
+import javax.xml.crypto.dsig.Transform;
+import javax.xml.crypto.dsig.XMLSignContext;
+import javax.xml.crypto.dsig.XMLSignature;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.dom.DOMSignContext;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
+import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
+import javax.xml.crypto.dsig.spec.DigestMethodParameterSpec;
+import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec;
+import javax.xml.crypto.dsig.spec.TransformParameterSpec;
+import javax.xml.namespace.QName;
+
+import org.slf4j.Logger;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.dom.XMLSignatureSigningStage;
+import net.shibboleth.metadata.dom.ds.XMLDSIGSupport;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+import net.shibboleth.utilities.java.support.xml.QNameSupport;
+
+/**
+ * A class that encapsulates the various stages in signing an XML document.
+ *
+ * <p>
+ * This implementation is not thread safe, but can be used sequentially to sign
+ * a number of different documents.
+ * </p>
+ *
+ * <p>
+ * As an adjunct to an {@link XMLSignatureSigningStage} instance, it snapshots its
+ * parameters during construction so that they can be used without synchronization
+ * during operation.
+ * </p>
+ */
+ at NotThreadSafe
+public class XMLSignatureSigner {
+
+ /* Fields passed from the calling class. */
+
+ /** Logger provided by the calling class. */
+ @Nonnull private final Logger log;
+
+ /** Private key used to sign data. */
+ @Nonnull private final PrivateKey privKey;
+
+ /** Public key associated with the given private key. */
+ @Nullable private final PublicKey publicKey;
+
+ /** Inclusive prefix list used with exclusive canonicalization. */
+ @Nonnull @NonnullElements @Unmodifiable
+ private final List<String> inclusivePrefixList;
+
+ /**
+ * Names of attributes to treat as ID attributes for signature referencing. Default value: list containing the
+ * non-namespace-qualified attributes 'ID', 'Id', 'id'
+ */
+ @Nonnull @NonnullElements @Unmodifiable
+ private final List<QName> idAttributeNames;
+
+ /** Explicit names to associate with the given signing key. */
+ @Nonnull @NonnullElements @Unmodifiable
+ private final List<String> keyNames;
+
+ /** Certificate chain, with end entity certificate as element 0, to be included with the signature. */
+ @Nonnull @NonnullElements @Unmodifiable
+ private final List<X509Certificate> certificates;
+
+ /** CRLs to be included with the signature. */
+ @Nonnull @NonnullElements @Unmodifiable
+ private final List<X509CRL> crls;
+
+ /** Whether key names should be included in the signature's KeyInfo. */
+ private final boolean includeKeyNames;
+
+ /** Whether the public key should be included in the signature's KeyInfo. */
+ private final boolean includeKeyValue;
+
+ /** Whether the end-entity certificate's subject name should be included in the signature's KeyInfo. */
+ private final boolean includeX509SubjectName;
+
+ /** Whether the certificates chain should be included in the signature's KeyInfo. */
+ private final boolean includeX509Certificates;
+
+ /** Whether the CRLs should be included in the signature's KeyInfo. */
+ private final boolean includeX509Crls;
+
+ /** Whether the end-entity certificate's issuer and serial number should be included in the signature's KeyInfo. */
+ private final boolean includeX509IssuerSerial;
+
+ /** Whether to debug digest operations by logging the pre-digest data stream. */
+ private final boolean debugPreDigest;
+
+ /** Whether to remove CR characters from generated signatures. */
+ private final boolean removingCRsFromSignature;
+
+ /* Fields derived from the calling class. */
+
+ /**
+ * Canonicalization algorithm to use. This is determined from the {@code c14nExclusive} and
+ * {@code c14nWithComments} properties.
+ */
+ @Nonnull private final String c14nAlgo;
+
+ /** Signature algorithm used. */
+ @Nonnull private final String sigAlgo;
+
+ /** Digest algorithm used. */
+ @Nonnull private final String digestAlgo;
+
+ /* Fields created during construction. */
+
+ /** Factory used to create XML signature objects. */
+ @Nonnull private final XMLSignatureFactory xmlSigFactory = XMLSignatureFactory.getInstance();
+
+ /** Factory used to create KeyInfo objects. */
+ @Nonnull private KeyInfoFactory keyInfoFactory = xmlSigFactory.getKeyInfoFactory();
+
+ /**
+ * Constructor.
+ *
+ * @param stage the {@link XMLSignatureSigningStage} we are acting as an adjunct to
+ * @param logger the logger for the calling stage
+ */
+ public XMLSignatureSigner(@Nonnull final XMLSignatureSigningStage stage, @Nonnull final Logger logger) {
+ log = logger;
+
+ /*
+ * Snapshot parameters while holding the lock on the stage, reducing the
+ * cost of the synchronization on the individual getters.
+ */
+ synchronized (stage) {
+ /* Whether to use exclusive canonicalization */
+ final boolean c14nExclusive = stage.isC14nExclusive();
+
+ /* Whether to include comments in the canonicalized data. */
+ final boolean c14nWithComments = stage.isC14nWithComments();
+
+ if (c14nExclusive) {
+ if (c14nWithComments) {
+ c14nAlgo = CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS;
+ } else {
+ c14nAlgo = CanonicalizationMethod.EXCLUSIVE;
+ }
+ } else {
+ if (c14nWithComments) {
+ c14nAlgo = CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS;
+ } else {
+ c14nAlgo = CanonicalizationMethod.INCLUSIVE;
+ }
+ }
+
+ switch (stage.getShaVariant()) {
+ case SHA1:
+ sigAlgo = SignatureMethod.RSA_SHA1;
+ digestAlgo = DigestMethod.SHA1;
+ break;
+
+ case SHA384:
+ sigAlgo = SignatureMethod.RSA_SHA384;
+ digestAlgo = DigestMethod.SHA384;
+ break;
+
+ case SHA512:
+ sigAlgo = SignatureMethod.RSA_SHA512;
+ digestAlgo = DigestMethod.SHA512;
+ break;
+
+ case SHA256:
+ default:
+ sigAlgo = SignatureMethod.RSA_SHA256;
+ digestAlgo = DigestMethod.SHA256;
+ break;
+ }
+
+ privKey = stage.getPrivateKey();
+ publicKey = stage.getPublicKey();
+ inclusivePrefixList = stage.getInclusivePrefixList();
+ idAttributeNames = stage.getIdAttributeNames();
+ keyNames = stage.getKeyNames();
+ certificates = stage.getCertificates();
+ crls = stage.getCrls();
+ includeKeyNames = stage.isIncludeKeyNames();
+ includeKeyValue = stage.isIncludeKeyValue();
+ includeX509SubjectName = stage.isIncludeX509SubjectName();
+ includeX509Certificates = stage.isIncludeX509Certificates();
+ includeX509Crls = stage.isIncludeX509Crls();
+ includeX509IssuerSerial = stage.isIncludeX509IssuerSerial();
+ debugPreDigest = stage.isDebugPreDigest() & log.isDebugEnabled();
+ removingCRsFromSignature = stage.isRemovingCRsFromSignature();
+ }
+
+ }
+
+ /**
+ * Sign an individual item.
+ *
+ * @param item the {@link Item} to sign
+ * @throws StageProcessingException if an error occurs preventing the signature from being made
+ */
+ public void sign(@Nonnull final Item<Element> item) throws StageProcessingException {
+ final Element element = item.unwrap();
+ final var signature = xmlSigFactory.newXMLSignature(buildSignedInfo(element), buildKeyInfo());
+
+ final XMLSignContext context = new DOMSignContext(privKey, element, element.getFirstChild());
+
+ // Enable caching reference values if required for debugging.
+ if (debugPreDigest) {
+ context.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
+ }
+
+ // Perform the signature operation
+ try {
+ signature.sign(context);
+ } catch (final Exception e) {
+ throw new StageProcessingException("Unable to create signature for element", e);
+ }
+
+ // Log the pre-digest data for debugging
+ try {
+ if (debugPreDigest) {
+ final Reference ref = signature.getSignedInfo().getReferences().get(0);
+ final String preDigest = new String(ref.getDigestInputStream().readAllBytes(), StandardCharsets.UTF_8);
+ log.debug("pre digest: {}", preDigest);
+ }
+ } catch (final IOException e) {
+ throw new StageProcessingException("Unable to log pre-digest data", e);
+ }
+
+ // Remove any CRs from selected signature elements.
+ if (removingCRsFromSignature) {
+ final Element signatureElement = ElementSupport.getFirstChildElement(element,
+ XMLDSIGSupport.SIGNATURE_NAME);
+ removeCRsFromNamedChildren(signatureElement, "SignatureValue");
+ removeCRsFromNamedChildren(signatureElement, "X509Certificate");
+ }
+ }
+
+ /**
+ * Gets the descriptor of signed content.
+ *
+ * @param target the element that will be signed
+ *
+ * @return signed content descriptor
+ *
+ * @throws StageProcessingException thrown if there is a problem creating the signed content descriptor
+ */
+ @Nonnull protected SignedInfo buildSignedInfo(@Nonnull final Element target) throws StageProcessingException {
+ C14NMethodParameterSpec c14nMethodSpec = null;
+ if (c14nAlgo.startsWith(CanonicalizationMethod.EXCLUSIVE) && inclusivePrefixList != null
+ && !inclusivePrefixList.isEmpty()) {
+ c14nMethodSpec = new ExcC14NParameterSpec(inclusivePrefixList);
+ }
+
+ final CanonicalizationMethod c14nMethod;
+ try {
+ c14nMethod = xmlSigFactory.newCanonicalizationMethod(c14nAlgo, c14nMethodSpec);
+ } catch (final Exception e) {
+ final String errMsg = "Unable to create transform " + c14nAlgo;
+ throw new StageProcessingException(errMsg, e);
+ }
+
+ final SignatureMethod sigMethod;
+ try {
+ sigMethod = xmlSigFactory.newSignatureMethod(sigAlgo, null);
+ } catch (final Exception e) {
+ final String errMsg = "Unable to create signature method " + sigAlgo;
+ throw new StageProcessingException(errMsg, e);
+ }
+
+ final List<Reference> refs = Collections.singletonList(buildSignatureReference(target));
+
+ return xmlSigFactory.newSignedInfo(c14nMethod, sigMethod, refs);
+ }
+
+ /**
+ * Builds the references to the signed content.
+ *
+ * @param target the element to be signed
+ *
+ * @return reference to signed content
+ *
+ * @throws StageProcessingException thrown if there is a problem creating the reference to the element
+ */
+ @Nonnull protected Reference buildSignatureReference(@Nonnull final Element target)
+ throws StageProcessingException {
+ final String id = getElementId(target);
+ final String refUri;
+ if (id == null) {
+ refUri = "";
+ } else {
+ refUri = "#" + id;
+ }
+
+ DigestMethod digestMethod = null;
+ try {
+ final DigestMethodParameterSpec digestMethodSpec = null;
+ digestMethod = xmlSigFactory.newDigestMethod(digestAlgo, digestMethodSpec);
+ } catch (final Exception e) {
+ final String errMsg = "Unable to create digest method " + digestAlgo;
+ throw new StageProcessingException(errMsg, e);
+ }
+
+ TransformParameterSpec transformSpec;
+ final ArrayList<Transform> transforms = new ArrayList<>();
+
+ try {
+ transformSpec = null;
+ transforms.add(xmlSigFactory.newTransform(Transform.ENVELOPED, transformSpec));
+ } catch (final Exception e) {
+ final String errMsg = "Unable to create transform " + Transform.ENVELOPED;
+ throw new StageProcessingException(errMsg, e);
+ }
+
+ try {
+ if (c14nAlgo.startsWith(CanonicalizationMethod.EXCLUSIVE) && inclusivePrefixList != null
+ && !inclusivePrefixList.isEmpty()) {
+ transformSpec = new ExcC14NParameterSpec(inclusivePrefixList);
+ }
+ transforms.add(xmlSigFactory.newTransform(c14nAlgo, transformSpec));
+ } catch (final Exception e) {
+ final String errMsg = "Unable to create transform " + c14nAlgo;
+ throw new StageProcessingException(errMsg, e);
+ }
+
+ return xmlSigFactory.newReference(refUri, digestMethod, transforms, null, null);
+ }
+
+ /**
+ * Determines the ID for the element to be signed. To determine the ID first, all the element attributes are
+ * inspected, if one matches the provided {@link #idAttributeNames} then the value of the attribute is used as the
+ * ID value. If no ID attribute names are given, or none of the given ones match, and one or more of the attributes
+ * is marked as an ID attribute (i.e. {@link Attr#isId()} is true), then the value of one of those attributes is
+ * used.
+ *
+ * @param target an element to be referenced by the signature
+ *
+ * @return the ID value for the element, or null
+ */
+ @Nullable protected String getElementId(@Nonnull final Element target) {
+ final NamedNodeMap attributes = target.getAttributes();
+ if (attributes == null || attributes.getLength() < 1) {
+ return null;
+ }
+
+ if (idAttributeNames != null && !idAttributeNames.isEmpty()) {
+ for (int i = 0; i < attributes.getLength(); i++) {
+ final Attr attribute = (Attr) attributes.item(i);
+ if (idAttributeNames.contains(QNameSupport.getNodeQName(attribute))) {
+ // mark the attribute as an ID attribute so that it can be referenced by the signature
+ target.setIdAttributeNode(attribute, true);
+ final String value = StringSupport.trimOrNull(attribute.getValue());
+ if (value != null) {
+ return value;
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < attributes.getLength(); i++) {
+ final Attr attribute = (Attr) attributes.item(i);
+ if (attribute.isId()) {
+ final String value = StringSupport.trimOrNull(attribute.getValue());
+ if (value != null) {
+ return value;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Builds the KeyInfo element to be included in the signature.
+ *
+ * @return KeyInfo element to be included in the signature
+ *
+ * @throws StageProcessingException thrown if there is a problem creating the KeyInfo descriptor
+ */
+ @Nullable protected KeyInfo buildKeyInfo() throws StageProcessingException {
+ final List<XMLStructure> keyInfoItems = new ArrayList<>();
+
+ addKeyNames(keyInfoItems);
+ addKeyValue(keyInfoItems);
+ addX509Data(keyInfoItems);
+
+ if (keyInfoItems.isEmpty()) {
+ return null;
+ }
+ return keyInfoFactory.newKeyInfo(keyInfoItems);
+ }
+
+ /**
+ * Adds key names to the KeyInfo, if key names are to be included.
+ *
+ * @param keyInfoItems collector for KeyInfo children
+ *
+ * @throws StageProcessingException thrown if there is a problem creating the KeyName content
+ */
+ protected void addKeyNames(@Nonnull @NonnullElements @Live final List<XMLStructure> keyInfoItems)
+ throws StageProcessingException {
+ if (!includeKeyNames) {
+ return;
+ }
+
+ if (keyNames != null && !keyNames.isEmpty()) {
+ for (final String name : keyNames) {
+ keyInfoItems.add(keyInfoFactory.newKeyName(name));
+ }
+ }
+ }
+
+ /**
+ * Adds raw key values to the KeyInfo if key values are to be included.
+ *
+ * @param keyInfoItems collector for KeyInfo children
+ *
+ * @throws StageProcessingException thrown if there is a problem creating the KeyValue content
+ */
+ protected void addKeyValue(@Nonnull @NonnullElements @Live final List<XMLStructure> keyInfoItems)
+ throws StageProcessingException {
+ if (!includeKeyValue) {
+ return;
+ }
+
+ PublicKey key = publicKey;
+
+ // If we have no explicit public key, we can extract one from a certificate, if we have one.
+ if (key == null && !certificates.isEmpty()) {
+ key = certificates.get(0).getPublicKey();
+ }
+
+ if (key != null) {
+ try {
+ keyInfoItems.add(keyInfoFactory.newKeyValue(key));
+ } catch (final Exception e) {
+ throw new StageProcessingException("Unable to create KeyValue", e);
+ }
+ }
+ }
+
+ /**
+ * Adds X509 data (subject names, certificates, CRLs, and Issuer/Serial) set to be included, into the key info.
+ *
+ * @param keyInfoItems collector for KeyInfo children
+ */
+ protected void addX509Data(@Nonnull @NonnullElements @Live final List<XMLStructure> keyInfoItems) {
+ final List<Object> x509Data = new ArrayList<>();
+
+ if (certificates != null && !certificates.isEmpty()) {
+ final X509Certificate endEntityCert = certificates.get(0);
+
+ if (includeX509SubjectName) {
+ final X500Principal subjectDn = endEntityCert.getSubjectX500Principal();
+ x509Data.add(subjectDn.getName(X500Principal.RFC2253));
+ }
+
+ if (includeX509Certificates) {
+ x509Data.addAll(certificates);
+ }
+
+ if (includeX509IssuerSerial) {
+ final X500Principal issuerDn = endEntityCert.getIssuerX500Principal();
+ final BigInteger serialNumber = endEntityCert.getSerialNumber();
+ x509Data.add(keyInfoFactory.newX509IssuerSerial(issuerDn.getName(X500Principal.RFC2253), serialNumber));
+ }
+ }
+
+ if (includeX509Crls && crls != null && !crls.isEmpty()) {
+ x509Data.add(crls);
+ }
+
+ if (!x509Data.isEmpty()) {
+ keyInfoItems.add(keyInfoFactory.newX509Data(x509Data));
+ }
+ }
+
+ /**
+ * Remove any CRs from the text content of named child elements.
+ *
+ * @param signature The <code>Signature</code> element to process.
+ * @param elementName The element name within the XML DSIG namespace to look for.
+ */
+ private void removeCRsFromNamedChildren(@Nonnull final Element signature, @Nonnull final String elementName) {
+ final NodeList nodes = signature.getElementsByTagNameNS(XMLSignature.XMLNS, elementName);
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node node = nodes.item(i);
+ final String text = node.getTextContent();
+ if (text.indexOf('\r') >= 0) {
+ node.setTextContent(text.replaceAll("\\r", ""));
+ }
+ }
+ }
+
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureValidator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/impl/XMLSignatureValidator.java
similarity index 98%
rename from aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureValidator.java
rename to aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/impl/XMLSignatureValidator.java
index a2e903a..98e33e3 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/XMLSignatureValidator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/impl/XMLSignatureValidator.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package net.shibboleth.metadata.dom;
+package net.shibboleth.metadata.dom.impl;
import java.security.PublicKey;
import java.util.Collections;
@@ -53,13 +53,17 @@ import org.w3c.dom.Node;
/**
* A class that encapsulates the various stages in validation of XML signatures as methods.
*
+ * <p>
* Failures, and the reason for the failure, are represented by exceptions.
+ * </p>
*
+ * <p>
* This code largely derives from XmlSecTool V1.2, with the abrupt program termination
* method of reporting issues replaced by throwing an exception. The intention is
* that this code might be reintegrated with XmlSecTool in a later release.
+ * </p>
*/
-final class XMLSignatureValidator {
+public final class XMLSignatureValidator {
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(XMLSignatureValidator.class);
@@ -84,7 +88,7 @@ final class XMLSignatureValidator {
* @param blacklistSignatureMethods set of blacklisted signature method URIs, or <code>null</code>
* @param emptyRefPermitted true if empty references are permitted
*/
- XMLSignatureValidator(@Nonnull final PublicKey key, @Nullable final Set<String> blacklistDigests,
+ public XMLSignatureValidator(@Nonnull final PublicKey key, @Nullable final Set<String> blacklistDigests,
@Nullable final Set<String> blacklistSignatureMethods,
final boolean emptyRefPermitted) {
Constraint.isNotNull(key, "public key can not be null");
@@ -246,7 +250,6 @@ final class XMLSignatureValidator {
final String alg = ref.getMessageDigestAlgorithm().getAlgorithmURI();
log.debug("blacklist checking digest {}", alg);
if (blacklistedDigests.contains(alg)) {
- log.error("Digest algorithm {} is blacklisted", alg);
throw new ValidationException("Digest algorithm " + alg + " is blacklisted");
}
} catch (final XMLSignatureException e) {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemIdentificationStrategy.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/impl/package-info.java
similarity index 67%
copy from aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemIdentificationStrategy.java
copy to aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/impl/package-info.java
index 1a3de26..bca8461 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/ItemIdentificationStrategy.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/impl/package-info.java
@@ -15,20 +15,7 @@
* limitations under the License.
*/
-package net.shibboleth.metadata;
-
-import javax.annotation.Nonnull;
-
-/** Strategy for determining a displayable identifier for a given {@link Item}. */
-public interface ItemIdentificationStrategy {
-
- /**
- * Gets an identifier for the item.
- *
- * @param item the item
- *
- * @return the identifier, never null
- */
- String getItemIdentifier(@Nonnull final Item<?> item);
-
-}
+/**
+ * Implementation classes related to processing DOM based metadata.
+ */
+package net.shibboleth.metadata.dom.impl;
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AbstractSAMLTraversalStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AbstractSAMLTraversalStage.java
index 28dcffd..e0c9404 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AbstractSAMLTraversalStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AbstractSAMLTraversalStage.java
@@ -77,11 +77,10 @@ public abstract class AbstractSAMLTraversalStage <C extends DOMTraversalContext>
final Attr id = entity.getAttributeNode("ID");
if (id != null) {
return id.getTextContent() + ": ";
- } else {
- final Attr entityID = entity.getAttributeNode("entityID");
- if (entityID != null) {
- return entityID.getTextContent() + ": ";
- }
+ }
+ final Attr entityID = entity.getAttributeNode("entityID");
+ if (entityID != null) {
+ return entityID.getTextContent() + ": ";
}
}
return "";
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMaker.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMaker.java
index 2b1973b..ea9e3aa 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMaker.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMaker.java
@@ -18,7 +18,7 @@
package net.shibboleth.metadata.dom.saml;
import javax.annotation.Nonnull;
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
import org.w3c.dom.Element;
@@ -32,7 +32,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
*
* @since 0.10.0
*/
- at ThreadSafe
+ at Immutable
public class AttributeElementMaker extends ElementMaker {
/** Value for the <code>Name</code> XML attribute. */
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcher.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcher.java
index ab8d425..5accfa3 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcher.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcher.java
@@ -18,6 +18,7 @@
package net.shibboleth.metadata.dom.saml;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
import org.w3c.dom.Element;
@@ -31,6 +32,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
*
* @since 0.10.0
*/
+ at Immutable
public class AttributeElementMatcher extends ElementMatcher {
/** <code>NameFormat</code> attribute value to match. */
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMaker.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMaker.java
index 9439f26..7758db7 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMaker.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMaker.java
@@ -18,7 +18,7 @@
package net.shibboleth.metadata.dom.saml;
import javax.annotation.Nonnull;
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
import org.w3c.dom.Element;
@@ -30,7 +30,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
* A class for constructing SAML <code>AttributeValue</code> elements
* for use with the {@link Container} system.
*/
- at ThreadSafe
+ at Immutable
public class AttributeValueElementMaker extends ElementMaker {
/** Value for the attribute. */
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcher.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcher.java
index d8551c0..3ccbac9 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcher.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcher.java
@@ -18,6 +18,7 @@
package net.shibboleth.metadata.dom.saml;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
import org.w3c.dom.Element;
@@ -29,6 +30,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
* text values,
* for use with the {@link net.shibboleth.metadata.dom.Container} system.
*/
+ at Immutable
public class AttributeValueElementMatcher extends ElementMatcher {
/** <code>Attribute</code> value to match. */
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/ContactPersonFilterStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/ContactPersonFilterStage.java
index a7612c4..86827d0 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/ContactPersonFilterStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/ContactPersonFilterStage.java
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
@@ -71,19 +72,19 @@ public class ContactPersonFilterStage extends AbstractIteratingStage<Element> {
/** 'other' person type constant. */
public static final String OTHER = "other";
- /** Class logger. */
- private final Logger log = LoggerFactory.getLogger(ContactPersonFilterStage.class);
-
/** Allowed contact person types. */
@Nonnull @NonnullElements @Unmodifiable
- private final Set<String> allowedTypes = Set.of(TECHNICAL, SUPPORT, ADMINISTRATIVE, BILLING, OTHER);
+ private static final Set<String> ALLOWED_TYPES = Set.of(TECHNICAL, SUPPORT, ADMINISTRATIVE, BILLING, OTHER);
+
+ /** Class logger. */
+ private final Logger log = LoggerFactory.getLogger(ContactPersonFilterStage.class);
/** Person types which are white/black listed depending on the value of {@link #whitelistingTypes}. */
- @Nonnull @NonnullElements @Unmodifiable
- private Set<String> designatedTypes = Set.copyOf(allowedTypes);
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
+ private Set<String> designatedTypes = Set.copyOf(ALLOWED_TYPES);
/** Whether {@link #designatedTypes} should be considered a whitelist. Default value: true */
- private boolean whitelistingTypes = true;
+ @GuardedBy("this") private boolean whitelistingTypes = true;
/**
* Gets the list of designated person types.
@@ -91,7 +92,7 @@ public class ContactPersonFilterStage extends AbstractIteratingStage<Element> {
* @return list of designated person types
*/
@Nonnull @NonnullElements @Unmodifiable
- public Collection<String> getDesignatedTypes() {
+ public final synchronized Collection<String> getDesignatedTypes() {
return designatedTypes;
}
@@ -106,7 +107,7 @@ public class ContactPersonFilterStage extends AbstractIteratingStage<Element> {
final Set<String> checkedTypes = new HashSet<>();
for (final String type : types) {
- if (allowedTypes.contains(type)) {
+ if (ALLOWED_TYPES.contains(type)) {
checkedTypes.add(type);
} else {
log.debug("Stage {}: {} is not an allowed contact person type and so has been ignored", getId(),
@@ -122,7 +123,7 @@ public class ContactPersonFilterStage extends AbstractIteratingStage<Element> {
*
* @return true if the designated roles should be considered a whitelist, false otherwise
*/
- public boolean isWhitelistingTypes() {
+ public final synchronized boolean isWhitelistingTypes() {
return whitelistingTypes;
}
@@ -205,18 +206,18 @@ public class ContactPersonFilterStage extends AbstractIteratingStage<Element> {
return false;
}
- if (!allowedTypes.contains(type)) {
+ if (!ALLOWED_TYPES.contains(type)) {
log.debug("The following ContactPerson contained an invalid contactType, it will be removed:\n{}",
SerializeSupport.prettyPrintXML(contactPerson));
return false;
}
- if (isWhitelistingTypes() && designatedTypes.contains(type)) {
+ if (isWhitelistingTypes() && getDesignatedTypes().contains(type)) {
// if we're whitelisting types and the person's type appears in the designated type list, keep them
return true;
}
- if (!isWhitelistingTypes() && !designatedTypes.contains(type)) {
+ if (!isWhitelistingTypes() && !getDesignatedTypes().contains(type)) {
// if we're blacklisting types and the person's type does not appear in the designated type list, keep them
return true;
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer.java
index d677c7a..a029cd7 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer.java
@@ -26,9 +26,9 @@ import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.json.Json;
import javax.json.stream.JsonGenerator;
-import javax.json.stream.JsonGeneratorFactory;
import javax.xml.namespace.QName;
import org.w3c.dom.Element;
@@ -38,9 +38,7 @@ import net.shibboleth.metadata.ItemCollectionSerializer;
import net.shibboleth.metadata.dom.saml.mdattr.MDAttrSupport;
import net.shibboleth.metadata.dom.saml.mdui.MDUISupport;
import net.shibboleth.metadata.pipeline.impl.BaseInitializableComponent;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.xml.ElementSupport;
/**
@@ -52,12 +50,8 @@ import net.shibboleth.utilities.java.support.xml.ElementSupport;
public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
implements ItemCollectionSerializer<Element> {
- /** Configured JSON generator factory. */
- @NonnullAfterInit
- private JsonGeneratorFactory factory;
-
/** Whether to pretty-print the resulting JSON. Default: <code>false</code> */
- private boolean prettyPrinting;
+ @GuardedBy("this") private boolean prettyPrinting;
/**
* Whether to include legacy display names if none are found in the
@@ -65,17 +59,17 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
*
* Default: <code>false</code>
*/
- private boolean includingLegacyDisplayNames;
+ @GuardedBy("this") private boolean includingLegacyDisplayNames;
/** Whether to include entity attributes. Default: <code>false</code> */
- private boolean includingEntityAttributes;
+ @GuardedBy("this") private boolean includingEntityAttributes;
/**
* Returns whether output is being pretty-printed.
*
* @return whether output is being pretty-printed
*/
- public boolean isPrettyPrinting() {
+ public final synchronized boolean isPrettyPrinting() {
return prettyPrinting;
}
@@ -84,7 +78,7 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
*
* @param pretty whether to pretty-print the output
*/
- public void setPrettyPrinting(final boolean pretty) {
+ public synchronized void setPrettyPrinting(final boolean pretty) {
prettyPrinting = pretty;
}
@@ -93,7 +87,7 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
*
* @return whether output includes legacy display names
*/
- public boolean isIncludingLegacyDisplayNames() {
+ public final synchronized boolean isIncludingLegacyDisplayNames() {
return includingLegacyDisplayNames;
}
@@ -102,7 +96,7 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
*
* @param includeLegacyDisplayNames whether to include legacy display names
*/
- public void setIncludingLegacyDisplayNames(final boolean includeLegacyDisplayNames) {
+ public synchronized void setIncludingLegacyDisplayNames(final boolean includeLegacyDisplayNames) {
includingLegacyDisplayNames = includeLegacyDisplayNames;
}
@@ -111,7 +105,7 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
*
* @return whether output includes entity attributes
*/
- public boolean isIncludingEntityAttributes() {
+ public final synchronized boolean isIncludingEntityAttributes() {
return includingEntityAttributes;
}
@@ -120,7 +114,7 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
*
* @param includeEntityAttributes whether to include entity attributes.
*/
- public void setIncludingEntityAttributes(final boolean includeEntityAttributes) {
+ public synchronized void setIncludingEntityAttributes(final boolean includeEntityAttributes) {
includingEntityAttributes = includeEntityAttributes;
}
@@ -262,7 +256,7 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
}
// Attempt to find display names elsewhere
- if (includingLegacyDisplayNames) {
+ if (isIncludingLegacyDisplayNames()) {
final var org = ElementSupport.getFirstChildElement(entity, SAMLMetadataSupport.ORGANIZATION_NAME);
if (org != null) {
final var displayNames =
@@ -278,6 +272,11 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
public void serializeCollection(@Nonnull @NonnullElements final Collection<Item<Element>> items,
@Nonnull final OutputStream output) throws IOException {
throwComponentStateExceptions();
+ final Map<String, String> generatorConfig = new HashMap<>();
+ if (isPrettyPrinting()) {
+ generatorConfig.put(JsonGenerator.PRETTY_PRINTING, "true");
+ }
+ final var factory = Json.createGeneratorFactory(generatorConfig);
final JsonGenerator gen = factory.createGenerator(output);
gen.writeStartArray();
for (final Item<Element> item : items) {
@@ -298,7 +297,7 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
MDUISupport.PRIVACYSTATEMENTURL_NAME, "PrivacyStatementURLs");
writeLogos(gen, uiInfo);
}
- if (includingEntityAttributes) {
+ if (isIncludingEntityAttributes()) {
writeEntityAttributes(gen, entity);
}
gen.writeEnd();
@@ -309,20 +308,4 @@ public class DiscoFeedCollectionSerializer extends BaseInitializableComponent
gen.close();
}
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
- final Map<String, String> generatorConfig = new HashMap<>();
- if (prettyPrinting) {
- generatorConfig.put(JsonGenerator.PRETTY_PRINTING, "true");
- }
- factory = Json.createGeneratorFactory(generatorConfig);
- }
-
- @Override
- protected void doDestroy() {
- factory = null;
- super.doDestroy();
- }
-
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntitiesDescriptorAssemblerStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntitiesDescriptorAssemblerStage.java
index 3e2a6ba..854523b 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntitiesDescriptorAssemblerStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntitiesDescriptorAssemblerStage.java
@@ -22,6 +22,7 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.namespace.QName;
@@ -52,7 +53,7 @@ import net.shibboleth.utilities.java.support.xml.NamespaceSupport;
public class EntitiesDescriptorAssemblerStage extends AbstractStage<Element> {
/** Name of the EntitiesDescriptor's Name attribute. */
- public static final QName NAME_ATTRIB_NAME = new QName("Name");
+ private static final QName NAME_ATTRIB_NAME = new QName("Name");
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(EntitiesDescriptorAssemblerStage.class);
@@ -61,13 +62,14 @@ public class EntitiesDescriptorAssemblerStage extends AbstractStage<Element> {
* Whether attempting to turn an empty item collection, which would result in a schema-invalid childless
* EntitiesDescriptor, should be treated as processing error. Default value: <code>false</code>
*/
- private boolean noChildrenAProcessingError;
+ @GuardedBy("this") private boolean noChildrenAProcessingError;
/** Strategy used to order a collection of Items. The default strategy performs no ordering. */
- @Nonnull
- private ItemOrderingStrategy<Element> orderingStrategy = new NoOpItemOrderingStrategy<>();
+ @Nonnull @GuardedBy("this")
+ private ItemOrderingStrategy<Element> itemOrderingStrategy = new NoOpItemOrderingStrategy<>();
/** Name to use for the EntitiesDescriptor. */
+ @Nullable @GuardedBy("this")
private String descriptorName;
/**
@@ -76,7 +78,7 @@ public class EntitiesDescriptorAssemblerStage extends AbstractStage<Element> {
*
* @return whether attempting to turn an empty item collection should be treated as processing error
*/
- public boolean isNoChildrenAProcessingError() {
+ public final synchronized boolean isNoChildrenAProcessingError() {
return noChildrenAProcessingError;
}
@@ -96,8 +98,8 @@ public class EntitiesDescriptorAssemblerStage extends AbstractStage<Element> {
*
* @return strategy used to order a collection of Items
*/
- @Nonnull public ItemOrderingStrategy<Element> getItemOrderingStrategy() {
- return orderingStrategy;
+ @Nonnull public final synchronized ItemOrderingStrategy<Element> getItemOrderingStrategy() {
+ return itemOrderingStrategy;
}
/**
@@ -107,7 +109,7 @@ public class EntitiesDescriptorAssemblerStage extends AbstractStage<Element> {
*/
public synchronized void setItemOrderingStrategy(@Nonnull final ItemOrderingStrategy<Element> strategy) {
throwSetterPreconditionExceptions();
- orderingStrategy = Constraint.isNotNull(strategy, "Item ordering strategy can not be null");
+ itemOrderingStrategy = Constraint.isNotNull(strategy, "Item ordering strategy can not be null");
}
/**
@@ -115,7 +117,7 @@ public class EntitiesDescriptorAssemblerStage extends AbstractStage<Element> {
*
* @return Name used for the generated descriptor, may be null
*/
- @Nullable public String getDescriptorName() {
+ @Nullable public final synchronized String getDescriptorName() {
return descriptorName;
}
@@ -129,16 +131,15 @@ public class EntitiesDescriptorAssemblerStage extends AbstractStage<Element> {
descriptorName = StringSupport.trimOrNull(name);
}
- /** {@inheritDoc} */
- @Override protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection)
+ @Override
+ protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection)
throws StageProcessingException {
if (itemCollection.isEmpty()) {
- if (noChildrenAProcessingError) {
+ if (isNoChildrenAProcessingError()) {
throw new StageProcessingException("Unable to assemble EntitiesDescriptor from an empty collection");
- } else {
- log.debug("Unable to assemble EntitiesDescriptor from an empty collection");
- return;
}
+ log.debug("Unable to assemble EntitiesDescriptor from an empty collection");
+ return;
}
final DOMImplementation domImpl =
@@ -156,7 +157,7 @@ public class EntitiesDescriptorAssemblerStage extends AbstractStage<Element> {
// Put a newline between the start and end tags
ElementSupport.appendTextContent(entitiesDescriptor, "\n");
- final List<Item<Element>> orderedItems = orderingStrategy.order(itemCollection);
+ final List<Item<Element>> orderedItems = getItemOrderingStrategy().order(itemCollection);
Element descriptor;
for (final Item<Element> item : orderedItems) {
descriptor = item.unwrap();
@@ -181,14 +182,15 @@ public class EntitiesDescriptorAssemblerStage extends AbstractStage<Element> {
* @param entitiesDescriptor the entity descriptor to which the
*/
protected void addDescriptorName(@Nonnull final Element entitiesDescriptor) {
- if (descriptorName != null) {
- AttributeSupport.appendAttribute(entitiesDescriptor, NAME_ATTRIB_NAME, descriptorName);
+ final var name = getDescriptorName();
+ if (name != null) {
+ AttributeSupport.appendAttribute(entitiesDescriptor, NAME_ATTRIB_NAME, name);
}
}
@Override
protected void doDestroy() {
- orderingStrategy = null;
+ itemOrderingStrategy = null;
descriptorName = null;
super.doDestroy();
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntitiesDescriptorDisassemblerStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntitiesDescriptorDisassemblerStage.java
index 311d7be..2c7b4c1 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntitiesDescriptorDisassemblerStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntitiesDescriptorDisassemblerStage.java
@@ -45,8 +45,8 @@ public class EntitiesDescriptorDisassemblerStage extends AbstractStage<Element>
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(EntitiesDescriptorDisassemblerStage.class);
- /** {@inheritDoc} */
- @Override protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection) {
+ @Override
+ protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> itemCollection) {
// make a copy of the input collection and clear it so that we can iterate over
// the copy and add to the provided collection
final ArrayList<Item<Element>> items = new ArrayList<>(itemCollection);
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntityFilterStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntityFilterStage.java
index 2047047..ef59845 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntityFilterStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntityFilterStage.java
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
@@ -43,21 +44,22 @@ public class EntityFilterStage extends AbstractFilteringStage<Element> {
private final Logger log = LoggerFactory.getLogger(EntityFilterStage.class);
/** Entities which are white/black listed depending on the value of {@link #whitelistingEntities}. */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private Set<String> designatedEntities = Set.of();
/** Whether {@link #designatedEntities} should be considered a whitelist or a blacklist. Default value: false */
- private boolean whitelistingEntities;
+ @GuardedBy("this") private boolean whitelistingEntities;
/** Whether EntitiesDescriptor that do not contain EntityDescriptors should be removed. Default value: true */
- private boolean removingEntitylessEntitiesDescriptor = true;
+ @GuardedBy("this") private boolean removingEntitylessEntitiesDescriptor = true;
/**
* Gets the list of designated entity IDs.
*
* @return list of designated entity IDs, never null
*/
- @Nonnull @NonnullElements @Unmodifiable public Collection<String> getDesignatedEntities() {
+ @Nonnull @NonnullElements @Unmodifiable
+ public final synchronized Collection<String> getDesignatedEntities() {
return designatedEntities;
}
@@ -77,7 +79,7 @@ public class EntityFilterStage extends AbstractFilteringStage<Element> {
*
* @return true if the designated entities should be considered a whitelist, false otherwise
*/
- public boolean isWhitelistingEntities() {
+ public final synchronized boolean isWhitelistingEntities() {
return whitelistingEntities;
}
@@ -96,7 +98,7 @@ public class EntityFilterStage extends AbstractFilteringStage<Element> {
*
* @return whether EntitiesDescriptor that do not contain EntityDescriptors should be removed
*/
- public boolean isRemovingEntitylessEntitiesDescriptor() {
+ public final synchronized boolean isRemovingEntitylessEntitiesDescriptor() {
return removingEntitylessEntitiesDescriptor;
}
@@ -110,13 +112,6 @@ public class EntityFilterStage extends AbstractFilteringStage<Element> {
removingEntitylessEntitiesDescriptor = remove;
}
- @Override
- protected void doDestroy() {
- designatedEntities = null;
-
- super.doDestroy();
- }
-
@Override
protected boolean doExecute(@Nonnull final Item<Element> item) {
final Element descriptor = item.unwrap();
@@ -169,8 +164,8 @@ public class EntityFilterStage extends AbstractFilteringStage<Element> {
}
}
- if (removingEntitylessEntitiesDescriptor && childEntitiesDescriptors.isEmpty()
- && childEntityDescriptors.isEmpty()) {
+ if (childEntitiesDescriptors.isEmpty() && childEntityDescriptors.isEmpty()
+ && isRemovingEntitylessEntitiesDescriptor()) {
return true;
}
@@ -188,13 +183,13 @@ public class EntityFilterStage extends AbstractFilteringStage<Element> {
final String entityId = entityDescriptor.getAttributeNS(null, "entityID");
// if we're whitelisting entities and this entity isn't in the list, kick it out
- if (isWhitelistingEntities() && !designatedEntities.contains(entityId)) {
+ if (isWhitelistingEntities() && !getDesignatedEntities().contains(entityId)) {
log.debug("{} pipeline stage removing entity {} because it wasn't on the whitelist", getId(), entityId);
return true;
}
// if we're backlisting entities and this entity is in the list, kick it out
- if (!isWhitelistingEntities() && designatedEntities.contains(entityId)) {
+ if (!isWhitelistingEntities() && getDesignatedEntities().contains(entityId)) {
log.debug("{} pipeline stage removing entity {} because it was on the blacklist", getId(), entityId);
return true;
}
@@ -202,4 +197,12 @@ public class EntityFilterStage extends AbstractFilteringStage<Element> {
// entity has been filtered and made it through, don't kick it out
return false;
}
+
+ @Override
+ protected void doDestroy() {
+ designatedEntities = null;
+
+ super.doDestroy();
+ }
+
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntityRoleFilterStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntityRoleFilterStage.java
index 7a54182..8888bea 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntityRoleFilterStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/EntityRoleFilterStage.java
@@ -24,6 +24,7 @@ import java.util.Objects;
import java.util.Set;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.namespace.QName;
@@ -48,36 +49,36 @@ import net.shibboleth.utilities.java.support.xml.QNameSupport;
@ThreadSafe
public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
- /** Class logger. */
- private final Logger log = LoggerFactory.getLogger(EntityRoleFilterStage.class);
-
/**
* Set containing the SAML-defined, named role descriptors: {@link SAMLMetadataSupport#IDP_SSO_DESCRIPTOR_NAME},
* {@link SAMLMetadataSupport#SP_SSO_DESCRIPTOR_NAME}, {@link SAMLMetadataSupport#AUTHN_AUTHORITY_DESCRIPTOR_NAME},
* {@link SAMLMetadataSupport#ATTRIBUTE_AUTHORITY_DESCRIPTOR_NAME}, {@link SAMLMetadataSupport#PDP_DESCRIPTOR_NAME}.
*/
- @Nonnull @NonnullElements @Unmodifiable
- private final Set<QName> namedRoles = Set.of(SAMLMetadataSupport.IDP_SSO_DESCRIPTOR_NAME,
+ @Nonnull @NonnullElements @Unmodifiable
+ private static final Set<QName> NAMED_ROLES = Set.of(SAMLMetadataSupport.IDP_SSO_DESCRIPTOR_NAME,
SAMLMetadataSupport.SP_SSO_DESCRIPTOR_NAME,
SAMLMetadataSupport.AUTHN_AUTHORITY_DESCRIPTOR_NAME,
SAMLMetadataSupport.ATTRIBUTE_AUTHORITY_DESCRIPTOR_NAME,
SAMLMetadataSupport.PDP_DESCRIPTOR_NAME);
+ /** Class logger. */
+ private final Logger log = LoggerFactory.getLogger(EntityRoleFilterStage.class);
+
/** Role element or type names which are white/black listed depending on the value of {@link #whitelistingRoles}. */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private Set<QName> designatedRoles = Set.of();
/** Whether {@link #designatedRoles} should be considered a whitelist or a blacklist. Default value: false */
- private boolean whitelistingRoles;
+ @GuardedBy("this") private boolean whitelistingRoles;
/**
* Whether EntityDescriptor elements that do not contain roles, after filtering, should be removed. Default value:
* true
*/
- private boolean removingRolelessEntities = true;
+ @GuardedBy("this") private boolean removingRolelessEntities = true;
/** Whether EntitiesDescriptor that do not contain EntityDescriptors should be removed. Default value: true */
- private boolean removingEntitylessEntitiesDescriptor = true;
+ @GuardedBy("this") private boolean removingEntitylessEntitiesDescriptor = true;
/**
* Gets the list of designated entity roles. The list may contain either role element names or schema types.
@@ -85,7 +86,7 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
* @return list of designated entity roles, never null
*/
@Nonnull @NonnullElements @Unmodifiable
- public Collection<QName> getDesignatedRoles() {
+ public final synchronized Collection<QName> getDesignatedRoles() {
return designatedRoles;
}
@@ -104,7 +105,7 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
*
* @return true if the designated roles should be considered a whitelist, false otherwise
*/
- public boolean isWhitelistingRoles() {
+ public final synchronized boolean isWhitelistingRoles() {
return whitelistingRoles;
}
@@ -123,7 +124,7 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
*
* @return true if EntityDescriptors without roles (after filtering) should be removed, false otherwise
*/
- public boolean isRemovingRolelessEntities() {
+ public final synchronized boolean isRemovingRolelessEntities() {
return removingRolelessEntities;
}
@@ -142,7 +143,7 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
*
* @return whether EntitiesDescriptor that do not contain EntityDescriptors should be removed
*/
- public boolean isRemovingEntitylessEntitiesDescriptor() {
+ public final synchronized boolean isRemovingEntitylessEntitiesDescriptor() {
return removingEntitylessEntitiesDescriptor;
}
@@ -156,13 +157,6 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
removingEntitylessEntitiesDescriptor = remove;
}
- @Override
- protected void doDestroy() {
- designatedRoles = null;
-
- super.doDestroy();
- }
-
@Override
protected boolean doExecute(@Nonnull final Item<Element> item) {
final Element descriptor = item.unwrap();
@@ -191,13 +185,12 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
*/
protected boolean processEntitiesDescriptor(@Nonnull final Element entitiesDescriptor) {
Iterator<Element> descriptorItr;
- Element descriptor;
final List<Element> childEntitiesDescriptors =
ElementSupport.getChildElements(entitiesDescriptor, SAMLMetadataSupport.ENTITIES_DESCRIPTOR_NAME);
descriptorItr = childEntitiesDescriptors.iterator();
while (descriptorItr.hasNext()) {
- descriptor = descriptorItr.next();
+ final var descriptor = descriptorItr.next();
if (processEntitiesDescriptor(descriptor)) {
entitiesDescriptor.removeChild(descriptor);
descriptorItr.remove();
@@ -208,15 +201,15 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
ElementSupport.getChildElements(entitiesDescriptor, SAMLMetadataSupport.ENTITY_DESCRIPTOR_NAME);
descriptorItr = childEntityDescriptors.iterator();
while (descriptorItr.hasNext()) {
- descriptor = descriptorItr.next();
+ final var descriptor = descriptorItr.next();
if (processEntityDescriptor(descriptor)) {
entitiesDescriptor.removeChild(descriptor);
descriptorItr.remove();
}
}
- if (removingEntitylessEntitiesDescriptor && childEntitiesDescriptors.isEmpty()
- && childEntityDescriptors.isEmpty()) {
+ if (childEntitiesDescriptors.isEmpty() && childEntityDescriptors.isEmpty()
+ && isRemovingEntitylessEntitiesDescriptor()) {
return true;
}
@@ -232,7 +225,7 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
* @return true if the entity descriptor should be removed, false otherwise
*/
protected boolean processEntityDescriptor(@Nonnull final Element entityDescriptor) {
- if (designatedRoles.isEmpty()) {
+ if (getDesignatedRoles().isEmpty()) {
return false;
}
@@ -241,7 +234,7 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
log.debug("{} pipeline stage filtering roles from EntityDescriptor {}", getId(), entityId);
final List<Element> roles = getFilteredRoles(entityId, entityDescriptor);
- if (removingRolelessEntities && roles.isEmpty()) {
+ if (roles.isEmpty() && isRemovingRolelessEntities()) {
return true;
}
@@ -260,25 +253,21 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
final List<Element> childElements = ElementSupport.getChildElements(entityDescriptor);
final Iterator<Element> childItr = childElements.iterator();
-
- Element child;
- QName childQName;
- QName roleIdentifier;
while (childItr.hasNext()) {
- child = childItr.next();
- childQName = QNameSupport.getNodeQName(child);
- roleIdentifier = null;
+ final Element child = childItr.next();
+ final QName childQName = QNameSupport.getNodeQName(child);
+ final QName roleIdentifier;
if (Objects.equals(childQName, SAMLMetadataSupport.ROLE_DESCRIPTOR_NAME)) {
roleIdentifier = DOMTypeSupport.getXSIType(child);
- } else if (namedRoles.contains(childQName)) {
+ } else if (NAMED_ROLES.contains(childQName)) {
roleIdentifier = childQName;
} else {
childItr.remove();
continue;
}
- final boolean isDesignatedRole = designatedRoles.contains(roleIdentifier);
+ final boolean isDesignatedRole = getDesignatedRoles().contains(roleIdentifier);
if (roleIdentifier != null) {
if ((isWhitelistingRoles() && !isDesignatedRole) || (!isWhitelistingRoles() && isDesignatedRole)) {
log.debug("{} pipeline stage removing role {} from EntityDescriptor {}", new Object[] {getId(),
@@ -294,4 +283,12 @@ public class EntityRoleFilterStage extends AbstractFilteringStage<Element> {
return childElements;
}
+
+ @Override
+ protected void doDestroy() {
+ designatedRoles = null;
+
+ super.doDestroy();
+ }
+
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/GenerateIdStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/GenerateIdStage.java
index 352a7c6..9291620 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/GenerateIdStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/GenerateIdStage.java
@@ -18,6 +18,7 @@
package net.shibboleth.metadata.dom.saml;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
import javax.xml.namespace.QName;
import org.w3c.dom.Attr;
@@ -32,15 +33,17 @@ import net.shibboleth.utilities.java.support.security.impl.Type4UUIDIdentifierGe
import net.shibboleth.utilities.java.support.xml.AttributeSupport;
/** A stage that populates the ID attribute of an EntitiesDescriptor or EntityDescriptor. */
+ at ThreadSafe
public class GenerateIdStage extends AbstractIteratingStage<Element> {
/** QName of the ID attribute added to the descriptor. */
public static final QName ID_ATTRIB = new QName("ID");
/** Strategy used to generate identifiers. */
+ @Nonnull
private final IdentifierGenerationStrategy idGenerator;
- /** Constructor. Initialized the {@link #idGenerator} to a {@link Type4UUIDIdentifierGenerationStrategy}. */
+ /** Constructor. Initialize the {@link #idGenerator} to a {@link Type4UUIDIdentifierGenerationStrategy}. */
public GenerateIdStage() {
idGenerator = new Type4UUIDIdentifierGenerationStrategy();
}
@@ -67,6 +70,7 @@ public class GenerateIdStage extends AbstractIteratingStage<Element> {
element.setAttributeNode(idAttribute);
}
+ // Don't need to synchronize; field initialized by constructor
idAttribute.setValue(idGenerator.generateIdentifier());
}
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/PullUpCacheDurationStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/PullUpCacheDurationStage.java
index 0eadedd..550c923 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/PullUpCacheDurationStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/PullUpCacheDurationStage.java
@@ -22,6 +22,7 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Attr;
@@ -43,9 +44,11 @@ import net.shibboleth.utilities.java.support.xml.ElementSupport;
public class PullUpCacheDurationStage extends AbstractIteratingStage<Element> {
/** The minimum cache duration. Default value: <code>0</code> */
+ @Nonnull @GuardedBy("this")
private Duration minCacheDuration = Duration.ZERO;
/** The maximum cache duration. Default value: {@value java.lang.Long#MAX_VALUE} */
+ @Nonnull @GuardedBy("this")
private Duration maxCacheDuration = Duration.ofMillis(Long.MAX_VALUE);
/**
@@ -53,7 +56,8 @@ public class PullUpCacheDurationStage extends AbstractIteratingStage<Element> {
*
* @return minimum cache duration, always 0 or greater
*/
- public Duration getMinimumCacheDuration() {
+ @Nonnull
+ public final synchronized Duration getMinimumCacheDuration() {
return minCacheDuration;
}
@@ -76,7 +80,8 @@ public class PullUpCacheDurationStage extends AbstractIteratingStage<Element> {
*
* @return maximum cache duration, always greater than 0
*/
- public Duration getMaximumCacheDuration() {
+ @Nonnull
+ public final synchronized Duration getMaximumCacheDuration() {
return maxCacheDuration;
}
@@ -106,6 +111,7 @@ public class PullUpCacheDurationStage extends AbstractIteratingStage<Element> {
* @return the shortest cache duration from the descriptor and its descendants or null if the descriptor does not
* contain a cache duration
*/
+ @Nullable
protected Duration getShortestCacheDuration(@Nonnull final Element descriptor) {
Duration shortestCacheDuration = null;
if (!SAMLMetadataSupport.isEntityOrEntitiesDescriptor(descriptor)) {
@@ -162,12 +168,12 @@ public class PullUpCacheDurationStage extends AbstractIteratingStage<Element> {
return;
}
- if (cacheDuration.compareTo(minCacheDuration) < 0) {
+ if (cacheDuration.compareTo(getMinimumCacheDuration()) < 0) {
AttributeSupport.appendDurationAttribute(descriptor, SAMLMetadataSupport.CACHE_DURATION_ATTRIB_NAME,
- minCacheDuration);
- } else if (cacheDuration.compareTo(maxCacheDuration) > 0) {
+ getMinimumCacheDuration());
+ } else if (cacheDuration.compareTo(getMaximumCacheDuration()) > 0) {
AttributeSupport.appendDurationAttribute(descriptor, SAMLMetadataSupport.CACHE_DURATION_ATTRIB_NAME,
- maxCacheDuration);
+ getMaximumCacheDuration());
} else {
AttributeSupport.appendDurationAttribute(descriptor, SAMLMetadataSupport.CACHE_DURATION_ATTRIB_NAME,
cacheDuration);
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/PullUpValidUntilStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/PullUpValidUntilStage.java
index 3b09dc7..4ad8bd9 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/PullUpValidUntilStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/PullUpValidUntilStage.java
@@ -23,6 +23,7 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Attr;
@@ -44,12 +45,14 @@ import net.shibboleth.utilities.java.support.xml.ElementSupport;
public class PullUpValidUntilStage extends AbstractIteratingStage<Element> {
/** The minimum amount of time a descriptor may be valid. Default value: 0 */
+ @Nonnull @GuardedBy("this")
private Duration minValidityDuration = Duration.ZERO;
/**
* The maximum amount of time a descriptor may be valid. Default value:
* {@value java.lang.Long#MAX_VALUE}
*/
+ @Nonnull @GuardedBy("this")
private Duration maxValidityDuration = Duration.ofMillis(Long.MAX_VALUE);
/**
@@ -57,7 +60,8 @@ public class PullUpValidUntilStage extends AbstractIteratingStage<Element> {
*
* @return minimum amount of time a descriptor may be valid, always 0 or greater
*/
- public Duration getMinimumValidityDuration() {
+ @Nonnull
+ public final synchronized Duration getMinimumValidityDuration() {
return minValidityDuration;
}
@@ -80,7 +84,8 @@ public class PullUpValidUntilStage extends AbstractIteratingStage<Element> {
*
* @return maximum maximum amount of time a descriptor may be valid, always greater than 0
*/
- public Duration getMaximumValidityDuration() {
+ @Nonnull
+ public final synchronized Duration getMaximumValidityDuration() {
return maxValidityDuration;
}
@@ -110,6 +115,7 @@ public class PullUpValidUntilStage extends AbstractIteratingStage<Element> {
* @return the shortest cache duration from the descriptor and its descendants or null if the descriptor does not
* contain a cache duration
*/
+ @Nullable
protected Instant getNearestValidUntil(@Nonnull final Element descriptor) {
Instant nearestValidUntil = null;
if (!SAMLMetadataSupport.isEntityOrEntitiesDescriptor(descriptor)) {
@@ -164,8 +170,8 @@ public class PullUpValidUntilStage extends AbstractIteratingStage<Element> {
}
final Instant now = Instant.now();
- final Instant minValidUntil = now.plus(minValidityDuration);
- final Instant maxValidUntil = now.plus(maxValidityDuration);
+ final Instant minValidUntil = now.plus(getMinimumValidityDuration());
+ final Instant maxValidUntil = now.plus(getMaximumValidityDuration());
final Instant boundedValidUntil;
if (validUntil.isBefore(minValidUntil)) {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLMetadataSupport.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLMetadataSupport.java
index 2e2c70b..e44a8c8 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLMetadataSupport.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLMetadataSupport.java
@@ -255,4 +255,4 @@ public final class SAMLMetadataSupport {
ElementSupport.appendChildElement(extensionsElement, extension);
}
-}
\ No newline at end of file
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLStringElementCheckingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLStringElementCheckingStage.java
index d58d326..bcf3b54 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLStringElementCheckingStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLStringElementCheckingStage.java
@@ -21,6 +21,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.metadata.dom.AbstractElementVisitingStage;
import net.shibboleth.metadata.dom.DOMTraversalContext;
@@ -35,6 +36,7 @@ import org.w3c.dom.Node;
*
* @since 0.10.0
*/
+ at ThreadSafe
public class SAMLStringElementCheckingStage extends AbstractElementVisitingStage {
/** Regular expression matching a string which contains no non-whitespace characters. */
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLSupport.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLSupport.java
index 37dd7c6..e0e6d75 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLSupport.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLSupport.java
@@ -86,9 +86,8 @@ public final class SAMLSupport {
final Attr attr = attribute.getAttributeNode("NameFormat");
if (attr == null) {
return ATTRNAME_FORMAT_UNSPECIFIED;
- } else {
- return attr.getValue();
}
+ return attr.getValue();
}
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SetCacheDurationStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SetCacheDurationStage.java
index 9f1b859..3091ee2 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SetCacheDurationStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SetCacheDurationStage.java
@@ -20,6 +20,7 @@ package net.shibboleth.metadata.dom.saml;
import java.time.Duration;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Element;
@@ -45,14 +46,15 @@ import net.shibboleth.utilities.java.support.xml.AttributeSupport;
public class SetCacheDurationStage extends AbstractIteratingStage<Element> {
/** Cache duration that will be set on each metadata element. */
- @NonnullAfterInit private Duration cacheDuration;
+ @NonnullAfterInit @GuardedBy("this") private Duration cacheDuration;
/**
* Gets the cache duration that will be set on each metadata element.
*
* @return cache duration
*/
- public Duration getCacheDuration() {
+ @NonnullAfterInit
+ public final synchronized Duration getCacheDuration() {
return cacheDuration;
}
@@ -77,7 +79,7 @@ public class SetCacheDurationStage extends AbstractIteratingStage<Element> {
if (SAMLMetadataSupport.isEntityOrEntitiesDescriptor(descriptor)) {
AttributeSupport.removeAttribute(descriptor, SAMLMetadataSupport.CACHE_DURATION_ATTRIB_NAME);
AttributeSupport.appendDurationAttribute(descriptor, SAMLMetadataSupport.CACHE_DURATION_ATTRIB_NAME,
- cacheDuration);
+ getCacheDuration());
}
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SetValidUntilStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SetValidUntilStage.java
index 2d16739..c4ddad3 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SetValidUntilStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SetValidUntilStage.java
@@ -21,6 +21,7 @@ import java.time.Duration;
import java.time.Instant;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Element;
@@ -46,14 +47,15 @@ import net.shibboleth.utilities.java.support.xml.AttributeSupport;
public class SetValidUntilStage extends AbstractIteratingStage<Element> {
/** Amount of time the descriptors will be valid. */
- @NonnullAfterInit private Duration validityDuration;
+ @NonnullAfterInit @GuardedBy("this") private Duration validityDuration;
/**
* Gets the amount of time the descriptors will be valid.
*
* @return amount of time the descriptors will be valid
*/
- public Duration getValidityDuration() {
+ @NonnullAfterInit
+ public final synchronized Duration getValidityDuration() {
return validityDuration;
}
@@ -78,7 +80,7 @@ public class SetValidUntilStage extends AbstractIteratingStage<Element> {
if (SAMLMetadataSupport.isEntityOrEntitiesDescriptor(descriptor)) {
AttributeSupport.removeAttribute(descriptor, SAMLMetadataSupport.VALID_UNTIL_ATTRIB_NAME);
AttributeSupport.appendDateTimeAttribute(descriptor, SAMLMetadataSupport.VALID_UNTIL_ATTRIB_NAME,
- Instant.now().plus(validityDuration));
+ Instant.now().plus(getValidityDuration()));
}
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/ValidateValidUntilStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/ValidateValidUntilStage.java
index 319e37e..34b9a2a 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/ValidateValidUntilStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/ValidateValidUntilStage.java
@@ -21,6 +21,7 @@ import java.time.Duration;
import java.time.Instant;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.w3c.dom.Element;
@@ -40,20 +41,20 @@ import net.shibboleth.utilities.java.support.xml.AttributeSupport;
public class ValidateValidUntilStage extends AbstractIteratingStage<Element> {
/** Whether the item is required to have a validUntil attribute. Default value: <code>true</code> */
- private boolean requireValidUntil = true;
+ @GuardedBy("this") private boolean requireValidUntil = true;
/**
* Interval from now within which the validUntil date must fall. A value of 0 indicates that no
* maximum interval is checked. Default value: 1 week
*/
- @Nonnull private Duration maxValidityInterval = Duration.ofDays(7);
+ @Nonnull @GuardedBy("this") private Duration maxValidityInterval = Duration.ofDays(7);
/**
* Gets whether the item is required to have a validUntil attribute.
*
* @return whether the item is required to have a validUntil attribute
*/
- public boolean isRequireValidUntil() {
+ public final synchronized boolean isRequireValidUntil() {
return requireValidUntil;
}
@@ -72,7 +73,8 @@ public class ValidateValidUntilStage extends AbstractIteratingStage<Element> {
*
* @return Interval from now within which the validUntil date must fall
*/
- public Duration getMaxValidityInterval() {
+ @Nonnull
+ public final synchronized Duration getMaxValidityInterval() {
return maxValidityInterval;
}
@@ -104,7 +106,7 @@ public class ValidateValidUntilStage extends AbstractIteratingStage<Element> {
AttributeSupport.getDateTimeAttribute(AttributeSupport.getAttribute(element,
SAMLMetadataSupport.VALID_UNTIL_ATTRIB_NAME));
if (validUntil == null) {
- if (requireValidUntil) {
+ if (isRequireValidUntil()) {
item.getItemMetadata().put(new ErrorStatus(getId(), "Item does not include a validUntil attribute"));
}
} else {
@@ -113,8 +115,8 @@ public class ValidateValidUntilStage extends AbstractIteratingStage<Element> {
item.getItemMetadata().put(new ErrorStatus(getId(), "Item has a validUntil prior to the current time"));
}
- if (!maxValidityInterval.isZero()) {
- final var upperBound = lowerBound.plus(maxValidityInterval);
+ if (!getMaxValidityInterval().isZero()) {
+ final var upperBound = lowerBound.plus(getMaxValidityInterval());
if (validUntil.isAfter(upperBound)) {
item.getItemMetadata().put(
new ErrorStatus(getId(), "Item has validUntil larger than the maximum validity interval"));
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/IPHintValidationStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/IPHintValidationStage.java
index 1be2480..23c04c6 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/IPHintValidationStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/IPHintValidationStage.java
@@ -60,7 +60,7 @@ public class IPHintValidationStage extends AbstractDOMTraversalStage<DOMTraversa
}
@Override
- protected boolean applicable(final Element element) {
+ protected boolean applicable(@Nonnull final Element element, @Nonnull final DOMTraversalContext context) {
return ElementSupport.isElementNamed(element, MDUISupport.IPHINT_NAME);
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/pipeline/SimplePipeline.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/pipeline/SimplePipeline.java
index 2900633..9ff5456 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/pipeline/SimplePipeline.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/pipeline/SimplePipeline.java
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.metadata.Item;
@@ -41,12 +42,12 @@ public class SimplePipeline<T> extends BaseIdentifiableInitializableComponent
implements Pipeline<T> {
/** Stages for this pipeline. */
- @Nonnull @NonnullElements
+ @Nonnull @NonnullElements @GuardedBy("this")
private List<Stage<T>> pipelineStages = Collections.emptyList();
@Override
@Nonnull @NonnullElements @Unmodifiable
- public List<Stage<T>> getStages() {
+ public final synchronized List<Stage<T>> getStages() {
return pipelineStages;
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/AcceptAllValidator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/AcceptAllValidator.java
index 1e984d6..f6a7acf 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/AcceptAllValidator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/AcceptAllValidator.java
@@ -18,6 +18,7 @@
package net.shibboleth.metadata.validate;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
import net.shibboleth.metadata.Item;
@@ -30,6 +31,7 @@ import net.shibboleth.metadata.Item;
*
* @since 0.10.0
*/
+ at Immutable
public class AcceptAllValidator<V> extends BaseValidator implements Validator<V> {
@Override
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/BaseValidator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/BaseValidator.java
index e758d0c..4f89612 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/BaseValidator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/BaseValidator.java
@@ -84,9 +84,8 @@ public abstract class BaseValidator extends BaseIdentifiableInitializableCompone
final String id = getId();
if (id == null) {
return stageId;
- } else {
- return stageId + "/" + getId();
}
+ return stageId + "/" + getId();
}
/**
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/RejectAllValidator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/RejectAllValidator.java
index 7663d97..874a907 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/RejectAllValidator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/RejectAllValidator.java
@@ -18,6 +18,7 @@
package net.shibboleth.metadata.validate;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
import net.shibboleth.metadata.Item;
@@ -33,6 +34,7 @@ import net.shibboleth.metadata.Item;
*
* @since 0.10.0
*/
+ at Immutable
public class RejectAllValidator<V> extends BaseValidator implements Validator<V> {
@Override
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/Validator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/Validator.java
index c7afade..aedc11b 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/Validator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/Validator.java
@@ -18,6 +18,7 @@
package net.shibboleth.metadata.validate;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.pipeline.StageProcessingException;
@@ -27,11 +28,16 @@ import net.shibboleth.utilities.java.support.component.InitializableComponent;
/**
* Interface for a validator to be applied to an object in the context of a given {@link Item}.
- *
+ *
+ * <p>
+ * {@code Validator}s <strong>must</strong> be thread-safe.
+ * </p>
+ *
* @param <V> type of the object to be validated
*
* @since 0.9.0
*/
+ at ThreadSafe
public interface Validator<V> extends DestructableComponent, IdentifiableComponent,
InitializableComponent {
@@ -64,7 +70,7 @@ public interface Validator<V> extends DestructableComponent, IdentifiableCompone
* @return an indication of whether to process additional validators
* @throws StageProcessingException if an error occurs during validation
*/
- Action validate(@Nonnull V e, @Nonnull Item<?> item, @Nonnull String stageId)
+ @Nonnull Action validate(@Nonnull V e, @Nonnull Item<?> item, @Nonnull String stageId)
throws StageProcessingException;
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/ValidatorSequence.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/ValidatorSequence.java
index 6f3a6f6..195a5ec 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/ValidatorSequence.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/ValidatorSequence.java
@@ -20,6 +20,8 @@ package net.shibboleth.metadata.validate;
import java.util.List;
import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.pipeline.StageProcessingException;
@@ -39,10 +41,11 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
*
* @since 0.10.0
*/
+ at ThreadSafe
public class ValidatorSequence<V> extends BaseValidator implements Validator<V> {
/** The list of validators to apply. */
- @Nonnull @NonnullElements @Unmodifiable
+ @Nonnull @NonnullElements @Unmodifiable @GuardedBy("this")
private List<Validator<V>> validators = List.of();
/**
@@ -50,7 +53,8 @@ public class ValidatorSequence<V> extends BaseValidator implements Validator<V>
*
* @param newValidators the list of validators to set
*/
- public void setValidators(@Nonnull @NonnullElements @Unmodifiable final List<Validator<V>> newValidators) {
+ public synchronized void setValidators(
+ @Nonnull @NonnullElements @Unmodifiable final List<Validator<V>> newValidators) {
throwSetterPreconditionExceptions();
validators = List.copyOf(newValidators);
}
@@ -61,14 +65,14 @@ public class ValidatorSequence<V> extends BaseValidator implements Validator<V>
* @return list of validators
*/
@Nonnull @NonnullElements @Unmodifiable
- public List<Validator<V>> getValidators() {
+ public final synchronized List<Validator<V>> getValidators() {
return validators;
}
@Override
public Action validate(@Nonnull final V value, @Nonnull final Item<?> item, @Nonnull final String stageId)
throws StageProcessingException {
- for (final Validator<V> validator: validators) {
+ for (final Validator<V> validator: getValidators()) {
final Action action = validator.validate(value, item, stageId);
if (action == Action.DONE) {
return action;
@@ -87,7 +91,7 @@ public class ValidatorSequence<V> extends BaseValidator implements Validator<V>
protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
- for (final Validator<V> validator : validators) {
+ for (final Validator<V> validator : getValidators()) {
if (!validator.isInitialized()) {
validator.initialize();
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509DSADetector.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509DSADetector.java
index b331bbb..9a80962 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509DSADetector.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509DSADetector.java
@@ -108,9 +108,8 @@ public class X509DSADetector extends BaseValidator implements Validator<X509Cert
if ("DSA".equals(key.getAlgorithm())) {
addStatus(error, "certificate contains a DSA key", item, stageId);
return action;
- } else {
- return Action.CONTINUE;
}
+ return Action.CONTINUE;
}
}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/AbstractDOMValidationStageTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/AbstractDOMValidationStageTest.java
index 708b8e4..4d8347a 100644
--- a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/AbstractDOMValidationStageTest.java
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/AbstractDOMValidationStageTest.java
@@ -40,7 +40,7 @@ public class AbstractDOMValidationStageTest extends BaseDOMTest {
private static class StringValidationStage extends AbstractDOMValidationStage<String, DOMTraversalContext> {
@Override
- protected boolean applicable(Element element) {
+ protected boolean applicable(Element element, DOMTraversalContext context) {
return "checkedElement".equals(element.getLocalName());
}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/XMLSignatureSigningStageTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/XMLSignatureSigningStageTest.java
index f657010..74573af 100644
--- a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/XMLSignatureSigningStageTest.java
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/XMLSignatureSigningStageTest.java
@@ -96,7 +96,7 @@ public class XMLSignatureSigningStageTest extends BaseDOMTest {
PrivateKey signingKey = KeyPairUtil.readPrivateKey(XMLSignatureSigningStageTest.class
.getResourceAsStream(classRelativeResource("signingKey.pem")));
- X509Certificate signingCert = (X509Certificate) CertUtil.readCertificate(XMLSignatureSigningStageTest.class
+ X509Certificate signingCert = CertUtil.readCertificate(XMLSignatureSigningStageTest.class
.getResourceAsStream(classRelativeResource("signingCert.pem")));
final List<X509Certificate> certs = new ArrayList<>();
certs.add(signingCert);
@@ -133,7 +133,7 @@ public class XMLSignatureSigningStageTest extends BaseDOMTest {
final PrivateKey signingKey = KeyPairUtil.readPrivateKey(XMLSignatureSigningStageTest.class
.getResourceAsStream(classRelativeResource("signingKey.pem")));
- final X509Certificate signingCert = (X509Certificate) CertUtil.readCertificate(XMLSignatureSigningStageTest.class
+ final X509Certificate signingCert = CertUtil.readCertificate(XMLSignatureSigningStageTest.class
.getResourceAsStream(classRelativeResource("signingCert.pem")));
final List<X509Certificate> certs = new ArrayList<>();
certs.add(signingCert);
@@ -188,7 +188,7 @@ public class XMLSignatureSigningStageTest extends BaseDOMTest {
public void setRemovingCRsFromSignature() throws Exception {
PrivateKey signingKey = KeyPairUtil.readPrivateKey(XMLSignatureSigningStageTest.class
.getResourceAsStream(classRelativeResource("signingKey.pem")));
- X509Certificate signingCert = (X509Certificate) CertUtil.readCertificate(XMLSignatureSigningStageTest.class
+ X509Certificate signingCert = CertUtil.readCertificate(XMLSignatureSigningStageTest.class
.getResourceAsStream(classRelativeResource("signingCert.pem")));
final List<X509Certificate> certs = new ArrayList<>();
certs.add(signingCert);
@@ -264,7 +264,7 @@ public class XMLSignatureSigningStageTest extends BaseDOMTest {
final var md = getInput("input.xml");
final var stage = new XMLSignatureSigningStage();
- final var signingCert = (X509Certificate) CertUtil.readCertificate(XMLSignatureSigningStageTest.class
+ final var signingCert = CertUtil.readCertificate(XMLSignatureSigningStageTest.class
.getResourceAsStream(classRelativeResource("signingCert.pem")));
final var certs = List.of(signingCert);
@@ -308,7 +308,13 @@ public class XMLSignatureSigningStageTest extends BaseDOMTest {
// If we didn't have a public key or a certificate, we don't expect to see a KeyInfo at all.
Assert.assertNull(keyInfo);
}
-
+
+ @Test
+ public final void testDefaultHash() {
+ final var stage = new XMLSignatureSigningStage();
+ Assert.assertEquals(stage.getShaVariant(), XMLSignatureSigningStage.ShaVariant.SHA256);
+ }
+
private boolean hasChildNamed(@Nonnull final Element element, @Nonnull final QName name) {
return !ElementSupport.getChildElements(element, name).isEmpty();
}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/SAMLStringElementCheckingStageTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/SAMLStringElementCheckingStageTest.java
index dfeface..a280d63 100644
--- a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/SAMLStringElementCheckingStageTest.java
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/SAMLStringElementCheckingStageTest.java
@@ -57,9 +57,9 @@ public class SAMLStringElementCheckingStageTest extends BaseDOMTest {
final Item<Element> outItem = items.get(0);
Assert.assertSame(outItem, item);
final List<ErrorStatus> errors = item.getItemMetadata().get(ErrorStatus.class);
- for (final ErrorStatus error : errors) {
- System.out.println(error.getComponentId() + ": " + error.getStatusMessage());
- }
+ //for (final ErrorStatus error : errors) {
+ // System.out.println(error.getComponentId() + ": " + error.getStatusMessage());
+ //}
Assert.assertEquals(errors.size(), 3);
}
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list