279 lines
14 KiB
Rust
279 lines
14 KiB
Rust
/// OSIRIS Rhai Engine
|
|
///
|
|
/// Creates a Rhai engine configured with OSIRIS contexts and methods.
|
|
|
|
use crate::context::OsirisContext;
|
|
use crate::objects::note::rhai::register_note_functions;
|
|
use crate::objects::event::rhai::register_event_functions;
|
|
use crate::objects::heroledger::rhai::register_heroledger_modules;
|
|
use crate::objects::kyc::rhai::register_kyc_modules;
|
|
use crate::objects::flow::rhai::register_flow_modules;
|
|
use crate::objects::communication::rhai::register_communication_modules;
|
|
use crate::objects::money::rhai::register_money_modules;
|
|
use crate::objects::legal::rhai::register_legal_modules;
|
|
use rhai::{Engine, def_package, FuncRegistration};
|
|
use rhai::packages::{Package, StandardPackage};
|
|
|
|
/// Register get_context function in a Rhai engine with signatory-based access control
|
|
///
|
|
/// Simple logic:
|
|
/// - Context is a list of public keys (participants)
|
|
/// - To get_context, at least one participant must be a signatory
|
|
/// - No state tracking, no caching - creates fresh context each time
|
|
pub fn register_context_api(engine: &mut rhai::Engine) {
|
|
// Register get_context function with signatory-based access control
|
|
// Usage: get_context(['pk1', 'pk2', 'pk3'])
|
|
engine.register_fn("get_context", move |context: rhai::NativeCallContext, participants: rhai::Array| -> Result<OsirisContext, Box<rhai::EvalAltResult>> {
|
|
// Extract SIGNATORIES from context tag
|
|
let tag_map = context
|
|
.tag()
|
|
.and_then(|tag| tag.read_lock::<rhai::Map>())
|
|
.ok_or_else(|| Box::new(rhai::EvalAltResult::ErrorRuntime("Context tag must be a Map.".into(), context.position())))?;
|
|
|
|
let signatories_dynamic = tag_map.get("SIGNATORIES")
|
|
.ok_or_else(|| Box::new(rhai::EvalAltResult::ErrorRuntime("'SIGNATORIES' not found in context tag Map.".into(), context.position())))?;
|
|
|
|
// Convert SIGNATORIES array to Vec<String>
|
|
let signatories_array = signatories_dynamic.clone().into_array()
|
|
.map_err(|e| Box::new(rhai::EvalAltResult::ErrorRuntime(format!("SIGNATORIES must be an array: {}", e).into(), context.position())))?;
|
|
|
|
let signatories: Vec<String> = signatories_array.into_iter()
|
|
.map(|s| s.into_string())
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.map_err(|e| Box::new(rhai::EvalAltResult::ErrorRuntime(format!("SIGNATORIES must contain strings: {}", e).into(), context.position())))?;
|
|
|
|
// Convert participants array to Vec<String>
|
|
let participant_keys: Vec<String> = participants.into_iter()
|
|
.map(|p| p.into_string())
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.map_err(|e| Box::new(rhai::EvalAltResult::ErrorRuntime(format!("Participants must be strings: {}", e).into(), context.position())))?;
|
|
|
|
// Verify at least one participant is a signatory
|
|
let has_signatory = participant_keys.iter().any(|p| signatories.contains(p));
|
|
if !has_signatory {
|
|
return Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
|
format!("Access denied: none of the participants are signatories. Signatories: {}", signatories.join(", ")).into(),
|
|
context.position()
|
|
)));
|
|
}
|
|
|
|
// Create context directly with participants
|
|
OsirisContext::builder()
|
|
.participants(participant_keys)
|
|
.build()
|
|
.map_err(|e| format!("Failed to create context: {}", e).into())
|
|
});
|
|
}
|
|
|
|
// Define the OSIRIS package
|
|
def_package! {
|
|
/// OSIRIS package with all OSIRIS types and functions
|
|
pub OsirisPackage(module) : StandardPackage {
|
|
// Register OsirisContext type with all its methods
|
|
module.set_custom_type::<OsirisContext>("OsirisContext");
|
|
|
|
// Register OsirisContext methods
|
|
FuncRegistration::new("participants")
|
|
.set_into_module(module, |ctx: &mut OsirisContext| ctx.participants());
|
|
FuncRegistration::new("context_id")
|
|
.set_into_module(module, |ctx: &mut OsirisContext| ctx.context_id());
|
|
// Typed save methods - all named "save" for function overloading using generic save_object
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, note: crate::objects::Note| ctx.save_object(note));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, event: crate::objects::Event| ctx.save_object(event));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, user: crate::objects::heroledger::user::User| ctx.save_object(user));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, group: crate::objects::heroledger::group::Group| ctx.save_object(group));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, account: crate::objects::heroledger::money::Account| ctx.save_object(account));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, zone: crate::objects::heroledger::dnsrecord::DNSZone| ctx.save_object(zone));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, kyc_info: crate::objects::KycInfo| ctx.save_object(kyc_info));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, kyc_session: crate::objects::KycSession| ctx.save_object(kyc_session));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, flow_template: crate::objects::FlowTemplate| ctx.save_object(flow_template));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, flow_instance: crate::objects::FlowInstance| ctx.save_object(flow_instance));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, verification: crate::objects::Verification| ctx.save_object(verification));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, account: crate::objects::Account| ctx.save_object(account));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, asset: crate::objects::Asset| ctx.save_object(asset));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, transaction: crate::objects::Transaction| ctx.save_object(transaction));
|
|
FuncRegistration::new("save")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, contract: crate::objects::Contract| ctx.save_object(contract));
|
|
FuncRegistration::new("list")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, collection: String| ctx.list(collection));
|
|
FuncRegistration::new("get")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, collection: String, id: String| ctx.get(collection, id));
|
|
FuncRegistration::new("delete")
|
|
.set_into_module(module, |ctx: &mut OsirisContext, collection: String, id: String| ctx.delete(collection, id));
|
|
|
|
// Register Note functions
|
|
register_note_functions(module);
|
|
|
|
// Register Event functions
|
|
register_event_functions(module);
|
|
|
|
// Register HeroLedger modules (User, Group, Account, DNSZone)
|
|
register_heroledger_modules(module);
|
|
|
|
// Register KYC modules (KycClient, KycSession)
|
|
register_kyc_modules(module);
|
|
|
|
// Register Flow modules (FlowTemplate, FlowInstance)
|
|
register_flow_modules(module);
|
|
|
|
// Register Communication modules (Verification, EmailClient)
|
|
register_communication_modules(module);
|
|
|
|
// Register Money modules (Account, Asset, Transaction, PaymentClient)
|
|
register_money_modules(module);
|
|
|
|
// Register Legal modules (Contract)
|
|
register_legal_modules(module);
|
|
|
|
// Register get_context function with signatory-based access control
|
|
FuncRegistration::new("get_context")
|
|
.set_into_module(module, |context: rhai::NativeCallContext, participants: rhai::Array| -> Result<OsirisContext, Box<rhai::EvalAltResult>> {
|
|
// Extract SIGNATORIES from context tag
|
|
let tag_map = context
|
|
.tag()
|
|
.and_then(|tag| tag.read_lock::<rhai::Map>())
|
|
.ok_or_else(|| Box::new(rhai::EvalAltResult::ErrorRuntime("Context tag must be a Map.".into(), context.position())))?;
|
|
|
|
let signatories_dynamic = tag_map.get("SIGNATORIES")
|
|
.ok_or_else(|| Box::new(rhai::EvalAltResult::ErrorRuntime("'SIGNATORIES' not found in context tag Map.".into(), context.position())))?;
|
|
|
|
// Convert SIGNATORIES array to Vec<String>
|
|
let signatories_array = signatories_dynamic.clone().into_array()
|
|
.map_err(|e| Box::new(rhai::EvalAltResult::ErrorRuntime(format!("SIGNATORIES must be an array: {}", e).into(), context.position())))?;
|
|
|
|
let signatories: Vec<String> = signatories_array.into_iter()
|
|
.map(|s| s.into_string())
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.map_err(|e| Box::new(rhai::EvalAltResult::ErrorRuntime(format!("SIGNATORIES must contain strings: {}", e).into(), context.position())))?;
|
|
|
|
// Convert participants array to Vec<String>
|
|
let participant_keys: Vec<String> = participants.into_iter()
|
|
.map(|p| p.into_string())
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.map_err(|e| Box::new(rhai::EvalAltResult::ErrorRuntime(format!("Participants must be strings: {}", e).into(), context.position())))?;
|
|
|
|
// Verify at least one participant is a signatory
|
|
let has_signatory = participant_keys.iter().any(|p| signatories.contains(p));
|
|
if !has_signatory {
|
|
return Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
|
format!("Access denied: none of the participants are signatories").into(),
|
|
context.position()
|
|
)));
|
|
}
|
|
|
|
// Create context directly with participants
|
|
OsirisContext::builder()
|
|
.participants(participant_keys)
|
|
.build()
|
|
.map_err(|e| format!("Failed to create context: {}", e).into())
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Register all OSIRIS components into an engine
|
|
/// This is a convenience function that registers the complete OsirisPackage
|
|
pub fn register_osiris_full(engine: &mut Engine) {
|
|
let package = OsirisPackage::new();
|
|
package.register_into_engine(engine);
|
|
}
|
|
|
|
/// Create a single OSIRIS engine (for backward compatibility)
|
|
pub fn create_osiris_engine() -> Result<Engine, Box<dyn std::error::Error>> {
|
|
let mut engine = Engine::new_raw();
|
|
register_osiris_full(&mut engine);
|
|
Ok(engine)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_create_osiris_engine() {
|
|
let result = create_osiris_engine();
|
|
assert!(result.is_ok());
|
|
|
|
let mut engine = result.unwrap();
|
|
|
|
// Set up context tags with SIGNATORIES (like in runner_rust example)
|
|
let mut tag_map = rhai::Map::new();
|
|
// Create a proper Rhai array
|
|
let signatories: rhai::Array = vec![
|
|
rhai::Dynamic::from("pk1".to_string()),
|
|
rhai::Dynamic::from("pk2".to_string()),
|
|
rhai::Dynamic::from("pk3".to_string()),
|
|
];
|
|
tag_map.insert("SIGNATORIES".into(), rhai::Dynamic::from(signatories));
|
|
tag_map.insert("DB_PATH".into(), "/tmp/test_db".to_string().into());
|
|
tag_map.insert("CONTEXT_ID".into(), "test_context".to_string().into());
|
|
engine.set_default_tag(rhai::Dynamic::from(tag_map));
|
|
|
|
// Test get_context with valid signatories
|
|
let mut scope = rhai::Scope::new();
|
|
let test_result = engine.eval_with_scope::<rhai::Dynamic>(
|
|
&mut scope,
|
|
r#"
|
|
// All participants must be signatories
|
|
let ctx = get_context(["pk1", "pk2"]);
|
|
ctx.context_id()
|
|
"#
|
|
);
|
|
|
|
if let Err(ref e) = test_result {
|
|
eprintln!("Test error: {}", e);
|
|
}
|
|
assert!(test_result.is_ok(), "Failed to get context: {:?}", test_result.err());
|
|
assert_eq!(test_result.unwrap().to_string(), "pk1,pk2");
|
|
}
|
|
|
|
#[test]
|
|
fn test_engine_with_manager_access_denied() {
|
|
let result = create_osiris_engine();
|
|
assert!(result.is_ok());
|
|
|
|
let mut engine = result.unwrap();
|
|
|
|
// Set up context tags with SIGNATORIES
|
|
let mut tag_map = rhai::Map::new();
|
|
// Create a proper Rhai array
|
|
let signatories: rhai::Array = vec![
|
|
rhai::Dynamic::from("pk1".to_string()),
|
|
rhai::Dynamic::from("pk2".to_string()),
|
|
];
|
|
tag_map.insert("SIGNATORIES".into(), rhai::Dynamic::from(signatories));
|
|
tag_map.insert("DB_PATH".into(), "/tmp/test_db".to_string().into());
|
|
tag_map.insert("CONTEXT_ID".into(), "test_context".to_string().into());
|
|
engine.set_default_tag(rhai::Dynamic::from(tag_map));
|
|
|
|
// Test get_context with invalid participant (not a signatory)
|
|
let mut scope = rhai::Scope::new();
|
|
let test_result = engine.eval_with_scope::<rhai::Dynamic>(
|
|
&mut scope,
|
|
r#"
|
|
// pk3 is not a signatory, should fail
|
|
let ctx = get_context(["pk1", "pk3"]);
|
|
ctx.context_id()
|
|
"#
|
|
);
|
|
|
|
// Should fail because pk3 is not in SIGNATORIES
|
|
assert!(test_result.is_err());
|
|
let err_msg = test_result.unwrap_err().to_string();
|
|
assert!(err_msg.contains("Access denied") || err_msg.contains("not a signatory"));
|
|
}
|
|
}
|