You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by lh...@apache.org on 2026/10/23 10:08:51 UTC
(pulsar) 20/23: [fix][client] Read the OAuth2 private key without requiring a content type (#20824)
This is an automated email from the ASF dual-hosted git repository.
lhotari pushed a commit to branch branch-5.0
in repository https://gitbox.apache.org/repos/asf/pulsar.git
commit 6f26ad38abc72aa867da8c6df0f7e30ee615a6f4
Author: Ozan Gunalp <oz...@gmail.com>
AuthorDate: Fri Oct 23 03:48:17 2026 +0200
[fix][client] Read the OAuth2 private key without requiring a content type (#20824)
Co-authored-by: Matteo Merli <mm...@apache.org>
(cherry picked from commit baf5ac0320ef50ecd3583215c0c3cadd99de309b)
---
.../impl/auth/oauth2/ClientCredentialsFlow.java | 32 ++++----
.../auth/oauth2/ClientCredentialsFlowTest.java | 94 ++++++++++++++++++++++
pulsar-client/src/test/resources/oauth2_key.json | 7 ++
3 files changed, 119 insertions(+), 14 deletions(-)
diff --git a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlow.java b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlow.java
index 735b655194a7..00067101b254 100644
--- a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlow.java
+++ b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlow.java
@@ -18,8 +18,8 @@
*/
package org.apache.pulsar.client.impl.auth.oauth2;
+import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
-import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URISyntaxException;
@@ -123,22 +123,12 @@ class ClientCredentialsFlow extends FlowBase {
* @return
* @throws IOException
*/
- private static KeyFile loadPrivateKey(String privateKeyURL) throws IOException {
+ @VisibleForTesting
+ static KeyFile loadPrivateKey(String privateKeyURL) throws IOException {
try {
URLConnection urlConnection = new org.apache.pulsar.client.api.url.URL(privateKeyURL).openConnection();
try {
- String protocol = urlConnection.getURL().getProtocol();
- String contentType = urlConnection.getContentType();
- if ("data".equals(protocol) && !"application/json".equals(contentType)) {
- throw new IllegalArgumentException(
- "Unsupported media type or encoding format: " + urlConnection.getContentType());
- }
- KeyFile privateKey;
- try (Reader r = new InputStreamReader((InputStream) urlConnection.getContent(),
- StandardCharsets.UTF_8)) {
- privateKey = KeyFile.fromJson(r);
- }
- return privateKey;
+ return loadPrivateKey(urlConnection);
} finally {
IOUtils.close(urlConnection);
}
@@ -147,6 +137,20 @@ class ClientCredentialsFlow extends FlowBase {
}
}
+ @VisibleForTesting
+ static KeyFile loadPrivateKey(URLConnection urlConnection) throws IOException {
+ String protocol = urlConnection.getURL().getProtocol();
+ if ("data".equals(protocol) && !"application/json".equals(urlConnection.getContentType())) {
+ throw new IllegalArgumentException(
+ "Unsupported media type or encoding format: " + urlConnection.getContentType());
+ }
+ // Read the stream directly: getContent() needs a content type to pick a handler, which not every
+ // URL handler reports
+ try (Reader r = new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8)) {
+ return KeyFile.fromJson(r);
+ }
+ }
+
@Override
public void initialize() throws PulsarClientException {
super.initialize();
diff --git a/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlowTest.java b/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlowTest.java
new file mode 100644
index 000000000000..46955dd85883
--- /dev/null
+++ b/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlowTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pulsar.client.impl.auth.oauth2;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import org.testng.annotations.Test;
+
+public class ClientCredentialsFlowTest {
+
+ private static final String KEY_JSON_BASE64 = "ewogICJ0eXBlIjogInR5cGUiLAogICJjbGllbnRfaWQiOiAiY2xpZW50LWlkIiwKICAi"
+ + "Y2xpZW50X3NlY3JldCI6ICJjbGllbnQtc2VjcmV0IiwKICAiY2xpZW50X2VtYWlsIjogImNsaWVudC1lbWFpbCIsCiAgImlzc3Vl"
+ + "cl91cmwiOiAiaHR0cHM6Ly9pc3N1ZXItdXJsLyIKfQ==";
+
+ private static void assertKeyFile(KeyFile keyFile) {
+ assertEquals(keyFile.getType(), "type");
+ assertEquals(keyFile.getClientId(), "client-id");
+ assertEquals(keyFile.getClientSecret(), "client-secret");
+ assertEquals(keyFile.getClientEmail(), "client-email");
+ assertEquals(keyFile.getIssuerUrl(), "https://issuer-url/");
+ }
+
+ private static URL keyResource() {
+ return ClientCredentialsFlowTest.class.getResource("/oauth2_key.json");
+ }
+
+ @Test
+ public void testLoadPrivateKeyFromFileUrl() throws Exception {
+ assertKeyFile(ClientCredentialsFlow.loadPrivateKey(keyResource().toString()));
+ }
+
+ @Test
+ public void testLoadPrivateKeyFromPath() throws Exception {
+ assertKeyFile(ClientCredentialsFlow.loadPrivateKey(new File(keyResource().toURI()).getAbsolutePath()));
+ }
+
+ @Test
+ public void testLoadPrivateKeyFromDataUrl() throws Exception {
+ assertKeyFile(ClientCredentialsFlow.loadPrivateKey("data:application/json;base64," + KEY_JSON_BASE64));
+ }
+
+ @Test
+ public void testLoadPrivateKeyRejectsDataUrlWithoutJsonContentType() {
+ assertThrows(IllegalArgumentException.class,
+ () -> ClientCredentialsFlow.loadPrivateKey("data:;base64," + KEY_JSON_BASE64));
+ }
+
+ @Test
+ public void testLoadPrivateKeyFromConnectionWithoutContentType() throws Exception {
+ URL resource = keyResource();
+ // Stands in for a connection from a custom URL handler that does not report a content type,
+ // for which URLConnection.getContent() fails with "no content-type"
+ URLConnection connection = new URLConnection(URI.create("http://localhost/oauth2_key.json").toURL()) {
+ @Override
+ public void connect() {
+ }
+
+ @Override
+ public String getContentType() {
+ return null;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return resource.openStream();
+ }
+ };
+ assertNull(connection.getContentType());
+ assertKeyFile(ClientCredentialsFlow.loadPrivateKey(connection));
+ }
+}
diff --git a/pulsar-client/src/test/resources/oauth2_key.json b/pulsar-client/src/test/resources/oauth2_key.json
new file mode 100644
index 000000000000..bf9e0de1c9fb
--- /dev/null
+++ b/pulsar-client/src/test/resources/oauth2_key.json
@@ -0,0 +1,7 @@
+{
+ "type": "type",
+ "client_id": "client-id",
+ "client_secret": "client-secret",
+ "client_email": "client-email",
+ "issuer_url": "https://issuer-url/"
+}