archive / remove access control from everything
This commit is contained in:
		
							
								
								
									
										380
									
								
								src/macros/_archive/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								src/macros/_archive/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<OurDB>`) 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, Box<EvalAltResult>> { | ||||
|     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<Db>` for database access. | ||||
| /// * `acs_clone`: Cloned `Arc<AccessControlService>`. | ||||
| /// * `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<EvalAltResult>> { | ||||
|                 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::<rhai::Map>()) | ||||
|                     .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<EvalAltResult>> { | ||||
|  | ||||
|                 // Inlined logic to get caller public key | ||||
|                 let tag_map = context | ||||
|                     .tag() | ||||
|                     .and_then(|tag| tag.read_lock::<rhai::Map>()) | ||||
|                     .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<EvalAltResult>> { | ||||
|                 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::<rhai::Map>()) | ||||
|                     .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<EvalAltResult>> { | ||||
|                 // Inlined logic to get caller public key | ||||
|                 let tag_map = context | ||||
|                     .tag() | ||||
|                     .and_then(|tag| tag.read_lock::<rhai::Map>()) | ||||
|                     .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()) | ||||
|             }, | ||||
|         ); | ||||
|     }; | ||||
| } | ||||
| @@ -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() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user