From c37be2dfcc447538be8363296b2b465c93ad3e11 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:26:51 +0200 Subject: [PATCH] archive / remove access control from everything --- src/macros/_archive/lib.rs | 380 +++++++++++++++++++++++++++++++++++++ src/macros/src/lib.rs | 28 --- 2 files changed, 380 insertions(+), 28 deletions(-) create mode 100644 src/macros/_archive/lib.rs diff --git a/src/macros/_archive/lib.rs b/src/macros/_archive/lib.rs new file mode 100644 index 0000000..9288fdd --- /dev/null +++ b/src/macros/_archive/lib.rs @@ -0,0 +1,380 @@ +//! # Rhai Authorization Crate +//! This crate provides authorization mechanisms for Rhai functions, particularly those interacting with a database. +//! It includes helper functions for authorization checks and macros to simplify the registration +//! of authorized Rhai functions. +//! ## Features: +//! - `is_super_admin`: Checks if a caller (identified by a public key) is a super admin. +//! - `can_access_resource`: Checks if a caller has specific access rights to a resource, using a database connection. +//! - `get_caller_public_key`: Helper to extract `CALLER_ID` from the Rhai `NativeCallContext`. +//! - `id_from_i64_to_u32`: Helper to convert `i64` Rhai IDs to `u32` Rust IDs. +//! - `register_authorized_get_by_id_fn!`: Macro to register a Rhai function that retrieves a single item by ID, with authorization checks. +//! - `register_authorized_list_fn!`: Macro to register a Rhai function that lists multiple items, filtering them based on authorization. +//! ## Usage: +//! 1. Use the macros to register your Rhai functions, providing a database connection (`Arc`) and necessary type/name information. +//! 2. The macros internally use `can_access_resource` for authorization checks. +//! 3. Ensure `CALLER_ID` is set in the Rhai engine's scope before calling authorized functions. + +use rhai::{EvalAltResult, Position}; +use std::convert::TryFrom; + +/// Extracts the `CALLER_ID` string constant from the Rhai `NativeCallContext`. +/// This key is used to identify the caller for authorization checks. +/// It first checks the current `Scope` and then falls back to the global constants cache. +/// +/// # Arguments +/// * `context`: The Rhai `NativeCallContext` of the currently executing function. +/// + +/// Converts an `i64` (common Rhai integer type) to a `u32` (common Rust ID type). +/// +/// # Arguments +/// * `id_i64`: The `i64` value to convert. +/// +/// # Errors +/// Returns `Err(EvalAltResult::ErrorMismatchDataType)` if the `i64` value cannot be represented as a `u32`. +pub fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "u32".to_string(), + format!("i64 value ({}) that cannot be represented as u32", id_i64), + Position::NONE, + )) + }) +} + +/// Extracts the `CALLER_ID` string constant from the Rhai `NativeCallContext`'s tag. +/// This key is used to identify the caller for authorization checks. + +/// Macro to register a Rhai function that retrieves a single resource by its ID, with authorization. +/// +/// The macro handles: +/// - Argument parsing (ID). +/// - Caller identification via `CALLER_ID`. +/// - Authorization check using `AccessControlService::can_access_resource`. +/// - Database call to fetch the resource. +/// - Error handling for type mismatches, authorization failures, DB errors, and not found errors. +/// +/// # Arguments +/// * `module`: Mutable reference to the Rhai `Module`. +/// * `db_clone`: Cloned `Arc` for database access. +/// * `acs_clone`: Cloned `Arc`. +/// * `rhai_fn_name`: String literal for the Rhai function name (e.g., "get_collection"). +/// * `resource_type_str`: String literal for the resource type (e.g., "Collection"), used in authorization checks and error messages. +/// * `db_method_name`: Identifier for the database method to call (e.g., `get_by_id`). +/// * `id_arg_type`: Rust type of the ID argument in Rhai (e.g., `i64`). +/// * `id_rhai_type_name`: String literal for the Rhai type name of the ID (e.g., "i64"), for error messages. +/// * `id_conversion_fn`: Path to a function converting `id_arg_type` to `actual_id_type` (e.g., `id_from_i64_to_u32`). +/// * `actual_id_type`: Rust type of the ID used in the database (e.g., `u32`). +/// * `rhai_return_rust_type`: Rust type of the resource returned by the DB and Rhai function (e.g., `RhaiCollection`). +#[macro_export] +macro_rules! register_authorized_get_by_id_fn { + ( + module: $module:expr, + rhai_fn_name: $rhai_fn_name:expr, // String literal for the Rhai function name (e.g., "get_collection") + resource_type_str: $resource_type_str:expr, // String literal for the resource type (e.g., "Collection") + rhai_return_rust_type: $rhai_return_rust_type:ty // Rust type of the resource returned (e.g., `RhaiCollection`) + ) => { + FuncRegistration::new($rhai_fn_name).set_into_module( + $module, + move |context: rhai::NativeCallContext, + id_val: i64| + -> Result<$rhai_return_rust_type, Box> { + let actual_id: u32 = $crate::id_from_i64_to_u32(id_val)?; + + // Inlined logic to get caller public key + let tag_map = context + .tag() + .and_then(|tag| tag.read_lock::()) + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + "Context tag must be a Map.".into(), + context.position(), + )) + })?; + + let pk_dynamic = tag_map.get("CALLER_ID").ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + "'CALLER_ID' not found in context tag Map.".into(), + context.position(), + )) + })?; + + let db_path = tag_map.get("DB_PATH").ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + "'DB_PATH' not found in context tag Map.".into(), + context.position(), + )) + })?; + + let db_path = db_path.clone().into_string()?; + + let circle_pk = tag_map.get("CONTEXT_ID").ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + "'CONTEXT_ID' not found in context tag Map.".into(), + context.position(), + )) + })?; + + let circle_pk = circle_pk.clone().into_string()?; + + let db_path = format!("{}/{}", db_path, circle_pk); + let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB")); + + let caller_pk_str = pk_dynamic.clone().into_string()?; + + println!("Checking access for public key: {}", caller_pk_str); + if circle_pk != caller_pk_str { + // Use the standalone can_access_resource function from heromodels + let has_access = heromodels::models::access::access::can_access_resource( + db.clone(), + &caller_pk_str, + actual_id, + $resource_type_str, + ); + + if !has_access { + return Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Access denied for public key: {}", caller_pk_str).into(), + context.position(), + ))); + } + } + + let result = db + .collection::<$rhai_return_rust_type>() + .unwrap() + .get_by_id(actual_id) + .map_err(|e| { + println!( + "Database error fetching {} with ID: {}", + $resource_type_str, actual_id + ); + Box::new(EvalAltResult::ErrorRuntime( + format!("Database error fetching {}: {:?}", $resource_type_str, e) + .into(), + context.position(), + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!( + "Database error fetching {} with ID: {}", + $resource_type_str, actual_id + ) + .into(), + context.position(), + )) + })?; + Ok(result) + }, + ); + }; +} + +// Macro to register a Rhai function that retrieves a single resource by its ID, with authorization. +#[macro_export] +macro_rules! register_authorized_create_by_id_fn { + ( + module: $module:expr, + rhai_fn_name: $rhai_fn_name:expr, // String literal for the Rhai function name (e.g., "get_collection") + resource_type_str: $resource_type_str:expr, // String literal for the resource type (e.g., "Collection") + rhai_return_rust_type: $rhai_return_rust_type:ty // Rust type of the resource returned (e.g., `RhaiCollection`) + ) => { + FuncRegistration::new($rhai_fn_name).set_into_module( + $module, + move |context: rhai::NativeCallContext, object: $rhai_return_rust_type| -> Result<$rhai_return_rust_type, Box> { + + // Inlined logic to get caller public key + let tag_map = context + .tag() + .and_then(|tag| tag.read_lock::()) + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("Context tag must be a Map.".into(), context.position())))?; + + let pk_dynamic = tag_map.get("CALLER_ID") + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CALLER_ID' not found in context tag Map.".into(), context.position())))?; + + let db_path = tag_map.get("DB_PATH") + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'DB_PATH' not found in context tag Map.".into(), context.position())))?; + + let db_path = db_path.clone().into_string()?; + + let circle_pk = tag_map.get("CONTEXT_ID") + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CONTEXT_ID' not found in context tag Map.".into(), context.position())))?; + + let circle_pk = circle_pk.clone().into_string()?; + + let db_path = format!("{}/{}", db_path, circle_pk); + let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB")); + + let caller_pk_str = pk_dynamic.clone().into_string()?; + + if circle_pk != caller_pk_str { + let is_circle_member = heromodels::models::access::access::is_circle_member( + db.clone(), + &caller_pk_str, + ); + if !is_circle_member { + // TODO: check if caller pk is member of circle + return Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Insufficient authorization. Caller public key {} does not match circle public key {}", caller_pk_str, circle_pk).into(), + context.position(), + ))); + } + } + + let result = db.set(&object).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Database error creating {}: {:?}", $resource_type_str, e).into(), + context.position(), + )) + })?; + Ok(result.1) + }, + ); + }; +} + +// Macro to register a Rhai function that retrieves a single resource by its ID, with authorization. +#[macro_export] +macro_rules! register_authorized_delete_by_id_fn { + ( + module: $module:expr, + rhai_fn_name: $rhai_fn_name:expr, // String literal for the Rhai function name (e.g., "get_collection") + resource_type_str: $resource_type_str:expr, // String literal for the resource type (e.g., "Collection") + rhai_return_rust_type: $rhai_return_rust_type:ty // Rust type of the resource returned (e.g., `RhaiCollection`) + ) => { + FuncRegistration::new($rhai_fn_name).set_into_module( + $module, + move |context: rhai::NativeCallContext, id_val: i64| -> Result<(), Box> { + let actual_id: u32 = $crate::id_from_i64_to_u32(id_val)?; + + // Inlined logic to get caller public key + let tag_map = context + .tag() + .and_then(|tag| tag.read_lock::()) + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("Context tag must be a Map.".into(), context.position())))?; + + let pk_dynamic = tag_map.get("CALLER_ID") + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CALLER_ID' not found in context tag Map.".into(), context.position())))?; + + let db_path = tag_map.get("DB_PATH") + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'DB_PATH' not found in context tag Map.".into(), context.position())))?; + + let db_path = db_path.clone().into_string()?; + + let circle_pk = tag_map.get("CONTEXT_ID") + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CONTEXT_ID' not found in context tag Map.".into(), context.position())))?; + + let circle_pk = circle_pk.clone().into_string()?; + + let db_path = format!("{}/{}", db_path, circle_pk); + let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB")); + + let caller_pk_str = pk_dynamic.clone().into_string()?; + + if circle_pk != caller_pk_str { + let is_circle_member = heromodels::models::access::access::is_circle_member( + db.clone(), + &caller_pk_str, + ); + if !is_circle_member { + // TODO: check if caller pk is member of circle + return Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Insufficient authorization. Caller public key {} does not match circle public key {}", caller_pk_str, circle_pk).into(), + context.position(), + ))); + } + } + + let result = db + .collection::<$rhai_return_rust_type>() + .unwrap() + .delete_by_id(actual_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Database error deleting {}: {:?}", $resource_type_str, e).into(), + context.position(), + )) + })?; + Ok(()) + }, + ); + }; +} + +/// Macro to register a Rhai function that lists all resources of a certain type, with authorization. +/// +/// The macro handles: +/// - Caller identification via `CALLER_ID`. +/// - Fetching all items of a specific type from the database. +/// - Filtering the items based on the standalone `can_access_resource` function for each item. +/// - Wrapping the authorized items in a specified collection type (e.g., `RhaiCollectionArray`). +/// - Error handling for DB errors during fetch or authorization checks. +/// +/// # Arguments +/// * `module`: Mutable reference to the Rhai `Module`. +/// * `rhai_fn_name`: String literal for the Rhai function name (e.g., "list_collections"). +/// * `resource_type_str`: String literal for the resource type (e.g., "Collection"), used in authorization checks. +/// * `rhai_return_rust_type`: Rust type of the resource item (e.g., `RhaiCollection`). +/// * `item_id_accessor`: Identifier for the method on `rhai_return_rust_type` that returns its ID (e.g., `id`). +/// * `rhai_return_wrapper_type`: Rust type that wraps a `Vec` of `rhai_return_rust_type` for Rhai (e.g., `RhaiCollectionArray`). +#[macro_export] +macro_rules! register_authorized_list_fn { + ( + module: $module:expr, + rhai_fn_name: $rhai_fn_name:expr, + resource_type_str: $resource_type_str:expr, + rhai_return_rust_type: $rhai_return_rust_type:ty, + rhai_return_wrapper_type: $rhai_return_wrapper_type:ty + ) => { + FuncRegistration::new($rhai_fn_name).set_into_module( + $module, + move |context: rhai::NativeCallContext| -> Result<$rhai_return_wrapper_type, Box> { + // Inlined logic to get caller public key + let tag_map = context + .tag() + .and_then(|tag| tag.read_lock::()) + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("Context tag must be a Map.".into(), context.position())))?; + + let pk_dynamic = tag_map.get("CALLER_ID") + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CALLER_ID' not found in context tag Map.".into(), context.position())))?; + + let caller_pk_str = pk_dynamic.clone().into_string()?; + + let db_path = tag_map.get("DB_PATH") + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'DB_PATH' not found in context tag Map.".into(), context.position())))?; + + let db_path = db_path.clone().into_string()?; + + let circle_pk = tag_map.get("CONTEXT_ID") + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CONTEXT_ID' not found in context tag Map.".into(), context.position())))?; + + let circle_pk = circle_pk.clone().into_string()?; + + let db_path = format!("{}/{}", db_path, circle_pk); + let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB")); + + let all_items: Vec<$rhai_return_rust_type> = db + .collection::<$rhai_return_rust_type>() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("{:?}", e).into(), Position::NONE)))? + .get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("{:?}", e).into(), Position::NONE)))?; + + let authorized_items: Vec<$rhai_return_rust_type> = all_items + .into_iter() + .filter(|item| { + let resource_id = item.id(); + heromodels::models::access::access::can_access_resource( + db.clone(), + &caller_pk_str, + resource_id, + $resource_type_str, + ) + }) + .collect(); + + Ok(authorized_items.into()) + }, + ); + }; +} diff --git a/src/macros/src/lib.rs b/src/macros/src/lib.rs index 9288fdd..225702e 100644 --- a/src/macros/src/lib.rs +++ b/src/macros/src/lib.rs @@ -208,20 +208,6 @@ macro_rules! register_authorized_create_by_id_fn { let caller_pk_str = pk_dynamic.clone().into_string()?; - if circle_pk != caller_pk_str { - let is_circle_member = heromodels::models::access::access::is_circle_member( - db.clone(), - &caller_pk_str, - ); - if !is_circle_member { - // TODO: check if caller pk is member of circle - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Insufficient authorization. Caller public key {} does not match circle public key {}", caller_pk_str, circle_pk).into(), - context.position(), - ))); - } - } - let result = db.set(&object).map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( format!("Database error creating {}: {:?}", $resource_type_str, e).into(), @@ -272,20 +258,6 @@ macro_rules! register_authorized_delete_by_id_fn { let caller_pk_str = pk_dynamic.clone().into_string()?; - if circle_pk != caller_pk_str { - let is_circle_member = heromodels::models::access::access::is_circle_member( - db.clone(), - &caller_pk_str, - ); - if !is_circle_member { - // TODO: check if caller pk is member of circle - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Insufficient authorization. Caller public key {} does not match circle public key {}", caller_pk_str, circle_pk).into(), - context.position(), - ))); - } - } - let result = db .collection::<$rhai_return_rust_type>() .unwrap()