[java-oidc-common] branch main updated: JCOMOIDC-8 Move OIDC attribute handling from the OIDC plugin

Henri Mikkonen henri.mikkonen at iki.fi
Wed Dec 30 12:38:53 UTC 2020


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

hjmikkon pushed a commit to branch main
in repository java-oidc-common.

View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=1699a876b67520e6e432fab2db2e9f80fd5593bc

The following commit(s) were added to refs/heads/main by this push:
       new  1699a87   JCOMOIDC-8 Move OIDC attribute handling from the OIDC plugin
1699a87 is described below

commit 1699a876b67520e6e432fab2db2e9f80fd5593bc
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Dec 30 14:37:43 2020 +0200

    JCOMOIDC-8 Move OIDC attribute handling from the OIDC plugin
    
    https://issues.shibboleth.net/jira/browse/JCOMOIDC-8
    
    Imported transcoders and their unit tests from java-idp-oidc.
---
 oidc-common-attribute-api/pom.xml                  |  40 ++++
 .../AbstractOIDCAttributeTranscoder.java           | 229 ++++++++++++++++++
 .../transcoding/OIDCAttributeTranscoder.java       |  46 ++++
 .../oidc/attribute/transcoding/package-info.java   |  20 ++
 oidc-common-attribute-impl/pom.xml                 |  63 +++++
 .../impl/OIDCByteAttributeTranscoder.java          | 106 +++++++++
 .../impl/OIDCScopedStringAttributeTranscoder.java  | 104 +++++++++
 .../impl/OIDCStringAttributeTranscoder.java        | 208 +++++++++++++++++
 .../attribute/transcoding/impl/package-info.java   |  19 ++
 .../impl/OIDCByteAttributeTranscoderTest.java      | 171 ++++++++++++++
 .../OIDCScopedStringAttributeTranscoderTest.java   | 185 +++++++++++++++
 .../impl/OIDCStringAttributeTranscoderTest.java    | 257 +++++++++++++++++++++
 .../src/test/resources/logback-test.xml            |  19 ++
 pom.xml                                            |  12 +
 14 files changed, 1479 insertions(+)

