[java-identity-provider] 02/27: Some initial working code.

Scott Cantor cantor.2 at osu.edu
Fri May 3 14:31:52 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=5aa3440a28b6a48f8b9db6d06641f4f5a38b9731

commit 5aa3440a28b6a48f8b9db6d06641f4f5a38b9731
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Apr 8 22:14:51 2019 -0400

    Some initial working code.
---
 .../attribute/transcoding/AttributeTranscoder.java |  12 +
 .../transcoding/AttributeTranscoderRegistry.java   |   7 +-
 .../impl/AttributeTranscoderRegistryImpl.java      | 227 +++++++-------
 .../impl/AttributeTranscoderRegistryTest.java      |  60 ----
 .../impl/AttributeTranscoderRegistryTest.java      | 335 +++++++++++++++++++++
 .../src/main/resources/conf/attribute-registry.xml |  25 +-
 .../AbstractSAML2AttributeTranscoder.java          |  39 +++
 7 files changed, 525 insertions(+), 180 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 fed32e9..2572616 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
@@ -29,6 +29,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import net.shibboleth.idp.attribute.AttributeDecodingException;
 import net.shibboleth.idp.attribute.AttributeEncodingException;
 import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 
 /**
  * Transcoders are objects that support both attribute encoding and decoding for bidirectional
@@ -47,6 +48,17 @@ import net.shibboleth.idp.attribute.IdPAttribute;
 public interface AttributeTranscoder<T> {
 
     /**
+     * Get the name of the encoded object that would be created by a given set of
+     * instructions.
+     * 
+     * @param properties properties governing the encoding process
+     * 
+     * @return a canonical name for objects produced by this transcoder for the
+     *  given instructions
+     */
+    @Nullable @NotEmpty String getEncodedName(@Nonnull final Properties properties);
+    
+    /**
      * Encode the supplied attribute into a protocol specific representation.
      * 
      * @param profileRequestContext current profile request context
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 6e8728d..756c0ab 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
@@ -38,6 +38,12 @@ import net.shibboleth.utilities.java.support.component.IdentifiedComponent;
 @ThreadSafe
 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";
     
@@ -55,7 +61,6 @@ public interface AttributeTranscoderRegistry extends IdentifiedComponent {
      */
     @Nonnull @NonnullElements @Unmodifiable
     Collection<Properties> getTranscodingProperties(@Nonnull final IdPAttribute from, @Nonnull final Class<?> to);
-    
 
     /**
      * Obtains a set of instructions for decoding an input object into an {@link IdPAttribute}.
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 a5391f7..6216fab 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
@@ -21,21 +21,18 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Properties;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.concurrent.ThreadSafe;
 
 import net.shibboleth.ext.spring.service.AbstractServiceableComponent;
-import net.shibboleth.idp.attribute.AttributeDecoder;
-import net.shibboleth.idp.attribute.AttributeEncoder;
 import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
 import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
-import net.shibboleth.utilities.java.support.annotation.ParameterName;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
-import net.shibboleth.utilities.java.support.collection.ClassToInstanceMultiMap;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
@@ -43,6 +40,12 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Predicates;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
+
 /** Service implementation of the {@link AttributeTranscoderRegistry} interface. */
 @ThreadSafe
 public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponent<AttributeTranscoderRegistry>
@@ -51,25 +54,16 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AttributeTranscoderRegistryImpl.class);
     
-    /** Registry of encoders for a given attribute ID and type of encoder. */
-    @Nonnull private final Map<String,ClassToInstanceMultiMap<AttributeEncoder>> attributeEncoders;
-
-    /** Registry of decoders for a given object "name" and type of decoder. */
-    @Nonnull private final Map<String,ClassToInstanceMultiMap<AttributeDecoder>> attributeDecoders;
+    /** Registry of transcoding instructions for a given "name" and type of object. */
+    @Nonnull private final Map<String,Multimap<Class<?>,Properties>> transcodingRegistry;
     
-    /** Registry of transcoder types and naming for supported object types. */
-    @Nonnull private final Map<Class<?>,TypeInfo> typeInfoRegistry;
+    /** Registry of naming functions for supported object types. */
+    @Nonnull private final Map<Class<?>,Function<?,String>> namingFunctionRegistry;
 
