archive / remove access control from everything
This commit is contained in:
parent
6d271068fc
commit
c37be2dfcc
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()
|
||||
|
Loading…
Reference in New Issue
Block a user