[java-identity-provider] branch master updated: IDP-1446 - Create CAS transcoders

Scott Cantor cantor.2 at osu.edu
Tue May 14 19:30:51 EDT 2019


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

scantor pushed a commit to branch master
in repository java-identity-provider.

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

The following commit(s) were added to refs/heads/master by this push:
       new  d6b0e74   IDP-1446 - Create CAS transcoders
d6b0e74 is described below

commit d6b0e7480aab52ce7bf7b59e3349ba1b0d8f838a
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue May 14 19:30:43 2019 -0400

    IDP-1446 - Create CAS transcoders
    
    https://issues.shibboleth.net/jira/browse/IDP-1446
    
    String and ScopeString CAS transcoders.
---
 idp-cas-api/pom.xml                                |  10 +
 .../attribute/AbstractCASAttributeTranscoder.java  | 237 ++++++++++++++++++++
 .../shibboleth/idp/cas/attribute/Attribute.java    |  74 ++++++
 .../idp/cas/attribute/CASAttributeTranscoder.java  |  33 +++
 .../shibboleth/idp/cas/attribute/package-info.java |  22 ++
 .../impl/CASScopedStringAttributeTranscoder.java   |  83 +++++++
 .../impl/CASStringAttributeTranscoder.java         |  73 ++++++
 .../attribute/transcoding/impl/package-info.java   |  22 ++
 .../CASScopedStringAttributeTranscoderTest.java    | 248 +++++++++++++++++++++
 .../impl/CASStringAttributeTranscoderTest.java     | 225 +++++++++++++++++++
 .../system/conf/attribute-registry-system.xml      |   8 +
 11 files changed, 1035 insertions(+)

diff --git a/idp-cas-api/pom.xml b/idp-cas-api/pom.xml
index bb6ef29..df7b5a6 100644
--- a/idp-cas-api/pom.xml
+++ b/idp-cas-api/pom.xml
@@ -23,6 +23,16 @@
     <dependencies>
         <!-- Compile Dependencies -->
         <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>idp-attribute-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>idp-saml-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-profile-api</artifactId>
         </dependency>
