implement end to end access control rhai example
This commit is contained in:
		@@ -1,29 +0,0 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "rhailib-examples"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
publish = false # This is a package of examples, not meant to be published
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
# Local Rhailib crates
 | 
			
		||||
rhai_client = { path = "../src/client" }
 | 
			
		||||
 | 
			
		||||
# External dependencies
 | 
			
		||||
rhai = "1.18.0"
 | 
			
		||||
tokio = { version = "1", features = ["full"] }
 | 
			
		||||
log = "0.4"
 | 
			
		||||
env_logger = "0.10"
 | 
			
		||||
serde_json = "1.0"
 | 
			
		||||
chrono = "0.4"
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "example_math_worker"
 | 
			
		||||
path = "example_math_worker.rs"
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "example_string_worker"
 | 
			
		||||
path = "example_string_worker.rs"
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "dedicated_reply_queue_demo"
 | 
			
		||||
path = "dedicated_reply_queue_demo.rs"
 | 
			
		||||
							
								
								
									
										45
									
								
								examples/end_to_end/alice.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								examples/end_to_end/alice.rhai
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
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();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
// auth_script.rhai
 | 
			
		||||
// This script calls a custom registered function 'check_permission'
 | 
			
		||||
// and passes the CALLER_PUBLIC_KEY to it.
 | 
			
		||||
// CALLER_PUBLIC_KEY is injected into the script's scope by the rhailib_worker.
 | 
			
		||||
 | 
			
		||||
check_permission(CALLER_PUBLIC_KEY)
 | 
			
		||||
							
								
								
									
										16
									
								
								examples/end_to_end/bob.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								examples/end_to_end/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/end_to_end/charlie.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								examples/end_to_end/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();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1,137 +1,84 @@
 | 
			
		||||
use rhai::{Engine, EvalAltResult};
 | 
			
		||||
use rhai_client::RhaiClient;
 | 
			
		||||
use rhai_client::RhaiClientBuilder;
 | 
			
		||||
use rhailib_engine::create_heromodels_engine;
 | 
			
		||||
use rhailib_worker::spawn_rhai_worker;
 | 
			
		||||
use std::{fs, path::Path, time::Duration};
 | 
			
		||||
use tokio::sync::mpsc;
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
// Custom Rhai function for authorization
 | 
			
		||||
// It takes the caller's public key as an argument.
 | 
			
		||||
