From f45c12c957fd478d87ff0868be4fb74996c07b3e Mon Sep 17 00:00:00 2001 From: Yann D'Isanto Date: Mon, 8 Jan 2024 14:50:48 -0500 Subject: [PATCH] feat: oauth2 client credentials support --- README.md | 32 +++++++++- .../sdk/api/auth/CredentialsFlowRequest.java | 17 ++++++ .../openfga/sdk/api/auth/OAuth2Client.java | 1 + .../api/configuration/ClientCredentials.java | 10 ++++ .../sdk/api/auth/OAuth2ClientTest.java | 59 +++++++++++++++---- .../configuration/ClientCredentialsTest.java | 17 ------ 6 files changed, 108 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index b00b82d..c772d07 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ public class Example { } ``` -#### Client Credentials +#### Auth0 Client Credentials ```java import com.fasterxml.jackson.databind.ObjectMapper; @@ -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 diff --git a/src/main/java/dev/openfga/sdk/api/auth/CredentialsFlowRequest.java b/src/main/java/dev/openfga/sdk/api/auth/CredentialsFlowRequest.java index f9f14ca..21b1c22 100644 --- a/src/main/java/dev/openfga/sdk/api/auth/CredentialsFlowRequest.java +++ b/src/main/java/dev/openfga/sdk/api/auth/CredentialsFlowRequest.java @@ -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; @@ -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 { @@ -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; @@ -64,6 +69,7 @@ public void setClientSecret(String clientSecret) { } @JsonProperty(JSON_PROPERTY_AUDIENCE) + @JsonInclude(JsonInclude.Include.NON_EMPTY) public String getAudience() { return audience; } @@ -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; diff --git a/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java b/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java index 8e0ac92..a75ffea 100644 --- a/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java +++ b/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java @@ -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"); } diff --git a/src/main/java/dev/openfga/sdk/api/configuration/ClientCredentials.java b/src/main/java/dev/openfga/sdk/api/configuration/ClientCredentials.java index 4163517..e4e68de 100644 --- a/src/main/java/dev/openfga/sdk/api/configuration/ClientCredentials.java +++ b/src/main/java/dev/openfga/sdk/api/configuration/ClientCredentials.java @@ -21,6 +21,7 @@ public class ClientCredentials { private String clientSecret; private String apiTokenIssuer; private String apiAudience; + private String scopes; public ClientCredentials() {} @@ -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; + } } diff --git a/src/test/java/dev/openfga/sdk/api/auth/OAuth2ClientTest.java b/src/test/java/dev/openfga/sdk/api/auth/OAuth2ClientTest.java index 30c34e6..7cb66a6 100644 --- a/src/test/java/dev/openfga/sdk/api/auth/OAuth2ClientTest.java +++ b/src/test/java/dev/openfga/sdk/api/auth/OAuth2ClientTest.java @@ -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"; @@ -42,15 +43,38 @@ private static Stream 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(); @@ -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()); @@ -85,7 +109,7 @@ private static Stream 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( @@ -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); diff --git a/src/test/java/dev/openfga/sdk/api/configuration/ClientCredentialsTest.java b/src/test/java/dev/openfga/sdk/api/configuration/ClientCredentialsTest.java index 3b09e70..220c90b 100644 --- a/src/test/java/dev/openfga/sdk/api/configuration/ClientCredentialsTest.java +++ b/src/test/java/dev/openfga/sdk/api/configuration/ClientCredentialsTest.java @@ -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())); - } }