[java-shib-attribute] branch main updated: JSATTR-6: SAML AttributeQuery DataConnector

Codeberg noreply at shibboleth.net
Mon Dec 15 07:11:14 UTC 2025


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

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

View the commit online:
https://codeberg.org/Shibboleth/java-shib-attribute/commit/3e854b0d2a592a9257331484f17e288d667d5035

The following commit(s) were added to refs/heads/main by this push:
     new 3e854b0d2 JSATTR-6: SAML AttributeQuery DataConnector
3e854b0d2 is described below

commit 3e854b0d2a592a9257331484f17e288d667d5035
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Wed Dec 3 22:51:15 2025 -0500

    JSATTR-6: SAML AttributeQuery DataConnector
    
    Unit tests and associated bug fixes.
---
 shib-attribute-resolver-impl/pom.xml               |  18 +
 .../dc/saml/impl/ExecutableQueryBuilder.java       | 193 +++++---
 .../resolver/dc/saml/impl/SAMLDataConnector.java   |  36 +-
 .../impl/DependencyAttributeSubjectResolver.java   |  13 +-
 .../dc/saml/util/impl/DecryptionProcessor.java     |  57 ++-
 .../dc/saml/util/impl/SubjectMatchProcessor.java   |  56 ++-
 .../dc/saml/impl/BaseSAMLDataConnectorTest.java    | 262 +++++++++++
 .../impl/BasicResponseMappingStrategyTest.java     | 409 +++++++++++++++++
 .../dc/saml/impl/ExecutableQueryBuilderTest.java   | 376 ++++++++++++++++
 .../dc/saml/impl/SAMLDataConnectorTest.java        | 290 ++++++++++++
 .../saml/impl/SAMLDataConnectorTestingSupport.java | 496 +++++++++++++++++++++
 .../SimpleAggregationSAMLDataConnectorTest.java    | 265 +++++++++++
 .../ChainingAuthorityEntityIDResolverTest.java     |  76 ++++
 .../impl/ChainingSelfEntityIDResolverTest.java     |  80 ++++
 .../plugin/impl/ChainingSubjectResolverTest.java   |  84 ++++
 .../impl/ContextAuthorityEntityIDResolverTest.java |  69 +++
 .../CriteriaDecryptionConfigurationLookupTest.java | 117 +++++
 .../impl/CriteriaSelfEntityIDResolverTest.java     | 105 +++++
 .../DependencyAttributeSubjectResolverTest.java    | 156 +++++++
 .../impl/AssertionValidationProcessorTest.java     | 165 +++++++
 .../dc/saml/util/impl/DecryptionProcessorTest.java | 395 ++++++++++++++++
 .../saml/util/impl/SubjectMatchProcessorTest.java  | 186 ++++++++
 22 files changed, 3778 insertions(+), 126 deletions(-)

diff --git a/shib-attribute-resolver-impl/pom.xml b/shib-attribute-resolver-impl/pom.xml
index cf7d2d144..360519108 100644
--- a/shib-attribute-resolver-impl/pom.xml
+++ b/shib-attribute-resolver-impl/pom.xml
@@ -116,6 +116,18 @@
             <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>shib-attribute-filter-impl</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>shib-saml-attribute-impl</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
             <artifactId>shib-attribute-testing</artifactId>
@@ -186,6 +198,12 @@
             <artifactId>nashorn-core</artifactId>
             <scope>test</scope>
         </dependency>
+        
+        <dependency>
+            <groupId>${spring.groupId}</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
 
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 ea09d9ddb..9be2efd0d 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
@@ -80,7 +80,7 @@ import net.shibboleth.shared.security.IdentifierGenerationStrategy;
  */
 public class ExecutableQueryBuilder extends AbstractInitializableComponent
         implements ExecutableSearchBuilder<ExecutableQuery> {
-    
+
     /** Logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(ExecutableQueryBuilder.class);
     
@@ -327,7 +327,6 @@ public class ExecutableQueryBuilder extends AbstractInitializableComponent
     }
 
     /** {@inheritDoc} */
-    // Checkstyle: MethodLength OFF
     @Override
     @Nonnull
     public ExecutableQuery build(@Nonnull final AttributeResolutionContext resolutionContext,
@@ -342,74 +341,9 @@ public class ExecutableQueryBuilder extends AbstractInitializableComponent
         final InOutOperationContext opContext = buildOperationContext(resolutionContext, dependencyAttributes,
                 authorityRoleDescriptor, authorityEndpoint);
             
-        // Checkstyle: AnonInnerLength OFF
-        return new ExecutableQuery() {
-
-            /** Logger. */
-            @Nonnull private final Logger log = LoggerFactory.getLogger(ExecutableQuery.class);
-                
-            @Nullable public String getResultCacheKey() {
-                final MessageContext outbound = opContext.getOutboundMessageContext();
-                assert outbound != null;
-                final AttributeQuery query = (AttributeQuery) outbound.getMessage();
-                assert query != null;
-                final Subject subject = query.getSubject();
-                assert subject != null;
-                final NameID nameid = subject.getNameID();
-                assert nameid != null;
-                final List<String> attribNames = query.getAttributes().stream()
-                        .map(Attribute::getName)
-                        .sorted()
-                        .toList();
-
-                final StringBuilder builder = new StringBuilder();
-                
-                builder.append(authorityEntityID);
-                builder.append(":");
-                builder.append(nameid.getValue());
-                if (attribNames.size() > 0) {
-                    builder.append(":");
-                    builder.append(StringSupport.listToStringValue(attribNames, ","));
-                }
-
-                return builder.toString();
-            }
-
-            /** {@inheritDoc} */
-            public String toString() {
-                return getResultCacheKey();
-            }
-
-            @Nonnull public ResponseData execute(@Nonnull final SOAPClient soapClient)
-                    throws SAMLException, SOAPException, SecurityException {
-                
-                log.trace("Executing AttributeQuery over SOAP 1.1 binding to endpoint: {}", authorityEndpoint);
-                soapClient.send(authorityEndpoint, opContext); 
-
-                final MessageContext inboundContext = opContext.getInboundMessageContext();
-                final Object message = inboundContext != null ? inboundContext.getMessage() : null;
-                if (message instanceof Response response) {
-                    validateResponse(response);
-                    return new ResponseData(response, opContext, resolutionContext, dependencyAttributes,
-                            authorityRoleDescriptor);
-                }
-                throw new SOAPException("SOAP message payload was not an instance of Response: " 
-                        + (message != null ? message.getClass().getName() : "(null)"));
-            }
-
-            private void validateResponse(@Nonnull final Response response) throws SAMLException {
-                final Status status = response.getStatus();
-                final StatusCode statusCode = status != null ? status.getStatusCode() : null;
-                if (statusCode == null || statusCode.getValue() == null) {
-                    throw new SAMLException("Response included no StatusCode, could not validate");
-                } else if (!StatusCode.SUCCESS.equals(statusCode.getValue())){
-                    throw new SAMLException("Response carried non-success StatusCode: " + statusCode.getValue());
-                }
-            }
-        };
-        // Checkstyle: AnonInnerLength ON
+        return new StandardExecutableQuery(authorityEntityID, authorityRoleDescriptor, authorityEndpoint, opContext,
+                resolutionContext, dependencyAttributes);
     }
-    // Checkstyle: MethodLength ON
 
     /**
      * Resolve the attribute authority entityID.
@@ -670,5 +604,126 @@ public class ExecutableQueryBuilder extends AbstractInitializableComponent
 
         return attributes;
     }
+    
+    /**
+    * Standard implementation of the {@link ExecutableQuery}.
+    */
+    private final class StandardExecutableQuery implements ExecutableQuery {
+
+       /** Attribute authority entityID. */
+       @Nonnull private final String authorityEntityID;
+
+       /** Attribute authority role descriptor. */
+       @Nonnull private final AttributeAuthorityDescriptor authorityRoleDescriptor;
+
+       /** Attribute authority endpoint. */
+       @Nonnull private final String authorityEndpoint;
+
+       /** SOAP client context. */
+       @Nonnull private final InOutOperationContext opContext;
+
+       /** Attribute resolution context. */
+       @Nonnull private final AttributeResolutionContext resolutionContext;
+
+       /** Dependency attributes. */
+       @Nonnull private final Map<String, List<IdPAttributeValue>> dependencyAttributes;
+
+       /** Logger. */
+       @Nonnull private final Logger log = LoggerFactory.getLogger(StandardExecutableQuery.class);
+
+       /**
+        * Constructor.
+        *
+        * @param entityID Attribute authority entityID
+        * @param roleDescriptor Attribute authority role descriptor
+        * @param endpoint Attribute authority endpoint.
+        * @param clientContext SOAP client context
+        * @param arc Attribute resolution context
+        * @param dependencies  dependency attributes
+        */
+       // Checkstyle: ParameterNumber OFF
+       private StandardExecutableQuery(@Nonnull final String entityID,
+               @Nonnull final AttributeAuthorityDescriptor roleDescriptor,
+               @Nonnull final String endpoint,
+               @Nonnull final InOutOperationContext clientContext,
+               @Nonnull final AttributeResolutionContext arc,
+               @Nonnull final Map<String, List<IdPAttributeValue>> dependencies) {
+           authorityEntityID = entityID;
+           authorityEndpoint = endpoint;
+           resolutionContext = arc;
+           opContext = clientContext;
+           dependencyAttributes = dependencies;
+           authorityRoleDescriptor = roleDescriptor;
+       }
+       // Checkstyle: ParameterNumber ON
+
+       /** {@inheritDoc} */
+       @Nullable public String getResultCacheKey() {
+           final MessageContext outbound = opContext.getOutboundMessageContext();
+           assert outbound != null;
+           final AttributeQuery query = (AttributeQuery) outbound.getMessage();
+           assert query != null;
+           final Subject subject = query.getSubject();
+           assert subject != null;
+           final NameID nameid = subject.getNameID();
+           assert nameid != null;
+           final List<String> attribNames = query.getAttributes().stream()
+                   .map(Attribute::getName)
+                   .sorted()
+                   .toList();
+
+           final StringBuilder builder = new StringBuilder();
+           
+           builder.append(authorityEntityID);
+           builder.append(":");
+           builder.append(nameid.getValue());
+           if (attribNames.size() > 0) {
+               builder.append(":");
+               builder.append(StringSupport.listToStringValue(attribNames, ","));
+           }
+
+           return builder.toString();
+       }
+
+       /** {@inheritDoc} */
+       public String toString() {
+           return getResultCacheKey();
+       }
+
+       /** {@inheritDoc} */
+       @Nonnull public ResponseData execute(@Nonnull final SOAPClient soapClient)
+               throws SAMLException, SOAPException, SecurityException {
+           
+           log.trace("Executing AttributeQuery over SOAP 1.1 binding to endpoint: {}", authorityEndpoint);
+           soapClient.send(authorityEndpoint, opContext); 
+
+           final MessageContext inboundContext = opContext.getInboundMessageContext();
+           final Object message = inboundContext != null ? inboundContext.getMessage() : null;
+           if (message instanceof Response response) {
+               validateResponse(response);
+               return new ResponseData(response, opContext, resolutionContext, dependencyAttributes,
+                       authorityRoleDescriptor);
+           }
+           throw new SOAPException("SOAP message payload was not an instance of Response: " 
+                   + (message != null ? message.getClass().getName() : "(null)"));
+       }
+
+       /**
+        * Validate the response.
+        * 
+        * @param response the response to validate
+        * 
+        * @throws SAMLException if response is not valid
+        */
+       private void validateResponse(@Nonnull final Response response) throws SAMLException {
+           final Status status = response.getStatus();
+           final StatusCode statusCode = status != null ? status.getStatusCode() : null;
+           if (statusCode == null || statusCode.getValue() == null) {
+               throw new SAMLException("Response included no StatusCode, could not validate");
+           } else if (!StatusCode.SUCCESS.equals(statusCode.getValue())){
+               throw new SAMLException("Response carried non-success StatusCode: " + statusCode.getValue());
+           }
+       }
+   }
 
 }
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 e6a2a7a73..cae1753c5 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
@@ -25,6 +25,7 @@ import org.opensaml.saml.saml2.encryption.Decrypter;
 import org.opensaml.security.SecurityException;
 import org.opensaml.soap.client.SOAPClient;
 import org.opensaml.soap.common.SOAPException;
+import org.opensaml.xmlsec.encryption.support.DecryptionException;
 import org.slf4j.Logger;
 
 import net.shibboleth.idp.attribute.IdPAttribute;
