Skip to content

Commit

Permalink
feat(java): Introduce OAuth2Client (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
booniepepper committed Sep 8, 2023
1 parent 7200f38 commit 5deabb8
Show file tree
Hide file tree
Showing 24 changed files with 795 additions and 169 deletions.
8 changes: 7 additions & 1 deletion .openapi-generator-ignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# Used but at a different location
**/client/Configuration.java
**/client/Pair.java
**/client/ApiException.java

# Unused
**/ServerConfiguration.java
**/ServerVariable.java
**/JSON.java
**/client/Configuration.java
**/RFC3339DateFormat.java
src/main/AndroidManifest.xml
build.sbt
pom.xml
Expand Down
23 changes: 14 additions & 9 deletions .openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,19 @@ gradlew
gradlew.bat
settings.gradle
src/main/java/dev/openfga/sdk/api/OpenFgaApi.java
src/main/java/dev/openfga/sdk/api/client/ApiBearerToken.java
src/main/java/dev/openfga/sdk/api/auth/AccessToken.java
src/main/java/dev/openfga/sdk/api/auth/CredentialsFlowRequest.java
src/main/java/dev/openfga/sdk/api/auth/CredentialsFlowResponse.java
src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java
src/main/java/dev/openfga/sdk/api/client/ApiClient.java
src/main/java/dev/openfga/sdk/api/client/ApiException.java
src/main/java/dev/openfga/sdk/api/client/ApiResponse.java
src/main/java/dev/openfga/sdk/api/client/ClientCredentials.java
src/main/java/dev/openfga/sdk/api/client/CredentialsMethod.java
src/main/java/dev/openfga/sdk/api/client/Pair.java
src/main/java/dev/openfga/sdk/api/client/RFC3339DateFormat.java
src/main/java/dev/openfga/sdk/api/configuration/ApiToken.java
src/main/java/dev/openfga/sdk/api/configuration/BaseConfiguration.java
src/main/java/dev/openfga/sdk/api/configuration/ClientCredentials.java
src/main/java/dev/openfga/sdk/api/configuration/Configuration.java
src/main/java/dev/openfga/sdk/api/configuration/ConfigurationOverride.java
src/main/java/dev/openfga/sdk/api/configuration/Credentials.java
src/main/java/dev/openfga/sdk/api/configuration/CredentialsMethod.java
src/main/java/dev/openfga/sdk/api/model/AbstractOpenApiSchema.java
src/main/java/dev/openfga/sdk/api/model/Any.java
src/main/java/dev/openfga/sdk/api/model/Assertion.java
Expand Down Expand Up @@ -141,12 +143,15 @@ src/main/java/dev/openfga/sdk/api/model/WriteAssertionsRequest.java
src/main/java/dev/openfga/sdk/api/model/WriteAuthorizationModelRequest.java
src/main/java/dev/openfga/sdk/api/model/WriteAuthorizationModelResponse.java
src/main/java/dev/openfga/sdk/api/model/WriteRequest.java
src/main/java/dev/openfga/sdk/errors/ApiException.java
src/main/java/dev/openfga/sdk/errors/FgaInvalidParameterException.java
src/main/java/dev/openfga/util/StringUtil.java
src/main/java/dev/openfga/sdk/util/Pair.java
src/main/java/dev/openfga/sdk/util/StringUtil.java
src/test-integration/java/dev/openfga/sdk/api/OpenFgaApiIntegrationTest.java
src/test/java/dev/openfga/sdk/api/OpenFgaApiTest.java
src/test/java/dev/openfga/sdk/api/OpenFgaApiTest.java
src/test/java/dev/openfga/sdk/api/client/ClientCredentialsTest.java
src/test/java/dev/openfga/sdk/api/auth/OAuth2ClientTest.java
src/test/java/dev/openfga/sdk/api/configuration/ClientCredentialsTest.java
src/test/java/dev/openfga/sdk/api/configuration/ConfigurationTest.java
src/test/java/dev/openfga/sdk/api/model/AnyTest.java
src/test/java/dev/openfga/sdk/api/model/AssertionTest.java
Expand Down Expand Up @@ -202,4 +207,4 @@ src/test/java/dev/openfga/sdk/api/model/WriteAssertionsRequestTest.java
src/test/java/dev/openfga/sdk/api/model/WriteAuthorizationModelRequestTest.java
src/test/java/dev/openfga/sdk/api/model/WriteAuthorizationModelResponseTest.java
src/test/java/dev/openfga/sdk/api/model/WriteRequestTest.java
src/test/java/dev/openfga/util/StringUtilTest.java
src/test/java/dev/openfga/sdk/util/StringUtilTest.java
193 changes: 145 additions & 48 deletions src/main/java/dev/openfga/sdk/api/OpenFgaApi.java

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions src/main/java/dev/openfga/sdk/api/auth/AccessToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* OpenFGA
* A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar.
*
* The version of the OpenAPI document: 0.1
* Contact: [email protected]
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package dev.openfga.sdk.api.auth;

import static dev.openfga.sdk.util.StringUtil.isNullOrWhitespace;

import java.time.Instant;
import java.util.Random;

class AccessToken {
private static final int TOKEN_EXPIRY_BUFFER_THRESHOLD_IN_SEC = 300;
private static final int TOKEN_EXPIRY_JITTER_IN_SEC =
300; // We add some jitter so that token refreshes are less likely to collide

private final Random random = new Random();
private Instant expiresAt;

private String token;

public boolean isValid() {
return !isNullOrWhitespace(token)
&& (expiresAt == null
|| expiresAt.isBefore(Instant.now()
.plusSeconds(TOKEN_EXPIRY_BUFFER_THRESHOLD_IN_SEC)
.plusSeconds(random.nextLong() % TOKEN_EXPIRY_JITTER_IN_SEC)));
}

public String getToken() {
return token;
}

public void setExpiresAt(Instant expiresAt) {
this.expiresAt = expiresAt;
}

public void setToken(String token) {
this.token = token;
}
}
85 changes: 85 additions & 0 deletions src/main/java/dev/openfga/sdk/api/auth/CredentialsFlowRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* OpenFGA
* A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar.
*
* The version of the OpenAPI document: 0.1
* Contact: [email protected]
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package dev.openfga.sdk.api.auth;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

/**
* A credentials flow request. It contains a Client ID and Secret that can be exchanged for an access token.
* <p>
* {@see "https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow"}
*/
@JsonPropertyOrder({
CredentialsFlowRequest.JSON_PROPERTY_CLIENT_ID,
CredentialsFlowRequest.JSON_PROPERTY_CLIENT_SECRET,
CredentialsFlowRequest.JSON_PROPERTY_AUDIENCE,
CredentialsFlowRequest.JSON_PROPERTY_GRANT_TYPE
})
class CredentialsFlowRequest {
public static final String JSON_PROPERTY_CLIENT_ID = "client_id";
private String clientId;

public static final String JSON_PROPERTY_CLIENT_SECRET = "client_secret";
private String clientSecret;

public static final String JSON_PROPERTY_AUDIENCE = "audience";
private String audience;

public static final String JSON_PROPERTY_GRANT_TYPE = "grant_type";
private String grantType;

@JsonCreator
public CredentialsFlowRequest() {}

@JsonProperty(JSON_PROPERTY_CLIENT_ID)
public String getClientId() {
return clientId;
}

@JsonProperty(JSON_PROPERTY_CLIENT_ID)
public void setClientId(String clientId) {
this.clientId = clientId;
}

@JsonProperty(JSON_PROPERTY_CLIENT_SECRET)
public String getClientSecret() {
return clientSecret;
}

@JsonProperty(JSON_PROPERTY_CLIENT_SECRET)
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}

@JsonProperty(JSON_PROPERTY_AUDIENCE)
public String getAudience() {
return audience;
}

@JsonProperty(JSON_PROPERTY_AUDIENCE)
public void setAudience(String audience) {
this.audience = audience;
}

@JsonProperty(JSON_PROPERTY_GRANT_TYPE)
public String getGrantType() {
return grantType;
}

@JsonProperty(JSON_PROPERTY_GRANT_TYPE)
public void setGrantType(String grantType) {
this.grantType = grantType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* OpenFGA
* A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar.
*
* The version of the OpenAPI document: 0.1
* Contact: [email protected]
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package dev.openfga.sdk.api.auth;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

/**
* A credentials flow response. Contains an access token that can be used to authenticate
* <p>
* {@see "https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow"}
*/
@JsonPropertyOrder({
CredentialsFlowResponse.JSON_PROPERTY_ACCESS_TOKEN,
CredentialsFlowResponse.JSON_PROPERTY_SCOPE,
CredentialsFlowResponse.JSON_PROPERTY_EXPIRES_IN,
CredentialsFlowResponse.JSON_PROPERTY_TOKEN_TYPE
})
class CredentialsFlowResponse {
public static final String JSON_PROPERTY_ACCESS_TOKEN = "access_token";
private String accessToken;

public static final String JSON_PROPERTY_SCOPE = "scope";
private String scope;

public static final String JSON_PROPERTY_EXPIRES_IN = "expires_in";
private long expiresInSeconds;

public static final String JSON_PROPERTY_TOKEN_TYPE = "token_type";
private String tokenType;

@JsonProperty(JSON_PROPERTY_ACCESS_TOKEN)
public String getAccessToken() {
return accessToken;
}

@JsonProperty(JSON_PROPERTY_ACCESS_TOKEN)
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

@JsonProperty(JSON_PROPERTY_SCOPE)
public String getScope() {
return scope;
}

@JsonProperty(JSON_PROPERTY_SCOPE)
public void setScope(String scope) {
this.scope = scope;
}

/**
* The expiration time, in seconds.
* <p>
* By the convention of RFC 6749 section 5.1, an expires_in value from a response will be understood
* as a value in seconds. {@see https://datatracker.ietf.org/doc/html/rfc6749#autoid-55}
* @return The expiration time, from now, in seconds
*/
@JsonProperty(JSON_PROPERTY_EXPIRES_IN)
public long getExpiresInSeconds() {
return expiresInSeconds;
}

@JsonProperty(JSON_PROPERTY_EXPIRES_IN)
public void setExpiresInSeconds(long expiresInSeconds) {
this.expiresInSeconds = expiresInSeconds;
}

@JsonProperty(JSON_PROPERTY_TOKEN_TYPE)
public String getTokenType() {
return tokenType;
}

@JsonProperty(JSON_PROPERTY_TOKEN_TYPE)
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
}
106 changes: 106 additions & 0 deletions src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* OpenFGA
* A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar.
*
* The version of the OpenAPI document: 0.1
* Contact: [email protected]
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package dev.openfga.sdk.api.auth;

import com.fasterxml.jackson.databind.ObjectMapper;
import dev.openfga.sdk.api.client.ApiClient;
import dev.openfga.sdk.api.configuration.*;
import dev.openfga.sdk.errors.ApiException;
import dev.openfga.sdk.errors.FgaInvalidParameterException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;

public class OAuth2Client {
private final HttpClient httpClient;
private final Credentials credentials;
private final ObjectMapper mapper;
private final AccessToken token = new AccessToken();
private final CredentialsFlowRequest authRequest;
private final String apiTokenIssuer;

/**
* Initializes a new instance of the {@link OAuth2Client} class
*
* @param configuration Configuration, including credentials, that can be used to retrieve an access tokens
* @param httpClient Http client
*/
public OAuth2Client(Configuration configuration, HttpClient httpClient, ObjectMapper mapper)
throws FgaInvalidParameterException {
this.credentials = configuration.getCredentials();

this.httpClient = httpClient;
this.mapper = mapper;
this.apiTokenIssuer = credentials.getClientCredentials().getApiTokenIssuer();
this.authRequest = new CredentialsFlowRequest();
this.authRequest.setClientId(credentials.getClientCredentials().getClientId());
this.authRequest.setClientSecret(credentials.getClientCredentials().getClientSecret());
this.authRequest.setAudience(credentials.getClientCredentials().getApiAudience());
this.authRequest.setGrantType("client_credentials");
}

/**
* Gets an access token, handling exchange when necessary. The access token is naively cached in memory until it
* expires.
*
* @return An access token in a {@link CompletableFuture}
*/
public CompletableFuture<String> getAccessToken() throws FgaInvalidParameterException, ApiException {
if (!token.isValid()) {
return exchangeToken().thenCompose(response -> {
token.setToken(response.getAccessToken());
token.setExpiresAt(Instant.now().plusSeconds(response.getExpiresInSeconds()));
return CompletableFuture.completedFuture(token.getToken());
});
}

return CompletableFuture.completedFuture(token.getToken());
}

/**
* Exchange a client id and client secret for an access token.
* @return The credentials flow response
*/
private CompletableFuture<CredentialsFlowResponse> exchangeToken()
throws ApiException, FgaInvalidParameterException {
try {
byte[] body = mapper.writeValueAsBytes(authRequest);

Configuration config = new Configuration().apiUrl("https://" + apiTokenIssuer);

HttpRequest request = ApiClient.requestBuilder("POST", "/oauth/token", body, config)
.build();

return httpClient
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenCompose(httpResponse -> {
if (httpResponse.statusCode() != HttpURLConnection.HTTP_OK) {
return CompletableFuture.failedFuture(new ApiException("exchangeToken", httpResponse));
}
try {
CredentialsFlowResponse response =
mapper.readValue(httpResponse.body(), CredentialsFlowResponse.class);
return CompletableFuture.completedFuture(response);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
});
} catch (IOException e) {
throw new ApiException(e);
}
}
}
Loading

0 comments on commit 5deabb8

Please sign in to comment.