[java-identity-provider] 04/27: Next checkpoint.

Scott Cantor cantor.2 at osu.edu
Fri May 3 14:31:54 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=733ec43a182bcb573f5766f1eca3eebdc884793e

commit 733ec43a182bcb573f5766f1eca3eebdc884793e
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Apr 10 17:51:55 2019 -0400

    Next checkpoint.
---
 .../attribute/transcoding/AttributeTranscoder.java |  12 +-
 .../transcoding/AttributeTranscoderRegistry.java   |   3 -
 .../impl/AttributeTranscoderRegistryImpl.java      |  74 +++++++----
 .../impl/AttributeTranscoderRegistryTest.java      | 136 +++++++++------------
 .../attribute/transcoding/impl/PairTranscoder.java |  27 ++--
 .../src/main/resources/conf/attribute-registry.xml |   1 -
 .../AbstractSAML2AttributeTranscoder.java          |  80 ++++++++++--
 .../AbstractSAMLAttributeTranscoder.java           | 116 +++++++++++++-----
 .../impl/SAML2StringAttributeTranscoder.java       |   8 +-
 9 files changed, 300 insertions(+), 157 deletions(-)

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 2572616..c50eef1 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
@@ -48,6 +48,13 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 public interface AttributeTranscoder<T> {
 
     /**
+     * Get the class representing the type of object supported by this transcoder.
+     * 
+     * @return object type supported
+     */
+    @Nonnull Class<T> getEncodedType();
+    
+    /**
      * Get the name of the encoded object that would be created by a given set of
      * instructions.
      * 
@@ -63,6 +70,7 @@ public interface AttributeTranscoder<T> {
      * 
      * @param profileRequestContext current profile request context
      * @param attribute the attribute to encode
+     * @param to specific type of object to encode
      * @param properties properties governing the encoding process, principally the resulting object's naming
      * 
      * @return the Object the attribute was encoded into
@@ -70,8 +78,8 @@ public interface AttributeTranscoder<T> {
      * @throws AttributeEncodingException if unable to successfully encode attribute
      */
     @Nullable T encode(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final IdPAttribute attribute, @Nonnull final Properties properties)
-                    throws AttributeEncodingException;
+            @Nonnull final IdPAttribute attribute, @Nonnull final Class<? extends T> to,
+            @Nonnull final Properties properties) throws AttributeEncodingException;
     
     /**
      * Decode the supplied object into a protocol-neutral representation.
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 756c0ab..07dce7b 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
@@ -41,9 +41,6 @@ public interface AttributeTranscoderRegistry extends IdentifiedComponent {
     /** Property name for accessing the name of the {@link IdPAttribute} to decode into. */
     @Nonnull @NotEmpty static final String PROP_ID = "id";
 