-    /**
-     * Constructor.
-     * 
-     * @param id ID of this service
-     */
-    public AttributeTranscoderRegistryImpl(@Nonnull @NotEmpty final String id) {
-        setId(id);
-        attributeEncoders = new HashMap<>();
-        attributeDecoders = new HashMap<>();
-        typeInfoRegistry = new HashMap<>();
+    /** Constructor. */
+    public AttributeTranscoderRegistryImpl() {
+        transcodingRegistry = new HashMap<>();
+        namingFunctionRegistry = new HashMap<>();
     }
     
     /** {@inheritDoc} */
@@ -77,111 +71,124 @@ public class AttributeTranscoderRegistryImpl extends AbstractServiceableComponen
         return this;
     }
 
-    public void setTypeRegistry(@Nonnull @NonnullElements Map<Class<?>,TypeInfo<?>> registry) {
+    /**
+     * Installs registry of naming functions mapped against the types of objects they support.
+     * 
+     * @param registry map of types to naming functions
+     */
+    public void setNamingRegistry(@Nonnull @NonnullElements final Map<Class<?>,Function<?,String>> registry) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        if (registry == null) {
+            namingFunctionRegistry.clear();
+            return;
+        }
         
+        registry.forEach((k,v) -> {
+            if (k != null && v != null) {
+                namingFunctionRegistry.put(k, v);
+            }
+        });
     }
 
-    public void setTranscoderRegistry(@Nonnull @NonnullElements Map<String,Collection<?>> registry) {
+    /**
+     * Install the transcoder mappings en masse.
+     * 
+     * <p>Each map entry connects an {@link IdPAttribute} name to the rules for transcoding to/from it.</p>
+     * 
+     * @param registry mappings from internal name to transcoding rules
+     */
+    public void setTranscoderRegistry(@Nonnull @NonnullElements final Map<String,Collection<Properties>> registry) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        if (registry == null) {
+            transcodingRegistry.clear();
+            return;
+        }
         
+        for (final Map.Entry<String,Collection<Properties>> entry : registry.entrySet()) {
+            
+            final String internalId = StringSupport.trimOrNull(entry.getKey());
+            if (internalId != null && entry.getValue() != null && !entry.getValue().isEmpty()) {
+
+                for (final Properties props : Collections2.filter(entry.getValue(), Predicates.notNull())) {
+
+                    final Object type = props.get(PROP_TYPE);
+                    final Object transcoder = props.get(PROP_TRANSCODER);
+                    
+                    if (type instanceof Class && transcoder instanceof AttributeTranscoder) {
+                        final String targetName = ((AttributeTranscoder) transcoder).getEncodedName(props);
+                        if (targetName != null) {
+                            
+                            final Properties copy = new Properties();
+                            copy.putAll(props);
+                            
+                            // Install mapping back to IdPAttribute's name.
+                            copy.setProperty(PROP_ID, internalId);
+                            
+                            Multimap<Class<?>,Properties> rulesetsForIdPName = transcodingRegistry.get(internalId);
+                            if (rulesetsForIdPName == null) {
+                                rulesetsForIdPName = ArrayListMultimap.create();
+                                transcodingRegistry.put(internalId, rulesetsForIdPName);
+                            }
+                            
+                            rulesetsForIdPName.put((Class) type, copy);
+
+                            Multimap<Class<?>,Properties> rulesetsForEncodedName = transcodingRegistry.get(targetName);
+                            if (rulesetsForEncodedName == null) {
+                                rulesetsForEncodedName = ArrayListMultimap.create();
+                                transcodingRegistry.put(targetName, rulesetsForEncodedName);
+                            }
+                            
+                            rulesetsForEncodedName.put((Class) type, copy);
+                            
+                        } else {
+                            log.warn("Transcoding rule for {} into type {} did not produce an encoded name",
+                                    internalId, ((Class) type).getName());
+                        }
+                    } else {
+                        log.warn("Transcoding rule for {} missing or invalid {} or {} properties", internalId,
+                                PROP_TYPE, PROP_TRANSCODER);
+                    }
+                }
+            }
+        }
     }
     
     /** {@inheritDoc} */
