[java-identity-provider] branch maint-4 updated: IDP-1992 - Add a DateTimeAttributeValue type

Scott Cantor cantor.2 at osu.edu
Tue Aug 9 14:25:15 UTC 2022


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

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

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

The following commit(s) were added to refs/heads/maint-4 by this push:
     new e3e45b670 IDP-1992 - Add a DateTimeAttributeValue type
e3e45b670 is described below

commit e3e45b67015e9eb4bb54105e3160b90e7aa0a107
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Aug 9 10:25:12 2022 -0400

    IDP-1992 - Add a DateTimeAttributeValue type
    
    https://shibboleth.atlassian.net/browse/IDP-1992
    
    CAS transcoder
    Continue to fix up null handling
    Add SAML 2 encoder to schema and Spring parser
---
 .../impl/SAML2DateTimeAttributeEncoderParser.java  |  45 +++++
 .../impl/AttributeResolverNamespaceHandler.java    |   6 +-
 .../SAML2DateTimeAttributeEncoderParserTest.java   |  70 ++++++++
 .../idp/attribute/resolver/spring/customBean.xml   |   2 +
 .../resolver/spring/enc/resolver/saml2DateTime.xml |  15 ++
 .../spring/enc/resolver/saml2DateTimeDefault.xml   |   8 +
 .../spring/enc/resolver/saml2DateTimeNoName.xml    |  12 ++
 .../impl/CASDateTimeAttributeTranscoder.java       |  84 +++-------
 .../impl/CASDateTimeAttributeTranscoderTest.java   | 183 +++++++++++++++++++++
 .../idp/conf/attribute-registry-system.xml         |   2 +
 .../impl/SAML2DateTimeAttributeTranscoder.java     |  14 +-
 .../impl/SAML2DateTimeAttributeTranscoderTest.java |  26 +++
 .../schema/shibboleth-attribute-resolver.xsd       |  42 +++--
 13 files changed, 434 insertions(+), 75 deletions(-)

diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/enc/impl/SAML2DateTimeAttributeEncoderParser.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/enc/impl/SAML2DateTimeAttributeEncoderParser.java
new file mode 100644
index 000000000..60b9b4180
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/enc/impl/SAML2DateTimeAttributeEncoderParser.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.spring.enc.impl;
+
+import javax.annotation.Nonnull;
+import javax.xml.namespace.QName;
+
+import org.springframework.beans.factory.config.BeanReference;
+import org.springframework.beans.factory.config.RuntimeBeanReference;
+
+import net.shibboleth.idp.attribute.resolver.spring.enc.BaseSAML2AttributeEncoderParser;
+import net.shibboleth.idp.attribute.resolver.spring.impl.AttributeResolverNamespaceHandler;
+import net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2DateTimeAttributeTranscoder;
+
+/**
+ * Spring Bean Definition Parser for {@link SAML2DateTimeAttributeTranscoder}.
+ */
+public class SAML2DateTimeAttributeEncoderParser extends BaseSAML2AttributeEncoderParser {
+
+    /** Schema type name.. */
+    @Nonnull public static final QName TYPE_NAME_RESOLVER = new QName(AttributeResolverNamespaceHandler.NAMESPACE, 
+            "SAML2DateTime");
+
+    /** {@inheritDoc} */
+    @Override
+    protected BeanReference buildTranscoder() {
+        return new RuntimeBeanReference("SAML2DateTimeTranscoder");
+    }
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java
index be674ee5c..27a59aec9 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java
@@ -52,6 +52,7 @@ import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML1ScopedStringAt
 import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML1StringAttributeEncoderParser;
 import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML1XMLObjectAttributeEncoderParser;
 import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML2Base64AttributeEncoderParser;
+import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML2DateTimeAttributeEncoderParser;
 import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML2ScopedStringAttributeEncoderParser;
 import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML2StringAttributeEncoderParser;
 import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML2XMLObjectAttributeEncoderParser;
@@ -141,7 +142,10 @@ public class AttributeResolverNamespaceHandler extends BaseSpringNamespaceHandle
         registerBeanDefinitionParser(SAML2StringAttributeEncoderParser.TYPE_NAME_RESOLVER,
                 new SAML2StringAttributeEncoderParser());
 
-         registerBeanDefinitionParser(SAML2XMLObjectAttributeEncoderParser.TYPE_NAME_RESOLVER,
+        registerBeanDefinitionParser(SAML2DateTimeAttributeEncoderParser.TYPE_NAME_RESOLVER,
+                new SAML2DateTimeAttributeEncoderParser());
+
+        registerBeanDefinitionParser(SAML2XMLObjectAttributeEncoderParser.TYPE_NAME_RESOLVER,
                 new SAML2XMLObjectAttributeEncoderParser());
     }
     // Checkstyle: MethodLength ON
diff --git a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/enc/SAML2DateTimeAttributeEncoderParserTest.java b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/enc/SAML2DateTimeAttributeEncoderParserTest.java
new file mode 100644
index 000000000..c0052c01e
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/enc/SAML2DateTimeAttributeEncoderParserTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.spring.enc;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.function.Predicate;
+
+import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.resolver.spring.testing.BaseEncoderDefinitionParserTest;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.saml.attribute.transcoding.SAML2AttributeTranscoder;
+import net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2DateTimeAttributeTranscoder;
+
+/**
+ * Test for {@link SAML2DateTimeAttributeEncoderParser}.
+ */
+ at SuppressWarnings("javadoc")
+public class SAML2DateTimeAttributeEncoderParserTest extends BaseEncoderDefinitionParserTest {
+  
+    protected void testWithProperties(final boolean activation, final Boolean encodeType) {
+        
+        final Map<String,Object> rule =
+                getAttributeTranscoderRule("resolver/saml2DateTime.xml", activation, encodeType).getMap();
+
+        assertTrue(rule.get(AttributeTranscoderRegistry.PROP_TRANSCODER) instanceof SAML2DateTimeAttributeTranscoder);
+        assertEquals(rule.get(SAML2AttributeTranscoder.PROP_NAME), "Saml2DateTime_ATTRIBUTE_NAME");
+        assertEquals(rule.get(SAML2AttributeTranscoder.PROP_NAME_FORMAT), "Saml2DateTime_ATTRIBUTE_NAME_FORMAT");
+        assertEquals(rule.get(SAML2AttributeTranscoder.PROP_FRIENDLY_NAME), "Saml2DateTime_ATTRIBUTE_FRIENDLY_NAME");
+        assertEquals(activation, ((Predicate<?>) rule.get(AttributeTranscoderRegistry.PROP_CONDITION)).test(null));
+        checkEncodeType(rule, SAML2AttributeTranscoder.PROP_ENCODE_TYPE, encodeType!=null ? encodeType : false);
+    }
+    
+    @Test public void defaultCase() {
+        final Map<String,Object> rule = getAttributeTranscoderRule("resolver/saml2DateTimeDefault.xml").getMap();
+
+        assertTrue(rule.get(AttributeTranscoderRegistry.PROP_TRANSCODER) instanceof SAML2DateTimeAttributeTranscoder);
+        assertEquals(rule.get(SAML2AttributeTranscoder.PROP_NAME), "Saml2DateTimeName");
+        assertNull(rule.get(SAML2AttributeTranscoder.PROP_NAME_FORMAT));
+        assertNull(rule.get(SAML2AttributeTranscoder.PROP_FRIENDLY_NAME));
+        assertFalse(((Predicate<?>) rule.get(AttributeTranscoderRegistry.PROP_CONDITION)).test(null));
+        checkEncodeType(rule, SAML2AttributeTranscoder.PROP_ENCODE_TYPE, true);
+    }
+    
+    @Test(expectedExceptions={BeanDefinitionStoreException.class,})  public void noName() {
+        getAttributeTranscoderRule("resolver/saml2DateTimeNoName.xml");
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/customBean.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/customBean.xml
index 19a1bb35e..f75f53321 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/customBean.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/customBean.xml
@@ -35,6 +35,8 @@
         class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2ByteAttributeTranscoder" />
     <bean id="SAML2XMLObjectTranscoder"
         class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2XMLObjectAttributeTranscoder" />
+    <bean id="SAML2DateTimeTranscoder"
+        class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2DateTimeAttributeTranscoder" />
         
     <bean id="SAML1StringTranscoder"
         class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML1StringAttributeTranscoder" />
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/enc/resolver/saml2DateTime.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/enc/resolver/saml2DateTime.xml
new file mode 100644
index 000000000..ed9e57263
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/enc/resolver/saml2DateTime.xml
@@ -0,0 +1,15 @@
+<AttributeEncoder 
+    xsi:type="SAML2DateTime"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+	xmlns="urn:mace:shibboleth:2.0:resolver"
+	name="Saml2DateTime_ATTRIBUTE_NAME" 
+	nameFormat="Saml2DateTime_ATTRIBUTE_NAME_FORMAT"
+	friendlyName="Saml2DateTime_ATTRIBUTE_FRIENDLY_NAME"
+	encodeType="%{the.encodeType.property:false}"
+	xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd">
+
+    <ActivationConditionScript>
+        <Script>%{the.activation.property}</Script>
+    </ActivationConditionScript>
+    
+</AttributeEncoder>
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/enc/resolver/saml2DateTimeDefault.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/enc/resolver/saml2DateTimeDefault.xml
new file mode 100644
index 000000000..b722f8795
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/enc/resolver/saml2DateTimeDefault.xml
@@ -0,0 +1,8 @@
+<AttributeEncoder 
+    xsi:type="SAML2DateTime"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+	xmlns="urn:mace:shibboleth:2.0:resolver"
+	name="Saml2DateTimeName"
+	activationConditionRef="shibboleth.Predicate"
+	xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd" /> 
+ 
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/enc/resolver/saml2DateTimeNoName.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/enc/resolver/saml2DateTimeNoName.xml
new file mode 100644
index 000000000..8a58bdd61
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/enc/resolver/saml2DateTimeNoName.xml
@@ -0,0 +1,12 @@
+<resolver:AttributeEncoder 
+    xsi:type="SAML2DateTime"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+	xmlns:enc="urn:mace:shibboleth:2.0:attribute:encoder" 
+	xmlns:resolver="urn:mace:shibboleth:2.0:resolver"
+	xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd
+                        urn:mace:shibboleth:2.0:resolver:pc http://shibboleth.net/schema/idp/shibboleth-attribute-resolver-pc.xsd
+                        urn:mace:shibboleth:2.0:resolver:ad http://shibboleth.net/schema/idp/shibboleth-attribute-resolver-ad.xsd
+                        urn:mace:shibboleth:2.0:resolver:dc http://shibboleth.net/schema/idp/shibboleth-attribute-resolver-dc.xsd
+                        urn:mace:shibboleth:2.0:attribute:encoder http://shibboleth.net/schema/idp/shibboleth-attribute-encoder.xsd
+                        urn:mace:shibboleth:2.0:security http://shibboleth.net/schema/idp/shibboleth-security.xsd" /> 
+ 
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoder.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASDateTimeAttributeTranscoder.java
similarity index 64%
copy from idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoder.java
copy to idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASDateTimeAttributeTranscoder.java
index dc992f261..dd8e38576 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoder.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASDateTimeAttributeTranscoder.java
@@ -15,21 +15,14 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.saml.attribute.transcoding.impl;
+package net.shibboleth.idp.cas.attribute.transcoding.impl;
 
 import java.time.Instant;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import org.opensaml.core.xml.XMLObject;
-import org.opensaml.core.xml.schema.XSAny;
-import org.opensaml.core.xml.schema.XSDateTime;
-import org.opensaml.core.xml.schema.XSInteger;
-import org.opensaml.core.xml.schema.XSString;
 import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.saml.saml2.core.Attribute;
-import org.opensaml.saml.saml2.core.AttributeValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -40,24 +33,23 @@ import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
 import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
 import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
-import net.shibboleth.idp.saml.attribute.transcoding.AbstractSAML2AttributeTranscoder;
-import net.shibboleth.idp.saml.attribute.transcoding.SAMLEncoderSupport;
+import net.shibboleth.idp.cas.attribute.AbstractCASAttributeTranscoder;
+import net.shibboleth.idp.cas.attribute.Attribute;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.xml.DOMTypeSupport;
 
 /**
- * {@link AttributeTranscoder} that supports {@link Attribute} and {@link DateTimeAttributeValue} objects.
- * 
- * @since 4.3.0
+ * {@link AttributeTranscoder} that supports {@link Attribute} and
+ * {@link DateTimeAttributeValue} objects.
  */
-public class SAML2DateTimeAttributeTranscoder extends AbstractSAML2AttributeTranscoder<DateTimeAttributeValue> {
+public class CASDateTimeAttributeTranscoder extends AbstractCASAttributeTranscoder<DateTimeAttributeValue> {
 
     /** One of "ms" or "s", controlling the unit to use when converting to an epoch. */
-    @Nonnull @NotEmpty public static final String PROP_EPOCH_UNITS = "saml2.epochUnits";
+    @Nonnull @NotEmpty public static final String PROP_EPOCH_UNITS = "cas.epochUnits";
 
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(SAML2DateTimeAttributeTranscoder.class);
-    
+    @Nonnull private final Logger log = LoggerFactory.getLogger(CASDateTimeAttributeTranscoder.class);
+
     /** {@inheritDoc} */
     @Override protected boolean canEncodeValue(@Nonnull final IdPAttribute attribute,
             @Nonnull final IdPAttributeValue value) {
@@ -65,20 +57,17 @@ public class SAML2DateTimeAttributeTranscoder extends AbstractSAML2AttributeTran
     }
 
     /** {@inheritDoc} */
-    @Override @Nullable protected XMLObject encodeValue(@Nullable final ProfileRequestContext profileRequestContext,
+    @Override @Nullable protected String encodeValue(@Nullable final ProfileRequestContext profileRequestContext,
             @Nonnull final IdPAttribute attribute, @Nonnull final TranscodingRule rule,
             @Nonnull final DateTimeAttributeValue value) throws AttributeEncodingException {
         
-        final Boolean encodeType = rule.getOrDefault(PROP_ENCODE_TYPE, Boolean.class, Boolean.TRUE);
-        
-        return SAMLEncoderSupport.encodeDateTimeValue(attribute, AttributeValue.DEFAULT_ELEMENT_NAME, value.getValue(),
-                encodeType);
+        return DOMTypeSupport.instantToString(value.getValue());
     }
 
     /** {@inheritDoc} */
     @Override @Nullable protected IdPAttributeValue decodeValue(
             @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Attribute attribute,
-            @Nonnull final TranscodingRule rule, @Nullable final XMLObject value) {
+            @Nonnull final TranscodingRule rule, @Nullable final String value) {
         
         if (value != null) {
             final Instant retVal = getDateTimeValue(rule, value);
@@ -90,43 +79,6 @@ public class SAML2DateTimeAttributeTranscoder extends AbstractSAML2AttributeTran
         return null;
     }
     
-    /**
-     * Function to return an XML object in date/time form.
-     * 
-     * @param rule transcoding rule
-     * @param object object to decode
-     * 
-     * @return decoded date/time, or null
-     */
-    @Nullable protected Instant getDateTimeValue(@Nonnull final TranscodingRule rule, @Nonnull final XMLObject object) {
-        Instant retVal = null;
-
-        if (object instanceof XSString) {
-            
-            return getDateTimeValue(rule, ((XSString) object).getValue());
-            
-        } else if (object instanceof XSInteger) {
-
-            return getDateTimeValue(rule, ((XSInteger) object).getValue().longValue());
-
-        } else if (object instanceof XSDateTime) {
-
-            retVal = ((XSDateTime) object).getValue();
-
-        } else if (object instanceof XSAny) {
-
-            final XSAny wc = (XSAny) object;
-            if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
-                return getDateTimeValue(rule, wc.getTextContent());
-            }
-        }
-
-        if (null == retVal) {
-            log.info("Value of type {} could not be converted", object.getClass().getSimpleName());
-        }
-        return retVal;
-    }
-
     /**
      * Convert a string value into an {@link Instant}.
      * 
@@ -136,15 +88,25 @@ public class SAML2DateTimeAttributeTranscoder extends AbstractSAML2AttributeTran
      * @return converted result or null
      */
     @Nullable protected Instant getDateTimeValue(@Nonnull final TranscodingRule rule, @Nullable final String value) {
+        if (value == null) {
+            return null;
+        }
+        
         try {
             final Long longVal = Long.valueOf(value);
             if (longVal != null) {
                 return getDateTimeValue(rule, longVal);
             }
         } catch (final NumberFormatException e) {
+        }
+
+        try {
             return DOMTypeSupport.stringToInstant(value);
+        } catch (final IllegalArgumentException e) {
         }
-        
+
+        log.warn("{} rule unable to process string value as numeric or ISO format",
+                rule.getOrDefault(AttributeTranscoderRegistry.PROP_ID, String.class, "(none)"));
         return null;
     }
     
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASDateTimeAttributeTranscoderTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASDateTimeAttributeTranscoderTest.java
new file mode 100644
index 000000000..9813e287d
--- /dev/null
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/attribute/transcoding/impl/CASDateTimeAttributeTranscoderTest.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.cas.attribute.transcoding.impl;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import net.shibboleth.ext.spring.testing.MockApplicationContext;
+import net.shibboleth.idp.attribute.AttributeEncodingException;
+import net.shibboleth.idp.attribute.ByteAttributeValue;
+import net.shibboleth.idp.attribute.DateTimeAttributeValue;
+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.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.cas.attribute.AbstractCASAttributeTranscoder;
+import net.shibboleth.idp.cas.attribute.Attribute;
+import net.shibboleth.idp.cas.attribute.CASAttributeTranscoder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** {@link CASDateTimeAttributeTranscoder} unit test. */
+public class CASDateTimeAttributeTranscoderTest {
+
+    private AttributeTranscoderRegistryImpl registry;
+    
+    private final static String ATTR_ID = "foo";
+    private final static String ATTR_NAME = "bar";
+    private final static String STRING_SECS = "1659979872";
+    private final static String STRING_MSECS = "1659979872969";
+    private final static String STRING_ISO = "2022-08-08T17:31:12.969Z";
+    private final static String STRING_INVALID = "invalid";
+
+    @BeforeClass public void setUp() throws ComponentInitializationException {
+        
+        registry = new AttributeTranscoderRegistryImpl();
+        registry.setId("test");
+                
+        final CASDateTimeAttributeTranscoder transcoder = new CASDateTimeAttributeTranscoder();
+        transcoder.initialize();
+        
+        registry.setNamingRegistry(Collections.singletonList(
+                new BasicNamingFunction<>(transcoder.getEncodedType(), new AbstractCASAttributeTranscoder.NamingFunction())));
+        
+        final Map<String,Object> ruleset1 = new HashMap<>();
+        ruleset1.put(AttributeTranscoderRegistry.PROP_ID, ATTR_ID);
+        ruleset1.put(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder);
+        ruleset1.put(CASAttributeTranscoder.PROP_NAME, ATTR_NAME);
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset1)));
+        registry.setApplicationContext(new MockApplicationContext());
+        registry.initialize();
+    }
+    
+    @AfterClass public void tearDown() {
+        registry.destroy();
+        registry = null;
+    }
+
+    @Test public void emptyEncode() throws Exception {
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final Attribute attr = TranscoderSupport.<Attribute>getTranscoder(ruleset).encode(
+                null, inputAttribute, Attribute.class, ruleset);
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+        Assert.assertTrue(attr.getValues().isEmpty());
+    }
+
+    @Test public void emptyDecode() throws Exception {
+        
+        final Attribute casAttribute = new Attribute(ATTR_NAME);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(casAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr = TranscoderSupport.getTranscoder(ruleset).decode(null, casAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_ID);
+        Assert.assertTrue(attr.getValues().isEmpty());
+    }
+
+    @Test(expectedExceptions = {AttributeEncodingException.class,}) public void inappropriate() throws Exception {
+        final int[] intArray = {1, 2, 3, 4};
+        final List<IdPAttributeValue> values =
+                List.of(new ByteAttributeValue(new byte[] {1, 2, 3,}), new IdPAttributeValue() {
+                    @Override
+                    public Object getNativeValue() {
+                        return intArray;
+                    }
+                    @Override
+                    public String getDisplayValue() {
+                        return intArray.toString();
+                    }
+                });
+
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        inputAttribute.setValues(values);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        TranscoderSupport.getTranscoder(ruleset).encode(null, inputAttribute, Attribute.class, ruleset);
+    }
+    
+    @Test public void single() throws Exception {
+        final List<IdPAttributeValue> values =
+                List.of(new ByteAttributeValue(new byte[] {1, 2, 3,}), new DateTimeAttributeValue(Instant.parse(STRING_ISO)));
+
+        final IdPAttribute inputAttribute = new IdPAttribute(ATTR_ID);
+        inputAttribute.setValues(values);
+        
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute, Attribute.class);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final Attribute attr = TranscoderSupport.<Attribute>getTranscoder(ruleset).encode(
+                null, inputAttribute, Attribute.class, ruleset);
+
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getName(), ATTR_NAME);
+
+        final Collection<String> children = attr.getValues();
+
+        Assert.assertEquals(children.size(), 1, "Encoding one entry");
+
+        final String child = children.iterator().next();
+
+        Assert.assertEquals(child, STRING_ISO);
+    }
+    
+    @Test public void singleDecode() throws Exception {
+        
+        final Attribute casAttribute = new Attribute(ATTR_NAME);
+        casAttribute.getValues().add(STRING_SECS);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(casAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr = TranscoderSupport.getTranscoder(ruleset).decode(null, casAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_ID);
+        Assert.assertEquals(attr.getValues().size(), 1);
+        Assert.assertEquals(((DateTimeAttributeValue)attr.getValues().get(0)).getValue(),
+                Instant.ofEpochSecond(Long.valueOf(STRING_SECS)));
+    }
+
+}
\ No newline at end of file
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/attribute-registry-system.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/attribute-registry-system.xml
index 5d470f944..f207ff803 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/attribute-registry-system.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/attribute-registry-system.xml
@@ -36,6 +36,8 @@
         class="net.shibboleth.idp.cas.attribute.transcoding.impl.CASStringAttributeTranscoder" />
     <bean id="CASScopedStringTranscoder"
         class="net.shibboleth.idp.cas.attribute.transcoding.impl.CASScopedStringAttributeTranscoder" />
+    <bean id="CASDateTimeTranscoder"
+        class="net.shibboleth.idp.cas.attribute.transcoding.impl.CASDateTimeAttributeTranscoder" />
     
     <bean id="shibboleth.TranscodingRule"
         class="net.shibboleth.idp.attribute.transcoding.TranscodingRule" abstract="true" />
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoder.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoder.java
index dc992f261..66f6b1098 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoder.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoder.java
@@ -105,7 +105,7 @@ public class SAML2DateTimeAttributeTranscoder extends AbstractSAML2AttributeTran
             
             return getDateTimeValue(rule, ((XSString) object).getValue());
             
-        } else if (object instanceof XSInteger) {
+        } else if (object instanceof XSInteger && ((XSInteger) object).getValue() != null) {
 
             return getDateTimeValue(rule, ((XSInteger) object).getValue().longValue());
 
@@ -136,15 +136,25 @@ public class SAML2DateTimeAttributeTranscoder extends AbstractSAML2AttributeTran
      * @return converted result or null
      */
     @Nullable protected Instant getDateTimeValue(@Nonnull final TranscodingRule rule, @Nullable final String value) {
+        if (value == null) {
+            return null;
+        }
+        
         try {
             final Long longVal = Long.valueOf(value);
             if (longVal != null) {
                 return getDateTimeValue(rule, longVal);
             }
         } catch (final NumberFormatException e) {
+        }
+
+        try {
             return DOMTypeSupport.stringToInstant(value);
+        } catch (final IllegalArgumentException e) {
         }
-        
+
+        log.warn("{} rule unable to process string value as numeric or ISO format",
+                rule.getOrDefault(AttributeTranscoderRegistry.PROP_ID, String.class, "(none)"));
         return null;
     }
     
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoderTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoderTest.java
index da03a2b00..7afc95db2 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoderTest.java
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/attribute/transcoding/impl/SAML2DateTimeAttributeTranscoderTest.java
@@ -149,6 +149,32 @@ public class SAML2DateTimeAttributeTranscoderTest extends OpenSAMLInitBaseTestCa
         Assert.assertTrue(attr.getValues().isEmpty());
     }
 
+
+    @Test public void invalidDecode() throws Exception {
+        
+        final XSString stringValue = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME);
+        stringValue.setValue(STRING_INVALID);
+
+        final XSString stringValue2 = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME);
+        stringValue2.setValue(STRING_ISO);
+
+        final Attribute samlAttribute = attributeBuilder.buildObject();
+        samlAttribute.setName(ATTR_NAME);
+        samlAttribute.setNameFormat(ATTR_NAMEFORMAT);
+        samlAttribute.getAttributeValues().add(stringValue);
+        samlAttribute.getAttributeValues().add(stringValue2);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(samlAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule ruleset = rulesets.iterator().next();
+        
+        final IdPAttribute attr = TranscoderSupport.<Attribute>getTranscoder(ruleset).decode(null, samlAttribute, ruleset);
+        
+        Assert.assertNotNull(attr);
+        Assert.assertEquals(attr.getId(), ATTR_NAME);
+        Assert.assertEquals(attr.getValues().size(), 1);
+    }
+
     @Test public void emptyRequestedDecode() throws Exception {
         
         final RequestedAttribute samlAttribute = reqAttributeBuilder.buildObject();
diff --git a/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd b/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd
index e8cee12df..948e3bb3a 100644
--- a/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd
+++ b/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd
@@ -3,7 +3,7 @@
     xmlns:resolver="urn:mace:shibboleth:2.0:resolver"
     xmlns:sec="urn:mace:shibboleth:2.0:security"
     targetNamespace="urn:mace:shibboleth:2.0:resolver"
-    elementFormDefault="qualified" version="4.1">
+    elementFormDefault="qualified" version="4.3">
 
     <import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
     <import namespace="urn:mace:shibboleth:2.0:security" schemaLocation="http://shibboleth.net/schema/idp/shibboleth-security.xsd"/>
@@ -507,7 +507,7 @@
                         <documentation>The SAML 1 NameFormat of the NameID.</documentation>
                     </annotation>
                 </attribute>
-                <attribute name="nameIdQualifier" type="resolver:string" use="optional">
+                <attribute name="nameIdQualifier" type="resolver:string">
                     <annotation>
                         <documentation>The SAML 1 NameQualifier of the NameID.</documentation>
                     </annotation>
@@ -534,12 +534,12 @@
                         <documentation>The SAML 2 NameFormat of the NameID.</documentation>
                     </annotation>
                 </attribute>
-                <attribute name="nameIdQualifier" type="resolver:string" use="optional">
+                <attribute name="nameIdQualifier" type="resolver:string">
                     <annotation>
                         <documentation>The SAML 2 NameQualifier of the NameID.</documentation>
                     </annotation>
                 </attribute>
-                <attribute name="nameIdSPQualifier" type="resolver:string" use="optional">
+                <attribute name="nameIdSPQualifier" type="resolver:string">
                     <annotation>
                         <documentation>The SAML 2 SPNameQualifier of the NameID.</documentation>
                     </annotation>
@@ -2068,7 +2068,27 @@
                         <documentation>The SAML 2 NameFormat of the attribute.</documentation>
                     </annotation>
                 </attribute>
-                <attribute name="friendlyName" type="resolver:string" use="optional">
+                <attribute name="friendlyName" type="resolver:string">
+                    <annotation>
+                        <documentation>The SAML 2 FriendlyName of the attribute.</documentation>
+                    </annotation>
+                </attribute>
+            </extension>
+        </complexContent>
+    </complexType>
+
+    <complexType name="SAML2DateTime">
+        <annotation>
+            <documentation>Defines a SAML 2 date/time encoder for an attribute.</documentation>
+        </annotation>
+        <complexContent>
+            <extension base="resolver:BaseAttributeEncoderType">
+                <attribute name="nameFormat" type="resolver:string">
+                    <annotation>
+                        <documentation>The SAML 2 NameFormat of the attribute.</documentation>
+                    </annotation>
+                </attribute>
+                <attribute name="friendlyName" type="resolver:string">
                     <annotation>
                         <documentation>The SAML 2 FriendlyName of the attribute.</documentation>
                     </annotation>
@@ -2088,7 +2108,7 @@
                         <documentation>The SAML 2 NameFormat of the attribute.</documentation>
                     </annotation>
                 </attribute>
-                <attribute name="friendlyName" type="resolver:string" use="optional">
+                <attribute name="friendlyName" type="resolver:string">
                     <annotation>
                         <documentation>The SAML 2 FriendlyName of the attribute.</documentation>
                     </annotation>
@@ -2108,7 +2128,7 @@
                         <documentation>The SAML 2 NameFormat of the attribute.</documentation>
                     </annotation>
                 </attribute>
-                <attribute name="friendlyName" type="resolver:string" use="optional">
+                <attribute name="friendlyName" type="resolver:string">
                     <annotation>
                         <documentation>The SAML 2 FriendlyName of the attribute.</documentation>
                     </annotation>
@@ -2123,7 +2143,7 @@
         </annotation>
         <complexContent>
             <extension base="resolver:BaseAttributeEncoderType">
-                <attribute name="scopeType" type="resolver:string" use="optional">
+                <attribute name="scopeType" type="resolver:string">
                     <annotation>
                         <documentation>
                             The type of scoping to use for the encoded attribute. Valid values are "inline" or
@@ -2131,7 +2151,7 @@
                         </documentation>
                     </annotation>
                 </attribute>
-                <attribute name="scopeDelimiter" type="resolver:string" use="optional">
+                <attribute name="scopeDelimiter" type="resolver:string">
                     <annotation>
                         <documentation>
                             If scopeType is "inline", this is the delimeter used between the attribute value and
@@ -2139,7 +2159,7 @@
                         </documentation>
                     </annotation>
                 </attribute>
-                <attribute name="scopeAttribute" type="resolver:string" use="optional">
+                <attribute name="scopeAttribute" type="resolver:string">
                     <annotation>
                         <documentation>
                             If scopeType is "attribute", this is the name of the name of the attribute used to carry the
@@ -2177,7 +2197,7 @@
                         <documentation>The SAML 2 NameFormat of the attribute.</documentation>
                     </annotation>
                 </attribute>
-                <attribute name="friendlyName" type="resolver:string" use="optional">
+                <attribute name="friendlyName" type="resolver:string">
                     <annotation>
                         <documentation>The SAML 2 FriendlyName of the attribute.</documentation>
                     </annotation>

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


More information about the commits mailing list