[java-identity-provider] 05/27: Revamp subtype support while working on SAML 2 tests.

Scott Cantor cantor.2 at osu.edu
Fri May 3 14:31:55 EDT 2019


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

scantor pushed a commit to branch feature/IDP-1434
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=147a0818c1350042aef54517ebce9587b4d3b532

commit 147a0818c1350042aef54517ebce9587b4d3b532
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Apr 10 20:57:10 2019 -0400

    Revamp subtype support while working on SAML 2 tests.
---
 .../transcoding/AbstractAttributeTranscoder.java   |  15 --
 .../attribute/transcoding/AttributeTranscoder.java |   4 +-
 .../transcoding/AttributeTranscoderRegistry.java   |   6 +
 .../impl/AttributeTranscoderRegistryImpl.java      |  95 ++++----
 .../impl/AttributeTranscoderRegistryTest.java      |  36 ++--
 .../AbstractSAML2AttributeTranscoder.java          |   9 +-
 .../AbstractSAMLAttributeTranscoder.java           |  59 +++--
 .../impl/SAML2StringAttributeTranscoder.java       |   6 +-
 .../impl/SAML2StringAttributeEncoderTest.java      | 238 +++++++++++++++++++++
 9 files changed, 362 insertions(+), 106 deletions(-)

diff --git a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AbstractAttributeTranscoder.java b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AbstractAttributeTranscoder.java
index a333a1a..4ac568f 100644
--- a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AbstractAttributeTranscoder.java
+++ b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AbstractAttributeTranscoder.java
@@ -21,8 +21,6 @@ import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
 
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -68,17 +66,4 @@ public abstract class AbstractAttributeTranscoder<T> extends AbstractInitializab
         activationCondition = Constraint.isNotNull(condition, "Activation condition cannot be null");
     }
     
-    /**
-     * Checks if the given value can be handled by the transcoder.
-     * 
-     * <p>In many cases this is simply a check to see if the given object is of the right type.</p>
-     * 
-     * @param idpAttribute the attribute being encoded, never null
-     * @param value the value to check, never null
-     * 
-     * @return true if the transcoder can encode this value, false if not
-     */
-    protected abstract boolean canEncodeValue(@Nonnull final IdPAttribute idpAttribute,
-            @Nonnull final IdPAttributeValue value);
-
 }
\ No newline at end of file
diff --git a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AttributeTranscoder.java b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AttributeTranscoder.java
index c50eef1..8ced8a8 100644
--- a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AttributeTranscoder.java
+++ b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AttributeTranscoder.java
@@ -77,7 +77,7 @@ public interface AttributeTranscoder<T> {
      * 
      * @throws AttributeEncodingException if unable to successfully encode attribute
      */
-    @Nullable T encode(@Nonnull final ProfileRequestContext profileRequestContext,
+    @Nullable T encode(@Nullable final ProfileRequestContext profileRequestContext,
             @Nonnull final IdPAttribute attribute, @Nonnull final Class<? extends T> to,
             @Nonnull final Properties properties) throws AttributeEncodingException;
     
@@ -92,7 +92,7 @@ public interface AttributeTranscoder<T> {
      * 
      * @throws AttributeDecodingException if unable to successfully decode object
      */
-    @Nullable IdPAttribute decode(@Nonnull final ProfileRequestContext profileRequestContext,
+    @Nullable IdPAttribute decode(@Nullable final ProfileRequestContext profileRequestContext,
             @Nonnull final T input, @Nonnull final Properties properties) throws AttributeDecodingException;
     
 }
\ No newline at end of file
diff --git a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AttributeTranscoderRegistry.java b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AttributeTranscoderRegistry.java
index 07dce7b..d378b06 100644
--- a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AttributeTranscoderRegistry.java
+++ b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/transcoding/AttributeTranscoderRegistry.java
@@ -44,6 +44,12 @@ public interface AttributeTranscoderRegistry extends IdentifiedComponent {
     /** Property name for accessing {@link AttributeTranscoder} object to use. */
     @Nonnull @NotEmpty static final String PROP_TRANSCODER = "transcoder";
     
+    /** Property name for whether a result with no values should be allowed, default false . */
+    @Nonnull @NotEmpty static final String PROP_ENCODE_NO_VALUES = "encodeNoValues";
+
+    /** Property name for whether a result with no values should be allowed, default true. */
+    @Nonnull @NotEmpty static final String PROP_DECODE_NO_VALUES = "decodeNoValues";
+
     /**
      * Obtains a set of instructions for encoding an input {@link IdPAttribute} into a target type.
      * 
diff --git a/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryImpl.java b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryImpl.java
index 607901d..0fa1ab3 100644
--- a/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryImpl.java
+++ b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryImpl.java
@@ -26,6 +26,7 @@ import java.util.Properties;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 
 import net.shibboleth.ext.spring.service.AbstractServiceableComponent;
@@ -62,14 +63,10 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
     /** Registry of naming functions for supported object types. */
     @Nonnull private final Map<Class<?>,Function<?,String>> namingFunctionRegistry;
     
-    /** Maps acceptable subtypes of a given class into the proper base class to use during registry fuctions. */
-    @Nonnull private final Map<Class<?>,Class<?>> classEquivalenceRegistry;
-
     /** Constructor. */
     public AttributeTranscoderRegistryImpl() {
         transcodingRegistry = new HashMap<>();
         namingFunctionRegistry = new HashMap<>();
-        classEquivalenceRegistry = new HashMap<>();
     }
     
     /** {@inheritDoc} */
@@ -95,29 +92,6 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
             }
         });
     }
