[java-shib-attribute] branch main updated: JSATTR-45 - Port over NameID transcoders from SP in-development work

Scott Cantor cantor.2 at osu.edu
Wed Sep 24 13:54:24 UTC 2025


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

scantor pushed a commit to branch main
in repository java-shib-attribute.

View the commit online:
http://git.shibboleth.net/view/?p=java-shib-attribute.git;a=commit;h=e638ac8dbf01ed64e7c3adb07c6faf9f7be9227c

The following commit(s) were added to refs/heads/main by this push:
     new e638ac8db JSATTR-45 - Port over NameID transcoders from SP in-development work
e638ac8db is described below

commit e638ac8dbf01ed64e7c3adb07c6faf9f7be9227c
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Sep 24 09:54:22 2025 -0400

    JSATTR-45 - Port over NameID transcoders from SP in-development work
    
    https://shibboleth.atlassian.net/browse/JSATTR-45
---
 .../transcoding/AbstractSAML2NameIDTranscoder.java | 224 +++++++++++++++++++++
 .../nameid/transcoding/SAML2NameIDTranscoder.java  |  39 ++++
 .../saml2/nameid/transcoding/package-info.java     |  18 ++
 .../impl/SAML2ScopedStringNameIDTranscoder.java    | 100 +++++++++
 .../impl/SAML2StringNameIDTranscoder.java          |  95 +++++++++
 .../nameid/transcoding/impl/package-info.java      |  18 ++
 .../SAML2ScopedStringNameIDTranscoderTest.java     | 147 ++++++++++++++
 .../impl/SAML2StringNameIDTranscoderTest.java      | 158 +++++++++++++++
 8 files changed, 799 insertions(+)

