[java-oidc-common] branch main updated: JCOMOIDC-40 - Complete OIDCAttributeTranscoder decode values function

Phil Smart philip.smart at jisc.ac.uk
Thu Jun 8 13:07:41 UTC 2023


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

philsmart 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=e859ededa6252e013e4e2dcb7512da7990a6bad3

The following commit(s) were added to refs/heads/main by this push:
     new e859ede  JCOMOIDC-40 - Complete OIDCAttributeTranscoder decode values function
e859ede is described below

commit e859ededa6252e013e4e2dcb7512da7990a6bad3
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Jun 8 14:07:37 2023 +0100

    JCOMOIDC-40 - Complete OIDCAttributeTranscoder decode values function
    
     - Completed #decodeValues of the OIDCScopedStringAttributeTranscoder.
    Supports JSON Strings and Strings nested in JSON Arrays.
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-40
---
 .../impl/OIDCScopedStringAttributeTranscoder.java  |  43 +++-
 .../OIDCScopedStringAttributeTranscoderTest.java   | 246 ++++++++++++++++++++-
 2 files changed, 279 insertions(+), 10 deletions(-)

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
index fca7618..c478d76 100644
--- 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
@@ -17,6 +17,7 @@
 
 package net.shibboleth.oidc.attribute.transcoding.impl;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -25,6 +26,8 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import net.minidev.json.JSONArray;
 import net.shibboleth.idp.attribute.AttributeDecodingException;
@@ -32,6 +35,7 @@ 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.AttributeTranscoderRegistry;
 import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
 import net.shibboleth.oidc.attribute.transcoding.AbstractOIDCAttributeTranscoder;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
@@ -39,13 +43,20 @@ 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
+ * string contains attribute value, delimiter and scope concatenated. If there are several attribute values they are
  * delimited with delimiter(space is default) or placed to array.
+ * 
+ * <p>For decoding, JSON Strings are split by the delimiter into a {@link ScopedStringAttributeValue}. For JSON Arrays,
+ * the elements are recessively visited and any JSON Strings are split by their delimiter into a list of
+ * {@link ScopedStringAttributeValue}s. </p>
  */
 public class OIDCScopedStringAttributeTranscoder extends AbstractOIDCAttributeTranscoder {
 
     /** Scope delimiter for combining the data. */
     @Nonnull @NotEmpty public static final String PROP_SCOPE_DELIMITER = "oidc.scopeDelimiter";
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(OIDCScopedStringAttributeTranscoder.class);
 
     /** {@inheritDoc} */
     @Override
@@ -97,8 +108,32 @@ public class OIDCScopedStringAttributeTranscoder extends AbstractOIDCAttributeTr
             @Nullable final ProfileRequestContext profileRequestContext, @Nonnull final Object input,
             @Nonnull final TranscodingRule rule) throws AttributeDecodingException {
         
-        // TODO: implement value decoding
-        return Collections.emptyList();
+        if (input instanceof String) {
+            final String stringValue = (String)input;
+            final String scopeDelimiter = rule.getOrDefault(PROP_SCOPE_DELIMITER, String.class, "@");
+            final int offset = stringValue.indexOf(scopeDelimiter);
+            if (offset < 0) {
+                log.warn("Ignoring value with no scope delimiter ({})", scopeDelimiter);
+                return Collections.emptyList();
+            }
+            return List.of(ScopedStringAttributeValue.valueOf(stringValue.substring(0, offset), 
+                    stringValue.substring(offset + scopeDelimiter.length())));
+        }
+        else if (input instanceof JSONArray) {
+            // Deal with array recursively
+            final JSONArray array = (JSONArray)input;
+            final List<IdPAttributeValue> arrayValuesDecoded = new ArrayList<>();
+            for (final Object element : array) {
+                arrayValuesDecoded.addAll(decodeValues(profileRequestContext, element, rule));
+            }
+            return arrayValuesDecoded;
+            
+        } else {
+            log.trace("Could not convert values of type {} for the claim '{}' to a scoped string attribute using "
+                    + "transcoding rule '{} -> {}'",
+                    input.getClass().getName(), input, rule.getOrDefault(PROP_NAME, String.class, "unknown"), 
+                    rule.getOrDefault(AttributeTranscoderRegistry.PROP_ID, String.class, "unknown"));
+            return Collections.emptyList();
+        }
     }
-
 }
\ 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
index 2027271..f0927f3 100644
--- 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
@@ -24,9 +24,15 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
 import net.minidev.json.JSONArray;
 import net.minidev.json.JSONObject;
 import net.shibboleth.ext.spring.testing.MockApplicationContext;
+import net.shibboleth.idp.attribute.AttributeDecodingException;
 import net.shibboleth.idp.attribute.AttributeEncodingException;
 import net.shibboleth.idp.attribute.ByteAttributeValue;
 import net.shibboleth.idp.attribute.IdPAttribute;
