[java-shib-attribute] 01/02: JSATTR-6: SAML AttributeQuery DataConnector

Brent Putman putmanb at georgetown.edu
Thu Nov 6 03:34:30 UTC 2025


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

putmanb pushed a commit to branch main
in repository java-shib-attribute.

View the commit online:
https://git.shibboleth.net/view/?p=java-shib-attribute.git;a=commit;h=867e51631762c2d411c6be41eedb0d14b495f9d4

commit 867e51631762c2d411c6be41eedb0d14b495f9d4
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Fri Oct 17 00:22:57 2025 -0400

    JSATTR-6: SAML AttributeQuery DataConnector
    
    Spring schema and parser impls.
    
    Other fixes discovered while testing.
---
 .../dc/saml/impl/BasicResponseMappingStrategy.java |  68 ++-
 .../dc/saml/impl/ExecutableQueryBuilder.java       |   3 -
 .../resolver/dc/saml/impl/SAMLDataConnector.java   |  31 ++
 .../impl/SimpleAggregationSAMLDataConnector.java   |  16 +-
 .../CriteriaDecryptionConfigurationLookup.java     |   7 +-
 .../impl/AttributeAuthorityEntityIDReference.java  |   2 +-
 .../impl/AttributeAuthorityEntityIDSource.java     |   8 +
 .../util/impl/AttributeAuthorityEntityIDValue.java |   2 +-
 .../dc/saml/util/impl/DecryptionProcessor.java     |   6 +-
 shib-attribute-resolver-spring/pom.xml             |  18 +-
 .../spring/dc/AbstractDataConnectorParser.java     |   2 +-
 .../saml/impl/AbstractSAMLDataConnectorParser.java | 601 +++++++++++++++++++++
 .../SimpleAggregationSAMLDataConnectorParser.java  | 177 ++++++
 .../impl/AttributeResolverNamespaceHandler.java    |   3 +
 .../schema/shibboleth-attribute-resolver.xsd       | 105 ++++
 .../dc/saml/impl/MockAttributeFilterService.java   | 111 ++++
 .../impl/MockDecryptionConfigurationResolver.java  |  48 ++
 .../spring/dc/saml/impl/MockSOAPClient.java        |  24 +-
 .../dc/saml/impl/MockSelfEntityIDResolver.java     |  44 ++
 ...mpleAggregationSAMLDataConnectorParserTest.java | 176 ++++++
 .../net/shibboleth/spring/parser.properties        |  21 +
 .../saml/resolver/saml-attribute-resolver-v2.xml   |  25 +
 .../resolver/spring/dc/saml/spring-beans.xml       |  65 +++
 23 files changed, 1521 insertions(+), 42 deletions(-)

diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BasicResponseMappingStrategy.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BasicResponseMappingStrategy.java
index e494be73e..bfb7aefa9 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BasicResponseMappingStrategy.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BasicResponseMappingStrategy.java
@@ -87,6 +87,16 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
     /** Flag indicating whether to filter attributes. */
     private boolean filterAttributes = true;
 
+    /**
+     * Get the instance of {@link AttributeTranscoderRegistry} to use.
+     * 
+     * @return the transcoder registry
+     */
+    @NonnullAfterInit
+    public AttributeTranscoderRegistry getTranscoderRegistry() {
+        return transcoderRegistry;
+    }
+    
     /**
      * Set the instance of {@link AttributeTranscoderRegistry} to use.
      * 
@@ -97,6 +107,16 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
         transcoderRegistry = registry;
     }
     
+    /**
+     * Get the instance of {@link MetadataResolver} to use.
+     * 
+     * @return the metadata resolver
+     */
+    @NonnullAfterInit
+    public MetadataResolver getMetadataResolver() {
+        return metadataResolver;
+    }
+    
     /**
      * Set the instance of {@link MetadataResolver} to use.
      * 
@@ -107,6 +127,16 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
         metadataResolver = resolver;
     }
     
+    /**
+     * Get the role descriptor resolver.
+     * 
+     * @return the role descriptor resolver
+     */
+    @NonnullAfterInit
+    public RoleDescriptorResolver getRoleDescriptorResolver() {
+        return roleDescriptorResolver;
+    }
+    
     /**
      * Set the role descriptor resolver.
      * 
@@ -117,6 +147,15 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
         roleDescriptorResolver = resolver;
     }
     
+    /**
+     * Get the instance of {@link AttributeFilter} service to use.
+     * 
+     * @return the attribute filter service
+     */
+    public ReloadableService<AttributeFilter>  getAttributeFilterService() {
+        return filterService;
+    }
+    
     /**
      * Set the instance of {@link AttributeFilter} service to use.
      * 
@@ -127,6 +166,15 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
         filterService = service;
     }
     
+    /**
+     * Get the flag indicating whether to filter attributes.
+     * 
+     * @return true if attributes should be filter, otherwise false
+     */
+    public boolean isFilterAttributes() {
+        return filterAttributes;
+    }
+    
     /**
      * Set the flag indicating whether to filter attributes.
      * 
@@ -141,16 +189,16 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
     protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
         
-        if (transcoderRegistry == null) {
+        if (getTranscoderRegistry() == null) {
             throw new ComponentInitializationException("AttributeTranscoderRegistry was null");
         }
-        if (metadataResolver == null) {
+        if (getMetadataResolver() == null) {
             throw new ComponentInitializationException("MetadataResolver was null");
         }
-        if (roleDescriptorResolver == null) {
+        if (getRoleDescriptorResolver() == null) {
             throw new ComponentInitializationException("RoleDescriptorResolver was null");
         }
-        if (filterService == null) {
+        if (getAttributeFilterService() == null) {
             throw new ComponentInitializationException("AttributeFilter service was null");
         }
     }
@@ -221,7 +269,7 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
         
         final List<IdPAttribute> attributeResults = new LinkedList<>();
 
-        final Collection<TranscodingRule> transcodingRules = transcoderRegistry.getTranscodingRules(samlAttribute);
+        final Collection<TranscodingRule> transcodingRules = getTranscoderRegistry().getTranscodingRules(samlAttribute);
         if (transcodingRules.isEmpty()) {
             log.debug("No transcoding rule for Attribute (Name '{}', NameFormat: '{}')",
                     samlAttribute.getName(),
@@ -268,7 +316,7 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
             @Nonnull final List<IdPAttribute> assertionResults, @Nullable final String assertionIssuerID,
             @Nullable final ProfileRequestContext profileContext, @Nonnull final ResponseData responseData) {
 
-        if (filterAttributes) {
+        if (isFilterAttributes()) {
             if (assertionIssuerID != null && profileContext != null) {
                 log.debug("Filtering attributes decoded from Assertion '{}' issued by '{}'",
                         assertion.getID(), assertionIssuerID);
@@ -309,7 +357,9 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
 
         populateFilterContext(filterContext, unfilteredAttributes, assertionIssuerID, responseData);
 
-        try (final ServiceableComponent<AttributeFilter> component = filterService.getServiceableComponent()) {
+        try (final ServiceableComponent<AttributeFilter> component = 
+                getAttributeFilterService().getServiceableComponent()) {
+
             final AttributeFilter filter = component.getComponent();
             filter.filterAttributes(filterContext);
             final Map<String,IdPAttribute>  filtered = filterContext.getFilteredIdPAttributes();
@@ -350,7 +400,7 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
         
         filterContext.setDirection(Direction.INBOUND)
             .setPrefilteredIdPAttributes(unfilteredAttributes)
-            .setMetadataResolver(metadataResolver)
+            .setMetadataResolver(getMetadataResolver())
             .setIssuerMetadataContextLookupStrategy(t -> issuerMetadataContext)
             .setAttributeIssuerID(assertionIssuer)
             .setAttributeRecipientID(selfEntityID);
@@ -442,7 +492,7 @@ public class BasicResponseMappingStrategy extends AbstractInitializableComponent
                 new EntityRoleCriterion(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME));
         try {
             final AttributeAuthorityDescriptor descriptor =
-                    (AttributeAuthorityDescriptor) roleDescriptorResolver.resolveSingle(criteriaSet);
+                    (AttributeAuthorityDescriptor) getRoleDescriptorResolver().resolveSingle(criteriaSet);
             if (descriptor != null) {
                 log.debug("Successfully resolved AttributeAuthorityDescriptor for entityID: {}", authorityEntityID);
                 return descriptor;
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/ExecutableQueryBuilder.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/ExecutableQueryBuilder.java
index 1776725a2..f81323c13 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/ExecutableQueryBuilder.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/ExecutableQueryBuilder.java
@@ -288,9 +288,6 @@ public class ExecutableQueryBuilder extends AbstractInitializableComponent
     protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
         
-        if (getSOAPClientSecurityConfigurationProfileId() == null) {
-            throw new ComponentInitializationException("SOAP client security config profile ID was null");
-        }
         if (getAuthorityEndpointResolver() == null) {
             throw new ComponentInitializationException("Authority EndpointResolver was null");
         }
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnector.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnector.java
index 0f4286c4f..939756f80 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnector.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnector.java
@@ -30,6 +30,8 @@ import org.slf4j.Logger;
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.resolver.DataConnector;
 import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.dc.ValidationException;
+import net.shibboleth.idp.attribute.resolver.dc.Validator;
 import net.shibboleth.idp.attribute.resolver.dc.impl.AbstractSearchDataConnector;
 import net.shibboleth.idp.attribute.resolver.dc.saml.ExecutableQuery;
 import net.shibboleth.idp.attribute.resolver.dc.saml.ResponseData;
@@ -64,6 +66,11 @@ public class SAMLDataConnector extends AbstractSearchDataConnector<ExecutableQue
     /** Flag indicating whether to perform matching of Assertion subjects against the value in the query. */
     private boolean subjectMatch = false;
     
