diff --git a/.github/workflows/test-cedarling.yml b/.github/workflows/test-cedarling.yml
index 896caa6c449..0d01fa97dee 100644
--- a/.github/workflows/test-cedarling.yml
+++ b/.github/workflows/test-cedarling.yml
@@ -21,13 +21,37 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
- name: Run Tests
+ working-directory: jans-cedarling
run: |
- cd ./jans-cedarling
cargo test --workspace
- - name: Run Clippy
+ - name: Run Clippy on native target
+ working-directory: jans-cedarling
run: |
- cd ./jans-cedarling
cargo clippy -- -Dwarnings
+ wasm_tests:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1
+ with:
+ egress-policy: audit
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
+ - name: Install WASM dependencies
+ run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
+ - name: Run WASM tests using chrome
+ working-directory: jans-cedarling/bindings/cedarling_wasm
+ run: |
+ wasm-pack test --headless --chrome
+ - name: Run WASM tests using firefox
+ working-directory: jans-cedarling/bindings/cedarling_wasm
+ run: |
+ wasm-pack test --headless --firefox
+ - name: Run Clippy on WASM target
+ working-directory: jans-cedarling
+ run: |
+ cargo clippy --target wasm32-unknown-unknown -- -Dwarnings
python_tests:
runs-on: ubuntu-latest
strategy:
@@ -48,6 +72,6 @@ jobs:
python3 -m pip install --upgrade pip || echo "Failed to upgrade pip"
python3 -m pip install tox
- name: Test with pytest
+ working-directory: jans-cedarling/bindings/cedarling_python
run: |
- cd ./jans-cedarling/bindings/cedarling_python
tox
diff --git a/docs/cedarling/cedarling-authz.md b/docs/cedarling/cedarling-authz.md
index 0f5f03fe0f3..89f3a5c7619 100644
--- a/docs/cedarling/cedarling-authz.md
+++ b/docs/cedarling/cedarling-authz.md
@@ -55,7 +55,9 @@ Action, Resource and Context is sent by the application in the authorization req
this is a sample request from a hypothetical application:
```js
-input = {
+const bootstrap_config = {...};
+const cedarling = await init(bootstrap_config);
+let input = {
"tokens": {
"access_token": "eyJhbGc....",
"id_token": "eyJjbGc...",
@@ -76,7 +78,7 @@ input = {
}
}
-decision_result = authz(input)
+decision_result = await cedarling(input)
```
## Automatically Adding Entity References to the Context
diff --git a/docs/cedarling/wasm/README.md b/docs/cedarling/wasm/README.md
new file mode 100644
index 00000000000..b58712cf220
--- /dev/null
+++ b/docs/cedarling/wasm/README.md
@@ -0,0 +1,228 @@
+---
+tags:
+ - cedarling
+ - wasm
+---
+
+# WASM for Cedarling
+
+Cedarling provides a binding for JavaScript programs via the `wasm-pack` tool. This allows browser developers to use the cedarling crate in their code directly.
+
+## Requirements
+
+- Rust 1.63 or greater
+- Installed `wasm-pack` via `cargo`
+- clang with `wasm` target support
+
+## Building
+
+- Install `wasm-pack` by:
+
+ ```sh
+ cargo install wasm-pack
+ ```
+
+- Build cedarling `wasm` in release:
+
+ ```bash
+ wasm-pack build --release --target web
+ ```
+
+ `wasm-pack` automatically make optimization of `wasm` binary file, using `wasm-opt`.
+- Get result in the `pkg` folder.
+
+## Including in projects
+
+For using result files in browser project you need make result `pkg` folder accessible for loading in the browser so that you can later import the corresponding file from the browser.
+
+Here is example of code snippet:
+
+```html
+
+```
+
+## Usage
+
+Before usage make sure that you have completed `Building` steps.
+You can find usage examples in the following locations:
+
+- `jans-cedarling/bindings/cedarling_wasm/index.html`: A simple example demonstrating basic usage.
+- `jans-cedarling/bindings/cedarling_wasm/cedarling_app.html`: A fully featured `Cedarling` browser app where you can test and validate your configuration.
+
+### Defined API
+
+```ts
+/**
+ * Create a new instance of the Cedarling application.
+ * This function can take as config parameter the eather `Map` other `Object`
+ */
+export function init(config: any): Promise;
+
+/**
+ * The instance of the Cedarling application.
+ */
+export class Cedarling {
+ /**
+ * Create a new instance of the Cedarling application.
+ * Assume that config is `Object`
+ */
+ static new(config: object): Promise;
+ /**
+ * Create a new instance of the Cedarling application.
+ * Assume that config is `Map`
+ */
+ static new_from_map(config: Map): Promise;
+ /**
+ * Authorize request
+ * makes authorization decision based on the [`Request`]
+ */
+ authorize(request: any): Promise;
+ /**
+ * Get logs and remove them from the storage.
+ * Returns `Array` of `Map`
+ */
+ pop_logs(): Array;
+ /**
+ * Get specific log entry.
+ * Returns `Map` with values or `null`.
+ */
+ get_log_by_id(id: string): any;
+ /**
+ * Returns a list of all log ids.
+ * Returns `Array` of `String`
+ */
+ get_log_ids(): Array;
+}
+
+/**
+ * A WASM wrapper for the Rust `cedarling::AuthorizeResult` struct.
+ * Represents the result of an authorization request.
+ */
+export class AuthorizeResult {
+ /**
+ * Convert `AuthorizeResult` to json string value
+ */
+ json_string(): string;
+ /**
+ * Result of authorization where principal is `Jans::Workload`
+ */
+ workload?: AuthorizeResultResponse;
+ /**
+ * Result of authorization where principal is `Jans::User`
+ */
+ person?: AuthorizeResultResponse;
+ /**
+ * Result of authorization
+ * true means `ALLOW`
+ * false means `Deny`
+ *
+ * this field is [`bool`] type to be compatible with [authzen Access Evaluation Decision](https://openid.github.io/authzen/#section-6.2.1).
+ */
+ decision: boolean;
+}
+
+/**
+ * A WASM wrapper for the Rust `cedar_policy::Response` struct.
+ * Represents the result of an authorization request.
+ */
+export class AuthorizeResultResponse {
+ /**
+ * Authorization decision
+ */
+ readonly decision: boolean;
+ /**
+ * Diagnostics providing more information on how this decision was reached
+ */
+ readonly diagnostics: Diagnostics;
+}
+
+/**
+ * Diagnostics
+ * ===========
+ *
+ * Provides detailed information about how a policy decision was made, including policies that contributed to the decision and any errors encountered during evaluation.
+ */
+export class Diagnostics {
+ /**
+ * `PolicyId`s of the policies that contributed to the decision.
+ * If no policies applied to the request, this set will be empty.
+ *
+ * The ids should be treated as unordered,
+ */
+ readonly reason: (string)[];
+ /**
+ * Errors that occurred during authorization. The errors should be
+ * treated as unordered, since policies may be evaluated in any order.
+ */
+ readonly errors: (PolicyEvaluationError)[];
+}
+
+/**
+ * PolicyEvaluationError
+ * =====================
+ *
+ * Represents an error that occurred when evaluating a Cedar policy.
+ */
+export class PolicyEvaluationError {
+ /**
+ * Id of the policy with an error
+ */
+ readonly id: string;
+ /**
+ * Underlying evaluation error string representation
+ */
+ readonly error: string;
+}
+```
diff --git a/jans-cedarling/.cargo/config.toml b/jans-cedarling/.cargo/config.toml
new file mode 100644
index 00000000000..4ec2f3b8620
--- /dev/null
+++ b/jans-cedarling/.cargo/config.toml
@@ -0,0 +1,2 @@
+[target.wasm32-unknown-unknown]
+runner = 'wasm-bindgen-test-runner'
diff --git a/jans-cedarling/Cargo.toml b/jans-cedarling/Cargo.toml
index 623620c7158..6649b2da915 100644
--- a/jans-cedarling/Cargo.toml
+++ b/jans-cedarling/Cargo.toml
@@ -7,13 +7,20 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
sparkv = { path = "sparkv" }
+jsonwebtoken = "9.3.0"
+jsonwebkey = "0.3.5"
+chrono = "0.4"
cedarling = { path = "cedarling" }
test_utils = { path = "test_utils" }
+wasm-bindgen = "0.2"
+wasm-bindgen-futures = "0.4"
+web-sys = "0.3"
+serde-wasm-bindgen = "0.6"
[profile.release]
strip = "symbols"
-debug-assertions = true
+debug-assertions = false
lto = "thin"
opt-level = "s"
codegen-units = 1
diff --git a/jans-cedarling/bindings/cedarling_python/Cargo.toml b/jans-cedarling/bindings/cedarling_python/Cargo.toml
index 2ad9e97c089..b28c005cdba 100644
--- a/jans-cedarling/bindings/cedarling_python/Cargo.toml
+++ b/jans-cedarling/bindings/cedarling_python/Cargo.toml
@@ -2,15 +2,17 @@
name = "cedarling_python"
version = "0.0.0"
edition = "2021"
+description = "Python binding to Cedarling"
+license = "Apache-2.0"
[lib]
name = "cedarling_python"
crate-type = ["cdylib"]
-[dependencies]
+# dependency for NOT wasm target
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
pyo3 = { version = "0.22.5", features = ["extension-module", "gil-refs"] }
-cedarling = { workspace = true }
+cedarling = { workspace = true, features = ["blocking"] }
serde = { workspace = true }
serde_json = { workspace = true }
serde-pyobject = "0.4.0"
-jsonwebtoken = "9.3.0"
diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result.rs
index fdd607bb0a5..a83372f48d1 100644
--- a/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result.rs
+++ b/jans-cedarling/bindings/cedarling_python/src/authorize/authorize_result.rs
@@ -30,7 +30,7 @@ pub struct AuthorizeResult {
impl AuthorizeResult {
/// Returns true if request is allowed
fn is_allowed(&self) -> bool {
- self.inner.is_allowed()
+ self.inner.decision
}
/// Get the decision value for workload
diff --git a/jans-cedarling/bindings/cedarling_python/src/authorize/mod.rs b/jans-cedarling/bindings/cedarling_python/src/authorize/mod.rs
index 6f967fa38be..5912207381b 100644
--- a/jans-cedarling/bindings/cedarling_python/src/authorize/mod.rs
+++ b/jans-cedarling/bindings/cedarling_python/src/authorize/mod.rs
@@ -4,8 +4,8 @@
*
* Copyright (c) 2024, Gluu, Inc.
*/
-use pyo3::prelude::*;
use pyo3::Bound;
+use pyo3::prelude::*;
pub(crate) mod authorize_result;
mod authorize_result_response;
diff --git a/jans-cedarling/bindings/cedarling_python/src/cedarling.rs b/jans-cedarling/bindings/cedarling_python/src/cedarling.rs
index 8de129b1e77..fb55e6c1852 100644
--- a/jans-cedarling/bindings/cedarling_python/src/cedarling.rs
+++ b/jans-cedarling/bindings/cedarling_python/src/cedarling.rs
@@ -62,14 +62,14 @@ use serde_pyobject::to_pyobject;
#[derive(Clone)]
#[pyclass]
pub struct Cedarling {
- inner: cedarling::Cedarling,
+ inner: cedarling::blocking::Cedarling,
}
#[pymethods]
impl Cedarling {
#[new]
fn new(config: &BootstrapConfig) -> PyResult {
- let inner = cedarling::Cedarling::new(config.inner())
+ let inner = cedarling::blocking::Cedarling::new(config.inner())
.map_err(|err| PyValueError::new_err(err.to_string()))?;
Ok(Self { inner })
}
@@ -112,7 +112,7 @@ impl Cedarling {
}
}
-fn log_entry_to_py(gil: Python, entry: &cedarling::bindings::LogEntry) -> PyResult {
+fn log_entry_to_py(gil: Python, entry: &serde_json::Value) -> PyResult {
to_pyobject(gil, entry)
.map(|v| v.unbind())
.map_err(|err| err.0)
diff --git a/jans-cedarling/bindings/cedarling_python/src/config/mod.rs b/jans-cedarling/bindings/cedarling_python/src/config/mod.rs
index 8f1b8c3a70e..4943836d61d 100644
--- a/jans-cedarling/bindings/cedarling_python/src/config/mod.rs
+++ b/jans-cedarling/bindings/cedarling_python/src/config/mod.rs
@@ -4,8 +4,8 @@
*
* Copyright (c) 2024, Gluu, Inc.
*/
-use pyo3::prelude::*;
use pyo3::Bound;
+use pyo3::prelude::*;
pub(crate) mod bootstrap_config;
diff --git a/jans-cedarling/bindings/cedarling_python/src/lib.rs b/jans-cedarling/bindings/cedarling_python/src/lib.rs
index 685260123e0..26f92fd5604 100644
--- a/jans-cedarling/bindings/cedarling_python/src/lib.rs
+++ b/jans-cedarling/bindings/cedarling_python/src/lib.rs
@@ -4,9 +4,10 @@
*
* Copyright (c) 2024, Gluu, Inc.
*/
+#![cfg(not(target_arch = "wasm32"))]
-use pyo3::prelude::*;
use pyo3::Bound;
+use pyo3::prelude::*;
mod authorize;
mod cedarling;
diff --git a/jans-cedarling/bindings/cedarling_wasm/Cargo.toml b/jans-cedarling/bindings/cedarling_wasm/Cargo.toml
new file mode 100644
index 00000000000..9749bcf08e8
--- /dev/null
+++ b/jans-cedarling/bindings/cedarling_wasm/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "cedarling_wasm"
+version = "0.0.0"
+edition = "2021"
+description = "The Cedarling is a performant local authorization service that runs the Rust Cedar Engine"
+license = "Apache-2.0"
+
+[lib]
+crate-type = ["cdylib"] # Required for WASM output
+
+[dependencies]
+# Common dependency for WASM interop
+wasm-bindgen = { workspace = true }
+wasm-bindgen-futures = { workspace = true }
+cedarling = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+serde-wasm-bindgen = { workspace = true }
+wasm-bindgen-test = "0.3.49"
+
+[profile.release]
+lto = true
+
+[package.metadata.wasm-pack.profile.release]
+wasm-opt = ['-O4', '--enable-reference-types', '--enable-gc']
+
+[dev-dependencies]
+# is used in testing
+test_utils = { workspace = true }
diff --git a/jans-cedarling/bindings/cedarling_wasm/README.md b/jans-cedarling/bindings/cedarling_wasm/README.md
new file mode 100644
index 00000000000..f49af05a1a8
--- /dev/null
+++ b/jans-cedarling/bindings/cedarling_wasm/README.md
@@ -0,0 +1,188 @@
+# Cedarling WASM
+
+This module is designed to build cedarling for browser wasm.
+
+## Building
+
+For building we use [`wasm-pack`](https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_Wasm) for install you can use command `cargo install wasm-pack`
+
+Build cedarling in release:
+
+```bash
+wasm-pack build --release --target web
+```
+
+Build cedarling in dev mode
+
+```bash
+wasm-pack build --target web --dev
+```
+
+Result files will be in `pkg` folder.
+
+## Testing
+
+For WASM testing we use `wasm-pack` and it allows to make test in `node`, `chrome`, `firefox`, `safari`. You just need specify appropriate flag.
+
+Example for firefox.
+
+```bash
+wasm-pack test --firefox
+```
+
+## Run browser example
+
+To run example using `index.html` you need execute following steps:
+
+1. Build wasm cedarling.
+2. Run webserver using `python3 -m http.server` or any other.
+3. Visit example app [localhost](http://localhost:8000/), on this app you will get log in browser console.
+ - Also you can try use cedarling with web app using [cedarling_app](http://localhost:8000/cedarling_app.html), using custom bootstrap properties and request.
+
+## WASM Usage
+
+After building WASM bindings in folder `pkg` you can find where you can find `cedarling_wasm.js` and `cedarling_wasm.d.ts` where is defined interface for application.
+
+In `index.html` described simple usage of `cedarling wasm` API:
+
+```js
+ import { BOOTSTRAP_CONFIG, REQUEST } from "/example_data.js" // Import js objects: bootstrap config and request
+ import initWasm, { init } from "/pkg/cedarling_wasm.js";
+
+ async function main() {
+ await initWasm(); // Initialize the WebAssembly module
+
+ let instance = await init(BOOTSTRAP_CONFIG);
+ let result = await instance.authorize(REQUEST);
+ console.log("result:", result);
+ }
+ main().catch(console.error);
+```
+
+Before using any function from library you need initialize WASM runtime by calling `initWasm` function.
+
+### Defined API
+
+```ts
+/**
+ * Create a new instance of the Cedarling application.
+ * This function can take as config parameter the eather `Map` other `Object`
+ */
+export function init(config: any): Promise;
+
+/**
+ * The instance of the Cedarling application.
+ */
+export class Cedarling {
+ /**
+ * Create a new instance of the Cedarling application.
+ * Assume that config is `Object`
+ */
+ static new(config: object): Promise;
+ /**
+ * Create a new instance of the Cedarling application.
+ * Assume that config is `Map`
+ */
+ static new_from_map(config: Map): Promise;
+ /**
+ * Authorize request
+ * makes authorization decision based on the [`Request`]
+ */
+ authorize(request: any): Promise;
+ /**
+ * Get logs and remove them from the storage.
+ * Returns `Array` of `Map`
+ */
+ pop_logs(): Array;
+ /**
+ * Get specific log entry.
+ * Returns `Map` with values or `null`.
+ */
+ get_log_by_id(id: string): any;
+ /**
+ * Returns a list of all log ids.
+ * Returns `Array` of `String`
+ */
+ get_log_ids(): Array;
+}
+
+/**
+ * A WASM wrapper for the Rust `cedarling::AuthorizeResult` struct.
+ * Represents the result of an authorization request.
+ */
+export class AuthorizeResult {
+ /**
+ * Convert `AuthorizeResult` to json string value
+ */
+ json_string(): string;
+ /**
+ * Result of authorization where principal is `Jans::Workload`
+ */
+ workload?: AuthorizeResultResponse;
+ /**
+ * Result of authorization where principal is `Jans::User`
+ */
+ person?: AuthorizeResultResponse;
+ /**
+ * Result of authorization
+ * true means `ALLOW`
+ * false means `Deny`
+ *
+ * this field is [`bool`] type to be compatible with [authzen Access Evaluation Decision](https://openid.github.io/authzen/#section-6.2.1).
+ */
+ decision: boolean;
+}
+
+/**
+ * A WASM wrapper for the Rust `cedar_policy::Response` struct.
+ * Represents the result of an authorization request.
+ */
+export class AuthorizeResultResponse {
+ /**
+ * Authorization decision
+ */
+ readonly decision: boolean;
+ /**
+ * Diagnostics providing more information on how this decision was reached
+ */
+ readonly diagnostics: Diagnostics;
+}
+
+/**
+ * Diagnostics
+ * ===========
+ *
+ * Provides detailed information about how a policy decision was made, including policies that contributed to the decision and any errors encountered during evaluation.
+ */
+export class Diagnostics {
+ /**
+ * `PolicyId`s of the policies that contributed to the decision.
+ * If no policies applied to the request, this set will be empty.
+ *
+ * The ids should be treated as unordered,
+ */
+ readonly reason: (string)[];
+ /**
+ * Errors that occurred during authorization. The errors should be
+ * treated as unordered, since policies may be evaluated in any order.
+ */
+ readonly errors: (PolicyEvaluationError)[];
+}
+
+/**
+ * PolicyEvaluationError
+ * =====================
+ *
+ * Represents an error that occurred when evaluating a Cedar policy.
+ */
+export class PolicyEvaluationError {
+ /**
+ * Id of the policy with an error
+ */
+ readonly id: string;
+ /**
+ * Underlying evaluation error string representation
+ */
+ readonly error: string;
+}
+```
diff --git a/jans-cedarling/bindings/cedarling_wasm/cedarling_app.html b/jans-cedarling/bindings/cedarling_wasm/cedarling_app.html
new file mode 100644
index 00000000000..f2874db77ff
--- /dev/null
+++ b/jans-cedarling/bindings/cedarling_wasm/cedarling_app.html
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+
+
+ Cedarling WASM App
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jans-cedarling/bindings/cedarling_wasm/src/lib.rs b/jans-cedarling/bindings/cedarling_wasm/src/lib.rs
new file mode 100644
index 00000000000..5fb8c52eaf7
--- /dev/null
+++ b/jans-cedarling/bindings/cedarling_wasm/src/lib.rs
@@ -0,0 +1,323 @@
+// This software is available under the Apache-2.0 license.
+// See https://www.apache.org/licenses/LICENSE-2.0.txt for full text.
+//
+// Copyright (c) 2024, Gluu, Inc.
+
+use cedarling::bindings::cedar_policy;
+use cedarling::{BootstrapConfig, BootstrapConfigRaw, LogStorage, Request};
+use serde::ser::{Serialize, SerializeStruct, Serializer};
+use serde_json::json;
+use serde_wasm_bindgen::Error;
+use std::rc::Rc;
+use wasm_bindgen::prelude::*;
+use wasm_bindgen_futures::js_sys::{Array, Map, Object, Reflect};
+
+#[cfg(test)]
+mod tests;
+
+/// The instance of the Cedarling application.
+#[wasm_bindgen]
+#[derive(Clone)]
+pub struct Cedarling {
+ instance: cedarling::Cedarling,
+}
+
+/// Create a new instance of the Cedarling application.
+/// This function can take as config parameter the eather `Map` other `Object`
+#[wasm_bindgen]
+pub async fn init(config: JsValue) -> Result {
+ if config.is_instance_of::