Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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` |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional for client. => Optional for java client.?

Just a note, we also need to make this field optional for other language clients that support oauth authentication (e.g. cpp, python, golang), otherwise, there may be inconsistent behavior, e.g. for java language this field is optional and for cpp this field is required, can you create several issues, in order for us to track this thing


### 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<>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why do you use a TreeMap here? I'm not sure if the order makes a difference.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only a TreeMap because I copied it from exchangeClientCredentials. I don't remember any specific reason for using this data structure.

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 |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if updating this field to be optional will cause confusion for users using other languages, one suggestion I have is to keep this field as required until other languages implement this change

| `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