+    /** Constructor. */
+    public SAMLDataConnector() {
+        setValidator(new NullValidator());
+    }
+    
     /**
      * Get the SOAP client instance.
      * 
@@ -248,6 +255,30 @@ public class SAMLDataConnector extends AbstractSearchDataConnector<ExecutableQue
         }
     }
 
+    /**
+     * Because we are a SearchDataConnector we need to have a validator, so this plugs the gap.
+     */
+   private final class NullValidator implements Validator {
+
+       /** Whether to raise an error. */
+       private boolean throwValidateError;
+
+       /** {@inheritDoc} */
+       @Override
+       public void validate(@Nonnull final DataConnector dataConnector) throws ValidationException {
+       }
+
+       /** {@inheritDoc} */
+       @Override
+       public void setThrowValidateError(final boolean what) {
+           throwValidateError = what;
+       }
 
+       /** {@inheritDoc} */
+       @Override
+       public boolean isThrowValidateError() {
+           return throwValidateError;
+       }
+   }
 
 }
\ No newline at end of file
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SimpleAggregationSAMLDataConnector.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SimpleAggregationSAMLDataConnector.java
index d44a9e768..c03595569 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SimpleAggregationSAMLDataConnector.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SimpleAggregationSAMLDataConnector.java
@@ -43,6 +43,7 @@ import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorit
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
@@ -94,7 +95,7 @@ public class SimpleAggregationSAMLDataConnector extends AbstractDataConnector {
      * @param sources the attribute authority entityID sources
      */
     public void setEntityIDSources(@Nullable final List<AttributeAuthorityEntityIDSource> sources) {
-        checkComponentActive();
+        checkSetterPreconditions();
 
         if (sources == null) {
             entityIDSources = CollectionSupport.emptyList();
@@ -104,6 +105,19 @@ public class SimpleAggregationSAMLDataConnector extends AbstractDataConnector {
                     .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableList())).get();
         }
     }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (getQueryConnector() == null) {
+            throw new ComponentInitializationException("Query connector was null");
+        }
+        if  (getEntityIDSources().isEmpty()) {
+            throw new ComponentInitializationException("EntityID sources list was empty");
+        }
+    }
 
     /** {@inheritDoc} */
     @Override
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaDecryptionConfigurationLookup.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaDecryptionConfigurationLookup.java
index 90c4e01a5..76d005ad2 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaDecryptionConfigurationLookup.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaDecryptionConfigurationLookup.java
@@ -56,7 +56,7 @@ public class CriteriaDecryptionConfigurationLookup extends AbstractInitializable
      * 
      * @return the resolver
      */
