You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by gn...@apache.org on 2026/03/12 08:31:07 UTC

(cxf) branch fix/model-encryption-support created (now 623906aa05)

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a change to branch fix/model-encryption-support
in repository https://gitbox.apache.org/repos/asf/cxf.git


      at 623906aa05 Fix ModelEncryptionSupport serialization/deserialization bugs

This branch includes the following new commits:

     new 623906aa05 Fix ModelEncryptionSupport serialization/deserialization bugs

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(cxf) 01/01: Fix ModelEncryptionSupport serialization/deserialization bugs

Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch fix/model-encryption-support
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 623906aa059fa8678775eff862f91993c2177930
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Thu Mar 12 09:30:50 2026 +0100

    Fix ModelEncryptionSupport serialization/deserialization bugs
    
    - Use empty string instead of space for null values in tokenizeString
    - Use tokenizeCollection/tokenizeMap instead of toString() to avoid
      brackets and spaces in serialized collections/maps
    - Use split with -1 limit to preserve trailing empty strings
    - Add empty check in parseSimpleMap to handle empty map strings
    - Apply getStringPart to client id and secret in recreateClient
    - Add testPublicClient test verifying null secret round-trips correctly
    
    Closes #816
    
    Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
 .../utils/crypto/ModelEncryptionSupport.java       | 116 +++++++++++----------
 .../oauth2/utils/crypto/CryptoUtilsTest.java       |  17 +++
 2 files changed, 79 insertions(+), 54 deletions(-)

diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/crypto/ModelEncryptionSupport.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/crypto/ModelEncryptionSupport.java
index e5c7d3e13e..18466d8a48 100644
--- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/crypto/ModelEncryptionSupport.java
+++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/crypto/ModelEncryptionSupport.java
@@ -21,11 +21,14 @@ package org.apache.cxf.rs.security.oauth2.utils.crypto;
 
 import java.security.Key;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
 
 import javax.crypto.SecretKey;
 import javax.security.auth.DestroyFailedException;
