[java-idp-plugin-vci] branch main updated: Parse credential information in bit more friendly manner for attributes

Codeberg noreply at shibboleth.net
Tue Sep 22 11:36:31 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-idp-plugin-vci.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/f8df3fc90a7f10d2699f86cb730e352234a4c2a6

The following commit(s) were added to refs/heads/main by this push:
     new f8df3fc  Parse credential information in bit more friendly manner for attributes
f8df3fc is described below

commit f8df3fc90a7f10d2699f86cb730e352234a4c2a6
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Tue Sep 22 14:36:17 2026 +0300

    Parse credential information in bit more friendly manner for attributes
---
 README.md                                          |  9 ++---
 .../impl/CredentialOfferRequestedClaim.java        | 12 +++++++
 .../impl/CredentialOfferRequestedCredential.java   | 38 ++++++++++++++++++++--
 .../CredentialOfferRequestedCredentialTest.java    | 21 ++++++++++++
 4 files changed, 74 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index cc82be6..0473977 100644
--- a/README.md
+++ b/README.md
@@ -745,8 +745,9 @@ code flow:
 
 </details>
 
-The *value* of the attribute is the serialized claim list. You define it as a
-`ScriptedAttribute` so that it may read the real attributes of the user:
+The *value* of the attribute is the claims as an array of `{"path": [...], "value": ...}`, the
+same array the Credential Offer API takes. You define it as a `ScriptedAttribute` so that it may
+read the real attributes of the user:
 
 <details>
 <summary>Example snippet of conf/attribute-resolver.xml</summary>
@@ -759,14 +760,14 @@ The *value* of the attribute is the serialized claim list. You define it as a
   <InputAttributeDefinition ref="sn" />
   <InputAttributeDefinition ref="eduPersonAffiliation" />
   <Script><![CDATA[
-      var credential = '{"requestedCredential":['
+      var credential = '['
         + '{"path":["diploma"],"value":"Scrum Master"},'
         + '{"path":["mail"],"value":"' + mail.getValues().get(0) + '"},'
         + '{"path":["eppn"],"value":"' + eduPersonPrincipalName.getValues().get(0) + '"},'
         + '{"path":["givenName"],"value":"' + givenName.getValues().get(0) + '"},'
         + '{"path":["familyName"],"value":"' + sn.getValues().get(0) + '"},'
         + '{"path":["affiliation"],"value":"' + eduPersonAffiliation.getValues().get(0) + '"}'
-        + ']}';
+        + ']';
       GeantIncubatorDiploma.addValue(credential);
   ]]></Script>
   <AttributeEncoder xsi:type="oidc:OIDCString" name="GeantIncubatorDiploma_SDJWT" />
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedClaim.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedClaim.java
index 683e13d..2611ef2 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedClaim.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedClaim.java
@@ -82,6 +82,18 @@ public final class CredentialOfferRequestedClaim {
         return value;
     }
 