-    @NonnullAfterInit public Resolver<List<DecryptionConfiguration>,CriteriaSet> getConfigurationResolver() {
+    @NonnullAfterInit public Resolver<List<DecryptionConfiguration>,CriteriaSet> getDecryptionConfigurationResolver() {
         return configurationResolver;
     }
     
@@ -65,7 +65,8 @@ public class CriteriaDecryptionConfigurationLookup extends AbstractInitializable
      * 
      * @param resolver the resolver instance
      */
-    public void setConfigurationResolver(@Nullable final Resolver<List<DecryptionConfiguration>, CriteriaSet> resolver) {
+    public void setDecryptionConfigurationResolver(
+            @Nullable final Resolver<List<DecryptionConfiguration>, CriteriaSet> resolver) {
         checkSetterPreconditions();
         configurationResolver = resolver;
     }
@@ -109,7 +110,7 @@ public class CriteriaDecryptionConfigurationLookup extends AbstractInitializable
                 new ProfileIDCriterion(securityConfigurationProfileId));
 
         try {
-            final List<DecryptionConfiguration> configs = getConfigurationResolver().resolveSingle(criteria);
+            final List<DecryptionConfiguration> configs = getDecryptionConfigurationResolver().resolveSingle(criteria);
             if (configs == null) {
                 log.warn("Failed to resolve list of DecryptionConfiguration from criteria");
                 return null;
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDReference.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDReference.java
index 3e295d683..2bd0d3bc9 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDReference.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDReference.java
@@ -27,7 +27,7 @@ public class AttributeAuthorityEntityIDReference extends AttributeAuthorityEntit
      *
      * @param reference the authority entityID reference (dependency attribute name)
      */
-    protected AttributeAuthorityEntityIDReference(@Nonnull final String reference) {
+    public AttributeAuthorityEntityIDReference(@Nonnull final String reference) {
         super(reference);
     }
 
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDSource.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDSource.java
index ec6bf16b9..b52e0696c 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDSource.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDSource.java
@@ -16,6 +16,8 @@ package net.shibboleth.idp.attribute.resolver.dc.saml.util.impl;
 
 import javax.annotation.Nonnull;
 
+import com.google.common.base.MoreObjects;
+
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.StringSupport;
 
@@ -46,4 +48,10 @@ public abstract class AttributeAuthorityEntityIDSource {
         return value;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass()).addValue(getValue()).toString();
+    }
+
 }
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDValue.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDValue.java
index 1e00f4391..9715a86f4 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDValue.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDValue.java
@@ -27,7 +27,7 @@ public class AttributeAuthorityEntityIDValue extends AttributeAuthorityEntityIDS
      *
      * @param entityID the authority entityID value
      */
-    protected AttributeAuthorityEntityIDValue(@Nonnull final String entityID) {
+    public AttributeAuthorityEntityIDValue(@Nonnull final String entityID) {
         super(entityID);
     }
 
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/DecryptionProcessor.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/DecryptionProcessor.java
index a2473f82a..4b5cc4dd6 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/DecryptionProcessor.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/DecryptionProcessor.java
@@ -72,7 +72,8 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
      * 
      * @return the resolver
      */
-    @NonnullAfterInit DecryptionParametersResolver getDecryptionParametersResolver() {
+    @NonnullAfterInit
+    public DecryptionParametersResolver getDecryptionParametersResolver() {
         return decryptionParamsResolver;
     }
 
@@ -91,7 +92,8 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
      * 
      * @return the strategy
      */
-    @NonnullAfterInit Function<ResponseData,List<DecryptionConfiguration>> getDecryptionConfigurationLookupStrategy() {
+    @NonnullAfterInit
+    public Function<ResponseData,List<DecryptionConfiguration>> getDecryptionConfigurationLookupStrategy() {
         return decryptionConfigurationLookupStrategy;
     }
 
diff --git a/shib-attribute-resolver-spring/pom.xml b/shib-attribute-resolver-spring/pom.xml
index 5e9b815b6..2f3781ed6 100644
--- a/shib-attribute-resolver-spring/pom.xml
+++ b/shib-attribute-resolver-spring/pom.xml
@@ -54,6 +54,10 @@
             <artifactId>shib-metadata-spring</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>${opensaml.groupId}</groupId>
+            <artifactId>opensaml-core-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-profile-api</artifactId>
@@ -62,6 +66,10 @@
             <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-security-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>${opensaml.groupId}</groupId>
+            <artifactId>opensaml-saml-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-spring</artifactId>
@@ -134,21 +142,11 @@
             <scope>test</scope>
         </dependency>
 
-        <dependency>
-            <groupId>${opensaml.groupId}</groupId>
-            <artifactId>opensaml-core-api</artifactId>
-            <scope>test</scope>
-        </dependency>
         <dependency>
             <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-messaging-api</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>${opensaml.groupId}</groupId>
-            <artifactId>opensaml-saml-api</artifactId>
-            <scope>test</scope>
-        </dependency>
         <dependency>
             <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-storage-api</artifactId>
diff --git a/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/AbstractDataConnectorParser.java b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/AbstractDataConnectorParser.java
index a6bbacff5..28934a517 100644
--- a/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/AbstractDataConnectorParser.java
+++ b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/AbstractDataConnectorParser.java
@@ -55,7 +55,7 @@ public abstract class AbstractDataConnectorParser extends BaseResolverPluginPars
     @Nonnull @NotEmpty public static final String ATTR_EXPORT_NAMES = "exportAttributes";
     
     /**
-     * Failfast LDAP, Realtional, Stored.
+     * Failfast LDAP, Realtional, Stored, SimpleAggregationSAML.
      */
     @Nonnull @NotEmpty public static final String ATTR_FAIL_FAST = "failFastInitialize";
 
diff --git a/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/AbstractSAMLDataConnectorParser.java b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/AbstractSAMLDataConnectorParser.java
new file mode 100644
index 000000000..04c5fd23f
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/AbstractSAMLDataConnectorParser.java
@@ -0,0 +1,601 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.xml.namespace.QName;
+
+import org.opensaml.core.xml.io.Unmarshaller;
+import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.w3c.dom.Element;
+
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.AttributeAuthorityEntityIDResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.BasicResponseMappingStrategy;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.ExecutableQueryBuilder;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnector;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SelfEntityIDResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SubjectResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.plugin.impl.CriteriaDecryptionConfigurationLookup;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AssertionValidationProcessor;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.DecryptionProcessor;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.SubjectMatchProcessor;
+import net.shibboleth.idp.attribute.resolver.spring.dc.AbstractDataConnectorParser;
+import net.shibboleth.idp.attribute.resolver.spring.dc.impl.CacheConfigParser;
+import net.shibboleth.idp.attribute.resolver.spring.impl.AttributeResolverNamespaceHandler;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.xml.ElementSupport;
+
+/**
+ * Base abstract parser for a {@link SAMLDataConnector}.
+ */
+public abstract class AbstractSAMLDataConnectorParser extends AbstractDataConnectorParser {
+    
+    /** Map of custom property definitions for bean wiring. */
+    @Nonnull private static final Map<String,CustomPropertyDef> CUSTOM_PROPERTY_DEFS = new HashMap<>();
+    static {
+        // General query builder 
+        CUSTOM_PROPERTY_DEFS.put("authorityEndpointResolver",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "EndpointResolver", "bean", true, true));
+        CUSTOM_PROPERTY_DEFS.put("roleDescriptorResolver",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "RoleDescriptorResolver", "bean", true, true));
+        CUSTOM_PROPERTY_DEFS.put("identifierGenerationStrategy",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "IdentifierGenerationStrategy", "bean", true, true));
+
+        // SOAP client
+        CUSTOM_PROPERTY_DEFS.put("SOAPClient",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "SOAPClient", "bean", true, true));
+        CUSTOM_PROPERTY_DEFS.put("SOAPPipelineName",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "SOAPPipelineName", "value", false, true));
+        CUSTOM_PROPERTY_DEFS.put("SOAPClientSecurityConfigurationProfileId",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "SOAPClientSecurityConfigurationProfileId", "value", false, true));
+        
+        // Decryption
+        CUSTOM_PROPERTY_DEFS.put("decryptionConfigurationResolver",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "DecryptionConfigurationResolver", "bean", true, true));
+        CUSTOM_PROPERTY_DEFS.put("decryptionParametersResolver",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "DecryptionParametersResolver", "bean", true, true));
+        CUSTOM_PROPERTY_DEFS.put("securityConfigurationProfileId",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "DecryptionSecurityConfigurationProfileId", "value", false, true));
+        
+        // Assertion validation
+        CUSTOM_PROPERTY_DEFS.put("assertionValidationContextBuilder",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "AssertionValidationContextBuilder", "bean", true, true));
+        CUSTOM_PROPERTY_DEFS.put("assertionValidator",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "AssertionValidator", "bean", true, true));
+
+        // Response mapping/filtering
+        CUSTOM_PROPERTY_DEFS.put("transcoderRegistry",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "AttributeTranscoderRegistry", "bean", true, true));
+        CUSTOM_PROPERTY_DEFS.put("attributeFilterService",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "AttributeFilterService", "bean", true, true));
+        CUSTOM_PROPERTY_DEFS.put("metadataResolver",
+                new CustomPropertyDef(AbstractSAMLDataConnectorParser.class,
+                        "MetadataResolver", "bean", true, true));
+        // Note: Mapping/filtering also uses roleDescriptorResolver, already defined above
+    }
+    
+    /** Map of custom bean properties for bean wiring. */
+    @Nonnull private final Map<String,CustomPropertyValue> resolvedCustomProperties = new HashMap<>();
+    
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractSAMLDataConnectorParser.class);
+    
+    /** Constructor. */
+    protected AbstractSAMLDataConnectorParser() {
+        super();
+        
+        resolveAndStoreCustomProperties(CUSTOM_PROPERTY_DEFS);
+    }
+    
+    /**
+     * Parse the things that are a part of the base data connector class hierarchy.
+     * 
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @param builder the builder to configure
+     */
+    private void parseCommonDataConnector(@Nonnull final Element config, @Nonnull final ParserContext parserContext,
+            @Nonnull final BeanDefinitionBuilder builder) {
+
+        // TODO Do we have the fail-fast concept or not?  Think probably not, since there is no "connection"
+        // to validate like with LDAP and RDBMS.
+        /*
+        if (config.hasAttributeNS(null, ATTR_FAIL_FAST)) {
+            builder.addPropertyValue("failFastInitialize", 
+                    StringSupport.trimOrNull(config.getAttributeNS(null, ATTR_FAIL_FAST)));
+        } else {
+            builder.addPropertyValue("failFastInitialize", FAIL_FAST_DEFAULT);
+        }
+        */
+        
+        final String resultCacheBeanID = CacheConfigParser.getBeanResultCacheID(config);
+        if (null != resultCacheBeanID) {
+            builder.addPropertyReference("resultsCache", resultCacheBeanID);
+         } else {
+             final CacheConfigParser parser = new CacheConfigParser(config);
+             builder.addPropertyValue("resultsCache", parser.createCache());
+         }
+    }
+    
+    /**
+     * Parse the SAML data connector.
+     * 
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @return the bean definition
+     */
+    @Nonnull
+    protected BeanDefinition parseSAMLDataConnector(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+
+        BeanDefinitionBuilder samlBuilder = BeanDefinitionBuilder.genericBeanDefinition(SAMLDataConnector.class);
+        
+        // Because this is a an ID-ed component, must have an ID, so just use a suffixed value
+        // based on owning connector's ID
+        final String ownerID = StringSupport.trimOrNull(config.getAttributeNS(null, "id"));
+        samlBuilder.addPropertyValue("id", ownerID + ".QueryConnector");
+        
+        parseCommonDataConnector(config, parserContext, samlBuilder);
+        
+        samlBuilder.addPropertyValue("executableSearchBuilder", parseQueryBuilder(config, parserContext));
+
+        samlBuilder.addPropertyValue("mappingStrategy", parseMappingStrategy(config, parserContext));
+
+        addCustomProperties(samlBuilder, "SOAPClient");
+        
+        samlBuilder.addPropertyValue("decryptionProcessor",
+                parseDecryptionProcessor(config, parserContext));
+        samlBuilder.addPropertyValue("assertionValidationProcessor",
+                parseAssertionValidationProcessor(config, parserContext));
+        samlBuilder.addPropertyValue("subjectMatchProcessor",
+                parseSubjectMatchProcessor(config, parserContext));
+        
+        if (config.hasAttributeNS(null, "subjectMatch")) {
+            samlBuilder.addPropertyValue("subjectMatch", 
+                    StringSupport.trimOrNull(config.getAttributeNS(null, "subjectMatch")));
+        } else {
+            samlBuilder.addPropertyValue("subjectMatch", false);
+        }
+        
+        samlBuilder.setInitMethodName("initialize");
+        samlBuilder.setDestroyMethodName("destroy");
+        
+        return samlBuilder.getBeanDefinition();
+    }
+    
+    /**
+     * Parse the response mapping strategy.
+     * 
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @return the bean definition
+     */
+    @Nonnull
+    protected BeanDefinition parseMappingStrategy(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+
+        final BeanDefinitionBuilder mappingBuilder =
+                BeanDefinitionBuilder.genericBeanDefinition(BasicResponseMappingStrategy.class);
+        
+        if (config.hasAttributeNS(null, "filterAttributes")) {
+            mappingBuilder.addPropertyValue("filterAttributes",
+                    StringSupport.trimOrNull(config.getAttributeNS(null, "filterAttributes")));
+        } else {
+            mappingBuilder.addPropertyValue("filterAttributes", "true");
+        }
+        
+        addCustomProperties(mappingBuilder, "transcoderRegistry", "metadataResolver", "roleDescriptorResolver",
+                "attributeFilterService");
+        
+        return mappingBuilder.getBeanDefinition();
+    }
+
+    /**
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @return the bean definition
+     */
+    @Nonnull
+    protected BeanDefinition parseQueryBuilder(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+
+        final BeanDefinitionBuilder queryBuilder =
+                BeanDefinitionBuilder.genericBeanDefinition(ExecutableQueryBuilder.class);
+        
+        addCustomProperties(queryBuilder,
+                "authorityEndpointResolver", "roleDescriptorResolver", "identifierGenerationStrategy",
+                "SOAPPipelineName", "SOAPClientSecurityConfigurationProfileId");
+        
+        queryBuilder.addPropertyValue("authorityEntityIDStrategy",
+                getAuthorityEntityIDStrategy(config, parserContext));
+        queryBuilder.addPropertyValue("selfEntityIDStrategy",
+                getSelfEntityIDStrategy(config, parserContext));
+        queryBuilder.addPropertyValue("subjectStrategy",
+                getSubjectStrategy(config, parserContext));
+
+        final Element requestedAttributesParent = 
+                ElementSupport.getFirstChildElement(config,
+                        new QName(AttributeResolverNamespaceHandler.NAMESPACE, "RequestedAttributes"));
+        if (requestedAttributesParent != null) {
+            final List<Element> attributeElems = ElementSupport.getChildElements(
+                    requestedAttributesParent, Attribute.DEFAULT_ELEMENT_NAME);
+            if (! attributeElems.isEmpty()) {
+                final Unmarshaller unmarshaller =
+                        XMLObjectSupport.getUnmarshaller(Attribute.DEFAULT_ELEMENT_NAME);
+                if (unmarshaller == null ) {
+                    throw new BeanCreationException("Could not obtain unmarshaller for SAML 2 Attribute type");
+                }
+                assert unmarshaller != null;
+
+                final List<Attribute> requestedAttributes = new ArrayList<>();
+                for (final Element attributeElem: attributeElems) {
+                    assert attributeElem != null;
+                    try {
+                        final Attribute attribute = (Attribute) unmarshaller.unmarshall(attributeElem);
+                        requestedAttributes.add(attribute);
+                    } catch (final UnmarshallingException e) {
+                        throw new BeanCreationException("Error unmarshalling configured requested SAML 2 Attribute", e);
+                    }
+                }
+                queryBuilder.addPropertyValue("requestedAttributes", requestedAttributes);
+            }
+        }
+        
+        queryBuilder.setInitMethodName("initialize");
+        queryBuilder.setDestroyMethodName("destroy");
+        
+        return queryBuilder.getBeanDefinition();
+    }
+    
+    /**
+     * Parse and return bean definition for {@link DecryptionProcessor}.
+     * 
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @return the bean definition
+     */
+    @Nonnull
+    protected BeanDefinition parseDecryptionProcessor(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+
+        final BeanDefinitionBuilder configLookupBuilder =
+                BeanDefinitionBuilder.genericBeanDefinition(CriteriaDecryptionConfigurationLookup.class);
+        addCustomProperties(configLookupBuilder, "decryptionConfigurationResolver", "securityConfigurationProfileId");
+        configLookupBuilder.setInitMethodName("initialize");
+        configLookupBuilder.setDestroyMethodName("destroy");
+
+        final BeanDefinitionBuilder builder =
+                BeanDefinitionBuilder.genericBeanDefinition(DecryptionProcessor.class);
+
+        builder.addPropertyValue("decryptionConfigurationLookupStrategy", configLookupBuilder.getBeanDefinition());
+        addCustomProperties(builder, "decryptionParametersResolver");
+        
+        builder.setInitMethodName("initialize");
+        builder.setDestroyMethodName("destroy");
+
+        return builder.getBeanDefinition();
+    }
+
+    /**
+     * Parse and return bean definition for {@link AssertionValidationProcessor}.
+     * 
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @return the bean definition
+     */
+    @Nonnull
+    protected BeanDefinition parseAssertionValidationProcessor(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+
+        final BeanDefinitionBuilder builder =
+                BeanDefinitionBuilder.genericBeanDefinition(AssertionValidationProcessor.class);
+
+        addCustomProperties(builder, "assertionValidationContextBuilder", "assertionValidator");
+        
+        builder.setInitMethodName("initialize");
+        builder.setDestroyMethodName("destroy");
+
+        return builder.getBeanDefinition();
+    }
+
+    /**
+     * Parse and return bean definition for {@link SubjectMatchProcessor}.
+     * 
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @return the bean definition
+     */
+    @Nonnull
+    protected BeanDefinition parseSubjectMatchProcessor(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+
+        final BeanDefinitionBuilder builder =
+                BeanDefinitionBuilder.genericBeanDefinition(SubjectMatchProcessor.class);
+
+        // Note this class has no dependencies and is not initializable or destroyable
+
+        return builder.getBeanDefinition();
+    }
+    
+    /**
+     * Get the resolved custom property value, if exists.
+     * 
+     * @param propName the custom property name
+     * 
+     * @return the custom property value, or null
+     */
+    @Nullable
+    protected CustomPropertyValue getResolvedCustomProperty(@Nonnull final String propName) {
+        return resolvedCustomProperties.get(propName);
+    }
+    
+    /**
+     * Add the specified indirected property values to the specified bean definition builder.
+     * 
+     * @param builder the bean definition builder
+     * @param propNames the property names
+     */
+    protected void addCustomProperties(@Nonnull final BeanDefinitionBuilder builder,
+            @Nonnull final String ... propNames) {
+        
+        for (final String propName : propNames) {
+            assert propName != null;
+            final CustomPropertyValue propValue = getResolvedCustomProperty(propName);
+            if (propValue != null) {
+                if (propValue.isReference()) {
+                    builder.addPropertyReference(propName, propValue.getValue());
+                } else {
+                    builder.addPropertyValue(propName, propValue.getValue());
+                }
+            }
+        }
+    }
+    
+    /**
+     * Resolve and store the custom properties indicated by the input map.
+     * 
+     * @param customPropertyDefs the custom property definitions to resolve
+     */
+    protected void resolveAndStoreCustomProperties(@Nonnull final Map<String,CustomPropertyDef> customPropertyDefs) {
+        for (final String beanProp: customPropertyDefs.keySet()) {
+            final CustomPropertyDef def = customPropertyDefs.get(beanProp);
+            final String propName = String.format("%s.%s.%s",
+                    def.getPrefix().getName(), def.getName(), def.getType());
+            assert propName != null;
+            final String propValue = getCustomProperty(propName, def.getDefault());
+            if (propValue != null) {
+                log.debug("For bean property '{}', resolved custom property value: {}", propName, propValue);
+                resolvedCustomProperties.put(beanProp, new CustomPropertyValue(propValue, def.isReference()));
+            } else if (def.isRequired()){
+                throw new BeanCreationException("Required custom property not found: " + propName);
+            }
+        }
+    }
+    
+    /**
+     * Get the bean definition for the {@link AttributeAuthorityEntityIDResolver}.
+     * 
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @return the bean definition
+     */
+    @Nonnull
+    protected abstract BeanDefinition getAuthorityEntityIDStrategy(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext);
+    
+    /**
+     * Get the bean definition for the {@link SelfEntityIDResolver}.
+     * 
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @return the bean definition
+     */
+    @Nonnull
+    protected abstract BeanDefinition getSelfEntityIDStrategy(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext);
+    
+    /**
+     * Get the bean definition for the {@link SubjectResolver}.
+     * 
+     * @param config the configuration element
+     * @param parserContext the current parser context
+     * @return the bean definition
+     */
+    @Nonnull
+    protected abstract BeanDefinition getSubjectStrategy(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext);
+    
+    
+    // Helper classes
+
+    /** A class for representing the definition of a custom property. */
+    protected static class CustomPropertyDef {
+        
+        /** Property class prefix. */
+        @Nonnull private Class<?> prefix;
+        
+        /** Property name. */
+        @Nonnull private String name;
+        
+        /** Property type. */
+        @Nonnull private String type;
+        
+        /** Flag indicating whether property is bean reference. */
+        private boolean reference;
+        
+        /** Flag indicating whether property is required. */
+        private boolean required;
+        
+        @Nullable private String defaultValue;
+
+       /**
+         * Constructor.
+         *
+         * @param prefixClass property class prefix
+         * @param propName property name
+         * @param propType property type
+         * @param isReference is property bean reference
+         * @param isRequired is property required
+         */
+        protected CustomPropertyDef(@Nonnull final Class<?> prefixClass, @Nonnull final String propName,
+                @Nonnull final String propType, final boolean isReference, final boolean isRequired) {
+            prefix = Constraint.isNotNull(prefixClass, "Prefix class was null");
+            name = Constraint.isNotNull(StringSupport.trimOrNull(propName), "Property name was null or empty");
+            type = Constraint.isNotNull(StringSupport.trimOrNull(propType), "Property name was null or empty");
+            reference = isReference;
+            required = isRequired;
+        }
+
+       /**
+         * Constructor.
+         *
+         * @param prefixClass property class prefix
+         * @param propName property name
+         * @param propType property type
+         * @param isReference is property bean reference
+         * @param isRequired is property required
+         * @param defaultVal the default value
+         */
+        protected CustomPropertyDef(@Nonnull final Class<?> prefixClass, @Nonnull final String propName,
+                @Nonnull final String propType, final boolean isReference, final boolean isRequired,
+                @Nullable final String defaultVal) {
+            this(prefixClass, propName, propType, isReference, isRequired);
+            defaultValue = StringSupport.trimOrNull(defaultVal);
+        }
+
+        /**
+         * Get the property class prefix.
+         * 
+         * @return the prefix
+         */
+        @Nonnull public Class<?> getPrefix() {
+            return prefix;
+        }
+
+        /**
+         * Get the property name.
+         * 
+         * @return the name
+         */
+        @Nonnull public String getName() {
+            return name;
+        }
+
+        /**
+         * Get the property type.
+         * 
+         * @return the type
+         */
+        @Nonnull public String getType() {
+            return type;
+        }
+
+        /**
+         * Get the flag indicating whether property is a bean reference.
+         * 
+         * @return the flag
+         */
+        public boolean isReference() {
+            return reference;
+        }
+
+        /**
+         * Get the flag indicating whether property is required.
+         * 
+         * @return the flag
+         */
+        public boolean isRequired() {
+            return required;
+        }
+
+        /**
+         * Get the default value
+         * 
+         * @return the default value
+         */
+        @Nullable public String getDefault() {
+            return defaultValue;
+        }
+
+    }
+    
+    /** A class for representing the value of a custom property. */
+    protected static class CustomPropertyValue {
+        
+        /** Property type. */
+        @Nonnull private String value;
+        
+        /** Flag indicating whether property is bean reference. */
+        private boolean reference;
+        
+        /**
+         * Constructor.
+         *
+         * @param propValue property type
+         * @param isReference is property bean reference
+         */
+        public CustomPropertyValue(@Nonnull final String propValue, final boolean isReference) {
+            value = Constraint.isNotNull(StringSupport.trimOrNull(propValue), "Property value was null or empty");
+            reference = isReference;
+        }
+
+        /**
+         * Get the property value
+         * 
+         * @return the value
+         */
+        @Nonnull public String getValue() {
+            return value;
+        }
+
+        /**
+         * Get the flag indicating whether property is a bean reference.
+         * 
+         * @return the flag
+         */
+        public boolean isReference() {
+            return reference;
+        }
+    
+    }
+    
+}
diff --git a/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/SimpleAggregationSAMLDataConnectorParser.java b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/SimpleAggregationSAMLDataConnectorParser.java
new file mode 100644
index 000000000..3a3d01036
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/SimpleAggregationSAMLDataConnectorParser.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.xml.namespace.QName;
+
+import org.slf4j.Logger;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.ManagedList;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SimpleAggregationSAMLDataConnector;
+import net.shibboleth.idp.attribute.resolver.dc.saml.plugin.impl.ContextAuthorityEntityIDResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.plugin.impl.CriteriaSelfEntityIDResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.plugin.impl.DependencyAttributeSubjectResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDReference;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDSource;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDValue;
+import net.shibboleth.idp.attribute.resolver.spring.impl.AttributeResolverNamespaceHandler;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.spring.util.SpringSupport;
+import net.shibboleth.shared.xml.ElementSupport;
+import net.shibboleth.shared.xml.QNameSupport;
+
+/**
+ * Bean definition Parser for a {@link SimpleAggregationSAMLDataConnector}.
+ */
+public class SimpleAggregationSAMLDataConnectorParser extends AbstractSAMLDataConnectorParser {
+    
+    /** Map of custom property definitions for bean wiring. */
+    @Nonnull private static final Map<String,CustomPropertyDef> CUSTOM_PROPERTY_DEFS = new HashMap<>();
+    static {
+        CUSTOM_PROPERTY_DEFS.put("selfEntityIDResolver",
+                new CustomPropertyDef(SimpleAggregationSAMLDataConnectorParser.class,
+                        "SelfEntityIDResolver", "bean", true, true));
+    }
+    
+    /** Schema type - resolver. */
+    @Nonnull public static final QName TYPE_NAME =
+            new QName(AttributeResolverNamespaceHandler.NAMESPACE, "SimpleAggregationSAML");
+
+    /** QName of <Entity> child element. */
+    private static final QName ENTITY = new QName(AttributeResolverNamespaceHandler.NAMESPACE, "Entity");
+
+    /** QName of <EntityReference> child element. */
+    private static final QName ENTITY_REF = new QName(AttributeResolverNamespaceHandler.NAMESPACE, "EntityReference");
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(SimpleAggregationSAMLDataConnectorParser.class);
+    
+    /** Constructor. */
+    public SimpleAggregationSAMLDataConnectorParser() {
+        resolveAndStoreCustomProperties(CUSTOM_PROPERTY_DEFS); 
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable protected Class<SimpleAggregationSAMLDataConnector> getBeanClass(@Nonnull final Element element) {
+        return SimpleAggregationSAMLDataConnector.class;
+    }
+    
+    /** {@inheritDoc} */
+    @Override protected void doParse(@Nonnull final Element config, @Nonnull final ParserContext parserContext,
+            @Nonnull final BeanDefinitionBuilder builder) {
+
+        super.doParse(config, parserContext, builder);
+        
+        log.debug("{} Parsing XML configuration {}", getLogPrefix(), config);
+
+        builder.addPropertyValue("queryConnector", parseSAMLDataConnector(config, parserContext));
+        
+        final ManagedList<AttributeAuthorityEntityIDSource> entityIDSources = new ManagedList<>();
+        for (final Element child : ElementSupport.getChildElements(config)) {
+            assert child != null;
+            final QName childName = QNameSupport.getNodeQName(child);
+            if (ENTITY.equals(childName)) {
+                final String value = StringSupport.trimOrNull(ElementSupport.getElementContentAsString(child));
+                if (value != null) {
+                    entityIDSources.add(new AttributeAuthorityEntityIDValue(value));
+                }
+            } else if (ENTITY_REF.equals(childName)) {
+                final String value = StringSupport.trimOrNull(ElementSupport.getElementContentAsString(child));
+                if (value != null) {
+                    entityIDSources.add(new AttributeAuthorityEntityIDReference(value));
+                }
+            }
+        }
+        log.debug("{} Saw {} total entityID sources: {}", getLogPrefix(), entityIDSources.size(), entityIDSources);
+        builder.addPropertyValue("entityIDSources", entityIDSources);
+        
+        builder.setInitMethodName("initialize");
+        builder.setDestroyMethodName("destroy");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    protected BeanDefinition getAuthorityEntityIDStrategy(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+        final BeanDefinitionBuilder strategyBuilder = 
+                BeanDefinitionBuilder.genericBeanDefinition(ContextAuthorityEntityIDResolver.class);
+        
+        // Note this class has no dependencies and is not initializable or destroyable
+
+        return strategyBuilder.getBeanDefinition();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    protected BeanDefinition getSelfEntityIDStrategy(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+        final BeanDefinitionBuilder strategyBuilder = 
+                BeanDefinitionBuilder.genericBeanDefinition(CriteriaSelfEntityIDResolver.class);
+
+        addCustomProperties(strategyBuilder, "selfEntityIDResolver");
+        
+        strategyBuilder.setInitMethodName("initialize");
+        strategyBuilder.setDestroyMethodName("destroy");
+
+        return strategyBuilder.getBeanDefinition();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    protected BeanDefinition getSubjectStrategy(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+        final BeanDefinitionBuilder strategyBuilder = 
+                BeanDefinitionBuilder.genericBeanDefinition(DependencyAttributeSubjectResolver.class);
+        
+        final Attr subjectAttributeIDs = config.getAttributeNodeNS(null, "subjectAttributeIDs");
+        if (subjectAttributeIDs != null) {
+            strategyBuilder.addPropertyValue("attributeIDs",
+                    SpringSupport.getAttributeValueAsList(subjectAttributeIDs));
+        }
+
+        if (config.hasAttributeNS(null, "subjectNameIDFormatMapRef")) {
+            final String refValue = StringSupport.trimOrNull(config.getAttributeNS(null, "subjectNameIDFormatMapRef"));
+            if (refValue != null) {
+                strategyBuilder.addPropertyReference("nameIDFormatMap", refValue);
+            }
+        }
+ 
+        if (config.hasAttributeNS(null, "subjectDefaultNameIDFormat")) {
+           strategyBuilder.addPropertyValue("defaultNameIDFormat",
+                   StringSupport.trimOrNull(config.getAttributeNS(null, "subjectDefaultNameIDFormat")));
+        }
+
+        strategyBuilder.setInitMethodName("initialize");
+        strategyBuilder.setDestroyMethodName("destroy");
+
+        return strategyBuilder.getBeanDefinition();
+    }
+    
+}
diff --git a/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java
index 623bd7133..046ff0ee5 100644
--- a/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java
+++ b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java
@@ -44,6 +44,7 @@ import net.shibboleth.idp.attribute.resolver.spring.dc.impl.StoredIdDataConnecto
 import net.shibboleth.idp.attribute.resolver.spring.dc.impl.SubjectDataConnectorParser;
 import net.shibboleth.idp.attribute.resolver.spring.dc.ldap.impl.LDAPDataConnectorParser;
 import net.shibboleth.idp.attribute.resolver.spring.dc.rdbms.impl.RDBMSDataConnectorParser;
+import net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.SimpleAggregationSAMLDataConnectorParser;
 import net.shibboleth.idp.attribute.resolver.spring.dc.storage.impl.StorageServiceDataConnectorParser;
 import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML1Base64AttributeEncoderParser;
 import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML1ScopedStringAttributeEncoderParser;
@@ -122,6 +123,8 @@ public class AttributeResolverNamespaceHandler extends BaseSpringNamespaceHandle
                 new StorageServiceDataConnectorParser());
         registerBeanDefinitionParser(EntityAttributesDataConnectorParser.TYPE_NAME,
                 new EntityAttributesDataConnectorParser());
+        registerBeanDefinitionParser(SimpleAggregationSAMLDataConnectorParser.TYPE_NAME,
+                new SimpleAggregationSAMLDataConnectorParser());
 
 
         // Encoders
diff --git a/shib-attribute-resolver-spring/src/main/resources/schema/shibboleth-attribute-resolver.xsd b/shib-attribute-resolver-spring/src/main/resources/schema/shibboleth-attribute-resolver.xsd
index 82b212670..b00c48776 100644
--- a/shib-attribute-resolver-spring/src/main/resources/schema/shibboleth-attribute-resolver.xsd
+++ b/shib-attribute-resolver-spring/src/main/resources/schema/shibboleth-attribute-resolver.xsd
@@ -1,9 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:resolver="urn:mace:shibboleth:2.0:resolver"
+    xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
     xmlns:sec="urn:mace:shibboleth:2.0:security" targetNamespace="urn:mace:shibboleth:2.0:resolver"
     elementFormDefault="qualified" version="5.0.0">
 
     <import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+    <import namespace="urn:oasis:names:tc:SAML:2.0:assertion" schemaLocation="http://docs.oasis-open.org/security/saml/v2.0/saml-schema-assertion-2.0.xsd"/>
     <import namespace="urn:mace:shibboleth:2.0:security" schemaLocation="http://shibboleth.net/schema/idp/shibboleth-security.xsd"/>
 
     <annotation>
@@ -1803,6 +1805,109 @@
             </extension>
         </complexContent>
     </complexType>
+    
+    <complexType name="AbstractSAMLType" abstract="true">
+        <annotation>
+            <documentation>
+                Abstract type connector for data connectors that pull information from a SAML 2.0 Attribute Authority.
+            </documentation>
+        </annotation>
+        <complexContent>
+            <extension base="resolver:BaseDataConnectorType">
+                <sequence>
+                    <choice maxOccurs="unbounded" minOccurs="0">
+                        <element ref="resolver:InputAttributeDefinition"/>
+                        <element ref="resolver:InputDataConnector"/>                                        
+                        <element ref="resolver:FailoverDataConnector"/>
+                        <element name="ResultCache" type="resolver:CacheConfigType"/>
+                        <element name="ResultCacheBean" type="resolver:string"/>
+                    </choice>
+                    <element name="RequestedAttributes" minOccurs="0" maxOccurs="1">
+                        <annotation>
+                            <documentation>
+                                An optional list of SAML 2 Attributes and values to include in the query.
+                            </documentation>
+                        </annotation>
+                        <complexType>
+                            <choice minOccurs="1" maxOccurs="unbounded">
+                                <element ref="saml2:Attribute" />
+                            </choice>
+                        </complexType>
+                    </element>
+                </sequence>
+                <attribute name="subjectMatch" type="string">
+                    <annotation>
+                        <documentation>
+                            A boolean flag indicating whether to perform matching of Assertion subjects against the value in the query.  
+                            Default value is false.
+                        </documentation>
+                    </annotation>
+                </attribute>
+                <attribute name="filterAttributes" type="string">
+                    <annotation>
+                        <documentation>
+                            A boolean flag indicating whether whether to filter attributes received in the SAML response.
+                            Default value is true.
+                        </documentation>
+                    </annotation>
+                </attribute>
+            </extension>
+        </complexContent>
+    </complexType>
+    
+    <complexType name="SimpleAggregationSAML">
+        <annotation>
+            <documentation>
+                A data connector that can pull and aggregate information from one or more configured SAML 2.0 Attribute Authorities.
+            </documentation>
+        </annotation>
+        <complexContent>
+            <extension base="resolver:AbstractSAMLType">
+                <choice minOccurs="1" maxOccurs="unbounded">
+                    <element name="Entity" type="string">
+                        <annotation>
+                            <documentation>
+                                The value of the element is an entityID of a SAML 2.0 Attribute Authority against which to query .
+                            </documentation>
+                        </annotation>
+                    </element>
+                    <element name="EntityReference" type="string">
+                        <annotation>
+                            <documentation>
+                                The value of the element is the ID of an attribute available for the user.
+                                Each of the attribute's serialized values is interpreted as the entityID of a
+                                SAML 2.0 Attribute Authority as per the Entity element.
+                            </documentation>
+                        </annotation>
+                    </element>
+                </choice>
+                <attribute name="subjectAttributeIDs" type="string">
+                    <annotation>
+                        <documentation>
+                            The list of dependency attribute IDs to search for a value used to construct
+                            the AttributeQuery Subject NameID value used in the SAML query.
+                        </documentation>
+                    </annotation>
+                </attribute>
+                <attribute name="subjectNameIDFormatMapRef" type="string">
+                    <annotation>
+                        <documentation>
+                            An optional map allowing to customize the NameID format URI on a per attribute ID basis.
+                        </documentation>
+                    </annotation>
+                </attribute>
+                <attribute name="subjectDefaultNameIDFormat" type="string">
+                    <annotation>
+                        <documentation>
+                            The default format URI used to construct the Subject NameID if a custom format for the attribute ID
+                            is not defined in the map configured via subjectNameIDFormatMapRef.
+                        </documentation>
+                    </annotation>
+                </attribute>
+            </extension>
+        </complexContent>
+    </complexType>
+    
 
     <!--  Support types for DataConnectors  -->
 
diff --git a/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockAttributeFilterService.java b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockAttributeFilterService.java
new file mode 100644
index 000000000..69de73190
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockAttributeFilterService.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.attribute.filter.AttributeFilter;
+import net.shibboleth.idp.attribute.filter.AttributeFilterException;
+import net.shibboleth.idp.attribute.filter.AttributeFilterPolicy;
+import net.shibboleth.idp.attribute.filter.context.AttributeFilterContext;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.service.ReloadableService;
+import net.shibboleth.shared.service.ServiceException;
+import net.shibboleth.shared.service.ServiceableComponent;
+
+/**
+ * Dummy implementation to satisfy wiring requirements.
+ */
+public class MockAttributeFilterService implements ReloadableService<AttributeFilter> {
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isInitialized() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void initialize() throws ComponentInitializationException {
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public Instant getLastSuccessfulReloadInstant() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public Instant getLastReloadAttemptInstant() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public Throwable getReloadFailureCause() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void reload() {
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public ServiceableComponent<AttributeFilter> getServiceableComponent() throws ServiceException {
+        return new ServiceableComponent<AttributeFilter>() {
+            
+            @Override
+            @Nonnull
+            public AttributeFilter getComponent() {
+                return new AttributeFilter() {
+                    
+                    @Override
+                    @Nullable
+                    public String getId() {
+                        return null;
+                    }
+                    
+                    @Override
+                    @Nonnull
+                    public List<AttributeFilterPolicy> getFilterPolicies() {
+                        return new ArrayList<>();
+                    }
+                    
+                    @Override
+                    public void filterAttributes(@Nonnull AttributeFilterContext filterContext) throws AttributeFilterException {
+                        
+                    }
+                };
+            }
+            
+            @Override
+            public void close() {
+            }
+        };
+    }
+
+}
diff --git a/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockDecryptionConfigurationResolver.java b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockDecryptionConfigurationResolver.java
new file mode 100644
index 000000000..8641e06f7
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockDecryptionConfigurationResolver.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.xmlsec.DecryptionConfiguration;
+
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.Resolver;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * Dummy implementation to satisfy wiring requirements.
+ */
+public class MockDecryptionConfigurationResolver implements Resolver<List<DecryptionConfiguration>, CriteriaSet> {
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public Iterable<List<DecryptionConfiguration>> resolve(@Nullable CriteriaSet criteria) throws ResolverException {
+        return CollectionSupport.emptyList();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public List<DecryptionConfiguration> resolveSingle(@Nullable CriteriaSet criteria) throws ResolverException {
+        return null;
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDReference.java b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockSOAPClient.java
similarity index 51%
copy from shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDReference.java
copy to shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockSOAPClient.java
index 3e295d683..23d3a9160 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AttributeAuthorityEntityIDReference.java
+++ b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockSOAPClient.java
@@ -12,23 +12,25 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.attribute.resolver.dc.saml.util.impl;
+package net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.security.SecurityException;
+import org.opensaml.soap.client.SOAPClient;
+import org.opensaml.soap.common.SOAPException;
+
 /**
- * Sub-type of {@link AttributeAuthorityEntityIDSource} which specifies a reference
- * to an entityID in the form of an attribute resolver dependency attribute name.
+ * Dummy implementation to satisfy wiring requirements.
  */
-public class AttributeAuthorityEntityIDReference extends AttributeAuthorityEntityIDSource {
+public class MockSOAPClient implements SOAPClient {
 
-    /**
-     * Constructor.
-     *
-     * @param reference the authority entityID reference (dependency attribute name)
-     */
-    protected AttributeAuthorityEntityIDReference(@Nonnull final String reference) {
-        super(reference);
+    /** {@inheritDoc} */
+    @Override
+    public void send(@Nonnull final String endpoint, @Nonnull final InOutOperationContext context)
+            throws SOAPException, SecurityException {
+        // Do nothing
     }
 
 }
diff --git a/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockSelfEntityIDResolver.java b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockSelfEntityIDResolver.java
new file mode 100644
index 000000000..f9f7f1f66
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/MockSelfEntityIDResolver.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.Resolver;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * Dummy implementation to satisfy wiring requirements.
+ */
+public class MockSelfEntityIDResolver implements Resolver<String, CriteriaSet> {
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public Iterable<String> resolve(@Nullable CriteriaSet criteria) throws ResolverException {
+        return CollectionSupport.emptyList();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public String resolveSingle(@Nullable CriteriaSet criteria) throws ResolverException {
+        return null;
+    }
+
+}
diff --git a/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/SimpleAggregationSAMLDataConnectorParserTest.java b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/SimpleAggregationSAMLDataConnectorParserTest.java
new file mode 100644
index 000000000..a045bfa63
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/saml/impl/SimpleAggregationSAMLDataConnectorParserTest.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.mock.env.MockPropertySource;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.resolver.dc.ExecutableSearchBuilder;
+import net.shibboleth.idp.attribute.resolver.dc.saml.ExecutableQuery;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.BasicResponseMappingStrategy;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.ExecutableQueryBuilder;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnector;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SimpleAggregationSAMLDataConnector;
+import net.shibboleth.idp.attribute.resolver.dc.saml.plugin.impl.ContextAuthorityEntityIDResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.plugin.impl.CriteriaSelfEntityIDResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.plugin.impl.DependencyAttributeSubjectResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDReference;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDSource;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDValue;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.spring.resource.PreferFileSystemResourceLoader;
+import net.shibboleth.shared.spring.util.ApplicationContextBuilder;
+
+/** Test for {@link SimpleAggregationSAMLDataConnector}. */
+ at SuppressWarnings("javadoc")
+public class SimpleAggregationSAMLDataConnectorParserTest extends XMLObjectBaseTestCase {
+
+    private GenericApplicationContext pendingTeardownContext = null;
+    
+    @AfterMethod public void tearDownTestContext() {
+        if (null == pendingTeardownContext ) {
+            return;
+        }
+        pendingTeardownContext.close();
+        pendingTeardownContext = null;
+    }
+    
+    private void setTestContext(final GenericApplicationContext context) {
+        tearDownTestContext();
+        pendingTeardownContext = context;
+    }
+    
+    @Test public void basicConfig() throws Exception {
+        
+        final MockPropertySource propSource = new MockPropertySource();
+        
+        final SimpleAggregationSAMLDataConnector connector =
+                getDataConnector(propSource,
+                        "net/shibboleth/idp/attribute/resolver/spring/dc/saml/resolver/saml-attribute-resolver-v2.xml");
+        assertNotNull(connector);
+        
+        final List<AttributeAuthorityEntityIDSource> sources = connector.getEntityIDSources();
+        Assert.assertNotNull(sources);
+        Assert.assertEquals(sources.size(), 3);
+        Assert.assertTrue(AttributeAuthorityEntityIDReference.class.isInstance(sources.get(0)));
+        Assert.assertEquals(sources.get(0).getValue(), "VO1");
+        Assert.assertTrue(AttributeAuthorityEntityIDValue.class.isInstance(sources.get(1)));
+        Assert.assertEquals(sources.get(1).getValue(), "https://aa.example.org");
+        Assert.assertTrue(AttributeAuthorityEntityIDReference.class.isInstance(sources.get(2)));
+        Assert.assertEquals(sources.get(2).getValue(), "VO2");
+
+        final SAMLDataConnector queryConnector = connector.getQueryConnector();
+        Assert.assertNotNull(queryConnector);
+
+        Assert.assertNotNull(queryConnector.getAssertionValidationProcessor());
+        Assert.assertNotNull(queryConnector.getAssertionValidationProcessor().getAssertionValidatorLookup());
+        Assert.assertNotNull(queryConnector.getAssertionValidationProcessor().getAssertionValidationContextBuilder());
+
+        Assert.assertNotNull(queryConnector.getDecryptionProcessor());
+        Assert.assertNotNull(queryConnector.getDecryptionProcessor().getDecryptionConfigurationLookupStrategy());
+        Assert.assertNotNull(queryConnector.getDecryptionProcessor().getDecryptionParametersResolver());
+
+        Assert.assertNotNull(queryConnector.getMappingStrategy());
+        Assert.assertTrue(BasicResponseMappingStrategy.class.isInstance(queryConnector.getMappingStrategy()));
+        final BasicResponseMappingStrategy mappingStrategy = BasicResponseMappingStrategy.class.cast(queryConnector.getMappingStrategy());
+        Assert.assertNotNull(mappingStrategy.getAttributeFilterService());
+        Assert.assertNotNull(mappingStrategy.getMetadataResolver());
+        Assert.assertNotNull(mappingStrategy.getRoleDescriptorResolver());
+        Assert.assertNotNull(mappingStrategy.getTranscoderRegistry());
+        Assert.assertTrue(mappingStrategy.isFilterAttributes());
+
+        Assert.assertNotNull(queryConnector.getResultsCache());
+
+        Assert.assertNotNull(queryConnector.getSOAPClient());
+        Assert.assertTrue(queryConnector.isSubjectMatch());
+        Assert.assertNotNull(queryConnector.getSubjectMatchProcessor());
+        Assert.assertNotNull(queryConnector.getValidator());
+
+        final ExecutableSearchBuilder<ExecutableQuery> queryBuilder = queryConnector.getExecutableSearchBuilder();
+        Assert.assertNotNull(queryBuilder);
+        
+        final  ExecutableQueryBuilder samlBuilder = ExecutableQueryBuilder.class.cast(queryBuilder);
+        
+        Assert.assertNotNull(samlBuilder.getAuthorityEndpointResolver());
+        Assert.assertNotNull(samlBuilder.getIdentifierGenerationStrategy());
+        Assert.assertNotNull(samlBuilder.getRoleDescriptorResolver());
+        Assert.assertEquals(samlBuilder.getSOAPClientSecurityConfigurationProfileId(), "http://shibboleth.net/ns/profiles/saml2/query/attribute");
+        Assert.assertEquals(samlBuilder.getSOAPPipelineName(), "SAML2.AttributeQuery");
+
+        Assert.assertTrue(ContextAuthorityEntityIDResolver.class.isInstance(samlBuilder.getAuthorityEntityIDStrategy()));
+
+        Assert.assertTrue(CriteriaSelfEntityIDResolver.class.isInstance(samlBuilder.getSelfEntityIDStrategy()));
+        final CriteriaSelfEntityIDResolver selfIDResolver = CriteriaSelfEntityIDResolver.class.cast(samlBuilder.getSelfEntityIDStrategy());
+        Assert.assertNotNull(selfIDResolver.getSelfEntityIDResolver());
+
+        Assert.assertTrue(DependencyAttributeSubjectResolver.class.isInstance(samlBuilder.getSubjectStrategy()));
+        final DependencyAttributeSubjectResolver subjectResolver = DependencyAttributeSubjectResolver.class.cast(samlBuilder.getSubjectStrategy());
+        Assert.assertEquals(subjectResolver.getAttributeIDs(), List.of("email"));
+        Assert.assertEquals(subjectResolver.getDefaultNameIDFormat(), "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
+        Assert.assertEquals(subjectResolver.getNameIDFormatMap(), Map.of("email", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"));
+
+        Assert.assertNotNull(samlBuilder.getRequestedAttributes());
+        Assert.assertEquals(samlBuilder.getRequestedAttributes().size(), 1);
+        final Attribute samlAttribute = samlBuilder.getRequestedAttributes().get(0);
+        Assert.assertEquals(samlAttribute.getFriendlyName(), "mail");
+        Assert.assertEquals(samlAttribute.getName(), "urn:oid:0.9.2342.19200300.100.1.3");
+        Assert.assertEquals(samlAttribute.getNameFormat(), "urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
+    }
+    
+    private SimpleAggregationSAMLDataConnector getDataConnector(final PropertySource<?> propSource, final String... beanDefinitions)
+            throws IOException {
+
+        final ResourceLoader loader = new PreferFileSystemResourceLoader();
+        
+        final ApplicationContextBuilder builder = new ApplicationContextBuilder();
+        builder.setName("ApplicationContext: " + SimpleAggregationSAMLDataConnectorParserTest.class);
+
+        final Collection<String> defs = new ArrayList<>(Arrays.asList(beanDefinitions));
+        defs.add("net/shibboleth/idp/attribute/resolver/spring/dc/saml/spring-beans.xml");
+
+        builder.setServiceConfigurations(defs.
+                                         stream().
+                                         map(s -> {assert s != null; return loader.getResource(s);}).
+                                         collect(CollectionSupport.nonnullCollector(Collectors.toList())).
+                                         get());
+        if (propSource != null) {
+            builder.setPropertySources(CollectionSupport.singletonList(propSource));
+        }
+        
+        final GenericApplicationContext context = builder.build();
+
+        setTestContext(context);
+                
+        return (SimpleAggregationSAMLDataConnector) context.getBean("mySAML");
+    }
+
+}
diff --git a/shib-attribute-resolver-spring/src/test/resources/META-INF/net/shibboleth/spring/parser.properties b/shib-attribute-resolver-spring/src/test/resources/META-INF/net/shibboleth/spring/parser.properties
new file mode 100644
index 000000000..200228584
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/test/resources/META-INF/net/shibboleth/spring/parser.properties
@@ -0,0 +1,21 @@
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.EndpointResolver.bean= shibboleth.EndpointResolver
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.RoleDescriptorResolver.bean= shibboleth.RoleDescriptorResolver
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.IdentifierGenerationStrategy.bean= shibboleth.DefaultIdentifierGenerationStrategy
+
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.SOAPClient.bean= shibboleth.SOAPClient.SAML
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.SOAPPipelineName.value = SAML2.AttributeQuery
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.SOAPClientSecurityConfigurationProfileId.value = http://shibboleth.net/ns/profiles/saml2/query/attribute
+
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.DecryptionParametersResolver.bean= shibboleth.DecryptionParametersResolver
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.DecryptionConfigurationResolver.bean= shibboleth.SAMLDataConnector.DecryptionConfigurationResolver
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.DecryptionSecurityConfigurationProfileId.value = http://shibboleth.net/ns/profiles/saml2/query/attribute
+
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.AssertionValidationContextBuilder.bean= shibboleth.SAMLDataConnector.AssertionValidationContextBuilder
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.AssertionValidator.bean= shibboleth.SAMLDataConnector.AssertionValidator
+
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.AttributeTranscoderRegistry.bean= shibboleth.AttributeRegistryService
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.AttributeFilterService.bean= shibboleth.AttributeFilterService
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.AbstractSAMLDataConnectorParser.MetadataResolver.bean= shibboleth.MetadataResolver
+# Note RoleDescriptorResolver already defined above
+
+net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.SimpleAggregationSAMLDataConnectorParser.SelfEntityIDResolver.bean = shibboleth.SAMLDataConnector.SelfEntityIDResolver 
\ No newline at end of file
diff --git a/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/saml/resolver/saml-attribute-resolver-v2.xml b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/saml/resolver/saml-attribute-resolver-v2.xml
new file mode 100644
index 000000000..1ba635383
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/saml/resolver/saml-attribute-resolver-v2.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<AttributeResolver 
+            xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+            xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd">
+
+    <DataConnector id="mySAML" xsi:type="SimpleAggregationSAML"
+        subjectMatch="true"
+        subjectAttributeIDs="email"
+        subjectNameIDFormatMapRef="SAML.SubjectNameIDFormatMap"
+        subjectDefaultNameIDFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
+    >
+    
+        <ResultCache expireAfterAccess="PT10S" maximumCachedElements="25"/>
+
+        <RequestedAttributes xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
+            <saml2:Attribute FriendlyName="mail" Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" />
+        </RequestedAttributes>
+
+        <EntityReference>VO1</EntityReference>
+        <Entity>https://aa.example.org</Entity>
+        <EntityReference>VO2</EntityReference>
+
+    </DataConnector>
+    
+</AttributeResolver>
diff --git a/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/saml/spring-beans.xml b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/saml/spring-beans.xml
new file mode 100644
index 000000000..8ec9c2064
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/saml/spring-beans.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:util="http://www.springframework.org/schema/util"
+    xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:c="http://www.springframework.org/schema/c"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
+                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+    default-init-method="initialize">
+
+    <bean id="shibboleth.DefaultIdentifierGenerationStrategy"
+            class="net.shibboleth.shared.security.IdentifierGenerationStrategy" factory-method="getInstance">
+        <constructor-arg>
+            <util:constant
+                static-field="net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType.SECURE" />
+        </constructor-arg>
+    </bean>
+    
+    <bean id="shibboleth.EndpointResolver" class="org.opensaml.saml.common.binding.impl.DefaultEndpointResolver"
+        p:inMetadataOrder="%{idp.bindings.inMetadataOrder:true}" />
+        
+    <bean id="shibboleth.RoleDescriptorResolver"
+        class="org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver"
+        c:mdResolver-ref="shibboleth.MetadataResolver" />   
+        
+    <bean id="shibboleth.MetadataResolver" p:id="myMetadataResolver" class="org.opensaml.saml.metadata.resolver.ChainingMetadataResolver">
+    </bean>
+
+    <bean id="shibboleth.SOAPClient.SAML" class="net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.MockSOAPClient">
+    </bean>
+
+    <bean id="shibboleth.SAMLDataConnector.SelfEntityIDResolver" class="net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.MockSelfEntityIDResolver">
+    </bean>
+
+    <bean id="shibboleth.SAMLDataConnector.DecryptionConfigurationResolver" class="net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.MockDecryptionConfigurationResolver">
+    </bean>
+
+    <bean id="shibboleth.DecryptionParametersResolver" class="org.opensaml.xmlsec.impl.BasicDecryptionParametersResolver">
+    </bean>
+    
+    <bean id="shibboleth.SAMLDataConnector.AssertionValidationContextBuilder" class="org.opensaml.saml.saml2.assertion.messaging.impl.DefaultAssertionValidationContextBuilder">
+    </bean>
+
+    <bean id="shibboleth.SAMLDataConnector.AssertionValidator" class="org.opensaml.saml.saml2.assertion.SAML20AssertionValidator">
+        <constructor-arg><list/></constructor-arg>
+        <constructor-arg><list/></constructor-arg>
+        <constructor-arg><list/></constructor-arg>
+        <constructor-arg value="#{null}"/>
+        <constructor-arg value="#{null}"/>
+        <constructor-arg value="#{null}"/>
+    </bean>
+
+    <bean id="shibboleth.AttributeRegistryService" class="net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl" >
+        <property name="id" value="myAttributeRegistry" />
+    </bean>
+
+    <bean id="shibboleth.AttributeFilterService" class="net.shibboleth.idp.attribute.resolver.spring.dc.saml.impl.MockAttributeFilterService">
+    </bean>
+    
+    <util:map id="SAML.SubjectNameIDFormatMap">
+        <entry key="email" value="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
+    </util:map>
+
+</beans>
\ No newline at end of file

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


More information about the commits mailing list