[java-shib-attribute] branch main updated: IDP-2057 - Support transcoders that derive SAML naming from metadata
Scott Cantor
cantor.2 at osu.edu
Tue Dec 12 14:07:33 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=ecb8c068e19e409a3f3a2c82cb9cfbf2c6280454
The following commit(s) were added to refs/heads/main by this push:
new ecb8c068e IDP-2057 - Support transcoders that derive SAML naming from metadata
ecb8c068e is described below
commit ecb8c068e19e409a3f3a2c82cb9cfbf2c6280454
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Dec 12 09:07:30 2023 -0500
IDP-2057 - Support transcoders that derive SAML naming from metadata
https://shibboleth.atlassian.net/browse/IDP-2057
Add support for metadata-driven naming to SAML transcoders.
Adjust registry to accomodate rules with no target name.
---
.../transcoding/AbstractAttributeTranscoder.java | 59 +++++++++++++-
.../impl/AttributeTranscoderRegistryImpl.java | 95 ++++++++++++----------
.../AbstractSAML1AttributeTranscoder.java | 54 ++++++++++--
.../AbstractSAML2AttributeTranscoder.java | 61 ++++++++++++--
.../transcoding/SAML1AttributeTranscoder.java | 14 ++++
.../transcoding/SAML2AttributeTranscoder.java | 14 ++++
6 files changed, 236 insertions(+), 61 deletions(-)
diff --git a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AbstractAttributeTranscoder.java b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AbstractAttributeTranscoder.java
index d3dde9fb1..dae1b01c1 100644
--- a/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AbstractAttributeTranscoder.java
+++ b/shib-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AbstractAttributeTranscoder.java
@@ -14,6 +14,8 @@
package net.shibboleth.idp.attribute.transcoding;
+import java.util.Set;
+import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
@@ -44,6 +46,9 @@ public abstract class AbstractAttributeTranscoder<T> extends AbstractInitializab
/** Condition for use of this transcoder. */
@Nonnull private Predicate<ProfileRequestContext> activationCondition;
+ /** Strategy to lookup naming overrides in metadata. */
+ @Nullable private Function<ProfileRequestContext,Set<String>> nameFromMetadataLookupStrategy;
+
/** Constructor. */
public AbstractAttributeTranscoder() {
activationCondition = PredicateSupport.alwaysTrue();
@@ -59,6 +64,20 @@ public abstract class AbstractAttributeTranscoder<T> extends AbstractInitializab
activationCondition = Constraint.isNotNull(condition, "Activation condition cannot be null");
}
+
+ /**
+ * Sets lookup strategy for obtaining tag-based naming overrides from metadata.
+ *
+ * @param strategy lookup strategy
+ *
+ * @since 5.1.0
+ */
+ public void setNameFromMetadataLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Set<String>> strategy) {
+ checkSetterPreconditions();
+
+ nameFromMetadataLookupStrategy = strategy;
+ }
/** {@inheritDoc} */
@Nullable public T encode(@Nullable final ProfileRequestContext profileRequestContext,
@@ -89,6 +108,41 @@ public abstract class AbstractAttributeTranscoder<T> extends AbstractInitializab
return attribute;
}
+ /**
+ * If enabled, search metadata for a tag value prefixed by the input attribute ID followed by
+ * a space character.
+ *
+ * <p>The remaining portion of the value will be interpreted in a protocol-specific way.</p>
+ *
+ * @param profileRequestContext profile request context
+ * @param attributeId attribute ID
+ *
+ * @return the first matching tag value from the installed lookup function
+ *
+ * @since 5.1.0
+ */
+ @Nullable protected String getNameFromMetadata(@Nullable final ProfileRequestContext profileRequestContext,
+ @Nonnull final String attributeId) {
+
+ if (nameFromMetadataLookupStrategy != null) {
+ final Set<String> tagValues = nameFromMetadataLookupStrategy.apply(profileRequestContext);
+ if (tagValues != null) {
+ for (final String tagValue : tagValues) {
+ if (tagValue != null && tagValue.startsWith(attributeId + ' ')) {
+ final String ret = tagValue.substring(attributeId.length() + 1);
+ if (!ret.isEmpty()) {
+ return ret;
+ }
+ }
+ }
+ log.debug("No applicable tag value found for metadata-driven naming for {}", attributeId);
+ } else {
+ log.debug("No tag values found for metadata-driven naming for {}", attributeId);
+ }
+ }
+
+ return null;
+ }
/**
* Encode the supplied attribute into a protocol specific representation.
@@ -135,7 +189,7 @@ public abstract class AbstractAttributeTranscoder<T> extends AbstractInitializab
@Nonnull final TranscodingRule rule) {
if (!activationCondition.test(profileRequestContext)) {
- log.debug("Transcoder inactive");
+ log.debug("Transcoder type {} inactive", getClass().getName());
return false;
}
@@ -144,7 +198,8 @@ public abstract class AbstractAttributeTranscoder<T> extends AbstractInitializab
rule.get(AttributeTranscoderRegistry.PROP_CONDITION, Predicate.class);
if (condition != null) {
if (!condition.test(profileRequestContext)) {
- log.debug("Transcoder inactive");
+ log.debug("Transcoder rule for {} inactive",
+ rule.get(AttributeTranscoderRegistry.PROP_ID, String.class));
return false;
}
}
diff --git a/shib-attribute-impl/src/main/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryImpl.java b/shib-attribute-impl/src/main/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryImpl.java
index 161a73697..57e269551 100644
--- a/shib-attribute-impl/src/main/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryImpl.java
+++ b/shib-attribute-impl/src/main/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryImpl.java
@@ -319,60 +319,67 @@ public class AttributeTranscoderRegistryImpl extends AbstractIdentifiableInitial
final Class<?> type = transcoder.getEncodedType();
final String targetName = transcoder.getEncodedName(copy);
- if (targetName != null) {
-
- Boolean encoder = copy.getOrDefault(PROP_ENCODER, Boolean.class, true);
- if (encoder == null) {
- encoder = true;
- }
- Boolean decoder = copy.getOrDefault(PROP_DECODER, Boolean.class, true);
- if (decoder == null) {
- decoder = true;
- }
- if (!encoder && !decoder) {
- log.warn("Transcoding rule for {} and type {} was disabled in both directions, ignoring",
- id, type.getName());
- return;
- }
+ Boolean encoder = copy.getOrDefault(PROP_ENCODER, Boolean.class, true);
+ if (encoder == null) {
+ encoder = true;
+ }
+ Boolean decoder = copy.getOrDefault(PROP_DECODER, Boolean.class, true);
+ if (decoder == null) {
+ decoder = true;
+ }
+
+ if (decoder && targetName == null) {
+ log.warn("Transcoding rule for {} and type {} did not produce a decodable target name, disabling decoding",
+ id, type.getName());
+ decoder = false;
+ }
+
+ if (!encoder && !decoder) {
+ log.warn("Transcoding rule for {} and type {} was disabled in both directions, ignoring",
+ id, type.getName());
+ return;
+ }
+
+ if (targetName != null) {
log.debug("Attribute mapping: {} {}-{} {} via {}", id, decoder ? "<" : "", encoder ? ">" : "", targetName,
transcoder.getClass().getSimpleName());
-
- // Install mapping back to IdPAttribute's trimmed name.
- copy.getMap().put(PROP_ID, id);
+ } else {
+ log.debug("Attribute mapping: {} {}-{} (metadata-driven naming only) via {}", id, decoder ? "<" : "",
+ encoder ? ">" : "", transcoder.getClass().getSimpleName());
+ }
+
+ // Install mapping back to IdPAttribute's trimmed name.
+ copy.getMap().put(PROP_ID, id);
- if (encoder) {
- Multimap<Class<?>,TranscodingRule> rulesetsForIdPName = transcodingRegistry.get(id);
- if (rulesetsForIdPName == null) {
- rulesetsForIdPName = ArrayListMultimap.create();
- transcodingRegistry.put(id, rulesetsForIdPName);
- }
- rulesetsForIdPName.put(type, copy);
+ if (encoder) {
+ Multimap<Class<?>,TranscodingRule> rulesetsForIdPName = transcodingRegistry.get(id);
+ if (rulesetsForIdPName == null) {
+ rulesetsForIdPName = ArrayListMultimap.create();
+ transcodingRegistry.put(id, rulesetsForIdPName);
}
+ rulesetsForIdPName.put(type, copy);
+ }
- if (decoder) {
- Multimap<Class<?>,TranscodingRule> rulesetsForEncodedName = transcodingRegistry.get(targetName);
- if (rulesetsForEncodedName == null) {
- rulesetsForEncodedName = ArrayListMultimap.create();
- transcodingRegistry.put(targetName, rulesetsForEncodedName);
- }
- rulesetsForEncodedName.put(type, copy);
+ if (targetName != null && decoder) {
+ Multimap<Class<?>,TranscodingRule> rulesetsForEncodedName = transcodingRegistry.get(targetName);
+ if (rulesetsForEncodedName == null) {
+ rulesetsForEncodedName = ArrayListMultimap.create();
+ transcodingRegistry.put(targetName, rulesetsForEncodedName);
}
+ rulesetsForEncodedName.put(type, copy);
+ }
- if (displayNameRegistry.containsKey(id)) {
- displayNameRegistry.get(id).putAll(copy.getDisplayNames());
- } else {
- displayNameRegistry.put(id, new HashMap<>(copy.getDisplayNames()));
- }
+ if (displayNameRegistry.containsKey(id)) {
+ displayNameRegistry.get(id).putAll(copy.getDisplayNames());
+ } else {
+ displayNameRegistry.put(id, new HashMap<>(copy.getDisplayNames()));
+ }
- if (descriptionRegistry.containsKey(id)) {
- descriptionRegistry.get(id).putAll(copy.getDescriptions());
- } else {
- descriptionRegistry.put(id, new HashMap<>(copy.getDescriptions()));
- }
-
+ if (descriptionRegistry.containsKey(id)) {
+ descriptionRegistry.get(id).putAll(copy.getDescriptions());
} else {
- log.warn("Transcoding rule for {} and type {} did not produce an encoded name", id, type.getName());
+ descriptionRegistry.put(id, new HashMap<>(copy.getDescriptions()));
}
}
// Checkstyle: CyclomaticComplexity ON
diff --git a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML1AttributeTranscoder.java b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML1AttributeTranscoder.java
index 9872e1934..c763f152e 100644
--- a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML1AttributeTranscoder.java
+++ b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML1AttributeTranscoder.java
@@ -40,6 +40,7 @@ import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
import net.shibboleth.idp.saml.xml.SAMLConstants;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
/**
* Base class for transcoders that operate on a SAML 1 {@link Attribute} or {@link AttributeDesignator}.
@@ -97,11 +98,6 @@ public abstract class AbstractSAML1AttributeTranscoder<EncodedType extends IdPAt
throw new AttributeEncodingException("Failed to encode any values for attribute " + attribute.getId());
}
- final String name = rule.get(PROP_NAME, String.class);
- if (Strings.isNullOrEmpty(name)) {
- throw new AttributeEncodingException("Required transcoder property '" + PROP_NAME + "' not found");
- }
-
final AttributeDesignator samlAttribute;
if (to.equals(Attribute.class)) {
@@ -120,11 +116,55 @@ public abstract class AbstractSAML1AttributeTranscoder<EncodedType extends IdPAt
throw new AttributeEncodingException("Unsupported target object type: " + to.getName());
}
+ encodeName(profileRequestContext, attribute, samlAttribute, rule);
+
+ return samlAttribute;
+ }
+
+ protected void encodeName(@Nullable final ProfileRequestContext profileRequestContext,
+ @Nullable final IdPAttribute attribute, @Nonnull final AttributeDesignator samlAttribute,
+ @Nonnull final TranscodingRule rule) throws AttributeEncodingException {
+
+ // Use metadata tag to derive name of Attribute?
+ final Boolean useMetadata = rule.getOrDefault(PROP_NAME_FROM_METADATA, Boolean.class, false);
+ if (useMetadata != null && useMetadata) {
+ final String id = attribute != null ? attribute.getId() :
+ rule.get(AttributeTranscoderRegistry.PROP_ID, String.class);
+ if (id == null) {
+ log.warn("Rule specified {} but no attribute ID available", PROP_NAME_FROM_METADATA);
+ } else {
+ final String tagValue = getNameFromMetadata(profileRequestContext, id);
+ if (tagValue != null) {
+ final int lastSpace = tagValue.lastIndexOf(' ');
+ final String name;
+ final String namespace;
+ if (lastSpace < 0) {
+ name = StringSupport.trimOrNull(tagValue);
+ namespace = null;
+ } else {
+ name = StringSupport.trimOrNull(tagValue.substring(0, lastSpace));
+ namespace = StringSupport.trimOrNull(tagValue.substring(lastSpace));
+ }
+ if (name != null) {
+ samlAttribute.setAttributeName(name);
+ samlAttribute.setAttributeNamespace(namespace);
+ log.debug("Encoding IdPAttribute {} via metadata tag as Name {}, Namespace {}", id,
+ name, namespace);
+ return;
+ }
+ log.warn("Metadata tag {}, value {}, was not in the expected form", METADATA_TAG_NAME, tagValue);
+ }
+ }
+ }
+
+ final String name = rule.get(PROP_NAME, String.class);
+ if (Strings.isNullOrEmpty(name)) {
+ throw new AttributeEncodingException("Required transcoder property '" + PROP_NAME + "' not found");
+ }
+
samlAttribute.setAttributeName(name);
samlAttribute.setAttributeNamespace(
rule.getOrDefault(PROP_NAMESPACE, String.class, SAMLConstants.SAML1_ATTR_NAMESPACE_URI));
-
- return samlAttribute;
}
/** {@inheritDoc} */
diff --git a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML2AttributeTranscoder.java b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML2AttributeTranscoder.java
index ad0778bf3..141eff042 100644
--- a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML2AttributeTranscoder.java
+++ b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML2AttributeTranscoder.java
@@ -26,6 +26,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.saml.common.SAMLObjectBuilder;
import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.metadata.RequestedAttribute;
+import org.slf4j.Logger;
import com.google.common.base.Strings;
@@ -37,6 +38,8 @@ import net.shibboleth.idp.attribute.IdPRequestedAttribute;
import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
/**
* Base class for transcoders that operate on a SAML 2 {@link Attribute} or {@link RequestedAttribute}.
@@ -46,6 +49,9 @@ import net.shibboleth.shared.collection.CollectionSupport;
public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAttributeValue> extends
AbstractSAMLAttributeTranscoder<Attribute,EncodedType> implements SAML2AttributeTranscoder<EncodedType> {
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractSAML2AttributeTranscoder.class);
+
/** Builder used to construct {@link Attribute} objects. */
@Nonnull private final SAMLObjectBuilder<Attribute> attributeBuilder;
@@ -69,7 +75,6 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
/** {@inheritDoc} */
@Nullable public String getEncodedName(@Nonnull final TranscodingRule rule) {
-
try {
// SAML 2 naming should be based on only what needs to be available from the properties alone.
return new NamingFunction().apply(buildAttribute(null, null, Attribute.class, rule,
@@ -90,11 +95,6 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
throw new AttributeEncodingException("Failed to encode any values for attribute " + attribute.getId());
}
- final String name = rule.get(PROP_NAME, String.class);
- if (Strings.isNullOrEmpty(name)) {
- throw new AttributeEncodingException("Required transcoder property '" + PROP_NAME + "' not found");
- }
-
final Attribute samlAttribute;
if (to.equals(Attribute.class)) {
@@ -108,8 +108,8 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
throw new AttributeEncodingException("Unsupported target object type: " + to.getName());
}
- samlAttribute.setName(name);
- samlAttribute.setNameFormat(rule.getOrDefault(PROP_NAME_FORMAT, String.class, Attribute.URI_REFERENCE));
+ encodeName(profileRequestContext, attribute, samlAttribute, rule);
+
samlAttribute.getAttributeValues().addAll(attributeValues);
final String friendlyName = rule.getOrDefault(PROP_FRIENDLY_NAME, String.class,
@@ -121,6 +121,51 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
return samlAttribute;
}
+ protected void encodeName(@Nullable final ProfileRequestContext profileRequestContext,
+ @Nullable final IdPAttribute attribute, @Nonnull final Attribute samlAttribute,
+ @Nonnull final TranscodingRule rule) throws AttributeEncodingException {
+
+ // Use metadata tag to derive name of Attribute?
+ final Boolean useMetadata = rule.getOrDefault(PROP_NAME_FROM_METADATA, Boolean.class, false);
+ if (useMetadata != null && useMetadata) {
+ final String id = attribute != null ? attribute.getId() :
+ rule.get(AttributeTranscoderRegistry.PROP_ID, String.class);
+ if (id == null) {
+ log.warn("Rule specified {} but no attribute ID available", PROP_NAME_FROM_METADATA);
+ } else {
+ final String tagValue = getNameFromMetadata(profileRequestContext, id);
+ if (tagValue != null) {
+ final int lastSpace = tagValue.lastIndexOf(' ');
+ final String name;
+ final String nameFormat;
+ if (lastSpace < 0) {
+ name = StringSupport.trimOrNull(tagValue);
+ nameFormat = null;
+ } else {
+ name = StringSupport.trimOrNull(tagValue.substring(0, lastSpace));
+ nameFormat = StringSupport.trimOrNull(tagValue.substring(lastSpace));
+ }
+ if (name != null) {
+ samlAttribute.setName(name);
+ samlAttribute.setNameFormat(nameFormat);
+ log.debug("Encoding IdPAttribute {} via metadata tag as Name {}, NameFormat {}", id,
+ name, nameFormat);
+ return;
+ }
+ log.warn("Metadata tag {}, value {}, was not in the expected form", METADATA_TAG_NAME, tagValue);
+ }
+ }
+ }
+
+ final String name = rule.get(PROP_NAME, String.class);
+ if (Strings.isNullOrEmpty(name)) {
+ throw new AttributeEncodingException("Required transcoder property '" + PROP_NAME + "' not found");
+ }
+
+ samlAttribute.setName(name);
+ samlAttribute.setNameFormat(rule.getOrDefault(PROP_NAME_FORMAT, String.class, Attribute.URI_REFERENCE));
+ }
+
/** {@inheritDoc} */
@Override
@Nonnull protected IdPAttribute buildIdPAttribute(
diff --git a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/SAML1AttributeTranscoder.java b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/SAML1AttributeTranscoder.java
index 26430952d..cbdb5a1b4 100644
--- a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/SAML1AttributeTranscoder.java
+++ b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/SAML1AttributeTranscoder.java
@@ -40,4 +40,18 @@ public interface SAML1AttributeTranscoder<EncodedType extends IdPAttributeValue>
/** The namespace of the attribute name. */
@Nonnull @NotEmpty static final String PROP_NAMESPACE = "saml1.namespace";
+ /**
+ * Flag to signal use of metadata to override name to encode.
+ *
+ * @since 5.1.0
+ */
+ @Nonnull @NotEmpty static final String PROP_NAME_FROM_METADATA = "saml1.nameFromMetadata";
+
+ /**
+ * Name of metadata tag/attribute to check for in the event that {@link #PROP_NAME_FROM_METADATA} is used.
+ *
+ * @since 5.1.0
+ */
+ @Nonnull @NotEmpty static final String METADATA_TAG_NAME = "http://shibboleth.net/ns/attributes/naming/saml1";
+
}
\ No newline at end of file
diff --git a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/SAML2AttributeTranscoder.java b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/SAML2AttributeTranscoder.java
index 171252e60..6e8a7c2d5 100644
--- a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/SAML2AttributeTranscoder.java
+++ b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/SAML2AttributeTranscoder.java
@@ -43,4 +43,18 @@ public interface SAML2AttributeTranscoder<EncodedType extends IdPAttributeValue>
/** The format of the attribute name. */
@Nonnull @NotEmpty static final String PROP_NAME_FORMAT = "saml2.nameFormat";
+ /**
+ * Flag to signal use of metadata to override name to encode.
+ *
+ * @since 5.1.0
+ */
+ @Nonnull @NotEmpty static final String PROP_NAME_FROM_METADATA = "saml2.nameFromMetadata";
+
+ /**
+ * Name of metadata tag/attribute to check for in the event that {@link #PROP_NAME_FROM_METADATA} is used.
+ *
+ * @since 5.1.0
+ */
+ @Nonnull @NotEmpty static final String METADATA_TAG_NAME = "http://shibboleth.net/ns/attributes/naming/saml2";
+
}
\ 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