diff --git a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/AbstractSAML2NameIDTranscoder.java b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/AbstractSAML2NameIDTranscoder.java
new file mode 100644
index 000000000..3e4907239
--- /dev/null
+++ b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/AbstractSAML2NameIDTranscoder.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.saml.saml2.nameid.transcoding;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.NameIDType;
+
+import com.google.common.base.Strings;
+
+import net.shibboleth.idp.attribute.AttributeDecodingException;
+import net.shibboleth.idp.attribute.AttributeEncodingException;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.saml.attribute.transcoding.AbstractSAMLAttributeTranscoder;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+//import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
+//import net.shibboleth.profile.context.navigate.RelyingPartyIdLookupFunction;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Base class for transcoders that operate on a SAML 2 {@link NameID}.
+ * 
+ * <p>For the moment, these are decode only, as the encoding side was already
+ * handled with a dedicated generation service. The registry concept came later.</p>
+ * 
+ * @param <EncodedType> the type of data that can be handled by the transcoder
+ */
+public abstract class AbstractSAML2NameIDTranscoder<EncodedType extends IdPAttributeValue> extends
+        AbstractSAMLAttributeTranscoder<NameID,EncodedType> implements SAML2NameIDTranscoder<EncodedType> {
+    
+    /** Function used to obtain the requester ID. */
+    @NonnullAfterInit private Function<ProfileRequestContext,String> serviceProviderNameLookupStrategy;
+
+    /** Function used to obtain the issuer ID. */
+    @NonnullAfterInit private Function<ProfileRequestContext,String> identityProviderNameLookupStrategy;
+    
+    /** Constructor. */
+    public AbstractSAML2NameIDTranscoder() {
+        //serviceProviderNameLookupStrategy = new IssuerLookupFunction();
+        //identityProviderNameLookupStrategy = new RelyingPartyIdLookupFunction();
+    }
+
+    /**
+     * Set the strategy used to locate the name of the service provider in this transaction.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setServiceProviderNameLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        checkSetterPreconditions();
+        serviceProviderNameLookupStrategy = Constraint.isNotNull(strategy, "SP name lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to locate the name of the identity provider in this transaction.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setIdentityProviderNameLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        checkSetterPreconditions();
+        identityProviderNameLookupStrategy = Constraint.isNotNull(strategy, "IdP name lookup strategy cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (serviceProviderNameLookupStrategy == null || identityProviderNameLookupStrategy == null) {
+            throw new ComponentInitializationException("IdP/SP name lookup strategies cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Nonnull public Class<NameID> getEncodedType() {
+        return NameID.class;
+    }
+    
+    /** {@inheritDoc} */
+    @Nullable public String getEncodedName(@Nonnull final TranscodingRule rule) {
+        final String format = rule.getOrDefault(PROP_NAME_FORMAT, String.class, NameIDType.UNSPECIFIED);
+        return "SAML2:NameID:" + format;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull protected NameID buildAttribute(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nullable final IdPAttribute attribute, @Nonnull final Class<? extends NameID> to,
+            @Nonnull final TranscodingRule rule, @Nonnull final List<XMLObject> attributeValues)
+                    throws AttributeEncodingException {
+
+        throw new AttributeEncodingException("NameID transcoders do not support encoding");
+    }
+        
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull protected IdPAttribute buildIdPAttribute(
+            @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final NameID nameID,
+            @Nonnull final TranscodingRule rule, @Nonnull final List<IdPAttributeValue> attributeValues)
+                    throws AttributeDecodingException {
+        
+        if (nameID.getValue() != null && attributeValues.isEmpty()) {
+            throw new AttributeDecodingException("Failed to decode value for NameID with Format " + nameID.getFormat());
+        }
+        
+        final String id = rule.get(AttributeTranscoderRegistry.PROP_ID, String.class);
+        if (Strings.isNullOrEmpty(id)) {
+            throw new AttributeDecodingException("Required transcoder property 'id' not found");
+        }
+        assert id != null;
+        
+        final IdPAttribute idpAttribute = new IdPAttribute(id);
+        idpAttribute.setValues(attributeValues);
+        return idpAttribute;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull protected Iterable<XMLObject> getValues(@Nonnull final NameID input) {
+        return input.getValue() != null ? CollectionSupport.singletonList(input) : CollectionSupport.emptyList();
+    }
+    
+    /**
+     * Compute the effective NameQualifier to use based on both the input and the transaction.
+     * 
+     * @param profileRequestContext profile request context
+     * @param input input object
+     * @param useDefaultQualifier true iff the qualifier should be defaulted in if not set
+     * 
+     * @return the effective NameQualifier to use
+     */
+    @Nonnull protected String getNameQualifier(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final NameID input, final boolean useDefaultQualifier) {
+        
+        String qual = input.getNameQualifier();
+        if (qual != null) {
+            return qual;
+        }
+        
+        if (useDefaultQualifier) {
+            qual = identityProviderNameLookupStrategy.apply(profileRequestContext);
+            if (qual != null) {
+                return qual;
+            }
+        }
+        
+        return "";
+    }
+
+    /**
+     * Compute the effective SPNameQualifier to use based on both the input and the transaction.
+     * 
+     * @param profileRequestContext profile request context
+     * @param input input object
+     * @param useDefaultQualifier true iff the qualifier should be defaulted in if not set
+     * 
+     * @return the effective NameQualifier to use
+     */
+    @Nonnull protected String getSPNameQualifier(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final NameID input, final boolean useDefaultQualifier) {
+        
+        String qual = input.getSPNameQualifier(); 
+        if (qual != null) {
+            return qual;
+        }
+        
+        if (useDefaultQualifier) {
+            qual = serviceProviderNameLookupStrategy.apply(profileRequestContext);
+            if (qual != null) {
+                return qual;
+            }
+        }
+        
+        return "";
+    }
+
+    /**
+     * A function to produce a "canonical" name for a SAML 2.0 {@link NameID} for transcoding rules.
+     */
+    public static class NamingFunction implements Function<NameID,String> {
+
+        /** {@inheritDoc} */
+        @Nullable public String apply(@Nullable final NameID input) {
+            
+            if (input == null) {
+                return null;
+            }
+        
+            String format = input.getFormat();
+            if (format == null) {
+                format = NameIDType.UNSPECIFIED;
+            }
+            
+            final StringBuilder builder = new StringBuilder();
+            builder.append("SAML2:NameID:").append(format);
+            return builder.toString();
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/SAML2NameIDTranscoder.java b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/SAML2NameIDTranscoder.java
new file mode 100644
index 000000000..a725a2ac8
--- /dev/null
+++ b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/SAML2NameIDTranscoder.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.saml.saml2.nameid.transcoding;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+import org.opensaml.saml.saml2.core.NameID;
+
+/**
+ * Marker interface for transcoders that operate on a SAML 2 {@link NameID}.
+ * 
+ * @param <EncodedType> the type of data that can be handled by the transcoder
+ */
+public interface SAML2NameIDTranscoder<EncodedType extends IdPAttributeValue> extends
+        AttributeTranscoder<NameID> {
+
+    /** The NameID format. */
+    @Nonnull @NotEmpty static final String PROP_NAME_FORMAT = "saml2.nameFormat";
+
+    /** Whether to default in missing qualifiers from the active transaction. */
+    @Nonnull @NotEmpty static final String PROP_DEFAULT_QUALIFIERS = "saml2.defaultQualifiers";
+
+}
\ No newline at end of file
diff --git a/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/package-info.java b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/package-info.java
new file mode 100644
index 000000000..e68314782
--- /dev/null
+++ b/shib-saml-attribute-api/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed 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.
+ */
+
+/**
+ * API classes for SAML 2.0 NameID decoding classes.
+ */
+package net.shibboleth.idp.saml.saml2.nameid.transcoding;
\ No newline at end of file
diff --git a/shib-saml-attribute-impl/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2ScopedStringNameIDTranscoder.java b/shib-saml-attribute-impl/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2ScopedStringNameIDTranscoder.java
new file mode 100644
index 000000000..0a38c46df
--- /dev/null
+++ b/shib-saml-attribute-impl/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2ScopedStringNameIDTranscoder.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.saml.saml2.nameid.transcoding.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.NameIDType;
+
+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.AttributeTranscoder;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.saml.saml2.nameid.transcoding.AbstractSAML2NameIDTranscoder;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * {@link AttributeTranscoder} that supports {@link NameID} and {@link ScopedStringAttributeValue} objects.
+ * 
+ * <p>The scope is defined to be the NameQualifier or the issuing entityID and must exist.</p>
+ */
+public class SAML2ScopedStringNameIDTranscoder extends AbstractSAML2NameIDTranscoder<ScopedStringAttributeValue> {
+
+    /** The decoding template. */
+    @Nonnull @NotEmpty public static final String PROP_TEMPLATE = "saml2.valueTemplate";
+
+    /** The default template. */
+    @Nonnull @NotEmpty public static final String DEFAULT_TEMPLATE = "$Name";
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean canEncodeValue(@Nonnull final IdPAttribute attribute, @Nonnull final IdPAttributeValue value) {
+        return value instanceof ScopedStringAttributeValue;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable protected XMLObject encodeValue(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule,
+            @Nonnull final ScopedStringAttributeValue value) throws AttributeEncodingException {
+
+        throw new AttributeEncodingException("NameID transcoders do not support encoding");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable protected IdPAttributeValue decodeValue(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final NameID nameID, @Nonnull final TranscodingRule rule, @Nullable final XMLObject value) {
+
+        final StringBuilder builder =
+                new StringBuilder(rule.getOrDefault(PROP_TEMPLATE, String.class, DEFAULT_TEMPLATE));
+        
+        
+        final Boolean flag = rule.getOrDefault(PROP_DEFAULT_QUALIFIERS, Boolean.class, false);
+        assert flag != null;
+        
+        int i = builder.indexOf("$Format");
+        if (i >= 0) {
+            final String format = nameID.getFormat();
+            builder.replace(i, i + 7, format != null ? format : NameIDType.UNSPECIFIED);
+        }
+        
+        i = builder.indexOf("$SPNameQualifier");
+        if (i >= 0) {
+            builder.replace(i, i + 16, getSPNameQualifier(profileRequestContext, nameID, flag));
+        }
+
+        i = builder.indexOf("$NameQualifier");
+        if (i >= 0) {
+            builder.replace(i, i + 14, getNameQualifier(profileRequestContext, nameID, flag));
+        }
+
+        i = builder.indexOf("$Name");
+        if (i >= 0) {
+            final String val = nameID.getValue();
+            builder.replace(i, i + 5, val != null ? val : "");
+        }
+        
+        return new ScopedStringAttributeValue(builder.toString(),
+                getNameQualifier(profileRequestContext, nameID, true));
+    }
+    
+}
\ No newline at end of file
diff --git a/shib-saml-attribute-impl/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2StringNameIDTranscoder.java b/shib-saml-attribute-impl/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2StringNameIDTranscoder.java
new file mode 100644
index 000000000..010ac37d6
--- /dev/null
+++ b/shib-saml-attribute-impl/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2StringNameIDTranscoder.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.saml.saml2.nameid.transcoding.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.NameIDType;
+
+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.AttributeTranscoder;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.saml.saml2.nameid.transcoding.AbstractSAML2NameIDTranscoder;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * {@link AttributeTranscoder} that supports {@link NameID} and {@link StringAttributeValue} objects.
+ */
+public class SAML2StringNameIDTranscoder extends AbstractSAML2NameIDTranscoder<StringAttributeValue> {
+
+    /** The decoding template. */
+    @Nonnull @NotEmpty public static final String PROP_TEMPLATE = "saml2.valueTemplate";
+
+    /** The default template. */
+    @Nonnull @NotEmpty public static final String DEFAULT_TEMPLATE = "$Name!!$NameQualifier!!$SPNameQualifier";
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean canEncodeValue(@Nonnull final IdPAttribute attribute, @Nonnull final IdPAttributeValue value) {
+        return value instanceof StringAttributeValue;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable protected XMLObject encodeValue(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule,
+            @Nonnull final StringAttributeValue value) throws AttributeEncodingException {
+
+        throw new AttributeEncodingException("NameID transcoders do not support encoding");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable protected IdPAttributeValue decodeValue(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final NameID nameID, @Nonnull final TranscodingRule rule, @Nullable final XMLObject value) {
+
+        final StringBuilder builder = new StringBuilder(rule.getOrDefault(PROP_TEMPLATE, String.class, DEFAULT_TEMPLATE));
+        
+        final Boolean flag = rule.getOrDefault(PROP_DEFAULT_QUALIFIERS, Boolean.class, false);
+        assert flag != null;
+        
+        int i = builder.indexOf("$Format");
+        if (i >= 0) {
+            final String format = nameID.getFormat();
+            builder.replace(i, i + 7, format != null ? format : NameIDType.UNSPECIFIED);
+        }
+        
+        i = builder.indexOf("$SPNameQualifier");
+        if (i >= 0) {
+            builder.replace(i, i + 16, getSPNameQualifier(profileRequestContext, nameID, flag));
+        }
+
+        i = builder.indexOf("$NameQualifier");
+        if (i >= 0) {
+            builder.replace(i, i + 14, getNameQualifier(profileRequestContext, nameID, flag));
+        }
+
+        i = builder.indexOf("$Name");
+        if (i >= 0) {
+            final String val = nameID.getValue();
+            builder.replace(i, i + 5, val != null ? val : "");
+        }
+        
+        return StringAttributeValue.valueOf(builder.toString());
+    }
+    
+}
\ No newline at end of file
diff --git a/shib-saml-attribute-impl/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/package-info.java b/shib-saml-attribute-impl/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/package-info.java
new file mode 100644
index 000000000..5abbb3126
--- /dev/null
+++ b/shib-saml-attribute-impl/src/main/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed 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.
+ */
+
+/**
+ * SAML 2.0 NameID transcoder implementations.
+ */
+package net.shibboleth.idp.saml.saml2.nameid.transcoding.impl;
\ No newline at end of file
diff --git a/shib-saml-attribute-impl/src/test/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2ScopedStringNameIDTranscoderTest.java b/shib-saml-attribute-impl/src/test/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2ScopedStringNameIDTranscoderTest.java
new file mode 100644
index 000000000..5bc90b537
--- /dev/null
+++ b/shib-saml-attribute-impl/src/test/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2ScopedStringNameIDTranscoderTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.saml.saml2.nameid.transcoding.impl;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.saml.common.SAMLObjectBuilder;
+import org.opensaml.saml.saml2.core.NameID;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.AttributeEncodingException;
+import net.shibboleth.idp.attribute.IdPAttribute;
+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.idp.saml.attribute.transcoding.SAML2AttributeTranscoder;
+import net.shibboleth.idp.saml.saml2.nameid.transcoding.AbstractSAML2NameIDTranscoder;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.testing.MockApplicationContext;
+
+/** {@link SAML2ScopedStringNameIDTranscoder} unit test. */
+ at SuppressWarnings("javadoc")
+public class SAML2ScopedStringNameIDTranscoderTest extends OpenSAMLInitBaseTestCase {
+
+    private AttributeTranscoderRegistryImpl registry;
+
+    private SAMLObjectBuilder<NameID> nameIDBuilder;
+
+    @Nonnull @NotEmpty private final static String ID = "NameIDAttribute";
+    @Nonnull @NotEmpty private final static String NAMEID_FORMAT = "Format";
+    @Nonnull @NotEmpty private final static String STRING_1 = "Value The First";
+    @Nonnull @NotEmpty private final static String STRING_2 = "Second string the value is";
+
+    @BeforeClass public void setUp() throws ComponentInitializationException {
+        nameIDBuilder = (SAMLObjectBuilder<NameID>)
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<NameID>ensureBuilder(
+                        NameID.DEFAULT_ELEMENT_NAME);
+        
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+
+        final SAML2ScopedStringNameIDTranscoder transcoder = new SAML2ScopedStringNameIDTranscoder();
+        transcoder.setIdentityProviderNameLookupStrategy(FunctionSupport.constant(null));
+        transcoder.setServiceProviderNameLookupStrategy(FunctionSupport.constant(null));
+        transcoder.initialize();
+        
+        registry.setNamingRegistry(CollectionSupport.singletonList(
+                new BasicNamingFunction<>(transcoder.getEncodedType(), new AbstractSAML2NameIDTranscoder.NamingFunction())));
+        
+        final Map<String,Object> ruleset1 = new HashMap<>();
+        ruleset1.put(AttributeTranscoderRegistry.PROP_ID, ID);
+        ruleset1.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset1.put(SAML2AttributeTranscoder.PROP_NAME_FORMAT, NAMEID_FORMAT);
+        ruleset1.put(SAML2StringNameIDTranscoder.PROP_TEMPLATE, "$Name");
+        
+        registry.setTranscoderRegistry(CollectionSupport.singletonList(new TranscodingRule(ruleset1)));
+        registry.setApplicationContext(new MockApplicationContext());
+        registry.initialize();
+    }
+    
+    @AfterClass public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+
+    @Test(expectedExceptions=AttributeEncodingException.class)
+    public void failedEncode() throws Exception {
+        final IdPAttribute inputAttribute = new IdPAttribute(ID);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, NameID.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        assert ruleset != null;
+        
+        TranscoderSupport.<NameID>getTranscoder(ruleset).encode(null, inputAttribute, NameID.class, ruleset);
+    }
+
+    @Test public void emptyDecode() throws Exception {
+        
+        final NameID nameID = nameIDBuilder.buildObject();
+        Collection<TranscodingRule> rulesets = registry.getTranscodingRules(nameID);
+        Assert.assertEquals(rulesets.size(), 0);
+        
+        nameID.setFormat(NAMEID_FORMAT);
+
+        rulesets = registry.getTranscodingRules(nameID);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        assert ruleset != null;
+        
+        final IdPAttribute attr = TranscoderSupport.<NameID>getTranscoder(ruleset).decode(null, nameID, ruleset);
+        assert attr != null;
+        Assert.assertEquals(attr.getId(), ID);
+        Assert.assertTrue(attr.getValues().isEmpty());
+    }
+    
+    @Test public void decode() throws Exception {
+                
+        final NameID nameID = nameIDBuilder.buildObject();
+        nameID.setFormat(NAMEID_FORMAT);
+        nameID.setNameQualifier(STRING_2);
+        nameID.setValue(STRING_1);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(nameID);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        assert ruleset != null;
+        
+        final IdPAttribute attr = TranscoderSupport.<NameID>getTranscoder(ruleset).decode(null, nameID, ruleset);
+        
+        assert attr != null;
+        Assert.assertEquals(attr.getId(), ID);
+        Assert.assertEquals(attr.getValues().size(), 1);
+        
+        final ScopedStringAttributeValue value = (ScopedStringAttributeValue) attr.getValues().get(0);
+        Assert.assertEquals(value.getValue(), STRING_1);
+        Assert.assertEquals(value.getScope(), STRING_2);
+    }
+
+}
\ No newline at end of file
diff --git a/shib-saml-attribute-impl/src/test/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2StringNameIDTranscoderTest.java b/shib-saml-attribute-impl/src/test/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2StringNameIDTranscoderTest.java
new file mode 100644
index 000000000..2b012b0e3
--- /dev/null
+++ b/shib-saml-attribute-impl/src/test/java/net/shibboleth/idp/saml/saml2/nameid/transcoding/impl/SAML2StringNameIDTranscoderTest.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.saml.saml2.nameid.transcoding.impl;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.saml.common.SAMLObjectBuilder;
+import org.opensaml.saml.saml2.core.NameID;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.AttributeEncodingException;
+import net.shibboleth.idp.attribute.IdPAttribute;
+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.idp.saml.attribute.transcoding.SAML2AttributeTranscoder;
+import net.shibboleth.idp.saml.saml2.nameid.transcoding.AbstractSAML2NameIDTranscoder;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.testing.MockApplicationContext;
+
+/** {@link SAML2StringNameIDTranscoder} unit test. */
+ at SuppressWarnings("javadoc")
+public class SAML2StringNameIDTranscoderTest extends OpenSAMLInitBaseTestCase {
+
+    private AttributeTranscoderRegistryImpl registry;
+
+    private SAMLObjectBuilder<NameID> nameIDBuilder;
+
+    @Nonnull @NotEmpty private final static String ID = "NameIDAttribute";
+    @Nonnull @NotEmpty private final static String NAMEID_FORMAT = "Format";
+    @Nonnull @NotEmpty private final static String STRING_1 = "Value The First";
+    @Nonnull @NotEmpty private final static String STRING_2 = "Second string the value is";
+
+    @BeforeClass public void setUp() throws ComponentInitializationException {
+        nameIDBuilder = (SAMLObjectBuilder<NameID>)
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<NameID>ensureBuilder(
+                        NameID.DEFAULT_ELEMENT_NAME);
+        
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+
+        final SAML2StringNameIDTranscoder transcoder = new SAML2StringNameIDTranscoder();
+        transcoder.setIdentityProviderNameLookupStrategy(FunctionSupport.constant(null));
+        transcoder.setServiceProviderNameLookupStrategy(FunctionSupport.constant(null));
+        transcoder.initialize();
+        
+        registry.setNamingRegistry(CollectionSupport.singletonList(
+                new BasicNamingFunction<>(transcoder.getEncodedType(), new AbstractSAML2NameIDTranscoder.NamingFunction())));
+        
+        final Map<String,Object> ruleset1 = new HashMap<>();
+        ruleset1.put(AttributeTranscoderRegistry.PROP_ID, ID);
+        ruleset1.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset1.put(SAML2AttributeTranscoder.PROP_NAME_FORMAT, NAMEID_FORMAT);
+        ruleset1.put(SAML2StringNameIDTranscoder.PROP_TEMPLATE, "$Name!!$NameQualifier");
+        
+        registry.setTranscoderRegistry(CollectionSupport.singletonList(new TranscodingRule(ruleset1)));
+        registry.setApplicationContext(new MockApplicationContext());
+        registry.initialize();
+    }
+    
+    @AfterClass public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+
+    @Test(expectedExceptions=AttributeEncodingException.class)
+    public void failedEncode() throws Exception {
+        final IdPAttribute inputAttribute = new IdPAttribute(ID);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, NameID.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        assert ruleset != null;
+        
+        TranscoderSupport.<NameID>getTranscoder(ruleset).encode(null, inputAttribute, NameID.class, ruleset);
+    }
+
+    @Test public void noValue() throws Exception {
+        
+        final NameID nameID = nameIDBuilder.buildObject();
+        Collection<TranscodingRule> rulesets = registry.getTranscodingRules(nameID);
+        Assert.assertEquals(rulesets.size(), 0);
+        
+        nameID.setFormat(NAMEID_FORMAT);
+
+        rulesets = registry.getTranscodingRules(nameID);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        assert ruleset != null;
+        
+        final IdPAttribute attr = TranscoderSupport.<NameID>getTranscoder(ruleset).decode(null, nameID, ruleset);
+        assert attr != null;
+        Assert.assertEquals(attr.getId(), ID);
+        Assert.assertTrue(attr.getValues().isEmpty());
+    }
+
+    @Test public void wrongFormat() throws Exception {
+        
+        final NameID nameID = nameIDBuilder.buildObject();
+        Collection<TranscodingRule> rulesets = registry.getTranscodingRules(nameID);
+        Assert.assertEquals(rulesets.size(), 0);
+        
+        nameID.setFormat("Wrong");
+        nameID.setNameQualifier(STRING_2);
+        nameID.setValue(STRING_1);
+
+        rulesets = registry.getTranscodingRules(nameID);
+        Assert.assertTrue(rulesets.isEmpty());
+    }
+    
+    @Test public void decode() throws Exception {
+                
+        final NameID nameID = nameIDBuilder.buildObject();
+        nameID.setFormat(NAMEID_FORMAT);
+        nameID.setNameQualifier(STRING_2);
+        nameID.setValue(STRING_1);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(nameID);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        assert ruleset != null;
+        
+        final IdPAttribute attr = TranscoderSupport.<NameID>getTranscoder(ruleset).decode(null, nameID, ruleset);
+        
+        assert attr != null;
+        Assert.assertEquals(attr.getId(), ID);
+        Assert.assertEquals(attr.getValues().size(), 1);
+        Assert.assertEquals(((StringAttributeValue)attr.getValues().get(0)).getValue().toString(), STRING_1 + "!!" + STRING_2);
+    }
+
+}
\ No newline at end of file

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


More information about the commits mailing list