@@ -198,6 +199,8 @@ public class SAMLDataConnector extends AbstractSearchDataConnector<ExecutableQue
     @Nullable
     protected Map<String, IdPAttribute> retrieveAttributes(@Nonnull final ExecutableQuery executable)
             throws ResolutionException {
+        checkComponentActive();
+
         try {
             final ResponseMappingStrategy strategy = getMappingStrategy();
             final SOAPClient localClient = getSOAPClient();
@@ -228,27 +231,42 @@ public class SAMLDataConnector extends AbstractSearchDataConnector<ExecutableQue
         // Encryption on an AttributeQuery response probably isn't that common, so be efficient.
         // Goal is to only build the Decrypter if/when needed, and only do it once
         // for both Assertions and Assertion content.
-        final DecryptionProcessor decryptionProcessor = getDecryptionProcessor();
         Decrypter decrypter = null;
-        if (decryptionProcessor.haveEncryptedAssertions(response)) {
-            decrypter = decryptionProcessor.buildDecrypter(responseData);
-            decryptionProcessor.decryptAssertions(response, decrypter);
+        if (getDecryptionProcessor().haveEncryptedAssertions(response)) {
+            try {
+                decrypter = getDecryptionProcessor().buildDecrypter(responseData);
+                getDecryptionProcessor().decryptAssertions(response, decrypter);
+            } catch (final DecryptionException e) {
+                log.warn("{} Fatal error building Decrypter, can not decrypt EncryptedAssertions", getLogPrefix());
+            }
+
+        }
+
+        if (response.getAssertions().isEmpty()) {
+            log.warn("{} SAML Response contained no unencrypted Assertions processable for attribute extraction",
+                    getLogPrefix());
+            return;
         }
         
         getAssertionValidationProcessor().validateAssertions(response, responseData.getSOAPClientContext());
         if (response.getAssertions().isEmpty()) {
             log.warn("{} SAML Response contained no valid Assertions", getLogPrefix());
-            throw new ResolutionException("SAML Response contained no valid Assertions");
+            return;
         }
         
-        if (decryptionProcessor.haveEncryptedContent(response)) {
-            if (decrypter == null) {
-                decrypter = decryptionProcessor.buildDecrypter(responseData);
+        if (getDecryptionProcessor().haveEncryptedContent(response)) {
+            try {
+                if (decrypter == null) {
+                    decrypter = getDecryptionProcessor().buildDecrypter(responseData);
+                }
+                getDecryptionProcessor().decryptAssertionContent(response, decrypter);
+            } catch (final DecryptionException e) {
+                log.warn("{} Fatal error building Decrypter, can not decrypt Assertion content", getLogPrefix());
             }
-            decryptionProcessor.decryptAssertionContent(response, decrypter);
         }
         
         if (isSubjectMatch()) {
+            log.debug("Subject match is enabled, processing");
             getSubjectMatchProcessor().process(responseData);
         } else {
             log.debug("Subject match is disabled, skipping");
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/DependencyAttributeSubjectResolver.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/DependencyAttributeSubjectResolver.java
index bdbd30d8d..8ffff77fe 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/DependencyAttributeSubjectResolver.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/DependencyAttributeSubjectResolver.java
@@ -157,17 +157,18 @@ public class DependencyAttributeSubjectResolver extends AbstractInitializableCom
             assert attributeID != null;
             log.trace("Evaluating attribute ID: {}", attributeID); 
             if (dependencyAttributes.containsKey(attributeID)) {
-                log.trace("Dependency attribute '{}' present, checking values");
+                log.trace("Dependency attribute '{}' present, checking values", attributeID);
                 for (final IdPAttributeValue value : dependencyAttributes.get(attributeID)) {
-                    if (value instanceof StringAttributeValue stringValue) {
-                        log.debug("In dependency attribute '{}' found string value '{}'",
-                                attributeID, stringValue.getValue());
-                        return buildSubject(stringValue.getValue(), attributeID);
-                    } else if (value instanceof ScopedStringAttributeValue scopedStringValue) {
+                    // ScopedString- is a subtype of String-, so must eval it first!
+                    if (value instanceof ScopedStringAttributeValue scopedStringValue) {
                         final String scopedString = scopedStringValue.getValue() + "@" + scopedStringValue.getScope();
                         log.debug("In dependency attribute '{}' found scoped string value '{}'",
                                 attributeID, scopedString);
                         return buildSubject(scopedString, attributeID);
+                    } else if (value instanceof StringAttributeValue stringValue) {
+                        log.debug("In dependency attribute '{}' found string value '{}'",
+                                attributeID, stringValue.getValue());
+                        return buildSubject(stringValue.getValue(), attributeID);
                     } else if (value instanceof XMLObjectAttributeValue xmlObjectValue 
                             && xmlObjectValue.getValue() instanceof NameID nameIDValue) {
                         try {
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 7531c7c17..4c129b592 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
@@ -43,7 +43,6 @@ import org.opensaml.xmlsec.criterion.DecryptionConfigurationCriterion;
 import org.opensaml.xmlsec.encryption.support.DecryptionException;
 import org.slf4j.Logger;
 
-import net.shibboleth.idp.attribute.resolver.ResolutionException;
 import net.shibboleth.idp.attribute.resolver.dc.saml.ResponseData;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.component.AbstractInitializableComponent;
@@ -161,11 +160,8 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
      * 
      * @param response the Response to process
      * @param decrypter the decrypter used for decryption operations
-     * 
-     * @throws ResolutionException if there is a fatal error during decryption
      */
-    public void decryptAssertions(@Nonnull final Response response, @Nonnull final Decrypter decrypter)
-            throws ResolutionException {
+    public void decryptAssertions(@Nonnull final Response response, @Nonnull final Decrypter decrypter) {
         checkComponentActive();
 
         final Collection<Assertion> decrypteds = new ArrayList<>();
@@ -183,7 +179,7 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
                     decrypteds.add(decrypted);
                 }
             } catch (final DecryptionException e) {
-                throw new ResolutionException("Error decrypting Assertion", e);
+                log.warn("Error decrypting EncryptedAssertion, will not be processed for attribute extraction", e);
             }
         }
         
@@ -196,7 +192,7 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
             try {
                 XMLObjectSupport.marshall(response);
             } catch (final MarshallingException e) {
-                throw new ResolutionException("Error re-marshalling Response after Assertion decryption", e);
+                log.warn("Error re-marshalling Response after Assertion decryption", e);
             }
         }
     }
@@ -210,18 +206,26 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
      * 
      * @param response the Response to process
      * @param decrypter the decrypter used for decryption operations
-     * 
-     * @throws ResolutionException if there is a fatal error during decryption
      */
-    public void decryptAssertionContent(@Nonnull final Response response, @Nonnull final Decrypter decrypter)
-            throws ResolutionException {
+    public void decryptAssertionContent(@Nonnull final Response response, @Nonnull final Decrypter decrypter) {
+        final List<Assertion> toRemove = new ArrayList<>();
 
         for (final Assertion assertion : response.getAssertions()) {
             if (assertion == null) {
                 continue;
             }
-            decryptEncryptedIDs(assertion, decrypter);
+            try {
+                decryptEncryptedIDs(assertion, decrypter);
+            } catch (final DecryptionException e) {
+                log.warn("Error decrypting EncryptedIDs in Assertion '{}', " 
+                        + "will not be processed for attribute extraction", assertion.getID(), e);
+                toRemove.add(assertion);
+                continue;
+            }
+
             decryptEncryptedAttributes(assertion, decrypter);
+            
+            response.getAssertions().removeAll(toRemove);
         }
     }
     
@@ -235,10 +239,10 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
      * @param assertion the Assertion to process
      * @param decrypter the decrypter used for decryption operations
      * 
-     * @throws ResolutionException if there is a fatal error during decryption
+     * @throws DecryptionException  there is a fatal error during decryption
      */
     public void decryptEncryptedIDs(@Nonnull final Assertion assertion, @Nonnull final Decrypter decrypter)
-            throws ResolutionException {
+            throws DecryptionException {
         checkComponentActive();
 
         final Subject subject = assertion.getSubject();
@@ -272,22 +276,18 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
      * 
      * @return the decrypted NameID element
      * 
-     * @throws ResolutionException if there is a fatal error during decryption
+     * @throws DecryptionException if there is a fatal error during decryption
      */
     @Nonnull public NameID decryptEncryptedID(@Nonnull final EncryptedID encID, @Nonnull final Decrypter decrypter)
-            throws ResolutionException {
+            throws DecryptionException {
         checkComponentActive();
         
-        try {
             final SAMLObject object = decrypter.decrypt(encID);
             if (object instanceof NameID) {
                 return (NameID) object;
             }
             throw new DecryptionException("Decrypted EncryptedID was not a NameID, was a "
                     + object.getElementQName().toString());
-        } catch (final DecryptionException e) {
-            throw new ResolutionException("Error decryptng EncryptedID", e);
-        }
     }
 
     /**
@@ -299,11 +299,8 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
      * 
      * @param assertion the Assertion to process
      * @param decrypter the decrypter to use
-     * 
-     * @throws ResolutionException  if there is a fatal error during decryption
      */
-    public void decryptEncryptedAttributes(@Nonnull final Assertion assertion, @Nonnull final Decrypter decrypter)
-            throws ResolutionException {
+    public void decryptEncryptedAttributes(@Nonnull final Assertion assertion, @Nonnull final Decrypter decrypter) {
         checkComponentActive();
 
         for (final AttributeStatement s : assertion.getAttributeStatements()) {
@@ -324,7 +321,7 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
                         decrypteds.add(decrypted);
                     }
                 } catch (final DecryptionException e) {
-                    throw new ResolutionException("Error decrypting Attribute", e);
+                    log.warn("Error decrypting EncryptedAttribute, will not be processed for attribute extraction", e);
                 }
             }
             
@@ -340,15 +337,15 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
      * 
      * @return the new Decrypter instance
      * 
-     * @throws ResolutionException if there is a fatal error constructing the Decrypter instance
+     * @throws DecryptionException if there is a fatal error constructing the Decrypter instance
      */
-    @Nonnull public Decrypter buildDecrypter(@Nonnull final ResponseData responseData) throws ResolutionException {
+    @Nonnull public Decrypter buildDecrypter(@Nonnull final ResponseData responseData) throws DecryptionException {
         checkComponentActive();
 
         final List<DecryptionConfiguration> configs = getDecryptionConfigurationLookupStrategy().apply(responseData);
         if (configs == null || configs.isEmpty()) {
             log.error("No DecryptionConfigurations returned by lookup strategy");
-            throw new ResolutionException("No DecryptionConfigurations returned by lookup strategy");
+            throw new DecryptionException("No DecryptionConfigurations returned by lookup strategy");
         }
         
         try {
@@ -359,9 +356,9 @@ public class DecryptionProcessor extends AbstractInitializableComponent {
             if (params != null) {
                 return new Decrypter(params);
             }
-            throw new ResolutionException("Failed to resolve DecryptionParameters");
+            throw new DecryptionException("Failed to resolve DecryptionParameters");
         } catch (final ResolverException e) {
-            throw new ResolutionException("Error resolving DecryptionParameters", e);
+            throw new DecryptionException("Error resolving DecryptionParameters", e);
         }
     }
 
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/SubjectMatchProcessor.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/SubjectMatchProcessor.java
index e1d8c1ea7..96e1cf863 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/SubjectMatchProcessor.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/SubjectMatchProcessor.java
@@ -19,6 +19,8 @@ import java.util.List;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.util.XMLObjectSupport;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.saml.saml2.core.Assertion;
 import org.opensaml.saml.saml2.core.AttributeQuery;
@@ -29,6 +31,7 @@ import org.slf4j.Logger;
 
 import net.shibboleth.idp.attribute.resolver.dc.saml.ResponseData;
 import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.xml.SerializeSupport;
 
 /**
  * A helper component for matching the {@link Subject} of an {@link Assertion} against the 
@@ -57,21 +60,20 @@ public class SubjectMatchProcessor {
         final Subject querySubject = query.getSubject();
         assert querySubject != null;
         
+        if (log.isTraceEnabled()) {
+            try {
+                log.trace("Matching Assertions' subject(s) against query Subject:\n{})",
+                        SerializeSupport.prettyPrintXML(XMLObjectSupport.marshall(querySubject)));
+            } catch (final MarshallingException e) {
+                log.warn("Error serialzing query Subject for logging", e);
+            }
+        }
+        
         final List<Assertion> toRemove = new LinkedList<>();
         
         for (final Assertion assertion : responseData.getResponse().getAssertions()) {
-            final Issuer assertionIssuer = assertion.getIssuer();
-
-            final Subject assertionSubject = assertion.getSubject();
-            if (assertionSubject == null) {
-                log.warn("Assertion '{}' from Issuer '{}' did not contain Subject, removing",
-                        assertion.getID(),
-                        assertionIssuer != null ? assertionIssuer.getValue() : "null");
-                toRemove.add(assertion);
-                continue;
-            }
-
-            if (! SAML2ObjectSupport.matchSubject(assertionSubject, querySubject)) {
+            if (! subjectMatches(assertion, querySubject)) {
+                final Issuer assertionIssuer = assertion.getIssuer();
                 log.warn("Subject of Assertion '{}' from Issuer '{}' did not match query Subject, removing",
                         assertion.getID(),
                         assertionIssuer != null ? assertionIssuer.getValue() : "null");
@@ -85,5 +87,35 @@ public class SubjectMatchProcessor {
         }
         
     }
+    
+    /**
+     * Evaluate whether the subject of the specified Assertion matches that of the specified Subject.
+     * 
+     * @param assertion the Assertion to evaluate
+     * @param querySubject the subject against which to match
+     * 
+     * @return true if Assertion subject matches, false if it does not match
+     */
+    private boolean subjectMatches(@Nonnull final Assertion assertion, @Nonnull final Subject querySubject) {
+        final Subject assertionSubject = assertion.getSubject();
+        if (assertionSubject == null) {
+            final Issuer assertionIssuer = assertion.getIssuer();
+            log.warn("Assertion '{}' from Issuer '{}' did not contain Subject, removing",
+                    assertion.getID(),
+                    assertionIssuer != null ? assertionIssuer.getValue() : "null");
+            return false;
+        }
+
+        if (log.isTraceEnabled()) {
+            try {
+                log.trace("Matching Assertion subject:\n{})",
+                        SerializeSupport.prettyPrintXML(XMLObjectSupport.marshall(assertionSubject)));
+            } catch (final MarshallingException e) {
+                log.warn("Error serialzing query Subject for logging", e);
+            }
+        }
+        
+        return SAML2ObjectSupport.matchSubject(assertionSubject, querySubject);
+    }
 
 }
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BaseSAMLDataConnectorTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BaseSAMLDataConnectorTest.java
new file mode 100644
index 000000000..1c53f589b
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BaseSAMLDataConnectorTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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.dc.saml.impl;
+
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.saml.common.binding.EndpointResolver;
+import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator;
+import org.opensaml.saml.saml2.assertion.impl.AudienceRestrictionConditionValidator;
+import org.opensaml.saml.saml2.assertion.messaging.impl.DefaultAssertionValidationContextBuilder;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.Subject;
+import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
+import org.opensaml.saml.saml2.encryption.Encrypter;
+import org.opensaml.saml.saml2.metadata.AttributeAuthorityDescriptor;
+import org.opensaml.saml.saml2.metadata.AttributeService;
+import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialSupport;
+import org.opensaml.security.crypto.KeySupport;
+import org.opensaml.xmlsec.DecryptionConfiguration;
+import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters;
+import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters;
+import org.opensaml.xmlsec.impl.BasicDecryptionConfiguration;
+import org.opensaml.xmlsec.impl.BasicDecryptionParametersResolver;
+import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.filter.AttributeFilterPolicy;
+import net.shibboleth.idp.attribute.filter.AttributeRule;
+import net.shibboleth.idp.attribute.filter.Matcher;
+import net.shibboleth.idp.attribute.filter.PolicyRequirementRule;
+import net.shibboleth.idp.attribute.filter.impl.AttributeFilterImpl;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.ResponseData;
+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.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.BasicNamingFunction;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
+import net.shibboleth.idp.saml.attribute.transcoding.AbstractSAML2AttributeTranscoder;
+import net.shibboleth.idp.saml.attribute.transcoding.SAML2AttributeTranscoder;
+import net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2StringAttributeTranscoder;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.security.impl.SecureRandomIdentifierGenerationStrategy;
+import net.shibboleth.shared.testing.MockApplicationContext;
+
+/**
+ *
+ */
+public abstract class BaseSAMLDataConnectorTest extends XMLObjectBaseTestCase {
+    
+    public static final String EMAIL_ID = "email";
+
+    public static final String AFFIL_ID = "affiliation";
+    
+    protected Encrypter encrypter;
+
+    //
+    // Helpers
+    //
+
+    protected AttributeFilterImpl buildAttributeFilter() throws ComponentInitializationException {
+        final AttributeRule emailPolicy1 = new AttributeRule();
+        emailPolicy1.setId("emailPolicy");
+        emailPolicy1.setAttributeId("email");
+        emailPolicy1.setMatcher(Matcher.MATCHES_ALL);
+        emailPolicy1.setIsDenyRule(false);
+        emailPolicy1.initialize();
+
+        final AttributeFilterPolicy filterPolicy1 = new AttributeFilterPolicy("policy1", PolicyRequirementRule.MATCHES_ALL,
+                CollectionSupport.listOf(emailPolicy1));
+        filterPolicy1.initialize();
+
+        final AttributeFilterImpl attributeFilter = new AttributeFilterImpl("attributeFilter", CollectionSupport.listOf(filterPolicy1));
+        attributeFilter.initialize();
+        return attributeFilter;
+    }
+
+    protected AttributeTranscoderRegistryImpl buildTranscoderRegistry() throws ComponentInitializationException {
+        final AttributeTranscoderRegistryImpl transcoderRegistry = new AttributeTranscoderRegistryImpl();
+        transcoderRegistry.setId("test");
+
+        final SAML2StringAttributeTranscoder transcoder = new SAML2StringAttributeTranscoder();
+        transcoder.initialize();
+
+        final Map<String,Object> rulesetEmail = new HashMap<>();
+        rulesetEmail.put(AttributeTranscoderRegistry.PROP_ID, EMAIL_ID);
+        rulesetEmail.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        rulesetEmail.put(SAML2AttributeTranscoder.PROP_ENCODE_TYPE, true);
+        rulesetEmail.put(SAML2AttributeTranscoder.PROP_NAME, EMAIL_ID);
+        rulesetEmail.put(SAML2AttributeTranscoder.PROP_NAME_FORMAT, Attribute.BASIC);
+
+        final Map<String,Object> rulesetAffil = new HashMap<>();
+        rulesetAffil.put(AttributeTranscoderRegistry.PROP_ID, AFFIL_ID);
+        rulesetAffil.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        rulesetAffil.put(SAML2AttributeTranscoder.PROP_ENCODE_TYPE, true);
+        rulesetAffil.put(SAML2AttributeTranscoder.PROP_NAME, AFFIL_ID);
+        rulesetAffil.put(SAML2AttributeTranscoder.PROP_NAME_FORMAT, Attribute.BASIC);
+
+        transcoderRegistry.setNamingRegistry(CollectionSupport.singletonList(
+                new BasicNamingFunction<>(transcoder.getEncodedType(), new AbstractSAML2AttributeTranscoder.NamingFunction())));
+        transcoderRegistry.setTranscoderRegistry(CollectionSupport.listOf(
+                new TranscodingRule(rulesetEmail),
+                new TranscodingRule(rulesetAffil)));
+        transcoderRegistry.setApplicationContext(new MockApplicationContext());
+        transcoderRegistry.initialize();
+        return transcoderRegistry;
+    }
+
+    protected ExecutableQueryBuilder buildQueryBuilder() throws ComponentInitializationException {
+        final ExecutableQueryBuilder queryBuilder = new ExecutableQueryBuilder();
+        queryBuilder.setAuthorityEndpointResolver( new EndpointResolver<AttributeService>() {
+            public AttributeService resolveSingle(@Nullable CriteriaSet criteria) throws ResolverException {
+                return SAMLDataConnectorTestingSupport.buildAttributeService();
+            }
+            @Nonnull
+            public Iterable<AttributeService> resolve(@Nullable CriteriaSet criteria) throws ResolverException {
+                return CollectionSupport.listOf(SAMLDataConnectorTestingSupport.buildAttributeService());
+            }
+        });
+
+        queryBuilder.setAuthorityEntityIDStrategy(new AttributeAuthorityEntityIDResolver() {
+            public String resolve(@Nonnull AttributeResolutionContext resolutionContext,
+                    @Nonnull Map<String, List<IdPAttributeValue>> dependencyAttributes) throws ResolutionException {
+                return SAMLDataConnectorTestingSupport.ISSUER;
+            }
+        });
+
+        queryBuilder.setIdentifierGenerationStrategy(new SecureRandomIdentifierGenerationStrategy());
+
+        queryBuilder.setRoleDescriptorResolver(SAMLDataConnectorTestingSupport.buildRoleResolver());
+
+        queryBuilder.setSelfEntityIDStrategy( new SelfEntityIDResolver() {
+            public String resolve(@Nonnull AttributeResolutionContext resolutionContext,
+                    @Nonnull Map<String, List<IdPAttributeValue>> dependencyAttributes,
+                    @Nonnull AttributeAuthorityDescriptor roleDescriptor) throws ResolutionException {
+                return "urn:test:attribute-client";
+            }
+        });
+
+        queryBuilder.setSOAPClientSecurityConfigurationProfileId("http://shibboleth.net/ns/profiles/saml2/query/attribute");
+
+        queryBuilder.setSOAPPipelineName("SAML2.AttributeQuery");
+
+        queryBuilder.setSubjectStrategy( new SubjectResolver() {
+            public Subject resolve(@Nonnull AttributeResolutionContext resolutionContext,
+                    @Nonnull Map<String, List<IdPAttributeValue>> dependencyAttributes,
+                    @Nonnull AttributeAuthorityDescriptor roleDescriptor) throws ResolutionException {
+                return SAMLDataConnectorTestingSupport.buildSubject();
+            }
+        });
+
+        return queryBuilder;
+    }
+
+    protected DecryptionProcessor buildDecryptionProcessor()
+            throws NoSuchAlgorithmException, NoSuchProviderException, ComponentInitializationException {
+
+        final KeyPair keyPair = KeySupport.generateKeyPair("RSA", 2048, null);
+
+        final DataEncryptionParameters dataParams = new DataEncryptionParameters();
+        dataParams.setAlgorithm(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM);
+
+        final KeyEncryptionParameters keyParams = new KeyEncryptionParameters();
+        keyParams.setAlgorithm(EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP);
+        keyParams.setEncryptionCredential(CredentialSupport.getSimpleCredential(keyPair.getPublic(), null));
+
+        encrypter = new Encrypter(dataParams, keyParams);
+
+        final Credential decryptionCredential =  CredentialSupport.getSimpleCredential(keyPair.getPublic(), keyPair.getPrivate());
+
+        final BasicDecryptionConfiguration decryptionConfiguration = new BasicDecryptionConfiguration();
+        decryptionConfiguration.setKEKKeyInfoCredentialResolver(new StaticKeyInfoCredentialResolver(decryptionCredential));
+        decryptionConfiguration.setEncryptedKeyResolver(new ChainingEncryptedKeyResolver(
+                CollectionSupport.listOf(new EncryptedElementTypeEncryptedKeyResolver(), new InlineEncryptedKeyResolver())));
+
+        final DecryptionProcessor decryptionProcessor = new DecryptionProcessor();
+        decryptionProcessor.setDecryptionParametersResolver(new BasicDecryptionParametersResolver());
+        decryptionProcessor.setDecryptionConfigurationLookupStrategy(new Function<ResponseData,List<DecryptionConfiguration>> () {
+            public List<DecryptionConfiguration> apply(ResponseData t) {
+                return CollectionSupport.listOf(decryptionConfiguration);
+            }
+        });
+
+        decryptionProcessor.initialize();
+        return decryptionProcessor;
+    }
+
+    protected AssertionValidationProcessor buildAssertionValidatonProcessor() throws ComponentInitializationException {
+        final DefaultAssertionValidationContextBuilder assertionValidationContextBuilder = new DefaultAssertionValidationContextBuilder();
+        assertionValidationContextBuilder.setValidIssuers(FunctionSupport.constant(Set.of(SAMLDataConnectorTestingSupport.ISSUER)));
+        assertionValidationContextBuilder.setSignatureRequired(PredicateSupport.alwaysFalse());
+        assertionValidationContextBuilder.setValidIssuers(new Function<InOutOperationContext, Set<String>>() {
+            // For testing this is just reflecting back whoever the Response issuer is
+            public Set<String> apply(InOutOperationContext t) {
+                final Response response = Response.class.cast(t.ensureInboundMessageContext().getMessage());
+                if (response != null) {
+                    final Issuer issuer = response.getIssuer();
+                    if (issuer != null) {
+                        final String value = issuer.getValue();
+                        if (value != null) {
+                            return CollectionSupport.setOf(value);
+                        }
+                    }
+                }
+                return null;
+            }
+        });
+
+        final SAML20AssertionValidator validator = new SAML20AssertionValidator(
+                CollectionSupport.listOf(new AudienceRestrictionConditionValidator()), 
+                CollectionSupport.emptyList(), 
+                CollectionSupport.emptyList(), 
+                null, 
+                null, 
+                new SAMLSignatureProfileValidator());
+
+        final AssertionValidationProcessor assertionProcessor = new AssertionValidationProcessor();
+        assertionProcessor.setAssertionValidationContextBuilder(assertionValidationContextBuilder);
+        assertionProcessor.setAssertionValidator(validator);
+        assertionProcessor.initialize();
+        return assertionProcessor;
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BasicResponseMappingStrategyTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BasicResponseMappingStrategyTest.java
new file mode 100644
index 000000000..82005227d
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/BasicResponseMappingStrategyTest.java
@@ -0,0 +1,409 @@
+/*
+ * 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.dc.saml.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeQuery;
+import org.opensaml.saml.saml2.core.Response;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.filter.AttributeFilterPolicy;
+import net.shibboleth.idp.attribute.filter.AttributeRule;
+import net.shibboleth.idp.attribute.filter.Matcher;
+import net.shibboleth.idp.attribute.filter.PolicyRequirementRule;
+import net.shibboleth.idp.attribute.filter.impl.AttributeFilterImpl;
+import net.shibboleth.idp.attribute.filter.matcher.impl.AttributeValueRegexpMatcher;
+import net.shibboleth.idp.attribute.resolver.dc.saml.ResponseData;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.BasicNamingFunction;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
+import net.shibboleth.idp.saml.attribute.transcoding.AbstractSAML2AttributeTranscoder;
+import net.shibboleth.idp.saml.attribute.transcoding.SAML2AttributeTranscoder;
+import net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2StringAttributeTranscoder;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.testing.MockApplicationContext;
+
+/**
+ *
+ */
+public class BasicResponseMappingStrategyTest extends XMLObjectBaseTestCase {
+    
+    private static final String EMAIL_ID = "email";
+
+    private static final String AFFIL_ID = "affiliation";
+    
+    private BasicResponseMappingStrategy strategy;
+    
+    private ResponseData responseData;
+
+    private AttributeTranscoderRegistryImpl transcoderRegistry;
+    
+    private AttributeFilterImpl attributeFilter;
+    
+    @BeforeMethod
+    public void beforeMethod() throws Exception {
+        transcoderRegistry = new AttributeTranscoderRegistryImpl();
+        transcoderRegistry.setId("test");
+
+        final SAML2StringAttributeTranscoder transcoder = new SAML2StringAttributeTranscoder();
+        transcoder.initialize();
+        
+        transcoderRegistry.setNamingRegistry(CollectionSupport.singletonList(
+                new BasicNamingFunction<>(transcoder.getEncodedType(), new AbstractSAML2AttributeTranscoder.NamingFunction())));
+        
+        final Map<String,Object> rulesetEmail = new HashMap<>();
+        rulesetEmail.put(AttributeTranscoderRegistry.PROP_ID, EMAIL_ID);
+        rulesetEmail.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        rulesetEmail.put(SAML2AttributeTranscoder.PROP_ENCODE_TYPE, true);
+        rulesetEmail.put(SAML2AttributeTranscoder.PROP_NAME, EMAIL_ID);
+        rulesetEmail.put(SAML2AttributeTranscoder.PROP_NAME_FORMAT, Attribute.BASIC);
+
+        final Map<String,Object> rulesetAffil = new HashMap<>();
+        rulesetAffil.put(AttributeTranscoderRegistry.PROP_ID, AFFIL_ID);
+        rulesetAffil.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        rulesetAffil.put(SAML2AttributeTranscoder.PROP_ENCODE_TYPE, true);
+        rulesetAffil.put(SAML2AttributeTranscoder.PROP_NAME, AFFIL_ID);
+        rulesetAffil.put(SAML2AttributeTranscoder.PROP_NAME_FORMAT, Attribute.BASIC);
+        
+        transcoderRegistry.setTranscoderRegistry(CollectionSupport.listOf(
+                new TranscodingRule(rulesetEmail),
+                new TranscodingRule(rulesetAffil)));
+        
+        transcoderRegistry.setApplicationContext(new MockApplicationContext());
+        transcoderRegistry.initialize();
+        
+        AttributeFilterPolicy filterPolicy1 = new AttributeFilterPolicy("policy1", PolicyRequirementRule.MATCHES_NONE, null);
+        filterPolicy1.initialize();
+        attributeFilter = new AttributeFilterImpl("attributeFilter", CollectionSupport.listOf(filterPolicy1));
+        attributeFilter.initialize();
+
+        strategy = new BasicResponseMappingStrategy(); 
+        strategy.setTranscoderRegistry(transcoderRegistry);
+        strategy.setMetadataResolver(SAMLDataConnectorTestingSupport.buildMetadataResolver());
+        strategy.setRoleDescriptorResolver(SAMLDataConnectorTestingSupport.buildRoleResolver());
+        strategy.setAttributeFilterService(SAMLDataConnectorTestingSupport.getAttributeFilterService(attributeFilter));
+
+        final AttributeQuery query = SAMLDataConnectorTestingSupport.buildQuery();
+       
+        final Response response = SAMLDataConnectorTestingSupport.buildResponse();
+       
+        responseData = new ResponseData(
+                response,
+                SAMLDataConnectorTestingSupport.buildOperationContext(query, response),
+                SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(),
+                CollectionSupport.emptyMap(),
+                SAMLDataConnectorTestingSupport.buildAuthorityDescriptor()
+                );
+    }
+    
+    @Test
+    public void noAssertions() throws Exception {
+        strategy.setFilterAttributes(false);
+        strategy.initialize();
+        
+        responseData.getResponse().getAssertions().clear();
+
+        final Map<String, IdPAttribute> attributes = strategy.map(responseData);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+
+        Assert.assertEquals(attributes.keySet().size(), 0);
+    }
+
+    @Test
+    public void oneAssertionNoAttributes() throws Exception {
+        strategy.setFilterAttributes(false);
+        strategy.initialize();
+
+        final Map<String, IdPAttribute> attributes = strategy.map(responseData);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+
+        Assert.assertEquals(attributes.keySet().size(), 0);
+    }
+
+    @Test
+    public void oneAssertionOneAttributeNoFilter() throws Exception {
+        strategy.setFilterAttributes(false);
+        strategy.initialize();
+        
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion, 
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at example.edu"));
+
+        final Map<String, IdPAttribute> attributes = strategy.map(responseData);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.keySet().size(), 1);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> values = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(values, Set.of("user1 at example.edu"));
+    }
+
+    @Test
+    public void oneAssertionTwoAttributesNoFilter() throws Exception {
+        strategy.setFilterAttributes(false);
+        strategy.initialize();
+        
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion, 
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at example.edu"));
+        SAMLDataConnectorTestingSupport.addAttribute(assertion, 
+                SAMLDataConnectorTestingSupport.buildAttribute("affiliation", Attribute.BASIC, "Staff", "Student"));
+
+        final Map<String, IdPAttribute> attributes = strategy.map(responseData);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+
+        Assert.assertEquals(attributes.keySet().size(), 2);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> valuesEmail = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesEmail, Set.of("user1 at example.edu"));
+
+        final IdPAttribute affil = attributes.get(AFFIL_ID);
+        Assert.assertNotNull(affil);
+        final Set<String> valuesAffil = affil.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesAffil, Set.of("Staff", "Student"));
+    }
+
+    @Test
+    public void twoAssertionsOneAttributeNoFilter() throws Exception {
+        strategy.setFilterAttributes(false);
+        strategy.initialize();
+        
+        responseData.getResponse().getAssertions().add(SAMLDataConnectorTestingSupport.buildAssertion(null));
+        
+        final Assertion assertion0 = responseData.getResponse().getAssertions().get(0);
+        assert assertion0 != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion0, 
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at example.edu"));
+
+        final Assertion assertion1 = responseData.getResponse().getAssertions().get(0);
+        assert assertion1 != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion1, 
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at somewhere.org"));
+
+        final Map<String, IdPAttribute> attributes = strategy.map(responseData);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.keySet().size(), 1);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> values = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(values, Set.of("user1 at example.edu", "user1 at somewhere.org"));
+    }
+
+    @Test
+    public void oneAssertionOneAttributeWithFilterFailsAll() throws Exception {
+        strategy.setFilterAttributes(true);
+        
+        AttributeFilterPolicy filterPolicy1 = new AttributeFilterPolicy("policy1", PolicyRequirementRule.MATCHES_ALL, null);
+        filterPolicy1.initialize();
+        attributeFilter = new AttributeFilterImpl("attributeFilter", CollectionSupport.listOf(filterPolicy1));
+        attributeFilter.initialize();
+        strategy.setAttributeFilterService(SAMLDataConnectorTestingSupport.getAttributeFilterService(attributeFilter));
+
+        strategy.initialize();
+        
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion, 
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at example.edu", "user1 at somewhere.org"));
+
+        final Map<String, IdPAttribute> attributes = strategy.map(responseData);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.keySet().size(), 0);
+        
+    }
+
+    @Test
+    public void oneAssertionOneAttributeWithFilterAllowsAll() throws Exception {
+        strategy.setFilterAttributes(true);
+        
+        final AttributeRule emailPolicy1 = new AttributeRule();
+        emailPolicy1.setId("emailPolicy");
+        emailPolicy1.setAttributeId("email");
+        emailPolicy1.setMatcher(Matcher.MATCHES_ALL);
+        emailPolicy1.setIsDenyRule(false);
+        emailPolicy1.initialize();
+
+        final AttributeFilterPolicy filterPolicy1 = new AttributeFilterPolicy("policy1", PolicyRequirementRule.MATCHES_ALL,
+                CollectionSupport.listOf(emailPolicy1));
+        filterPolicy1.initialize();
+        attributeFilter = new AttributeFilterImpl("attributeFilter", CollectionSupport.listOf(filterPolicy1));
+        attributeFilter.initialize();
+        strategy.setAttributeFilterService(SAMLDataConnectorTestingSupport.getAttributeFilterService(attributeFilter));
+
+        strategy.initialize();
+        
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion, 
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at example.edu", "user1 at somewhere.org"));
+
+        final Map<String, IdPAttribute> attributes = strategy.map(responseData);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.keySet().size(), 1);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> values = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(values, Set.of("user1 at example.edu", "user1 at somewhere.org"));
+    }
+
+    @Test
+    public void twoAssertionOneAttributeWithFilterAllowsPattern() throws Exception {
+        strategy.setFilterAttributes(true);
+        
+        final AttributeValueRegexpMatcher regexMatcher = new AttributeValueRegexpMatcher();
+        regexMatcher.setId("emailRegEx");
+        regexMatcher.setPattern(Pattern.compile("^.*@example.edu$"));
+        regexMatcher.initialize();
+        
+        final AttributeRule emailPolicy1 = new AttributeRule();
+        emailPolicy1.setId("emailPolicy");
+        emailPolicy1.setAttributeId("email");
+        emailPolicy1.setMatcher(regexMatcher);
+        emailPolicy1.setIsDenyRule(false);
+        emailPolicy1.initialize();
+
+        final AttributeFilterPolicy filterPolicy1 = new AttributeFilterPolicy("policy1", PolicyRequirementRule.MATCHES_ALL,
+                CollectionSupport.listOf(emailPolicy1));
+        filterPolicy1.initialize();
+        attributeFilter = new AttributeFilterImpl("attributeFilter", CollectionSupport.listOf(filterPolicy1));
+        attributeFilter.initialize();
+        strategy.setAttributeFilterService(SAMLDataConnectorTestingSupport.getAttributeFilterService(attributeFilter));
+
+        strategy.initialize();
+        
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion, 
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at example.edu", "user1 at somewhere.org"));
+
+        final Map<String, IdPAttribute> attributes = strategy.map(responseData);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.keySet().size(), 1);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> values = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(values, Set.of("user1 at example.edu"));
+    }
+
+    @Test
+    public void oneAssertionOneAttributeWithFilterAllowsPattern() throws Exception {
+        strategy.setFilterAttributes(true);
+        
+        final AttributeValueRegexpMatcher regexMatcher = new AttributeValueRegexpMatcher();
+        regexMatcher.setId("emailRegEx");
+        regexMatcher.setPattern(Pattern.compile("^.*@example.edu$"));
+        regexMatcher.initialize();
+        
+        final AttributeRule emailPolicy1 = new AttributeRule();
+        emailPolicy1.setId("emailPolicy");
+        emailPolicy1.setAttributeId("email");
+        emailPolicy1.setMatcher(regexMatcher);
+        emailPolicy1.setIsDenyRule(false);
+        emailPolicy1.initialize();
+
+        final AttributeFilterPolicy filterPolicy1 = new AttributeFilterPolicy("policy1", PolicyRequirementRule.MATCHES_ALL,
+                CollectionSupport.listOf(emailPolicy1));
+        filterPolicy1.initialize();
+        attributeFilter = new AttributeFilterImpl("attributeFilter", CollectionSupport.listOf(filterPolicy1));
+        attributeFilter.initialize();
+        strategy.setAttributeFilterService(SAMLDataConnectorTestingSupport.getAttributeFilterService(attributeFilter));
+
+        strategy.initialize();
+        
+        final Assertion assertion0 = responseData.getResponse().getAssertions().get(0);
+        assert assertion0 != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion0, 
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at example.edu", "user1 at somewhere.org"));
+        
+        final Assertion assertion1 = responseData.getResponse().getAssertions().get(0);
+        assert assertion1 != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion1, 
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user2 at example.edu", "cio at company.com"));
+
+        final Map<String, IdPAttribute> attributes = strategy.map(responseData);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.keySet().size(), 1);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> values = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(values, Set.of("user1 at example.edu", "user2 at example.edu"));
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/ExecutableQueryBuilderTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/ExecutableQueryBuilderTest.java
new file mode 100644
index 000000000..e7b0fadb3
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/ExecutableQueryBuilderTest.java
@@ -0,0 +1,376 @@
+/*
+ * 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.dc.saml.impl;
+
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.saml.common.SAMLException;
+import org.opensaml.saml.common.binding.EndpointResolver;
+import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
+import org.opensaml.saml.common.messaging.context.SAMLProtocolContext;
+import org.opensaml.saml.common.messaging.context.SAMLSelfEntityContext;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeQuery;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.Status;
+import org.opensaml.saml.saml2.core.StatusCode;
+import org.opensaml.saml.saml2.core.Subject;
+import org.opensaml.saml.saml2.metadata.AttributeAuthorityDescriptor;
+import org.opensaml.saml.saml2.metadata.AttributeService;
+import org.opensaml.saml.saml2.metadata.EntityDescriptor;
+import org.opensaml.saml.saml2.metadata.RoleDescriptor;
+import org.opensaml.security.SecurityException;
+import org.opensaml.soap.client.SOAPClient;
+import org.opensaml.soap.client.SOAPClientContext;
+import org.opensaml.soap.client.security.SOAPClientSecurityContext;
+import org.opensaml.soap.common.SOAPException;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.ExecutableQuery;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.security.impl.SecureRandomIdentifierGenerationStrategy;
+
+/**
+ *
+ */
+public class ExecutableQueryBuilderTest extends XMLObjectBaseTestCase {
+    
+    private ExecutableQueryBuilder builder;
+    
+    private AttributeResolutionContext arc;
+    
+    private Map<String, List<IdPAttributeValue>> dependencies;
+    
+    @BeforeMethod
+    public void setupMethod() {
+        builder = new ExecutableQueryBuilder();
+        
+        builder.setAuthorityEndpointResolver( new EndpointResolver<AttributeService>() {
+            public AttributeService resolveSingle(@Nullable CriteriaSet criteria) throws ResolverException {
+                return SAMLDataConnectorTestingSupport.buildAttributeService();
+            }
+            @Nonnull
+            public Iterable<AttributeService> resolve(@Nullable CriteriaSet criteria) throws ResolverException {
+                return CollectionSupport.listOf(SAMLDataConnectorTestingSupport.buildAttributeService());
+            }
+        });
+        
+        builder.setAuthorityEntityIDStrategy(new AttributeAuthorityEntityIDResolver() {
+            public String resolve(@Nonnull AttributeResolutionContext resolutionContext,
+                    @Nonnull Map<String, List<IdPAttributeValue>> dependencyAttributes) throws ResolutionException {
+                return SAMLDataConnectorTestingSupport.ISSUER;
+            }
+        });
+        
+        builder.setIdentifierGenerationStrategy(new SecureRandomIdentifierGenerationStrategy());
+        
+        builder.setRoleDescriptorResolver(SAMLDataConnectorTestingSupport.buildRoleResolver());
+        
+        builder.setSelfEntityIDStrategy( new SelfEntityIDResolver() {
+            public String resolve(@Nonnull AttributeResolutionContext resolutionContext,
+                    @Nonnull Map<String, List<IdPAttributeValue>> dependencyAttributes,
+                    @Nonnull AttributeAuthorityDescriptor roleDescriptor) throws ResolutionException {
+                return "urn:test:attribute-client";
+            }
+        });
+        
+        builder.setSOAPClientSecurityConfigurationProfileId("http://shibboleth.net/ns/profiles/saml2/query/attribute");
+        
+        builder.setSOAPPipelineName("SAML2.AttributeQuery");
+        
+        builder.setSubjectStrategy( new SubjectResolver() {
+            public Subject resolve(@Nonnull AttributeResolutionContext resolutionContext,
+                    @Nonnull Map<String, List<IdPAttributeValue>> dependencyAttributes,
+                    @Nonnull AttributeAuthorityDescriptor roleDescriptor) throws ResolutionException {
+                return SAMLDataConnectorTestingSupport.buildSubject();
+            }
+        });
+        
+        arc = SAMLDataConnectorTestingSupport.buildAttributeResolutionContext();
+        
+        dependencies = SAMLDataConnectorTestingSupport.buildDependencyAttributes();
+    }
+    
+    @Test
+    public void basic() throws Exception {
+        builder.initialize();
+        
+        final ExecutableQuery executableQuery = builder.build(arc, dependencies);
+        Assert.assertNotNull(executableQuery);
+        
+        Assert.assertEquals(executableQuery.getResultCacheKey(),
+                SAMLDataConnectorTestingSupport.ISSUER 
+                + ":" 
+                + SAMLDataConnectorTestingSupport.SUBJECT_VALUE);
+
+        final String entityID = (String) ReflectionTestUtils.getField(executableQuery, "authorityEntityID");
+        Assert.assertEquals(entityID, SAMLDataConnectorTestingSupport.ISSUER);
+        
+        final String endpoint = (String) ReflectionTestUtils.getField(executableQuery, "authorityEndpoint");
+        Assert.assertEquals(endpoint, SAMLDataConnectorTestingSupport.AA_ENDPOINT);
+        
+        final AttributeAuthorityDescriptor role =
+                (AttributeAuthorityDescriptor) ReflectionTestUtils.getField(executableQuery, "authorityRoleDescriptor");
+        Assert.assertNotNull(role);
+
+        final InOutOperationContext opContext =
+                (InOutOperationContext) ReflectionTestUtils.getField(executableQuery, "opContext");
+        Assert.assertNotNull(opContext);
+        validateOpContext(opContext, builder.getRequestedAttributes());
+    }
+    
+    @Test
+    public void withRequestedAttributes() throws Exception {
+        builder.setRequestedAttributes(CollectionSupport.listOf(
+                SAMLDataConnectorTestingSupport.buildAttribute("urn:test:attr:foo", null),
+                SAMLDataConnectorTestingSupport.buildAttribute("urn:test:attr:bar", null)
+                ));
+
+        builder.initialize();
+        
+        final ExecutableQuery executableQuery = builder.build(arc, dependencies);
+        Assert.assertNotNull(executableQuery);
+        
+        Assert.assertEquals(executableQuery.getResultCacheKey(),
+                SAMLDataConnectorTestingSupport.ISSUER 
+                + ":" 
+                + SAMLDataConnectorTestingSupport.SUBJECT_VALUE
+                + ":"
+                + "urn:test:attr:bar,urn:test:attr:foo");
+        
+        final String entityID = (String) ReflectionTestUtils.getField(executableQuery, "authorityEntityID");
+        Assert.assertEquals(entityID, SAMLDataConnectorTestingSupport.ISSUER);
+        
+        final String endpoint = (String) ReflectionTestUtils.getField(executableQuery, "authorityEndpoint");
+        Assert.assertEquals(endpoint, SAMLDataConnectorTestingSupport.AA_ENDPOINT);
+        
+        final AttributeAuthorityDescriptor role =
+                (AttributeAuthorityDescriptor) ReflectionTestUtils.getField(executableQuery, "authorityRoleDescriptor");
+        Assert.assertNotNull(role);
+
+        final InOutOperationContext opContext =
+                (InOutOperationContext) ReflectionTestUtils.getField(executableQuery, "opContext");
+        Assert.assertNotNull(opContext);
+        validateOpContext(opContext, builder.getRequestedAttributes());
+    }
+    
+    @Test
+    public void receiveValid() throws Exception {
+        final SOAPClient soapClient = new SOAPClient() {
+            public void send(@Nonnull String endpoint, @Nonnull InOutOperationContext context)
+                    throws SOAPException, SecurityException {
+                context.ensureInboundMessageContext().setMessage(SAMLDataConnectorTestingSupport.buildResponse());
+            }
+        };
+
+       builder.initialize();
+
+       final ExecutableQuery executableQuery = builder.build(arc, dependencies);
+       Assert.assertNotNull(executableQuery);
+       
+       executableQuery.execute(soapClient);
+    }
+
+    @Test(expectedExceptions = SOAPException.class)
+    public void receiveMissingPayload() throws Exception {
+        final SOAPClient soapClient = new SOAPClient() {
+            public void send(@Nonnull String endpoint, @Nonnull InOutOperationContext context)
+                    throws SOAPException, SecurityException {
+                context.ensureInboundMessageContext().setMessage(null);
+            }
+        };
+
+       builder.initialize();
+
+       final ExecutableQuery executableQuery = builder.build(arc, dependencies);
+       Assert.assertNotNull(executableQuery);
+       
+       executableQuery.execute(soapClient);
+    }
+
+    @Test(expectedExceptions = SOAPException.class)
+    public void receivePayloadWrongType() throws Exception {
+        final SOAPClient soapClient = new SOAPClient() {
+            public void send(@Nonnull String endpoint, @Nonnull InOutOperationContext context)
+                    throws SOAPException, SecurityException {
+                context.ensureInboundMessageContext().setMessage(XMLObjectSupport.buildXMLObject(simpleXMLObjectQName));
+            }
+        };
+
+       builder.initialize();
+
+       final ExecutableQuery executableQuery = builder.build(arc, dependencies);
+       Assert.assertNotNull(executableQuery);
+       
+       executableQuery.execute(soapClient);
+    }
+
+    @Test
+    public void responseValidationSuccess() throws Exception {
+        builder.initialize();
+        
+        final ExecutableQuery executableQuery = builder.build(arc, dependencies);
+        Assert.assertNotNull(executableQuery);
+        
+        final Response response = SAMLDataConnectorTestingSupport.buildResponse();
+        
+        ReflectionTestUtils.invokeMethod(executableQuery, "validateResponse", response);
+    }
+    
+    @Test
+    public void responseValidationNonSuccess() throws Exception {
+        builder.initialize();
+        
+        final ExecutableQuery executableQuery = builder.build(arc, dependencies);
+        Assert.assertNotNull(executableQuery);
+        
+        final Response response = SAMLDataConnectorTestingSupport.buildResponse();
+        final Status status = response.getStatus();
+        assert status != null;
+        StatusCode statusCode = status.getStatusCode();
+        assert statusCode != null;
+        statusCode.setValue(StatusCode.REQUESTER);
+        
+        try {
+            ReflectionTestUtils.invokeMethod(executableQuery, "validateResponse", response);
+            Assert.fail("validateResponse should have failed due to non-success StatusCode");
+        } catch (final UndeclaredThrowableException e) {
+            // With reflection have to do it this way since the actual exception thrown is the one indicated
+            Assert.assertTrue(SAMLException.class.isInstance(e.getCause()));
+        }
+    }
+
+    @Test
+    public void responseValidationMissingStatusCode() throws Exception {
+        builder.initialize();
+        
+        final ExecutableQuery executableQuery = builder.build(arc, dependencies);
+        Assert.assertNotNull(executableQuery);
+        
+        final Response response = SAMLDataConnectorTestingSupport.buildResponse();
+        final Status status = response.getStatus();
+        assert status != null;
+        status.setStatusCode(null);
+        
+        try {
+            ReflectionTestUtils.invokeMethod(executableQuery, "validateResponse", response);
+            Assert.fail("validateResponse should have failed due to missing StatusCode");
+        } catch (final UndeclaredThrowableException e) {
+            // With reflection have to do it this way since the actual exception thrown is the one indicated
+            Assert.assertTrue(SAMLException.class.isInstance(e.getCause()));
+        }
+    }
+    
+    //
+    // Helpers
+    //
+
+    private void validateOpContext(InOutOperationContext opContext, List<Attribute> requestedAttributes) {
+        MessageContext outbound = opContext.getOutboundMessageContext();
+        assert outbound != null;
+        AttributeQuery query = (AttributeQuery) outbound.getMessage();
+        Assert.assertNotNull(query);
+        assert query != null;
+        
+        Assert.assertNotNull(query.getID());
+        Assert.assertNotNull(query.getDestination());
+        Assert.assertNotNull(query.getIssueInstant());
+        
+        Assert.assertNotNull(query.getIssuer());
+        Issuer issuer = query.getIssuer();
+        assert issuer != null;
+        Assert.assertEquals(issuer.getValue(), "urn:test:attribute-client");
+        
+        Assert.assertNotNull(query.getSubject());
+        Subject subject = query.getSubject();
+        assert subject != null;
+        Assert.assertNotNull(subject.getNameID());
+        NameID nameID = subject.getNameID();
+        assert nameID != null;
+        Assert.assertEquals(nameID.getValue(), SAMLDataConnectorTestingSupport.SUBJECT_VALUE);
+        Assert.assertEquals(nameID.getFormat(), SAMLDataConnectorTestingSupport.SUBJECT_FORMAT);
+        
+        if (requestedAttributes != null && !requestedAttributes.isEmpty()) {
+            Assert.assertEquals(query.getAttributes().size(), requestedAttributes.size());
+            Set<String> control = requestedAttributes.stream().map(Attribute::getName).collect(
+                    CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+            Set<String> requested = query.getAttributes().stream().map(Attribute::getName).collect(
+                    CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+            Assert.assertEquals(requested, control);
+        }
+        
+        SAMLProtocolContext protocolContext = opContext.getSubcontext(SAMLProtocolContext.class);
+        Assert.assertNotNull(protocolContext);
+        assert protocolContext != null;
+        Assert.assertEquals(protocolContext.getProtocol(), SAMLConstants.SAML20P_NS);
+
+        SOAPClientContext soapContext = opContext.getSubcontext(SOAPClientContext.class);
+        Assert.assertNotNull(soapContext);
+        assert soapContext!= null;
+        Assert.assertEquals(soapContext.getPipelineName(), "SAML2.AttributeQuery");
+
+        SOAPClientSecurityContext soapSecurityContext = opContext.getSubcontext(SOAPClientSecurityContext.class);
+        Assert.assertNotNull(soapSecurityContext);
+        assert soapSecurityContext!= null;
+        Assert.assertEquals(soapSecurityContext.getSecurityConfigurationProfileId(), "http://shibboleth.net/ns/profiles/saml2/query/attribute");
+
+        SAMLSelfEntityContext selfContext = opContext.getSubcontext(SAMLSelfEntityContext.class);
+        Assert.assertNotNull(selfContext);
+        assert selfContext != null;
+        Assert.assertEquals(selfContext.getEntityId(), "urn:test:attribute-client");
+
+        SAMLPeerEntityContext peerContext = opContext.getSubcontext(SAMLPeerEntityContext.class);
+        Assert.assertNotNull(peerContext);
+        assert peerContext != null;
+        Assert.assertEquals(peerContext.getEntityId(), SAMLDataConnectorTestingSupport.ISSUER);
+        Assert.assertEquals(peerContext.getRole(), AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
+
+        SAMLMetadataContext metadataContext = peerContext.getSubcontext(SAMLMetadataContext.class);
+        Assert.assertNotNull(metadataContext);
+        assert metadataContext != null;
+        Assert.assertNotNull(metadataContext.getEntityDescriptor());
+        EntityDescriptor entity = metadataContext.getEntityDescriptor();
+        assert entity != null;
+        Assert.assertEquals(entity.getEntityID(), SAMLDataConnectorTestingSupport.ISSUER);
+        Assert.assertNotNull(metadataContext.getRoleDescriptor());
+        RoleDescriptor role = metadataContext.getRoleDescriptor();
+        assert role != null;
+        Assert.assertTrue(AttributeAuthorityDescriptor.class.isInstance(role));
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnectorTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnectorTest.java
new file mode 100644
index 000000000..debceb8ae
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnectorTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.dc.saml.impl;
+
+import java.security.KeyPair;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.encryption.Encrypter;
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.CredentialSupport;
+import org.opensaml.security.crypto.KeySupport;
+import org.opensaml.soap.client.SOAPClient;
+import org.opensaml.soap.common.SOAPException;
+import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters;
+import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.filter.impl.AttributeFilterImpl;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.SubjectMatchProcessor;
+import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ *
+ */
+public class SAMLDataConnectorTest extends BaseSAMLDataConnectorTest {
+    
+    private SAMLDataConnector queryConnector;
+    
+    private AttributeResolutionContext arc;
+    
+    private BasicResponseMappingStrategy mappingStrategy;
+    
+    private Response response;
+    
+    @BeforeMethod
+    public void beforeMethod() throws Exception {
+        queryConnector = new SAMLDataConnector();
+        queryConnector.setId("saml-test");
+        
+        queryConnector.setAssertionValidationProcessor(buildAssertionValidatonProcessor());
+        
+        queryConnector.setDecryptionProcessor(buildDecryptionProcessor());
+
+        queryConnector.setSubjectMatchProcessor(new SubjectMatchProcessor());
+        queryConnector.setSubjectMatch(false);
+
+        final ExecutableQueryBuilder queryBuilder = buildQueryBuilder();
+        queryBuilder.initialize();
+        queryConnector.setExecutableSearchBuilder(queryBuilder);
+
+        final AttributeTranscoderRegistryImpl transcoderRegistry = buildTranscoderRegistry();
+
+        final AttributeFilterImpl attributeFilter = buildAttributeFilter();
+
+        mappingStrategy = new BasicResponseMappingStrategy();
+        mappingStrategy.setMetadataResolver(SAMLDataConnectorTestingSupport.buildMetadataResolver());
+        mappingStrategy.setRoleDescriptorResolver(SAMLDataConnectorTestingSupport.buildRoleResolver());
+        mappingStrategy.setFilterAttributes(false);
+        mappingStrategy.setAttributeFilterService(SAMLDataConnectorTestingSupport.getAttributeFilterService(attributeFilter));
+        mappingStrategy.setTranscoderRegistry(transcoderRegistry);
+        queryConnector.setMappingStrategy(mappingStrategy);
+
+        queryConnector.setSOAPClient( new SOAPClient() {
+            public void send(@Nonnull String endpoint, @Nonnull InOutOperationContext context) throws SOAPException, SecurityException {
+                context.ensureInboundMessageContext().setMessage(response);
+            }
+        });
+        
+        arc = SAMLDataConnectorTestingSupport.buildAttributeResolutionContext();
+        
+        response = SAMLDataConnectorTestingSupport.buildResponse();
+        final Assertion assertion = response.getAssertions().get(0);
+        assert assertion != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion,
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at example.edu"));
+        SAMLDataConnectorTestingSupport.addAttribute(assertion,
+                SAMLDataConnectorTestingSupport.buildAttribute("affiliation", Attribute.BASIC, "Student", "Staff"));
+    }
+    
+    @Test
+    public void success() throws Exception {
+        mappingStrategy.initialize();
+        queryConnector.initialize();
+        
+        final Map<String, IdPAttribute> attributes = queryConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 2);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> valuesEmail = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesEmail, Set.of("user1 at example.edu"));
+
+        final IdPAttribute affil = attributes.get(AFFIL_ID);
+        Assert.assertNotNull(affil);
+        final Set<String> valuesAffil = affil.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesAffil, Set.of("Staff", "Student"));
+    }
+
+    @Test
+    public void successWithFilter() throws Exception {
+        mappingStrategy.setFilterAttributes(true);
+        mappingStrategy.initialize();
+        queryConnector.initialize();
+        
+        final Map<String, IdPAttribute> attributes = queryConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 1);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> valuesEmail = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesEmail, Set.of("user1 at example.edu"));
+    }
+    
+    @Test
+    public void successWithSubjectMatch() throws Exception {
+        mappingStrategy.initialize();
+        queryConnector.setSubjectMatch(true);
+        queryConnector.initialize();
+        
+        final Map<String, IdPAttribute> attributes = queryConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 2);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> valuesEmail = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesEmail, Set.of("user1 at example.edu"));
+
+        final IdPAttribute affil = attributes.get(AFFIL_ID);
+        Assert.assertNotNull(affil);
+        final Set<String> valuesAffil = affil.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesAffil, Set.of("Staff", "Student"));
+    }
+
+    @Test
+    public void failWithSubjectMatch() throws Exception {
+        mappingStrategy.initialize();
+        queryConnector.setSubjectMatch(true);
+        queryConnector.initialize();
+        
+        final Assertion assertion = response.getAssertions().get(0);
+        assert assertion != null;
+        assertion.setSubject(SAMLDataConnectorTestingSupport.buildSubject(
+                SAMLDataConnectorTestingSupport.buildNameID(NameID.TRANSIENT, "abc123")));
+        
+        final Map<String, IdPAttribute> attributes = queryConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 0);
+    }
+    
+    @Test
+    public void successWithEncryptedAssertion() throws Exception {
+        mappingStrategy.initialize();
+        queryConnector.initialize();
+        
+        final Assertion assertion = response.getAssertions().get(0);
+        final EncryptedAssertion encrypted = encrypter.encrypt(assertion);
+        response.getAssertions().clear();
+        response.getEncryptedAssertions().add(encrypted);
+        
+        final Map<String, IdPAttribute> attributes = queryConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 2);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> valuesEmail = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesEmail, Set.of("user1 at example.edu"));
+
+        final IdPAttribute affil = attributes.get(AFFIL_ID);
+        Assert.assertNotNull(affil);
+        final Set<String> valuesAffil = affil.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesAffil, Set.of("Staff", "Student"));
+    }
+
+    @Test
+    public void failWithEncryptedAssertion() throws Exception {
+        mappingStrategy.initialize();
+        queryConnector.initialize();
+        
+        // Encrypt with a different key than decrypter resolves
+        final KeyPair keyPair = KeySupport.generateKeyPair("RSA", 2048, null);
+        final DataEncryptionParameters dataParams = new DataEncryptionParameters();
+        dataParams.setAlgorithm(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM);
+        final KeyEncryptionParameters keyParams = new KeyEncryptionParameters();
+        keyParams.setAlgorithm(EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP);
+        keyParams.setEncryptionCredential(CredentialSupport.getSimpleCredential(keyPair.getPublic(), null));
+        encrypter = new Encrypter(dataParams, keyParams);
+        
+        final Assertion assertion = response.getAssertions().get(0);
+        final EncryptedAssertion encrypted = encrypter.encrypt(assertion);
+        response.getAssertions().clear();
+        response.getEncryptedAssertions().add(encrypted);
+        
+        final Map<String, IdPAttribute> attributes = queryConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 0);
+    }
+
+    @Test
+    public void failOnAssertionValidation() throws Exception {
+        mappingStrategy.initialize();
+        queryConnector.initialize();
+        
+        final Assertion assertion = response.getAssertions().get(0);
+        assert assertion != null;
+        // Default lifetime in validator is 5 minutes, so this should cause failure
+        assertion.setIssueInstant(Instant.now().minus(Duration.ofHours(1)));
+        
+        final Map<String, IdPAttribute> attributes = queryConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 0);
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnectorTestingSupport.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnectorTestingSupport.java
new file mode 100644
index 000000000..79d5cbdf9
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SAMLDataConnectorTestingSupport.java
@@ -0,0 +1,496 @@
+/*
+ * 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.dc.saml.impl;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLRuntimeException;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.core.xml.schema.impl.XSStringBuilder;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.SAMLVersion;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.metadata.resolver.MetadataResolver;
+import org.opensaml.saml.metadata.resolver.RoleDescriptorResolver;
+import org.opensaml.saml.metadata.resolver.filter.MetadataFilter;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeQuery;
+import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.AttributeValue;
+import org.opensaml.saml.saml2.core.EncryptedAttribute;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.Status;
+import org.opensaml.saml.saml2.core.StatusCode;
+import org.opensaml.saml.saml2.core.Subject;
+import org.opensaml.saml.saml2.metadata.AttributeAuthorityDescriptor;
+import org.opensaml.saml.saml2.metadata.AttributeService;
+import org.opensaml.saml.saml2.metadata.EntityDescriptor;
+import org.opensaml.saml.saml2.metadata.RoleDescriptor;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.XMLObjectAttributeValue;
+import net.shibboleth.idp.attribute.filter.AttributeFilter;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.security.impl.SecureRandomIdentifierGenerationStrategy;
+import net.shibboleth.shared.service.ReloadableService;
+import net.shibboleth.shared.service.ServiceException;
+import net.shibboleth.shared.service.ServiceableComponent;
+
+/**
+ * Support for testing SAML data connectors and related components.
+ */
+public class SAMLDataConnectorTestingSupport {
+    
+    public static final String ISSUER = "urn:test:aa";
+
+    public static final String AA_ENDPOINT = "https://aa.example.edu/attribute-query";
+    
+    public static final String SUBJECT_FORMAT = NameID.PERSISTENT;
+    
+    public static final String SUBJECT_VALUE = "urn:test:nameid:persistent:test.user";
+    
+    public static final SecureRandomIdentifierGenerationStrategy identifierStrategy =
+            new SecureRandomIdentifierGenerationStrategy();
+    
+    /** Constructor. */
+    private SAMLDataConnectorTestingSupport() { }
+    
+    @Nonnull
+    public static AttributeResolutionContext buildAttributeResolutionContext() {
+        final ProfileRequestContext prc = new ProfileRequestContext();
+        AttributeResolutionContext arc = prc.ensureSubcontext(AttributeResolutionContext.class);
+        arc.ensureSubcontext(AttributeResolverWorkContext.class);
+        return arc;
+    }
+    
+    @Nonnull
+    public static MetadataResolver buildMetadataResolver() {
+        return new MockMetadataResolver();
+    }
+    
+    @Nonnull
+    public static RoleDescriptorResolver buildRoleResolver() {
+        return new MockRoleDescriptorResolver();
+    }
+    
+    @Nonnull
+    public static ReloadableService<AttributeFilter> getAttributeFilterService(@Nonnull final AttributeFilter filter) {
+        return new MockAttributeFilterService(filter);
+    }
+    
+    @Nonnull
+    public static AttributeAuthorityDescriptor buildAuthorityDescriptor() {
+        final EntityDescriptor entity = (EntityDescriptor) XMLObjectSupport.buildXMLObject(EntityDescriptor.ELEMENT_QNAME);
+        entity.setEntityID(ISSUER);
+
+        final AttributeAuthorityDescriptor role =  (AttributeAuthorityDescriptor) XMLObjectSupport.buildXMLObject(
+                AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
+        role.getAttributeServices().add(buildAttributeService());
+        
+        entity.getRoleDescriptors().add(role);
+
+        return role;
+    }
+    
+    @Nonnull
+    public static AttributeService buildAttributeService() {
+        final AttributeService service = (AttributeService) XMLObjectSupport.buildXMLObject(AttributeService.DEFAULT_ELEMENT_NAME);
+        service.setBinding(SAMLConstants.SAML2_SOAP11_BINDING_URI);
+        service.setLocation(AA_ENDPOINT);
+        return service;
+    }
+    
+    @Nonnull
+    public static Map<String, List<IdPAttributeValue>> buildDependencyAttributes() {
+        final Map<String, List<IdPAttributeValue>>  attribs = new HashMap<>();
+        
+        attribs.put("email", List.of(new StringAttributeValue("Test.User at example.edu")));
+
+        attribs.put("eppn", List.of(new ScopedStringAttributeValue("test.user", "example.edu")));
+
+        attribs.put("uid", List.of(new StringAttributeValue("test.user")));
+        
+        attribs.put("NameID", List.of(new XMLObjectAttributeValue(buildNameID())));
+        
+        return attribs;
+    }
+    
+    @Nonnull
+    public static Attribute buildAttribute(@Nonnull final String name, @Nullable final String format, final String ... values) {
+        final Attribute attribute = (Attribute) XMLObjectSupport.buildXMLObject(Attribute.DEFAULT_ELEMENT_NAME);
+        attribute.setName(name);
+        attribute.setNameFormat(format);
+        
+        XSStringBuilder builder = (XSStringBuilder) XMLObjectSupport.getBuilder(XSString.TYPE_NAME);
+        if (builder == null) {
+            throw new XMLRuntimeException("Builder for XSString was not available");
+        }
+        for (String value : values) {
+           final XSString xmlValue = builder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
+           xmlValue.setValue(value);
+           attribute.getAttributeValues().add(xmlValue);
+        }
+
+        return attribute;
+    }
+    
+    @Nonnull
+    public static NameID buildNameID(@Nonnull final String format, @Nonnull final String value) {
+        final NameID nameID = (NameID) XMLObjectSupport.buildXMLObject(NameID.DEFAULT_ELEMENT_NAME);
+        nameID.setFormat(format);
+        nameID.setValue(value);
+        return nameID;
+    }
+    
+    @Nonnull
+    public static NameID buildNameID() {
+        return buildNameID(SUBJECT_FORMAT, SUBJECT_VALUE);
+    }
+    
+    @Nonnull
+    public static Subject buildSubject(@Nonnull final NameID nameID) {
+        final Subject subject = (Subject) XMLObjectSupport.buildXMLObject(Subject.DEFAULT_ELEMENT_NAME);
+        subject.setNameID(nameID);
+        return subject;
+    }
+
+    @Nonnull
+    public static Subject buildSubject() {
+        final Subject subject = (Subject) XMLObjectSupport.buildXMLObject(Subject.DEFAULT_ELEMENT_NAME);
+        subject.setNameID(buildNameID());
+        return subject;
+    }
+
+    @Nonnull
+    public static AttributeQuery buildQuery() {
+        final AttributeQuery query = (AttributeQuery) XMLObjectSupport.buildXMLObject(AttributeQuery.DEFAULT_ELEMENT_NAME);
+        query.setSubject(buildSubject());
+        return query;
+    }
+
+    @Nonnull
+    public static Response buildResponse() {
+        final Response response = (Response) XMLObjectSupport.buildXMLObject(Response.DEFAULT_ELEMENT_NAME);
+        response.setID(identifierStrategy.generateIdentifier());
+        response.setIssuer(buildIssuer());
+        response.setVersion(SAMLVersion.VERSION_20);
+        response.setIssueInstant(Instant.now());
+
+        final Status status = (Status) XMLObjectSupport.buildXMLObject(Status.DEFAULT_ELEMENT_NAME);
+        final StatusCode statusCode = (StatusCode) XMLObjectSupport.buildXMLObject(StatusCode.DEFAULT_ELEMENT_NAME);
+        statusCode.setValue(StatusCode.SUCCESS);
+        status.setStatusCode(statusCode);
+        response.setStatus(status);
+        
+        response.getAssertions().add(buildAssertion(null));
+
+        return response;
+    }
+    
+    @Nonnull
+    public static Assertion buildAssertion(@Nullable final Subject subject) {
+       final Assertion assertion = (Assertion) XMLObjectSupport.buildXMLObject(Assertion.DEFAULT_ELEMENT_NAME);
+       assertion.setID(identifierStrategy.generateIdentifier());
+       assertion.setVersion(SAMLVersion.VERSION_20);
+       assertion.setIssueInstant(Instant.now());
+
+       assertion.setIssuer(buildIssuer());
+       assertion.setSubject(subject != null ? subject : buildSubject());
+       return assertion;
+    }
+    
+    @Nonnull static Issuer buildIssuer() {
+        return buildIssuer(ISSUER);
+    }
+
+    @Nonnull static Issuer buildIssuer(@Nullable final String issuerID) {
+        final Issuer issuer = (Issuer) XMLObjectSupport.buildXMLObject(Issuer.DEFAULT_ELEMENT_NAME);
+        issuer.setValue(issuerID);
+        return issuer;
+    }
+
+    @Nonnull
+    public static InOutOperationContext buildOperationContext(@Nullable final AttributeQuery request,
+            @Nullable final Response response) {
+        final MessageContext outbound = new MessageContext();
+        outbound.setMessage(request != null ? request : buildQuery());
+
+        final MessageContext inbound = new MessageContext();
+        inbound.setMessage(response != null ? response : buildResponse());
+
+        final InOutOperationContext opContext = new InOutOperationContext(inbound, outbound);
+
+        return opContext;
+    }
+    
+    @Nonnull
+    public static Response getResponse(@Nonnull InOutOperationContext opContext) {
+        final MessageContext inbound = opContext.getInboundMessageContext();
+        if (inbound != null) {
+            final Response response = (Response) inbound.getMessage();
+            if (response != null)  {
+                return response;
+            }
+        }
+        throw new XMLRuntimeException("Could not obtain Response from operation context");
+    }
+
+    @Nonnull
+    public static AttributeQuery getQuery(@Nonnull InOutOperationContext opContext) {
+        final MessageContext outbound = opContext.getOutboundMessageContext();
+        if (outbound != null) {
+            final AttributeQuery query = (AttributeQuery) outbound.getMessage();
+            if (query != null)  {
+                return query;
+            }
+        }
+        throw new XMLRuntimeException("Could not obtain AttributeQuery from operation context");
+    }
+
+    @Nonnull
+    public static Subject getSubject(@Nonnull AttributeQuery query) {
+        if (query != null)  {
+            final Subject subject = query.getSubject();
+            if (subject != null) {
+                return subject;
+            }
+        }
+        throw new XMLRuntimeException("Could not obtain Subject from AttributeQuery");
+    }
+
+    @Nonnull
+    public static Subject getSubject(@Nonnull Assertion assertion) {
+        if (assertion != null)  {
+            final Subject subject = assertion.getSubject();
+            if (subject != null) {
+                return subject;
+            }
+        }
+        throw new XMLRuntimeException("Could not obtain Subject from Assertion");
+    }
+    
+    public static void addAttribute(@Nonnull Assertion assertion, @Nonnull Attribute attribute) {
+        AttributeStatement statement = null;
+        if (assertion.getAttributeStatements().isEmpty()) {
+            statement = (AttributeStatement) XMLObjectSupport.buildXMLObject(AttributeStatement.DEFAULT_ELEMENT_NAME);
+            assertion.getAttributeStatements().add(statement);
+        } else {
+            statement = assertion.getAttributeStatements().get(0);
+        }
+        statement.getAttributes().add(attribute);
+    }
+
+    public static void addEncryptedAttribute(@Nonnull Assertion assertion, @Nonnull EncryptedAttribute attribute) {
+        AttributeStatement statement = null;
+        if (assertion.getAttributeStatements().isEmpty()) {
+            statement = (AttributeStatement) XMLObjectSupport.buildXMLObject(AttributeStatement.DEFAULT_ELEMENT_NAME);
+            assertion.getAttributeStatements().add(statement);
+        } else {
+            statement = assertion.getAttributeStatements().get(0);
+        }
+        statement.getEncryptedAttributes().add(attribute);
+    }
+
+    public static class MockRoleDescriptorResolver implements RoleDescriptorResolver {
+
+        /** {@inheritDoc} */
+        @Override
+        @Nonnull
+        public Iterable<RoleDescriptor> resolve(@Nullable CriteriaSet criteria) throws ResolverException {
+            return CollectionSupport.listOf(buildAuthorityDescriptor());
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        @Nullable
+        public RoleDescriptor resolveSingle(@Nullable CriteriaSet criteria) throws ResolverException {
+            return buildAuthorityDescriptor();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        @Nullable
+        public String getId() {
+            return "rdr-1";
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean isRequireValidMetadata() {
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void setRequireValidMetadata(boolean requireValidMetadata) {
+            
+        }
+        
+    }
+    
+    public static class MockMetadataResolver implements MetadataResolver {
+
+        /** {@inheritDoc} */
+        @Override
+        @Nonnull
+        public Iterable<EntityDescriptor> resolve(@Nullable CriteriaSet criteria) throws ResolverException {
+            final EntityDescriptor ed = (EntityDescriptor) buildAuthorityDescriptor().getParent();
+            assert ed != null;
+            return CollectionSupport.listOf(ed);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        @Nullable
+        public EntityDescriptor resolveSingle(@Nullable CriteriaSet criteria) throws ResolverException {
+            return (EntityDescriptor) buildAuthorityDescriptor().getParent();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        @Nullable
+        public String getId() {
+            return "mdr-1";
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        @Nullable
+        public String getType() {
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean isRequireValidMetadata() {
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void setRequireValidMetadata(boolean requireValidMetadata) {
+
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        @Nullable
+        public MetadataFilter getMetadataFilter() {
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void setMetadataFilter(@Nullable MetadataFilter newFilter) {
+            
+        }
+        
+    }
+    
+    public static class MockAttributeFilterService implements ReloadableService<AttributeFilter> {
+        
+        @Nonnull private AttributeFilter filter;
+        
+        /**
+         * Constructor.
+         *
+         * @param attributefilter the attributeFilter
+         */
+        public MockAttributeFilterService(@Nonnull final AttributeFilter attributefilter) {
+            filter = Constraint.isNotNull(attributefilter, "Filter was null");
+        }
+
+        /** Constructor. */
+
+        /** {@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 filter;
+                }
+                
+                @Override
+                public void close() {
+                    
+                }
+            };
+        }
+        
+    }
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SimpleAggregationSAMLDataConnectorTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SimpleAggregationSAMLDataConnectorTest.java
new file mode 100644
index 000000000..dcefc7fc8
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/impl/SimpleAggregationSAMLDataConnectorTest.java
@@ -0,0 +1,265 @@
+/*
+ * 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.dc.saml.impl;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.security.SecurityException;
+import org.opensaml.soap.client.SOAPClient;
+import org.opensaml.soap.common.SOAPException;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.filter.impl.AttributeFilterImpl;
+import net.shibboleth.idp.attribute.resolver.ResolverAttributeDefinitionDependency;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.plugin.impl.ContextAuthorityEntityIDResolver;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDReference;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDValue;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.SubjectMatchProcessor;
+import net.shibboleth.idp.attribute.resolver.testing.MockStaticAttributeDefinition;
+import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ *
+ */
+public class SimpleAggregationSAMLDataConnectorTest extends BaseSAMLDataConnectorTest {
+    
+    public static final String ISSUER1 = SAMLDataConnectorTestingSupport.ISSUER;
+    public static final String ISSUER2 = "urn:test:someOtherAA";
+    
+    private SimpleAggregationSAMLDataConnector aggregationConnector;
+    
+    private SAMLDataConnector queryConnector;
+    
+    private AttributeResolutionContext arc;
+    
+    private BasicResponseMappingStrategy mappingStrategy;
+    
+    private Response response1, response2;
+    
+    @BeforeMethod
+    public void beforeMethod() throws Exception {
+        queryConnector = new SAMLDataConnector();
+        queryConnector.setId("saml-test");
+        
+        queryConnector.setAssertionValidationProcessor(buildAssertionValidatonProcessor());
+        
+        queryConnector.setDecryptionProcessor(buildDecryptionProcessor());
+
+        queryConnector.setSubjectMatchProcessor(new SubjectMatchProcessor());
+        queryConnector.setSubjectMatch(false);
+
+        final ExecutableQueryBuilder queryBulder = buildQueryBuilder();
+        queryBulder.setAuthorityEntityIDStrategy(new ContextAuthorityEntityIDResolver());
+        queryBulder.initialize();
+        queryConnector.setExecutableSearchBuilder(queryBulder);
+
+        final AttributeTranscoderRegistryImpl transcoderRegistry = buildTranscoderRegistry();
+
+        final AttributeFilterImpl attributeFilter = buildAttributeFilter();
+
+        mappingStrategy = new BasicResponseMappingStrategy();
+        mappingStrategy.setMetadataResolver(SAMLDataConnectorTestingSupport.buildMetadataResolver());
+        mappingStrategy.setRoleDescriptorResolver(SAMLDataConnectorTestingSupport.buildRoleResolver());
+        mappingStrategy.setFilterAttributes(false);
+        mappingStrategy.setAttributeFilterService(SAMLDataConnectorTestingSupport.getAttributeFilterService(attributeFilter));
+        mappingStrategy.setTranscoderRegistry(transcoderRegistry);
+        queryConnector.setMappingStrategy(mappingStrategy);
+
+        queryConnector.setSOAPClient( new SOAPClient() {
+            public void send(@Nonnull String endpoint, @Nonnull InOutOperationContext context) throws SOAPException, SecurityException {
+                final AttributeAuthorityEntityIDContext authorityIDContext = arc.ensureSubcontext(AttributeAuthorityEntityIDContext.class);
+                switch (authorityIDContext.getAuthorityEntityID()) {
+                    case ISSUER1:
+                        context.ensureInboundMessageContext().setMessage(response1);
+                        break;
+                    case ISSUER2:
+                        context.ensureInboundMessageContext().setMessage(response2);
+                        break;
+                    default:
+                        throw new RuntimeException("Saw unexpected authority entityID: " + authorityIDContext.getAuthorityEntityID());
+                }
+            }
+        });
+        
+        aggregationConnector = new SimpleAggregationSAMLDataConnector();
+        aggregationConnector.setId("agg-saml-test");
+        aggregationConnector.setQueryConnector(queryConnector);
+        aggregationConnector.setEntityIDSources(CollectionSupport.listOf(
+                new AttributeAuthorityEntityIDValue(ISSUER1)));
+        
+        arc = SAMLDataConnectorTestingSupport.buildAttributeResolutionContext();
+        
+        response1 = SAMLDataConnectorTestingSupport.buildResponse();
+        final Assertion assertion1 = response1.getAssertions().get(0);
+        assert assertion1 != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion1,
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user1 at example.edu"));
+        SAMLDataConnectorTestingSupport.addAttribute(assertion1,
+                SAMLDataConnectorTestingSupport.buildAttribute("affiliation", Attribute.BASIC, "Student", "Staff"));
+
+        response2 = SAMLDataConnectorTestingSupport.buildResponse();
+        response2.setIssuer(SAMLDataConnectorTestingSupport.buildIssuer(ISSUER2));
+        final Assertion assertion2 = response2.getAssertions().get(0);
+        assertion2.setIssuer(SAMLDataConnectorTestingSupport.buildIssuer(ISSUER2));
+        assert assertion2 != null;
+        SAMLDataConnectorTestingSupport.addAttribute(assertion2,
+                SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user at somewhere.edu"));
+        SAMLDataConnectorTestingSupport.addAttribute(assertion2,
+                SAMLDataConnectorTestingSupport.buildAttribute("affiliation", Attribute.BASIC, "Alumni", "Faculty"));
+    }
+    
+    @Test
+    public void successOneAuthorityByValue() throws Exception {
+        mappingStrategy.initialize();
+        queryConnector.initialize();
+        aggregationConnector.initialize();
+        
+        final Map<String, IdPAttribute> attributes = aggregationConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 2);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> valuesEmail = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesEmail, Set.of("user1 at example.edu"));
+
+        final IdPAttribute affil = attributes.get(AFFIL_ID);
+        Assert.assertNotNull(affil);
+        final Set<String> valuesAffil = affil.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesAffil, Set.of("Staff", "Student"));
+    }
+
+    @Test
+    public void successTwoAuthoritiesByValue() throws Exception {
+        aggregationConnector.setEntityIDSources(CollectionSupport.listOf(
+                new AttributeAuthorityEntityIDValue(ISSUER1),
+                new AttributeAuthorityEntityIDValue(ISSUER2)));
+
+        mappingStrategy.initialize();
+        queryConnector.initialize();
+        aggregationConnector.initialize();
+        
+        final Map<String, IdPAttribute> attributes = aggregationConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 2);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> valuesEmail = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesEmail, Set.of("user1 at example.edu", "user at somewhere.edu"));
+
+        final IdPAttribute affil = attributes.get(AFFIL_ID);
+        Assert.assertNotNull(affil);
+        final Set<String> valuesAffil = affil.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesAffil, Set.of("Staff", "Student", "Faculty", "Alumni"));
+    }
+
+    @Test
+    public void failOneAuthorityByReferenceNoDependencies() throws Exception {
+        aggregationConnector.setEntityIDSources(CollectionSupport.listOf(new AttributeAuthorityEntityIDReference("userAttributeAuthority")));
+
+        mappingStrategy.initialize();
+        queryConnector.initialize();
+        aggregationConnector.initialize();
+        
+        final Map<String, IdPAttribute> attributes = aggregationConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 0);
+    }
+    
+    @Test
+    public void successOneAuthorityByReference() throws Exception {
+        final String aaDepName = "userAttributeAuthority";
+
+        final IdPAttribute idpAttribute = new IdPAttribute(aaDepName);
+        idpAttribute.setValues(CollectionSupport.listOf(new StringAttributeValue(ISSUER1)));
+        final MockStaticAttributeDefinition attribDef = new MockStaticAttributeDefinition();
+        attribDef.setId(aaDepName);
+        attribDef.setValue(idpAttribute);
+        attribDef.initialize();
+
+        arc.ensureSubcontext(AttributeResolverWorkContext.class).recordAttributeDefinitionResolution(attribDef, idpAttribute);
+
+        aggregationConnector.setEntityIDSources(CollectionSupport.listOf(new AttributeAuthorityEntityIDReference(aaDepName)));
+        aggregationConnector.setAttributeDependencies(CollectionSupport.setOf(new ResolverAttributeDefinitionDependency(aaDepName)));
+
+        mappingStrategy.initialize();
+        queryConnector.initialize();
+        aggregationConnector.initialize();
+        
+        final Map<String, IdPAttribute> attributes = aggregationConnector.resolve(arc);
+        Assert.assertNotNull(attributes);
+        assert attributes != null;
+        
+        Assert.assertEquals(attributes.size(), 2);
+        
+        final IdPAttribute email = attributes.get(EMAIL_ID);
+        Assert.assertNotNull(email);
+        final Set<String> valuesEmail = email.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesEmail, Set.of("user1 at example.edu"));
+
+        final IdPAttribute affil = attributes.get(AFFIL_ID);
+        Assert.assertNotNull(affil);
+        final Set<String> valuesAffil = affil.getValues().stream()
+                .filter(StringAttributeValue.class::isInstance)
+                .map(StringAttributeValue.class::cast)
+                .map(StringAttributeValue::getValue)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        Assert.assertEquals(valuesAffil, Set.of("Staff", "Student"));
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ChainingAuthorityEntityIDResolverTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ChainingAuthorityEntityIDResolverTest.java
new file mode 100644
index 000000000..cf04aa8d4
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ChainingAuthorityEntityIDResolverTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.dc.saml.plugin.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ *
+ */
+public class ChainingAuthorityEntityIDResolverTest extends XMLObjectBaseTestCase {
+    
+    private ChainingAuthorityEntityIDResolver resolver;
+    
+    private AttributeResolutionContext arc;
+    
+    private Map<String, List<IdPAttributeValue>> dependencies;
+    
+    @BeforeMethod
+    public void setupMethod() {
+        resolver = new ChainingAuthorityEntityIDResolver();
+
+        arc = SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(); 
+        dependencies = SAMLDataConnectorTestingSupport.buildDependencyAttributes();
+    }
+    
+    @Test
+    public void noMembers() throws Exception {
+        resolver.setResolvers(CollectionSupport.emptyList());
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null;
+        
+        final String entityID = resolver.resolve(arc, dependencies);
+        Assert.assertNull(entityID);
+    }
+
+    @Test
+    public void basic() throws Exception {
+        resolver.setResolvers(List.of(
+                (arc, deps) ->  { return null; },
+                (arc, deps) ->  { return "urn:test:aa"; },
+                (arc, deps) ->  { return null; }
+                ));
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null;
+        
+        final String entityID = resolver.resolve(arc, dependencies);
+        Assert.assertNotNull(entityID);
+        Assert.assertEquals(entityID, "urn:test:aa");
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ChainingSelfEntityIDResolverTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ChainingSelfEntityIDResolverTest.java
new file mode 100644
index 000000000..780684f12
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ChainingSelfEntityIDResolverTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.dc.saml.plugin.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.saml.saml2.metadata.AttributeAuthorityDescriptor;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ *
+ */
+public class ChainingSelfEntityIDResolverTest extends XMLObjectBaseTestCase {
+    
+   private ChainingSelfEntityIDResolver resolver;
+    
+    private AttributeResolutionContext arc;
+    
+    private Map<String, List<IdPAttributeValue>> dependencies;
+    
+    private AttributeAuthorityDescriptor authorityDescriptor;
+    
+    @BeforeMethod
+    public void setupMethod() {
+        resolver = new ChainingSelfEntityIDResolver();
+
+        arc = SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(); 
+        dependencies = SAMLDataConnectorTestingSupport.buildDependencyAttributes();
+        authorityDescriptor = SAMLDataConnectorTestingSupport.buildAuthorityDescriptor();
+    }
+    
+    @Test
+    public void noMembers() throws Exception {
+        resolver.setResolvers(CollectionSupport.emptyList());
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+        
+        final String entityID = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNull(entityID);
+    }
+
+    @Test
+    public void basic() throws Exception {
+        resolver.setResolvers(List.of(
+                (arc, deps, authDesc) ->  { return null; },
+                (arc, deps, authDesc) ->  { return "urn:test:self"; },
+                (arc, deps, authDesc) ->  { return null; }
+                ));
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+        
+        final String entityID = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNotNull(entityID);
+        Assert.assertEquals(entityID, "urn:test:self");
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ChainingSubjectResolverTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ChainingSubjectResolverTest.java
new file mode 100644
index 000000000..9393ef283
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ChainingSubjectResolverTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.dc.saml.plugin.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.saml.saml2.core.Subject;
+import org.opensaml.saml.saml2.metadata.AttributeAuthorityDescriptor;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ *
+ */
+public class ChainingSubjectResolverTest extends XMLObjectBaseTestCase {
+    
+   private ChainingSubjectResolver resolver;
+    
+    private AttributeResolutionContext arc;
+    
+    private Map<String, List<IdPAttributeValue>> dependencies;
+    
+    private AttributeAuthorityDescriptor authorityDescriptor;
+    
+    private Subject targetSubject;
+    
+    @BeforeMethod
+    public void setupMethod() {
+        resolver = new ChainingSubjectResolver();
+
+        arc = SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(); 
+        dependencies = SAMLDataConnectorTestingSupport.buildDependencyAttributes();
+        authorityDescriptor = SAMLDataConnectorTestingSupport.buildAuthorityDescriptor();
+        targetSubject = SAMLDataConnectorTestingSupport.buildSubject();
+    }
+    
+    @Test
+    public void noMembers() throws Exception {
+        resolver.setResolvers(CollectionSupport.emptyList());
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+        
+        final Subject subject = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNull(subject);
+    }
+
+    @Test
+    public void basic() throws Exception {
+        resolver.setResolvers(List.of(
+                (arc, deps, authDesc) ->  { return null; },
+                (arc, deps, authDesc) ->  { return targetSubject; },
+                (arc, deps, authDesc) ->  { return null; }
+                ));
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+        
+        final Subject subject = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNotNull(subject);
+        Assert.assertSame(subject, targetSubject);
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ContextAuthorityEntityIDResolverTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ContextAuthorityEntityIDResolverTest.java
new file mode 100644
index 000000000..8288baeb4
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/ContextAuthorityEntityIDResolverTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.dc.saml.plugin.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+import net.shibboleth.idp.attribute.resolver.dc.saml.util.impl.AttributeAuthorityEntityIDContext;
+
+/**
+ *
+ */
+public class ContextAuthorityEntityIDResolverTest extends XMLObjectBaseTestCase {
+    
+    private ContextAuthorityEntityIDResolver resolver;
+    
+    private AttributeResolutionContext arc;
+    
+    private Map<String, List<IdPAttributeValue>> dependencies;
+    
+    @BeforeMethod
+    public void setupMethod() {
+        resolver = new ContextAuthorityEntityIDResolver();
+
+        arc = SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(); 
+        dependencies = SAMLDataConnectorTestingSupport.buildDependencyAttributes();
+    }
+    
+    @Test
+    public void missing() throws Exception {
+        assert arc != null ; assert dependencies != null;
+        
+        final String entityID = resolver.resolve(arc, dependencies);
+        Assert.assertNull(entityID);
+    }
+
+    @Test
+    public void present() throws Exception {
+        assert arc != null; assert dependencies != null;
+        
+        final AttributeAuthorityEntityIDContext aaContext = new AttributeAuthorityEntityIDContext(("urn:test:aa"));
+        arc.addSubcontext(aaContext, true);
+
+        final String entityID = resolver.resolve(arc, dependencies);
+        Assert.assertNotNull(entityID);
+        Assert.assertEquals(entityID, "urn:test:aa");
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaDecryptionConfigurationLookupTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaDecryptionConfigurationLookupTest.java
new file mode 100644
index 000000000..997ca5bd7
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaDecryptionConfigurationLookupTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.dc.saml.plugin.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.criterion.ProfileIDCriterion;
+import org.opensaml.saml.criterion.RoleDescriptorCriterion;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.xmlsec.DecryptionConfiguration;
+import org.opensaml.xmlsec.impl.BasicDecryptionConfiguration;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.resolver.dc.saml.ResponseData;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.Resolver;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ *
+ */
+public class CriteriaDecryptionConfigurationLookupTest extends XMLObjectBaseTestCase {
+    
+    private CriteriaDecryptionConfigurationLookup lookup;
+    
+    private List<DecryptionConfiguration> targetConfigs;
+    
+    private ResponseData responseData;
+    
+    @BeforeMethod
+    public void setupMethod() {
+        lookup = new CriteriaDecryptionConfigurationLookup(); 
+        lookup.setSecurityConfigurationProfileId("http://shibboleth.net/ns/profiles/saml2/query/attribute");
+        
+        targetConfigs = List.of(new BasicDecryptionConfiguration(), new BasicDecryptionConfiguration(), new BasicDecryptionConfiguration());
+        lookup.setDecryptionConfigurationResolver(new MockResolver());
+        
+        responseData = new ResponseData(
+                (Response) XMLObjectSupport.buildXMLObject(Response.DEFAULT_ELEMENT_NAME),
+                new InOutOperationContext(new MessageContext(), new MessageContext()),
+                SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(),
+                SAMLDataConnectorTestingSupport.buildDependencyAttributes(),
+                SAMLDataConnectorTestingSupport.buildAuthorityDescriptor()
+                );
+    }
+    
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void missingChildResolver() throws ComponentInitializationException {
+        lookup.setDecryptionConfigurationResolver(null);
+        lookup.initialize();
+    }
+
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void missingProfileID() throws ComponentInitializationException {
+        lookup.setSecurityConfigurationProfileId(null);
+        lookup.initialize();
+    }
+
+    @Test
+    public void basic() throws ComponentInitializationException {
+        lookup.initialize();
+        
+        final List<DecryptionConfiguration> configs = lookup.apply(responseData);
+        Assert.assertNotNull(configs);
+        Assert.assertSame(configs, targetConfigs);
+    }
+
+    // Helpers
+    private class MockResolver implements Resolver<List<DecryptionConfiguration>, CriteriaSet>  {
+
+        /** {@inheritDoc} */
+        @Override
+        @Nonnull
+        public Iterable<List<DecryptionConfiguration>> resolve(@Nullable CriteriaSet criteria) throws ResolverException {
+            final List<DecryptionConfiguration> configs = resolveSingle(criteria);
+            return configs == null ? CollectionSupport.emptyList() : CollectionSupport.listOf(configs);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        @Nullable
+        public List<DecryptionConfiguration> resolveSingle(@Nullable CriteriaSet criteria) throws ResolverException {
+            if (criteria != null
+                    && criteria.contains(RoleDescriptorCriterion.class)
+                    && criteria.contains(ProfileIDCriterion.class)) {
+                return targetConfigs;
+            } else {
+                return null;
+            }
+        }
+        
+    }
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaSelfEntityIDResolverTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaSelfEntityIDResolverTest.java
new file mode 100644
index 000000000..e5e66e6a3
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/CriteriaSelfEntityIDResolverTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.dc.saml.plugin.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.saml.criterion.RoleDescriptorCriterion;
+import org.opensaml.saml.saml2.metadata.AttributeAuthorityDescriptor;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.Resolver;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ *
+ */
+public class CriteriaSelfEntityIDResolverTest extends XMLObjectBaseTestCase {
+    
+    private CriteriaSelfEntityIDResolver resolver;
+    
+    private AttributeResolutionContext arc;
+    
+    private Map<String, List<IdPAttributeValue>> dependencies;
+    
+    private AttributeAuthorityDescriptor authorityDescriptor;
+    
+    @BeforeMethod
+    public void setupMethod() {
+        resolver = new CriteriaSelfEntityIDResolver(); 
+        
+        resolver.setSelfEntityIDResolver(new MockResolver());
+        
+        arc = SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(); 
+        dependencies = SAMLDataConnectorTestingSupport.buildDependencyAttributes();
+        authorityDescriptor = SAMLDataConnectorTestingSupport.buildAuthorityDescriptor();
+    }
+    
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void missingChildResolver() throws ComponentInitializationException {
+        resolver.setSelfEntityIDResolver(null);
+        resolver.initialize();
+    }
+
+    @Test
+    public void basic() throws Exception {
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+        
+        final String entityID = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNotNull(entityID);
+        Assert.assertEquals(entityID, "urn:test:self");
+    }
+
+    // Helpers
+    private class MockResolver implements Resolver<String, CriteriaSet>  {
+
+        /** {@inheritDoc} */
+        @Override
+        @Nonnull
+        public Iterable<String> resolve(@Nullable CriteriaSet criteria) throws ResolverException {
+            final String entityID = resolveSingle(criteria);
+            return entityID == null ? CollectionSupport.emptyList() : CollectionSupport.listOf(entityID);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        @Nullable
+        public String resolveSingle(@Nullable CriteriaSet criteria) throws ResolverException {
+            if (criteria != null
+                    && criteria.contains(RoleDescriptorCriterion.class)) {
+                return "urn:test:self";
+            } else {
+                return null;
+            }
+        }
+        
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/DependencyAttributeSubjectResolverTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/DependencyAttributeSubjectResolverTest.java
new file mode 100644
index 000000000..bc41bb7bd
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/plugin/impl/DependencyAttributeSubjectResolverTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.dc.saml.plugin.impl;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Subject;
+import org.opensaml.saml.saml2.metadata.AttributeAuthorityDescriptor;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ *
+ */
+public class DependencyAttributeSubjectResolverTest extends XMLObjectBaseTestCase {
+    
+    private DependencyAttributeSubjectResolver resolver;
+    
+    private AttributeResolutionContext arc;
+    
+    private Map<String, List<IdPAttributeValue>> dependencies;
+    
+    private AttributeAuthorityDescriptor authorityDescriptor;
+    
+    @BeforeMethod
+    public void setupMethod() {
+        resolver = new DependencyAttributeSubjectResolver();
+        resolver.setAttributeIDs(List.of("foobar", "email", "uid"));
+        resolver.setDefaultNameIDFormat(NameID.PERSISTENT);
+
+        final Map<String, String> formatMap = new HashMap<>();
+        formatMap.put("email", NameID.EMAIL);
+        formatMap.put("uid", NameID.UNSPECIFIED);
+        resolver.setNameIDFormatMap(formatMap);
+
+        arc = SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(); 
+        dependencies = SAMLDataConnectorTestingSupport.buildDependencyAttributes();
+        authorityDescriptor = SAMLDataConnectorTestingSupport.buildAuthorityDescriptor();
+    }
+    
+    @Test
+    public void emptyDependencies() throws Exception {
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+
+        final Subject subject = resolver.resolve(arc, CollectionSupport.emptyMap(), authorityDescriptor);
+        Assert.assertNull(subject);
+    }
+
+    @Test
+    public void emptyAttributeIDs() throws Exception {
+        resolver.setAttributeIDs(CollectionSupport.emptyList());
+        resolver.initialize();
+        
+        assert arc != null && dependencies != null && authorityDescriptor != null;
+
+
+        final Subject subject = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNull(subject);
+    }
+
+    @Test
+    public void email() throws Exception {
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+
+        final Subject subject = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNotNull(subject);
+        assert subject != null;
+        
+        Assert.assertNotNull(subject.getNameID());
+        final NameID nameID = subject.getNameID();
+        assert nameID != null;
+        Assert.assertEquals(nameID.getValue(), "Test.User at example.edu");
+        Assert.assertEquals(nameID.getFormat(), NameID.EMAIL);
+        
+    }
+    @Test
+    public void uid() throws Exception {
+        resolver.setAttributeIDs(List.of("foobar", "uid", "email"));
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+
+        final Subject subject = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNotNull(subject);
+        assert subject != null;
+        
+        Assert.assertNotNull(subject.getNameID());
+        final NameID nameID = subject.getNameID();
+        assert nameID != null;
+        Assert.assertEquals(nameID.getValue(), "test.user");
+        Assert.assertEquals(nameID.getFormat(), NameID.UNSPECIFIED);
+    }
+
+    @Test
+    public void eppn() throws Exception {
+        resolver.setAttributeIDs(List.of("foobar", "eppn", "email", "uid"));
+        resolver.initialize();
+
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+
+        final Subject subject = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNotNull(subject);
+        assert subject != null;
+        
+        Assert.assertNotNull(subject.getNameID());
+        final NameID nameID = subject.getNameID();
+        assert nameID != null;
+        Assert.assertEquals(nameID.getValue(), "test.user at example.edu");
+        Assert.assertEquals(nameID.getFormat(), NameID.PERSISTENT);
+    }
+
+    @Test
+    public void NameID() throws Exception {
+        resolver.setAttributeIDs(List.of("foobar", "NameID", "email", "uid"));
+        resolver.initialize();
+        
+        assert arc != null ; assert dependencies != null ; assert authorityDescriptor != null;
+
+        final Subject subject = resolver.resolve(arc, dependencies, authorityDescriptor);
+        Assert.assertNotNull(subject);
+        assert subject != null;
+        
+        Assert.assertNotNull(subject.getNameID());
+        final NameID nameID = subject.getNameID();
+        assert nameID != null;
+        Assert.assertEquals(nameID.getValue(), SAMLDataConnectorTestingSupport.SUBJECT_VALUE);
+        Assert.assertEquals(nameID.getFormat(), SAMLDataConnectorTestingSupport.SUBJECT_FORMAT);
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AssertionValidationProcessorTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AssertionValidationProcessorTest.java
new file mode 100644
index 000000000..a68fb9234
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/AssertionValidationProcessorTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.dc.saml.util.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Set;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator;
+import org.opensaml.saml.saml2.assertion.impl.AudienceRestrictionConditionValidator;
+import org.opensaml.saml.saml2.assertion.messaging.impl.DefaultAssertionValidationContextBuilder;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.AttributeQuery;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.dc.saml.ResponseData;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.logic.PredicateSupport;
+
+/**
+ *
+ */
+public class AssertionValidationProcessorTest extends XMLObjectBaseTestCase {
+    
+    private AssertionValidationProcessor processor;
+    
+    private DefaultAssertionValidationContextBuilder contextBuilder;
+    
+    private ResponseData responseData;
+    
+    @BeforeMethod
+    public void setupMethod() throws Exception {
+        contextBuilder = new DefaultAssertionValidationContextBuilder();
+        contextBuilder.setValidIssuers(FunctionSupport.constant(Set.of(SAMLDataConnectorTestingSupport.ISSUER)));
+        
+        final SAML20AssertionValidator validator = new SAML20AssertionValidator(
+                CollectionSupport.listOf(new AudienceRestrictionConditionValidator()), 
+                CollectionSupport.emptyList(), 
+                CollectionSupport.emptyList(), 
+                null, 
+                null, 
+                new SAMLSignatureProfileValidator());
+
+        processor = new AssertionValidationProcessor();
+        processor.setAssertionValidationContextBuilder(contextBuilder);
+        processor.setAssertionValidator(validator);
+        
+        final AttributeQuery query = SAMLDataConnectorTestingSupport.buildQuery();
+        final Response response = SAMLDataConnectorTestingSupport.buildResponse();
+        
+        responseData = new ResponseData(
+                response,
+                SAMLDataConnectorTestingSupport.buildOperationContext(query, response),
+                SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(),
+                SAMLDataConnectorTestingSupport.buildDependencyAttributes(),
+                SAMLDataConnectorTestingSupport.buildAuthorityDescriptor()
+                );
+        
+    }
+    
+    @Test(expectedExceptions = ResolutionException.class)
+    public void failNoValidator() throws Exception {
+        processor.setAssertionValidator(null);
+        processor.setAssertionValidatorLookup(FunctionSupport.constant(null));
+        processor.initialize();
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 1);
+        
+        processor.validateAssertions(responseData.getResponse(), responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 1);
+    }
+
+    @Test
+    public void oneAssertionSuccess() throws Exception {
+        contextBuilder.setSignatureRequired(PredicateSupport.alwaysFalse());
+        processor.initialize();
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 1);
+        
+        processor.validateAssertions(responseData.getResponse(), responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 1);
+    }
+
+    @Test
+    public void twoAssertionsSuccess() throws Exception {
+        contextBuilder.setSignatureRequired(PredicateSupport.alwaysFalse());
+        processor.initialize();
+        
+        responseData.getResponse().getAssertions().add(SAMLDataConnectorTestingSupport.buildAssertion(null));
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 2);
+        
+        processor.validateAssertions(responseData.getResponse(), responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 2);
+    }
+
+    @Test
+    public void oneAssertionFail() throws Exception {
+        contextBuilder.setSignatureRequired(PredicateSupport.alwaysTrue());
+        processor.initialize();
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 1);
+        
+        processor.validateAssertions(responseData.getResponse(), responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 0);
+    }
+
+    @Test
+    public void twoAssertionsOneFails() throws Exception {
+        contextBuilder.setSignatureRequired(PredicateSupport.alwaysFalse());
+        processor.initialize();
+        
+        responseData.getResponse().getAssertions().add(SAMLDataConnectorTestingSupport.buildAssertion(null));
+        final Assertion assertion = responseData.getResponse().getAssertions().get(1);
+        assert assertion != null;
+        // Default lifetime in validator is 5 minutes, so this should cause failure
+        assertion.setIssueInstant(Instant.now().minus(Duration.ofHours(1)));
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 2);
+        
+        processor.validateAssertions(responseData.getResponse(), responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 1);
+    }
+
+    @Test
+    public void twoAssertionsBothFail() throws Exception {
+        contextBuilder.setSignatureRequired(PredicateSupport.alwaysTrue());
+        processor.initialize();
+        
+        responseData.getResponse().getAssertions().add(SAMLDataConnectorTestingSupport.buildAssertion(null));
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 2);
+        
+        processor.validateAssertions(responseData.getResponse(), responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 0);
+    }
+
+ 
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/DecryptionProcessorTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/DecryptionProcessorTest.java
new file mode 100644
index 000000000..87caa9f0d
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/DecryptionProcessorTest.java
@@ -0,0 +1,395 @@
+/*
+ * 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.dc.saml.util.impl;
+
+import java.security.KeyPair;
+import java.util.List;
+import java.util.function.Function;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeQuery;
+import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.EncryptedAttribute;
+import org.opensaml.saml.saml2.core.EncryptedID;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.Subject;
+import org.opensaml.saml.saml2.encryption.Decrypter;
+import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
+import org.opensaml.saml.saml2.encryption.Encrypter;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialSupport;
+import org.opensaml.security.crypto.KeySupport;
+import org.opensaml.xmlsec.DecryptionConfiguration;
+import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters;
+import org.opensaml.xmlsec.encryption.support.DecryptionException;
+import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters;
+import org.opensaml.xmlsec.impl.BasicDecryptionConfiguration;
+import org.opensaml.xmlsec.impl.BasicDecryptionParametersResolver;
+import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.resolver.dc.saml.ResponseData;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ *
+ */
+public class DecryptionProcessorTest extends XMLObjectBaseTestCase {
+    
+    private DecryptionProcessor processor;
+    
+    private ResponseData responseData;
+    
+    private Encrypter encrypter;
+    
+    private Credential decryptionCredential;
+    
+    private BasicDecryptionConfiguration decryptionConfiguration;
+    
+    @BeforeMethod
+    public void beforeMethod() throws Exception {
+        final KeyPair keyPair = KeySupport.generateKeyPair("RSA", 2048, null);
+
+        final DataEncryptionParameters dataParams = new DataEncryptionParameters();
+        dataParams.setAlgorithm(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM);
+
+        final KeyEncryptionParameters keyParams = new KeyEncryptionParameters();
+        keyParams.setAlgorithm(EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP);
+        keyParams.setEncryptionCredential(CredentialSupport.getSimpleCredential(keyPair.getPublic(), null));
+        
+        encrypter = new Encrypter(dataParams, keyParams);
+
+        decryptionCredential =  CredentialSupport.getSimpleCredential(keyPair.getPublic(), keyPair.getPrivate());
+
+        decryptionConfiguration = new BasicDecryptionConfiguration();
+        decryptionConfiguration.setKEKKeyInfoCredentialResolver(new StaticKeyInfoCredentialResolver(decryptionCredential));
+        decryptionConfiguration.setEncryptedKeyResolver(new ChainingEncryptedKeyResolver(
+                CollectionSupport.listOf(new EncryptedElementTypeEncryptedKeyResolver(), new InlineEncryptedKeyResolver())));
+
+        processor = new DecryptionProcessor();
+        processor.setDecryptionParametersResolver(new BasicDecryptionParametersResolver());
+        processor.setDecryptionConfigurationLookupStrategy(new Function<ResponseData,List<DecryptionConfiguration>> () {
+            public List<DecryptionConfiguration> apply(ResponseData t) {
+                return CollectionSupport.listOf(decryptionConfiguration);
+            }
+        });
+        
+        final AttributeQuery query = SAMLDataConnectorTestingSupport.buildQuery();
+        final Response response = SAMLDataConnectorTestingSupport.buildResponse();
+        
+        responseData = new ResponseData(
+                response,
+                SAMLDataConnectorTestingSupport.buildOperationContext(query, response),
+                SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(),
+                SAMLDataConnectorTestingSupport.buildDependencyAttributes(),
+                SAMLDataConnectorTestingSupport.buildAuthorityDescriptor()
+                );
+    }
+    
+    @Test
+    public void buildDecrypter() throws Exception {
+        processor.initialize();
+        
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+    }
+
+    @Test(expectedExceptions = DecryptionException.class)
+    public void buildDecrypterFail() throws Exception {
+        processor.setDecryptionConfigurationLookupStrategy(new Function<ResponseData, List<DecryptionConfiguration>>() {
+            @Override
+            public List<DecryptionConfiguration> apply(ResponseData t) {
+                return null;
+            }
+        });
+        processor.initialize();
+        
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+    }
+
+    @Test
+    public void haveEncryptedAssertions() throws Exception {
+        processor.initialize();
+        
+        Assert.assertFalse(processor.haveEncryptedAssertions(responseData.getResponse()));
+        
+        responseData.getResponse().getEncryptedAssertions().add(encrypter.encrypt(SAMLDataConnectorTestingSupport.buildAssertion(null)));
+
+        Assert.assertTrue(processor.haveEncryptedAssertions(responseData.getResponse()));
+    }
+
+    @Test
+    public void haveEncryptedContentWithAttribute() throws Exception {
+        processor.initialize();
+        
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        final Attribute attrib = SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user at example.edu");
+        SAMLDataConnectorTestingSupport.addAttribute(assertion, attrib);
+
+        Assert.assertFalse(processor.haveEncryptedContent(responseData.getResponse()));
+        
+        assertion.getAttributeStatements().clear();
+        final EncryptedAttribute encryptedAttrib = encrypter.encrypt(attrib);
+        SAMLDataConnectorTestingSupport.addEncryptedAttribute(assertion, encryptedAttrib);
+
+        Assert.assertTrue(processor.haveEncryptedContent(responseData.getResponse()));
+    }
+
+    @Test
+    public void haveEncryptedContentWithNameID() throws Exception {
+        processor.initialize();
+
+        Assert.assertFalse(processor.haveEncryptedContent(responseData.getResponse()));
+        
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        final Subject subject = assertion.getSubject();
+        assert subject != null;
+        final EncryptedID encryptedID = encrypter.encrypt(SAMLDataConnectorTestingSupport.buildNameID());
+        subject.setNameID(null);
+        subject.setEncryptedID(encryptedID);
+        
+        Assert.assertTrue(processor.haveEncryptedContent(responseData.getResponse()));
+    }
+
+    @Test
+    public void decryptAssertions() throws Exception {
+        processor.initialize();
+        
+        responseData.getResponse().getAssertions().clear();
+        responseData.getResponse().getEncryptedAssertions().add(encrypter.encrypt(SAMLDataConnectorTestingSupport.buildAssertion(null)));
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 0);
+        Assert.assertEquals(responseData.getResponse().getEncryptedAssertions().size(), 1);
+        
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+
+        processor.decryptAssertions(responseData.getResponse(), decrypter);
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 1);
+        Assert.assertEquals(responseData.getResponse().getEncryptedAssertions().size(), 0);
+    }
+
+    @Test
+    public void decryptAssertionsFail() throws Exception {
+        final KeyPair keyPair = KeySupport.generateKeyPair("RSA", 2048, null);
+        decryptionConfiguration.setKEKKeyInfoCredentialResolver(
+                new StaticKeyInfoCredentialResolver(CredentialSupport.getSimpleCredential(keyPair.getPublic(), keyPair.getPrivate())));
+        processor.initialize();
+        
+        responseData.getResponse().getAssertions().clear();
+        responseData.getResponse().getEncryptedAssertions().add(encrypter.encrypt(SAMLDataConnectorTestingSupport.buildAssertion(null)));
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 0);
+        Assert.assertEquals(responseData.getResponse().getEncryptedAssertions().size(), 1);
+        
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+
+        processor.decryptAssertions(responseData.getResponse(), decrypter);
+        Assert.assertEquals(responseData.getResponse().getAssertions().size(), 0);
+        Assert.assertEquals(responseData.getResponse().getEncryptedAssertions().size(), 1);
+    }
+
+    @Test
+    public void decryptAssertionContent() throws Exception {
+        processor.initialize();
+
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        assertion.getAttributeStatements().clear();
+        final Attribute attrib = SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user at example.edu");
+        final EncryptedAttribute encryptedAttrib = encrypter.encrypt(attrib);
+        SAMLDataConnectorTestingSupport.addEncryptedAttribute(assertion, encryptedAttrib);
+        final AttributeStatement attribStatement = assertion.getAttributeStatements().get(0);
+        assert attribStatement != null;
+        Assert.assertEquals(attribStatement.getAttributes().size(), 0);
+        Assert.assertEquals(attribStatement.getEncryptedAttributes().size(), 1);
+
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+        
+        processor.decryptAssertionContent(responseData.getResponse(), decrypter);
+
+        Assert.assertEquals(attribStatement.getAttributes().size(), 1);
+        Assert.assertEquals(attribStatement.getEncryptedAttributes().size(), 0);
+    }
+
+    @Test
+    public void decryptAssertionContentFail() throws Exception {
+        final KeyPair keyPair = KeySupport.generateKeyPair("RSA", 2048, null);
+        decryptionConfiguration.setKEKKeyInfoCredentialResolver(
+                new StaticKeyInfoCredentialResolver(CredentialSupport.getSimpleCredential(keyPair.getPublic(), keyPair.getPrivate())));
+        processor.initialize();
+
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        assertion.getAttributeStatements().clear();
+        final Attribute attrib = SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user at example.edu");
+        final EncryptedAttribute encryptedAttrib = encrypter.encrypt(attrib);
+        SAMLDataConnectorTestingSupport.addEncryptedAttribute(assertion, encryptedAttrib);
+        final AttributeStatement attribStatement = assertion.getAttributeStatements().get(0);
+        assert attribStatement != null;
+        Assert.assertEquals(attribStatement.getAttributes().size(), 0);
+        Assert.assertEquals(attribStatement.getEncryptedAttributes().size(), 1);
+
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+        
+        processor.decryptAssertionContent(responseData.getResponse(), decrypter);
+
+        Assert.assertEquals(attribStatement.getAttributes().size(), 0);
+        Assert.assertEquals(attribStatement.getEncryptedAttributes().size(), 1);
+    }
+
+    @Test
+    public void decryptEncryptedIDs() throws Exception {
+        processor.initialize();
+        
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        Subject subject = assertion.getSubject();
+        assert subject != null;
+        final EncryptedID encryptedID = encrypter.encrypt(SAMLDataConnectorTestingSupport.buildNameID());
+        subject.setNameID(null);
+        subject.setEncryptedID(encryptedID);
+
+        Assert.assertNull(subject.getNameID());
+        Assert.assertNotNull(subject.getEncryptedID());
+
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+        
+        processor.decryptEncryptedIDs(assertion, decrypter);
+        
+        subject = assertion.getSubject();
+        assert subject != null;
+        Assert.assertNotNull(subject.getNameID());
+        Assert.assertNull(subject.getEncryptedID());
+    }
+
+    @Test(expectedExceptions = DecryptionException.class)
+    public void decryptEncryptedIDsFail() throws Exception {
+        final KeyPair keyPair = KeySupport.generateKeyPair("RSA", 2048, null);
+        decryptionConfiguration.setKEKKeyInfoCredentialResolver(
+                new StaticKeyInfoCredentialResolver(CredentialSupport.getSimpleCredential(keyPair.getPublic(), keyPair.getPrivate())));
+        processor.initialize();
+
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        Subject subject = assertion.getSubject();
+        assert subject != null;
+        final EncryptedID encryptedID = encrypter.encrypt(SAMLDataConnectorTestingSupport.buildNameID());
+        subject.setNameID(null);
+        subject.setEncryptedID(encryptedID);
+
+        Assert.assertNull(subject.getNameID());
+        Assert.assertNotNull(subject.getEncryptedID());
+
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+        
+        processor.decryptEncryptedIDs(assertion, decrypter);
+    }
+
+    @Test
+    public void decryptEncryptedID() throws Exception {
+        processor.initialize();
+        
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+        
+        final EncryptedID encryptedID = encrypter.encrypt(SAMLDataConnectorTestingSupport.buildNameID());
+
+        final NameID nameID = processor.decryptEncryptedID(encryptedID, decrypter);
+        
+        Assert.assertNotNull(nameID);
+    }
+
+    @Test(expectedExceptions = DecryptionException.class)
+    public void decryptEncryptedIDFail() throws Exception {
+        final KeyPair keyPair = KeySupport.generateKeyPair("RSA", 2048, null);
+        decryptionConfiguration.setKEKKeyInfoCredentialResolver(
+                new StaticKeyInfoCredentialResolver(CredentialSupport.getSimpleCredential(keyPair.getPublic(), keyPair.getPrivate())));
+        processor.initialize();
+        
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+        
+        final EncryptedID encryptedID = encrypter.encrypt(SAMLDataConnectorTestingSupport.buildNameID());
+
+        final NameID nameID = processor.decryptEncryptedID(encryptedID, decrypter);
+    }
+
+    @Test
+    public void decryptEncryptedAttributes() throws Exception {
+        processor.initialize();
+
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        assertion.getAttributeStatements().clear();
+        final Attribute attrib = SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user at example.edu");
+        EncryptedAttribute encryptedAttrib = encrypter.encrypt(attrib);
+        SAMLDataConnectorTestingSupport.addEncryptedAttribute(assertion, encryptedAttrib);
+        AttributeStatement attribStatement = assertion.getAttributeStatements().get(0);
+        assert attribStatement != null;
+        Assert.assertEquals(attribStatement.getAttributes().size(), 0);
+        Assert.assertEquals(attribStatement.getEncryptedAttributes().size(), 1);
+
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+        
+        processor.decryptEncryptedAttributes(assertion, decrypter);
+
+        Assert.assertEquals(attribStatement.getAttributes().size(), 1);
+        Assert.assertEquals(attribStatement.getEncryptedAttributes().size(), 0);
+    }
+
+    @Test
+    public void decryptEncryptedAttributesFail() throws Exception {
+        final KeyPair keyPair = KeySupport.generateKeyPair("RSA", 2048, null);
+        decryptionConfiguration.setKEKKeyInfoCredentialResolver(
+                new StaticKeyInfoCredentialResolver(CredentialSupport.getSimpleCredential(keyPair.getPublic(), keyPair.getPrivate())));
+        processor.initialize();
+
+        final Assertion assertion = responseData.getResponse().getAssertions().get(0);
+        assert assertion != null;
+        assertion.getAttributeStatements().clear();
+        final Attribute attrib = SAMLDataConnectorTestingSupport.buildAttribute("email", Attribute.BASIC, "user at example.edu");
+        EncryptedAttribute encryptedAttrib = encrypter.encrypt(attrib);
+        SAMLDataConnectorTestingSupport.addEncryptedAttribute(assertion, encryptedAttrib);
+        AttributeStatement attribStatement = assertion.getAttributeStatements().get(0);
+        assert attribStatement != null;
+        Assert.assertEquals(attribStatement.getAttributes().size(), 0);
+        Assert.assertEquals(attribStatement.getEncryptedAttributes().size(), 1);
+
+        final Decrypter decrypter = processor.buildDecrypter(responseData);
+        Assert.assertNotNull(decrypter);
+        
+        processor.decryptEncryptedAttributes(assertion, decrypter);
+
+        Assert.assertEquals(attribStatement.getAttributes().size(), 0);
+        Assert.assertEquals(attribStatement.getEncryptedAttributes().size(), 1);
+    }
+
+}
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/SubjectMatchProcessorTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/SubjectMatchProcessorTest.java
new file mode 100644
index 000000000..3e83ef1d9
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/saml/util/impl/SubjectMatchProcessorTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.dc.saml.util.impl;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.AttributeQuery;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.Subject;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.resolver.dc.saml.ResponseData;
+import net.shibboleth.idp.attribute.resolver.dc.saml.impl.SAMLDataConnectorTestingSupport;
+
+/**
+ *
+ */
+public class SubjectMatchProcessorTest extends XMLObjectBaseTestCase {
+    
+    private SubjectMatchProcessor processor;
+    
+    private ResponseData responseData;
+    
+    @BeforeMethod
+    public void setupMethod() throws Exception {
+        
+        processor = new SubjectMatchProcessor();
+        
+        final AttributeQuery query = SAMLDataConnectorTestingSupport.buildQuery();
+        
+        final Response response = SAMLDataConnectorTestingSupport.buildResponse();
+        
+        responseData = new ResponseData(
+                response,
+                SAMLDataConnectorTestingSupport.buildOperationContext(query, response),
+                SAMLDataConnectorTestingSupport.buildAttributeResolutionContext(),
+                SAMLDataConnectorTestingSupport.buildDependencyAttributes(),
+                SAMLDataConnectorTestingSupport.buildAuthorityDescriptor()
+                );
+    }
+
+    @Test
+    public void oneAssertionMatches() throws Exception {
+        responseData.getResponse().getAssertions().clear();
+
+        final Subject subject1 = SAMLDataConnectorTestingSupport.getSubject(
+                SAMLDataConnectorTestingSupport.getQuery(responseData.getSOAPClientContext()));
+        final Assertion assertion1 = SAMLDataConnectorTestingSupport.buildAssertion(
+                XMLObjectSupport.cloneXMLObject(subject1));
+        responseData.getResponse().getAssertions().add(assertion1);
+
+        
+        processor.process(responseData);
+        
+        final Response response = SAMLDataConnectorTestingSupport.getResponse(responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(response.getAssertions().size(), 1);
+    }
+    
+    @Test
+    public void twoAssertionsBothMatch() throws Exception {
+        responseData.getResponse().getAssertions().clear();
+
+        final Subject subject1 = SAMLDataConnectorTestingSupport.getSubject(
+                SAMLDataConnectorTestingSupport.getQuery(responseData.getSOAPClientContext()));
+        final Assertion assertion1 = SAMLDataConnectorTestingSupport.buildAssertion(
+                XMLObjectSupport.cloneXMLObject(subject1));
+        responseData.getResponse().getAssertions().add(assertion1);
+
+        final Subject subject2 = SAMLDataConnectorTestingSupport.getSubject(
+                SAMLDataConnectorTestingSupport.getQuery(responseData.getSOAPClientContext()));
+        final Assertion assertion2 = SAMLDataConnectorTestingSupport.buildAssertion(
+                XMLObjectSupport.cloneXMLObject(subject2));
+        responseData.getResponse().getAssertions().add(assertion2);
+
+        
+        processor.process(responseData);
+        
+        final Response response = SAMLDataConnectorTestingSupport.getResponse(responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(response.getAssertions().size(), 2);
+    }
+    
+    @Test
+    public void oneAssertionMissingSubject() throws Exception {
+        responseData.getResponse().getAssertions().clear();
+
+        final Assertion assertion1 = SAMLDataConnectorTestingSupport.buildAssertion(null);
+        assertion1.setSubject(null);
+        responseData.getResponse().getAssertions().add(assertion1);
+
+        
+        processor.process(responseData);
+        
+        final Response response = SAMLDataConnectorTestingSupport.getResponse(responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(response.getAssertions().size(), 0);
+    }
+    
+    @Test
+    public void oneAssertionNoMatch() throws Exception {
+        responseData.getResponse().getAssertions().clear();
+
+        final Subject subject1 = SAMLDataConnectorTestingSupport.buildSubject(
+                SAMLDataConnectorTestingSupport.buildNameID(NameID.TRANSIENT, "abc123"));
+        final Assertion assertion1 = SAMLDataConnectorTestingSupport.buildAssertion(subject1);
+        responseData.getResponse().getAssertions().add(assertion1);
+
+        
+        processor.process(responseData);
+        
+        final Response response = SAMLDataConnectorTestingSupport.getResponse(responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(response.getAssertions().size(), 0);
+    }
+    
+    @Test
+    public void twoAssertionsNeitherMatches() throws Exception {
+        responseData.getResponse().getAssertions().clear();
+
+        final Subject subject1 = SAMLDataConnectorTestingSupport.buildSubject(
+                SAMLDataConnectorTestingSupport.buildNameID(NameID.TRANSIENT, "abc123"));
+        final Assertion assertion1 = SAMLDataConnectorTestingSupport.buildAssertion(subject1);
+        responseData.getResponse().getAssertions().add(assertion1);
+
+        final Subject subject2 = SAMLDataConnectorTestingSupport.buildSubject(
+                SAMLDataConnectorTestingSupport.buildNameID(NameID.TRANSIENT, "def456"));
+        final Assertion assertion2 = SAMLDataConnectorTestingSupport.buildAssertion(subject2);
+        responseData.getResponse().getAssertions().add(assertion2);
+        
+        processor.process(responseData);
+        
+        final Response response = SAMLDataConnectorTestingSupport.getResponse(responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(response.getAssertions().size(), 0);
+    }
+    
+    @Test
+    public void twoAssertionsOneMatches() throws Exception {
+        responseData.getResponse().getAssertions().clear();
+
+        final Subject subject1 = SAMLDataConnectorTestingSupport.buildSubject(
+                SAMLDataConnectorTestingSupport.buildNameID(NameID.TRANSIENT, "abc123"));
+        final Assertion assertion1 = SAMLDataConnectorTestingSupport.buildAssertion(subject1);
+        responseData.getResponse().getAssertions().add(assertion1);
+
+        final Subject subject2 = SAMLDataConnectorTestingSupport.getSubject(
+                SAMLDataConnectorTestingSupport.getQuery(responseData.getSOAPClientContext()));
+        final Assertion assertion2 = SAMLDataConnectorTestingSupport.buildAssertion(
+                XMLObjectSupport.cloneXMLObject(subject2));
+        responseData.getResponse().getAssertions().add(assertion2);
+
+        
+        processor.process(responseData);
+        
+        final Response response = SAMLDataConnectorTestingSupport.getResponse(responseData.getSOAPClientContext());
+        
+        Assert.assertEquals(response.getAssertions().size(), 1);
+        
+        // Make sure we kept the correct one
+        Subject responseSubject = SAMLDataConnectorTestingSupport.getSubject(response.getAssertions().get(0));
+        NameID responseNameID = responseSubject.getNameID();
+        Assert.assertNotNull(responseNameID);
+        assert responseNameID != null;
+        Assert.assertEquals(responseNameID.getValue(), SAMLDataConnectorTestingSupport.SUBJECT_VALUE);
+        Assert.assertEquals(responseNameID.getFormat(), SAMLDataConnectorTestingSupport.SUBJECT_FORMAT);
+    }
+    
+
+}

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


More information about the commits mailing list