[java-shib-attribute] branch main updated: Migrate metadata-backed lookup strategies for configuration out of IdP.
Scott Cantor
cantor.2 at osu.edu
Wed Feb 1 20:26:16 UTC 2023
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-shib-attribute.
View the commit online:
http://git.shibboleth.net/view/?p=java-shib-attribute.git;a=commit;h=b16133b9ac4c90d8a76e014b372b6404cea322d0
The following commit(s) were added to refs/heads/main by this push:
new b16133b9a Migrate metadata-backed lookup strategies for configuration out of IdP.
b16133b9a is described below
commit b16133b9ac4c90d8a76e014b372b6404cea322d0
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Feb 1 15:26:13 2023 -0500
Migrate metadata-backed lookup strategies for configuration out of IdP.
---
shib-attribute-api/pom.xml | 13 +-
...tractCollectionConfigurationLookupStrategy.java | 141 +++++
...tMetadataDrivenConfigurationLookupStrategy.java | 607 +++++++++++++++++++++
.../config/BeanConfigurationLookupStrategy.java | 169 ++++++
.../config/BooleanConfigurationLookupStrategy.java | 121 ++++
.../config/DoubleConfigurationLookupStrategy.java | 115 ++++
.../DurationConfigurationLookupStrategy.java | 143 +++++
.../config/IntegerConfigurationLookupStrategy.java | 114 ++++
.../config/ListConfigurationLookupStrategy.java | 90 +++
.../config/LongConfigurationLookupStrategy.java | 120 ++++
.../config/SetConfigurationLookupStrategy.java | 91 +++
.../config/StringConfigurationLookupStrategy.java | 125 +++++
.../idp/attribute/config/package-info.java | 23 +
13 files changed, 1870 insertions(+), 2 deletions(-)
diff --git a/shib-attribute-api/pom.xml b/shib-attribute-api/pom.xml
index 9467031c3..cbdd07c88 100644
--- a/shib-attribute-api/pom.xml
+++ b/shib-attribute-api/pom.xml
@@ -39,14 +39,23 @@
<artifactId>opensaml-saml-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>${shib-shared.groupId}</groupId>
+ <artifactId>shib-spring</artifactId>
+ </dependency>
+
<dependency>
<groupId>${spring.groupId}</groupId>
<artifactId>spring-core</artifactId>
</dependency>
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
<dependency>
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
</dependency>
<dependency>
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/AbstractCollectionConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/AbstractCollectionConfigurationLookupStrategy.java
new file mode 100644
index 000000000..1eca3e3f3
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/AbstractCollectionConfigurationLookupStrategy.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSBase64Binary;
+import org.opensaml.core.xml.schema.XSBoolean;
+import org.opensaml.core.xml.schema.XSBooleanValue;
+import org.opensaml.core.xml.schema.XSDateTime;
+import org.opensaml.core.xml.schema.XSInteger;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.core.xml.schema.XSURI;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives List<String>-valued
+ * configuration settings based on EntityAttribute extension tags.
+ *
+ * @param <T1> type of collection member
+ * @param <T2> type of collection itself
+ *
+ * @since 5.0.0
+ */
+public abstract class AbstractCollectionConfigurationLookupStrategy<T1,T2>
+ extends AbstractMetadataDrivenConfigurationLookupStrategy<T2> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractCollectionConfigurationLookupStrategy.class);
+
+ /** Type of bean in collection. */
+ @NonnullAfterInit private Class<T1> propertyType;
+
+ /**
+ * Get the type of object to coerce collection elements into.
+ *
+ * @return object type
+ */
+ @NonnullAfterInit public Class<T1> getPropertyType() {
+ return propertyType;
+ }
+
+ /**
+ * Set the type of object to coerce collection elements into.
+ *
+ * @param type object type
+ */
+ public void setPropertyType(@Nonnull final Class<T1> type) {
+ checkSetterPreconditions();
+ propertyType = Constraint.isNotNull(type, "Property type cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (propertyType == null) {
+ throw new ComponentInitializationException("Property type cannot be null");
+ }
+ }
+
+ /**
+ * Helper method to manufacture instance of object using a string constructor or a cast.
+ *
+ * @param input the input string
+ *
+ * @return the new object or the existing object if casting is possible
+ *
+ * @throws ReflectiveOperationException if the attempt fails
+ */
+ protected T1 createInstanceFromString(@Nonnull @NotEmpty final String input) throws ReflectiveOperationException {
+ if (propertyType.isAssignableFrom(input.getClass())) {
+ return propertyType.cast(input);
+ }
+
+ return propertyType.getConstructor(String.class).newInstance(input);
+ }
+
+ // Checkstyle: CyclomaticComplexity OFF
+ /**
+ * Convert an XMLObject to a String if the type is supported.
+ *
+ * @param object object to convert
+ *
+ * @return the converted value, or null
+ */
+ @Nullable protected String xmlObjectToString(@Nonnull final XMLObject object) {
+ if (object instanceof XSString) {
+ return ((XSString) object).getValue();
+ } else if (object instanceof XSURI) {
+ return ((XSURI) object).getURI();
+ } else if (object instanceof XSBoolean) {
+ final XSBooleanValue value = ((XSBoolean) object).getValue();
+ return value != null ? (value.getValue() ? "1" : "0") : null;
+ } else if (object instanceof XSInteger) {
+ final Integer value = ((XSInteger) object).getValue();
+ return value != null ? value.toString() : null;
+ } else if (object instanceof XSDateTime) {
+ final Instant dt = ((XSDateTime) object).getValue();
+ return dt != null ? Long.toString(dt.toEpochMilli()) : null;
+ } else if (object instanceof XSBase64Binary) {
+ return ((XSBase64Binary) object).getValue();
+ } else if (object instanceof XSAny) {
+ final XSAny wc = (XSAny) object;
+ if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
+ return wc.getTextContent();
+ }
+ }
+
+ log.error("Unsupported conversion to String from XMLObject type ({})", object.getClass().getName());
+ return null;
+ }
+// Checkstyle: CyclomaticComplexity ON
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/AbstractMetadataDrivenConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/AbstractMetadataDrivenConfigurationLookupStrategy.java
new file mode 100644
index 000000000..7d66cf4cb
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/AbstractMetadataDrivenConfigurationLookupStrategy.java
@@ -0,0 +1,607 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ProfileIdLookup;
+import org.opensaml.saml.ext.saml2mdattr.EntityAttributes;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.metadata.EntitiesDescriptor;
+import org.opensaml.saml.saml2.metadata.EntityDescriptor;
+import org.opensaml.saml.saml2.metadata.Extensions;
+import org.opensaml.soap.client.security.SOAPClientSecurityProfileIdLookupFunction;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.AttributesMapContainer;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.shared.annotation.constraint.Live;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.LockableClassToInstanceMultiMap;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives configuration
+ * settings based on EntityAttribute extension tags.
+ *
+ * <p>The function is tailored with properties that determine what tag it looks for, with subclasses
+ * handling the specific type conversion logic.</p>
+ *
+ * <p>If a specific property is unavailable, then null is returned.</p>
+ *
+ * @param <T> type of property being returned
+ *
+ * @since 5.0.0
+ */
+public abstract class AbstractMetadataDrivenConfigurationLookupStrategy<T> extends AbstractInitializableComponent
+ implements Function<BaseContext,T> {
+
+ /** Default profile ID lookup for PRC-based usage. */
+ @Nonnull private static final Function<ProfileRequestContext,String> DEFAULT_PRC_PROFILE_ID_LOOKUP;
+
+ /** Default profile ID lookup for MC-based usage. */
+ @Nonnull private static final Function<MessageContext,String> DEFAULT_MC_PROFILE_ID_LOOKUP;
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(AbstractMetadataDrivenConfigurationLookupStrategy.class);
+
+ /** Require use of URI attribute name format. */
+ private boolean strictNameFormat;
+
+ /** Cache the lookup in the context tree. */
+ private boolean enableCaching;
+
+ /** Examine only decoded/mapped tags in object metadata. */
+ private boolean ignoreUnmappedEntityAttributes;
+
+ /** Prevents prefixing of property name by profile/aliases. */
+ private boolean explicitPropertyName;
+
+ /** Base name of property to produce. */
+ @NonnullAfterInit @NotEmpty private String propertyName;
+
+ /** Alternative "full" property identifiers to support. */
+ @NonnullAfterInit @NonnullElements private Collection<String> propertyAliases;
+
+ /** Default to return in the absence of a property. */
+ @Nonnull private Function<BaseContext,T> defaultValueStrategy;
+
+ /** Strategy for obtaining metadata via ProfileRequestContext. */
+ @NonnullAfterInit private Function<ProfileRequestContext,EntityDescriptor> profileMetadataLookupStrategy;
+
+ /** Strategy for obtaining metadata via MessageContext. */
+ @NonnullAfterInit private Function<MessageContext,EntityDescriptor> messageMetadataLookupStrategy;
+
+ /** Strategy for obtaining profile ID for property naming. */
+ @Nullable private Function<BaseContext,String> profileIdLookupStrategy;
+
+ /** Constructor. */
+ public AbstractMetadataDrivenConfigurationLookupStrategy() {
+ enableCaching = true;
+ defaultValueStrategy = FunctionSupport.constant(null);
+ }
+
+ /**
+ * Sets whether tag matching should examine and require an Attribute NameFormat of the URI type.
+ *
+ * <p>Default is false.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setStrictNameFormat(final boolean flag) {
+ checkSetterPreconditions();
+
+ strictNameFormat = flag;
+ }
+
+ /**
+ * Sets whether property lookup should be cached in the profile context tree.
+ *
+ * <p>Default is true.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setEnableCaching(final boolean flag) {
+ checkSetterPreconditions();
+
+ enableCaching = flag;
+ }
+
+ /**
+ * Sets whether property lookup should be based solely on mapped/decoded objects
+ * and not on underlying SAML Attributes.
+ *
+ * <p>Default is false.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setIgnoreUnmappedEntityAttributes(final boolean flag) {
+ checkSetterPreconditions();
+
+ ignoreUnmappedEntityAttributes = flag;
+ }
+
+ /**
+ * Sets whether to treat the property name as absolute instead of auto-prefixed
+ * by profile or alias values.
+ *
+ * <p>Used to allow for direct lookup of a specific tag instead implicitly prefixing the
+ * tag name based on configuration "context".</p>
+ *
+ * @param flag flag to set
+ *
+ * @since 4.3.0
+ */
+ public void setExplicitPropertyName(final boolean flag) {
+ checkSetterPreconditions();
+
+ explicitPropertyName = flag;
+ }
+
+ /**
+ * Sets the "base" name of the property/setting to derive.
+ *
+ * @param name base property name
+ */
+ public void setPropertyName(@Nonnull @NotEmpty final String name) {
+ checkSetterPreconditions();
+
+ propertyName = Constraint.isNotNull(StringSupport.trimOrNull(name), "Property name cannot be null or empty");
+ }
+
+ /**
+ * Sets profile ID aliases to include when checking for metadata tags (the property name is suffixed to the
+ * aliases).
+ *
+ * <p>This allows alternative tag names to be checked.</p>
+ *
+ * @param aliases alternative profile IDs
+ */
+ public void setProfileAliases(@Nonnull @NonnullElements final Collection<String> aliases) {
+ checkSetterPreconditions();
+
+ Constraint.isNotNull(aliases, "Alias collection cannot be null");
+ propertyAliases = List.copyOf(StringSupport.normalizeStringCollection(aliases));
+ }
+
+ /**
+ * Sets a default value to return as the function result in the absence of an explicit property.
+ *
+ * @param value default value to return
+ */
+ public void setDefaultValue(@Nullable final T value) {
+ checkSetterPreconditions();
+
+ defaultValueStrategy = FunctionSupport.constant(value);
+ }
+
+ /**
+ * Sets a default value function to apply in the absence of an explicit property.
+ *
+ * @param strategy default function to apply
+ *
+ * @since 4.0.0
+ */
+ public void setDefaultValueStrategy(@Nonnull final Function<BaseContext,T> strategy) {
+ checkSetterPreconditions();
+
+ defaultValueStrategy = Constraint.isNotNull(strategy, "Default value strategy cannot be null");
+ }
+
+ /**
+ * Sets lookup strategy for metadata to examine by way of {@link ProfileRequestContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProfileMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,EntityDescriptor> strategy) {
+ checkSetterPreconditions();
+
+ profileMetadataLookupStrategy = Constraint.isNotNull(strategy, "Metadata lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets lookup strategy for metadata to examine by way of {@link MessageContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setMessageMetadataLookupStrategy(@Nonnull final Function<MessageContext,EntityDescriptor> strategy) {
+ checkSetterPreconditions();
+
+ messageMetadataLookupStrategy = Constraint.isNotNull(strategy, "Metadata lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets lookup strategy for profile ID to base property names on.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProfileIdLookupStrategy(@Nonnull final Function<BaseContext,String> strategy) {
+ checkSetterPreconditions();
+
+ profileIdLookupStrategy = Constraint.isNotNull(strategy, "Profile ID lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (profileMetadataLookupStrategy == null || messageMetadataLookupStrategy == null) {
+ throw new ComponentInitializationException("SAML metadata lookup strategy cannot be null");
+ }
+
+ if (propertyName == null) {
+ throw new ComponentInitializationException("Property name cannot be null or empty");
+ } else if (propertyAliases == null) {
+ propertyAliases = Collections.emptyList();
+ }
+
+ // Now attach the property name to the end of the alias list entries.
+ propertyAliases = propertyAliases.stream()
+ .map(s -> s + (s.endsWith("/") ? propertyName : '/' + propertyName))
+ .collect(Collectors.toUnmodifiableList());
+
+ }
+
+ // Checkstyle: CyclomaticComplexity|MethodLength OFF
+ /** {@inheritDoc} */
+ @Nullable public T apply(@Nullable final BaseContext input) {
+ checkComponentActive();
+
+ CachedConfigurationContext cacheContext = null;
+
+ if (enableCaching && input != null) {
+ cacheContext = input.getOrCreateSubcontext(CachedConfigurationContext.class);
+ if (cacheContext.getPropertyMap().containsKey(propertyName)) {
+ log.debug("Returning cached property '{}'", propertyName);
+ return (T) cacheContext.getPropertyMap().get(propertyName);
+ }
+ }
+
+ final EntityDescriptor entity;
+ final String profileId;
+
+ if (input instanceof ProfileRequestContext) {
+ entity = profileMetadataLookupStrategy.apply((ProfileRequestContext) input);
+ } else if (input instanceof MessageContext) {
+ entity = messageMetadataLookupStrategy.apply((MessageContext) input);
+ } else {
+ entity = null;
+ }
+
+ if (entity == null) {
+ log.debug("No metadata available for relying party, applying default strategy for '{}'", propertyName);
+ return defaultValueStrategy.apply(input);
+ }
+
+ if (!explicitPropertyName) {
+ if (profileIdLookupStrategy != null) {
+ profileId = profileIdLookupStrategy.apply(input);
+ } else if (input instanceof ProfileRequestContext) {
+ profileId = DEFAULT_PRC_PROFILE_ID_LOOKUP.apply((ProfileRequestContext) input);
+ } else if (input instanceof MessageContext) {
+ profileId = DEFAULT_MC_PROFILE_ID_LOOKUP.apply((MessageContext) input);
+ } else {
+ profileId = "";
+ }
+ } else {
+ profileId = null;
+ }
+
+ // Look for "primary" tag name based on profile/property using mapped tags.
+ IdPAttribute idpAttribute = findMatchingMappedTag(entity,
+ profileId != null ? profileId + '/' + propertyName : propertyName);
+ if (idpAttribute != null) {
+ log.debug("Found matching tag '{}' for property '{}'", idpAttribute.getId(), propertyName);
+ final T result = translate(idpAttribute);
+ if (enableCaching) {
+ assert cacheContext != null;
+ cacheContext.getPropertyMap().put(propertyName, result);
+ }
+ return result;
+ }
+
+ // Check aliases.
+ for (final String alias : propertyAliases) {
+ idpAttribute = findMatchingMappedTag(entity, alias);
+ if (idpAttribute != null) {
+ log.debug("Found matching tag '{}' for property '{}'", idpAttribute.getId(), propertyName);
+ final T result = translate(idpAttribute);
+ if (enableCaching) {
+ assert cacheContext != null;
+ cacheContext.getPropertyMap().put(propertyName, result);
+ }
+ return result;
+ }
+ }
+
+ if (ignoreUnmappedEntityAttributes) {
+ log.debug("No applicable mapped tag, applying default strategy for '{}'", propertyName);
+ final T ret = defaultValueStrategy.apply(input);
+ if (enableCaching) {
+ assert cacheContext != null;
+ cacheContext.getPropertyMap().put(propertyName, ret);
+ }
+ return ret;
+ }
+
+ // Look for "primary" tag name based on profile/property.
+ Attribute attribute = findMatchingTag(entity,
+ profileId != null ? profileId + '/' + propertyName : propertyName);
+ if (attribute != null) {
+ log.debug("Found matching tag '{}' for property '{}'", attribute.getName(), propertyName);
+ final T result = translate(attribute);
+ if (enableCaching) {
+ assert cacheContext != null;
+ cacheContext.getPropertyMap().put(propertyName, result);
+ }
+ return result;
+ }
+
+ // Check aliases.
+ for (final String alias : propertyAliases) {
+ attribute = findMatchingTag(entity, alias);
+ if (attribute != null) {
+ log.debug("Found matching tag '{}' for property '{}'", attribute.getName(), propertyName);
+ final T result = translate(attribute);
+ if (enableCaching) {
+ assert cacheContext != null;
+ cacheContext.getPropertyMap().put(propertyName, result);
+ }
+ return result;
+ }
+ }
+
+ log.debug("No applicable tag, applying default strategy for '{}'", propertyName);
+ final T ret = defaultValueStrategy.apply(input);
+ if (enableCaching) {
+ assert cacheContext != null;
+ cacheContext.getPropertyMap().put(propertyName, ret);
+ }
+ return ret;
+ }
+// Checkstyle: CyclomaticComplexity|MethodLength ON
+
+ /**
+ * Translate the value(s) into a setting of the appropriate type.
+ *
+ * @param tag tag to translate
+ *
+ * @return the setting derived from the tag's value(s)
+ */
+ @Nullable private T translate(@Nonnull final Attribute tag) {
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ if (values == null || values.isEmpty()) {
+ log.debug("Tag '{}' contained no values, no setting returned for '{}'", tag.getName(), propertyName);
+ return null;
+ }
+
+ return doTranslate(tag);
+ }
+
+ /**
+ * Translate the value(s) into a setting of the appropriate type.
+ *
+ * @param tag tag to translate
+ *
+ * @return the setting derived from the tag's value(s)
+ */
+ @Nullable private T translate(@Nonnull final IdPAttribute tag) {
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ if (values == null || values.isEmpty()) {
+ log.debug("Tag '{}' contained no values, no setting returned for '{}'", tag.getId(), propertyName);
+ return null;
+ }
+
+ return doTranslate(tag);
+ }
+
+ /**
+ * Translate the value(s) into a setting of the appropriate type.
+ *
+ * <p>Overrides of this function can assume a non-zero collection of values.</p>
+ *
+ * @param tag tag to translate
+ *
+ * @return the setting derived from the tag's value(s)
+ */
+ @Nullable protected abstract T doTranslate(@Nonnull final Attribute tag);
+
+ /**
+ * Translate the value(s) into a setting of the appropriate type.
+ *
+ * <p>Overrides of this function can assume a non-zero collection of values.</p>
+ *
+ * @param tag tag to translate
+ *
+ * @return the setting derived from the tag's value(s)
+ */
+ @Nullable protected abstract T doTranslate(@Nonnull final IdPAttribute tag);
+
+ /**
+ * Find first matching attribute in the input object's node metadata.
+ *
+ * @param entity the metadata to examine
+ * @param name the tag name to search for
+ *
+ * @return matching attribute, or null
+ */
+ @Nullable private IdPAttribute findMatchingMappedTag(@Nonnull final EntityDescriptor entity,
+ @Nonnull @NotEmpty final String name) {
+
+ // Check for a tag match in the node metadata of the entity and its parent(s).
+ IdPAttribute tag = findMatchingMappedTag(entity.getObjectMetadata(), name);
+ if (tag != null) {
+ return tag;
+ }
+
+ XMLObject parent = entity.getParent();
+ while (parent instanceof EntitiesDescriptor) {
+ tag = findMatchingMappedTag(parent.getObjectMetadata(), name);
+ if (tag != null) {
+ return tag;
+ }
+ parent = parent.getParent();
+ }
+
+ return null;
+ }
+
+ /**
+ * Find a matching entity attribute in the input metadata.
+ *
+ * @param entity the metadata to examine
+ * @param name the tag name to search for
+ *
+ * @return matching attribute or null
+ */
+ @Nullable private Attribute findMatchingTag(@Nonnull final EntityDescriptor entity,
+ @Nonnull @NotEmpty final String name) {
+
+ // Check for a tag match in the EntityAttributes extension of the entity and its parent(s).
+ Extensions exts = entity.getExtensions();
+ if (exts != null) {
+ final List<XMLObject> children = exts.getUnknownXMLObjects(EntityAttributes.DEFAULT_ELEMENT_NAME);
+ if (!children.isEmpty() && children.get(0) instanceof EntityAttributes) {
+ final Attribute tag = findMatchingTag((EntityAttributes) children.get(0), name);
+ if (tag != null) {
+ return tag;
+ }
+ }
+ }
+
+ EntitiesDescriptor group = (EntitiesDescriptor) entity.getParent();
+ while (group != null) {
+ exts = group.getExtensions();
+ if (exts != null) {
+ final List<XMLObject> children = exts.getUnknownXMLObjects(EntityAttributes.DEFAULT_ELEMENT_NAME);
+ if (!children.isEmpty() && children.get(0) instanceof EntityAttributes) {
+ final Attribute tag = findMatchingTag((EntityAttributes) children.get(0), name);
+ if (tag != null) {
+ return tag;
+ }
+ }
+ }
+ group = (EntitiesDescriptor) group.getParent();
+ }
+
+ return null;
+ }
+
+ /**
+ * Find first matching attribute in the input object's node metadata.
+ *
+ * @param input the metadata to examine
+ * @param name the tag name to search for
+ *
+ * @return matching attribute, or null
+ */
+ @Nullable private IdPAttribute findMatchingMappedTag(@Nonnull final LockableClassToInstanceMultiMap<?> input,
+ @Nonnull @NotEmpty final String name) {
+
+ final List<AttributesMapContainer> containerList = input.get(AttributesMapContainer.class);
+ if (null == containerList || containerList.isEmpty()) {
+ return null;
+ }
+
+ final AttributesMapContainer container = containerList.get(0);
+ if (container == null || container.get().isEmpty()) {
+ return null;
+ }
+
+ final Collection<IdPAttribute> matches = container.get().get(name);
+ return matches.isEmpty() ? null : matches.iterator().next();
+ }
+
+ /**
+ * Find a matching entity attribute in the input metadata.
+ *
+ * @param entityAttributes the metadata to examine
+ * @param name the tag name to search for
+ *
+ * @return matching attribute or null
+ */
+ @Nullable private Attribute findMatchingTag(@Nonnull final EntityAttributes entityAttributes,
+ @Nonnull @NotEmpty final String name) {
+
+ for (final Attribute tag : entityAttributes.getAttributes()) {
+ if (Objects.equals(tag.getName(), name)
+ && (!strictNameFormat || Objects.equals(tag.getNameFormat(), Attribute.URI_REFERENCE))) {
+ return tag;
+ }
+ }
+
+ return null;
+ }
+
+ /** A child context that caches derived configuration properties. */
+ public static final class CachedConfigurationContext extends BaseContext {
+
+ /** Cached property map. */
+ @Nonnull private Map<String,Object> propertyMap;
+
+ /** Constructor. */
+ public CachedConfigurationContext() {
+ propertyMap = new HashMap<>();
+ }
+
+ /**
+ * Get cached property map.
+ *
+ * @return cached property map
+ */
+ @Nonnull @Live Map<String,Object> getPropertyMap() {
+ return propertyMap;
+ }
+ }
+
+ static {
+ // Init PRC defaults.
+ DEFAULT_PRC_PROFILE_ID_LOOKUP = new ProfileIdLookup();
+
+ // Init MC defaults.
+ DEFAULT_MC_PROFILE_ID_LOOKUP = new SOAPClientSecurityProfileIdLookupFunction();
+ }
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/BeanConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/BeanConfigurationLookupStrategy.java
new file mode 100644
index 000000000..7f4df6613
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/BeanConfigurationLookupStrategy.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.saml.saml2.core.Attribute;
+
+import org.slf4j.Logger;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives bean-based
+ * configuration settings based on EntityAttribute extension tags.
+ *
+ * <p>Defaults to no caching of the result to avoid bean lifecycle issues if relying party config is reloaded.</p>
+ *
+ * @param <T> type of bean
+ *
+ * @since 5.0.0
+ */
+public class BeanConfigurationLookupStrategy<T> extends AbstractMetadataDrivenConfigurationLookupStrategy<T>
+ implements ApplicationContextAware {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(BeanConfigurationLookupStrategy.class);
+
+ /** Enclosing Spring context. */
+ @NonnullAfterInit private ApplicationContext applicationContext;
+
+ /** Type of bean to return. */
+ @NonnullAfterInit private Class<T> propertyType;
+
+ /** Constructor. */
+ public BeanConfigurationLookupStrategy() {
+ setEnableCaching(false);
+ }
+
+ /**
+ * Set the type of bean to search for.
+ *
+ * @param type bean type
+ */
+ public void setPropertyType(@Nonnull final Class<T> type) {
+ checkSetterPreconditions();
+ propertyType = Constraint.isNotNull(type, "Property type cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ public void setApplicationContext(@Nonnull final ApplicationContext context) throws BeansException {
+ checkSetterPreconditions();
+ applicationContext = context;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (propertyType == null || applicationContext == null) {
+ throw new ComponentInitializationException("Property type and Spring ApplicationContext cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected T doTranslate(@Nonnull final IdPAttribute tag) {
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getId());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Bean property of tyoe '{}'", tag.getId(), propertyType.getSimpleName());
+
+ final IdPAttributeValue value = values.get(0);
+ if (value instanceof StringAttributeValue) {
+ try {
+ return applicationContext.getBean(((StringAttributeValue) value).getValue(), propertyType);
+ } catch (final BeansException e) {
+ log.error("Error locating appropriately typed bean named {}",
+ ((StringAttributeValue) value).getValue(), e);
+ return null;
+ }
+ }
+ log.error("Tag '{}' contained non-string value, returning null");
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected T doTranslate(@Nonnull final Attribute tag) {
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getName());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Bean property of tyoe '{}'", tag.getName(), propertyType.getSimpleName());
+ return xmlObjectToBean(values.get(0));
+ }
+
+ /**
+ * Convert an XMLObject to a Spring bean reference if the type is supported.
+ *
+ * @param object object to convert
+ *
+ * @return the converted value, or null
+ */
+ @Nullable private T xmlObjectToBean(@Nonnull final XMLObject object) {
+ String value = null;
+ if (object instanceof XSString) {
+ value = ((XSString) object).getValue();
+ } else if (object instanceof XSAny) {
+ final XSAny wc = (XSAny) object;
+ if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
+ value = wc.getTextContent();
+ }
+ }
+
+ if (value != null) {
+ try {
+ assert propertyType != null;
+ return applicationContext.getBean(value, propertyType);
+ } catch (final BeansException e) {
+ log.error("Error locating appropriately typed bean named {}", value, e);
+ return null;
+ }
+ }
+
+ log.error("Unsupported conversion to Spring bean from XMLObject type ({})", object.getClass().getName());
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/BooleanConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/BooleanConfigurationLookupStrategy.java
new file mode 100644
index 000000000..5a2fcb8cb
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/BooleanConfigurationLookupStrategy.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSBoolean;
+import org.opensaml.core.xml.schema.XSBooleanValue;
+import org.opensaml.core.xml.schema.XSInteger;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives Boolean-valued
+ * configuration settings based on EntityAttribute extension tags.
+ *
+ * @since 5.0.0
+ */
+public class BooleanConfigurationLookupStrategy extends AbstractMetadataDrivenConfigurationLookupStrategy<Boolean> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(BooleanConfigurationLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Boolean doTranslate(@Nonnull final IdPAttribute tag) {
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getId());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Boolean property", tag.getId());
+
+ final IdPAttributeValue value = values.get(0);
+ if (value instanceof StringAttributeValue) {
+ return XSBooleanValue.valueOf(((StringAttributeValue) value).getValue()).getValue();
+ }
+ log.error("Tag '{}' contained non-string value, returning null");
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Boolean doTranslate(@Nonnull final Attribute tag) {
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getName());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Boolean property", tag.getName());
+ return xmlObjectToBoolean(values.get(0));
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /**
+ * Convert an XMLObject to a Boolean if the type is supported.
+ *
+ * @param object object to convert
+ *
+ * @return the converted value, or null
+ */
+ @Nullable private Boolean xmlObjectToBoolean(@Nonnull final XMLObject object) {
+ if (object instanceof XSString) {
+ final String value = ((XSString) object).getValue();
+ if (value != null) {
+ return XSBooleanValue.valueOf(value).getValue();
+ }
+ return null;
+ } else if (object instanceof XSBoolean) {
+ final XSBooleanValue value = ((XSBoolean) object).getValue();
+ return value != null ? value.getValue() : null;
+ } else if (object instanceof XSInteger) {
+ final Integer value = ((XSInteger) object).getValue();
+ return value != null ? value != 0 : null;
+ } else if (object instanceof XSAny) {
+ final XSAny wc = (XSAny) object;
+ if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
+ final String value = wc.getTextContent();
+ if (value != null) {
+ return XSBooleanValue.valueOf(value).getValue();
+ }
+ return null;
+ }
+ }
+
+ log.error("Unsupported conversion to Boolean from XMLObject type ({})", object.getClass().getName());
+ return null;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/DoubleConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/DoubleConfigurationLookupStrategy.java
new file mode 100644
index 000000000..490a039cf
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/DoubleConfigurationLookupStrategy.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSBoolean;
+import org.opensaml.core.xml.schema.XSBooleanValue;
+import org.opensaml.core.xml.schema.XSInteger;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives Double-valued
+ * configuration settings based on EntityAttribute extension tags.
+ *
+ * @since 5.0.0
+ */
+public class DoubleConfigurationLookupStrategy extends AbstractMetadataDrivenConfigurationLookupStrategy<Double> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DoubleConfigurationLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Double doTranslate(@Nonnull final IdPAttribute tag) {
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getId());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Double property", tag.getId());
+
+ final IdPAttributeValue value = values.get(0);
+ if (value instanceof StringAttributeValue) {
+ return Double.valueOf(((StringAttributeValue) value).getValue());
+ }
+ log.error("Tag '{}' contained non-string value, returning null");
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Double doTranslate(@Nonnull final Attribute tag) {
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getName());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Double property", tag.getName());
+ return xmlObjectToDouble(values.get(0));
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /**
+ * Convert an XMLObject to a Double if the type is supported.
+ *
+ * @param object object to convert
+ *
+ * @return the converted value, or null
+ */
+ @Nullable private Double xmlObjectToDouble(@Nonnull final XMLObject object) {
+ if (object instanceof XSString) {
+ final String value = ((XSString) object).getValue();
+ return value != null ? Double.valueOf(value) : null;
+ } else if (object instanceof XSBoolean) {
+ final XSBooleanValue value = ((XSBoolean) object).getValue();
+ return value != null ? (value.getValue() ? 1.0 : 0.0) : null;
+ } else if (object instanceof XSInteger) {
+ final Integer value = ((XSInteger) object).getValue();
+ return value != null ? value.doubleValue() : null;
+ } else if (object instanceof XSAny) {
+ final XSAny wc = (XSAny) object;
+ if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
+ final String value = wc.getTextContent();
+ return value != null ? Double.valueOf(value) : null;
+ }
+ }
+
+ log.error("Unsupported conversion to Double from XMLObject type ({})", object.getClass().getName());
+ return null;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/DurationConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/DurationConfigurationLookupStrategy.java
new file mode 100644
index 000000000..f07b2e1b2
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/DurationConfigurationLookupStrategy.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.time.Duration;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSInteger;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+
+import org.springframework.core.convert.converter.Converter;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.spring.config.StringToDurationConverter;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives Long-valued
+ * configuration settings that are durations, based on EntityAttribute extension tags.
+ *
+ * @since 5.0.0
+ */
+public class DurationConfigurationLookupStrategy extends AbstractMetadataDrivenConfigurationLookupStrategy<Duration> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DurationConfigurationLookupStrategy.class);
+
+ /** Converter to handle duration strings. */
+ @Nonnull private final Converter<String,Duration> durationConverter;
+
+ /** Constructor. */
+ public DurationConfigurationLookupStrategy() {
+ durationConverter = new StringToDurationConverter();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Duration doTranslate(@Nonnull final IdPAttribute tag) {
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getId());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Duration property", tag.getId());
+
+ final IdPAttributeValue value = values.get(0);
+ if (value instanceof StringAttributeValue) {
+ try {
+ return durationConverter.convert(((StringAttributeValue) value).getValue());
+ } catch (final IllegalArgumentException e) {
+ log.error("Error converting duration", e);
+ return null;
+ }
+ }
+ log.error("Tag '{}' contained non-string value, returning null");
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Duration doTranslate(@Nonnull final Attribute tag) {
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getName());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Duration property", tag.getName());
+ return xmlObjectToDuration(values.get(0));
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /**
+ * Convert an XMLObject to a Long based on a duration if the type is supported.
+ *
+ * @param object object to convert
+ *
+ * @return the converted value, or null
+ */
+ @Nullable private Duration xmlObjectToDuration(@Nonnull final XMLObject object) {
+ if (object instanceof XSString) {
+ final String value = ((XSString) object).getValue();
+ if (value != null) {
+ try {
+ return durationConverter.convert(value);
+ } catch (final IllegalArgumentException e) {
+ log.error("Error converting duration", e);
+ return null;
+ }
+ }
+ return null;
+ } else if (object instanceof XSInteger) {
+ final Integer value = ((XSInteger) object).getValue();
+ return value != null ? Duration.ofMillis(value.longValue()) : null;
+ } else if (object instanceof XSAny) {
+ final XSAny wc = (XSAny) object;
+ if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
+ final String value = wc.getTextContent();
+ if (value != null) {
+ try {
+ return durationConverter.convert(value);
+ } catch (final IllegalArgumentException e) {
+ log.error("Error converting duration", e);
+ return null;
+ }
+ }
+ return null;
+ }
+ }
+
+ log.error("Unsupported conversion to Duration from XMLObject type ({})", object.getClass().getName());
+ return null;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/IntegerConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/IntegerConfigurationLookupStrategy.java
new file mode 100644
index 000000000..11700a8f8
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/IntegerConfigurationLookupStrategy.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSBoolean;
+import org.opensaml.core.xml.schema.XSBooleanValue;
+import org.opensaml.core.xml.schema.XSInteger;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives Integer-valued
+ * configuration settings based on EntityAttribute extension tags.
+ *
+ * @since 5.0.0
+ */
+public class IntegerConfigurationLookupStrategy extends AbstractMetadataDrivenConfigurationLookupStrategy<Integer> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(IntegerConfigurationLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Integer doTranslate(@Nonnull final IdPAttribute tag) {
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getId());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Integer property", tag.getId());
+
+ final IdPAttributeValue value = values.get(0);
+ if (value instanceof StringAttributeValue) {
+ return Integer.decode(((StringAttributeValue) value).getValue());
+ }
+ log.error("Tag '{}' contained non-string value, returning null");
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Integer doTranslate(@Nonnull final Attribute tag) {
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getName());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Integer property", tag.getName());
+ return xmlObjectToInteger(values.get(0));
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /**
+ * Convert an XMLObject to an Integer if the type is supported.
+ *
+ * @param object object to convert
+ *
+ * @return the converted value, or null
+ */
+ @Nullable private Integer xmlObjectToInteger(@Nonnull final XMLObject object) {
+ if (object instanceof XSString) {
+ final String value = ((XSString) object).getValue();
+ return value != null ? Integer.decode(value) : null;
+ } else if (object instanceof XSBoolean) {
+ final XSBooleanValue value = ((XSBoolean) object).getValue();
+ return value != null ? (value.getValue() ? 1 : 0) : null;
+ } else if (object instanceof XSInteger) {
+ return ((XSInteger) object).getValue();
+ } else if (object instanceof XSAny) {
+ final XSAny wc = (XSAny) object;
+ if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
+ final String value = wc.getTextContent();
+ return value != null ? Integer.decode(value) : null;
+ }
+ }
+
+ log.error("Unsupported conversion to Integer from XMLObject type ({})", object.getClass().getName());
+ return null;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/ListConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/ListConfigurationLookupStrategy.java
new file mode 100644
index 000000000..f963f4bbe
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/ListConfigurationLookupStrategy.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives List<String>-valued
+ * configuration settings based on EntityAttribute extension tags.
+ *
+ * @param <T> type of object in list
+ *
+ * @since 5.0.0
+ */
+public class ListConfigurationLookupStrategy<T> extends AbstractCollectionConfigurationLookupStrategy<T,List<T>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ListConfigurationLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected List<T> doTranslate(@Nonnull final IdPAttribute tag) {
+
+ log.debug("Converting tag '{}' to List<{}> property", tag.getId(), getPropertyType().getSimpleName());
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ final List<T> result = new ArrayList<>(values.size());
+ for (final IdPAttributeValue value : values) {
+ if (value instanceof StringAttributeValue) {
+ try {
+ result.add(createInstanceFromString(((StringAttributeValue) value).getValue()));
+ } catch (final Exception e) {
+ log.error("Error converting tag value into {}", getPropertyType().getSimpleName(), e);
+ }
+ }
+ }
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected List<T> doTranslate(@Nonnull final Attribute tag) {
+
+ log.debug("Converting tag '{}' to List<{}> property", tag.getName(), getPropertyType().getSimpleName());
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ final List<T> result = new ArrayList<>(values.size());
+ for (final XMLObject value : values) {
+ assert value != null;
+ final String converted = xmlObjectToString(value);
+ if (converted != null) {
+ try {
+ result.add(createInstanceFromString(converted));
+ } catch (final Exception e) {
+ log.error("Error converting tag value into {}", getPropertyType().getSimpleName(), e);
+ }
+ }
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/LongConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/LongConfigurationLookupStrategy.java
new file mode 100644
index 000000000..d93ea7bd5
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/LongConfigurationLookupStrategy.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.time.Instant;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSBoolean;
+import org.opensaml.core.xml.schema.XSBooleanValue;
+import org.opensaml.core.xml.schema.XSDateTime;
+import org.opensaml.core.xml.schema.XSInteger;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives Long-valued
+ * configuration settings based on EntityAttribute extension tags.
+ *
+ * @since 5.0.0
+ */
+public class LongConfigurationLookupStrategy extends AbstractMetadataDrivenConfigurationLookupStrategy<Long> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(LongConfigurationLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Long doTranslate(@Nonnull final IdPAttribute tag) {
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getId());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Long property", tag.getId());
+
+ final IdPAttributeValue value = values.get(0);
+ if (value instanceof StringAttributeValue) {
+ return Long.decode(((StringAttributeValue) value).getValue());
+ }
+ log.error("Tag '{}' contained non-string value, returning null");
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Long doTranslate(@Nonnull final Attribute tag) {
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getName());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to Long property", tag.getName());
+ return xmlObjectToLong(values.get(0));
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /**
+ * Convert an XMLObject to a Long if the type is supported.
+ *
+ * @param object object to convert
+ *
+ * @return the converted value, or null
+ */
+ @Nullable private Long xmlObjectToLong(@Nonnull final XMLObject object) {
+ if (object instanceof XSString) {
+ final String value = ((XSString) object).getValue();
+ return value != null ? Long.decode(value) : null;
+ } else if (object instanceof XSBoolean) {
+ final XSBooleanValue value = ((XSBoolean) object).getValue();
+ return value != null ? (value.getValue() ? 1L : 0L) : null;
+ } else if (object instanceof XSInteger) {
+ final Integer value = ((XSInteger) object).getValue();
+ return value != null ? value.longValue() : null;
+ } else if (object instanceof XSDateTime) {
+ final Instant dt = ((XSDateTime) object).getValue();
+ return dt != null ? dt.toEpochMilli() : null;
+ } else if (object instanceof XSAny) {
+ final XSAny wc = (XSAny) object;
+ if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
+ final String value = wc.getTextContent();
+ return value != null ? Long.decode(value) : null;
+ }
+ }
+
+ log.error("Unsupported conversion to Long from XMLObject type ({})", object.getClass().getName());
+ return null;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/SetConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/SetConfigurationLookupStrategy.java
new file mode 100644
index 000000000..3f601bda6
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/SetConfigurationLookupStrategy.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives Set<String>-valued
+ * configuration settings based on EntityAttribute extension tags.
+ *
+ * @param <T> type of object in list
+ *
+ * @since 5.0.0
+ */
+public class SetConfigurationLookupStrategy<T> extends AbstractCollectionConfigurationLookupStrategy<T,Set<T>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(SetConfigurationLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Set<T> doTranslate(@Nonnull final IdPAttribute tag) {
+
+ log.debug("Converting tag '{}' to List<{}> property", tag.getId(), getPropertyType().getSimpleName());
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ final Set<T> result = new HashSet<>(values.size());
+ for (final IdPAttributeValue value : values) {
+ if (value instanceof StringAttributeValue) {
+ try {
+ result.add(createInstanceFromString(((StringAttributeValue) value).getValue()));
+ } catch (final Exception e) {
+ log.error("Error converting tag value into {}", getPropertyType().getSimpleName(), e);
+ }
+ }
+ }
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Set<T> doTranslate(@Nonnull final Attribute tag) {
+
+ log.debug("Converting tag '{}' to Set<String> property", tag.getName());
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ final Set<T> result = new HashSet<>(values.size());
+ for (final XMLObject value : values) {
+ assert value != null;
+ final String converted = xmlObjectToString(value);
+ if (converted != null) {
+ try {
+ result.add(createInstanceFromString(converted));
+ } catch (final Exception e) {
+ log.error("Error converting tag value into {}", getPropertyType().getSimpleName(), e);
+ }
+ }
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/StringConfigurationLookupStrategy.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/StringConfigurationLookupStrategy.java
new file mode 100644
index 000000000..2a5b76e1f
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/StringConfigurationLookupStrategy.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.config;
+
+import java.time.Instant;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSBase64Binary;
+import org.opensaml.core.xml.schema.XSBoolean;
+import org.opensaml.core.xml.schema.XSBooleanValue;
+import org.opensaml.core.xml.schema.XSDateTime;
+import org.opensaml.core.xml.schema.XSInteger;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.core.xml.schema.XSURI;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy function that examines SAML metadata associated with a relying party and derives String-valued
+ * configuration settings based on EntityAttribute extension tags.
+ *
+ * @since 5.0.0
+ */
+public class StringConfigurationLookupStrategy extends AbstractMetadataDrivenConfigurationLookupStrategy<String> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(StringConfigurationLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected String doTranslate(@Nonnull final IdPAttribute tag) {
+
+ final List<IdPAttributeValue> values = tag.getValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getId());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to String property", tag.getId());
+
+ final IdPAttributeValue value = values.get(0);
+ if (value instanceof StringAttributeValue) {
+ return ((StringAttributeValue) value).getValue();
+ }
+ log.error("Tag '{}' contained non-string value, returning null");
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected String doTranslate(@Nonnull final Attribute tag) {
+
+ final List<XMLObject> values = tag.getAttributeValues();
+ if (values.size() != 1) {
+ log.error("Tag '{}' contained multiple values, returning none", tag.getName());
+ return null;
+ }
+
+ log.debug("Converting tag '{}' to String property", tag.getName());
+ return xmlObjectToString(values.get(0));
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /**
+ * Convert an XMLObject to a String if the type is supported.
+ *
+ * @param object object to convert
+ *
+ * @return the converted value, or null
+ */
+ @Nullable private String xmlObjectToString(@Nonnull final XMLObject object) {
+ if (object instanceof XSString) {
+ return ((XSString) object).getValue();
+ } else if (object instanceof XSURI) {
+ return ((XSURI) object).getURI();
+ } else if (object instanceof XSBoolean) {
+ final XSBooleanValue value = ((XSBoolean) object).getValue();
+ return value != null ? (value.getValue() ? "1" : "0") : null;
+ } else if (object instanceof XSInteger) {
+ final Integer value = ((XSInteger) object).getValue();
+ return value != null ? value.toString() : null;
+ } else if (object instanceof XSDateTime) {
+ final Instant dt = ((XSDateTime) object).getValue();
+ return dt != null ? Long.toString(dt.toEpochMilli()) : null;
+ } else if (object instanceof XSBase64Binary) {
+ return ((XSBase64Binary) object).getValue();
+ } else if (object instanceof XSAny) {
+ final XSAny wc = (XSAny) object;
+ if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
+ return wc.getTextContent();
+ }
+ }
+
+ log.error("Unsupported conversion to String from XMLObject type ({})", object.getClass().getName());
+ return null;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+
+}
\ No newline at end of file
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/package-info.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/package-info.java
new file mode 100644
index 000000000..978a9c840
--- /dev/null
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/config/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+/**
+ * Configuration support for leveraging {@link net.shibboleth.idp.attribute.IdPAttribute} objects
+ * decoded from SAML metadata for access to settings.
+ */
+package net.shibboleth.idp.attribute.config;
\ 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