Skip to content

Commit

Permalink
feat: oauth2 client credentials support
Browse files Browse the repository at this point in the history
  • Loading branch information
le-yams authored and rhamzeh committed Jan 8, 2024
1 parent 7c82547 commit f45c12c
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 28 deletions.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ public class Example {
}
```

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

```java
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -195,6 +195,36 @@ 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();
}
}
```


### Get your Store ID

Expand Down
17 changes: 17 additions & 0 deletions src/main/java/dev/openfga/sdk/api/auth/CredentialsFlowRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package dev.openfga.sdk.api.auth;

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 @@ -25,6 +26,7 @@
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 @@ -37,6 +39,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 @@ -64,6 +69,7 @@ public void setClientSecret(String clientSecret) {
}

@JsonProperty(JSON_PROPERTY_AUDIENCE)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public String getAudience() {
return audience;
}
Expand All @@ -73,6 +79,17 @@ public void setAudience(String audience) {
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
1 change: 1 addition & 0 deletions src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public OAuth2Client(Configuration configuration, ApiClient apiClient) throws Fga
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class ClientCredentials {
private String clientSecret;
private String apiTokenIssuer;
private String apiAudience;
private String scopes;

public ClientCredentials() {}

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

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

public String getScopes() {
return this.scopes;
}
}
59 changes: 49 additions & 10 deletions src/test/java/dev/openfga/sdk/api/auth/OAuth2ClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,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 @@ -42,15 +43,38 @@ private static Stream<Arguments> apiTokenIssuers() {

@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 @@ public void exchangeToken(String apiTokenIssuer, String tokenEndpointUrl) throws
public void apiTokenIssuer_invalidScheme() {
// When
var exception =
assertThrows(FgaInvalidParameterException.class, () -> newOAuth2Client("ftp://issuer.fga.example"));
assertThrows(FgaInvalidParameterException.class, () -> newAuth0Client("ftp://issuer.fga.example"));

// Then
assertEquals("Required parameter scheme was invalid when calling apiTokenIssuer.", exception.getMessage());
Expand All @@ -85,7 +109,7 @@ private static Stream<Arguments> invalidApiTokenIssuers() {
@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(
Expand All @@ -94,18 +118,33 @@ public void apiTokenIssuers_invalidURI(String invalidApiTokenIssuer) {
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
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,4 @@ public void assertValid_invalidApiTokenIssuer() {
"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()));
}
}

0 comments on commit f45c12c

Please sign in to comment.