move dsl's to rhailib
This commit is contained in:
		
							
								
								
									
										41
									
								
								examples/access_control/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								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_PUBLIC_KEY` matches the worker's own `CIRCLE_PUBLIC_KEY` 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
									
								
								examples/access_control/alice.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								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
									
								
								examples/access_control/bob.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								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
									
								
								examples/access_control/charlie.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								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
									
								
								examples/access_control/circle.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								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();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										150
									
								
								examples/access_control/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								examples/access_control/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
			
		||||
use rhai_client::RhaiClientBuilder;
 | 
			
		||||
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 = RhaiClientBuilder::new()
 | 
			
		||||
        .redis_url(REDIS_URL)
 | 
			
		||||
        .caller_id(ALICE_ID)
 | 
			
		||||
        .build()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    client_alice.new_play_request()
 | 
			
		||||
        .recipient_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 = RhaiClientBuilder::new()
 | 
			
		||||
        .redis_url(REDIS_URL)
 | 
			
		||||
        .caller_id(BOB_ID)
 | 
			
		||||
        .build()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    
 | 
			
		||||
    client_bob.new_play_request()
 | 
			
		||||
        .recipient_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 = RhaiClientBuilder::new()
 | 
			
		||||
        .redis_url(REDIS_URL)
 | 
			
		||||
        .caller_id(CHARLIE_ID)
 | 
			
		||||
        .build()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    
 | 
			
		||||
    client_charlie.new_play_request()
 | 
			
		||||
        .recipient_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 = RhaiClientBuilder::new()
 | 
			
		||||
        .redis_url(REDIS_URL)
 | 
			
		||||
        .caller_id(CIRCLE_ID)
 | 
			
		||||
        .build()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    client_circle.new_play_request()
 | 
			
		||||
        .recipient_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()
 | 
			
		||||
    .recipient_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 = RhaiClientBuilder::new()
 | 
			
		||||
    .redis_url(REDIS_URL)
 | 
			
		||||
    .caller_id(CHARLIE_ID)
 | 
			
		||||
    .build()
 | 
			
		||||
    .unwrap();
 | 
			
		||||
 | 
			
		||||
client_charlie.new_play_request()
 | 
			
		||||
    .recipient_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