-    
-    /**
-     * Installs registry of mappings from subclasses of the "officially" registered types to their proper
-     * official type so that the registry can ignore them.
-     * 
-     * <p>For example, if a transcoder registry for type Foo also handles subtypes of Foo, those subtypes
-     * should be registered with mappings to Foo.</p>
-     * 
-     * @param registry mappings from subclass to the canonical parent class
-     */
-    public void setClassEquivalenceRegistry(@Nonnull @NonnullElements final Map<Class<?>,Class<?>> registry) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        if (registry == null) {
-            classEquivalenceRegistry.clear();
-            return;
-        }
-        
-        registry.forEach((k,v) -> {
-            if (k != null && v != null && v.isAssignableFrom(k)) {
-                classEquivalenceRegistry.put(k, v);
-            }
-        });
-    }
 
     /**
      * Installs the transcoder mappings en masse.
@@ -153,9 +127,19 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
         Constraint.isNotNull(to, "Target type cannot be null");
         
         final Multimap<Class<?>,Properties> propertyCollections = transcodingRegistry.get(from.getId());
+        if (propertyCollections == null) {
+            return Collections.emptyList();
+        }
         
-        return propertyCollections != null ? ImmutableList.copyOf(propertyCollections.get(getEffectiveType(to)))
-                : Collections.emptyList();
+        final Class<?> effectiveType = getEffectiveType(to);
+        if (effectiveType == null) {
+            log.warn("Unsupported object type: {}", to.getClass().getName());
+            return Collections.emptyList();
+        }
+        
+        log.trace("Using rules for effective type {}", effectiveType.getName());
+        
+        return ImmutableList.copyOf(propertyCollections.get(effectiveType));
     }
 
     /** {@inheritDoc} */
@@ -165,21 +149,24 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
         Constraint.isNotNull(from, "Input object cannot be null");
         
         final Class<?> effectiveType = getEffectiveType(from.getClass());
+        if (effectiveType == null) {
+            log.warn("Unsupported object type: {}", from.getClass().getName());
+            return Collections.emptyList();
+        }
         