-    @Nonnull @NonnullElements @Unmodifiable
-    public <T> Collection<AttributeEncoder<T>> getEncoders(@Nonnull final IdPAttribute from,
-            @Nonnull final Class<T> to) {
+    @Nonnull @NonnullElements @Unmodifiable public Collection<Properties> getTranscodingProperties(
+            @Nonnull final IdPAttribute from, @Nonnull final Class<?> to) {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        Constraint.isNotNull(from, "IdPAttribute cannot be null");
+        Constraint.isNotNull(to, "Target type cannot be null");
         
-        final TypeInfo<T> typeInfo = typeInfoRegistry.get(to);
-        if (typeInfo == null) {
-            log.warn("Unsupported object type: {}", to.getName());
-            return Collections.emptyList();
-        }
+        final Multimap<Class<?>,Properties> propertyCollections = transcodingRegistry.get(from.getId());
         
-        final ClassToInstanceMultiMap<AttributeEncoder> encoders = attributeEncoders.get(from.getId());
-                
-        return encoders != null ? encoders.get(typeInfo.getEncoderType()) : Collections.emptyList();
+        return propertyCollections != null ? ImmutableList.copyOf(propertyCollections.get(to))
+                : Collections.emptyList();
     }
 
     /** {@inheritDoc} */
-    @Nonnull @NonnullElements @Unmodifiable
-    public <T> Collection<AttributeDecoder<T>> getDecoders(@Nonnull final T from) {
+    @Nonnull @NonnullElements @Unmodifiable public <T> Collection<Properties> getTranscodingProperties(
+            @Nonnull final T from) {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        Constraint.isNotNull(from, "Input object cannot be null");
         
-        final TypeInfo<T> typeInfo = typeInfoRegistry.get(from.getClass());
-        if (typeInfo == null) {
+        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);
+            if (id != null) {
+                final Multimap<Class<?>,Properties> propertyCollections = transcodingRegistry.get(id);
+                
+                return propertyCollections != null ? ImmutableList.copyOf(propertyCollections.get(from.getClass()))
+                        : Collections.emptyList();
+            } else {
+                log.warn("Object of type {} did not have a canonical name", from.getClass().getName());
+            }
+        } else {
             log.warn("Unsupported object type: {}", from.getClass().getName());
-            return Collections.emptyList();
-        }
-        
-        final String id = StringSupport.trimOrNull(typeInfo.getNamingFunction().apply(from));
-        if (id == null) {
-            log.warn("Object of type {} did not have a canonical name", from.getClass().getName());
-            return Collections.emptyList();
         }
         
-        final ClassToInstanceMultiMap<AttributeDecoder> decoders = attributeDecoders.get(id);
-        
-        return decoders != null ? decoders.get(typeInfo.getDecoderType()) : Collections.emptyList();
-    }
-
-    /**
-     * Metadata connecting data types to naming functions and codec types.
-     * 
-     * @param <T> object type
-     */
-    public static class TypeInfo<T> {
-        
-        /** Function to derive a canonical name. */
-        @Nonnull private final Function<T,String> namingFunction;
-        
-        /** Type of encoder. */
-        @Nonnull private final Class<AttributeEncoder<T>> encoderType;
-        
-        /** Type of decoder. */
-        @Nonnull private final Class<AttributeDecoder<T>> decoderType;
-        
-        /**
-         * Constructor.
-         *
-         * @param naming canonical naming function
-         * @param encoder encoder type
-         * @param decoder decoder type
-         */
-        public TypeInfo(@Nonnull @ParameterName(name="naming") final Function<T,String> naming,
-                @Nonnull @ParameterName(name="encoder") final Class<AttributeEncoder<T>> encoder,
-                @Nonnull @ParameterName(name="decoder") final Class<AttributeDecoder<T>> decoder) {
-            
-            namingFunction = Constraint.isNotNull(naming, "Naming function cannot be null");
-            encoderType = Constraint.isNotNull(encoder, "Encoder type cannot be null");
-            decoderType = Constraint.isNotNull(decoder, "Decoder type cannot be null");
-        }
-        
-        /**
-         * Gets the function deriving a canonical name for an object.
-         * 
-         * @return function deriving a canonical name for an object
-         */
-        @Nonnull public Function<T,String> getNamingFunction() {
-            return namingFunction;
-        }
-
-        /**
-         * Gets the type of encoder supporting an object.
-         * 
-         * @return type of encoder supporting an object
-         */
-        @Nonnull public Class<AttributeEncoder<T>> getEncoderType() {
-            return encoderType;
-        }
-
-        /**
-         * Gets the type of decoder supporting an object.
-         * 
-         * @return type of decoder supporting an object
-         */
-        @Nonnull public Class<AttributeDecoder<T>> getDecoderType() {
-            return decoderType;
-        }
+        return Collections.emptyList();
     }
-
+    
 }
