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

(cxf) branch coheigea/CXF-9240 created (now 5d5da1b5ea6)

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

coheigea pushed a change to branch coheigea/CXF-9240
in repository https://gitbox.apache.org/repos/asf/cxf.git


      at 5d5da1b5ea6 CXF-9240 - OAuthJSONProvider.appendJsonPair() does not escape JSON string values

This branch includes the following new commits:

     new 5d5da1b5ea6 CXF-9240 - OAuthJSONProvider.appendJsonPair() does not escape JSON string values

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: CXF-9240 - OAuthJSONProvider.appendJsonPair() does not escape JSON string values

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

coheigea pushed a commit to branch coheigea/CXF-9240
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 5d5da1b5ea6c89b34f597173d7f014f585ed8256
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Wed Aug 12 09:58:54 2026 +0100

    CXF-9240 - OAuthJSONProvider.appendJsonPair() does not escape JSON string values
---
 .../oauth2/provider/OAuthJSONProvider.java         | 125 ++++++---------------
 .../oauth2/provider/OAuthJSONProviderTest.java     |  72 +++++++++++-
 2 files changed, 107 insertions(+), 90 deletions(-)

diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/OAuthJSONProvider.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/OAuthJSONProvider.java
index 23ce43525a6..b5d7b26fd01 100644
--- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/OAuthJSONProvider.java
+++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/OAuthJSONProvider.java
@@ -24,8 +24,10 @@ import java.io.OutputStream;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -76,144 +78,89 @@ public class OAuthJSONProvider implements MessageBodyWriter<Object>,
     }
 
     private void writeTokenIntrospection(TokenIntrospection obj, OutputStream os) throws IOException {
-        StringBuilder sb = new StringBuilder();
-        sb.append('{');
-        appendJsonPair(sb, "active", obj.isActive(), false);
+        Map<String, Object> response = new LinkedHashMap<>();
+        response.put("active", obj.isActive());
         if (obj.isActive()) {
             if (obj.getClientId() != null) {
-                sb.append(',');
-                appendJsonPair(sb, OAuthConstants.CLIENT_ID, obj.getClientId());
+                response.put(OAuthConstants.CLIENT_ID, obj.getClientId());
             }
             if (obj.getUsername() != null) {
-                sb.append(',');
-                appendJsonPair(sb, "username", obj.getUsername());
+                response.put("username", obj.getUsername());
             }
             if (obj.getTokenType() != null) {
-                sb.append(',');
-                appendJsonPair(sb, OAuthConstants.ACCESS_TOKEN_TYPE, obj.getTokenType());
+                response.put(OAuthConstants.ACCESS_TOKEN_TYPE, obj.getTokenType());
             }
             if (obj.getScope() != null) {
-                sb.append(',');
-                appendJsonPair(sb, OAuthConstants.SCOPE, obj.getScope());
+                response.put(OAuthConstants.SCOPE, obj.getScope());
             }
             if (!StringUtils.isEmpty(obj.getAud())) {
-                sb.append(',');
                 if (obj.getAud().size() == 1) {
-                    appendJsonPair(sb, "aud", obj.getAud().get(0));
+                    response.put("aud", obj.getAud().get(0));
                 } else {
-                    StringBuilder arr = new StringBuilder();
-                    arr.append('[');
-                    List<String> auds = obj.getAud();
-                    for (int i = 0; i < auds.size(); i++) {
-                        if (i > 0) {
-                            arr.append(',');
-                        }
-                        arr.append('"').append(auds.get(i)).append('"');
-                    }
-                    arr.append(']');
-                    appendJsonPair(sb, "aud", arr.toString(), false);
-
+                    response.put("aud", new ArrayList<>(obj.getAud()));
                 }
             }
             if (obj.getIss() != null) {
-                sb.append(',');
-                appendJsonPair(sb, "iss", obj.getIss());
+                response.put("iss", obj.getIss());
             }
-            sb.append(',');
-            appendJsonPair(sb, "iat", obj.getIat(), false);
+            response.put("iat", obj.getIat());
             if (obj.getExp() != null) {
-                sb.append(',');
-                appendJsonPair(sb, "exp", obj.getExp(), false);
+                response.put("exp", obj.getExp());
             }
             if (obj.getNbf() != null) {
-                sb.append(',');
-                appendJsonPair(sb, "nbf", obj.getNbf(), false);
+                response.put("nbf", obj.getNbf());
             }
             if (!obj.getExtensions().isEmpty()) {
                 for (Map.Entry<String, String> entry : obj.getExtensions().entrySet()) {
-                    sb.append(',');
                     if (JoseConstants.HEADER_X509_THUMBPRINT_SHA256.equals(entry.getKey())) {
-                        StringBuilder cnfObj = new StringBuilder();
-                        cnfObj.append('{');
-                        appendJsonPair(cnfObj, entry.getKey(), entry.getValue());
-                        cnfObj.append('}');
-                        appendJsonPair(sb, JwtConstants.CLAIM_CONFIRMATION, cnfObj.toString(), false);
+                        Map<String, Object> cnfObj = new LinkedHashMap<>();
+                        cnfObj.put(entry.getKey(), entry.getValue());
+                        response.put(JwtConstants.CLAIM_CONFIRMATION, cnfObj);
                     } else {
-                        appendJsonPair(sb, entry.getKey(), entry.getValue());
+                        response.put(entry.getKey(), entry.getValue());
                     }
                 }
             }
         }
-        sb.append('}');
-        String result = sb.toString();
-        os.write(result.getBytes(StandardCharsets.UTF_8));
-        os.flush();
-
+        writeJson(response, os);
     }
 
     private void writeOAuthError(OAuthError obj, OutputStream os) throws IOException {
-        StringBuilder sb = new StringBuilder();
-        sb.append('{');
-        appendJsonPair(sb, OAuthConstants.ERROR_KEY, obj.getError());
+        Map<String, Object> response = new LinkedHashMap<>();
+        response.put(OAuthConstants.ERROR_KEY, obj.getError());
         if (obj.getErrorDescription() != null) {
-            sb.append(',');
-            appendJsonPair(sb, OAuthConstants.ERROR_DESCRIPTION_KEY, obj.getErrorDescription());
+            response.put(OAuthConstants.ERROR_DESCRIPTION_KEY, obj.getErrorDescription());
         }
         if (obj.getErrorUri() != null) {
-            sb.append(',');
-            appendJsonPair(sb, OAuthConstants.ERROR_URI_KEY, obj.getErrorUri());
+            response.put(OAuthConstants.ERROR_URI_KEY, obj.getErrorUri());
         }
-
-        sb.append('}');
-        String result = sb.toString();
-        os.write(result.getBytes(StandardCharsets.UTF_8));
-        os.flush();
+        writeJson(response, os);
     }
 
     private void writeAccessToken(ClientAccessToken obj, OutputStream os) throws IOException {
-        StringBuilder sb = new StringBuilder();
-        sb.append('{');
-        appendJsonPair(sb, OAuthConstants.ACCESS_TOKEN, obj.getTokenKey());
-        sb.append(',');
-        appendJsonPair(sb, OAuthConstants.ACCESS_TOKEN_TYPE, obj.getTokenType());
+        Map<String, Object> response = new LinkedHashMap<>();
+        response.put(OAuthConstants.ACCESS_TOKEN, obj.getTokenKey());
+        response.put(OAuthConstants.ACCESS_TOKEN_TYPE, obj.getTokenType());
         if (obj.getExpiresIn() != -1) {
-            sb.append(',');
-            appendJsonPair(sb, OAuthConstants.ACCESS_TOKEN_EXPIRES_IN, obj.getExpiresIn(), false);
+            response.put(OAuthConstants.ACCESS_TOKEN_EXPIRES_IN, obj.getExpiresIn());
         }
         if (obj.getApprovedScope() != null) {
-            sb.append(',');
-            appendJsonPair(sb, OAuthConstants.SCOPE, obj.getApprovedScope());
+            response.put(OAuthConstants.SCOPE, obj.getApprovedScope());
         }
         if (obj.getRefreshToken() != null) {
-            sb.append(',');
-            appendJsonPair(sb, OAuthConstants.REFRESH_TOKEN, obj.getRefreshToken());
+            response.put(OAuthConstants.REFRESH_TOKEN, obj.getRefreshToken());
         }
         Map<String, String> parameters = obj.getParameters();
         for (Map.Entry<String, String> entry : parameters.entrySet()) {
-            sb.append(',');
-            appendJsonPair(sb, entry.getKey(), entry.getValue());
+            response.put(entry.getKey(), entry.getValue());
         }
-        sb.append('}');
-        String result = sb.toString();
-        os.write(result.getBytes(StandardCharsets.UTF_8));
-        os.flush();
-    }
-
-    private void appendJsonPair(StringBuilder sb, String key, Object value) {
-        appendJsonPair(sb, key, value, true);
+        writeJson(response, os);
     }
 