-    /** Property name for accessing the class of object supported by a given ruleset. */
-    @Nonnull @NotEmpty static final String PROP_TYPE = "type";
-
     /** Property name for accessing {@link AttributeTranscoder} object to use. */
     @Nonnull @NotEmpty static final String PROP_TRANSCODER = "transcoder";
     
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 24d8e1f..607901d 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
@@ -61,11 +61,15 @@ 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} */
@@ -91,9 +95,32 @@ 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);
+            }
+        });
+    }
 
     /**
-     * Install the transcoder mappings en masse.
+     * Installs the transcoder mappings en masse.
      * 
      * <p>Each map entry connects an {@link IdPAttribute} name to the rules for transcoding to/from it.</p>
      * 
@@ -127,7 +154,7 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
         
         final Multimap<Class<?>,Properties> propertyCollections = transcodingRegistry.get(from.getId());
         
-        return propertyCollections != null ? ImmutableList.copyOf(propertyCollections.get(to))
+        return propertyCollections != null ? ImmutableList.copyOf(propertyCollections.get(getEffectiveType(to)))
                 : Collections.emptyList();
     }
 
@@ -137,14 +164,16 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
         Constraint.isNotNull(from, "Input object cannot be null");
         
+        final Class<?> effectiveType = getEffectiveType(from.getClass());
+        
         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<T,String>) namingFunction).apply(from);
+            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(from.getClass()))
+                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());
@@ -169,20 +198,8 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
      * @param ruleset transcoding rules
      */
     private void addMapping(@Nonnull @NotEmpty final String id, @Nonnull final Properties ruleset) {
-        Object type = ruleset.get(PROP_TYPE);
+
         Object transcoder = ruleset.get(PROP_TRANSCODER);
-        
-        if (type instanceof String) {
-            try {
-                type = Class.forName((String) type);
-            } catch (final ClassNotFoundException e) {
-                log.warn("Target class type {} not found in transcoding rule for {}", type, id);
-                return;
-            }
-        } else if (type == null) {
-            log.warn("Transcoding rule for {} missing {} property", id, PROP_TYPE);
-        }
-        
         if (transcoder instanceof String) {
             try {
                 transcoder = Class.forName((String) transcoder).getDeclaredConstructor().newInstance();
@@ -197,6 +214,7 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
             log.warn("Transcoding rule for {} missing {} property", id, PROP_TRANSCODER);
         }
 
+        final Class<?> type = ((AttributeTranscoder) transcoder).getEncodedType();
         final String targetName = ((AttributeTranscoder) transcoder).getEncodedName(ruleset);
         if (targetName != null) {
 
@@ -204,7 +222,6 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
             copy.putAll(ruleset);
 
             copy.put(PROP_TRANSCODER, transcoder);
-            copy.put(PROP_TYPE, type);
             
             // Install mapping back to IdPAttribute's name.
             copy.setProperty(PROP_ID, id);
@@ -215,7 +232,7 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
                 transcodingRegistry.put(id, rulesetsForIdPName);
             }
             
-            rulesetsForIdPName.put((Class) type, copy);
+            rulesetsForIdPName.put(type, copy);
 
             Multimap<Class<?>,Properties> rulesetsForEncodedName = transcodingRegistry.get(targetName);
             if (rulesetsForEncodedName == null) {
@@ -223,12 +240,25 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
                 transcodingRegistry.put(targetName, rulesetsForEncodedName);
             }
             
-            rulesetsForEncodedName.put((Class) type, copy);
+            rulesetsForEncodedName.put(type, copy);
             
         } else {
-            log.warn("Transcoding rule for {} into type {} did not produce an encoded name",
-                    id, ((Class) type).getName());
+            log.warn("Transcoding rule for {} into type {} did not produce an encoded name", id, type.getName());
         }
     }
 
+    /**
+     * Convert an input type into the appropriate type (possibly itself) to use in looking up
+     * rules in the registry.
+     * 
+     * @param inputType the type passed into the registry operation
+     * 
+     * @return the appropriate type to use subsequently
+     */
+    @Nonnull private Class<?> getEffectiveType(@Nonnull final Class<?> inputType) {
+        
+        final Class<?> outputType = classEquivalenceRegistry.get(inputType);
+        return outputType != null ? outputType : inputType;
+    }
+    
 }
\ 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 689c036..9dad889 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
@@ -26,7 +26,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
-import org.opensaml.profile.context.ProfileRequestContext;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
@@ -36,15 +35,12 @@ import net.shibboleth.idp.attribute.AttributeDecodingException;
 import net.shibboleth.idp.attribute.AttributeEncodingException;
 import net.shibboleth.idp.attribute.EmptyAttributeValue;
 import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.StringAttributeValue;