\ No newline at end of file
diff --git a/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/AttributeTranscoderRegistryTest.java b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/AttributeTranscoderRegistryTest.java
deleted file mode 100644
index 6429bc8..0000000
--- a/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/AttributeTranscoderRegistryTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.attribute.impl;
-
-import java.util.Collection;
-
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import net.shibboleth.idp.attribute.AttributeDecoder;
-import net.shibboleth.idp.attribute.AttributeDecodingException;
-import net.shibboleth.idp.attribute.AttributeEncoder;
-import net.shibboleth.idp.attribute.AttributeEncodingException;
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
-import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
-
-/**
- * Test for {@link AttributeTranscoderRegistry}.
- */
-public class AttributeTranscoderRegistryTest {
-
-
-    @Test void testEncode() throws AttributeEncodingException {
-        
-        final AttributeTranscoderRegistry registry = new AttributeTranscoderRegistryImpl("test");
-        
-        final IdPAttribute foo = new IdPAttribute("foo");
-        
-        final Collection<AttributeEncoder<String>> encoders = registry.getEncoders(foo, String.class);
-        
-        final String s = encoders.iterator().next().encode(foo);
-    }
-   
-    @Test void testDecode() throws AttributeDecodingException {
-        
-        final AttributeTranscoderRegistry registry = new AttributeTranscoderRegistryImpl("test");
-        
-        final String foo = new String("foo");
-        
-        final Collection<AttributeDecoder<String>> decoders = registry.getDecoders(foo);
-        
-        final IdPAttribute a = decoders.iterator().next().decode(foo);
-    }
-}
\ 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
new file mode 100644
index 0000000..02b58a9
--- /dev/null
+++ b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/transcoding/impl/AttributeTranscoderRegistryTest.java
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.transcoding.impl;
+
+import java.util.ArrayList;
+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 org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+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}.
+ */
+public class AttributeTranscoderRegistryTest {
+    
+    private AttributeTranscoderRegistryImpl registry;
+    
+    @BeforeMethod public void setUp() throws ComponentInitializationException {
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+        
+        registry.setNamingRegistry(Collections.singletonMap(
+                Pair.class, (Pair p) -> "{Pair}" + p.getFirst().toString()));
+        
+        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, Pair.class);
+        ruleset2.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset2.setProperty("name", "baz");
+        
+        mappings.put("foo", Arrays.asList(ruleset1, ruleset2));
+        mappings.put("foo2", Collections.singletonList(ruleset2));
+        
+        registry.setTranscoderRegistry(mappings);
+        
+        registry.initialize();
+    }
+    
+    @AfterMethod public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+
+
+    // Test no mappings to encode IdPAttribute.
+    @Test public void testEncodeNoMappings() throws AttributeEncodingException {
+        
+        Assert.assertTrue(registry.getTranscodingProperties(new IdPAttribute("frobnitz"), Pair.class).isEmpty());
+        Assert.assertTrue(registry.getTranscodingProperties(new IdPAttribute("foo"), String.class).isEmpty());
+}
+
+    // Test no reverse mappings from a Pair/String to an IdPAttribute
+    @Test public void testDecodeNoMappings() throws AttributeDecodingException {
+        
+        Assert.assertTrue(registry.getTranscodingProperties(new Pair("foo", "value")).isEmpty());
+        Assert.assertTrue(registry.getTranscodingProperties(new String("foo")).isEmpty());
+    }
+    
+    @Test public void testEncodeNoValues() throws AttributeEncodingException {
+        final IdPAttribute foo = new IdPAttribute("foo");
+        
+        final List<Pair<String,Object>> pairs = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(foo, Pair.class)) {
+            final AttributeTranscoder<Pair<String,Object>> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            pairs.add(t.encode(null, foo, ruleset));
+        }
+        
+        Assert.assertEquals(pairs.size(), 2);
+        
+        Assert.assertEquals(pairs.get(0).getFirst(), "bar");
+        Assert.assertNull(pairs.get(0).getSecond());
+        
+        Assert.assertEquals(pairs.get(1).getFirst(), "baz");
+        Assert.assertNull(pairs.get(1).getSecond());
+    }
+
+    @Test public void testDecodeOneNoValues() throws AttributeDecodingException {
+        
+        final Pair<String,Object> bar = new Pair("bar", null);
+        
+        final List<IdPAttribute> attributes = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(bar)) {
+            final AttributeTranscoder<Pair<String,Object>> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            attributes.add(t.decode(null, bar, ruleset));
+        }
+        
+        Assert.assertEquals(attributes.size(), 1);
+        
+        Assert.assertEquals(attributes.get(0).getId(), "foo");
+        Assert.assertTrue(attributes.get(0).getValues().isEmpty());
+    }
+
+    @Test public void testDecodeTwoNoValues() throws AttributeDecodingException {
+        
+        final Pair<String,Object> baz = new Pair("baz", null);
+        
+        final List<IdPAttribute> attributes = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(baz)) {
+            final AttributeTranscoder<Pair<String,Object>> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            attributes.add(t.decode(null, baz, ruleset));
+        }
+        
+        Assert.assertEquals(attributes.size(), 2);
+        
+        Assert.assertEquals(attributes.get(0).getId(), "foo");
+        Assert.assertTrue(attributes.get(0).getValues().isEmpty());
+
+        Assert.assertEquals(attributes.get(1).getId(), "foo2");
+        Assert.assertTrue(attributes.get(1).getValues().isEmpty());
+    }
+
+    @Test public void testEncodeStringValues() throws AttributeEncodingException {
+        final IdPAttribute foo = new IdPAttribute("foo");
+        foo.setValues(Collections.singletonList(StringAttributeValue.valueOf("value")));
+        
+        final List<Pair<String,Object>> pairs = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(foo, Pair.class)) {
+            final AttributeTranscoder<Pair<String,Object>> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            pairs.add(t.encode(null, foo, 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 testDecodeOneStringValues() throws AttributeDecodingException {
+        
+        final Pair<String,Object> bar = new Pair("bar", "value");
+        
+        final List<IdPAttribute> attributes = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(bar)) {
+            final AttributeTranscoder<Pair<String,Object>> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            attributes.add(t.decode(null, bar, ruleset));
+        }
+        
+        Assert.assertEquals(attributes.size(), 1);
+        
+        Assert.assertEquals(attributes.get(0).getId(), "foo");
+        Assert.assertEquals(attributes.get(0).getValues().get(0).getValue(), "value");
+    }
+    
+    @Test public void testDecodeTwoStringValues() throws AttributeDecodingException {
+        
+        final Pair<String,Object> baz = new Pair("baz", "value");
+        
+        final List<IdPAttribute> attributes = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(baz)) {
+            final AttributeTranscoder<Pair<String,Object>> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            attributes.add(t.decode(null, baz, ruleset));
+        }
+        
+        Assert.assertEquals(attributes.size(), 2);
+        
+        Assert.assertEquals(attributes.get(0).getId(), "foo");
+        Assert.assertEquals(attributes.get(0).getValues().get(0).getValue(), "value");
+
+        Assert.assertEquals(attributes.get(1).getId(), "foo2");
+        Assert.assertEquals(attributes.get(1).getValues().get(0).getValue(), "value");
+    }
+
+    @Test public void testEncodeUnsupportedValues() throws AttributeEncodingException {
+        final IdPAttribute foo = new IdPAttribute("foo");
+        foo.setValues(Collections.singletonList(EmptyAttributeValue.ZERO_LENGTH));
+        
+        final List<Pair<String,Object>> pairs = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(foo, Pair.class)) {
+            final AttributeTranscoder<Pair<String,Object>> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            pairs.add(t.encode(null, foo, ruleset));
+        }
+        
+        Assert.assertEquals(pairs.size(), 2);
+        
+        Assert.assertEquals(pairs.get(0).getFirst(), "bar");
+        Assert.assertNull(pairs.get(0).getSecond());
+        
+        Assert.assertEquals(pairs.get(1).getFirst(), "baz");
+        Assert.assertNull(pairs.get(0).getSecond());
+    }
+    
+    @Test public void testDecodeOneUnsupportedValues() throws AttributeDecodingException {
+        
+        final Pair<String,Object> bar = new Pair("bar", 0L);
+        
+        final List<IdPAttribute> attributes = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(bar)) {
+            final AttributeTranscoder<Pair<String,Object>> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            attributes.add(t.decode(null, bar, ruleset));
+        }
+        
+        Assert.assertEquals(attributes.size(), 1);
+        
+        Assert.assertEquals(attributes.get(0).getId(), "foo");
+        Assert.assertTrue(attributes.get(0).getValues().isEmpty());
+    }
+    
+    @Test public void testDecodeTwoUnsupportedValues() throws AttributeDecodingException {
+        
+        final Pair<String,Object> baz = new Pair("baz", 0L);
+        
+        final List<IdPAttribute> attributes = new ArrayList<>();
+        
+        for (final Properties ruleset : registry.getTranscodingProperties(baz)) {
+            final AttributeTranscoder<Pair<String,Object>> t =
+                    (AttributeTranscoder) ruleset.get(AttributeTranscoderRegistry.PROP_TRANSCODER);            
+            attributes.add(t.decode(null, baz, ruleset));
+        }
+        
+        Assert.assertEquals(attributes.size(), 2);
+        
+        Assert.assertEquals(attributes.get(0).getId(), "foo");
+        Assert.assertTrue(attributes.get(0).getValues().isEmpty());
+
+        Assert.assertEquals(attributes.get(1).getId(), "foo2");
+        Assert.assertTrue(attributes.get(1).getValues().isEmpty());
+    }
+    
+    private static 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;
+        }
+
+        /** {@inheritDoc} */
+        protected boolean canEncodeValue(IdPAttribute idpAttribute, IdPAttributeValue value) {
+            return value instanceof StringAttributeValue;
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/conf/attribute-registry.xml b/idp-conf/src/main/resources/conf/attribute-registry.xml
index e1e8eaf..e1eafb1 100644
--- a/idp-conf/src/main/resources/conf/attribute-registry.xml
+++ b/idp-conf/src/main/resources/conf/attribute-registry.xml
@@ -25,18 +25,25 @@
     
         <entry key="uid">
             <map>
-                <entry>
-                    <key>
-                        <ref>SAML2.Attribute</ref>
-                    </key>
-                    <map>
-                        <entry key="transcoder" ref="SAML2StringTranscoder" />
-                        <entry key="name" value="urn:oid:0.9.2342.19200300.100.1.1" />
-                    </map>
-                </entry>
+				<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>
         </entry>
             
     </util:map>
+    
+    <util:map id="shibboleth.DefaultNamingRegistry">
+        <entry>
+            <key>
+                <ref bean="SAML2.Attribute" />
+            </key>
+            <bean class="net.shibboleth.idp.saml.attribute.transcoding.AbstractSAML2AttributeTranscoder.NamingFunction" />
+        </entry>
+    </util:map>
 
+    <bean class="net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl"
+        p:namingRegistry-ref="shibboleth.DefaultNamingRegistry"
+        p:transcoderRegistry-ref="shibboleth.DefaultAttributeRegistry" />
+    
 </beans>
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 d32b635..b5f1f2a 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
@@ -17,10 +17,13 @@
 
 package net.shibboleth.idp.saml.attribute.transcoding;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Properties;
+import java.util.function.Function;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import net.shibboleth.idp.attribute.AttributeEncodingException;
 import net.shibboleth.idp.attribute.IdPAttribute;
@@ -64,6 +67,18 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
         }
     }
 
+    
+    /** {@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()));
+        } catch (final AttributeEncodingException e) {
+            return null;
+        }
+    }
+    
     /** {@inheritDoc} */
     @Override
     @Nonnull protected Attribute buildAttribute(@Nonnull final ProfileRequestContext profileRequestContext,
@@ -88,4 +103,28 @@ public abstract class AbstractSAML2AttributeTranscoder<EncodedType extends IdPAt
         return samlAttribute;
     }
 
+    /**
+     * A function to produce a "canonical" name for a SAML 2.0 {@link Attribute} for transcoding rules.
+     */
+    public static class NamingFunction implements Function<Attribute,String> {
+
+        /** {@inheritDoc} */
+        @Nullable public String apply(@Nullable final Attribute input) {
+            
+            if (input == null || input.getName() == null) {
+                return null;
+            }
+            
+            String format = input.getNameFormat();
+            if (format == null) {
+                format = Attribute.UNSPECIFIED;
+            }
+            
+            final StringBuilder builder = new StringBuilder();
+            builder.append('{').append(format).append('}').append(input.getName());
+            return builder.toString();
+        }
+
+    }
+
 }
\ 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