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