-        final Function<?,String> namingFunction = namingFunctionRegistry.get(from.getClass());
-        if (namingFunction != null) {
-            // Don't know if we can work around this cast or not.
-            final String id = ((Function<? super T,String>) namingFunction).apply(from);
-            if (id != null) {
-                final Multimap<Class<?>,Properties> propertyCollections = transcodingRegistry.get(id);
-                
-                return propertyCollections != null ? ImmutableList.copyOf(propertyCollections.get(effectiveType))
-                        : Collections.emptyList();
-            } else {
-                log.warn("Object of type {} did not have a canonical name", from.getClass().getName());
-            }
+        log.trace("Using rules for effective type {}", effectiveType.getName());
+        
+        final Function<?,String> namingFunction = namingFunctionRegistry.get(effectiveType);
+        
+        // Don't know if we can work around this cast or not.
+        final String id = ((Function<? super T,String>) namingFunction).apply(from);
+        if (id != null) {
+            final Multimap<Class<?>,Properties> propertyCollections = transcodingRegistry.get(id);
+            
+            return propertyCollections != null ? ImmutableList.copyOf(propertyCollections.get(effectiveType))
+                    : Collections.emptyList();
         } else {
-            log.warn("Unsupported object type: {}", from.getClass().getName());
+            log.warn("Object of type {} did not have a canonical name", from.getClass().getName());
         }
         
         return Collections.emptyList();
@@ -253,12 +240,28 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
      * 
      * @param inputType the type passed into the registry operation
      * 
-     * @return the appropriate type to use subsequently
+     * @return the appropriate type to use subsequently or null if not found
      */
-    @Nonnull private Class<?> getEffectiveType(@Nonnull final Class<?> inputType) {
+    @Nullable private Class<?> getEffectiveType(@Nonnull final Class<?> inputType) {
+        
+        // Walk the superclass tree.
+        Class<?> returnType = inputType;
+        while (returnType != null && !namingFunctionRegistry.containsKey(returnType)) {
+            returnType = returnType.getSuperclass();
+        }
+        
+        if (returnType != null) {
+            return returnType;
+        }
+        
+        for (final Class<?> iface : inputType.getInterfaces()) {
+            returnType = getEffectiveType(iface);
+            if (returnType != null) {
+                return returnType;
+            }
+        }
         
-        final Class<?> outputType = classEquivalenceRegistry.get(inputType);
-        return outputType != null ? outputType : inputType;
+        return null;
     }
     
 }
\ No newline at end of file
diff --git a/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryTest.java b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryTest.java
index 9dad889..edea506 100644
--- a/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryTest.java
+++ b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryTest.java
@@ -27,8 +27,8 @@ import java.util.Map;
 import java.util.Properties;
 
 import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import net.shibboleth.idp.attribute.AttributeDecodingException;
@@ -49,15 +49,13 @@ public class AttributeTranscoderRegistryTest {
     
     private AttributeTranscoderRegistryImpl registry;
     
-    @BeforeMethod public void setUp() throws ComponentInitializationException {
+    @BeforeClass public void setUp() throws ComponentInitializationException {
         registry = new AttributeTranscoderRegistryImpl();
         registry.setId("test");
         
         registry.setNamingRegistry(Collections.singletonMap(
                 Pair.class, (Pair p) -> "{Pair}" + p.getFirst().toString()));
         
-        registry.setClassEquivalenceRegistry(Collections.singletonMap(MyPair1.class, Pair.class));
-        
         final PairTranscoder transcoder = new PairTranscoder();
         
         final Map<String,Collection<Properties>> mappings = new HashMap<>();
@@ -78,7 +76,7 @@ public class AttributeTranscoderRegistryTest {
         registry.initialize();
     }
     
-    @AfterMethod public void tearDown() {
+    @AfterClass public void tearDown() {
         registry.destroy();
         registry = null;
     }
@@ -88,8 +86,7 @@ public class AttributeTranscoderRegistryTest {
     @Test public void testEncodeNoMappings() throws AttributeEncodingException {
         
         Assert.assertTrue(registry.getTranscodingProperties(new IdPAttribute("frobnitz"), Pair.class).isEmpty());
-        Assert.assertTrue(registry.getTranscodingProperties(new IdPAttribute("frobnitz"), MyPair1.class).isEmpty());
-        Assert.assertTrue(registry.getTranscodingProperties(new IdPAttribute("foo"), MyPair2.class).isEmpty());
+        Assert.assertTrue(registry.getTranscodingProperties(new IdPAttribute("frobnitz"), MyPair.class).isEmpty());
         Assert.assertTrue(registry.getTranscodingProperties(new IdPAttribute("foo"), String.class).isEmpty());
 }
 
@@ -97,8 +94,7 @@ public class AttributeTranscoderRegistryTest {
     @Test public void testDecodeNoMappings() throws AttributeDecodingException {
         
         Assert.assertTrue(registry.getTranscodingProperties(new Pair("foo", "value")).isEmpty());
-        Assert.assertTrue(registry.getTranscodingProperties(new MyPair1("foo", "value")).isEmpty());
-        Assert.assertTrue(registry.getTranscodingProperties(new MyPair2("bar", "value")).isEmpty());
+        Assert.assertTrue(registry.getTranscodingProperties(new MyPair("foo", "value")).isEmpty());
         Assert.assertTrue(registry.getTranscodingProperties(new String("bar")).isEmpty());
     }
     
@@ -186,12 +182,12 @@ public class AttributeTranscoderRegistryTest {
         final IdPAttribute foo = new IdPAttribute("foo");
         foo.setValues(Collections.singletonList(StringAttributeValue.valueOf("value")));
         
-        final List<MyPair1> pairs = new ArrayList<>();
+        final List<MyPair> pairs = new ArrayList<>();
         
-        for (final Properties ruleset : registry.getTranscodingProperties(foo, MyPair1.class)) {
-            final AttributeTranscoder<MyPair1> t =
+        for (final Properties ruleset : registry.getTranscodingProperties(foo, MyPair.class)) {
+            final AttributeTranscoder<MyPair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
-            pairs.add(t.encode(null, foo, MyPair1.class, ruleset));
+            pairs.add(t.encode(null, foo, MyPair.class, ruleset));
         }
         
         Assert.assertEquals(pairs.size(), 2);
@@ -302,18 +298,12 @@ public class AttributeTranscoderRegistryTest {
         Assert.assertTrue(attributes.get(1).getValues().isEmpty());
     }
     
-    /** Marker classes to exercise subtype support. */
+    /** Marker class to exercise subtype support. */
     
-    public static class MyPair1 extends Pair {
-        public MyPair1(Object one, Object two) {
+    public static class MyPair extends Pair {
+        public MyPair(Object one, Object two) {
             super(one, two);
         }
     }
 
-    public static class MyPair2 extends Pair {
-        public MyPair2(Object one, Object two) {
-            super(one, two);
-        }        
-    }
-
 }
\ No newline at end of file
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML2AttributeTranscoder.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML2AttributeTranscoder.java
index acd3114..44e444d 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML2AttributeTranscoder.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAML2AttributeTranscoder.java
@@ -92,8 +92,8 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
     
     /** {@inheritDoc} */
     @Override
-    @Nonnull protected Attribute buildAttribute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final IdPAttribute attribute, @Nonnull final Class<? extends Attribute> to,
+    @Nonnull protected Attribute buildAttribute(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nullable final IdPAttribute attribute, @Nonnull final Class<? extends Attribute> to,
             @Nonnull final Properties properties, @Nonnull @NonnullElements final List<XMLObject> attributeValues)
                     throws AttributeEncodingException {
 
@@ -119,7 +119,8 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
         samlAttribute.setNameFormat(properties.getProperty(PROP_NAME_FORMAT, Attribute.URI_REFERENCE));
         samlAttribute.getAttributeValues().addAll(attributeValues);
         
-        final String friendlyName = properties.getProperty(PROP_FRIENDLY_NAME, attribute.getId());
+        final String friendlyName = properties.getProperty(PROP_FRIENDLY_NAME,
+                attribute != null ? attribute.getId() : "");
         if (!friendlyName.isBlank()) {
             samlAttribute.setFriendlyName(friendlyName);
         }
@@ -130,7 +131,7 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
     /** {@inheritDoc} */
     @Override
     @Nonnull protected IdPAttribute buildIdPAttribute(
-            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
             @Nonnull final Properties properties,
             @Nonnull @NonnullElements final List<IdPAttributeValue<?>> attributeValues)
                     throws AttributeDecodingException {
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAMLAttributeTranscoder.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAMLAttributeTranscoder.java
index 464ef9f..c9ce061 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAMLAttributeTranscoder.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/AbstractSAMLAttributeTranscoder.java
@@ -30,6 +30,7 @@ import net.shibboleth.idp.attribute.AttributeEncodingException;
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.transcoding.AbstractAttributeTranscoder;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
@@ -68,7 +69,7 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
     @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractSAMLAttributeTranscoder.class);
         
     /** {@inheritDoc} */
-    @Nullable public AttributeType encode(@Nonnull final ProfileRequestContext profileRequestContext,
+    @Nullable public AttributeType encode(@Nullable final ProfileRequestContext profileRequestContext,
             @Nonnull final IdPAttribute attribute, @Nonnull final Class<? extends AttributeType> to,
             @Nonnull final Properties properties) throws AttributeEncodingException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
@@ -94,7 +95,7 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
 
             if (!canEncodeValue(attribute, o)) {
                 log.warn("Skipping value of attribute '{}'; Type {} cannot be encoded by this encoder ({}).",
-                        attributeId, o.getClass().getName(), this.getClass().getName());
+                        attributeId, o.getClass().getSimpleName(), this.getClass().getSimpleName());
                 continue;
             }
 
@@ -102,18 +103,28 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
             final XMLObject samlAttributeValue =
                     encodeValue(profileRequestContext, attribute, properties, attributeValue);
             if (samlAttributeValue == null) {
-                log.debug("Skipping empty value for attribute {}", attributeId);
+                log.debug("Skipping null value for attribute {}", attributeId);
             } else {
                 samlAttributeValues.add(samlAttributeValue);
             }
         }
+        
+        if (samlAttributeValues.isEmpty()) {
+            Object allowNoValues = properties.get(AttributeTranscoderRegistry.PROP_ENCODE_NO_VALUES);
+            if (!(allowNoValues instanceof Boolean)) {
+                allowNoValues = false;
+            }
+            if (! (Boolean) allowNoValues) {
+                throw new AttributeEncodingException("No values encoded for attribute " + attributeId);
+            }
+        }
 
         log.debug("Completed encoding {} values for attribute {}", samlAttributeValues.size(), attributeId);
         return buildAttribute(profileRequestContext, attribute, to, properties, samlAttributeValues);
     }
 
     /** {@inheritDoc} */
-    @Nullable public IdPAttribute decode(@Nonnull final ProfileRequestContext profileRequestContext,
+    @Nullable public IdPAttribute decode(@Nullable final ProfileRequestContext profileRequestContext,
             @Nonnull final AttributeType input, @Nonnull final Properties properties)
                     throws AttributeDecodingException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
@@ -145,6 +156,16 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
                 idpAttributeValues.add(idpAttributeValue);
             }
         }
+        
+        if (idpAttributeValues.isEmpty()) {
+            Object allowNoValues = properties.get(AttributeTranscoderRegistry.PROP_DECODE_NO_VALUES);
+            if (!(allowNoValues instanceof Boolean)) {
+                allowNoValues = true;
+            }
+            if (! (Boolean) allowNoValues) {
+                throw new AttributeDecodingException("No values decoded for attribute " + attributeName);
+            }
+        }
 
         log.debug("Completed decoding {} values for attribute {}", idpAttributeValues.size(), attributeName);
         return buildIdPAttribute(profileRequestContext, input, properties, idpAttributeValues);
@@ -201,11 +222,24 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
         }
 
         if (null == retVal) {
-            log.info("Value of type {} could not be converted", object.getClass().toString());
+            log.info("Value of type {} could not be converted", object.getClass().getSimpleName());
         }
         return retVal;
     }
 // Checkstyle: CyclomaticComplexity ON
+
+    /**
+     * Checks if the given value can be handled by the transcoder.
+     * 
+     * <p>In many cases this is simply a check to see if the given object is of the right type.</p>
+     * 
+     * @param idpAttribute the attribute being encoded, never null
+     * @param value the value to check, never null
+     * 
+     * @return true if the transcoder can encode this value, false if not
+     */
+    protected abstract boolean canEncodeValue(@Nonnull final IdPAttribute idpAttribute,
+            @Nonnull final IdPAttributeValue value);
     
     /**
      * Builds a SAML attribute element from the given attribute values.
@@ -220,10 +254,10 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
      * 
      * @throws AttributeEncodingException thrown if there is a problem constructing the SAML attribute
      */
-    @Nonnull protected abstract AttributeType buildAttribute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final IdPAttribute attribute, @Nonnull final Class<? extends AttributeType> to,
-            @Nonnull final Properties properties, @Nonnull @NonnullElements final List<XMLObject> attributeValues)
-                    throws AttributeEncodingException;
+    @Nonnull protected abstract AttributeType buildAttribute(
+            @Nullable final ProfileRequestContext profileRequestContext, @Nullable final IdPAttribute attribute,
+            @Nonnull final Class<? extends AttributeType> to, @Nonnull final Properties properties,
+            @Nonnull @NonnullElements final List<XMLObject> attributeValues) throws AttributeEncodingException;
 
     /**
      * Encodes an attribute value into a SAML AttributeValue element.
@@ -237,7 +271,7 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
      * 
      * @throws AttributeEncodingException thrown if there is a problem encoding the attribute value
      */
-    @Nullable protected abstract XMLObject encodeValue(@Nonnull final ProfileRequestContext profileRequestContext,
+    @Nullable protected abstract XMLObject encodeValue(@Nullable final ProfileRequestContext profileRequestContext,
             @Nonnull final IdPAttribute attribute, @Nonnull final Properties properties,
             @Nonnull final EncodedType value) throws AttributeEncodingException;
 
@@ -254,7 +288,7 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
      * @throws AttributeDecodingException thrown if there is a problem constructing the IdPAttribute
      */
     @Nonnull protected abstract IdPAttribute buildIdPAttribute(
-            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final AttributeType attribute,
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final AttributeType attribute,
             @Nonnull final Properties properties,
             @Nonnull @NonnullElements final List<IdPAttributeValue<?>> attributeValues)
                     throws AttributeDecodingException;
@@ -268,7 +302,6 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
      */
     @Nonnull protected abstract Iterable<XMLObject> getValues(@Nonnull final AttributeType input);
     
-    
     /**
      * Function to decode a single {@link XMLObject} into an {@link IdPAttributeValue}.
      * 
@@ -280,7 +313,7 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
      * @return the returned final {@link IdPAttributeValue} or null if decoding failed
      */
     @Nullable protected abstract IdPAttributeValue<?> decodeValue(
-            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final AttributeType attribute,
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final AttributeType attribute,
             @Nonnull final Properties properties, @Nullable final XMLObject value);
 
 }
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2StringAttributeTranscoder.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2StringAttributeTranscoder.java
index 420c308..dd6cfbd 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2StringAttributeTranscoder.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2StringAttributeTranscoder.java
@@ -54,13 +54,13 @@ public class SAML2StringAttributeTranscoder extends AbstractSAML2AttributeTransc
     }
 
     /** {@inheritDoc} */
-    @Override @Nullable protected XMLObject encodeValue(@Nonnull final ProfileRequestContext profileRequestContext,
+    @Override @Nullable protected XMLObject encodeValue(@Nullable final ProfileRequestContext profileRequestContext,
             @Nonnull final IdPAttribute attribute, @Nonnull final Properties properties,
             @Nonnull final StringAttributeValue value) throws AttributeEncodingException {
         
         if (value instanceof LocalizedStringAttributeValue || value instanceof ScopedStringAttributeValue) {
             log.warn("Attribute '{}': Lossy encoding of attribute value of type {} to SAML2 String Attribute",
-                    attribute.getId(), value.getClass().getName());
+                    attribute.getId(), value.getClass().getSimpleName());
         }
         
         final Object encodeType = properties.getOrDefault(PROP_ENCODE_TYPE, Boolean.TRUE);
@@ -72,7 +72,7 @@ public class SAML2StringAttributeTranscoder extends AbstractSAML2AttributeTransc
 
     /** {@inheritDoc} */
     @Override @Nullable protected IdPAttributeValue<?> decodeValue(
-            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
             @Nonnull final Properties properties, @Nullable final XMLObject value) {
         
         return value != null ? StringAttributeValue.valueOf(getStringValue(value)) : null;
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2StringAttributeEncoderTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2StringAttributeEncoderTest.java
new file mode 100644
index 0000000..b41146a
--- /dev/null
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2StringAttributeEncoderTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.saml.attribute.transcoding.impl;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.attribute.AttributeEncodingException;
+import net.shibboleth.idp.attribute.ByteAttributeValue;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
+import net.shibboleth.idp.saml.attribute.transcoding.AbstractSAML2AttributeTranscoder;
+import net.shibboleth.idp.saml.attribute.transcoding.AbstractSAMLAttributeTranscoder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.opensaml.core.OpenSAMLInitBaseTestCase;
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.saml.common.SAMLObjectBuilder;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeValue;
+import org.opensaml.saml.saml2.metadata.RequestedAttribute;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/** {@link SAML2StringAttributeTranscoder} unit test. */
+public class SAML2StringAttributeEncoderTest extends OpenSAMLInitBaseTestCase {
+
+    private AttributeTranscoderRegistryImpl registry;
+
+    @Nonnull private SAMLObjectBuilder<Attribute> attributeBuilder;
+
+    @Nonnull private SAMLObjectBuilder<RequestedAttribute> reqAttributeBuilder;
+
+    private final static String ATTR_NAME = "foo";
+    private final static String ATTR_NAMEFORMAT = "Namespace";
+    private final static String ATTR_FRIENDLYNAME = "friendly";
+    private final static String STRING_1 = "Value The First";
+    private final static String STRING_2 = "Second string the value is";
+
+    @BeforeClass public void setUp() throws ComponentInitializationException {
+        
+        attributeBuilder = (SAMLObjectBuilder<Attribute>)
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<Attribute>getBuilderOrThrow(
+                        Attribute.TYPE_NAME);
+        reqAttributeBuilder = (SAMLObjectBuilder<RequestedAttribute>)
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<RequestedAttribute>getBuilderOrThrow(
+                        RequestedAttribute.TYPE_NAME);
+        
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+        
+        registry.setNamingRegistry(Collections.singletonMap(
+                Attribute.class, new AbstractSAML2AttributeTranscoder.NamingFunction()));
+        
+        final SAML2StringAttributeTranscoder transcoder = new SAML2StringAttributeTranscoder();
+        transcoder.initialize();
+        
+        final Map<String,Collection<Properties>> mappings = new HashMap<>();
+        
+        final Properties ruleset1 = new Properties();
+        ruleset1.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset1.put(AbstractSAMLAttributeTranscoder.PROP_ENCODE_TYPE, true);
+        ruleset1.setProperty(AbstractSAMLAttributeTranscoder.PROP_NAME, ATTR_NAME);
+        ruleset1.setProperty(AbstractSAML2AttributeTranscoder.PROP_NAME_FORMAT, ATTR_NAMEFORMAT);
+        ruleset1.setProperty(AbstractSAML2AttributeTranscoder.PROP_FRIENDLY_NAME, ATTR_FRIENDLYNAME);
+        
+        mappings.put(ATTR_NAME, Collections.singletonList(ruleset1));
+        
+        registry.setTranscoderRegistry(mappings);
+        
+        registry.initialize();
+    }
+    
+    @AfterClass public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+
+    @Test(expectedExceptions = {AttributeEncodingException.class,}) public void emptyEncode() throws Exception {
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_NAME);
+
+        final Collection<Properties> rulesets = registry.getTranscodingProperties(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final Properties ruleset = rulesets.iterator().next();
+        
+        final Attribute attr =
+                ((AttributeTranscoder<Attribute>) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER)).encode(
+                        null, inputAttribute, Attribute.class, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+        Assert.assertEquals(attr.getNameFormat(), ATTR_NAMEFORMAT);
+        Assert.assertEquals(attr.getFriendlyName(), ATTR_FRIENDLYNAME);
+        Assert.assertTrue(attr.getAttributeValues().isEmpty());
+    }
+
+    @Test public void emptyDecode() throws Exception {
+        
+        final Attribute samlAttribute = attributeBuilder.buildObject();
+        samlAttribute.setName(ATTR_NAME);
+        samlAttribute.setNameFormat(ATTR_NAMEFORMAT);
+
+        final Collection<Properties> rulesets = registry.getTranscodingProperties(samlAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final Properties ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr =
+                ((AttributeTranscoder<Attribute>) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER)).decode(
+                        null, samlAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_NAME);
+        Assert.assertTrue(attr.getValues().isEmpty());
+    }
+
+    @Test(expectedExceptions = {AttributeEncodingException.class,}) public void inappropriate() throws Exception {
+        final int[] intArray = {1, 2, 3, 4};
+        final Collection<? extends IdPAttributeValue<?>> values =
+                Arrays.asList(new ByteAttributeValue(new byte[] {1, 2, 3,}), new IdPAttributeValue<Object>() {
+                    @Override
+                    public Object getValue() {
+                        return intArray;
+                    }
+                    @Override
+                    public String getDisplayValue() {
+                        return intArray.toString();
+                    }
+                });
+
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_NAME);
+        inputAttribute.setValues(values);
+
+        final Collection<Properties> rulesets = registry.getTranscodingProperties(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final Properties ruleset = rulesets.iterator().next();
+        
+        ((AttributeTranscoder<Attribute>) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER)).encode(
+                null, inputAttribute, Attribute.class, ruleset);
+    }
+
+    @Test public void single() throws Exception {
+        final Collection<? extends IdPAttributeValue<?>> values =
+                Arrays.asList(new ByteAttributeValue(new byte[] {1, 2, 3,}), new StringAttributeValue(STRING_1));
+
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_NAME);
+        inputAttribute.setValues(values);
+        
+        final Collection<Properties> rulesets = registry.getTranscodingProperties(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final Properties ruleset = rulesets.iterator().next();
+        
+        final Attribute attr =
+                ((AttributeTranscoder<Attribute>) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER)).encode(
+                        null, inputAttribute, Attribute.class, ruleset);
+
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+        Assert.assertEquals(attr.getNameFormat(), ATTR_NAMEFORMAT);
+        Assert.assertEquals(attr.getFriendlyName(), ATTR_FRIENDLYNAME);
+
+        final List<XMLObject> children = attr.getOrderedChildren();
+
+        Assert.assertEquals(children.size(), 1, "Encoding one entry");
+
+        final XMLObject child = children.get(0);
+
+        Assert.assertEquals(child.getElementQName(), AttributeValue.DEFAULT_ELEMENT_NAME,
+                "Attribute Value not inside <AttributeValue/>");
+
+        Assert.assertTrue(child instanceof XSString, "Child of result attribute should be a string");
+
+        final XSString childAsString = (XSString) child;
+
+        Assert.assertEquals(childAsString.getValue(), STRING_1);
+    }
+
+    @Test public void multi() throws Exception {
+        final Collection<? extends IdPAttributeValue<?>> values =
+                Arrays.asList(new ByteAttributeValue(new byte[] {1, 2, 3,}),
+                        new StringAttributeValue(STRING_1),
+                        new StringAttributeValue(STRING_2),
+                        new ScopedStringAttributeValue(STRING_1, STRING_2));
+
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_NAME);
+        inputAttribute.setValues(values);
+
+        final Collection<Properties> rulesets = registry.getTranscodingProperties(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final Properties ruleset = rulesets.iterator().next();
+        
+        final Attribute attr =
+                ((AttributeTranscoder<Attribute>) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER)).encode(
+                        null, inputAttribute, Attribute.class, ruleset);
+
+        Assert.assertNotNull(attr);
+
+        final List<XMLObject> children = attr.getOrderedChildren();
+        Assert.assertEquals(children.size(), 3, "Encoding three entries");
+
+        for (final XMLObject child: children) {
+            Assert.assertTrue(child instanceof XSString, "Child of result attribute should be a string");
+            final String childAsString = ((XSString) children.get(0)).getValue();
+            Assert.assertTrue(STRING_1.equals(childAsString)||STRING_2.equals(childAsString));
+        }
+    }
+
+}
\ 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