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.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 void writeTo(Object obj, Class cls, Type t, Annotation[] anns, MediaTy } private void writeTokenIntrospection(TokenIntrospection obj, OutputStream os) throws IOException { - StringBuilder sb = new StringBuilder(); - sb.append('{'); - appendJsonPair(sb, "active", obj.isActive(), false); + Map 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 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 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 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 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 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 parameters = obj.getParameters(); for (Map.Entry 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 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.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 void testReadHawkClientAccessToken() throws Exception { 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(), + bos); + + Map 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(), + bos); + + Map 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(), + bos); + + Map parsed = new JsonMapObjectReaderWriter().fromJson(bos.toString()); + @SuppressWarnings("unchecked") + List aud = (List)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)); + } + +}