Skip to content
Draft
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
23 changes: 23 additions & 0 deletions src/main/java/dev/openfga/sdk/api/OpenFgaApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,29 @@ private String pathWithParams(String basePath, Object... params) {
return path.toString();
}

/**
* Get an access token asynchronously. This method is only available when using CLIENT_CREDENTIALS
* authentication. The token can be used for making API calls to other services that accept the same token.
*
* @return A CompletableFuture containing the access token
* @throws IllegalStateException when the credentials method is not CLIENT_CREDENTIALS
* @throws FgaInvalidParameterException when the configuration is invalid
* @throws ApiException when token retrieval fails
*/
public CompletableFuture<String> getAccessToken() throws IllegalStateException, FgaInvalidParameterException, ApiException {
CredentialsMethod credentialsMethod = this.configuration.getCredentials().getCredentialsMethod();

if (credentialsMethod != CredentialsMethod.CLIENT_CREDENTIALS) {
throw new IllegalStateException("getAccessToken() is only available when using CLIENT_CREDENTIALS authentication method");
}

if (oAuth2Client == null) {
throw new IllegalStateException("OAuth2Client is not initialized");
}

return oAuth2Client.getAccessToken();
}

/**
* Get an access token. Expects that configuration is valid (meaning it can
* pass {@link Configuration#assertValid()}) and expects that if the
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ public void setConfiguration(ClientConfiguration configuration) throws FgaInvali
this.api = new OpenFgaApi(configuration, apiClient);
}

/**
* Get an access token asynchronously. This method is only available when using CLIENT_CREDENTIALS
* authentication. The token can be used for making API calls to other services that accept the same token.
*
* @return A CompletableFuture containing the access token
* @throws IllegalStateException when the credentials method is not CLIENT_CREDENTIALS
* @throws FgaInvalidParameterException when the configuration is invalid
* @throws ApiException when token retrieval fails
*/
public CompletableFuture<String> getAccessToken() throws IllegalStateException, FgaInvalidParameterException, ApiException {
configuration.assertValid();
return api.getAccessToken();
}

/* ********
* Stores *
**********/
Expand Down
64 changes: 64 additions & 0 deletions src/test/java/dev/openfga/sdk/api/OpenFgaApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1928,4 +1928,68 @@ DEFAULT_STORE_ID, DEFAULT_AUTH_MODEL_ID, new WriteAssertionsRequest())
assertEquals(
"{\"code\":\"internal_error\",\"message\":\"Internal Server Error\"}", exception.getResponseData());
}

@Test
public void getAccessToken_withClientCredentials() throws Exception {
// Given
String accessToken = "test-access-token-12345";
String tokenResponse = String.format("{\"access_token\":\"%s\",\"token_type\":\"Bearer\",\"expires_in\":3600}", accessToken);

// Mock the token endpoint
mockHttpClient.onPost("https://auth.fga.example/oauth/token").doReturn(200, tokenResponse);

ClientCredentials clientCredentials = new ClientCredentials()
.clientId("test-client-id")
.clientSecret("test-client-secret")
.apiTokenIssuer("https://auth.fga.example")
.apiAudience("https://api.fga.example");

Configuration configuration = new Configuration()
.apiUrl("https://api.fga.example")
.credentials(new Credentials(clientCredentials));

OpenFgaApi api = new OpenFgaApi(configuration, mockApiClient);

// When
String result = api.getAccessToken().get();

// Then
assertEquals(accessToken, result);
mockHttpClient.verify().post("https://auth.fga.example/oauth/token").called();
}

@Test
public void getAccessToken_withApiToken() throws Exception {
// Given - API token configuration
ApiToken apiToken = new ApiToken("static-api-token");
Configuration configuration = new Configuration()
.apiUrl("https://api.fga.example")
.credentials(new Credentials(apiToken));

OpenFgaApi api = new OpenFgaApi(configuration, mockApiClient);

// When & Then
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
api.getAccessToken().get();
});

assertEquals("getAccessToken() is only available when using CLIENT_CREDENTIALS authentication method", exception.getMessage());
}

@Test
public void getAccessToken_withNoCredentials() throws Exception {
// Given - No credentials configuration
Configuration configuration = new Configuration()
.apiUrl("https://api.fga.example")
.credentials(new Credentials());

OpenFgaApi api = new OpenFgaApi(configuration, mockApiClient);

// When & Then
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
api.getAccessToken().get();
});

assertEquals("getAccessToken() is only available when using CLIENT_CREDENTIALS authentication method", exception.getMessage());
}
}
59 changes: 59 additions & 0 deletions src/test/java/dev/openfga/sdk/api/client/OpenFgaClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2993,6 +2993,65 @@ public void setAuthorizationModelId() throws Exception {
+ "If this behavior ever changes, it could be a subtle breaking change.");
}

@Test
public void getAccessToken_withClientCredentials() throws Exception {
// Given
String accessToken = "client-test-token-67890";
String tokenResponse = String.format("{\"access_token\":\"%s\",\"token_type\":\"Bearer\",\"expires_in\":3600}", accessToken);

// Mock the token endpoint
mockHttpClient.onPost("https://auth.fga.example/oauth/token").doReturn(200, tokenResponse);

ClientCredentials clientCredentials = new ClientCredentials()
.clientId("test-client-id")
.clientSecret("test-client-secret")
.apiTokenIssuer("https://auth.fga.example")
.apiAudience("https://api.fga.example");

ClientConfiguration clientConfiguration = new ClientConfiguration()
.apiUrl("https://api.fga.example")
.storeId(DEFAULT_STORE_ID)
.credentials(new Credentials(clientCredentials));

var mockApiClient = mock(ApiClient.class);
when(mockApiClient.getHttpClient()).thenReturn(mockHttpClient);
when(mockApiClient.getObjectMapper()).thenReturn(new ObjectMapper());
when(mockApiClient.getHttpClientBuilder()).thenReturn(mock(HttpClient.Builder.class));

OpenFgaClient client = new OpenFgaClient(clientConfiguration, mockApiClient);

// When
String result = client.getAccessToken().get();

// Then
assertEquals(accessToken, result);
mockHttpClient.verify().post("https://auth.fga.example/oauth/token").called();
}

@Test
public void getAccessToken_withApiToken() throws Exception {
// Given - API token configuration
ApiToken apiToken = new ApiToken("static-api-token-client");
ClientConfiguration clientConfiguration = new ClientConfiguration()
.apiUrl("https://api.fga.example")
.storeId(DEFAULT_STORE_ID)
.credentials(new Credentials(apiToken));

var mockApiClient = mock(ApiClient.class);
when(mockApiClient.getHttpClient()).thenReturn(mockHttpClient);
when(mockApiClient.getObjectMapper()).thenReturn(new ObjectMapper());
when(mockApiClient.getHttpClientBuilder()).thenReturn(mock(HttpClient.Builder.class));

OpenFgaClient client = new OpenFgaClient(clientConfiguration, mockApiClient);

// When & Then - The exception is thrown directly, not wrapped in ExecutionException
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
client.getAccessToken();
});

assertEquals("getAccessToken() is only available when using CLIENT_CREDENTIALS authentication method", exception.getMessage());
}

private Matcher<String> anyValidUUID() {
return new UUIDMatcher();
}
Expand Down
Loading