[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