This guide explores the process of generating the Kotlin binding for Cedarling using Cedarling UniFFI. The Kotlin binding is then wrapped in a Java class to enable convenient use in Java applications.
- Rust: Install it from the official Rust website.
- Java Development Kit (JDK): version 11 or higher
- Apache Maven: Install it from Apache Maven Website
- Build Cedarling by executing below command from
./jans/jans-cedarlingof cloned jans project:
cargo build -r -p cedarling_uniffiIn target/release, you should find the libcedarling_uniffi.dylib (if Mac OS), libcedarling_uniffi.so (if Linux OS), or libcedarling_uniffi.dll (if Windows OS) file, depending on the operating system you are using.
- Generate the bindings for Kotlin by running the command below. Replace
{build_file}withlibcedarling_uniffi.dylib,libcedarling_uniffi.so, orlibcedarling_uniffi.dll, depending on which file is generated intarget/release.
cargo run --bin uniffi-bindgen generate --library ./target/release/{build_file} --language kotlin --out-dir ./bindings/cedarling-java/src/main/kotlin/io/jans/cedarling- Copy the generated
libcedarling_uniffi.dylib,libcedarling_uniffi.so, orlibcedarling_uniffi.dllfile to resource directory of thecedarling-javaMaven project. Replace{build_file}in the below commad withlibcedarling_uniffi.dylib,libcedarling_uniffi.so, orlibcedarling_uniffi.dll, depending on which file is generated intarget/release.
mkdir ./bindings/cedarling-java/src/main/resources
cp ./target/release/{build_file} ./bindings/cedarling-java/src/main/resources- Change directory to
./bindings/cedarling-javaand run below command to buildcedarling-javajar file. This will generatecedarling-java-{version}-distribution.jarat./bindings/cedarling-java/target/.
mvn clean installTo use Cedarling Java bindings in Java Maven Project add following repository and dependency in pom.xml of the project
<repositories>
<repository>
<id>jans</id>
<name>Janssen project repository</name>
<url>https://maven.jans.io/maven</url>
</repository>
</repositories>
<dependency>
<groupId>io.jans</groupId>
<artifactId>cedarling-java</artifactId>
<version>{latest-jans-stable-version}</version>
</dependency>
Here is a simple recipe to add scopes in access-token using update_token script only if the requesting client has authorization_code grant-type. We will use below policy for this:
@id("Allow_authorization_code")
permit (
principal is Jans::Workload,
action == Jans::Action::"Execute",
resource is Jans::Application
)
when {
principal.grantTypes.contains("authorization_code")
};namespace Jans {
type Context = {
current_time?: Long,
device_health?: Set<String>,
fraud_indicators?: Set<String>,
geolocation?: Set<String>,
network?: String,
network_type?: String,
operating_system?: String,
user_agent?: String
};
type Url = __cedar::String;
type email_address = {
domain: String,
uid: String
};
entity Application = {
grantTypes: Set<String>
};
entity Role;
entity TrustedIssuer = {
issuer_entity_id: Url
};
entity User in [Role] = {
email?: email_address,
role: Set<String>,
sub?: String
};
entity Workload = {
client_id: String,
grantTypes: Set<String>,
iss?: TrustedIssuer,
name?: String,
rp_id?: String,
spiffe_id?: String
};
action "Execute" appliesTo {
principal: [Workload],
resource: [Application],
context: Context
};
}
-
Upload bootstrap.json and update_token_script.cjar at
/opt/jans/jetty/jans-auth/custom/staticlocation of the auth server. -
Upload the generated
cedarling-java-{version}-distribution.jarat/opt/jans/jetty/jans-auth/custom/libslocation of the auth server. Rather than building thecedarling-java-{version}.jarfrom source code, you can directly download the latest version of the jar from the Maven repository. -
The following java Update Token script has been created for calling Cedarling authorization. Enable the script with following Custom Properties:
Key Values BOOTSTRAP_JSON_PATH ./custom/static/bootstrap.json -
Map the script with client used to perform authentication.
- The script runs before access_token generation and includes openid and profile scopes into the token if the oidc client has authorization_code in grant-types.
Cedarling supports multiple ways to load policy stores:
{
"CEDARLING_POLICY_STORE_LOCAL_FN": "/path/to/policy-store.json",
"CEDARLING_POLICY_STORE_URI": "https://lock-server.example.com/policy-store"
}Policy stores can be structured as directories with human-readable Cedar files:
policy-store/
├── metadata.json # Required: Store metadata (id, name, version)
├── manifest.json # Optional: File checksums for integrity validation
├── schema.cedarschema # Required: Cedar schema (human-readable)
├── policies/ # Required: .cedar policy files
│ ├── allow-read.cedar
│ └── deny-guest.cedar
├── templates/ # Optional: .cedar template files
├── entities/ # Optional: .json entity files
└── trusted-issuers/ # Optional: .json issuer configurations
metadata.json structure:
{
"cedar_version": "4.4.0",
"policy_store": {
"id": "abc123def456",
"name": "My Application Policies",
"version": "1.0.0"
}
}Policy stores can be packaged as .cjar files (ZIP archives) for easy distribution.
For testing scenarios, you may want to disable JWT validation. You can configure this in your bootstrap configuration:
{
"CEDARLING_JWT_SIG_VALIDATION": "disabled",
"CEDARLING_JWT_STATUS_VALIDATION": "disabled"
}For complete configuration documentation, see cedarling-properties.md.
The Context Data API allows you to push external data into the Cedarling evaluation context, making it available in Cedar policies through the context.data namespace.
These methods are available on the underlying UniFFI-generated Cedarling instance returned by getCedarling().
In Java/Kotlin bindings, JsonValue is represented as a plain String.
import uniffi.cedarling_uniffi.*;
import org.json.JSONObject;
import java.util.List;
CedarlingAdapter adapter = new CedarlingAdapter();
adapter.loadFromJson(bootstrapJson);
Cedarling cedarling = adapter.getCedarling();
// Push data with optional TTL (in seconds)
// The TTL parameter is a nullable Long representing seconds.
// Pass null to use the default TTL from configuration, or pass a Long value for a custom TTL.
String value = "{\"role\":[\"admin\",\"editor\"],\"country\":\"US\"}";
cedarling.pushDataCtx("user:123", value, null); // null uses default TTL
cedarling.pushDataCtx("config:app", value, 300L); // Custom TTL: 300 seconds (5 minutes)
// Get data
String result = cedarling.getDataCtx("user:123");
if (result != null) {
JSONObject data = new JSONObject(result);
}
// Get data entry
DataEntry entry = cedarling.getDataEntryCtx("user:123");
if (entry != null) {
System.out.println("Key: " + entry.getKey());
System.out.println("Data type: " + entry.getDataType());
System.out.println("Created at: " + entry.getCreatedAt());
}
// Remove / clear / list / stats
boolean removed = cedarling.removeDataCtx("user:123");
cedarling.clearDataCtx();
List<DataEntry> entries = cedarling.listDataCtx();
DataStoreStats stats = cedarling.getStatsCtx();Data pushed via the Context Data API is automatically available in Cedar policies under the context.data namespace:
permit(
principal,
action == Action::"read",
resource
) when {
context.data has principal.id &&
context.data[principal.id].role.contains("admin")
};
The data is injected into the evaluation context before policy evaluation, allowing policies to make decisions based on dynamically pushed data.
The Context Data API methods throw DataException with specific variants:
DataException.InvalidKey()- Thrown when the key is empty or invalidDataException.KeyNotFound(String key)- Thrown when attempting to access a non-existent keyDataException.StorageLimitExceeded(Long max)- Thrown when the storage limit is exceededDataException.TtlExceeded(Long provided, Long max)- Thrown when the provided TTL exceeds the maximum allowed TTLDataException.ValueTooLarge(Long size, Long max)- Thrown when the value size exceeds the maximum allowed sizeDataException.SerializationException(String message)- Thrown when serialization/deserialization fails
Example error handling:
try {
cedarling.pushDataCtx("", "{\"data\":\"value\"}", null); // Empty key
} catch (DataException.InvalidKey e) {
System.out.println("Invalid key provided");
} catch (DataException.StorageLimitExceeded e) {
System.out.println("Storage limit exceeded: max=" + e.getMax());
} catch (DataException.SerializationException e) {
System.out.println("Serialization error: " + e.getMessage());
} catch (DataException e) {
System.out.println("Data operation failed: " + e);
}Note: The CedarlingAdapter wrapper performs additional validation and may throw DataException.InvalidKey() for null or empty keys, or DataException.SerializationException() for null values before calling the underlying Rust implementation.
## Trusted Issuer Loading Info
`CedarlingAdapter` exposes trusted issuer loading status methods:
```java
boolean loadedByName = adapter.isTrustedIssuerLoadedByName("issuer_id");
boolean loadedByIss = adapter.isTrustedIssuerLoadedByIss("https://issuer.example.org");
long totalIssuers = adapter.totalIssuers();
long loadedCount = adapter.loadedTrustedIssuersCount();
List<String> loadedIds = adapter.loadedTrustedIssuerIds();
List<String> failedIds = adapter.failedTrustedIssuerIds();
These methods are useful when your policy store includes trusted-issuers/ definitions.
