Skip to content

Commit

Permalink
Add more configuration properties. Closes #70
Browse files Browse the repository at this point in the history
eliadheid authored and dheid committed Jan 2, 2025
1 parent 021acd2 commit 792afa2
Showing 17 changed files with 1,217 additions and 271 deletions.
225 changes: 206 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -9,9 +9,15 @@ A Spring Boot Starter for OpenFGA.

## About

[OpenFGA](https://openfga.dev) is an open source Fine-Grained Authorization solution inspired by [Google's Zanzibar paper](https://research.google/pubs/pub48190/). It was created by the FGA team at [Auth0](https://auth0.com) based on [Auth0 Fine-Grained Authorization (FGA)](https://fga.dev), available under [a permissive license (Apache-2)](https://github.com/openfga/rfcs/blob/main/LICENSE) and welcomes community contributions.
[OpenFGA](https://openfga.dev) is an open source Fine-Grained Authorization solution inspired
by [Google's Zanzibar paper](https://research.google/pubs/pub48190/). It was created by the FGA team
at [Auth0](https://auth0.com) based on [Auth0 Fine-Grained Authorization (FGA)](https://fga.dev), available
under [a permissive license (Apache-2)](https://github.com/openfga/rfcs/blob/main/LICENSE) and welcomes community
contributions.

OpenFGA is designed to make it easy for application builders to model their permission layer, and to add and integrate fine-grained authorization into their applications. OpenFGA’s design is optimized for reliability and low latency at a high scale.
OpenFGA is designed to make it easy for application builders to model their permission layer, and to add and integrate
fine-grained authorization into their applications. OpenFGA’s design is optimized for reliability and low latency at a
high scale.

## Resources

@@ -43,6 +49,7 @@ implementation("dev.openfga:openfga-spring-boot-starter:0.0.1")
* Apache Maven

```xml

<dependency>
<groupId>dev.openfga</groupId>
<artifactId>openfga-spring-boot-starter</artifactId>
@@ -54,12 +61,20 @@ implementation("dev.openfga:openfga-spring-boot-starter:0.0.1")

### Requirements

Java 17 and Spring Boot 3
Java >= 17 and Spring Boot >= 3

### Configuring the starter

The OpenFGA Spring Boot Starter can be configured via standard [Spring configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config).
The configuration properties are used to create an [OpenFgaClient](https://github.com/openfga/java-sdk/blob/main/src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java) instance.
The OpenFGA Spring Boot Starter can be configured via
standard [Spring configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config).
The configuration properties are used to create
an [OpenFgaClient](https://github.com/openfga/java-sdk/blob/main/src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java)
instance.

To initialize the OpenFGA Spring Boot Starter, please provide the configuration property `openfga.api-url`. An
`OpenFgaClient` instance will then be created with the provided configuration.

The following examples demonstrate how to configure the OpenFGA Spring Boot Starter.

#### No Credentials

@@ -99,21 +114,169 @@ openfga:
credentials:
method: CLIENT_CONFIGURATION # constant
config:
client-id: YOUR_CLIENT_ID
client-secret: YOUR_CLIENT_SECRET
api-token-issuer: YOUR_API_TOKEN_ISSUER
api-audience: YOUR_API_AUDIENCE
scopes: YOUR_SPACE_SEPERATED_SCOPES
client-id: YOUR_CLIENT_ID
client-secret: YOUR_CLIENT_SECRET
api-token-issuer: YOUR_API_TOKEN_ISSUER
api-audience: YOUR_API_AUDIENCE
scopes: YOUR_SPACE_SEPERATED_SCOPES
```
#### Full Configuration Example
```yaml
# src/main/resources/application.yaml

openfga:
api-url: YOUR_FGA_API_URL
store-id: YOUR_FGA_STORE_ID
authorization-model-id: YOUR_FGA_AUTHORIZATION_MODEL_ID
user-agent: YOUR_USER_AGENT # default: openfga-sdk java/version
read-timeout: 1m # default: 10 seconds
connect-timeout: 15 # default: 10 seconds
max-retries: 3 # default: no retries
minimum-retry-delay: 1m
http-version: HTTP_2
default-headers:
X-SOME-HEADER: Some Header Value
telemetry-configuration:
fga_client_request_model_id: YOUR_FGA_CLIENT_REQUEST_MODEL_ID
credentials:
method: CLIENT_CONFIGURATION # constant
config:
client-id: YOUR_CLIENT_ID
client-secret: YOUR_CLIENT_SECRET
api-token-issuer: YOUR_API_TOKEN_ISSUER
api-audience: YOUR_API_AUDIENCE
scopes: YOUR_SPACE_SEPERATED_SCOPES
```
### Configuration Properties
The OpenFGA Spring Boot Starter can be configured using the following properties:
#### `openfga.api-url`

- **Description**: The base URL of the OpenFGA API endpoint.
- **Example**: `https://api.openfga.example.com`

#### `openfga.store-id`

- **Description**: The unique identifier for the store in OpenFGA.
- **Example**: `store-12345`

#### `openfga.authorization-model-id`

- **Description**: The unique identifier for the authorization model in OpenFGA.
- **Example**: `auth-model-67890`

#### `openfga.user-agent`

- **Description**: The user agent string to be included in the request headers.
- **Example**: `MyApp/1.0.0`
- **Default**: `openfga-sdk java/version`

#### `openfga.read-timeout`

- **Description**: The maximum duration to wait for a read operation to complete. Default unit is seconds. Must be positive or null.
- **Example**: `30s`
- **Default**: `10s`

#### `openfga.connect-timeout`

- **Description**: The maximum duration to wait for a connection to be established. Default unit is seconds. Must be positive or null.
- **Example**: `10s`
- **Default**: `10s`

#### `openfga.max-retries`

- **Description**: The maximum number of retry attempts for failed requests. Must be positive or null. If you set this to a positive value, ensure that you also set the `minimum-retry-delay` property.
- **Example**: `5`
- **Default**: No retries

#### `openfga.minimum-retry-delay`

- **Description**: The minimum delay between retry attempts. Default unit is seconds. Must be positive or null. Only used if `max-retries` is set.
- **Example**: `500ms`
- **Default**: `10s`

#### `openfga.http-version`

- **Description**: The HTTP version to use for requests.
- **Example**: `HTTP_1_1`
- **Default**: `HTTP_2`

#### `openfga.default-headers`

- **Description**: Default headers to be included in all requests.
- **Example**:

```yaml
default-headers:
X-Custom-Header: CustomHeaderValue
```

#### `openfga.telemetry-configuration`

- **Description**: Configuration settings for telemetry, which help in monitoring and logging the behavior of the
OpenFGA client.
- **Example**:

```yaml
telemetry-configuration:
fga_client_request_model_id: "example-model-id"
```

#### `openfga.credentials.method`

- **Description**: Specifies the authentication method to be used for connecting to the OpenFGA API.
- **Possible Values**:
- `NONE`: No authentication.
- `API_TOKEN`: Use an API token for authentication.
- `CLIENT_CREDENTIALS`: Use OAuth2 client credentials for authentication.

#### `openfga.credentials.config.api-token`

- **Description**: The API token used for authenticating requests when the `API_TOKEN` method is selected.
- **Example**: `your-api-token`

#### `openfga.credentials.config.client-id`

- **Description**: The client ID used for OAuth2 authentication when the `CLIENT_CREDENTIALS` method is selected.
- **Example**: `your-client-id`

#### `openfga.credentials.config.client-secret`

- **Description**: The client secret used for OAuth2 authentication when the `CLIENT_CREDENTIALS` method is selected.
- **Example**: `your-client-secret`

#### `openfga.credentials.config.api-token-issuer`

- **Description**: The issuer of the API token used for OAuth2 authentication when the `CLIENT_CREDENTIALS` method is
selected.
- **Example**: `https://issuer.example.com`

#### `openfga.credentials.config.api-audience`

- **Description**: The audience for the API token used for OAuth2 authentication when the `CLIENT_CREDENTIALS` method is
selected.
- **Example**: `https://api.example.com`

#### `openfga.credentials.config.scopes`

- **Description**: The scopes required for OAuth2 authentication when the `CLIENT_CREDENTIALS` method is selected.
Scopes are space-separated.
- **Example**: `read write`

### Using the `fgaClient` bean

Once configured, an `fgaClient` bean is available to be injected into your Spring components:

```java
@Service
public class MyService {
@Autowired
private OpenFgaClient fgaClient;
}
@@ -124,11 +287,10 @@ This can be used to interact with the FGA API, for example to write authorizatio
```java
public Document createDoc(String id) {
// ...
ClientWriteRequest writeRequest = new ClientWriteRequest()
.writes(List.of(new ClientTupleKey()
.user(String.format("user:%s", SecurityContextHolder.getContext().getAuthentication()))
.relation("owner")
._object(String.format("document:%s", id))));
ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(new ClientTupleKey()
.user(String.format("user:%s", SecurityContextHolder.getContext().getAuthentication()))
.relation("owner")
._object(String.format("document:%s", id))));
try {
fgaClient.write(writeRequest).get();
@@ -163,20 +325,45 @@ public Document getDocument(@PathVariable String docId) {
}
```

## Customize ApiClient and HttpClient Configuration

To customize the `ApiClient` configuration, create a `@Bean` method in your Spring Boot application:

```java
@Bean
public ApiClient apiClient(HttpClient.Builder builder, ObjectMapper mapper) {
return new ApiClient(httpClientBuilder, objectMapper);
}
```

Similarly, to customize the `HttpClient.Builder`:

```java
@Bean
public HttpClient.Builder httpClientBuilder() {
return HttpClient.newBuilder()
.version(Version.HTTP_2);
}
```

## Contributing

### Issues

If you have found a bug or if you have a feature request, please [create an issue](https://github.com/openfga/fga-spring-boot/issues). Please do not report security vulnerabilities on the public GitHub issue tracker.
If you have found a bug or if you have a feature request,
please [create an issue](https://github.com/openfga/fga-spring-boot/issues). Please do not report security
vulnerabilities on the public GitHub issue tracker.

### Pull Requests

Pull requests are welcome, however we do kindly ask that for non-trivial changes or feature additions, that you create an [issue]((https://github.com/openfga/fga-spring-boot/issues)) first.
Pull requests are welcome, however, we do kindly ask that for non-trivial changes or feature additions, that you create
an [issue]((https://github.com/openfga/fga-spring-boot/issues)) first.

## Author

[OpenFGA](https://github.com/openfga)

## License

This project is licensed under the Apache-2.0 license. See the [LICENSE](https://github.com/openfga/fga-spring-boot/blob/main/LICENSE) file for more info.
This project is licensed under the Apache-2.0 license. See
the [LICENSE](https://github.com/openfga/fga-spring-boot/blob/main/LICENSE) file for more info.
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -45,6 +45,10 @@ dependencies {

api 'dev.openfga:openfga-sdk:0.7.2'

compileOnly 'com.fasterxml.jackson.core:jackson-databind'
compileOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
compileOnly 'org.openapitools:jackson-databind-nullable:0.2.6'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.hamcrest:hamcrest:2.2'
@@ -69,6 +73,10 @@ spotless {
removeUnusedImports()
importOrder()
}
flexmark {
target "*.md"
flexmark()
}
}

test {
14 changes: 10 additions & 4 deletions examples/servlet/README.md
Original file line number Diff line number Diff line change
@@ -16,9 +16,15 @@ To use a different FGA server, update `src/main/resources/application.yaml` acco

### Start the example application:

To run a local OpenFGA instance, you can use the provided `docker-compose.yml` file:

```shell
docker-compose up -d
```

In a terminal, start the application:

```bash
```shell
./gradlew bootRun
```

@@ -46,15 +52,15 @@ You should receive a 200 response with the document:

Execute a request for document 2, for which user `anne` does **not** have viewer access to:

```bash
```shell
curl http://localhost:8080/documents/2
```

You should receive a 403 response, as user `anne` does not have the required relation to document 2.

You can also create a document, for which user `anne` will be granted the owner relation for the document:

```bash
```shell
curl -d '{"id": "10", "content": "new document content"}' -H 'Content-Type: application/json' http://localhost:8080/documents
```

@@ -64,7 +70,7 @@ To run the example using a non-published version of the Okta FGA Spring Boot Sta

In the root directory of this repository, run:

```bash
```shell
./gradlew assemble publishToMavenLocal
```

7 changes: 7 additions & 0 deletions examples/servlet/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
openfga:
image: openfga/openfga:latest
command: run
ports:
- "4000:8080"

6 changes: 6 additions & 0 deletions examples/servlet/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -8,3 +8,9 @@ logging:
### NO AUTH - for example purposes only!! ###
openfga:
api-url: http://localhost:4000
user-agent: My Custom App 12.0
read-timeout: 30
connect-timeout: 5s
max-retries: 3
minimum-retry-delay: 5
http-version: HTTP_1_1
6 changes: 5 additions & 1 deletion src/main/java/dev/openfga/OpenFga.java
Original file line number Diff line number Diff line change
@@ -26,7 +26,11 @@ public class OpenFga {

private final OpenFgaClient fgaClient;

// Inject OpenFga client
/**
* Create a new OpenFGA instance.
*
* @param fgaClient The OpenFGA client to use
*/
public OpenFga(OpenFgaClient fgaClient) {
this.fgaClient = fgaClient;
}
14 changes: 14 additions & 0 deletions src/main/java/dev/openfga/OpenFgaCheckException.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
package dev.openfga;

/**
* Exception thrown when there is an error during an OpenFGA check operation.
* This exception is typically used to wrap lower-level exceptions and provide
* additional context about the failure.
*
* See the {@link OpenFga#check(String, String, String, String, String)} method for more information about the check operation.
*
*/
public class OpenFgaCheckException extends RuntimeException {

/**
* Constructs a new OpenFgaCheckException with the specified detail message and cause.
*
* @param message the detail message explaining the reason for the exception
* @param cause the underlying cause of the exception
*/
public OpenFgaCheckException(String message, Throwable cause) {
super(message, cause);
}
Original file line number Diff line number Diff line change
@@ -8,6 +8,10 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

/**
* Conditional that checks if the OpenFGA API URL is set. If it is, the
* {@link OpenFgaClient} bean will be created.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@ConditionalOnProperty(name = {"openfga.api-url"})
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dev.openfga.autoconfigure;

import java.net.http.HttpClient;

/**
* Callback interface that can be used to customize the configuration of an {@link HttpClient.Builder}.
*/
@FunctionalInterface
public interface HttpClientBuilderCustomizer {

/**
* Callback to customize a {@link HttpClient.Builder} instance.
* @param builder HTTP builder to customize
*/
void customize(HttpClient.Builder builder);
}
33 changes: 33 additions & 0 deletions src/main/java/dev/openfga/autoconfigure/HttpVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.openfga.autoconfigure;

import java.net.http.HttpClient;

/**
* The HTTP protocol version to use for the client connection to the server when making requests to OpenFGA.
*/
public enum HttpVersion {

/**
* HTTP version 1.1
* <p>
* This version is widely supported and used for most HTTP communications.
*/
HTTP_1_1(HttpClient.Version.HTTP_1_1),

/**
* HTTP version 2
* <p>
* This version offers improved performance and efficiency over HTTP/1.1.
*/
HTTP_2(HttpClient.Version.HTTP_2);

private final HttpClient.Version version;

HttpVersion(HttpClient.Version version) {
this.version = version;
}

HttpClient.Version getVersion() {
return version;
}
}
178 changes: 149 additions & 29 deletions src/main/java/dev/openfga/autoconfigure/OpenFgaAutoConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
package dev.openfga.autoconfigure;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import dev.openfga.OpenFga;
import dev.openfga.sdk.api.client.ApiClient;
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.*;
import dev.openfga.sdk.errors.FgaInvalidParameterException;
import java.net.http.HttpClient;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.openapitools.jackson.nullable.JsonNullableModule;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;

/**
@@ -21,55 +35,161 @@
@EnableConfigurationProperties(OpenFgaProperties.class)
public class OpenFgaAutoConfiguration {

private final OpenFgaProperties openFgaProperties;
/**
* Configures the OpenFGA client with the provided properties.
*
* @param openFgaProperties the configuration properties for OpenFGA
* @return the configured {@link ClientConfiguration} bean
*/
@Bean
@ConditionalOnMissingBean
public ClientConfiguration fgaConfig(OpenFgaProperties openFgaProperties) {
ClientConfiguration clientConfiguration = new ClientConfiguration();
PropertyMapper map = PropertyMapper.get();
map.from(openFgaProperties::getCredentials)
.whenNonNull()
.as(OpenFgaAutoConfiguration::toCredentials)
.to(clientConfiguration::credentials);
map.from(openFgaProperties::getApiUrl).whenHasText().to(clientConfiguration::apiUrl);
map.from(openFgaProperties::getStoreId).whenHasText().to(clientConfiguration::storeId);
map.from(openFgaProperties::getAuthorizationModelId)
.whenHasText()
.to(clientConfiguration::authorizationModelId);
map.from(openFgaProperties::getUserAgent).whenHasText().to(clientConfiguration::userAgent);
map.from(openFgaProperties::getReadTimeout).whenNonNull().to(clientConfiguration::readTimeout);
map.from(openFgaProperties::getConnectTimeout).whenNonNull().to(clientConfiguration::connectTimeout);
map.from(openFgaProperties::getMaxRetries).whenNonNull().to(clientConfiguration::maxRetries);
map.from(openFgaProperties::getMinimumRetryDelay).whenNonNull().to(clientConfiguration::minimumRetryDelay);
map.from(openFgaProperties::getDefaultHeaders).whenNonNull().to(clientConfiguration::defaultHeaders);
map.from(openFgaProperties::getTelemetryConfiguration)
.whenNonNull()
.as(OpenFgaAutoConfiguration::toTelemetryConfiguration)
.to(clientConfiguration::telemetryConfiguration);
return clientConfiguration;
}

private static Credentials toCredentials(OpenFgaProperties.Credentials credentialsProperties) {
Credentials credentials = new Credentials();
if (OpenFgaProperties.CredentialsMethod.API_TOKEN == credentialsProperties.getMethod()) {
credentials.setCredentialsMethod(CredentialsMethod.API_TOKEN);
credentials.setApiToken(
new ApiToken(credentialsProperties.getConfig().getApiToken()));
} else if (OpenFgaProperties.CredentialsMethod.CLIENT_CREDENTIALS == credentialsProperties.getMethod()) {
ClientCredentials clientCredentials = new ClientCredentials()
.clientId(credentialsProperties.getConfig().getClientId())
.clientSecret(credentialsProperties.getConfig().getClientSecret())
.apiTokenIssuer(credentialsProperties.getConfig().getApiTokenIssuer())
.apiAudience(credentialsProperties.getConfig().getApiAudience())
.scopes(credentialsProperties.getConfig().getScopes());
credentials.setCredentialsMethod(CredentialsMethod.CLIENT_CREDENTIALS);
credentials.setClientCredentials(clientCredentials);
}
return credentials;
}

public OpenFgaAutoConfiguration(OpenFgaProperties openFgaProperties) {
this.openFgaProperties = openFgaProperties;
private static TelemetryConfiguration toTelemetryConfiguration(
Map<TelemetryMetric, Map<TelemetryAttribute, Object>> telemetryConfiguration) {
return new TelemetryConfiguration()
.metrics(telemetryConfiguration.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey().getMetric(), metric -> metric.getValue().entrySet().stream()
.collect(Collectors.toMap(
metricConfig ->
metricConfig.getKey().getAttribute(),
metricConfig -> Optional.ofNullable(metricConfig.getValue()))))));
}

/**
* Provides a default {@link HttpClient.Builder} bean if none is already defined and the
* {@code openfga.http-version} property is set.
*
* @return a default {@link HttpClient.Builder} bean
*/
@Bean
@ConditionalOnProperty(name = "openfga.http-version")
@ConditionalOnMissingBean
public ClientConfiguration fgaConfig() {
var credentials = new Credentials();
HttpClient.Builder defaultHttpClientBuilder() {
return HttpClient.newBuilder();
}

var credentialsProperties = openFgaProperties.getCredentials();
/**
* Provides a default {@link HttpClientBuilderCustomizer} bean if none is already defined.
*
* @param openFgaProperties the configuration properties for OpenFGA
* @return a customizer for the {@link HttpClient.Builder}
*/
@Bean
@ConditionalOnMissingBean
HttpClientBuilderCustomizer defaultHttpClientBuilderCustomizer(OpenFgaProperties openFgaProperties) {
return builder -> {
if (openFgaProperties.getHttpVersion() != null) {
builder.version(openFgaProperties.getHttpVersion().getVersion());
}
};
}

if (credentialsProperties != null) {
if (OpenFgaProperties.CredentialsMethod.API_TOKEN.equals(credentialsProperties.getMethod())) {
credentials.setCredentialsMethod(CredentialsMethod.API_TOKEN);
credentials.setApiToken(
new ApiToken(credentialsProperties.getConfig().getApiToken()));
} else if (OpenFgaProperties.CredentialsMethod.CLIENT_CREDENTIALS.equals(
credentialsProperties.getMethod())) {
ClientCredentials clientCredentials = new ClientCredentials()
.clientId(credentialsProperties.getConfig().getClientId())
.clientSecret(credentialsProperties.getConfig().getClientSecret())
.apiTokenIssuer(credentialsProperties.getConfig().getApiTokenIssuer())
.apiAudience(credentialsProperties.getConfig().getApiAudience())
.scopes(credentialsProperties.getConfig().getScopes());
/**
* Creates an {@link ApiClient} bean if none exists.
*
* @param httpClientBuilderProvider provides the {@link HttpClient.Builder} bean
* @param objectMapperProvider provides the {@link ObjectMapper} bean
* @param httpClientBuilderCustomizer customizes the {@link HttpClient.Builder}
* @return the configured {@link ApiClient} bean
*/
@Bean
@ConditionalOnMissingBean
public ApiClient apiClient(
ObjectProvider<HttpClient.Builder> httpClientBuilderProvider,
ObjectProvider<ObjectMapper> objectMapperProvider,
HttpClientBuilderCustomizer httpClientBuilderCustomizer) {

credentials.setCredentialsMethod(CredentialsMethod.CLIENT_CREDENTIALS);
credentials.setClientCredentials(clientCredentials);
}
if (httpClientBuilderProvider.getIfAvailable() == null && objectMapperProvider.getIfAvailable() == null) {
return new ApiClient();
}
HttpClient.Builder httpClientBuilder = httpClientBuilderProvider.getIfAvailable(HttpClient::newBuilder);
httpClientBuilderCustomizer.customize(httpClientBuilder);
return new ApiClient(
httpClientBuilder,
objectMapperProvider.getIfAvailable(OpenFgaAutoConfiguration::createDefaultObjectMapper));
}

return new ClientConfiguration()
.apiUrl(openFgaProperties.getApiUrl())
.storeId(openFgaProperties.getStoreId())
.authorizationModelId(openFgaProperties.getAuthorizationModelId())
.credentials(credentials);
private static ObjectMapper createDefaultObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
mapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
mapper.registerModule(new JavaTimeModule());
mapper.registerModule(new JsonNullableModule());
return mapper;
}

/**
* Defines an {@link OpenFgaClient} bean if not already present in the context.
*
* @param configuration the {@link ClientConfiguration} bean containing OpenFGA settings
* @param apiClient the {@link ApiClient} bean for making API requests
* @return a configured {@link OpenFgaClient} bean
*/
@Bean
@ConditionalOnMissingBean
public OpenFgaClient fgaClient(ClientConfiguration configuration) {
public OpenFgaClient fgaClient(ClientConfiguration configuration, ApiClient apiClient) {
try {
return new OpenFgaClient(configuration);
return new OpenFgaClient(configuration, apiClient);
} catch (FgaInvalidParameterException e) {
throw new BeanCreationException("Failed to create OpenFgaClient", e);
}
}

/**
* Creates an {@link OpenFga} bean if no other bean of this type is present.
*
* @param openFgaClient the {@link OpenFgaClient} bean
* @return the {@link OpenFga} bean
*/
@Bean
@ConditionalOnMissingBean
public OpenFga fga(OpenFgaClient openFgaClient) {
376 changes: 357 additions & 19 deletions src/main/java/dev/openfga/autoconfigure/OpenFgaProperties.java

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions src/main/java/dev/openfga/autoconfigure/TelemetryAttribute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package dev.openfga.autoconfigure;

import dev.openfga.sdk.telemetry.Attribute;
import dev.openfga.sdk.telemetry.Attributes;
import java.util.Objects;

/**
* The telemetry attributes that can be used to log telemetry data.
*
* See <a href="https://openfga.dev/docs/getting-started/configure-telemetry">OpenFGA Telemetry</a> for more information.
*/
public enum TelemetryAttribute {
/**
* The client ID used in the request, if applicable.
*/
FGA_CLIENT_REQUEST_CLIENT_ID(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID),
/**
* The FGA method / action of the request.
*/
FGA_CLIENT_REQUEST_METHOD(Attributes.FGA_CLIENT_REQUEST_METHOD),
/**
* The authorization model ID used in the request, if applicable.
*/
FGA_CLIENT_REQUEST_MODEL_ID(Attributes.FGA_CLIENT_REQUEST_MODEL_ID),
/**
* The store ID used in the request, if applicable.
*/
FGA_CLIENT_REQUEST_STORE_ID(Attributes.FGA_CLIENT_REQUEST_STORE_ID),
/**
* The authorization model ID used by the server when evaluating the request, if applicable.
*/
FGA_CLIENT_RESPONSE_MODEL_ID(Attributes.FGA_CLIENT_RESPONSE_MODEL_ID),
/**
* The HTTP host used in the request, e.g. "example.com".
*/
HTTP_HOST(Attributes.HTTP_HOST),
/**
* The HTTP method used in the request, e.g. "GET".
*/
HTTP_REQUEST_METHOD(Attributes.HTTP_REQUEST_METHOD),
/**
* The number of times the request was retried
*/
HTTP_REQUEST_RESEND_COUNT(Attributes.HTTP_REQUEST_RESEND_COUNT),
/**
* The HTTP status code returned by the server for the request, e.g. 200.
*/
HTTP_RESPONSE_STATUS_CODE(Attributes.HTTP_RESPONSE_STATUS_CODE),
/**
* The complete URL used in the request, e.g. "https://example.com/path".
*/
URL_FULL(Attributes.URL_FULL),
/**
* The scheme used in the request, e.g. "https".
*/
URL_SCHEME(Attributes.URL_SCHEME),
/**
* The user agent used in the request, e.g. "Mozilla/5.0".
*/
USER_AGENT(Attributes.USER_AGENT);

private final Attribute attribute;

TelemetryAttribute(Attribute attribute) {
this.attribute = Objects.requireNonNull(attribute);
}

Attribute getAttribute() {
return attribute;
}
}
36 changes: 36 additions & 0 deletions src/main/java/dev/openfga/autoconfigure/TelemetryMetric.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dev.openfga.autoconfigure;

import dev.openfga.sdk.telemetry.Counters;
import dev.openfga.sdk.telemetry.Histograms;
import dev.openfga.sdk.telemetry.Metric;
import java.util.Objects;

/**
* The telemetry metrics that can be used to log telemetry data.
*
* See <a href="https://openfga.dev/docs/getting-started/configure-telemetry">OpenFGA Telemetry</a> for more information.
*/
public enum TelemetryMetric {
/**
* The CREDENTIALS_REQUEST counter represents the number of times an access token is requested.
*/
CREDENTIALS_REQUEST(Counters.CREDENTIALS_REQUEST),
/**
* A histogram for measuring the total time it took (in milliseconds) for the FGA server to process and evaluate the request.
*/
QUERY_DURATION(Histograms.QUERY_DURATION),
/**
* A histogram for measuring the total time (in milliseconds) it took for the request to complete, including the time it took to send the request and receive the response.
*/
REQUEST_DURATION(Histograms.REQUEST_DURATION);

private final Metric metric;

TelemetryMetric(Metric metric) {
this.metric = Objects.requireNonNull(metric);
}

Metric getMetric() {
return metric;
}
}
10 changes: 5 additions & 5 deletions src/test/java/dev/openfga/OpenFgaTest.java
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@
import org.springframework.security.core.context.SecurityContextHolder;

@ExtendWith(MockitoExtension.class)
public class OpenFgaTest {
class OpenFgaTest {

@Mock
private OpenFgaClient mockClient;
@@ -34,7 +34,7 @@ public class OpenFgaTest {
private ClientCheckResponse mockCheckResponse;

@Test
public void fgaCheckCalled() throws Exception {
void fgaCheckCalled() throws Exception {
// given
OpenFga openFga = new OpenFga(mockClient);
when(mockCheckResponseFuture.get()).thenReturn(mockCheckResponse);
@@ -56,7 +56,7 @@ public void fgaCheckCalled() throws Exception {
}

@Test
public void usesPrincipalNameAsUserId() throws Exception {
void usesPrincipalNameAsUserId() throws Exception {
// given
Principal principal = () -> "userId";
Authentication auth = new UsernamePasswordAuthenticationToken(principal, null);
@@ -82,7 +82,7 @@ public void usesPrincipalNameAsUserId() throws Exception {
}

@Test
public void failsWhenNoUserIdSpecifiedAndNotFoundInContext() throws Exception {
void failsWhenNoUserIdSpecifiedAndNotFoundInContext() throws Exception {
// given
OpenFga openFga = new OpenFga(mockClient);

@@ -95,7 +95,7 @@ public void failsWhenNoUserIdSpecifiedAndNotFoundInContext() throws Exception {
}

@Test
public void failsWhenCheckHasException() throws Exception {
void failsWhenCheckHasException() throws Exception {
// given
OpenFga openFga = new OpenFga(mockClient);

194 changes: 0 additions & 194 deletions src/test/java/dev/openfga/autoconfigure/FgaAutoConfigurationTests.java

This file was deleted.

Large diffs are not rendered by default.

0 comments on commit 792afa2

Please sign in to comment.