+    /**
+     * Build a requested claim of a path and a value.
+     *
+     * @param path  Path of the claim
+     * @param value Value of the claim
+     * @return CredentialOfferRequestedClaim instance
+     */
+    public static CredentialOfferRequestedClaim of(@Nonnull @NotEmpty final List<String> path,
+            @Nonnull final Object value) {
+        return new CredentialOfferRequestedClaim(path, value);
+    }
+
     /**
      * Parse CredentialOfferRequestedClaim instance from Map.
      * 
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedCredential.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedCredential.java
index 76999e6..7251e5f 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedCredential.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedCredential.java
@@ -19,6 +19,7 @@ package org.geant.shibboleth.plugin.openidvci.messaging.impl;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.Map;
 
 import javax.annotation.Nonnull;
 
@@ -33,6 +34,9 @@ import com.nimbusds.oauth2.sdk.ParseException;
  */
 public class CredentialOfferRequestedCredential {
 
+    /** Key of the requested claims. */
+    private static final String REQUESTED_CREDENTIAL_KEY = "requestedCredential";
+
     /** Requested credentials. */
     private final List<CredentialOfferRequestedClaim> requestedCredential;
 
@@ -83,8 +87,11 @@ public class CredentialOfferRequestedCredential {
      */
     public static CredentialOfferRequestedCredential parse(@Nonnull final String request)
             throws ParseException, JsonMappingException, JsonProcessingException {
-        return new ObjectMapper().readValue(request, CredentialOfferRequestedCredential.class);
-
+        final Object parsed = new ObjectMapper().readValue(request, Object.class);
+        if (parsed instanceof Map<?, ?> content && content.containsKey(REQUESTED_CREDENTIAL_KEY)) {
+            return parse(content.get(REQUESTED_CREDENTIAL_KEY));
+        }
+        return parse(parsed);
     }
 
     /**
@@ -103,7 +110,34 @@ public class CredentialOfferRequestedCredential {
             }
             return new CredentialOfferRequestedCredential(requestedCredential);
         }
+        if (object instanceof Map<?, ?> claims) {
+            collectClaims(claims, new ArrayList<>(), requestedCredential);
+            if (requestedCredential.isEmpty()) {
+                throw new ParseException("Invalid credential offer requested credential: No claims");
+            }
+            return new CredentialOfferRequestedCredential(requestedCredential);
+        }
         throw new ParseException("Invalid credential offer requested credential: Parsing failed for "
                 + (object != null ? object.toString() : "null"));
     }
+
+    /**
+     * Collect the claims of a claims object, a nested object naming the path of the claims below it.
+     *
+     * @param claims    claims object to read
+     * @param path      path of the claims object
+     * @param collected claims collected so far
+     */
+    private static void collectClaims(@Nonnull final Map<?, ?> claims, @Nonnull final List<String> path,
+            @Nonnull final List<CredentialOfferRequestedClaim> collected) {
+        for (final Map.Entry<?, ?> claim : claims.entrySet()) {
+            final List<String> claimPath = new ArrayList<>(path);
+            claimPath.add(String.valueOf(claim.getKey()));
+            if (claim.getValue() instanceof Map<?, ?> nested) {
+                collectClaims(nested, claimPath, collected);
+            } else if (claim.getValue() != null) {
+                collected.add(CredentialOfferRequestedClaim.of(claimPath, claim.getValue()));
+            }
+        }
+    }
 }
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedCredentialTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedCredentialTest.java
index ed61dbf..ae84c79 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedCredentialTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferRequestedCredentialTest.java
@@ -51,6 +51,27 @@ public class CredentialOfferRequestedCredentialTest {
         Assert.assertEquals(requestedCredential.getRequestedCredential().get(0).getValue(), "stringValue");
     }
 
+    @Test
+    public void claimsObject() throws JsonProcessingException, ParseException {
+        requestedCredential = CredentialOfferRequestedCredential.parse("{\"diploma\":\"Scrum Master\","
+                + "\"mail\":\"incubatorUser at example.org\",\"address\":{\"city\":\"Helsinki\"}}");
+        Assert.assertEquals(requestedCredential.getRequestedCredential().size(), 3);
+        final CredentialOfferRequestedClaim nested = requestedCredential.getRequestedCredential().stream()
+                .filter(claim -> claim.getPath().size() == 2).findFirst().orElse(null);
+        Assert.assertNotNull(nested);
+        Assert.assertEquals(nested.getPath(), Arrays.asList("address", "city"));
+        Assert.assertEquals(nested.getValue(), "Helsinki");
+    }
+
+    @Test
+    public void claimsArray() throws JsonProcessingException, ParseException {
+        requestedCredential = CredentialOfferRequestedCredential
+                .parse("[{\"path\":[\"mail\"],\"value\":\"incubatorUser at example.org\"}]");
+        Assert.assertEquals(requestedCredential.getRequestedCredential().size(), 1);
+        Assert.assertEquals(requestedCredential.getRequestedCredential().get(0).getValue(),
+                "incubatorUser at example.org");
+    }
+
     @Test
     public void exampleString() throws JsonProcessingException, ParseException {
         CredentialOfferRequestedCredential.parse(

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


More information about the commits mailing list