-    private void appendJsonPair(StringBuilder sb, String key, Object value,
-                                boolean valueQuote) {
-        sb.append('"').append(key).append('"');
-        sb.append(':');
-        if (valueQuote) {
-            sb.append('"');
-        }
-        sb.append(value);
-        if (valueQuote) {
-            sb.append('"');
-        }
+    private void writeJson(Map<String, Object> response, OutputStream os) throws IOException {
+        String result = new JsonMapObjectReaderWriter().toJson(response);
+        os.write(result.getBytes(StandardCharsets.UTF_8));
+        os.flush();
     }
 
     public boolean isReadable(Class<?> cls, Type t, Annotation[] anns, MediaType mt) {
diff --git a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/OAuthJSONProviderTest.java b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/OAuthJSONProviderTest.java
index adb6adbd9d5..75cd4ecaac2 100644
--- a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/OAuthJSONProviderTest.java
+++ b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/OAuthJSONProviderTest.java
@@ -23,13 +23,17 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import jakarta.ws.rs.core.MediaType;
 import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter;
 import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken;
+import org.apache.cxf.rs.security.oauth2.common.OAuthError;
 import org.apache.cxf.rs.security.oauth2.common.TokenIntrospection;
 import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
 
@@ -252,4 +256,70 @@ public class OAuthJSONProviderTest {
                      macToken.getParameters().get(OAuthConstants.HAWK_TOKEN_ALGORITHM));
     }
 
-}
\ No newline at end of file
+    @Test
+    public void testWriteClientAccessTokenEscapesJsonStringValues() throws Exception {
+        ClientAccessToken token = new ClientAccessToken(OAuthConstants.BEARER_TOKEN_TYPE, "1234");
+        token.setExpiresIn(10);
+        token.setApprovedScope("read\"write\\scope\nnext\tstep");
+        token.setParameters(Collections.singletonMap("custom", "value\"\\\r\n"));
+
+        OAuthJSONProvider provider = new OAuthJSONProvider();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        provider.writeTo(token,
+                         ClientAccessToken.class,
+                         ClientAccessToken.class,
+                         new Annotation[] {},
+                         MediaType.APPLICATION_JSON_TYPE,
+                         new MetadataMap<String, Object>(),
+                         bos);
+
+        Map<String, Object> parsed = new JsonMapObjectReaderWriter().fromJson(bos.toString());
+        assertEquals("read\"write\\scope\nnext\tstep", parsed.get(OAuthConstants.SCOPE));
+        assertEquals("value\"\\\r\n", parsed.get("custom"));
+        assertFalse(parsed.containsKey("scope\""));
+    }
+
+    @Test
+    public void testWriteOAuthErrorEscapesErrorDescription() throws Exception {
+        OAuthError error = new OAuthError("invalid_request", "bad \"request\"\\line\nnext\rline\tend");
+
+        OAuthJSONProvider provider = new OAuthJSONProvider();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        provider.writeTo(error,
+                         OAuthError.class,
+                         OAuthError.class,
+                         new Annotation[] {},
+                         MediaType.APPLICATION_JSON_TYPE,
+                         new MetadataMap<String, Object>(),
+                         bos);
+
+        Map<String, Object> parsed = new JsonMapObjectReaderWriter().fromJson(bos.toString());
+        assertEquals("invalid_request", parsed.get(OAuthConstants.ERROR_KEY));
+        assertEquals("bad \"request\"\\line\nnext\rline\tend", parsed.get(OAuthConstants.ERROR_DESCRIPTION_KEY));
+    }
+
+    @Test
+    public void testWriteTokenIntrospectionEscapesAudienceValues() throws Exception {
+        TokenIntrospection introspection = new TokenIntrospection(true);
+        introspection.setIat(10L);
+        introspection.setAud(Arrays.asList("https://a.example/\"quoted\"", "https://b.example/path\\slash\nnext"));
+
+        OAuthJSONProvider provider = new OAuthJSONProvider();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        provider.writeTo(introspection,
+                         TokenIntrospection.class,
+                         TokenIntrospection.class,
+                         new Annotation[] {},
+                         MediaType.APPLICATION_JSON_TYPE,
+                         new MetadataMap<String, Object>(),
+                         bos);
+
+        Map<String, Object> parsed = new JsonMapObjectReaderWriter().fromJson(bos.toString());
+        @SuppressWarnings("unchecked")
+        List<String> aud = (List<String>)parsed.get("aud");
+        assertEquals(2, aud.size());
+        assertEquals("https://a.example/\"quoted\"", aud.get(0));
+        assertEquals("https://b.example/path\\slash\nnext", aud.get(1));
+    }
+
+}