diff --git a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/AbstractCASAttributeTranscoder.java b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/AbstractCASAttributeTranscoder.java
new file mode 100644
index 0000000..fa2dc67
--- /dev/null
+++ b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/AbstractCASAttributeTranscoder.java
@@ -0,0 +1,237 @@
+/*
+ * 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.cas.attribute;
+
+import java.util.ArrayList;
+import java.util.List;
+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.transcoding.AbstractAttributeTranscoder;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+
+/**
+ * Base class for transcoders that support CAS attributes.
+ *
+ * @param <EncodedType> the type of data that can be handled by the transcoder
+ */
+public abstract class AbstractCASAttributeTranscoder<EncodedType extends IdPAttributeValue>
+        extends AbstractAttributeTranscoder<Attribute> implements CASAttributeTranscoder {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractCASAttributeTranscoder.class);
+        
+    /** {@inheritDoc} */
+    @Nonnull public Class<Attribute> getEncodedType() {
+        return Attribute.class;
+    }
+    
+    /** {@inheritDoc} */
+    @Nullable public String getEncodedName(@Nonnull final TranscodingRule rule) {
+        
+        // CAS naming should be based on only what needs to be available from the properties alone.
+        final String name = rule.getOrDefault(PROP_NAME, String.class,
+                rule.get(AttributeTranscoderRegistry.PROP_ID, String.class));
+        if (name != null) {
+            return new NamingFunction().apply(new Attribute(name));
+        } else {
+            return null;
+        }
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public Attribute doEncode(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final Class<? extends Attribute> to,
+            @Nonnull final TranscodingRule rule) throws AttributeEncodingException {
+
+        final String attributeId = attribute.getId();
+
+        log.debug("Beginning to encode attribute {}", attributeId);
+
+        final String name = rule.getOrDefault(PROP_NAME, String.class, attributeId);
+        
+        final Attribute casAttribute = new Attribute(name);
+        
+        for (final IdPAttributeValue o : attribute.getValues()) {
+            if (o == null) {
+                // filtered out upstream leave in test for sanity
+                log.debug("Skipping null value of attribute {}", attributeId);
+                continue;
+            }
+
+            if (!canEncodeValue(attribute, o)) {
+                log.warn("Skipping value of attribute '{}'; Type {} cannot be encoded by this encoder ({}).",
+                        attributeId, o.getClass().getSimpleName(), this.getClass().getSimpleName());
+                continue;
+            }
+
+            final EncodedType attributeValue = (EncodedType) o;
+            final String casAttributeValue = encodeValue(profileRequestContext, attribute, rule, attributeValue);
+            if (casAttributeValue == null) {
+                log.debug("Skipping null value for attribute {}", attributeId);
+            } else {
+                casAttribute.getValues().add(casAttributeValue);
+            }
+        }
+        
+        if (!attribute.getValues().isEmpty() && casAttribute.getValues().isEmpty()) {
+            throw new AttributeEncodingException("Failed to encode any values for attribute " + attribute.getId());
+        }
+        
+        log.debug("Encoded {} values for attribute {}", casAttribute.getValues().size(), attributeId);
+        return casAttribute;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public IdPAttribute doDecode(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final Attribute input, @Nonnull final TranscodingRule rule) throws AttributeDecodingException {
+
+        final String attributeName = getEncodedName(rule);
+        
+        log.debug("Beginning to decode attribute {}", attributeName);
+
+        final List<IdPAttributeValue<?>> idpAttributeValues = new ArrayList<>();
+
+        for (final String o : input.getValues()) {
+            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, rule, o);
+            if (idpAttributeValue == null) {
+                log.debug("Unable to decode value of attribute {}", attributeName);
+            } else {
+                idpAttributeValues.add(idpAttributeValue);
+            }
+        }
+
+        if (!idpAttributeValues.isEmpty()) {
+            log.debug("Decoded {} values for attribute {}", idpAttributeValues.size(), attributeName);
+        }
+        return buildIdPAttribute(profileRequestContext, input, rule, idpAttributeValues);
+    }
+
+    /**
+     * 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);
+    
+    /**
+     * Encodes an attribute value into a string.
+     * 
+     * @param profileRequestContext current profile request
+     * @param attribute the attribute being encoded
+     * @param rule properties to control encoding
+     * @param value the value to encode
+     * 
+     * @return the attribute value or null if the resulting attribute value would be empty
+     * 
+     * @throws AttributeEncodingException thrown if there is a problem encoding the attribute value
+     */
+    @Nullable protected abstract String encodeValue(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule,
+            @Nonnull final EncodedType value) throws AttributeEncodingException;
+
+    /**
+     * Builds an {@link IdPAttribute} from the given values.
+     * 
+     * @param profileRequestContext current profile request
+     * @param attribute the attribute being decoded
+     * @param rule properties to control decoding
+     * @param attributeValues the decoded values for the attribute
+     * 
+     * @return the IdPAttribute object
+     * 
+     * @throws AttributeDecodingException thrown if there is a problem constructing the IdPAttribute
+     */
+    @Nonnull protected IdPAttribute buildIdPAttribute(
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
+            @Nonnull final TranscodingRule rule,
+            @Nonnull @NonnullElements final List<IdPAttributeValue<?>> attributeValues)
+                    throws AttributeDecodingException {
+        
+        final String id = rule.get(AttributeTranscoderRegistry.PROP_ID, String.class);
+        if (Strings.isNullOrEmpty(id)) {
+            throw new AttributeDecodingException("Required transcoder property 'id' not found");
+        }
+        
+        if (!((Attribute) attribute).getValues().isEmpty() && attributeValues.isEmpty()) {
+            throw new AttributeDecodingException("Failed to decode any values for attribute " + attribute.getName());
+        }
+        
+        final IdPAttribute idpAttribute = new IdPAttribute(id);
+        idpAttribute.setValues(attributeValues);
+        return idpAttribute;
+    }
+    
+    /**
+     * Function to decode a single string value into an {@link IdPAttributeValue}.
+     * 
+     * @param profileRequestContext current profile request
+     * @param attribute the attribute being decoded
+     * @param rule 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 ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
+            @Nonnull final TranscodingRule rule, @Nullable final String value);
+
+    /** A function to produce a "canonical" name for a CAS {@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;
+            }
+            
+            return "CAS:" + input.getName();
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/Attribute.java b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/Attribute.java
new file mode 100644
index 0000000..7eeacb4
--- /dev/null
+++ b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/Attribute.java
@@ -0,0 +1,74 @@
+/*
+ * 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.cas.attribute;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+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.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Wrapper class for a CAS attribute/values construct in a validate response.
+ */
+public class Attribute {
+
+    /** Name of attribute. */
+    @Nonnull @NotEmpty private String name;
+    
+    /** String values. */
+    @Nonnull @NonnullElements private final Collection<String> values;
+    
+    /**
+     * Constructor.
+     * 
+     * @param theName name of attribute
+     * 
+     */
+    public Attribute(@Nonnull @NotEmpty final String theName) {
+        name = Constraint.isNotNull(StringSupport.trimOrNull(theName),
+                "CAS attribute name cannot be null or empty.");
+        
+        values = new ArrayList<>();
+    }
+    
+    /**
+     * Get the attribute's name.
+     * 
+     * @return the name
+     * 
+     */
+    @Nonnull @NotEmpty public String getName() {
+        return name;
+    }
+    
+    /**
+     * Get string values.
+     * 
+     * @return string value collection
+     */
+    @Nonnull @NonnullElements @Live public Collection<String> getValues() {
+        return values;
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/CASAttributeTranscoder.java b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/CASAttributeTranscoder.java
new file mode 100644
index 0000000..7d69678
--- /dev/null
+++ b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/CASAttributeTranscoder.java
@@ -0,0 +1,33 @@
+/*
+ * 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.cas.attribute;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/**
+ * Marker interface for transcoders that support CAS attributes.
+ */
+public interface CASAttributeTranscoder extends AttributeTranscoder<Attribute> {
+
+    /** The attribute name. */
+    @Nonnull @NotEmpty static final String PROP_NAME = "name";
+
+}
\ No newline at end of file
diff --git a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/package-info.java b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/package-info.java
new file mode 100644
index 0000000..b087d34
--- /dev/null
+++ b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/attribute/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * APIs for CAS attribute support.
+ */
+
+package net.shibboleth.idp.cas.attribute;
\ No newline at end of file
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASScopedStringAttributeTranscoder.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASScopedStringAttributeTranscoder.java
new file mode 100644
index 0000000..8886d48
--- /dev/null
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASScopedStringAttributeTranscoder.java
@@ -0,0 +1,83 @@
+/*
+ * 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.cas.attribute.transcoding.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.attribute.AttributeEncodingException;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.cas.attribute.AbstractCASAttributeTranscoder;
+import net.shibboleth.idp.cas.attribute.Attribute;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link net.shibboleth.idp.attribute.AttributeTranscoder} that supports {@link Attribute} and
+ * {@link ScopedStringAttributeValue} objects.
+ */
+public class CASScopedStringAttributeTranscoder extends AbstractCASAttributeTranscoder<ScopedStringAttributeValue> {
+
+    /** Scope delimiter. */
+    @Nonnull @NotEmpty public static final String PROP_SCOPE_DELIMITER = "scopeDelimiter";
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(CASScopedStringAttributeTranscoder.class);
+
+    /** {@inheritDoc} */
+    @Override protected boolean canEncodeValue(@Nonnull final IdPAttribute attribute,
+            @Nonnull final IdPAttributeValue value) {
+        return value instanceof ScopedStringAttributeValue;
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable protected String encodeValue(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule,
+            @Nonnull final ScopedStringAttributeValue value) throws AttributeEncodingException {
+        
+        final String scopeDelimiter = rule.getOrDefault(PROP_SCOPE_DELIMITER, String.class, "@");
+        return value.getValue() + scopeDelimiter + value.getScope();
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable protected IdPAttributeValue<?> decodeValue(
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
+            @Nonnull final TranscodingRule rule, @Nullable final String value) {
+        
+        if (value == null) {
+            return null;
+        }
+        
+        final String scopeDelimiter = rule.getOrDefault(PROP_SCOPE_DELIMITER, String.class, "@");
+        final int offset = value.indexOf(scopeDelimiter);
+        if (offset < 0) {
+            log.warn("Ignoring value with no scope delimiter ({})", scopeDelimiter);
+            return null;
+        }
+
+        return ScopedStringAttributeValue.valueOf(value.substring(0, offset),
+                value.substring(offset + scopeDelimiter.length()));
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASStringAttributeTranscoder.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASStringAttributeTranscoder.java
new file mode 100644
index 0000000..f958d60
--- /dev/null
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASStringAttributeTranscoder.java
@@ -0,0 +1,73 @@
+/*
+ * 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.cas.attribute.transcoding.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.attribute.AttributeEncodingException;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.LocalizedStringAttributeValue;
+import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.cas.attribute.AbstractCASAttributeTranscoder;
+import net.shibboleth.idp.cas.attribute.Attribute;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link net.shibboleth.idp.attribute.AttributeTranscoder} that supports {@link Attribute} and
+ * {@link StringAttributeValue} objects.
+ */
+public class CASStringAttributeTranscoder extends AbstractCASAttributeTranscoder<StringAttributeValue> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(CASStringAttributeTranscoder.class);
+
+    /** {@inheritDoc} */
+    @Override protected boolean canEncodeValue(@Nonnull final IdPAttribute attribute,
+            @Nonnull final IdPAttributeValue value) {
+        return value instanceof StringAttributeValue;
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable protected String encodeValue(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule,
+            @Nonnull final StringAttributeValue value) throws AttributeEncodingException {
+        
+        if (value instanceof LocalizedStringAttributeValue || value instanceof ScopedStringAttributeValue) {
+            log.warn("Attribute '{}': Lossy encoding of attribute value of type {} to SAML1 String Attribute",
+                    attribute.getId(), value.getClass().getSimpleName());
+        }
+        
+        return value.getValue();
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable protected IdPAttributeValue<?> decodeValue(
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
+            @Nonnull final TranscodingRule rule, @Nullable final String value) {
+        
+        return value != null ? StringAttributeValue.valueOf(value) : null;
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/package-info.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/package-info.java
new file mode 100644
index 0000000..0a7d8e3
--- /dev/null
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Implementation classes for CAS attribute support. 
+ */
+
+package net.shibboleth.idp.cas.attribute.transcoding.impl;
\ No newline at end of file
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASScopedStringAttributeTranscoderTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASScopedStringAttributeTranscoderTest.java
new file mode 100644
index 0000000..4986af5
--- /dev/null
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASScopedStringAttributeTranscoderTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.cas.attribute.transcoding.impl;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+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.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.TranscoderSupport;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
+import net.shibboleth.idp.cas.attribute.AbstractCASAttributeTranscoder;
+import net.shibboleth.idp.cas.attribute.Attribute;
+import net.shibboleth.idp.cas.attribute.CASAttributeTranscoder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/** {@link CASScopedStringAttributeTranscoder} unit test. */
+public class CASScopedStringAttributeTranscoderTest {
+
+    private AttributeTranscoderRegistryImpl registry;
+    
+    private final static String ATTR_ID = "foo";
+    private final static String ATTR_NAME = "bar";
+    private final static String STRING_1 = "Value The First";
+    private final static String STRING_2 = "Second string the value is";
+    private final static String SCOPE_1 = "scope1.example.org";
+    private final static String SCOPE_2 = "scope2";
+    private final static String DELIMITER = "#";
+
+    @BeforeClass public void setUp() throws ComponentInitializationException {
+        
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+                
+        final CASScopedStringAttributeTranscoder transcoder = new CASScopedStringAttributeTranscoder();
+        transcoder.initialize();
+        
+        registry.setNamingRegistry(Collections.singletonMap(transcoder.getEncodedType(),
+                new AbstractCASAttributeTranscoder.NamingFunction()));
+        
+        final Map<String,Object> ruleset1 = new HashMap<>();
+        ruleset1.put(AttributeTranscoderRegistry.PROP_ID, ATTR_ID);
+        ruleset1.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset1.put(CASAttributeTranscoder.PROP_NAME, ATTR_NAME);
+        ruleset1.put(CASScopedStringAttributeTranscoder.PROP_SCOPE_DELIMITER, DELIMITER);
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset1)));
+        
+        registry.initialize();
+    }
+    
+    @AfterClass public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+
+    @Test public void emptyEncode() throws Exception {
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final Attribute attr = TranscoderSupport.<Attribute>getTranscoder(ruleset).encode(
+                null, inputAttribute, Attribute.class, ruleset);
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+        Assert.assertTrue(attr.getValues().isEmpty());
+    }
+
+    @Test public void emptyDecode() throws Exception {
+        
+        final Attribute casAttribute = new Attribute(ATTR_NAME);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(casAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr = TranscoderSupport.getTranscoder(ruleset).decode(null, casAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_ID);
+        Assert.assertTrue(attr.getValues().isEmpty());
+    }
+
+    @Test(expectedExceptions = {AttributeEncodingException.class,}) public void inappropriate() throws Exception {
+        final int[] intArray = {1, 2, 3, 4};
+        final Collection<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_ID);
+        inputAttribute.setValues(values);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        TranscoderSupport.getTranscoder(ruleset).encode(null, inputAttribute, Attribute.class, ruleset);
+    }
+    
+    @Test public void single() throws Exception {
+        final Collection<IdPAttributeValue<?>> values =
+                Arrays.asList(new ByteAttributeValue(new byte[] {1, 2, 3,}),
+                        new ScopedStringAttributeValue(STRING_1, SCOPE_1),
+                        new StringAttributeValue(STRING_1),
+                        new StringAttributeValue(STRING_1 + "@" + SCOPE_1));
+
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        inputAttribute.setValues(values);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final Attribute attr = TranscoderSupport.<Attribute>getTranscoder(ruleset).encode(
+                null, inputAttribute, Attribute.class, ruleset);
+
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+
+        final Collection<String> children = attr.getValues();
+
+        Assert.assertEquals(children.size(), 1, "Encoding one entry");
+
+        final String child = children.iterator().next();
+
+        Assert.assertEquals(child, STRING_1 + DELIMITER + SCOPE_1, "Input equals output");
+    }
+    
+    @Test public void singleDecode() throws Exception {
+        
+        final Attribute casAttribute = new Attribute(ATTR_NAME);
+        casAttribute.getValues().add(STRING_1 + DELIMITER + SCOPE_1);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(casAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr = TranscoderSupport.getTranscoder(ruleset).decode(null, casAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_ID);
+        Assert.assertEquals(attr.getValues().size(), 1);
+        
+        final ScopedStringAttributeValue value = (ScopedStringAttributeValue) attr.getValues().get(0);
+        Assert.assertEquals(value.getValue(), STRING_1);
+        Assert.assertEquals(value.getScope(), SCOPE_1);
+    }
+        
+    @Test public void multi() throws Exception {
+        final Collection<IdPAttributeValue<?>> values =
+                Arrays.asList(new ByteAttributeValue(new byte[] {1, 2, 3,}),
+                        new ScopedStringAttributeValue(STRING_1, SCOPE_1),
+                        new ScopedStringAttributeValue(STRING_2, SCOPE_2));
+
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        inputAttribute.setValues(values);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final Attribute attr = TranscoderSupport.<Attribute>getTranscoder(ruleset).encode(
+                null, inputAttribute, Attribute.class, ruleset);
+
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+
+        final Collection<String> children = attr.getValues();
+        Assert.assertEquals(children.size(), 2, "Encoding 2 entries");
+
+        final Iterator<String> iter = children.iterator();
+        final String child1 = iter.next();
+        final String child2 = iter.next();
+        
+        final String value1 = STRING_1 + DELIMITER + SCOPE_1;
+        final String value2 = STRING_2 + DELIMITER + SCOPE_2;
+
+        Assert.assertTrue(child1.equals(value1) || child1.equals(value2));
+        Assert.assertTrue(child2.equals(value1) || child2.equals(value2));
+    }
+
+    @Test public void multiDecode() throws Exception {
+        
+        final Attribute casAttribute = new Attribute(ATTR_NAME);
+        casAttribute.getValues().add(STRING_1 + DELIMITER + SCOPE_1);
+        casAttribute.getValues().add(STRING_2 + DELIMITER + SCOPE_2);
+        casAttribute.getValues().add(STRING_2 + '@' + SCOPE_2);
+        casAttribute.getValues().add(STRING_2);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(casAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr = TranscoderSupport.getTranscoder(ruleset).decode(null, casAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_ID);
+        Assert.assertEquals(attr.getValues().size(), 2);
+
+        final ScopedStringAttributeValue value1 = (ScopedStringAttributeValue) attr.getValues().get(0);
+        final ScopedStringAttributeValue value2 = (ScopedStringAttributeValue) attr.getValues().get(1);
+        Assert.assertTrue(STRING_1.equals(value1.getValue()) || STRING_1.equals(value2.getValue()));
+        Assert.assertTrue(STRING_2.equals(value1.getValue()) || STRING_2.equals(value2.getValue()));
+        Assert.assertTrue(SCOPE_1.equals(value1.getScope()) || SCOPE_1.equals(value2.getScope()));
+        Assert.assertTrue(SCOPE_2.equals(value1.getScope()) || SCOPE_2.equals(value2.getScope()));
+    }
+
+}
\ No newline at end of file
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASStringAttributeTranscoderTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASStringAttributeTranscoderTest.java
new file mode 100644
index 0000000..aadebaf
--- /dev/null
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASStringAttributeTranscoderTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.cas.attribute.transcoding.impl;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+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.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.TranscoderSupport;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
+import net.shibboleth.idp.cas.attribute.AbstractCASAttributeTranscoder;
+import net.shibboleth.idp.cas.attribute.Attribute;
+import net.shibboleth.idp.cas.attribute.CASAttributeTranscoder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/** {@link CASStringAttributeTranscoder} unit test. */
+public class CASStringAttributeTranscoderTest {
+
+    private AttributeTranscoderRegistryImpl registry;
+    
+    private final static String ATTR_ID = "foo";
+    private final static String ATTR_NAME = "bar";
+    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 {
+        
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+                
+        final CASStringAttributeTranscoder transcoder = new CASStringAttributeTranscoder();
+        transcoder.initialize();
+        
+        registry.setNamingRegistry(Collections.singletonMap(transcoder.getEncodedType(),
+                new AbstractCASAttributeTranscoder.NamingFunction()));
+        
+        final Map<String,Object> ruleset1 = new HashMap<>();
+        ruleset1.put(AttributeTranscoderRegistry.PROP_ID, ATTR_ID);
+        ruleset1.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset1.put(CASAttributeTranscoder.PROP_NAME, ATTR_NAME);
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset1)));
+        
+        registry.initialize();
+    }
+    
+    @AfterClass public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+
+    @Test public void emptyEncode() throws Exception {
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final Attribute attr = TranscoderSupport.<Attribute>getTranscoder(ruleset).encode(
+                null, inputAttribute, Attribute.class, ruleset);
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+        Assert.assertTrue(attr.getValues().isEmpty());
+    }
+
+    @Test public void emptyDecode() throws Exception {
+        
+        final Attribute casAttribute = new Attribute(ATTR_NAME);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(casAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr = TranscoderSupport.getTranscoder(ruleset).decode(null, casAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_ID);
+        Assert.assertTrue(attr.getValues().isEmpty());
+    }
+
+    @Test(expectedExceptions = {AttributeEncodingException.class,}) public void inappropriate() throws Exception {
+        final int[] intArray = {1, 2, 3, 4};
+        final Collection<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_ID);
+        inputAttribute.setValues(values);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        TranscoderSupport.getTranscoder(ruleset).encode(null, inputAttribute, Attribute.class, ruleset);
+    }
+    
+    @Test public void single() throws Exception {
+        final Collection<IdPAttributeValue<?>> values =
+                Arrays.asList(new ByteAttributeValue(new byte[] {1, 2, 3,}), new StringAttributeValue(STRING_1));
+
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        inputAttribute.setValues(values);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final Attribute attr = TranscoderSupport.<Attribute>getTranscoder(ruleset).encode(
+                null, inputAttribute, Attribute.class, ruleset);
+
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+
+        final Collection<String> children = attr.getValues();
+
+        Assert.assertEquals(children.size(), 1, "Encoding one entry");
+
+        final String child = children.iterator().next();
+
+        Assert.assertEquals(child, STRING_1);
+    }
+    
+    @Test public void singleDecode() throws Exception {
+        
+        final Attribute casAttribute = new Attribute(ATTR_NAME);
+        casAttribute.getValues().add(STRING_1);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(casAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr = TranscoderSupport.getTranscoder(ruleset).decode(null, casAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_ID);
+        Assert.assertEquals(attr.getValues().size(), 1);
+        Assert.assertEquals(attr.getValues().get(0).getValue().toString(), STRING_1);
+    }
+        
+    @Test public void multi() throws Exception {
+        final Collection<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_ID);
+        inputAttribute.setValues(values);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final Attribute attr = TranscoderSupport.<Attribute>getTranscoder(ruleset).encode(
+                null, inputAttribute, Attribute.class, ruleset);
+
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+
+        final Collection<String> children = attr.getValues();
+        Assert.assertEquals(children.size(), 3, "Encoding three entries");
+
+        for (final String child: children) {
+            Assert.assertTrue(STRING_1.equals(child)||STRING_2.equals(child));
+        }
+    }
+
+    @Test public void multiDecode() throws Exception {
+        
+        final Attribute casAttribute = new Attribute(ATTR_NAME);
+        casAttribute.getValues().add(STRING_1);
+        casAttribute.getValues().add(STRING_2);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(casAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr = TranscoderSupport.getTranscoder(ruleset).decode(null, casAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_ID);
+        Assert.assertEquals(attr.getValues().size(), 2);
+        Assert.assertEquals(attr.getValues().get(0).getValue().toString(), STRING_1);
+        Assert.assertEquals(attr.getValues().get(1).getValue().toString(), STRING_2);
+    }
+
+}
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/system/conf/attribute-registry-system.xml b/idp-conf/src/main/resources/system/conf/attribute-registry-system.xml
index cf21b80..ddde56a 100644
--- a/idp-conf/src/main/resources/system/conf/attribute-registry-system.xml
+++ b/idp-conf/src/main/resources/system/conf/attribute-registry-system.xml
@@ -30,6 +30,11 @@
     <bean id="SAML1XMLObjectTranscoder"
         class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML1XMLObjectAttributeTranscoder" />
     
+    <bean id="CASStringTranscoder"
+        class="net.shibboleth.idp.cas.attribute.transcoding.impl.CASStringAttributeTranscoder" />
+    <bean id="CASScopedStringTranscoder"
+        class="net.shibboleth.idp.cas.attribute.transcoding.impl.CASScopedStringAttributeTranscoder" />
+    
     <util:map id="DefaultNamingRegistry">
         <entry key="#{T(org.opensaml.saml.saml2.core.Attribute)}">
             <bean class="net.shibboleth.idp.saml.attribute.transcoding.AbstractSAML2AttributeTranscoder.NamingFunction" />
@@ -37,6 +42,9 @@
         <entry key="#{T(org.opensaml.saml.saml1.core.AttributeDesignator)}">
             <bean class="net.shibboleth.idp.saml.attribute.transcoding.AbstractSAML1AttributeTranscoder.NamingFunction" />
         </entry>
+        <entry key="#{T(net.shibboleth.idp.cas.attribute.Attribute)}">
+            <bean class="net.shibboleth.idp.cas.attribute.AbstractCASAttributeTranscoder.NamingFunction" />
+        </entry>
     </util:map>
     
     <bean id="shibboleth.TranscodingRule"

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list