Skip to content

Commit d4d9e7c

Browse files
committed
fix: send OAuth2 credentials request as form-urlencoded post
1 parent d05ef22 commit d4d9e7c

File tree

5 files changed

+107
-104
lines changed

5 files changed

+107
-104
lines changed

src/main/java/dev/openfga/sdk/api/auth/CredentialsFlowRequest.java

+23-70
Original file line numberDiff line numberDiff line change
@@ -12,91 +12,44 @@
1212

1313
package dev.openfga.sdk.api.auth;
1414

15-
import com.fasterxml.jackson.annotation.JsonCreator;
16-
import com.fasterxml.jackson.annotation.JsonInclude;
17-
import com.fasterxml.jackson.annotation.JsonProperty;
18-
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
15+
import java.net.URLEncoder;
16+
import java.nio.charset.StandardCharsets;
17+
import java.util.HashMap;
18+
import java.util.Map;
19+
import java.util.stream.Collectors;
1920

2021
/**
2122
* A credentials flow request. It contains a Client ID and Secret that can be exchanged for an access token.
2223
* <p>
2324
* {@see "https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow"}
2425
*/
25-
@JsonPropertyOrder({
26-
CredentialsFlowRequest.JSON_PROPERTY_CLIENT_ID,
27-
CredentialsFlowRequest.JSON_PROPERTY_CLIENT_SECRET,
28-
CredentialsFlowRequest.JSON_PROPERTY_AUDIENCE,
29-
CredentialsFlowRequest.JSON_PROPERTY_SCOPE,
30-
CredentialsFlowRequest.JSON_PROPERTY_GRANT_TYPE
31-
})
3226
class CredentialsFlowRequest {
33-
public static final String JSON_PROPERTY_CLIENT_ID = "client_id";
34-
private String clientId;
27+
public static final String CLIENT_ID_PARAM_NAME = "client_id";
28+
public static final String CLIENT_SECRET_PARAM_NAME = "client_secret";
29+
public static final String AUDIENCE_PARAM_NAME = "audience";
30+
public static final String SCOPE_PARAM_NAME = "scope";
31+
public static final String GRANT_TYPE_PARAM_NAME = "grant_type";
3532

36-
public static final String JSON_PROPERTY_CLIENT_SECRET = "client_secret";
37-
private String clientSecret;
33+
private final Map<String, String> parameters = new HashMap<>();
3834

39-
public static final String JSON_PROPERTY_AUDIENCE = "audience";
40-
private String audience;
41-
42-
public static final String JSON_PROPERTY_SCOPE = "scope";
43-
private String scope;
44-
45-
public static final String JSON_PROPERTY_GRANT_TYPE = "grant_type";
46-
private String grantType;
47-
48-
@JsonCreator
49-
public CredentialsFlowRequest() {}
50-
51-
@JsonProperty(JSON_PROPERTY_CLIENT_ID)
52-
public String getClientId() {
53-
return clientId;
54-
}
55-
56-
@JsonProperty(JSON_PROPERTY_CLIENT_ID)
57-
public void setClientId(String clientId) {
58-
this.clientId = clientId;
59-
}
60-
61-
@JsonProperty(JSON_PROPERTY_CLIENT_SECRET)
62-
public String getClientSecret() {
63-
return clientSecret;
64-
}
65-
66-
@JsonProperty(JSON_PROPERTY_CLIENT_SECRET)
67-
public void setClientSecret(String clientSecret) {
68-
this.clientSecret = clientSecret;
69-
}
70-
71-
@JsonProperty(JSON_PROPERTY_AUDIENCE)
72-
@JsonInclude(JsonInclude.Include.NON_EMPTY)
73-
public String getAudience() {
74-
return audience;
35+
public CredentialsFlowRequest(String clientId, String clientSecret) {
36+
this.parameters.put(CLIENT_ID_PARAM_NAME, clientId);
37+
this.parameters.put(CLIENT_SECRET_PARAM_NAME, clientSecret);
38+
this.parameters.put(GRANT_TYPE_PARAM_NAME, "client_credentials");
7539
}
7640

77-
@JsonProperty(JSON_PROPERTY_AUDIENCE)
78-
public void setAudience(String audience) {
79-
this.audience = audience;
80-
}
81-
82-
@JsonProperty(JSON_PROPERTY_SCOPE)
83-
@JsonInclude(JsonInclude.Include.NON_EMPTY)
84-
public String getScope() {
85-
return scope;
86-
}
87-
88-
@JsonProperty(JSON_PROPERTY_SCOPE)
8941
public void setScope(String scope) {
90-
this.scope = scope;
42+
this.parameters.put(SCOPE_PARAM_NAME, scope);
9143
}
9244

93-
@JsonProperty(JSON_PROPERTY_GRANT_TYPE)
94-
public String getGrantType() {
95-
return grantType;
45+
public void setAudience(String audience) {
46+
this.parameters.put(AUDIENCE_PARAM_NAME, audience);
9647
}
9748

98-
@JsonProperty(JSON_PROPERTY_GRANT_TYPE)
99-
public void setGrantType(String grantType) {
100-
this.grantType = grantType;
49+
public String buildFormRequestBody() {
50+
return parameters.entrySet().stream()
51+
.filter(e -> e.getValue() != null)
52+
.map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
53+
.collect(Collectors.joining("&"));
10154
}
10255
}

src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java

+9-17
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import dev.openfga.sdk.api.configuration.*;
1717
import dev.openfga.sdk.errors.ApiException;
1818
import dev.openfga.sdk.errors.FgaInvalidParameterException;
19-
import java.io.IOException;
2019
import java.net.URI;
2120
import java.net.http.HttpRequest;
2221
import java.time.Instant;
@@ -40,12 +39,10 @@ public OAuth2Client(Configuration configuration, ApiClient apiClient) throws Fga
4039

4140
this.apiClient = apiClient;
4241
this.apiTokenIssuer = buildApiTokenIssuer(clientCredentials.getApiTokenIssuer());
43-
this.authRequest = new CredentialsFlowRequest();
44-
this.authRequest.setClientId(clientCredentials.getClientId());
45-
this.authRequest.setClientSecret(clientCredentials.getClientSecret());
42+
this.authRequest =
43+
new CredentialsFlowRequest(clientCredentials.getClientId(), clientCredentials.getClientSecret());
4644
this.authRequest.setAudience(clientCredentials.getApiAudience());
4745
this.authRequest.setScope(clientCredentials.getScopes());
48-
this.authRequest.setGrantType("client_credentials");
4946
}
5047

5148
/**
@@ -72,21 +69,16 @@ public CompletableFuture<String> getAccessToken() throws FgaInvalidParameterExce
7269
*/
7370
private CompletableFuture<CredentialsFlowResponse> exchangeToken()
7471
throws ApiException, FgaInvalidParameterException {
75-
try {
76-
byte[] body = apiClient.getObjectMapper().writeValueAsBytes(authRequest);
77-
78-
Configuration config = new Configuration().apiUrl(apiTokenIssuer);
7972

80-
HttpRequest.Builder requestBuilder = ApiClient.requestBuilder("POST", "", body, config);
73+
Configuration config = new Configuration().apiUrl(apiTokenIssuer);
8174

82-
HttpRequest request = requestBuilder.build();
75+
HttpRequest.Builder requestBuilder =
76+
ApiClient.formRequestBuilder("POST", "", this.authRequest.buildFormRequestBody(), config);
77+
HttpRequest request = requestBuilder.build();
8378

84-
return new HttpRequestAttempt<>(request, "exchangeToken", CredentialsFlowResponse.class, apiClient, config)
85-
.attemptHttpRequest()
86-
.thenApply(ApiResponse::getData);
87-
} catch (IOException e) {
88-
throw new ApiException(e);
89-
}
79+
return new HttpRequestAttempt<>(request, "exchangeToken", CredentialsFlowResponse.class, apiClient, config)
80+
.attemptHttpRequest()
81+
.thenApply(ApiResponse::getData);
9082
}
9183

9284
private static String buildApiTokenIssuer(String issuer) throws FgaInvalidParameterException {

src/main/java/dev/openfga/sdk/api/client/ApiClient.java

+17
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,23 @@ public static HttpRequest.Builder requestBuilder(
106106
return builder;
107107
}
108108

109+
/**
110+
* Creates a {@link HttpRequest.Builder} for a {@code x-www-form-urlencoded} request.
111+
* @param method the HTTP method to be make.
112+
* @param path the URL path.
113+
* @param body the request body. It must be URL-encoded.
114+
* @param configuration the client configuration.
115+
* @return a configured builder.
116+
* @throws FgaInvalidParameterException
117+
*/
118+
public static HttpRequest.Builder formRequestBuilder(
119+
String method, String path, String body, Configuration configuration) throws FgaInvalidParameterException {
120+
HttpRequest.Builder builder =
121+
requestBuilder(method, path, HttpRequest.BodyPublishers.ofString(body), configuration);
122+
builder.header("content-type", "application/x-www-form-urlencoded");
123+
return builder;
124+
}
125+
109126
private static HttpRequest.Builder requestBuilder(
110127
String method, String path, HttpRequest.BodyPublisher bodyPublisher, Configuration configuration)
111128
throws FgaInvalidParameterException {

src/test/java/dev/openfga/sdk/api/auth/OAuth2ClientTest.java

+52-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1+
/*
2+
* OpenFGA
3+
* A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar.
4+
*
5+
* The version of the OpenAPI document: 0.1
6+
* Contact: [email protected]
7+
*
8+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
9+
* https://openapi-generator.tech
10+
* Do not edit the class manually.
11+
*/
12+
113
package dev.openfga.sdk.api.auth;
214

3-
import static org.hamcrest.Matchers.is;
15+
import static org.hamcrest.Matchers.allOf;
16+
import static org.hamcrest.core.StringContains.containsString;
417
import static org.junit.jupiter.api.Assertions.*;
518
import static org.mockito.Mockito.mock;
619
import static org.mockito.Mockito.when;
@@ -10,6 +23,8 @@
1023
import dev.openfga.sdk.api.client.ApiClient;
1124
import dev.openfga.sdk.api.configuration.*;
1225
import dev.openfga.sdk.errors.FgaInvalidParameterException;
26+
import java.net.URLEncoder;
27+
import java.nio.charset.StandardCharsets;
1328
import java.util.stream.Stream;
1429
import org.junit.jupiter.api.Test;
1530
import org.junit.jupiter.params.ParameterizedTest;
@@ -41,16 +56,25 @@ private static Stream<Arguments> apiTokenIssuers() {
4156
"https://issuer.fga.example:8080/some_endpoint"));
4257
}
4358

59+
private String urlEncode(String value) {
60+
return URLEncoder.encode(value, StandardCharsets.UTF_8);
61+
}
62+
4463
@ParameterizedTest
4564
@MethodSource("apiTokenIssuers")
4665
public void exchangeAuth0Token(String apiTokenIssuer, String tokenEndpointUrl) throws Exception {
4766
// Given
4867
OAuth2Client auth0 = newAuth0Client(apiTokenIssuer);
49-
String expectedPostBody = String.format(
50-
"{\"client_id\":\"%s\",\"client_secret\":\"%s\",\"audience\":\"%s\",\"grant_type\":\"%s\"}",
51-
CLIENT_ID, CLIENT_SECRET, AUDIENCE, GRANT_TYPE);
68+
5269
String responseBody = String.format("{\"access_token\":\"%s\"}", ACCESS_TOKEN);
53-
mockHttpClient.onPost(tokenEndpointUrl).withBody(is(expectedPostBody)).doReturn(200, responseBody);
70+
mockHttpClient
71+
.onPost(tokenEndpointUrl)
72+
.withBody(allOf(
73+
containsString(String.format("client_id=%s", CLIENT_ID)),
74+
containsString(String.format("client_secret=%s", CLIENT_SECRET)),
75+
containsString(String.format("audience=%s", AUDIENCE)),
76+
containsString(String.format("grant_type=%s", GRANT_TYPE))))
77+
.doReturn(200, responseBody);
5478

5579
// When
5680
String result = auth0.getAccessToken().get();
@@ -59,7 +83,12 @@ public void exchangeAuth0Token(String apiTokenIssuer, String tokenEndpointUrl) t
5983
mockHttpClient
6084
.verify()
6185
.post(tokenEndpointUrl)
62-
.withBody(is(expectedPostBody))
86+
.withBody(allOf(
87+
containsString(String.format("client_id=%s", CLIENT_ID)),
88+
containsString(String.format("client_secret=%s", CLIENT_SECRET)),
89+
containsString(String.format("audience=%s", AUDIENCE)),
90+
containsString(String.format("grant_type=%s", GRANT_TYPE))))
91+
.withHeader("Content-Type", "application/x-www-form-urlencoded")
6392
.called();
6493
assertEquals(ACCESS_TOKEN, result);
6594
}
@@ -68,21 +97,31 @@ public void exchangeAuth0Token(String apiTokenIssuer, String tokenEndpointUrl) t
6897
@MethodSource("apiTokenIssuers")
6998
public void exchangeOAuth2Token(String apiTokenIssuer, String tokenEndpointUrl) throws Exception {
7099
// Given
71-
OAuth2Client oAuth2 = newOAuth2Client(apiTokenIssuer);
72-
String expectedPostBody = String.format(
73-
"{\"client_id\":\"%s\",\"client_secret\":\"%s\",\"scope\":\"%s\",\"grant_type\":\"%s\"}",
74-
CLIENT_ID, CLIENT_SECRET, SCOPES, GRANT_TYPE);
100+
OAuth2Client auth0 = newOAuth2Client(apiTokenIssuer);
101+
75102
String responseBody = String.format("{\"access_token\":\"%s\"}", ACCESS_TOKEN);
76-
mockHttpClient.onPost(tokenEndpointUrl).withBody(is(expectedPostBody)).doReturn(200, responseBody);
103+
mockHttpClient
104+
.onPost(tokenEndpointUrl)
105+
.withBody(allOf(
106+
containsString(String.format("client_id=%s", CLIENT_ID)),
107+
containsString(String.format("client_secret=%s", CLIENT_SECRET)),
108+
containsString(String.format("scope=%s", urlEncode(SCOPES))),
109+
containsString(String.format("grant_type=%s", GRANT_TYPE))))
110+
.doReturn(200, responseBody);
77111

78112
// When
79-
String result = oAuth2.getAccessToken().get();
113+
String result = auth0.getAccessToken().get();
80114

81115
// Then
82116
mockHttpClient
83117
.verify()
84118
.post(tokenEndpointUrl)
85-
.withBody(is(expectedPostBody))
119+
.withBody(allOf(
120+
containsString(String.format("client_id=%s", CLIENT_ID)),
121+
containsString(String.format("client_secret=%s", CLIENT_SECRET)),
122+
containsString(String.format("scope=%s", urlEncode(SCOPES))),
123+
containsString(String.format("grant_type=%s", GRANT_TYPE))))
124+
.withHeader("Content-Type", "application/x-www-form-urlencoded")
86125
.called();
87126
assertEquals(ACCESS_TOKEN, result);
88127
}

src/test/java/dev/openfga/sdk/api/client/OpenFgaClientTest.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package dev.openfga.sdk.api.client;
1414

1515
import static org.hamcrest.Matchers.*;
16+
import static org.hamcrest.core.StringContains.containsString;
1617
import static org.junit.jupiter.api.Assertions.*;
1718
import static org.mockito.Mockito.*;
1819

@@ -137,14 +138,15 @@ public void createStore_withClientCredentials() throws Exception {
137138
.apiAudience(apiAudience)));
138139
fga.setConfiguration(clientConfiguration);
139140

140-
String expectedOAuth2Body = String.format(
141-
"{\"client_id\":\"%s\",\"client_secret\":\"%s\",\"audience\":\"%s\",\"grant_type\":\"client_credentials\"}",
142-
clientId, clientSecret, apiAudience);
143141
String expectedBody = String.format("{\"name\":\"%s\"}", DEFAULT_STORE_NAME);
144142
String requestBody = String.format("{\"id\":\"%s\",\"name\":\"%s\"}", DEFAULT_STORE_ID, DEFAULT_STORE_NAME);
145143
mockHttpClient
146144
.onPost(String.format("https://%s/oauth/token", apiTokenIssuer))
147-
.withBody(is(expectedOAuth2Body))
145+
.withBody(allOf(
146+
containsString(String.format("client_id=%s", clientId)),
147+
containsString(String.format("client_secret=%s", clientSecret)),
148+
containsString(String.format("audience=%s", apiAudience)),
149+
containsString(String.format("grant_type=%s", "client_credentials"))))
148150
.doReturn(200, String.format("{\"access_token\":\"%s\"}", apiToken));
149151
mockHttpClient
150152
.onPost("https://localhost/stores")

0 commit comments

Comments
 (0)