Skip to content

Commit

Permalink
feat(java-sdk): oauth2 client credentials support (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhamzeh authored Dec 18, 2023
2 parents f5c6e5b + 1cc6e93 commit b10c3d6
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 28 deletions.
32 changes: 31 additions & 1 deletion config/clients/java/template/README_initializing.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class Example {
}
```

#### Client Credentials
#### Auth0 Client Credentials

```java
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -74,3 +74,33 @@ public class Example {
}
}
```

#### Oauth2 Credentials

```java
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
import dev.openfga.sdk.api.configuration.ClientCredentials;
import dev.openfga.sdk.api.configuration.Credentials;
import java.net.http.HttpClient;

public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")) // Optional, can be overridden per request
.credentials(new Credentials(
new ClientCredentials()
.apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER"))
.scopes(System.getenv("FGA_API_SCOPES")) // optional space separated scopes
.clientId(System.getenv("FGA_CLIENT_ID"))
.clientSecret(System.getenv("FGA_CLIENT_SECRET"))
));
var fgaClient = new OpenFgaClient(config);
var response = fgaClient.readAuthorizationModels().get();
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class ClientCredentials {
private String clientSecret;
private String apiTokenIssuer;
private String apiAudience;
private String scopes;
public ClientCredentials() { }

Expand Down Expand Up @@ -55,4 +56,13 @@ public class ClientCredentials {
public String getApiAudience() {
return this.apiAudience;
}

public ClientCredentials scopes(String scopes) {
this.scopes = scopes;
return this;
}

public String getScopes() {
return this.scopes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,4 @@ public class ClientCredentialsTest {
"Required parameter apiTokenIssuer was invalid when calling ClientCredentials.",
exception.getMessage()));
}

@Test
public void assertValid_invalidApiAudience() {
INVALID_IDENTIFIERS.stream()
// Given
.map(invalid -> new ClientCredentials()
.clientId(VALID_CLIENT_ID)
.clientSecret(VALID_CLIENT_SECRET)
.apiTokenIssuer(VALID_API_TOKEN_ISSUER)
.apiAudience(invalid))
// When
.map(creds -> assertThrows(FgaInvalidParameterException.class, creds::assertValid))
// Then
.forEach(exception -> assertEquals(
"Required parameter apiAudience was invalid when calling ClientCredentials.",
exception.getMessage()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package {{authPackage}};

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

Expand All @@ -14,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
CredentialsFlowRequest.JSON_PROPERTY_CLIENT_ID,
CredentialsFlowRequest.JSON_PROPERTY_CLIENT_SECRET,
CredentialsFlowRequest.JSON_PROPERTY_AUDIENCE,
CredentialsFlowRequest.JSON_PROPERTY_SCOPE,
CredentialsFlowRequest.JSON_PROPERTY_GRANT_TYPE
})
class CredentialsFlowRequest {
Expand All @@ -26,6 +28,9 @@ class CredentialsFlowRequest {
public static final String JSON_PROPERTY_AUDIENCE = "audience";
private String audience;
public static final String JSON_PROPERTY_SCOPE = "scope";
private String scope;
public static final String JSON_PROPERTY_GRANT_TYPE = "grant_type";
private String grantType;
Expand Down Expand Up @@ -53,6 +58,7 @@ class CredentialsFlowRequest {
}

@JsonProperty(JSON_PROPERTY_AUDIENCE)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public String getAudience() {
return audience;
}
Expand All @@ -62,6 +68,17 @@ class CredentialsFlowRequest {
this.audience = audience;
}

@JsonProperty(JSON_PROPERTY_SCOPE)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public String getScope() {
return scope;
}

@JsonProperty(JSON_PROPERTY_SCOPE)
public void setScope(String scope) {
this.scope = scope;
}

@JsonProperty(JSON_PROPERTY_GRANT_TYPE)
public String getGrantType() {
return grantType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class OAuth2Client {
this.authRequest.setClientId(clientCredentials.getClientId());
this.authRequest.setClientSecret(clientCredentials.getClientSecret());
this.authRequest.setAudience(clientCredentials.getApiAudience());
this.authRequest.setScope(clientCredentials.getScopes());
this.authRequest.setGrantType("client_credentials");
}

Expand Down
54 changes: 44 additions & 10 deletions config/clients/java/template/creds-OAuth2ClientTest.java.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class OAuth2ClientTest {
private static final String CLIENT_ID = "client";
private static final String CLIENT_SECRET = "secret";
private static final String AUDIENCE = "audience";
private static final String SCOPES = "scope1 scope2";
private static final String GRANT_TYPE = "client_credentials";
private static final String ACCESS_TOKEN = "0123456789";
Expand All @@ -43,15 +44,38 @@ class OAuth2ClientTest {

@ParameterizedTest
@MethodSource("apiTokenIssuers")
public void exchangeToken(String apiTokenIssuer, String tokenEndpointUrl) throws Exception {
public void exchangeAuth0Token(String apiTokenIssuer, String tokenEndpointUrl) throws Exception {
// Given
OAuth2Client oAuth2 = newOAuth2Client(apiTokenIssuer);
OAuth2Client auth0 = newAuth0Client(apiTokenIssuer);
String expectedPostBody = String.format(
"{\"client_id\":\"%s\",\"client_secret\":\"%s\",\"audience\":\"%s\",\"grant_type\":\"%s\"}",
CLIENT_ID, CLIENT_SECRET, AUDIENCE, GRANT_TYPE);
String responseBody = String.format("{\"access_token\":\"%s\"}", ACCESS_TOKEN);
mockHttpClient.onPost(tokenEndpointUrl).withBody(is(expectedPostBody)).doReturn(200, responseBody);
// When
String result = auth0.getAccessToken().get();
// Then
mockHttpClient
.verify()
.post(tokenEndpointUrl)
.withBody(is(expectedPostBody))
.called();
assertEquals(ACCESS_TOKEN, result);
}

@ParameterizedTest
@MethodSource("apiTokenIssuers")
public void exchangeOAuth2Token(String apiTokenIssuer, String tokenEndpointUrl) throws Exception {
// Given
OAuth2Client oAuth2 = newOAuth2Client(apiTokenIssuer);
String expectedPostBody = String.format(
"{\"client_id\":\"%s\",\"client_secret\":\"%s\",\"scope\":\"%s\",\"grant_type\":\"%s\"}",
CLIENT_ID, CLIENT_SECRET, SCOPES, GRANT_TYPE);
String responseBody = String.format("{\"access_token\":\"%s\"}", ACCESS_TOKEN);
mockHttpClient.onPost(tokenEndpointUrl).withBody(is(expectedPostBody)).doReturn(200, responseBody);
// When
String result = oAuth2.getAccessToken().get();
Expand All @@ -67,7 +91,7 @@ class OAuth2ClientTest {
@Test
public void apiTokenIssuer_invalidScheme() {
// When
var exception = assertThrows(FgaInvalidParameterException.class, () -> newOAuth2Client("ftp://issuer.fga.example"));
var exception = assertThrows(FgaInvalidParameterException.class, () -> newAuth0Client("ftp://issuer.fga.example"));
// Then
assertEquals("Required parameter scheme was invalid when calling apiTokenIssuer.", exception.getMessage());
Expand All @@ -85,25 +109,35 @@ class OAuth2ClientTest {
@MethodSource("invalidApiTokenIssuers")
public void apiTokenIssuers_invalidURI(String invalidApiTokenIssuer) {
// When
var exception = assertThrows(FgaInvalidParameterException.class, () -> newOAuth2Client(invalidApiTokenIssuer));
var exception = assertThrows(FgaInvalidParameterException.class, () -> newAuth0Client(invalidApiTokenIssuer));
// Then
assertEquals("Required parameter apiTokenIssuer was invalid when calling ClientCredentials.", exception.getMessage());
assertInstanceOf(IllegalArgumentException.class, exception.getCause());
}

private OAuth2Client newAuth0Client(String apiTokenIssuer) throws FgaInvalidParameterException {
return newClientCredentialsClient(apiTokenIssuer, new Credentials(new ClientCredentials()
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.apiAudience(AUDIENCE)
.apiTokenIssuer(apiTokenIssuer)));
}

private OAuth2Client newOAuth2Client(String apiTokenIssuer) throws FgaInvalidParameterException {
return newClientCredentialsClient(apiTokenIssuer, new Credentials(new ClientCredentials()
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.scopes(SCOPES)
.apiTokenIssuer(apiTokenIssuer)));
}

private OAuth2Client newClientCredentialsClient(String apiTokenIssuer, Credentials credentials) throws FgaInvalidParameterException {
System.setProperty("HttpRequestAttempt.debug-logging", "enable");
mockHttpClient = new HttpClientMock();
mockHttpClient.debugOn();
var credentials = new Credentials(new ClientCredentials()
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.apiAudience(AUDIENCE)
.apiTokenIssuer(apiTokenIssuer));
var configuration = new Configuration().apiUrl("").credentials(credentials);
var apiClient = mock(ApiClient.class);
Expand Down

0 comments on commit b10c3d6

Please sign in to comment.