[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