diff --git a/config/clients/java/template/README_initializing.mustache b/config/clients/java/template/README_initializing.mustache index a68db5d4..bf64c285 100644 --- a/config/clients/java/template/README_initializing.mustache +++ b/config/clients/java/template/README_initializing.mustache @@ -45,7 +45,7 @@ public class Example { } ``` -#### Client Credentials +#### Auth0 Client Credentials ```java import com.fasterxml.jackson.databind.ObjectMapper; @@ -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(); + } +} +``` diff --git a/config/clients/java/template/config-ClientCredentials.java.mustache b/config/clients/java/template/config-ClientCredentials.java.mustache index 27cd7374..cbaf794a 100644 --- a/config/clients/java/template/config-ClientCredentials.java.mustache +++ b/config/clients/java/template/config-ClientCredentials.java.mustache @@ -10,6 +10,7 @@ public class ClientCredentials { private String clientSecret; private String apiTokenIssuer; private String apiAudience; + private String scopes; public ClientCredentials() { } @@ -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; + } } \ No newline at end of file diff --git a/config/clients/java/template/config-ClientCredentialsTest.java.mustache b/config/clients/java/template/config-ClientCredentialsTest.java.mustache index 9b053c55..b0f533af 100644 --- a/config/clients/java/template/config-ClientCredentialsTest.java.mustache +++ b/config/clients/java/template/config-ClientCredentialsTest.java.mustache @@ -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())); - } } diff --git a/config/clients/java/template/creds-CredentialsFlowRequest.java.mustache b/config/clients/java/template/creds-CredentialsFlowRequest.java.mustache index bd3e1b45..06219828 100644 --- a/config/clients/java/template/creds-CredentialsFlowRequest.java.mustache +++ b/config/clients/java/template/creds-CredentialsFlowRequest.java.mustache @@ -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; @@ -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 { @@ -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; @@ -53,6 +58,7 @@ class CredentialsFlowRequest { } @JsonProperty(JSON_PROPERTY_AUDIENCE) + @JsonInclude(JsonInclude.Include.NON_EMPTY) public String getAudience() { return audience; } @@ -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; diff --git a/config/clients/java/template/creds-OAuth2Client.java.mustache b/config/clients/java/template/creds-OAuth2Client.java.mustache index 8becbf27..bdb1f1ad 100644 --- a/config/clients/java/template/creds-OAuth2Client.java.mustache +++ b/config/clients/java/template/creds-OAuth2Client.java.mustache @@ -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"); } diff --git a/config/clients/java/template/creds-OAuth2ClientTest.java.mustache b/config/clients/java/template/creds-OAuth2ClientTest.java.mustache index 13d0f10c..72d2ea68 100644 --- a/config/clients/java/template/creds-OAuth2ClientTest.java.mustache +++ b/config/clients/java/template/creds-OAuth2ClientTest.java.mustache @@ -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"; @@ -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(); @@ -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()); @@ -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);