Skip to content

Commit e14944d

Browse files
feat(jans-cedarling): Support Initializing Cedarling with .cjar Bytes in UniFFI Bindings (#13548)
* feat(jans-cedarling): add load_from_json_with_archive_bytes method to uniffi bindings Signed-off-by: haileyesus2433 <[email protected]> * docs: enhance Cedarling initialization with new loading methods - Introduced `load_from_json_with_archive_bytes` method, allowing Cedarling instances to be initialized with a JSON string and raw bytes of a Cedar archive, improving flexibility for environments like Android. - Added usage examples in Kotlin and Swift for the new loading method, enhancing developer guidance. Signed-off-by: haileyesus2433 <[email protected]> * fix(jans-cedarling): add blank line Signed-off-by: haileyesus2433 <[email protected]> --------- Signed-off-by: haileyesus2433 <[email protected]>
1 parent a744d68 commit e14944d

File tree

4 files changed

+116
-2
lines changed

4 files changed

+116
-2
lines changed

docs/cedarling/developer/mobile-apps/cedarling-uniffi.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,34 @@ Speaking about Cedarling, it interacts with outside world mainly using 3 interfa
4545
**Usage in Kotlin:**
4646

4747
```declarative
48-
val cedarling: Cedarling = Cedarling.loadFromJson("/path/to/config.json")
48+
val cedarling: Cedarling = Cedarling.loadFromFile("/path/to/config.json")
49+
```
50+
51+
- **Cedarling::load_from_json_with_archive_bytes**
52+
53+
Loads a Cedarling instance from a bootstrap JSON string plus the raw bytes of a Cedar archive (`.cjar`). Use this when the host cannot pass a policy store path—for example **Android `assets/`** read via `AssetManager`. Fields `CEDARLING_POLICY_STORE_LOCAL`, `CEDARLING_POLICY_STORE_URI`, and `CEDARLING_POLICY_STORE_LOCAL_FN` in the JSON are ignored; the archive bytes are the policy source (same idea as WASM `init_from_archive_bytes`).
54+
55+
```declarative
56+
#[uniffi::constructor]
57+
pub fn load_from_json_with_archive_bytes(
58+
config: String,
59+
archive_bytes: Vec<u8>,
60+
) -> Result<Self, CedarlingError>
61+
```
62+
63+
**Usage in Swift:**
64+
65+
```declarative
66+
let cedarling = try Cedarling.loadFromJsonWithArchiveBytes(
67+
config: configJson,
68+
archiveBytes: cjarData
69+
)
70+
```
71+
72+
**Usage in Kotlin:**
73+
74+
```declarative
75+
val cedarling = Cedarling.loadFromJsonWithArchiveBytes(configJson, cjarBytes)
4976
```
5077

5178
- **Cedarling::authorizeUnsigned**

jans-cedarling/bindings/cedarling_uniffi/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,42 @@ Policy stores can be packaged as `.cjar` files (ZIP archives) for easy distribut
215215
- Works across all platforms
216216
- Supports integrity validation via manifest
217217

218+
### Initializing Cedarling (UniFFI)
219+
220+
Regenerate Kotlin/Swift bindings from the library (`uniffi-bindgen generate …`) so these constructors match your checkout. Exact method names and argument labels are in the generated `cedarling_uniffi.kt` / `cedarling_uniffi.swift`; the table below is a rough map from the Rust API:
221+
222+
| Rust constructor | Kotlin (typical) | Swift (typical) |
223+
| --- | --- | --- |
224+
| `load_from_json` | `Cedarling.loadFromJson(...)` | `Cedarling.loadFromJson(config:)` |
225+
| `load_from_file` | `Cedarling.loadFromFile(...)` | `Cedarling.loadFromFile(path:)` |
226+
| `load_from_json_with_archive_bytes` | `Cedarling.loadFromJsonWithArchiveBytes(...)` | `Cedarling.loadFromJsonWithArchiveBytes(config:archiveBytes:)` |
227+
228+
- **`load_from_json`** — Policy store location comes from the JSON (`CEDARLING_POLICY_STORE_LOCAL_FN`, `CEDARLING_POLICY_STORE_URI`, or `CEDARLING_POLICY_STORE_LOCAL`), same as core Cedarling bootstrap rules.
229+
- **`load_from_file`** — Load bootstrap from a path, then resolve the policy store from fields in that file.
230+
- **`load_from_json_with_archive_bytes`** — Pass the bootstrap JSON as a string **and** the raw bytes of a `.cjar` archive. Fields `CEDARLING_POLICY_STORE_LOCAL`, `CEDARLING_POLICY_STORE_URI`, and `CEDARLING_POLICY_STORE_LOCAL_FN` in the JSON are **ignored**; the archive is the only policy source. This mirrors the WASM helper `init_from_archive_bytes` and fits **Android `assets/`**, where you open files with `AssetManager` (no ordinary filesystem path for native code), or any host that already has the archive in memory.
231+
232+
**Kotlin (Android assets):**
233+
234+
```kotlin
235+
val bootstrapJson =
236+
assets.open("bootstrap.json").bufferedReader().use { it.readText() }
237+
val archiveBytes = assets.open("policy-store.cjar").readBytes()
238+
val cedarling = Cedarling.loadFromJsonWithArchiveBytes(bootstrapJson, archiveBytes)
239+
```
240+
241+
**Swift (bundle resources):**
242+
243+
```swift
244+
let bootstrapUrl = Bundle.main.url(forResource: "bootstrap", withExtension: "json")!
245+
let cjarUrl = Bundle.main.url(forResource: "policy-store", withExtension: "cjar")!
246+
let bootstrapJson = try String(contentsOf: bootstrapUrl)
247+
let archiveBytes = try Data(contentsOf: cjarUrl)
248+
let cedarling = try Cedarling.loadFromJsonWithArchiveBytes(
249+
config: bootstrapJson,
250+
archiveBytes: archiveBytes
251+
)
252+
```
253+
218254
### Authorization APIs
219255

220256
The UniFFI binding exposes two authorization methods:

jans-cedarling/bindings/cedarling_uniffi/src/lib.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use cedarling::{
88
self as core, BootstrapConfig, BootstrapConfigRaw, DataApi, DataEntry as CoreDataEntry,
9-
DataStoreStats as CoreDataStoreStats, LogStorage,
9+
DataStoreStats as CoreDataStoreStats, LogStorage, PolicyStoreSource,
1010
};
1111
use std::sync::Arc;
1212
mod result;
@@ -293,6 +293,43 @@ impl Cedarling {
293293
Ok(Self { inner: cedarling })
294294
}
295295

296+
/// Loads Cedarling from bootstrap JSON plus a Cedar archive (`.cjar`) as raw bytes.
297+
///
298+
/// host cannot pass a filesystem path—for example Android assets loaded via `AssetManager`.
299+
#[uniffi::constructor]
300+
pub fn load_from_json_with_archive_bytes(
301+
config: String,
302+
archive_bytes: Vec<u8>,
303+
) -> Result<Self, CedarlingError> {
304+
let mut raw_config: BootstrapConfigRaw =
305+
serde_json::from_str(&config).map_err(|e| CedarlingError::InitializationFailed {
306+
error_msg: e.to_string(),
307+
})?;
308+
309+
raw_config.local_policy_store = None;
310+
raw_config.policy_store_uri = None;
311+
// Set a dummy .cjar file path to satisfy validation (will be overridden below)
312+
raw_config.policy_store_local_fn = Some("dummy.cjar".to_string());
313+
314+
let mut bootstrap_config = BootstrapConfig::from_raw_config(&raw_config).map_err(|e| {
315+
CedarlingError::InitializationFailed {
316+
error_msg: e.to_string(),
317+
}
318+
})?;
319+
320+
// Override the policy store source with the archive bytes
321+
bootstrap_config.policy_store_config.source =
322+
PolicyStoreSource::ArchiveBytes(archive_bytes);
323+
324+
let cedarling = core::blocking::Cedarling::new(&bootstrap_config).map_err(|e| {
325+
CedarlingError::InitializationFailed {
326+
error_msg: e.to_string(),
327+
}
328+
})?;
329+
330+
Ok(Self { inner: cedarling })
331+
}
332+
296333
// Loads Cedarling instance from a configuration file
297334
#[uniffi::constructor]
298335
pub fn load_from_file(path: String) -> Result<Self, CedarlingError> {

jans-cedarling/bindings/cedarling_uniffi/src/tests.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Copyright (c) 2024, Gluu, Inc.
55

66
use crate::Cedarling;
7+
use crate::CedarlingError;
78
use crate::{EntityData, JsonValue};
89
use serde_json::json;
910
use std::sync::Arc;
@@ -79,6 +80,19 @@ fn create_test_cedarling() -> Cedarling {
7980
.expect("Error in initializing Cedarling")
8081
}
8182

83+
#[test]
84+
fn test_load_from_json_with_archive_bytes_rejects_invalid() {
85+
let config =
86+
std::fs::read_to_string("../../bindings/cedarling_uniffi/test_files/bootstrap.json")
87+
.expect("bootstrap.json should be readable");
88+
let result = Cedarling::load_from_json_with_archive_bytes(config, vec![0x00, 0x01, 0x02, 0x03]);
89+
assert!(
90+
matches!(&result, Err(CedarlingError::InitializationFailed { .. })),
91+
"invalid archive bytes should yield InitializationFailed, is_ok={}",
92+
result.is_ok()
93+
);
94+
}
95+
8296
#[test]
8397
fn test_data_api_push_and_get() {
8498
let cedarling = create_test_cedarling();

0 commit comments

Comments
 (0)