Skip to content

Commit

Permalink
[Java Client] Make Audience Field Optional in OAuth2 Client Credentia…
Browse files Browse the repository at this point in the history
…ls (#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
  • Loading branch information
michaeljmarshall authored Dec 3, 2021
1 parent 6c1dcda commit b2b5463
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -45,9 +45,9 @@ public static Authentication clientCredentials(URL issuerUrl, URL credentialsUrl
*
* @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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ public void close() throws Exception {
*/
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,21 @@ public void close() throws Exception {

/**
* 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 {
Expand All @@ -96,15 +107,7 @@ String buildClientCredentialsBody(Map<String, String> bodyMap) {
*/
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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ public void testConfigure() throws Exception {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,13 @@ public void exchangeClientCredentialsSuccessByScopeTest() throws
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);
Expand All @@ -80,22 +74,16 @@ public void exchangeClientCredentialsSuccessByScopeTest() throws

@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);
Expand Down
4 changes: 2 additions & 2 deletions site2/docs/security-oauth2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| `scope` | Scope of an access request. <br />For more more information, see [access token scope](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3). | api://pulsar-cluster-1/.default | 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`.
Expand Down Expand Up @@ -64,7 +64,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

Expand Down
4 changes: 2 additions & 2 deletions site2/website-next/docs/security-oauth2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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><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 |
| `scope` | Scope of an access request. <br />For more more information, see [access token scope](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3). | api://pulsar-cluster-1/.default | 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`.
Expand Down Expand Up @@ -68,7 +68,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

Expand Down

0 comments on commit b2b5463

Please sign in to comment.