Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(java-sdk): oauth2 client credentials support #257

Merged
merged 1 commit into from
Dec 18, 2023
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
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
Loading