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 2021/12/03 17:29:00 UTC

[pulsar] 01/04: [Java Client] Make Audience Field Optional in OAuth2 Client Credentials (#11988)

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

lhotari pushed a commit to branch branch-2.9
in repository https://gitbox.apache.org/repos/asf/pulsar.git

commit 06c344f2a60ffa995be81669059be1cd6ef6851e
Author: Michael Marshall <mi...@datastax.com>
AuthorDate: Thu Dec 2 22:55:44 2021 -0600

    [Java Client] Make Audience Field Optional in OAuth2 Client Credentials (#11988)
    
    * [Java Client] Make Audience Field in Client Credentials Optional
    
    * Update site2/website-next/docs
    
    * Remove update to 2.8.1 docs
    
    * Update more docs based on code review
    
    (cherry picked from commit b2b5463a574806b611c4065a0a27c154e7cbe0bd)
---
 .../auth/oauth2/AuthenticationFactoryOAuth2.java   |  6 +++---
 .../impl/auth/oauth2/ClientCredentialsFlow.java    |  4 ++--
 .../pulsar/client/impl/auth/oauth2/README.md       |  4 ++--
 .../impl/auth/oauth2/protocol/TokenClient.java     | 25 ++++++++++++----------
 .../impl/auth/oauth2/AuthenticationOAuth2Test.java | 13 +++++++++++
 .../impl/auth/oauth2/protocol/TokenClientTest.java | 18 +++-------------
 site2/docs/security-oauth2.md                      |  4 ++--
 site2/website-next/docs/security-oauth2.md         |  4 ++--
 8 files changed, 41 insertions(+), 37 deletions(-)

diff --git a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/AuthenticationFactoryOAuth2.java b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/AuthenticationFactoryOAuth2.java
index 707fcaf..cf56774 100644
--- a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/AuthenticationFactoryOAuth2.java
+++ b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/AuthenticationFactoryOAuth2.java
@@ -33,7 +33,7 @@ public final class AuthenticationFactoryOAuth2 {
      *
      * @param issuerUrl the issuer URL
      * @param credentialsUrl the credentials URL
-     * @param audience the audience identifier
+     * @param audience An optional field. The audience identifier used by some Identity Providers, like Auth0.
      * @return an Authentication object
      */
     public static Authentication clientCredentials(URL issuerUrl, URL credentialsUrl, String audience) {
@@ -45,9 +45,9 @@ public final class AuthenticationFactoryOAuth2 {
      *
      * @param issuerUrl the issuer URL
      * @param credentialsUrl the credentials URL
-     * @param audience the audience identifier
+     * @param audience An optional field. The audience identifier used by some Identity Providers, like Auth0.
      * @param scope An optional field. The value of the scope parameter is expressed as a list of space-delimited,
-     *              case-sensitive strings.  The strings are defined by the authorization server.
+     *              case-sensitive strings. The strings are defined by the authorization server.
      *              If the value contains multiple space-delimited strings, their order does not matter,
      *              and each string adds an additional access range to the requested scope.
      *              From here: https://datatracker.ietf.org/doc/html/rfc6749#section-4.4.2
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 b011e85..bf0c289 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
@@ -118,10 +118,10 @@ class ClientCredentialsFlow extends FlowBase {
      */
     public static ClientCredentialsFlow fromParameters(Map<String, String> params) {
         URL issuerUrl = parseParameterUrl(params, CONFIG_PARAM_ISSUER_URL);
-        String audience = parseParameterString(params, CONFIG_PARAM_AUDIENCE);
         String privateKeyUrl = parseParameterString(params, CONFIG_PARAM_KEY_FILE);
-        // This is an optional parameter
+        // These are optional parameters, so we only perform a get
         String scope = params.get(CONFIG_PARAM_SCOPE);
+        String audience = params.get(CONFIG_PARAM_AUDIENCE);
         return ClientCredentialsFlow.builder()
                 .issuerUrl(issuerUrl)
                 .audience(audience)
diff --git a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/README.md b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/README.md
index 55ffe58..b8b1237 100644
--- a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/README.md
+++ b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/README.md
@@ -46,7 +46,7 @@ The following parameters are supported:
 | `type` | Oauth 2.0 auth type. Optional. | default: `client_credentials`  |
 | `issuerUrl` | URL of the provider which allows Pulsar to obtain an access token. Required. | `https://accounts.google.com` |
 | `privateKey` | URL to a JSON credentials file (in JSON format; see below). Required. | See "Supported Pattern Formats" |
-| `audience`  | An OAuth 2.0 "resource server" identifier for the Pulsar cluster. Required. | `https://broker.example.com` |
+| `audience`  | An OAuth 2.0 "resource server" identifier for the Pulsar cluster. Required by some Identity Providers. Optional for client. | `https://broker.example.com` |
 
 ### Supported Pattern Formats of `privateKey`
 The `privateKey` parameter supports the following three pattern formats, and contains client Credentials:
@@ -88,7 +88,7 @@ curl --request POST \
 In which,
 - `issuerUrl` parameter in this plugin is mapped to `--url https://dev-kt-aa9ne.us.auth0.com`
 - `privateKey` file parameter in this plugin should at least contains fields `client_id` and `client_secret`.
-- `audience` parameter in this plugin is mapped to  `"audience":"https://dev-kt-aa9ne.us.auth0.com/api/v2/"`
+- `audience` parameter in this plugin is mapped to  `"audience":"https://dev-kt-aa9ne.us.auth0.com/api/v2/"`. This field is only used by some identity providers.
 
 ## Pulsar Client Config
 You can use the provider with the following Pulsar clients.
diff --git a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/protocol/TokenClient.java b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/protocol/TokenClient.java
index f8667e8..c2b9779 100644
--- a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/protocol/TokenClient.java
+++ b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/protocol/TokenClient.java
@@ -73,10 +73,21 @@ public class TokenClient implements ClientCredentialsExchanger {
 
     /**
      * Constructing http request parameters.
-     * @param bodyMap List of parameters to be requested.
+     * @param req object with relevant request parameters
      * @return Generate the final request body from a map.
      */
-    String buildClientCredentialsBody(Map<String, String> bodyMap) {
+    String buildClientCredentialsBody(ClientCredentialsExchangeRequest req) {
+        Map<String, String> bodyMap = new TreeMap<>();
+        bodyMap.put("grant_type", "client_credentials");
+        bodyMap.put("client_id", req.getClientId());
+        bodyMap.put("client_secret", req.getClientSecret());
+        // Only set audience and scope if they are non-empty.
+        if (!StringUtils.isBlank(req.getAudience())) {
+            bodyMap.put("audience", req.getAudience());
+        }
+        if (!StringUtils.isBlank(req.getScope())) {
+            bodyMap.put("scope", req.getScope());
+        }
         return bodyMap.entrySet().stream()
                 .map(e -> {
                     try {
@@ -96,15 +107,7 @@ public class TokenClient implements ClientCredentialsExchanger {
      */
     public TokenResult exchangeClientCredentials(ClientCredentialsExchangeRequest req)
             throws TokenExchangeException, IOException {
-        Map<String, String> bodyMap = new TreeMap<>();
-        bodyMap.put("grant_type", "client_credentials");
-        bodyMap.put("client_id", req.getClientId());
-        bodyMap.put("client_secret", req.getClientSecret());
-        bodyMap.put("audience", req.getAudience());
-        if (!StringUtils.isBlank(req.getScope())) {
-            bodyMap.put("scope", req.getScope());
-        }
-        String body = buildClientCredentialsBody(bodyMap);
+        String body = buildClientCredentialsBody(req);
 
         try {
 
diff --git a/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/AuthenticationOAuth2Test.java b/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/AuthenticationOAuth2Test.java
index ac14dd2..3ae578c 100644
--- a/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/AuthenticationOAuth2Test.java
+++ b/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/AuthenticationOAuth2Test.java
@@ -86,6 +86,19 @@ public class AuthenticationOAuth2Test {
         params.put("privateKey", "data:base64,e30=");
         params.put("issuerUrl", "http://localhost");
         params.put("audience", "http://localhost");
+        params.put("scope", "http://localhost");
+        ObjectMapper mapper = new ObjectMapper();
+        String authParams = mapper.writeValueAsString(params);
+        this.auth.configure(authParams);
+        assertNotNull(this.auth.flow);
+    }
+
+    @Test
+    public void testConfigureWithoutOptionalParams() throws Exception {
+        Map<String, String> params = new HashMap<>();
+        params.put("type", "client_credentials");
+        params.put("privateKey", "data:base64,e30=");
+        params.put("issuerUrl", "http://localhost");
         ObjectMapper mapper = new ObjectMapper();
         String authParams = mapper.writeValueAsString(params);
         this.auth.configure(authParams);
diff --git a/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/protocol/TokenClientTest.java b/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/protocol/TokenClientTest.java
index 1617359..da70d6c 100644
--- a/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/protocol/TokenClientTest.java
+++ b/pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/protocol/TokenClientTest.java
@@ -47,19 +47,13 @@ public class TokenClientTest {
         DefaultAsyncHttpClient defaultAsyncHttpClient = mock(DefaultAsyncHttpClient.class);
         URL url = new URL("http://localhost");
         TokenClient tokenClient = new TokenClient(url, defaultAsyncHttpClient);
-        Map<String, String> bodyMap = new TreeMap<>();
         ClientCredentialsExchangeRequest request = ClientCredentialsExchangeRequest.builder()
                 .audience("test-audience")
                 .clientId("test-client-id")
                 .clientSecret("test-client-secret")
                 .scope("test-scope")
                 .build();
-        bodyMap.put("grant_type", "client_credentials");
-        bodyMap.put("client_id", request.getClientId());
-        bodyMap.put("client_secret", request.getClientSecret());
-        bodyMap.put("audience", request.getAudience());
-        bodyMap.put("scope", request.getScope());
-        String body = tokenClient.buildClientCredentialsBody(bodyMap);
+        String body = tokenClient.buildClientCredentialsBody(request);
         BoundRequestBuilder boundRequestBuilder = mock(BoundRequestBuilder.class);
         Response response = mock(Response.class);
         ListenableFuture<Response> listenableFuture = mock(ListenableFuture.class);
@@ -80,22 +74,16 @@ public class TokenClientTest {
 
     @Test
     @SuppressWarnings("unchecked")
-    public void exchangeClientCredentialsSuccessByNoScopeTest() throws
+    public void exchangeClientCredentialsSuccessWithoutOptionalClientCredentialsTest() throws
             IOException, TokenExchangeException, ExecutionException, InterruptedException {
         DefaultAsyncHttpClient defaultAsyncHttpClient = mock(DefaultAsyncHttpClient.class);
         URL url = new URL("http://localhost");
         TokenClient tokenClient = new TokenClient(url, defaultAsyncHttpClient);
-        Map<String, String> bodyMap = new TreeMap<>();
         ClientCredentialsExchangeRequest request = ClientCredentialsExchangeRequest.builder()
-                .audience("test-audience")
                 .clientId("test-client-id")
                 .clientSecret("test-client-secret")
                 .build();
-        bodyMap.put("grant_type", "client_credentials");
-        bodyMap.put("client_id", request.getClientId());
-        bodyMap.put("client_secret", request.getClientSecret());
-        bodyMap.put("audience", request.getAudience());
-        String body = tokenClient.buildClientCredentialsBody(bodyMap);
+        String body = tokenClient.buildClientCredentialsBody(request);
         BoundRequestBuilder boundRequestBuilder = mock(BoundRequestBuilder.class);
         Response response = mock(Response.class);
         ListenableFuture<Response> listenableFuture = mock(ListenableFuture.class);
diff --git a/site2/docs/security-oauth2.md b/site2/docs/security-oauth2.md
index 7ea9f35..35d0b5f 100644
--- a/site2/docs/security-oauth2.md
+++ b/site2/docs/security-oauth2.md
@@ -28,7 +28,7 @@ The following table lists parameters supported for the `client credentials` auth
 | `type` | Oauth 2.0 authentication type. |  `client_credentials` (default) | Optional |
 | `issuerUrl` | URL of the authentication provider which allows the Pulsar client to obtain an access token | `https://accounts.google.com` | Required |
 | `privateKey` | URL to a JSON credentials file  | Support the following pattern formats: <br> <li> `file:///path/to/file` <li>`file:/path/to/file` <li> `data:application/json;base64,<base64-encoded value>` | Required |
-| `audience`  | An OAuth 2.0 "resource server" identifier for the Pulsar cluster | `https://broker.example.com` | Required |
+| `audience`  | An OAuth 2.0 "resource server" identifier for the Pulsar cluster | `https://broker.example.com` | Optional |
 
 The credentials file contains service account credentials used with the client authentication type. The following shows an example of a credentials file `credentials_file.json`.
 
@@ -63,7 +63,7 @@ In the above example, the mapping relationship is shown as below.
 
 - The `issuerUrl` parameter in this plugin is mapped to `--url https://dev-kt-aa9ne.us.auth0.com`.
 - The `privateKey` file parameter in this plugin should at least contains the `client_id` and `client_secret` fields.
-- The `audience` parameter in this plugin is mapped to  `"audience":"https://dev-kt-aa9ne.us.auth0.com/api/v2/"`.
+- The `audience` parameter in this plugin is mapped to  `"audience":"https://dev-kt-aa9ne.us.auth0.com/api/v2/"`. This field is only used by some identity providers.
 
 ## Client Configuration
 
diff --git a/site2/website-next/docs/security-oauth2.md b/site2/website-next/docs/security-oauth2.md
index 19c3789..820a696 100644
--- a/site2/website-next/docs/security-oauth2.md
+++ b/site2/website-next/docs/security-oauth2.md
@@ -32,7 +32,7 @@ The following table lists parameters supported for the `client credentials` auth
 | `type` | Oauth 2.0 authentication type. |  `client_credentials` (default) | Optional |
 | `issuerUrl` | URL of the authentication provider which allows the Pulsar client to obtain an access token | `https://accounts.google.com` | Required |
 | `privateKey` | URL to a JSON credentials file  | Support the following pattern formats: <br /> <li> `file:///path/to/file` </li><li>`file:/path/to/file` </li><li> `data:application/json;base64,<base64-encoded value>` </li>| Required |
-| `audience`  | An OAuth 2.0 "resource server" identifier for the Pulsar cluster | `https://broker.example.com` | Required |
+| `audience`  | An OAuth 2.0 "resource server" identifier for the Pulsar cluster | `https://broker.example.com` | Optional |
 
 The credentials file contains service account credentials used with the client authentication type. The following shows an example of a credentials file `credentials_file.json`.
 
@@ -71,7 +71,7 @@ In the above example, the mapping relationship is shown as below.
 
 - The `issuerUrl` parameter in this plugin is mapped to `--url https://dev-kt-aa9ne.us.auth0.com`.
 - The `privateKey` file parameter in this plugin should at least contains the `client_id` and `client_secret` fields.
-- The `audience` parameter in this plugin is mapped to  `"audience":"https://dev-kt-aa9ne.us.auth0.com/api/v2/"`.
+- The `audience` parameter in this plugin is mapped to  `"audience":"https://dev-kt-aa9ne.us.auth0.com/api/v2/"`. This field is only used by some identity providers.
 
 ## Client Configuration