diff --git a/oidc-common-attribute-api/pom.xml b/oidc-common-attribute-api/pom.xml
new file mode 100644
index 0000000..1e79e55
--- /dev/null
+++ b/oidc-common-attribute-api/pom.xml
@@ -0,0 +1,40 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>net.shibboleth.oidc</groupId>
+        <artifactId>oidc-common-parent</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>oidc-common-attribute-api</artifactId>
+    <packaging>jar</packaging>
+    <name>Shibboleth OIDC Java Common Library - OIDC attribute API</name>
+    <description>Interfaces for common OIDC attributes.</description>
+
+    <properties>
+        <checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
+        <automatic.module.name>net.shibboleth.oidc.attribute.api</automatic.module.name>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>net.shibboleth.idp</groupId>
+            <artifactId>idp-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.shibboleth.idp</groupId>
+            <artifactId>idp-attribute-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opensaml</groupId>
+            <artifactId>opensaml-profile-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>oauth2-oidc-sdk</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/oidc-common-attribute-api/src/main/java/net/shibboleth/oidc/attribute/transcoding/AbstractOIDCAttributeTranscoder.java b/oidc-common-attribute-api/src/main/java/net/shibboleth/oidc/attribute/transcoding/AbstractOIDCAttributeTranscoder.java
new file mode 100644
index 0000000..568d29a
--- /dev/null
+++ b/oidc-common-attribute-api/src/main/java/net/shibboleth/oidc/attribute/transcoding/AbstractOIDCAttributeTranscoder.java
@@ -0,0 +1,229 @@
+/*
+ * 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.oidc.attribute.transcoding;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+import com.nimbusds.openid.connect.sdk.ClaimsRequest;
+import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
+
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.attribute.AttributeDecodingException;
+import net.shibboleth.idp.attribute.AttributeEncodingException;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.IdPRequestedAttribute;
+import net.shibboleth.idp.attribute.transcoding.AbstractAttributeTranscoder;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+
+/**
+ * Abstract class for OIDC attribute encoders.
+ */
+public abstract class AbstractOIDCAttributeTranscoder
+        extends AbstractAttributeTranscoder<JSONObject> implements OIDCAttributeTranscoder {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractOIDCAttributeTranscoder.class);
+
+    /** {@inheritDoc} */
+    public Class<JSONObject> getEncodedType() {
+        return JSONObject.class;
+    }
+
+    /** {@inheritDoc} */
+    @Nullable public String getEncodedName(@Nonnull final TranscodingRule rule) {
+
+        final String name = rule.getOrDefault(PROP_NAME, String.class,
+                rule.get(AttributeTranscoderRegistry.PROP_ID, String.class));
+        if (name != null) {
+            return "OIDC:" + name;
+        }
+        return null;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public JSONObject doEncode(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final Class<? extends JSONObject> to,
+            @Nonnull final TranscodingRule rule) throws AttributeEncodingException {
+
+        final String attributeId = attribute.getId();
+
+        log.trace("Beginning to encode attribute {}", attributeId);
+
+        final Object claimValues = encodeValues(profileRequestContext, attribute, rule);
+        
+        if (claimValues instanceof JSONArray) {
+            log.trace("Encoded {} value(s) for attribute {}", ((JSONArray) claimValues).size(), attributeId);
+        } else {
+            log.trace("Encoded {} value(s) for attribute {}", claimValues != null ? 1 : 0, attributeId);
+        }
+        
+        return buildClaim(profileRequestContext, attribute, rule, claimValues);
+    }
+
+    /**
+     * Builds an OIDC claim structure with the given values.
+     * 
+     * @param profileRequestContext current profile request context
+     * @param attribute the attribute being encoded
+     * @param rule properties to control encoding
+     * @param claimValues the encoded value(s) for the claim
+     * 
+     * @return the OIDC claim (for the moment in the form of a single-valued JSON structure
+     * 
+     * @throws AttributeEncodingException if there's a problem constructing the claim
+     */
+    @Nonnull protected JSONObject buildClaim(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nullable final IdPAttribute attribute, @Nonnull final TranscodingRule rule,
+            @Nonnull @NonnullElements final Object claimValues) throws AttributeEncodingException {
+        
+        if (claimValues == null) {
+            throw new AttributeEncodingException("Unable to build OIDC claim with no value(s)");
+        }
+    
+        final String name = rule.getOrDefault(PROP_NAME, String.class, attribute.getId());
+        
+        final JSONObject claim = new JSONObject();
+        claim.put(name, claimValues);
+        
+        return claim;
+    }
+
+    /**
+     * Performs encoding of {@link IdPAttribute}'s values based on rule into a claim value.
+     * 
+     * @param profileRequestContext current profile request
+     * @param attribute the attribute being encoded
+     * @param rule properties to control encoding
+     *  
+     * @return Integer, Boolean, String, JSON Array or JSON Object value for claim
+     * 
+     * @throws AttributeEncodingException if there's a problem encoding the values
+     */
+    @Nullable protected abstract Object encodeValues(@Nullable final ProfileRequestContext profileRequestContext,
+           @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule)
+                   throws AttributeEncodingException;
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public IdPAttribute doDecode(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final JSONObject input, @Nonnull final TranscodingRule rule)
+                    throws AttributeDecodingException {
+
+        final String attributeName = getEncodedName(rule);
+        
+        log.trace("Beginning to decode claim {}", attributeName);
+
+        final List<IdPAttributeValue> idpAttributeValues =
+                decodeValues(profileRequestContext, input.get(attributeName), rule);
+
+        log.trace("Decoded {} values for claim {}", idpAttributeValues.size(), attributeName);
+        return buildIdPAttribute(profileRequestContext, input, rule, idpAttributeValues);
+    }
+
+    /**
+     * Builds an {@link IdPAttribute} from the given values.
+     * 
+     * @param profileRequestContext current profile request
+     * @param claim the claim being decoded
+     * @param rule properties to control decoding
+     * @param attributeValues the decoded values for the attribute
+     * 
+     * @return the IdPAttribute object
+     * 
+     * @throws AttributeDecodingException if there's a problem constructing the IdPAttribute
+     */
+    @Nonnull protected IdPAttribute buildIdPAttribute(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final JSONObject claim, @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");
+        }
+
+        final String name = rule.getOrDefault(PROP_NAME, String.class, id);
+
+        // This is awful, don't shame me.
+        final Object claimValue = claim.get(name);
+        if (claimValue instanceof ClaimsRequest.Entry) {
+            final IdPRequestedAttribute idpAttribute = new IdPRequestedAttribute(id);
+            idpAttribute.setRequired(ClaimRequirement.ESSENTIAL.equals(
+                    ((ClaimsRequest.Entry) claimValue).getClaimRequirement()));
+            idpAttribute.setValues(attributeValues);
+            return idpAttribute;
+        }
+        
+        final IdPAttribute idpAttribute = new IdPAttribute(id);
+        idpAttribute.setValues(attributeValues);
+        return idpAttribute;
+    }
+    
+    /**
+     * Subclasses should override this method to perform specific claim value decoding into {@link IdPAttributeValue}
+     * objects.
+     * 
+     * @param profileRequestContext current profile request context
+     * @param input the raw claim value
+     * @param rule properties to control decoding
+     * 
+     * @return a possibly empty list of decoded values
+     * 
+     * @throws AttributeDecodingException if there's a problem decoding the claim value
+     */
+    @Nonnull @NonnullElements protected abstract List<IdPAttributeValue> decodeValues(
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Object input,
+            @Nonnull final TranscodingRule rule) throws AttributeDecodingException;
+    
+    
+   /**
+     * A function to produce a "canonical" name for an OIDC claim for transcoding rules.
+     */
+    public static class NamingFunction implements Function<JSONObject,String> {
+
+        /** {@inheritDoc} */
+        @Nullable public String apply(@Nullable final JSONObject input) {
+            
+            if (input == null) {
+                return null;
+            }
+            
+            final Set<String> keys = input.keySet();
+            if (keys.size() != 1) {
+                return null;
+            }
+            
+            return "OIDC:" + keys.iterator().next();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/oidc-common-attribute-api/src/main/java/net/shibboleth/oidc/attribute/transcoding/OIDCAttributeTranscoder.java b/oidc-common-attribute-api/src/main/java/net/shibboleth/oidc/attribute/transcoding/OIDCAttributeTranscoder.java
new file mode 100644
index 0000000..672d487
--- /dev/null
+++ b/oidc-common-attribute-api/src/main/java/net/shibboleth/oidc/attribute/transcoding/OIDCAttributeTranscoder.java
@@ -0,0 +1,46 @@
+/*
+ * 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.oidc.attribute.transcoding;
+
+import javax.annotation.Nonnull;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/**
+ * Marker interface for transcoders that operate on OIDC claims.
+ */
+public interface OIDCAttributeTranscoder extends AttributeTranscoder<JSONObject> {
+
+    /** The claim name. */
+    @Nonnull @NotEmpty static final String PROP_NAME = "oidc.name";
+
+    /** Whether to encode data to JSON array. */
+    @Nonnull @NotEmpty static final String PROP_ASARRAY = "oidc.asArray";
+
+    /** Whether to encode data to JSON integer. */
+    @Nonnull @NotEmpty static final String PROP_ASINTEGER = "oidc.asInteger";
+
+    /** Whether to encode data to JSON integer. */
+    @Nonnull @NotEmpty static final String PROP_ASBOOLEAN = "oidc.asBoolean";
+
+    /** Separator to use when not encoding multiple values to array. */
+    @Nonnull @NotEmpty static final String PROP_STRING_DELIMITER = "oidc.stringDelimiter";
+
+}
\ No newline at end of file
diff --git a/oidc-common-attribute-api/src/main/java/net/shibboleth/oidc/attribute/transcoding/package-info.java b/oidc-common-attribute-api/src/main/java/net/shibboleth/oidc/attribute/transcoding/package-info.java
new file mode 100644
index 0000000..9617499
--- /dev/null
+++ b/oidc-common-attribute-api/src/main/java/net/shibboleth/oidc/attribute/transcoding/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/** OIDC Attribute Transcoding base classes. */
+
+package net.shibboleth.oidc.attribute.transcoding;
\ No newline at end of file
diff --git a/oidc-common-attribute-impl/pom.xml b/oidc-common-attribute-impl/pom.xml
new file mode 100644
index 0000000..9ec53b5
--- /dev/null
+++ b/oidc-common-attribute-impl/pom.xml
@@ -0,0 +1,63 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>net.shibboleth.oidc</groupId>
+        <artifactId>oidc-common-parent</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>oidc-common-attribute-impl</artifactId>
+    <packaging>jar</packaging>    
+    <name>Shibboleth OIDC Java Common Library - OIDC attribute implementation</name>
+    <description>Implementation for common OIDC attribute functions/features.</description>
+    
+    <properties>
+        <checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
+        <automatic.module.name>net.shibboleth.oidc.attribute.impl</automatic.module.name>
+    </properties>
+    
+    <dependencies>
+        <dependency>
+            <groupId>net.shibboleth.oidc</groupId>
+            <artifactId>oidc-common-attribute-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.shibboleth.idp</groupId>
+            <artifactId>idp-attribute-api</artifactId>
+        </dependency>
+        <!-- Provided Dependencies -->
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>net.shibboleth.idp</groupId>
+            <artifactId>idp-profile-api</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.shibboleth.idp</groupId>
+            <artifactId>idp-core</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.shibboleth.idp</groupId>
+            <artifactId>idp-attribute-resolver-spring</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.shibboleth.ext</groupId>
+            <artifactId>spring-extensions</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${spring.groupId}</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCByteAttributeTranscoder.java b/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCByteAttributeTranscoder.java
new file mode 100644
index 0000000..3324f71
--- /dev/null
+++ b/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCByteAttributeTranscoder.java
@@ -0,0 +1,106 @@
+/*
+ * 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.oidc.attribute.transcoding.impl;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.minidev.json.JSONArray;
+import net.shibboleth.idp.attribute.AttributeDecodingException;
+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.transcoding.TranscodingRule;
+import net.shibboleth.oidc.attribute.transcoding.AbstractOIDCAttributeTranscoder;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.codec.Base64Support;
+import net.shibboleth.utilities.java.support.codec.EncodingException;
+
+/**
+ * Class encoding byte attributes to base64 encoded string json object. Name of the attribute will be set as the key.
+ * The string contains base64 coded attribute value. If there are several attribute values they are delimited with
+ * space. The output may be set also to array. The output may also be set to be int instead of b64. In that case each
+ * value (byte[]) is converted to int array and placed into array.
+ */
+public class OIDCByteAttributeTranscoder extends AbstractOIDCAttributeTranscoder {
+
+// Checkstyle: CyclomaticComplexity OFF
+    /** {@inheritDoc} */
+    @Override
+    @Nullable protected Object encodeValues(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule)
+                    throws AttributeEncodingException {
+        
+        final Boolean asInteger = rule.getOrDefault(PROP_ASINTEGER, Boolean.class, false);
+        final Boolean asArray = rule.getOrDefault(PROP_ASARRAY, Boolean.class, false);
+        final String delimiter = rule.getOrDefault(PROP_STRING_DELIMITER, String.class, ""); 
+        
+        String attributeString = "";
+        final JSONArray array = new JSONArray();
+        for (final IdPAttributeValue value : attribute.getValues()) {
+            if (value instanceof ByteAttributeValue && ((ByteAttributeValue) value).getValue() != null) {
+                if (asInteger) {
+                    final JSONArray innerArray = new JSONArray();
+                    for (final byte byteValue : ((ByteAttributeValue) value).getValue()) {
+                        innerArray.add((int) byteValue);
+                    }
+                    // each byte array is converted to JSON int array and placed into JSON array.
+                    array.add(innerArray);
+                } else {
+                    if (attributeString.length() > 0) {
+                        attributeString += delimiter;
+                    }
+                    try {
+                        attributeString +=
+                                Base64Support.encode(((ByteAttributeValue) value).getValue(), Base64Support.UNCHUNKED);
+                    } catch (final EncodingException e) {
+                        //convert exception and throw if any of the values can not be base64 encoded
+                        throw new AttributeEncodingException("Attribtue value could not be base64 encoded",e);
+                    }
+                    if (asArray) {
+                        array.add(attributeString.toString());
+                        attributeString = "";
+                    }
+                }
+            }
+        }
+        if (asArray || asInteger) {
+            return array.size() == 0 ? null : array;
+        } else {
+            return attributeString.toString().isEmpty() ? null : attributeString.toString();
+        }
+    }
+// Checkstyle: CyclomaticComplexity ON
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull @NonnullElements protected List<IdPAttributeValue> decodeValues(
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Object input,
+            @Nonnull final TranscodingRule rule) throws AttributeDecodingException {
+        
+        // TODO: implement value decoding
+        return Collections.emptyList();
+    }
+
+}
\ No newline at end of file
diff --git a/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCScopedStringAttributeTranscoder.java b/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCScopedStringAttributeTranscoder.java
new file mode 100644
index 0000000..fca7618
--- /dev/null
+++ b/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCScopedStringAttributeTranscoder.java
@@ -0,0 +1,104 @@
+/*
+ * 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.oidc.attribute.transcoding.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.minidev.json.JSONArray;
+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.ScopedStringAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.oidc.attribute.transcoding.AbstractOIDCAttributeTranscoder;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/**
+ * Class encoding scoped string attributes to string json object. Name of the attribute will be set as the key. The
+ * string contains attribute value, delimiter and scope catenated. If there are several attribute values they are
+ * delimited with delimiter(space is default) or placed to array.
+ */
+public class OIDCScopedStringAttributeTranscoder extends AbstractOIDCAttributeTranscoder {
+
+    /** Scope delimiter for combining the data. */
+    @Nonnull @NotEmpty public static final String PROP_SCOPE_DELIMITER = "oidc.scopeDelimiter";
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable protected Object encodeValues(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule)
+                    throws AttributeEncodingException {
+        
+        if (attribute.getValues().isEmpty()) {
+            return null;
+        }
+    
+        final String scopeDelimiter = rule.getOrDefault(PROP_SCOPE_DELIMITER, String.class, "@");
+        
+        final List<String> values = attribute.getValues()
+                .stream()
+                .filter(ScopedStringAttributeValue.class::isInstance)
+                .map(ScopedStringAttributeValue.class::cast)
+                .map(v -> v.getValue() + scopeDelimiter + v.getScope())
+                .collect(Collectors.toUnmodifiableList());
+        
+        // Values of arbitrary mapped type placed into JSONArray.
+        if (rule.getOrDefault(PROP_ASARRAY, Boolean.class, false)) {
+            final JSONArray array = new JSONArray();
+            array.addAll(values);
+            return array;
+        }
+        
+        // String concatenation.
+        
+        final String delimiter = rule.getOrDefault(PROP_STRING_DELIMITER, String.class, " ");
+        
+        boolean stringEmpty = true;
+        final StringBuilder attributeString = new StringBuilder();
+        for (final String value : values) {
+            if (!stringEmpty) {
+                attributeString.append(delimiter);
+            } else {
+                stringEmpty = false;
+            }
+            attributeString.append(value);
+        }
+        
+        return attributeString.length() != 0 ? attributeString.toString() : null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull @NonnullElements protected List<IdPAttributeValue> decodeValues(
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Object input,
+            @Nonnull final TranscodingRule rule) throws AttributeDecodingException {
+        
+        // TODO: implement value decoding
+        return Collections.emptyList();
+    }
+
+}
\ No newline at end of file
diff --git a/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCStringAttributeTranscoder.java b/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCStringAttributeTranscoder.java
new file mode 100644
index 0000000..98a11e2
--- /dev/null
+++ b/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCStringAttributeTranscoder.java
@@ -0,0 +1,208 @@
+/*
+ * 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.oidc.attribute.transcoding.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.schema.XSBooleanValue;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicates;
+
+import net.minidev.json.JSONArray;
+import net.minidev.json.parser.JSONParser;
+import net.minidev.json.parser.ParseException;
+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.StringAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.oidc.attribute.transcoding.AbstractOIDCAttributeTranscoder;
+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.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+
+/**
+ * Class encoding string attributes to string json object. Name of the attribute will be set as the key. The string
+ * contains attribute value. If there are several attribute values they are delimited with delimiter(space is default)
+ * or placed to array. If the value is set to be interpreted as int, only values that may be parsed to int are used. In
+ * case output is interpreted as int but not set to array, only the first parsable value is used. If the value is
+ * interpreted as boolean the value is true if string value equals to "true" ignoring the case. If boolean values are
+ * not set to array the first string value is considered to be the result. Finally, the result may be placed to json
+ * Object.
+ */
+public class OIDCStringAttributeTranscoder extends AbstractOIDCAttributeTranscoder {
+
+    /** Whether to parse the values into and out as a JSON object. */
+    @Nonnull @NotEmpty public static final String PROP_ASOBJECT = "oidc.asObject";
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(OIDCStringAttributeTranscoder.class);
+
+    /** {@inheritDoc} */
+// Checkstyle: CyclomaticComplexity OFF
+    @Override
+    @Nullable protected Object encodeValues(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule)
+                    throws AttributeEncodingException {
+        
+        if (attribute.getValues().isEmpty()) {
+            return null;
+        }
+        
+        final List<?> values = transformValues(rule,
+                attribute.getValues()
+                    .stream()
+                    .filter(StringAttributeValue.class::isInstance)
+                    .map(StringAttributeValue.class::cast)
+                    .map(StringAttributeValue::getValue));
+        
+        // Handle String values parsed into JSON.
+        if (rule.getOrDefault(PROP_ASOBJECT, Boolean.class, false)) {
+            if (values.size() == 1) {
+                final Object value = values.get(0);
+                if (!(value instanceof String)) {
+                    throw new AttributeEncodingException("Value for IdPAttribute '" + attribute.getId() +
+                            "' is defined to be parsed as JSON, but is not String-typed");
+                }
+                return toJSONObject((String) value);
+            } else {
+                final JSONArray array = new JSONArray();
+                for (final Object value : values) {
+                    if (!(value instanceof String)) {
+                        log.warn("Value for IdPAttribute '{}' is defined to be parsed as JSON, but is not String-typed",
+                                attribute.getId());
+                        continue;
+                    }
+                    try {
+                        array.add(toJSONObject((String) value));
+                    } catch (final AttributeEncodingException e) {
+                        log.warn("IdPAttribute '{}'", attribute.getId(), e);
+                    }
+                }
+                return array;
+            }
+        }
+        
+        // Values of arbitrary mapped type placed into JSONArray.
+        if (rule.getOrDefault(PROP_ASARRAY, Boolean.class, false)) {
+            final JSONArray array = new JSONArray();
+            array.addAll(values);
+            return array;
+        }
+        
+        // String concatenation / integer or boolean value.
+        final String delimiter = rule.getOrDefault(PROP_STRING_DELIMITER, String.class, " ");
+        
+        boolean stringEmpty = true;
+        final StringBuilder attributeString = new StringBuilder();
+        for (final Object value : values) {
+            if (value instanceof Integer || value instanceof Boolean) {
+                log.warn("Values converted to Integer/Boolean but non-Array encoding, ignoring all but first value");
+                return value;
+            } else if (value instanceof String) {
+                if (!stringEmpty) {
+                    attributeString.append(delimiter);
+                } else {
+                    stringEmpty = false;
+                }
+                attributeString.append(value);
+            } else {
+                throw new AttributeEncodingException("Unrecognized value type: " + value.getClass().getName());
+            }
+        }
+        
+        return attributeString.length() != 0 ? attributeString.toString() : null;
+    }
+// Checkstyle: CyclomaticComplexity ON
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull @NonnullElements protected List<IdPAttributeValue> decodeValues(
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Object input,
+            @Nonnull final TranscodingRule rule) throws AttributeDecodingException {
+        
+        // TODO: implement value decoding
+        return Collections.emptyList();
+    }
+    
+    /**
+     * Parses string as JSONObject.
+     * 
+     * @param value string to parse
+     * 
+     * @return string parsed as JSONObject
+     * 
+     * @throws AttributeEncodingException if parsing fails 
+     */
+    @Nonnull private Object toJSONObject(@Nonnull @NotEmpty final String value) throws AttributeEncodingException {
+        try {
+            return new JSONParser(JSONParser.MODE_PERMISSIVE).parse(value);
+        } catch (final ParseException e) {
+            log.trace("Unable to parse string '{}' into JSONObject", value, e);
+            throw new AttributeEncodingException("Unable to parse string into JSONObject", e);
+        }
+    }
+
+    /**
+     * Converts string values to List of Integer, String or Boolean depending on rule.
+     * 
+     * @param rule transcoding rule
+     * @param attributeValues IdPAttribute values
+     * 
+     * @return list of String, Boolean or Integer objects
+     */
+    @Nonnull @NonnullElements @NotLive @Unmodifiable private List<?> transformValues(
+            @Nonnull final TranscodingRule rule, @Nonnull @NonnullElements final Stream<String> attributeValues) {
+        
+        if (rule.getOrDefault(PROP_ASINTEGER, Boolean.class, false)) {
+            log.debug("String values interpreted as integer by rule");
+            return attributeValues
+                    .map(v -> {
+                        try {
+                            return Integer.parseInt(v);
+                        } catch (final NumberFormatException e) {
+                            log.debug("Value '{}' not a parseable integer", v);
+                            return null;
+                        }
+                    })
+                    .filter(Predicates.notNull())
+                    .collect(Collectors.toUnmodifiableList());
+        }
+        
+        if (rule.getOrDefault(PROP_ASBOOLEAN, Boolean.class, false)) {
+            log.debug("String values interpreted as Boolean by rule");
+            return attributeValues
+                    .map(v -> XSBooleanValue.valueOf(v).getValue())
+                    .collect(Collectors.toUnmodifiableList());
+        }
+
+        return attributeValues.collect(Collectors.toUnmodifiableList());
+    }
+
+}
\ No newline at end of file
diff --git a/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/package-info.java b/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/package-info.java
new file mode 100644
index 0000000..6b43284
--- /dev/null
+++ b/oidc-common-attribute-impl/src/main/java/net/shibboleth/oidc/attribute/transcoding/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+/** Attribute encoder implementations. */
+package net.shibboleth.oidc.attribute.transcoding.impl;
\ No newline at end of file
diff --git a/oidc-common-attribute-impl/src/test/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCByteAttributeTranscoderTest.java b/oidc-common-attribute-impl/src/test/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCByteAttributeTranscoderTest.java
new file mode 100644
index 0000000..b217b8c
--- /dev/null
+++ b/oidc-common-attribute-impl/src/test/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCByteAttributeTranscoderTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.oidc.attribute.transcoding.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import net.shibboleth.ext.spring.testing.MockApplicationContext;
+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.utilities.java.support.codec.Base64Support;
+import net.shibboleth.utilities.java.support.codec.DecodingException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.BasicNamingFunction;
+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.oidc.attribute.transcoding.AbstractOIDCAttributeTranscoder;
+import net.shibboleth.oidc.attribute.transcoding.OIDCAttributeTranscoder;
+import net.shibboleth.oidc.attribute.transcoding.impl.OIDCByteAttributeTranscoder;
+
+public class OIDCByteAttributeTranscoderTest {
+
+    private AttributeTranscoderRegistryImpl registry;
+    private Map<String,Object> ruleset;
+
+    private final static String ATTR_ID = "foo";
+    private final static String ATTR_NAME = "bar";
+
+    @BeforeMethod
+    protected void setUp() throws Exception {
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+                
+        final OIDCByteAttributeTranscoder transcoder = new OIDCByteAttributeTranscoder();
+        transcoder.initialize();
+        
+        registry.setNamingRegistry(Collections.singletonList(
+                new BasicNamingFunction<>(transcoder.getEncodedType(),
+                        new AbstractOIDCAttributeTranscoder.NamingFunction())));
+        
+        ruleset = new HashMap<>();
+        ruleset.put(AttributeTranscoderRegistry.PROP_ID, ATTR_ID);
+        ruleset.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset.put(OIDCAttributeTranscoder.PROP_NAME, ATTR_NAME);
+    }
+    
+    @AfterMethod
+    public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+
+    @Test(expectedExceptions=AttributeEncodingException.class)
+    public void testNoValues() throws ComponentInitializationException, AttributeEncodingException {
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        TranscoderSupport.getTranscoder(tr).encode(null, inputAttribute, JSONObject.class, tr);
+    }
+
+    @Test
+    public void testEncoding() throws ComponentInitializationException, AttributeEncodingException, DecodingException {
+
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> byteAttributeValues = new ArrayList<>();
+        final byte[] bytes = new byte[1];
+        bytes[0] = 0;
+        byteAttributeValues.add(new ByteAttributeValue(bytes));
+        inputAttribute.setValues(byteAttributeValues);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        final JSONObject object = TranscoderSupport.<JSONObject>getTranscoder(tr).encode(
+                null, inputAttribute, JSONObject.class, tr);
+        final String base64Coded = (String) object.get(ATTR_NAME);
+        Assert.assertEquals(bytes, Base64Support.decode(base64Coded));
+    }
+    
+    @Test
+    public void testInteger() throws ComponentInitializationException, AttributeEncodingException {
+        ruleset.put(OIDCAttributeTranscoder.PROP_ASINTEGER, true);
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> byteAttributeValues = new ArrayList<>();
+        final byte[] bytes = new byte[2];
+        bytes[0] = 0;
+        bytes[1] = 1;
+        byteAttributeValues.add(new ByteAttributeValue(bytes));
+        inputAttribute.setValues(byteAttributeValues);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        final JSONObject object = TranscoderSupport.<JSONObject>getTranscoder(tr).encode(
+                null, inputAttribute, JSONObject.class, tr);
+
+        final JSONArray array = (JSONArray)object.get(ATTR_NAME);
+        final JSONArray arrayInts = (JSONArray)array.get(0);
+        
+        Assert.assertEquals(arrayInts.get(0),0);
+        Assert.assertEquals(arrayInts.get(1),1);
+    }
+
+
+    @Test(expectedExceptions=AttributeEncodingException.class)
+    public void testEncodingWrongType() throws ComponentInitializationException, AttributeEncodingException {
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        inputAttribute.setValues(Collections.singletonList(new StringAttributeValue("test")));
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        TranscoderSupport.getTranscoder(tr).encode(null, inputAttribute, JSONObject.class, tr);
+    }
+}
\ No newline at end of file
diff --git a/oidc-common-attribute-impl/src/test/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCScopedStringAttributeTranscoderTest.java b/oidc-common-attribute-impl/src/test/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCScopedStringAttributeTranscoderTest.java
new file mode 100644
index 0000000..2027271
--- /dev/null
+++ b/oidc-common-attribute-impl/src/test/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCScopedStringAttributeTranscoderTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.oidc.attribute.transcoding.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import net.shibboleth.ext.spring.testing.MockApplicationContext;
+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.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.BasicNamingFunction;
+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.oidc.attribute.transcoding.AbstractOIDCAttributeTranscoder;
+import net.shibboleth.oidc.attribute.transcoding.OIDCAttributeTranscoder;
+import net.shibboleth.oidc.attribute.transcoding.impl.OIDCScopedStringAttributeTranscoder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class OIDCScopedStringAttributeTranscoderTest {
+
+    private AttributeTranscoderRegistryImpl registry;
+    private Map<String,Object> ruleset;
+    
+
+    private final static String ATTR_ID = "foo";
+    private final static String ATTR_NAME = "bar";
+    private final static String STRING_1 = "value1";
+    private final static String STRING_2 = "value2";
+    private final static String SCOPE_1 = "scope1";
+    private final static String SCOPE_2 = "scope2";
+    
+    @BeforeMethod
+    protected void setUp() throws Exception {
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+                
+        final OIDCScopedStringAttributeTranscoder transcoder = new OIDCScopedStringAttributeTranscoder();
+        transcoder.initialize();
+        
+        registry.setNamingRegistry(Collections.singletonList(
+                new BasicNamingFunction<>(transcoder.getEncodedType(),
+                        new AbstractOIDCAttributeTranscoder.NamingFunction())));
+        
+        ruleset = new HashMap<>();
+        ruleset.put(AttributeTranscoderRegistry.PROP_ID, ATTR_ID);
+        ruleset.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset.put(OIDCAttributeTranscoder.PROP_NAME, ATTR_NAME);
+    }
+    
+    @AfterMethod
+    public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+    
+    @Test(expectedExceptions=AttributeEncodingException.class)
+    public void testNoValues() throws ComponentInitializationException, AttributeEncodingException {
+        
+        ruleset.put(OIDCAttributeTranscoder.PROP_STRING_DELIMITER, ";");
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        TranscoderSupport.getTranscoder(tr).encode(null, inputAttribute, JSONObject.class, tr);
+    }
+
+    @Test
+    public void testEncoding() throws ComponentInitializationException, AttributeEncodingException {
+
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> stringAttributeValues = new ArrayList<>();
+        
+        stringAttributeValues.add(new ScopedStringAttributeValue(STRING_1, SCOPE_1));
+        stringAttributeValues.add(new ScopedStringAttributeValue(STRING_2, SCOPE_2));
+        inputAttribute.setValues(stringAttributeValues);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final JSONObject object = TranscoderSupport.<JSONObject>getTranscoder(tr).encode(
+                null, inputAttribute, JSONObject.class, tr);
+        
+        final String v = (String) object.get(ATTR_NAME);
+
+        Assert.assertTrue(v.split(" ").length == 2);
+        Assert.assertTrue(v.split(" ")[0].equals(STRING_1 + '@' + SCOPE_1));
+        Assert.assertTrue(v.split(" ")[1].equals(STRING_2 + '@' + SCOPE_2));
+    }
+    
+    @Test
+    public void testEncoding2() throws ComponentInitializationException, AttributeEncodingException {
+
+        ruleset.put(OIDCAttributeTranscoder.PROP_ASARRAY, true);
+        ruleset.put(OIDCScopedStringAttributeTranscoder.PROP_SCOPE_DELIMITER, ":");
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> stringAttributeValues = new ArrayList<>();
+        
+        stringAttributeValues.add(new ScopedStringAttributeValue(STRING_1, SCOPE_1));
+        stringAttributeValues.add(new ScopedStringAttributeValue(STRING_2, SCOPE_2));
+        inputAttribute.setValues(stringAttributeValues);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final JSONObject object = TranscoderSupport.<JSONObject>getTranscoder(tr).encode(
+                null, inputAttribute, JSONObject.class, tr);
+
+        final JSONArray array = (JSONArray) object.get(ATTR_NAME);
+        
+        Assert.assertTrue(array.size() == 2);
+        Assert.assertEquals(array.get(0), STRING_1 + ':' + SCOPE_1);
+        Assert.assertEquals(array.get(1), STRING_2 + ':' + SCOPE_2);
+    }
+
+    @Test(expectedExceptions=AttributeEncodingException.class)
+    public void testEncodingWrongType() throws ComponentInitializationException, AttributeEncodingException {
+
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> byteAttributeValues = new ArrayList<>();
+        final byte[] bytes = new byte[1];
+        bytes[0] = 0;
+        byteAttributeValues.add(new ByteAttributeValue(bytes));
+        inputAttribute.setValues(byteAttributeValues);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        TranscoderSupport.<JSONObject>getTranscoder(tr).encode(null, inputAttribute, JSONObject.class, tr);
+    }
+    
+}
\ No newline at end of file
diff --git a/oidc-common-attribute-impl/src/test/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCStringAttributeTranscoderTest.java b/oidc-common-attribute-impl/src/test/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCStringAttributeTranscoderTest.java
new file mode 100644
index 0000000..00c78cb
--- /dev/null
+++ b/oidc-common-attribute-impl/src/test/java/net/shibboleth/oidc/attribute/transcoding/impl/OIDCStringAttributeTranscoderTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.oidc.attribute.transcoding.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import net.shibboleth.ext.spring.testing.MockApplicationContext;
+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.utilities.java.support.component.ComponentInitializationException;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.BasicNamingFunction;
+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.oidc.attribute.transcoding.AbstractOIDCAttributeTranscoder;
+import net.shibboleth.oidc.attribute.transcoding.OIDCAttributeTranscoder;
+import net.shibboleth.oidc.attribute.transcoding.impl.OIDCStringAttributeTranscoder;
+
+public class OIDCStringAttributeTranscoderTest {
+
+    private AttributeTranscoderRegistryImpl registry;
+    private Map<String,Object> ruleset;
+    
+
+    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";
+
+    @BeforeMethod
+    protected void setUp() throws Exception {
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+                
+        final OIDCStringAttributeTranscoder transcoder = new OIDCStringAttributeTranscoder();
+        transcoder.initialize();
+        
+        registry.setNamingRegistry(Collections.singletonList(
+                new BasicNamingFunction<>(transcoder.getEncodedType(),
+                        new AbstractOIDCAttributeTranscoder.NamingFunction())));
+        
+        ruleset = new HashMap<>();
+        ruleset.put(AttributeTranscoderRegistry.PROP_ID, ATTR_ID);
+        ruleset.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset.put(OIDCAttributeTranscoder.PROP_NAME, ATTR_NAME);
+    }
+    
+    @AfterMethod
+    public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+
+    @Test(expectedExceptions=AttributeEncodingException.class)
+    public void testNoValues() throws ComponentInitializationException, AttributeEncodingException {
+        
+        ruleset.put(OIDCAttributeTranscoder.PROP_STRING_DELIMITER, ";");
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        TranscoderSupport.getTranscoder(tr).encode(null, inputAttribute, JSONObject.class, tr);
+    }
+    
+    @Test
+    public void testEncodingString() throws ComponentInitializationException, AttributeEncodingException {
+        
+        ruleset.put(OIDCAttributeTranscoder.PROP_STRING_DELIMITER, ";");
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> stringAttributeValues = new ArrayList<>();
+        stringAttributeValues.add(new StringAttributeValue(STRING_1));
+        stringAttributeValues.add(new StringAttributeValue(STRING_2));
+        inputAttribute.setValues(stringAttributeValues);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        final JSONObject object = TranscoderSupport.<JSONObject>getTranscoder(tr).encode(
+                null, inputAttribute, JSONObject.class, tr);
+        Assert.assertTrue(((String) object.get(ATTR_NAME)).split(";").length == 2);
+        Assert.assertTrue(((String) object.get(ATTR_NAME)).split(";")[0].equals(STRING_1));
+        Assert.assertTrue(((String) object.get(ATTR_NAME)).split(";")[1].equals(STRING_2));
+    }
+
+    @Test
+    public void testEncodingStringArray() throws ComponentInitializationException, AttributeEncodingException {
+        
+        ruleset.put(OIDCAttributeTranscoder.PROP_ASARRAY, true);
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> stringAttributeValues = new ArrayList<>();
+        stringAttributeValues.add(new StringAttributeValue(STRING_1));
+        stringAttributeValues.add(new StringAttributeValue(STRING_2));
+        inputAttribute.setValues(stringAttributeValues);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        final JSONObject array = TranscoderSupport.<JSONObject>getTranscoder(tr).encode(
+                null, inputAttribute, JSONObject.class, tr);
+        Assert.assertTrue(array.get(ATTR_NAME) instanceof JSONArray);
+        final JSONArray v = (JSONArray) array.get(ATTR_NAME);
+        Assert.assertTrue(v.size() == 2);
+        Assert.assertEquals(STRING_1, v.get(0));
+        Assert.assertEquals(STRING_2, v.get(1));
+    }
+    
+    @Test
+    public void testEncodingInteger() throws ComponentInitializationException, AttributeEncodingException {
+
+        ruleset.put(OIDCAttributeTranscoder.PROP_ASINTEGER, true);
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> stringAttributeValues = new ArrayList<>();
+        stringAttributeValues.add(new StringAttributeValue(STRING_1));
+        stringAttributeValues.add(new StringAttributeValue("2"));
+        inputAttribute.setValues(stringAttributeValues);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        final JSONObject object = TranscoderSupport.<JSONObject>getTranscoder(tr).encode(
+                null, inputAttribute, JSONObject.class, tr);
+
+        Assert.assertEquals(2, object.get(ATTR_NAME));
+    }
+    
+    @Test
+    public void testEncodingBoolean() throws ComponentInitializationException, AttributeEncodingException {
+
+        ruleset.put(OIDCAttributeTranscoder.PROP_ASBOOLEAN, true);
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> stringAttributeValues = new ArrayList<>();
+        stringAttributeValues.add(new StringAttributeValue("true"));
+        stringAttributeValues.add(new StringAttributeValue("2"));
+        inputAttribute.setValues(stringAttributeValues);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        final JSONObject object = TranscoderSupport.<JSONObject>getTranscoder(tr).encode(
+                null, inputAttribute, JSONObject.class, tr);
+        
+        Assert.assertTrue((Boolean) object.get(ATTR_NAME));
+    }
+    
+    @Test
+    public void testEncodingJSONObject() throws ComponentInitializationException, AttributeEncodingException {
+
+        ruleset.put(OIDCStringAttributeTranscoder.PROP_ASOBJECT, true);
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> stringAttributeValues = new ArrayList<>();
+        stringAttributeValues.add(new StringAttributeValue("{ \"name\":\"John\", \"age\":30, \"car\":null }"));
+        inputAttribute.setValues(stringAttributeValues);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final JSONObject object = TranscoderSupport.<JSONObject>getTranscoder(tr).encode(
+                null, inputAttribute, JSONObject.class, tr);
+
+        final JSONObject v = (JSONObject) object.get(ATTR_NAME);
+        
+        Assert.assertEquals("John", v.get("name"));
+        Assert.assertEquals(30, v.get("age"));
+        Assert.assertTrue(v.containsKey("car"));
+    }
+
+    @Test(expectedExceptions=AttributeEncodingException.class)
+    public void testEncodingWrongType() throws ComponentInitializationException, AttributeEncodingException {
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        final List<IdPAttributeValue> byteAttributeValues = new ArrayList<>();
+        final byte[] bytes = new byte[1];
+        bytes[0] = 0;
+        byteAttributeValues.add(new ByteAttributeValue(bytes));
+        inputAttribute.setValues(byteAttributeValues);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, JSONObject.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        TranscoderSupport.<JSONObject>getTranscoder(tr).encode(null, inputAttribute, JSONObject.class, tr);
+    }
+
+}
\ No newline at end of file
diff --git a/oidc-common-attribute-impl/src/test/resources/logback-test.xml b/oidc-common-attribute-impl/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..552c933
--- /dev/null
+++ b/oidc-common-attribute-impl/src/test/resources/logback-test.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<configuration>
+    <logger name="net.shibboleth.oidc" level="ALL"/>
+    <logger name="org.springframework" level="DEBUG"/>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%level [%logger:%line] - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <root>
+        <level value="DEBUG" />
+        <appender-ref ref="STDOUT" />
+    </root>
+    
+</configuration>
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9ae5051..3981d56 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,6 +83,16 @@
     <dependencyManagement>
         <!-- oidc-common project dependencies -->
         <dependencies>
+            <dependency>
+                <groupId>net.shibboleth.oidc</groupId>
+                <artifactId>oidc-common-attribute-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>net.shibboleth.oidc</groupId>
+                <artifactId>oidc-common-attribute-impl</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>net.shibboleth.oidc</groupId>
                 <artifactId>oidc-common-crypto-api</artifactId>
@@ -155,6 +165,8 @@
         </dependencies>
     </dependencyManagement>
     <modules>
+        <module>oidc-common-attribute-api</module>
+        <module>oidc-common-attribute-impl</module>
         <module>oidc-common-crypto-api</module>
         <module>oidc-common-crypto-impl</module>
         <module>oidc-common-metadata-api</module>

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


More information about the commits mailing list