-import net.shibboleth.idp.attribute.transcoding.AbstractAttributeTranscoder;
 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.utilities.java.support.collection.Pair;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 /**
  * Test for {@link AttributeTranscoderRegistry}.
@@ -60,17 +56,17 @@ public class AttributeTranscoderRegistryTest {
         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<>();
         
         final Properties ruleset1 = new Properties();
-        ruleset1.put(AttributeTranscoderRegistry.PROP_TYPE, Pair.class);
         ruleset1.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
         ruleset1.setProperty("name", "bar");
         
         final Properties ruleset2 = new Properties();
-        ruleset2.put(AttributeTranscoderRegistry.PROP_TYPE, "net.shibboleth.utilities.java.support.collection.Pair");
         ruleset2.put(AttributeTranscoderRegistry.PROP_TRANSCODER, "net.shibboleth.idp.attribute.transcoding.impl.PairTranscoder");
         ruleset2.setProperty("name", "baz");
         
@@ -92,6 +88,8 @@ 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("foo"), String.class).isEmpty());
 }
 
@@ -99,18 +97,20 @@ public class AttributeTranscoderRegistryTest {
     @Test public void testDecodeNoMappings() throws AttributeDecodingException {
         
         Assert.assertTrue(registry.getTranscodingProperties(new Pair("foo", "value")).isEmpty());
-        Assert.assertTrue(registry.getTranscodingProperties(new String("foo")).isEmpty());
+        Assert.assertTrue(registry.getTranscodingProperties(new MyPair1("foo", "value")).isEmpty());
+        Assert.assertTrue(registry.getTranscodingProperties(new MyPair2("bar", "value")).isEmpty());
+        Assert.assertTrue(registry.getTranscodingProperties(new String("bar")).isEmpty());
     }
     
     @Test public void testEncodeNoValues() throws AttributeEncodingException {
         final IdPAttribute foo = new IdPAttribute("foo");
         
-        final List<Pair<String,Object>> pairs = new ArrayList<>();
+        final List<Pair> pairs = new ArrayList<>();
         
         for (final Properties ruleset : registry.getTranscodingProperties(foo, Pair.class)) {
-            final AttributeTranscoder<Pair<String,Object>> t =
+            final AttributeTranscoder<Pair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
-            pairs.add(t.encode(null, foo, ruleset));
+            pairs.add(t.encode(null, foo, Pair.class, ruleset));
         }
         
         Assert.assertEquals(pairs.size(), 2);
@@ -124,12 +124,12 @@ public class AttributeTranscoderRegistryTest {
 
     @Test public void testDecodeOneNoValues() throws AttributeDecodingException {
         
-        final Pair<String,Object> bar = new Pair("bar", null);
+        final Pair bar = new Pair("bar", null);
         
         final List<IdPAttribute> attributes = new ArrayList<>();
         
         for (final Properties ruleset : registry.getTranscodingProperties(bar)) {
-            final AttributeTranscoder<Pair<String,Object>> t =
+            final AttributeTranscoder<Pair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
             attributes.add(t.decode(null, bar, ruleset));
         }
@@ -142,12 +142,12 @@ public class AttributeTranscoderRegistryTest {
 
     @Test public void testDecodeTwoNoValues() throws AttributeDecodingException {
         
-        final Pair<String,Object> baz = new Pair("baz", null);
+        final Pair baz = new Pair("baz", null);
         
         final List<IdPAttribute> attributes = new ArrayList<>();
         
         for (final Properties ruleset : registry.getTranscodingProperties(baz)) {
-            final AttributeTranscoder<Pair<String,Object>> t =
+            final AttributeTranscoder<Pair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
             attributes.add(t.decode(null, baz, ruleset));
         }
@@ -165,12 +165,33 @@ public class AttributeTranscoderRegistryTest {
         final IdPAttribute foo = new IdPAttribute("foo");
         foo.setValues(Collections.singletonList(StringAttributeValue.valueOf("value")));
         
-        final List<Pair<String,Object>> pairs = new ArrayList<>();
+        final List<Pair> pairs = new ArrayList<>();
         
         for (final Properties ruleset : registry.getTranscodingProperties(foo, Pair.class)) {
-            final AttributeTranscoder<Pair<String,Object>> t =
+            final AttributeTranscoder<Pair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
-            pairs.add(t.encode(null, foo, ruleset));
+            pairs.add(t.encode(null, foo, Pair.class, ruleset));
+        }
+        
+        Assert.assertEquals(pairs.size(), 2);
+        
+        Assert.assertEquals(pairs.get(0).getFirst(), "bar");
+        Assert.assertEquals(pairs.get(0).getSecond(), "value");
+        
+        Assert.assertEquals(pairs.get(1).getFirst(), "baz");
+        Assert.assertEquals(pairs.get(1).getSecond(), "value");
+    }
+
+    @Test public void testEncodeSubtypeStringValues() throws AttributeEncodingException {
+        final IdPAttribute foo = new IdPAttribute("foo");
+        foo.setValues(Collections.singletonList(StringAttributeValue.valueOf("value")));
+        
+        final List<MyPair1> pairs = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(foo, MyPair1.class)) {
+            final AttributeTranscoder<MyPair1> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            pairs.add(t.encode(null, foo, MyPair1.class, ruleset));
         }
         
         Assert.assertEquals(pairs.size(), 2);
@@ -184,12 +205,12 @@ public class AttributeTranscoderRegistryTest {
     
     @Test public void testDecodeOneStringValues() throws AttributeDecodingException {
         
-        final Pair<String,Object> bar = new Pair("bar", "value");
+        final Pair bar = new Pair("bar", "value");
         
         final List<IdPAttribute> attributes = new ArrayList<>();
         
         for (final Properties ruleset : registry.getTranscodingProperties(bar)) {
-            final AttributeTranscoder<Pair<String,Object>> t =
+            final AttributeTranscoder<Pair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
             attributes.add(t.decode(null, bar, ruleset));
         }
@@ -202,12 +223,12 @@ public class AttributeTranscoderRegistryTest {
     
     @Test public void testDecodeTwoStringValues() throws AttributeDecodingException {
         
-        final Pair<String,Object> baz = new Pair("baz", "value");
+        final Pair baz = new Pair("baz", "value");
         
         final List<IdPAttribute> attributes = new ArrayList<>();
         
         for (final Properties ruleset : registry.getTranscodingProperties(baz)) {
-            final AttributeTranscoder<Pair<String,Object>> t =
+            final AttributeTranscoder<Pair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
             attributes.add(t.decode(null, baz, ruleset));
         }
@@ -225,12 +246,12 @@ public class AttributeTranscoderRegistryTest {
         final IdPAttribute foo = new IdPAttribute("foo");
         foo.setValues(Collections.singletonList(EmptyAttributeValue.ZERO_LENGTH));
         
-        final List<Pair<String,Object>> pairs = new ArrayList<>();
+        final List<Pair> pairs = new ArrayList<>();
         
         for (final Properties ruleset : registry.getTranscodingProperties(foo, Pair.class)) {
-            final AttributeTranscoder<Pair<String,Object>> t =
+            final AttributeTranscoder<Pair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
-            pairs.add(t.encode(null, foo, ruleset));
+            pairs.add(t.encode(null, foo, Pair.class, ruleset));
         }
         
         Assert.assertEquals(pairs.size(), 2);
@@ -244,12 +265,12 @@ public class AttributeTranscoderRegistryTest {
     
     @Test public void testDecodeOneUnsupportedValues() throws AttributeDecodingException {
         
-        final Pair<String,Object> bar = new Pair("bar", 0L);
+        final Pair bar = new Pair("bar", 0L);
         
         final List<IdPAttribute> attributes = new ArrayList<>();
         
         for (final Properties ruleset : registry.getTranscodingProperties(bar)) {
-            final AttributeTranscoder<Pair<String,Object>> t =
+            final AttributeTranscoder<Pair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
             attributes.add(t.decode(null, bar, ruleset));
         }
@@ -262,12 +283,12 @@ public class AttributeTranscoderRegistryTest {
     
     @Test public void testDecodeTwoUnsupportedValues() throws AttributeDecodingException {
         
-        final Pair<String,Object> baz = new Pair("baz", 0L);
+        final Pair baz = new Pair("baz", 0L);
         
         final List<IdPAttribute> attributes = new ArrayList<>();
         
         for (final Properties ruleset : registry.getTranscodingProperties(baz)) {
-            final AttributeTranscoder<Pair<String,Object>> t =
+            final AttributeTranscoder<Pair> t =
                     (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
             attributes.add(t.decode(null, baz, ruleset));
         }
@@ -281,55 +302,18 @@ public class AttributeTranscoderRegistryTest {
         Assert.assertTrue(attributes.get(1).getValues().isEmpty());
     }
     
-    public class PairTranscoder extends AbstractAttributeTranscoder<Pair<String,Object>> {
-
-        /** {@inheritDoc} */
-        public String getEncodedName(Properties properties) {
-            if (properties.containsKey("name")) {
-                return "{Pair}" + properties.getProperty("name");
-            } else {
-                return null;
-            }
-        }
-
-        /** {@inheritDoc} */
-        public Pair<String,Object> encode(ProfileRequestContext profileRequestContext, IdPAttribute attribute, Properties properties)
-                throws AttributeEncodingException {
-            
-            final String name = StringSupport.trimOrNull(properties.getProperty("name"));
-            if (name == null) {
-                throw new AttributeEncodingException("No name property");
-            }
-            
-            if (attribute.getValues().isEmpty() || !canEncodeValue(attribute, attribute.getValues().get(0))) {
-                return new Pair<>(name, null);
-            } else {
-                return new Pair<>(name, attribute.getValues().get(0).getValue());
-            }
-        }
-
-        /** {@inheritDoc} */
-        public IdPAttribute decode(ProfileRequestContext profileRequestContext, Pair<String,Object> input, Properties properties)
-                throws AttributeDecodingException {
-           
-            final String id = StringSupport.trimOrNull(properties.getProperty(AttributeTranscoderRegistry.PROP_ID));
-            if (id == null) {
-                throw new AttributeDecodingException("No id property");
-            }
-            
-            final IdPAttribute idattr = new IdPAttribute(id);
-            
-            if (input.getSecond() instanceof String) {
-                idattr.setValues(Collections.singletonList(StringAttributeValue.valueOf((String) input.getSecond())));
-            }
-            
-            return idattr;
+    /** Marker classes to exercise subtype support. */
+    
+    public static class MyPair1 extends Pair {
+        public MyPair1(Object one, Object two) {
+            super(one, two);
         }
+    }
 
-        /** {@inheritDoc} */
-        protected boolean canEncodeValue(IdPAttribute idpAttribute, IdPAttributeValue value) {
-            return value instanceof StringAttributeValue;
-        }
+    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-attribute-impl/src/test/java/net/shibboleth/idp/attribute/transcoding/impl/PairTranscoder.java b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/transcoding/impl/PairTranscoder.java
index 9cb0384..e38287a 100644
--- a/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/transcoding/impl/PairTranscoder.java
+++ b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/transcoding/impl/PairTranscoder.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.attribute.transcoding.impl;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.Collections;
 import java.util.Properties;
 
@@ -35,7 +36,12 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
 /**
  * Sample transcoder for tests. 
  */
-public class PairTranscoder extends AbstractAttributeTranscoder<Pair<String,Object>> {
+public class PairTranscoder extends AbstractAttributeTranscoder<Pair> {
+
+    /** {@inheritDoc} */
+    public Class<Pair> getEncodedType() {
+        return Pair.class;
+    }
 
     /** {@inheritDoc} */
     public String getEncodedName(Properties properties) {
@@ -47,23 +53,28 @@ public class PairTranscoder extends AbstractAttributeTranscoder<Pair<String,Obje
     }
 
     /** {@inheritDoc} */
-    public Pair<String,Object> encode(ProfileRequestContext profileRequestContext, IdPAttribute attribute, Properties properties)
+    public Pair encode(ProfileRequestContext profileRequestContext, IdPAttribute attribute, Class<? extends Pair> to, Properties properties)
             throws AttributeEncodingException {
         
         final String name = StringSupport.trimOrNull(properties.getProperty("name"));
         if (name == null) {
             throw new AttributeEncodingException("No name property");
         }
-        
-        if (attribute.getValues().isEmpty() || !canEncodeValue(attribute, attribute.getValues().get(0))) {
-            return new Pair<>(name, null);
-        } else {
-            return new Pair<>(name, attribute.getValues().get(0).getValue());
+
+        try {
+            if (attribute.getValues().isEmpty() || !canEncodeValue(attribute, attribute.getValues().get(0))) {
+                return to.getDeclaredConstructor(Object.class, Object.class).newInstance(name, null);
+            } else {
+                return to.getDeclaredConstructor(Object.class, Object.class).newInstance(name, attribute.getValues().get(0).getValue());
+            }
+        } catch (final InstantiationException | IllegalAccessException | IllegalArgumentException
+                | InvocationTargetException | NoSuchMethodException | SecurityException e) {
+            throw new AttributeEncodingException(e);
         }
     }
 
     /** {@inheritDoc} */
-    public IdPAttribute decode(ProfileRequestContext profileRequestContext, Pair<String,Object> input, Properties properties)
+    public IdPAttribute decode(ProfileRequestContext profileRequestContext, Pair input, Properties properties)
             throws AttributeDecodingException {
        
         final String id = StringSupport.trimOrNull(properties.getProperty(AttributeTranscoderRegistry.PROP_ID));
diff --git a/idp-conf/src/main/resources/conf/attribute-registry.xml b/idp-conf/src/main/resources/conf/attribute-registry.xml
index e1eafb1..2b3b203 100644
--- a/idp-conf/src/main/resources/conf/attribute-registry.xml
+++ b/idp-conf/src/main/resources/conf/attribute-registry.xml
@@ -25,7 +25,6 @@
     
         <entry key="uid">
             <map>
-				<entry key="type" value-ref="SAML2.Attribute" />
 				<entry key="transcoder" value-ref="SAML2StringTranscoder" />
 				<entry key="name" value="urn:oid:0.9.2342.19200300.100.1.1" />
             </map>
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 b5f1f2a..acd3114 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
@@ -25,18 +25,21 @@ import java.util.function.Function;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import net.shibboleth.idp.attribute.AttributeDecodingException;
 import net.shibboleth.idp.attribute.AttributeEncodingException;
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.IdPRequestedAttribute;
+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.logic.ConstraintViolationException;
 
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
 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 com.google.common.base.Strings;
 
@@ -57,23 +60,31 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
     /** Builder used to construct {@link Attribute} objects. */
     @Nonnull private final SAMLObjectBuilder<Attribute> attributeBuilder;
 
+    /** Builder used to construct {@link RequestedAttribute} objects. */
+    @Nonnull private final SAMLObjectBuilder<RequestedAttribute> reqAttributeBuilder;
+    
     /** Constructor. */
     public AbstractSAML2AttributeTranscoder() {
-        attributeBuilder =
-                (SAMLObjectBuilder<Attribute>) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(
+        attributeBuilder = (SAMLObjectBuilder<Attribute>)
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<Attribute>getBuilderOrThrow(
                         Attribute.TYPE_NAME);
-        if (attributeBuilder == null) {
-            throw new ConstraintViolationException("SAML 2 Attribute builder is unavailable");
-        }
+        reqAttributeBuilder = (SAMLObjectBuilder<RequestedAttribute>)
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<RequestedAttribute>getBuilderOrThrow(
+                        RequestedAttribute.TYPE_NAME);
     }
 
+    /** {@inheritDoc} */
+    @Nonnull public Class<Attribute> getEncodedType() {
+        return Attribute.class;
+    }
     
     /** {@inheritDoc} */
     @Nullable public String getEncodedName(@Nonnull final Properties properties) {
         
         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, properties, Collections.emptyList()));
+            return new NamingFunction().apply(buildAttribute(null, null, Attribute.class, properties,
+                    Collections.emptyList()));
         } catch (final AttributeEncodingException e) {
             return null;
         }
@@ -82,15 +93,28 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
     /** {@inheritDoc} */
     @Override
     @Nonnull protected Attribute buildAttribute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final IdPAttribute attribute, @Nonnull final Properties properties,
-            @Nonnull @NonnullElements final List<XMLObject> attributeValues) throws AttributeEncodingException {
+            @Nonnull final IdPAttribute attribute, @Nonnull final Class<? extends Attribute> to,
+            @Nonnull final Properties properties, @Nonnull @NonnullElements final List<XMLObject> attributeValues)
+                    throws AttributeEncodingException {
 
         final String name = properties.getProperty(PROP_NAME);
         if (Strings.isNullOrEmpty(name)) {
             throw new AttributeEncodingException("Required transcoder property 'name' not found");
         }
-                
-        final Attribute samlAttribute = attributeBuilder.buildObject();
+
+        final Attribute samlAttribute;
+        
+        if (to.equals(Attribute.class)) {
+            samlAttribute = attributeBuilder.buildObject();
+        } else if (to.equals(RequestedAttribute.class)) {
+            samlAttribute = reqAttributeBuilder.buildObject();
+            if (attribute instanceof IdPRequestedAttribute) {
+                ((RequestedAttribute) samlAttribute).setIsRequired(((IdPRequestedAttribute) attribute).getIsRequired());
+            }
+        } else {
+            throw new AttributeEncodingException("Unsupported target object type: " + to.getName());
+        }
+
         samlAttribute.setName(name);
         samlAttribute.setNameFormat(properties.getProperty(PROP_NAME_FORMAT, Attribute.URI_REFERENCE));
         samlAttribute.getAttributeValues().addAll(attributeValues);
@@ -102,6 +126,38 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
         
         return samlAttribute;
     }
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull protected IdPAttribute buildIdPAttribute(
+            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
+            @Nonnull final Properties properties,
+            @Nonnull @NonnullElements final List<IdPAttributeValue<?>> attributeValues)
+                    throws AttributeDecodingException {
+        
+        final String id = properties.getProperty(AttributeTranscoderRegistry.PROP_ID);
+        if (Strings.isNullOrEmpty(id)) {
+            throw new AttributeDecodingException("Required transcoder property 'id' not found");
+        }
+        
+        final IdPAttribute idpAttribute;
+        if (attribute instanceof RequestedAttribute) {
+            idpAttribute = new IdPRequestedAttribute(id);
+            ((IdPRequestedAttribute) idpAttribute).setRequired(((RequestedAttribute) attribute).isRequired());
+        } else {
+            idpAttribute = new IdPAttribute(id);
+        }
+        
+        idpAttribute.setValues(attributeValues);
+        
+        return idpAttribute;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull protected Iterable<XMLObject> getValues(@Nonnull final Attribute input) {
+        return input.getAttributeValues();
+    }
 
     /**
      * A function to produce a "canonical" name for a SAML 2.0 {@link Attribute} for transcoding rules.
@@ -121,7 +177,7 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
             }
             
             final StringBuilder builder = new StringBuilder();
-            builder.append('{').append(format).append('}').append(input.getName());
+            builder.append("SAML2:{").append(format).append('}').append(input.getName());
             return builder.toString();
         }
 
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 eeb12c1..464ef9f 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
@@ -69,8 +69,8 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
         
     /** {@inheritDoc} */
     @Nullable public AttributeType encode(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final IdPAttribute attribute, @Nonnull final Properties properties)
-                    throws AttributeEncodingException {
+            @Nonnull final IdPAttribute attribute, @Nonnull final Class<? extends AttributeType> to,
+            @Nonnull final Properties properties) throws AttributeEncodingException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
         Constraint.isNotNull(attribute, "Attribute to encode cannot be null");
 
@@ -83,15 +83,8 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
         
         log.debug("Beginning to encode attribute {}", attributeId);
 
-        if (attribute.getValues().isEmpty()) {
-            log.warn("Unable to encode {} attribute, contains no values", attributeId);
-            return null;
-        }
-
         final List<XMLObject> samlAttributeValues = new ArrayList<>();
 
-        EncodedType attributeValue;
-        XMLObject samlAttributeValue;
         for (final IdPAttributeValue o : attribute.getValues()) {
             if (o == null) {
                 // filtered out upstream leave in test for sanity
@@ -105,8 +98,9 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
                 continue;
             }
 
-            attributeValue = (EncodedType) o;
-            samlAttributeValue = encodeValue(profileRequestContext, attribute, properties, attributeValue);
+            final EncodedType attributeValue = (EncodedType) o;
+            final XMLObject samlAttributeValue =
+                    encodeValue(profileRequestContext, attribute, properties, attributeValue);
             if (samlAttributeValue == null) {
                 log.debug("Skipping empty value for attribute {}", attributeId);
             } else {
@@ -114,21 +108,46 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
             }
         }
 
-        if (samlAttributeValues.isEmpty()) {
-            log.warn("Attribute {} did not contain any encodable values", attributeId);
-            return null;
-        }
-
         log.debug("Completed encoding {} values for attribute {}", samlAttributeValues.size(), attributeId);
-        return buildAttribute(profileRequestContext, attribute, properties, samlAttributeValues);
+        return buildAttribute(profileRequestContext, attribute, to, properties, samlAttributeValues);
     }
 
     /** {@inheritDoc} */
     @Nullable public IdPAttribute decode(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AttributeType input, @Nonnull final Properties properties)
                     throws AttributeDecodingException {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        Constraint.isNotNull(input, "Attribute to decode cannot be null");
+
+        final String attributeName = getEncodedName(properties);
+
+        if (!getActivationCondition().test(profileRequestContext)) {
+            log.debug("Decoder for attribute {} inactive", attributeName);
+            return null;
+        }
         
-        return null;
+        log.debug("Beginning to decode attribute {}", attributeName);
+
+        final List<IdPAttributeValue<?>> idpAttributeValues = new ArrayList<>();
+        final Iterable<XMLObject> samlAttributeValues = getValues(input);
+
+        for (final XMLObject o : samlAttributeValues) {
+            if (o == null) {
+                // filtered out upstream leave in test for sanity
+                log.debug("Skipping null value of attribute {}", attributeName);
+                continue;
+            }
+
+            final IdPAttributeValue<?> idpAttributeValue = decodeValue(profileRequestContext, input, properties, o);
+            if (idpAttributeValue == null) {
+                log.debug("Unable to decode value of attribute {}", attributeName);
+            } else {
+                idpAttributeValues.add(idpAttributeValue);
+            }
+        }
+
+        log.debug("Completed decoding {} values for attribute {}", idpAttributeValues.size(), attributeName);
+        return buildIdPAttribute(profileRequestContext, input, properties, idpAttributeValues);
     }
 
     /**
@@ -189,12 +208,30 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
 // Checkstyle: CyclomaticComplexity ON
     
     /**
-     * Encodes an attribute value in to a SAML attribute value element.
+     * Builds a SAML attribute element from the given attribute values.
+     * 
+     * @param profileRequestContext current profile request
+     * @param attribute the attribute being encoded
+     * @param to target type to create
+     * @param properties properties to control encoding
+     * @param attributeValues the encoded values for the attribute
+     * 
+     * @return the SAML attribute object
+     * 
+     * @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;
+
+    /**
+     * Encodes an attribute value into a SAML AttributeValue element.
      * 
      * @param profileRequestContext current profile request
      * @param attribute the attribute being encoded
      * @param properties properties to control encoding
-     * @param value the value to encoder
+     * @param value the value to encode
      * 
      * @return the attribute value or null if the resulting attribute value would be empty
      * 
@@ -205,28 +242,45 @@ public abstract class AbstractSAMLAttributeTranscoder<AttributeType extends SAML
             @Nonnull final EncodedType value) throws AttributeEncodingException;
 
     /**
-     * Builds a SAML attribute element from the given attribute values.
+     * Builds an {@link IdPAttribute} from the given values.
      * 
      * @param profileRequestContext current profile request
-     * @param attribute the attribute being encoded
-     * @param properties properties to control encoding
-     * @param attributeValues the encoded values for the attribute, never null or containing null elements
+     * @param attribute the attribute being decoded
+     * @param properties properties to control decoding
+     * @param attributeValues the decoded values for the attribute
      * 
-     * @return the SAML attribute element
+     * @return the IdPAttribute object
      * 
-     * @throws AttributeEncodingException thrown if there is a problem constructing the SAML attribute
+     * @throws AttributeDecodingException thrown if there is a problem constructing the IdPAttribute
      */
-    @Nonnull protected abstract AttributeType buildAttribute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final IdPAttribute attribute, @Nonnull final Properties properties,
-            @Nonnull @NonnullElements final List<XMLObject> attributeValues) throws AttributeEncodingException;
+    @Nonnull protected abstract IdPAttribute buildIdPAttribute(
+            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final AttributeType attribute,
+            @Nonnull final Properties properties,
+            @Nonnull @NonnullElements final List<IdPAttributeValue<?>> attributeValues)
+                    throws AttributeDecodingException;
+    
+    /**
+     * Returns the values to decode from the concrete input object.
+     * 
+     * @param input input object
+     * 
+     * @return values to decode
+     */
+    @Nonnull protected abstract Iterable<XMLObject> getValues(@Nonnull final AttributeType input);
+    
     
     /**
      * Function to decode a single {@link XMLObject} into an {@link IdPAttributeValue}.
      * 
-     * @param object the object to decode
+     * @param profileRequestContext current profile request
+     * @param attribute the attribute being decoded
+     * @param properties properties to control decoding
+     * @param value the value to decode
      * 
      * @return the returned final {@link IdPAttributeValue} or null if decoding failed
      */
-    @Nullable protected abstract IdPAttributeValue<?> decodeValue(@Nullable final XMLObject object);
+    @Nullable protected abstract IdPAttributeValue<?> decodeValue(
+            @Nonnull 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 a63c08d..420c308 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
@@ -33,6 +33,7 @@ import net.shibboleth.idp.saml.attribute.transcoding.AbstractSAML2AttributeTrans
 
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.saml2.core.Attribute;
 import org.opensaml.saml.saml2.core.AttributeValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -70,8 +71,11 @@ public class SAML2StringAttributeTranscoder extends AbstractSAML2AttributeTransc
     }
 
     /** {@inheritDoc} */
-    @Nullable protected IdPAttributeValue<?> decodeValue(@Nullable final XMLObject object) {
-        return StringAttributeValue.valueOf(getStringValue(object));
+    @Override @Nullable protected IdPAttributeValue<?> decodeValue(
+            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
+            @Nonnull final Properties properties, @Nullable final XMLObject value) {
+        
+        return value != null ? StringAttributeValue.valueOf(getStringValue(value)) : null;
     }
     
 }
\ 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