@@ -39,14 +45,8 @@ 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;
@@ -59,6 +59,7 @@ public class OIDCScopedStringAttributeTranscoderTest {
     private final static String STRING_2 = "value2";
     private final static String SCOPE_1 = "scope1";
     private final static String SCOPE_2 = "scope2";
+    private final static String DELIMITER = "@";
     
     @BeforeMethod
     protected void setUp() throws Exception {
@@ -182,4 +183,237 @@ public class OIDCScopedStringAttributeTranscoderTest {
         TranscoderSupport.<JSONObject>getTranscoder(tr).encode(null, inputAttribute, JSONObject.class, tr);
     }
     
+    
+    @Test
+    public void testDecodingNoValues() throws ComponentInitializationException, AttributeDecodingException {
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();        
+       
+        // Map from oidc claim.name 'bar' to shib attribute id 'foo'. 
+        final JSONObject inputAttribute = new JSONObject();
+        inputAttribute.put(ATTR_NAME, null);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final IdPAttribute decodedAttribute = TranscoderSupport.<JSONObject>getTranscoder(tr)
+                .decode(null, inputAttribute, tr);
+
+        Assert.assertNotNull(decodedAttribute);
+        Assert.assertEquals(decodedAttribute.getId(), ATTR_ID);
+        Assert.assertEquals(decodedAttribute.getValues().size(), 0);
+    }
+    
+    @Test
+    public void testDecodingScopedString() throws ComponentInitializationException, AttributeDecodingException {
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();        
+       
+        // Map from oidc claim.name 'bar' to shib attribute id 'foo'. 
+        final JSONObject inputAttribute = new JSONObject();  
+        inputAttribute.put(ATTR_NAME, STRING_1+ DELIMITER + SCOPE_1);     
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final IdPAttribute decodedAttribute = TranscoderSupport.<JSONObject>getTranscoder(tr)
+                .decode(null, inputAttribute, tr);
+
+        Assert.assertNotNull(decodedAttribute);
+        Assert.assertEquals(decodedAttribute.getId(), ATTR_ID);
+        Assert.assertEquals(decodedAttribute.getValues().size(), 1);
+        Assert.assertTrue(decodedAttribute.getValues().get(0) instanceof ScopedStringAttributeValue);
+        final var scopedString = (ScopedStringAttributeValue) decodedAttribute.getValues().get(0);
+        Assert.assertEquals(scopedString.getScope(), SCOPE_1);
+        Assert.assertEquals(scopedString.getValue(), STRING_1);
+    }
+    
+    @Test
+    public void testDecodingScopedStringWithNewDelimiter() 
+            throws ComponentInitializationException, AttributeDecodingException {
+        
+        ruleset.put(OIDCScopedStringAttributeTranscoder.PROP_SCOPE_DELIMITER, "#");
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();        
+       
+        // Map from oidc claim.name 'bar' to shib attribute id 'foo'. 
+        final JSONObject inputAttribute = new JSONObject();  
+        inputAttribute.put(ATTR_NAME, STRING_1+ "#" + SCOPE_1);     
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final IdPAttribute decodedAttribute = TranscoderSupport.<JSONObject>getTranscoder(tr)
+                .decode(null, inputAttribute, tr);
+
+        Assert.assertNotNull(decodedAttribute);
+        Assert.assertEquals(decodedAttribute.getId(), ATTR_ID);
+        Assert.assertEquals(decodedAttribute.getValues().size(), 1);
+        Assert.assertTrue(decodedAttribute.getValues().get(0) instanceof ScopedStringAttributeValue);
+        final var scopedString = (ScopedStringAttributeValue) decodedAttribute.getValues().get(0);
+        Assert.assertEquals(scopedString.getScope(), SCOPE_1);
+        Assert.assertEquals(scopedString.getValue(), STRING_1);
+    }
+    
+    @Test
+    public void testDecodingUnScopedString() throws ComponentInitializationException, AttributeDecodingException {
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();        
+       
+        // Map from oidc claim.name 'bar' to shib attribute id 'foo'. 
+        final JSONObject inputAttribute = new JSONObject();
+        // No delimeter so should not decode
+        inputAttribute.put(ATTR_NAME, STRING_1);     
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final IdPAttribute decodedAttribute = TranscoderSupport.<JSONObject>getTranscoder(tr)
+                .decode(null, inputAttribute, tr);
+
+        Assert.assertNotNull(decodedAttribute);
+        Assert.assertEquals(decodedAttribute.getId(), ATTR_ID);
+        Assert.assertEquals(decodedAttribute.getValues().size(), 0);
+    }
+    
+    @Test
+    public void testDecodingWrongType() throws ComponentInitializationException, AttributeDecodingException {
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();        
+       
+        // Map from oidc claim.name 'bar' to shib attribute id 'foo'. 
+        // Byte is not supported, so should not be decoded
+        final JSONObject inputAttribute = new JSONObject();    
+        inputAttribute.put(ATTR_NAME, new byte[1]);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+        
+        final IdPAttribute decodedAttribute = TranscoderSupport.<JSONObject>getTranscoder(tr)
+                .decode(null, inputAttribute, tr);
+        
+        Assert.assertNotNull(decodedAttribute);
+        Assert.assertEquals(decodedAttribute.getId(), ATTR_ID);
+        Assert.assertEquals(decodedAttribute.getValues().size(), 0);
+
+    }
+    
+    @Test
+    public void testDecodingStringArrayAsScopedStrings() throws ComponentInitializationException, AttributeDecodingException {
+        
+        ruleset.put(OIDCStringAttributeTranscoder.PROP_ARRAY_AS_STRING, true);
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();        
+       
+        // Map from oidc claim.name 'bar' to shib attribute id 'foo'. 
+        final JSONObject inputAttribute = new JSONObject();        
+        final JSONArray array = new JSONArray();
+        array.add(STRING_1+ DELIMITER + SCOPE_1);
+        array.add(STRING_2+ DELIMITER + SCOPE_2);               
+        inputAttribute.put(ATTR_NAME, array);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final IdPAttribute decodedAttribute = TranscoderSupport.<JSONObject>getTranscoder(tr)
+                .decode(null, inputAttribute, tr);
+
+        Assert.assertNotNull(decodedAttribute);
+        Assert.assertEquals(decodedAttribute.getId(), ATTR_ID);
+        Assert.assertEquals(decodedAttribute.getValues().size(), 2);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(0)).getScope(), SCOPE_1);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(0)).getValue(), STRING_1);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(1)).getScope(), SCOPE_2);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(1)).getValue(), STRING_2);
+    }
+    
+    @Test
+    public void testDecodingComplexArray() throws ComponentInitializationException, AttributeDecodingException {
+        
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();        
+       
+        // Map from oidc claim.name 'bar' to shib attribute id 'foo'. 
+        final JSONObject inputAttribute = new JSONObject();        
+        final JSONArray firstArray = new JSONArray();
+        firstArray.add(STRING_1+ DELIMITER + SCOPE_1);
+        
+        final JSONArray secondArray = new JSONArray();
+        secondArray.add(STRING_2+ DELIMITER + SCOPE_2);  
+        
+        firstArray.add(secondArray);      
+        
+        inputAttribute.put(ATTR_NAME, firstArray);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final IdPAttribute decodedAttribute = TranscoderSupport.<JSONObject>getTranscoder(tr)
+                .decode(null, inputAttribute, tr);
+
+        Assert.assertNotNull(decodedAttribute);
+        Assert.assertEquals(decodedAttribute.getId(), ATTR_ID);
+        Assert.assertEquals(decodedAttribute.getValues().size(), 2);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(0)).getScope(), SCOPE_1);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(0)).getValue(), STRING_1);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(1)).getScope(), SCOPE_2);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(1)).getValue(), STRING_2);
+    }
+    
+    @Test
+    public void testDecodingComplexArray_NumberNotSupported() 
+            throws ComponentInitializationException, AttributeDecodingException {
+
+        registry.setTranscoderRegistry(Collections.singletonList(new TranscodingRule(ruleset)));
+        registry.setApplicationContext(new MockApplicationContext());        
+        registry.initialize();        
+       
+        // Map from oidc claim.name 'bar' to shib attribute id 'foo'. 
+        final JSONObject inputAttribute = new JSONObject();        
+        final JSONArray firstArray = new JSONArray();
+        firstArray.add(STRING_1+ DELIMITER + SCOPE_1);
+        
+        // This should be ignored in the result
+        final JSONArray secondArray = new JSONArray();
+        secondArray.add(1);  
+        
+        firstArray.add(secondArray);
+        
+        inputAttribute.put(ATTR_NAME, firstArray);
+
+        final Collection<TranscodingRule> rulesets = registry.getTranscodingRules(inputAttribute);
+        Assert.assertEquals(rulesets.size(), 1);
+        final TranscodingRule tr = rulesets.iterator().next();
+
+        final IdPAttribute decodedAttribute = TranscoderSupport.<JSONObject>getTranscoder(tr)
+                .decode(null, inputAttribute, tr);
+        
+        Assert.assertNotNull(decodedAttribute);
+        Assert.assertEquals(decodedAttribute.getId(), ATTR_ID);
+        Assert.assertEquals(decodedAttribute.getValues().size(), 1);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(0)).getScope(), SCOPE_1);
+        Assert.assertEquals(((ScopedStringAttributeValue)decodedAttribute.getValues().get(0)).getValue(), STRING_1);
+
+   }
+    
 }
\ 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