move rhailib to herolib
This commit is contained in:
41
rhailib/examples/access_control/README.md
Normal file
41
rhailib/examples/access_control/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Access Control Demonstration
|
||||
|
||||
This example demonstrates a practical access control scenario using `rhailib`. It showcases how a user, Alice, can manage her own data within her Rhai worker, grant specific access rights to another user, Bob, and deny access to an unauthorized user, Charlie.
|
||||
|
||||
## Overview
|
||||
|
||||
The example involves three key participants:
|
||||
|
||||
1. **Alice (`alice_pk`)**: The owner of the Rhai worker. She runs `alice.rhai` to populate her database with various objects and collections. Some of these are private, while others are explicitly shared with Bob.
|
||||
|
||||
2. **Bob (`bob_pk`)**: A user who has been granted some access rights by Alice. In this example, he attempts to run `bob.rhai`, which tries to write data to Alice's worker.
|
||||
|
||||
3. **Charlie (`charlie_pk`)**: An unauthorized user. He attempts to run `charlie.rhai`, which is identical to Bob's script.
|
||||
|
||||
The core of the access control mechanism lies within the `rhailib_worker`. When a script is submitted for execution, the worker automatically enforces that the `CALLER_ID` matches the worker's own `CONTEXT_ID` for any write operations. This ensures that only the owner (Alice) can modify her data.
|
||||
|
||||
## Scenario and Expected Outcomes
|
||||
|
||||
1. **Alice Populates Her Database**: Alice's script (`alice.rhai`) runs first. It successfully creates:
|
||||
- A private object.
|
||||
- An object shared with Bob.
|
||||
- A private collection containing a private book and slides that are individually shared with Bob.
|
||||
- A shared collection.
|
||||
This demonstrates that the owner of the worker can freely write to her own database.
|
||||
|
||||
2. **Bob's Query**: Bob's script (`bob.rhai`) is executed next. The script attempts to create new objects in Alice's database. This operation fails with an `Insufficient authorization` error. The logs will show that `bob_pk` does not match the circle's public key, `alice_pk`.
|
||||
|
||||
3. **Charlie's Query**: Charlie's script (`charlie.rhai`) also fails with the same authorization error, as he is not the owner of the worker.
|
||||
|
||||
This example clearly illustrates the built-in ownership and write protection provided by the Rhai worker.
|
||||
|
||||
## Running the Example
|
||||
|
||||
Ensure Redis is running and accessible at `redis://127.0.0.1/`.
|
||||
|
||||
From the `rhailib` root directory, run:
|
||||
```bash
|
||||
cargo run --example access_control
|
||||
```
|
||||
|
||||
Observe the logs to see Alice's script complete successfully, followed by the authorization errors for Bob and Charlie, confirming that the access control is working as expected.
|
50
rhailib/examples/access_control/alice.rhai
Normal file
50
rhailib/examples/access_control/alice.rhai
Normal file
@@ -0,0 +1,50 @@
|
||||
new_circle()
|
||||
.title("Alice's Circle")
|
||||
.description("Some objects in this circle are shared with Bob")
|
||||
.save_circle();
|
||||
|
||||
let private_object = new_object()
|
||||
.title("Alice's Private Object")
|
||||
.description("This object can only be seen and modified by Alice")
|
||||
.save_object();
|
||||
|
||||
let object_shared_with_bob = new_object()
|
||||
.title("Alice's Shared Object")
|
||||
.description("This object can be seen by Bob but modified only by Alice")
|
||||
.save_object();
|
||||
|
||||
let new_access = new_access()
|
||||
.object_id(object_shared_with_bob.id())
|
||||
.circle_public_key("bob_pk")
|
||||
.save_access();
|
||||
|
||||
let book_private = new_book()
|
||||
.title("Alice's private book")
|
||||
.description("This book is prive to Alice")
|
||||
.save_book();
|
||||
|
||||
let slides_shared = new_slides()
|
||||
.title("Alice's shared slides")
|
||||
.description("These slides, despite being in a private collection, are shared with Bob")
|
||||
.save_slides();
|
||||
|
||||
let new_access = new_access()
|
||||
.object_id(slides_shared.id)
|
||||
.circle_public_key("bob_pk")
|
||||
.save_access();
|
||||
|
||||
let collection_private = new_collection()
|
||||
.title("Alice's private collection")
|
||||
.description("This collection is only visible to Alice")
|
||||
.add_book(book_private.id)
|
||||
.add_slides(slides_shared.id)
|
||||
.save_collection();
|
||||
|
||||
|
||||
let collection_shared = new_collection()
|
||||
.title("Alice's shared collection")
|
||||
.description("This collection is shared with Bob")
|
||||
.save_collection();
|
||||
|
||||
|
||||
|
16
rhailib/examples/access_control/bob.rhai
Normal file
16
rhailib/examples/access_control/bob.rhai
Normal file
@@ -0,0 +1,16 @@
|
||||
let private_object = new_object()
|
||||
.title("Alice's Private Object")
|
||||
.description("This object can only be seen and modified by Alice")
|
||||
.save_object();
|
||||
|
||||
let object_shared_with_bob = new_object()
|
||||
.title("Alice's Shared Collection")
|
||||
.description("This object can be seen by Bob but modified only by Alice")
|
||||
.save_object();
|
||||
|
||||
let new_access = new_access()
|
||||
.object_id(object_shared_with_bob.id())
|
||||
.circle_public_key("bob_pk")
|
||||
.save_access();
|
||||
|
||||
|
16
rhailib/examples/access_control/charlie.rhai
Normal file
16
rhailib/examples/access_control/charlie.rhai
Normal file
@@ -0,0 +1,16 @@
|
||||
let private_object = new_object()
|
||||
.title("Alice's Private Object")
|
||||
.description("This object can only be seen and modified by Alice")
|
||||
.save_object();
|
||||
|
||||
let object_shared_with_bob = new_object()
|
||||
.title("Alice's Shared Collection")
|
||||
.description("This object can be seen by Bob but modified only by Alice")
|
||||
.save_object();
|
||||
|
||||
let new_access = new_access()
|
||||
.object_id(object_shared_with_bob.id())
|
||||
.circle_public_key("bob_pk")
|
||||
.save_access();
|
||||
|
||||
|
51
rhailib/examples/access_control/circle.rhai
Normal file
51
rhailib/examples/access_control/circle.rhai
Normal file
@@ -0,0 +1,51 @@
|
||||
new_circle()
|
||||
.title("Alice and Charlie's Circle")
|
||||
.description("Some objects in this circle are shared with Bob")
|
||||
.add_member("alice_pk")
|
||||
.add_member("charlie_pk")
|
||||
.save_circle();
|
||||
|
||||
let private_object = new_object()
|
||||
.title("Alice and Charlie's Private Object")
|
||||
.description("This object can only be seen and modified by Alice and Charlie")
|
||||
.save_object();
|
||||
|
||||
let object_shared_with_bob = new_object()
|
||||
.title("Alice and Charlie's Shared Object")
|
||||
.description("This object can be seen by Bob but modified only by Alice and Charlie")
|
||||
.save_object();
|
||||
|
||||
let new_access = new_access()
|
||||
.object_id(object_shared_with_bob.id())
|
||||
.circle_public_key("bob_pk")
|
||||
.save_access();
|
||||
|
||||
let book_private = new_book()
|
||||
.title("Alice and Charlie's private book")
|
||||
.description("This book is prive to Alice and Charlie")
|
||||
.save_book();
|
||||
|
||||
let slides_shared = new_slides()
|
||||
.title("Alice and Charlie's shared slides")
|
||||
.description("These slides, despite being in a private collection, are shared with Bob")
|
||||
.save_slides();
|
||||
|
||||
let new_access = new_access()
|
||||
.object_id(slides_shared.id)
|
||||
.circle_public_key("bob_pk")
|
||||
.save_access();
|
||||
|
||||
let collection_private = new_collection()
|
||||
.title("Alice and Charlie's private collection")
|
||||
.description("This collection is only visible to Alice and Charlie")
|
||||
.add_book(book_private.id)
|
||||
.add_slides(slides_shared.id)
|
||||
.save_collection();
|
||||
|
||||
let collection_shared = new_collection()
|
||||
.title("Alice and Charlie's shared collection")
|
||||
.description("This collection is shared with Bob")
|
||||
.save_collection();
|
||||
|
||||
|
||||
|
172
rhailib/examples/access_control/main.rs
Normal file
172
rhailib/examples/access_control/main.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use rhai_dispatcher::RhaiDispatcherBuilder;
|
||||
use rhailib_worker::spawn_rhai_worker;
|
||||
use std::time::Duration;
|
||||
use tempfile::Builder;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
const ALICE_ID: &str = "alice_pk";
|
||||
const BOB_ID: &str = "bob_pk";
|
||||
const CHARLIE_ID: &str = "charlie_pk";
|
||||
const CIRCLE_ID: &str = "circle_pk";
|
||||
const REDIS_URL: &str = "redis://127.0.0.1/";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
// Create a temporary directory for the database
|
||||
let temp_dir = Builder::new().prefix("rhai-example").tempdir()?;
|
||||
let db_path = temp_dir.path().to_str().unwrap().to_string();
|
||||
|
||||
// 1. Create a Rhai engine and register custom functionality
|
||||
let engine = rhailib_engine::create_heromodels_engine();
|
||||
|
||||
// 2. Spawn the Rhai worker
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
|
||||
let worker_handle = tokio::spawn(spawn_rhai_worker(
|
||||
ALICE_ID.to_string(),
|
||||
db_path.clone(),
|
||||
engine,
|
||||
REDIS_URL.to_string(),
|
||||
shutdown_rx,
|
||||
false, // use_sentinel
|
||||
));
|
||||
|
||||
log::info!("Rhai worker spawned for circle: {}", ALICE_ID);
|
||||
|
||||
// Give the worker a moment to start up
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
// Alice populates her rhai worker
|
||||
let client_alice = RhaiDispatcherBuilder::new()
|
||||
.redis_url(REDIS_URL)
|
||||
.caller_id(ALICE_ID)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
client_alice
|
||||
.new_play_request()
|
||||
.worker_id(&ALICE_ID)
|
||||
.context_id(&ALICE_ID)
|
||||
.script_path("examples/access_control/alice.rhai")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.await_response()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
log::info!("Alice's database populated.");
|
||||
|
||||
// Bob queries Alice's rhai worker
|
||||
let client_bob = RhaiDispatcherBuilder::new()
|
||||
.redis_url(REDIS_URL)
|
||||
.caller_id(BOB_ID)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
client_bob
|
||||
.new_play_request()
|
||||
.worker_id(&ALICE_ID)
|
||||
.context_id(&ALICE_ID)
|
||||
.script_path("examples/access_control/bob.rhai")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.await_response()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
log::info!("Bob's query to Alice's database completed.");
|
||||
|
||||
// Charlie queries Alice's rhai worker
|
||||
let client_charlie = RhaiDispatcherBuilder::new()
|
||||
.redis_url(REDIS_URL)
|
||||
.caller_id(CHARLIE_ID)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
client_charlie
|
||||
.new_play_request()
|
||||
.worker_id(&ALICE_ID)
|
||||
.context_id(&ALICE_ID)
|
||||
.script_path("examples/access_control/charlie.rhai")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.await_response()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
log::info!("Charlie's query to Alice's database completed.");
|
||||
|
||||
// Spawn the Rhai worker for Alice's and Charlie's circle
|
||||
let engine = rhailib_engine::create_heromodels_engine();
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
|
||||
let worker_handle = tokio::spawn(spawn_rhai_worker(
|
||||
CIRCLE_ID.to_string(),
|
||||
db_path.clone(),
|
||||
engine,
|
||||
REDIS_URL.to_string(),
|
||||
shutdown_rx,
|
||||
false, // use_sentinel
|
||||
));
|
||||
|
||||
// Alice populates the rhai worker of their circle with Charlie.
|
||||
let client_circle = RhaiDispatcherBuilder::new()
|
||||
.redis_url(REDIS_URL)
|
||||
.caller_id(CIRCLE_ID)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
client_circle
|
||||
.new_play_request()
|
||||
.worker_id(&CIRCLE_ID)
|
||||
.context_id(&CIRCLE_ID)
|
||||
.script_path("examples/access_control/circle.rhai")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.await_response()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
log::info!("Circles's database populated.");
|
||||
|
||||
// Give the worker a moment to start up
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
// Alice queries the rhai worker of their circle with Charlie.
|
||||
client_alice
|
||||
.new_play_request()
|
||||
.worker_id(&CIRCLE_ID)
|
||||
.context_id(&CIRCLE_ID)
|
||||
.script_path("examples/access_control/alice.rhai")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.await_response()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
log::info!("Bob's query to Alice's database completed.");
|
||||
|
||||
// Charlie queries Alice's rhai worker
|
||||
let client_charlie = RhaiDispatcherBuilder::new()
|
||||
.redis_url(REDIS_URL)
|
||||
.caller_id(CHARLIE_ID)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
client_charlie
|
||||
.new_play_request()
|
||||
.worker_id(&ALICE_ID)
|
||||
.context_id(&ALICE_ID)
|
||||
.script_path("examples/access_control/charlie.rhai")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.await_response()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
log::info!("Charlie's query to Alice's database completed.");
|
||||
|
||||
// 5. Shutdown the worker (optional, could also let it run until program exits)
|
||||
log::info!("Signaling worker to shutdown...");
|
||||
let _ = shutdown_tx.send(()).await;
|
||||
if let Err(e) = worker_handle.await {
|
||||
log::error!("Worker task panicked or encountered an error: {:?}", e);
|
||||
}
|
||||
log::info!("Worker shutdown complete.");
|
||||
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user