@@ -279,7 +282,7 @@ public final class ModelEncryptionSupport {
         // Permissions
         if (!parts[9].trim().isEmpty()) {
             List<OAuthPermission> perms = new LinkedList<>();
-            String[] allPermParts = parts[9].split("\\.");
+            String[] allPermParts = parts[9].split("\\.", -1);
             for (int i = 0; i + 4 < allPermParts.length; i = i + 5) {
                 OAuthPermission perm = new OAuthPermission(allPermParts[i], allPermParts[i + 1]);
                 perm.setDefaultPermission(Boolean.parseBoolean(allPermParts[i + 2]));
@@ -301,7 +304,7 @@ public final class ModelEncryptionSupport {
 
     private static String tokenizeRefreshToken(RefreshToken token) {
         String seq = tokenizeServerToken(token);
-        return seq + SEP + token.getAccessTokens().toString();
+        return seq + SEP + tokenizeCollection(token.getAccessTokens());
     }
 
     private static String tokenizeServerToken(ServerAccessToken token) {
@@ -328,32 +331,14 @@ public final class ModelEncryptionSupport {
         state.append(tokenizeString(token.getGrantType()));
         // 7: audience
         state.append(SEP);
-        state.append(token.getAudiences().toString());
+        state.append(tokenizeCollection(token.getAudiences()));
         // 8: other parameters
         state.append(SEP);
-        // {key=value, key=value}
-        state.append(token.getParameters().toString());
+        state.append(tokenizeMap(token.getParameters()));
         // 9: permissions
         state.append(SEP);
-        if (token.getScopes().isEmpty()) {
-            state.append(' ');
-        } else {
-            for (OAuthPermission p : token.getScopes()) {
-                // 9.1
-                state.append(tokenizeString(p.getPermission()));
-                state.append('.');
-                // 9.2
-                state.append(tokenizeString(p.getDescription()));
-                state.append('.');
-                // 9.3
-                state.append(p.isDefaultPermission());
-                state.append('.');
-                // 9.4
-                state.append(p.getHttpVerbs().toString());
-                state.append('.');
-                // 9.5
-                state.append(p.getUris().toString());
-            }
+        if (!token.getScopes().isEmpty()) {
+            state.append(tokenizePermissions(token.getScopes()));
         }
         state.append(SEP);
         // 10: code verifier
@@ -363,16 +348,33 @@ public final class ModelEncryptionSupport {
         tokenizeUserSubject(state, token.getSubject());
         // 13: extra properties
         state.append(SEP);
-        // {key=value, key=value}
-        state.append(token.getExtraProperties().toString());
+        state.append(tokenizeMap(token.getExtraProperties()));
         return state.toString();
     }
 
 
+    private static String tokenizePermissions(List<OAuthPermission> perms) {
+        StringJoiner joiner = new StringJoiner(".");
+        for (OAuthPermission p : perms) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(tokenizeString(p.getPermission()));
+            sb.append('.');
+            sb.append(tokenizeString(p.getDescription()));
+            sb.append('.');
+            sb.append(p.isDefaultPermission());
+            sb.append('.');
+            sb.append(tokenizeCollection(p.getHttpVerbs()));
+            sb.append('.');
+            sb.append(tokenizeCollection(p.getUris()));
+            joiner.add(sb);
+        }
+        return joiner.toString();
+    }
+
     private static Client recreateClientInternal(String sequence) {
         String[] parts = getParts(sequence);
-        Client c = new Client(parts[0],
-                              parts[1],
+        Client c = new Client(getStringPart(parts[0]),
+                              getStringPart(parts[1]),
                               Boolean.parseBoolean(parts[2]),
                               getStringPart(parts[3]), getStringPart(parts[4]));
         c.setApplicationDescription(getStringPart(parts[5]));
@@ -410,22 +412,22 @@ public final class ModelEncryptionSupport {
         state.append(tokenizeString(client.getApplicationLogoUri()));
         state.append(SEP);
         // 7: app certificates
-        state.append(client.getApplicationCertificates());
+        state.append(tokenizeCollection(client.getApplicationCertificates()));
         state.append(SEP);
         // 8: grants
-        state.append(client.getAllowedGrantTypes().toString());
+        state.append(tokenizeCollection(client.getAllowedGrantTypes()));
         state.append(SEP);
         // 9: redirect URIs
-        state.append(client.getRedirectUris().toString());
+        state.append(tokenizeCollection(client.getRedirectUris()));
         state.append(SEP);
         // 10: registered scopes
-        state.append(client.getRegisteredScopes().toString());
+        state.append(tokenizeCollection(client.getRegisteredScopes()));
         state.append(SEP);
         // 11: registered audiences
-        state.append(client.getRegisteredAudiences().toString());
+        state.append(tokenizeCollection(client.getRegisteredAudiences()));
         state.append(SEP);
         // 12: properties
-        state.append(client.getProperties().toString());
+        state.append(tokenizeMap(client.getProperties()));
         state.append(SEP);
         // 13: subject
         tokenizeUserSubject(state, client.getSubject());
@@ -471,34 +473,31 @@ public final class ModelEncryptionSupport {
         state.append(tokenizeString(grant.getClientCodeChallenge()));
         state.append(SEP);
         // 7: approved scopes
-        state.append(grant.getApprovedScopes().toString());
+        state.append(tokenizeCollection(grant.getApprovedScopes()));
         state.append(SEP);
         // 8: subject
         tokenizeUserSubject(state, grant.getSubject());
         // 9: extra properties
         state.append(SEP);
-        // {key=value, key=value}
-        state.append(grant.getExtraProperties().toString());
+        state.append(tokenizeMap(grant.getExtraProperties()));
         return state.toString();
     }
 
     public static String getStringPart(String str) {
-        return " ".equals(str) ? null : str;
-    }
-
-    private static String prepareSimpleString(String str) {
-        return str.trim().isEmpty() ? "" : str.substring(1, str.length() - 1);
+        return "".equals(str) ? null : str;
     }
 
     private static List<String> parseSimpleList(String listStr) {
-        String pureStringList = prepareSimpleString(listStr);
-        if (pureStringList.isEmpty()) {
+        if (listStr.isEmpty()) {
             return Collections.emptyList();
         }
-        return Arrays.asList(pureStringList.split(","));
+        return Arrays.asList(listStr.split(",", -1));
     }
 
     public static Map<String, String> parseSimpleMap(String mapStr) {
+        if (mapStr.isEmpty()) {
+            return new HashMap<>();
+        }
         Map<String, String> props = new HashMap<>();
         List<String> entries = parseSimpleList(mapStr);
         for (String entry : entries) {
@@ -509,20 +508,18 @@ public final class ModelEncryptionSupport {
     }
 
     public static String[] getParts(String sequence) {
-        return sequence.split("\\" + SEP);
+        return sequence.split("\\" + SEP, -1);
     }
 
     private static UserSubject recreateUserSubject(String sequence) {
         UserSubject subject = null;
         if (!sequence.trim().isEmpty()) {
-            String[] subjectParts = sequence.split("\\.");
+            String[] subjectParts = sequence.split("\\.", -1);
             subject = new UserSubject(getStringPart(subjectParts[0]), getStringPart(subjectParts[1]));
             subject.setRoles(parseSimpleList(subjectParts[2]));
             subject.setProperties(parseSimpleMap(subjectParts[3]));
         }
         return subject;
-
-
     }
 
     private static void tokenizeUserSubject(StringBuilder state, UserSubject subject) {
@@ -534,16 +531,27 @@ public final class ModelEncryptionSupport {
             state.append(tokenizeString(subject.getId()));
             state.append('.');
             // 3
-            state.append(subject.getRoles().toString());
+            state.append(tokenizeCollection(subject.getRoles()));
             state.append('.');
             // 4
-            state.append(subject.getProperties().toString());
-        } else {
-            state.append(' ');
+            state.append(tokenizeMap(subject.getProperties()));
         }
     }
 
     public static String tokenizeString(String str) {
-        return str != null ? str : " ";
+        return str != null ? str : "";
+    }
+
+    private static String tokenizeCollection(Collection<? extends String> col) {
+        return col != null ? String.join(",", col) : "";
+    }
+
+    private static String tokenizeMap(Map<String, String> map) {
+        if (map == null || map.isEmpty()) {
+            return "";
+        }
+        return map.entrySet().stream()
+            .map(String::valueOf)
+            .collect(Collectors.joining(","));
     }
 }
diff --git a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/utils/crypto/CryptoUtilsTest.java b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/utils/crypto/CryptoUtilsTest.java
index dd529d45ad..afafe94e4f 100644
--- a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/utils/crypto/CryptoUtilsTest.java
+++ b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/utils/crypto/CryptoUtilsTest.java
@@ -50,6 +50,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -192,6 +193,22 @@ public class CryptoUtilsTest {
     }
 
 
+    @Test
+    public void testPublicClient() throws Exception {
+        Client c = new Client("client", null, false);
+        c.setSubject(new UserSubject("subject", "id"));
+
+        String encryptedClient = ModelEncryptionSupport.encryptClient(c, p.key);
+        Client c2 = ModelEncryptionSupport.decryptClient(encryptedClient, p.key);
+
+        assertEquals(c.getClientId(), c2.getClientId());
+        assertEquals(c.getClientSecret(), c2.getClientSecret());
+        assertNull(c2.getClientSecret());
+        assertFalse(c2.isConfidential());
+        assertEquals("subject", c2.getSubject().getLogin());
+        assertEquals("id", c2.getSubject().getId());
+    }
+
     @Test
     public void testCodeGrantJSON() throws Exception {
         Client c = new Client("client", "secret", true);