fn check_permission(caller_pk: String) -> Result<String, Box<EvalAltResult>> {
 | 
			
		||||
    log::info!("check_permission called with PK: {}", caller_pk);
 | 
			
		||||
    if caller_pk == "admin_pk" {
 | 
			
		||||
        Ok("Access Granted: Welcome Admin!".to_string())
 | 
			
		||||
    } else if caller_pk == "user_pk" {
 | 
			
		||||
        Ok("Limited Access: Welcome User!".to_string())
 | 
			
		||||
    } else {
 | 
			
		||||
        Ok(format!("Access Denied: Unknown public key '{}'", caller_pk))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
const ALICE_ID: &str = "alice_pk";
 | 
			
		||||
const BOB_ID: &str = "bob_pk";
 | 
			
		||||
const CHARLIE_ID: &str = "charlie_pk";
 | 
			
		||||
const REDIS_URL: &str = "redis://127.0.0.1/";
 | 
			
		||||
const DB_DIRECTORY: &str = "./db";
 | 
			
		||||
 | 
			
		||||
#[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();
 | 
			
		||||
 | 
			
		||||
    let redis_url = "redis://127.0.0.1/";
 | 
			
		||||
    let worker_circle_pk = "auth_worker_circle".to_string();
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    // 1. Create a Rhai engine and register custom functionality
 | 
			
		||||
    let mut engine = Engine::new();
 | 
			
		||||
    engine.register_fn("check_permission", check_permission);
 | 
			
		||||
    log::info!("Custom 'check_permission' function registered with Rhai engine.");
 | 
			
		||||
    let mut 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(
 | 
			
		||||
        0, // worker_id
 | 
			
		||||
        worker_circle_pk.clone(),
 | 
			
		||||
        ALICE_ID.to_string(),
 | 
			
		||||
        DB_DIRECTORY.to_string(),
 | 
			
		||||
        engine,
 | 
			
		||||
        redis_url.to_string(),
 | 
			
		||||
        REDIS_URL.to_string(),
 | 
			
		||||
        shutdown_rx,
 | 
			
		||||
        false, // use_sentinel
 | 
			
		||||
    ));
 | 
			
		||||
    log::info!("Rhai worker spawned for circle: {}", worker_circle_pk);
 | 
			
		||||
    
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    // 3. Create a Rhai client
 | 
			
		||||
    let client = RhaiClient::new(redis_url)?;
 | 
			
		||||
    log::info!("Rhai client created.");
 | 
			
		||||
    // Alice populates her rhai worker
 | 
			
		||||
    let client_alice = RhaiClientBuilder::new()
 | 
			
		||||
        .redis_url(REDIS_URL)
 | 
			
		||||
        .caller_id(ALICE_ID)
 | 
			
		||||
        .build()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    // 4. Load the Rhai script content
 | 
			
		||||
    let script_path_str = "examples/end_to_end/auth_script.rhai"; // Relative to Cargo.toml / rhailib root
 | 
			
		||||
    let script_content = match fs::read_to_string(script_path_str) {
 | 
			
		||||
        Ok(content) => content,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Failed to read script file '{}': {}", script_path_str, e);
 | 
			
		||||
            // Attempt to read from an alternative path if run via `cargo run --example`
 | 
			
		||||
            // where current dir might be the crate root.
 | 
			
		||||
            let alt_script_path = Path::new(file!())
 | 
			
		||||
                .parent()
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .join("auth_script.rhai");
 | 
			
		||||
            log::info!("Attempting alternative script path: {:?}", alt_script_path);
 | 
			
		||||
            fs::read_to_string(&alt_script_path)?
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    log::info!("Loaded script content from '{}'", script_path_str);
 | 
			
		||||
    client_alice.new_play_request()
 | 
			
		||||
        .recipient_id(&ALICE_ID)
 | 
			
		||||
        .script_path("examples/end_to_end/alice.rhai")
 | 
			
		||||
        .timeout(Duration::from_secs(10))   
 | 
			
		||||
        .await_response().await.unwrap();
 | 
			
		||||
    
 | 
			
		||||
    log::info!("Alice's database populated.");
 | 
			
		||||
 | 
			
		||||
    // Define different caller public keys
 | 
			
		||||
    let admin_caller_pk = "admin_pk".to_string();
 | 
			
		||||
    let user_caller_pk = "user_pk".to_string();
 | 
			
		||||
    let unknown_caller_pk = "unknown_pk".to_string();
 | 
			
		||||
    // 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/end_to_end/bob.rhai")
 | 
			
		||||
        .timeout(Duration::from_secs(10))   
 | 
			
		||||
        .await_response().await.unwrap();
 | 
			
		||||
    
 | 
			
		||||
    log::info!("Bob's query to Alice's database completed.");
 | 
			
		||||
 | 
			
		||||
    let callers = vec![
 | 
			
		||||
        ("Admin", admin_caller_pk),
 | 
			
		||||
        ("User", user_caller_pk),
 | 
			
		||||
        ("Unknown", unknown_caller_pk),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    for (caller_name, caller_pk) in callers {
 | 
			
		||||
        let task_id = Uuid::new_v4().to_string();
 | 
			
		||||
        log::info!(
 | 
			
		||||
            "Submitting script for caller '{}' (PK: {}) with task_id: {}",
 | 
			
		||||
            caller_name,
 | 
			
		||||
            caller_pk,
 | 
			
		||||
            task_id
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        match client
 | 
			
		||||
            .submit_script_and_await_result(
 | 
			
		||||
                &worker_circle_pk,
 | 
			
		||||
                task_id.clone(),        // task_id (UUID) first
 | 
			
		||||
                script_content.clone(), // script_content second
 | 
			
		||||
                Duration::from_secs(10),
 | 
			
		||||
                Some(caller_pk.clone()), // This is the CALLER_PUBLIC_KEY
 | 
			
		||||
            )
 | 
			
		||||
            .await
 | 
			
		||||
        {
 | 
			
		||||
            Ok(details) => {
 | 
			
		||||
                log::info!(
 | 
			
		||||
                    "Task {} for caller '{}' (PK: {}) completed. Status: {}, Output: {:?}, Error: {:?}",
 | 
			
		||||
                    task_id,
 | 
			
		||||
                    caller_name,
 | 
			
		||||
                    caller_pk,
 | 
			
		||||
                    details.status,
 | 
			
		||||
                    details.output,
 | 
			
		||||
                    details.error
 | 
			
		||||
                );
 | 
			
		||||
                // Basic assertion for expected output
 | 
			
		||||
                if caller_pk == "admin_pk" {
 | 
			
		||||
                    assert_eq!(
 | 
			
		||||
                        details.output,
 | 
			
		||||
                        Some("Access Granted: Welcome Admin!".to_string())
 | 
			
		||||
                    );
 | 
			
		||||
                } else if caller_pk == "user_pk" {
 | 
			
		||||
                    assert_eq!(
 | 
			
		||||
                        details.output,
 | 
			
		||||
                        Some("Limited Access: Welcome User!".to_string())
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!(
 | 
			
		||||
                    "Task {} for caller '{}' (PK: {}) failed: {}",
 | 
			
		||||
                    task_id,
 | 
			
		||||
                    caller_name,
 | 
			
		||||
                    caller_pk,
 | 
			
		||||
                    e
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        tokio::time::sleep(Duration::from_millis(100)).await; // Small delay between submissions
 | 
			
		||||
    }
 | 
			
		||||
    // 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/end_to_end/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...");
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								examples/end_to_end/query.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								examples/end_to_end/query.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();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user