implement auth macros
This commit is contained in:
parent
8c88695953
commit
aa4712b8af
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -140,16 +140,6 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "authorization"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"heromodels",
|
|
||||||
"heromodels_core",
|
|
||||||
"rhai",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -1653,6 +1643,16 @@ version = "0.4.27"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"heromodels",
|
||||||
|
"heromodels_core",
|
||||||
|
"rhai",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2378,11 +2378,11 @@ dependencies = [
|
|||||||
name = "rhailib_dsl"
|
name = "rhailib_dsl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"authorization",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"heromodels",
|
"heromodels",
|
||||||
"heromodels-derive",
|
"heromodels-derive",
|
||||||
"heromodels_core",
|
"heromodels_core",
|
||||||
|
"macros",
|
||||||
"rhai",
|
"rhai",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -39,6 +39,6 @@ members = [
|
|||||||
"src/monitor", # Added the new monitor package to workspace
|
"src/monitor", # Added the new monitor package to workspace
|
||||||
"src/repl", # Added the refactored REPL package
|
"src/repl", # Added the refactored REPL package
|
||||||
"examples",
|
"examples",
|
||||||
"src/rhai_engine_ui", "src/authorization", "src/dsl",
|
"src/rhai_engine_ui", "src/macros", "src/dsl",
|
||||||
]
|
]
|
||||||
resolver = "2" # Recommended for new workspaces
|
resolver = "2" # Recommended for new workspaces
|
||||||
|
@ -10,7 +10,7 @@ heromodels = { path = "../../../db/heromodels", features = ["rhai"] }
|
|||||||
heromodels_core = { path = "../../../db/heromodels_core" }
|
heromodels_core = { path = "../../../db/heromodels_core" }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
heromodels-derive = { path = "../../../db/heromodels-derive" }
|
heromodels-derive = { path = "../../../db/heromodels-derive" }
|
||||||
authorization = { path = "../authorization"}
|
macros = { path = "../macros"}
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@ fn register_example_module(engine: &mut Engine, db: Arc<OurDB>) {
|
|||||||
|
|
||||||
register_authorized_get_by_id_fn!(
|
register_authorized_get_by_id_fn!(
|
||||||
module: &mut module,
|
module: &mut module,
|
||||||
db_clone: db.clone(),
|
|
||||||
rhai_fn_name: "get_collection",
|
rhai_fn_name: "get_collection",
|
||||||
resource_type_str: "Collection",
|
resource_type_str: "Collection",
|
||||||
rhai_return_rust_type: heromodels::models::library::collection::Collection // Use Collection struct
|
rhai_return_rust_type: heromodels::models::library::collection::Collection // Use Collection struct
|
||||||
@ -53,24 +52,11 @@ fn register_example_module(engine: &mut Engine, db: Arc<OurDB>) {
|
|||||||
engine.register_global_module(module.into());
|
engine.register_global_module(module.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
fn create_alice_engine(db_dir: &str, alice_pk: &str) -> Engine {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
let temp_dir = tempdir().unwrap();
|
let db_path = format!("{}/{}", db_dir, alice_pk);
|
||||||
let db = Arc::new(OurDB::new(temp_dir.path(), false).expect("Failed to create DB"));
|
let db = Arc::new(OurDB::new(&db_path, false).expect("Failed to create DB"));
|
||||||
|
|
||||||
register_example_module(&mut engine, db.clone());
|
|
||||||
|
|
||||||
println!("--- Registered Functions ---");
|
|
||||||
// The closure now returns Option<FuncMetadata> by cloning the metadata.
|
|
||||||
// FuncMetadata is Clone and 'static, satisfying collect_fn_metadata's requirements.
|
|
||||||
for metadata_clone in engine.collect_fn_metadata(None, |info: rhai::FuncInfo<'_>| Some(info.metadata.clone()), true) {
|
|
||||||
if metadata_clone.name == "get_collection" {
|
|
||||||
println!("Found get_collection function, args: {:?}", metadata_clone.param_types);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("--------------------------");
|
|
||||||
|
|
||||||
|
|
||||||
// Populate DB using the new `create_collection` helper.
|
// Populate DB using the new `create_collection` helper.
|
||||||
// Ownership is no longer on the collection itself, so we don't need owner_pk here.
|
// Ownership is no longer on the collection itself, so we don't need owner_pk here.
|
||||||
@ -83,70 +69,111 @@ fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
|||||||
let coll1 = Collection::new()
|
let coll1 = Collection::new()
|
||||||
.title("Alice's Private Collection")
|
.title("Alice's Private Collection")
|
||||||
.description("This is Alice's private collection");
|
.description("This is Alice's private collection");
|
||||||
let coll2 = Collection::new()
|
|
||||||
.title("Bob's Shared Collection")
|
|
||||||
.description("This is Bob's shared collection");
|
|
||||||
let coll3 = Collection::new()
|
let coll3 = Collection::new()
|
||||||
.title("General Collection")
|
.title("General Collection")
|
||||||
.description("This is a general collection");
|
.description("This is a general collection");
|
||||||
|
|
||||||
db.set(&coll1).expect("Failed to set collection");
|
db.set(&coll1).expect("Failed to set collection");
|
||||||
db.set(&coll2).expect("Failed to set collection");
|
|
||||||
db.set(&coll3).expect("Failed to set collection");
|
db.set(&coll3).expect("Failed to set collection");
|
||||||
|
|
||||||
// Grant access based on the new model.
|
// Grant access based on the new model.
|
||||||
grant_access(&db, "alice_pk", "Collection", coll1.id());
|
grant_access(&db, "alice_pk", "Collection", coll1.id());
|
||||||
grant_access(&db, "bob_pk", "Collection", coll2.id());
|
grant_access(&db, "user_pk", "Collection", coll3.id());
|
||||||
grant_access(&db, "alice_pk", "Collection", coll2.id()); // Alice can also see Bob's collection.
|
|
||||||
grant_access(&db, "general_user_pk", "Collection", coll3.id());
|
|
||||||
|
|
||||||
|
register_example_module(&mut engine, db.clone());
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("DB_PATH".into(), db_dir.clone().into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
engine
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_bob_engine(db_dir: &str, bob_pk: &str) -> Engine {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let db_path = format!("{}/{}", db_dir, bob_pk);
|
||||||
|
let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB"));
|
||||||
|
|
||||||
|
let coll2 = Collection::new()
|
||||||
|
.title("Bob's Shared Collection")
|
||||||
|
.description("This is Bob's shared collection Alice has access.");
|
||||||
|
|
||||||
|
db.set(&coll2).expect("Failed to set collection");
|
||||||
|
grant_access(&db, "alice_pk", "Collection", coll2.id());
|
||||||
|
|
||||||
|
register_example_module(&mut engine, db.clone());
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("DB_PATH".into(), db_dir.clone().into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), "bob_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
engine
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_user_engine(db_dir: &str, user_pk: &str) -> Engine {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let db_path = format!("{}/{}", db_dir, user_pk);
|
||||||
|
let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB"));
|
||||||
|
register_example_module(&mut engine, db.clone());
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("DB_PATH".into(), db_dir.clone().into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), "user_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
engine
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
let db_path = format!("{}/hero/db", std::env::var("HOME").unwrap());
|
||||||
|
let alice_pk = "alice_pk";
|
||||||
|
let bob_pk = "bob_pk";
|
||||||
|
let user_pk = "user_pk";
|
||||||
|
|
||||||
|
let mut engine_alice = create_alice_engine(&db_path, alice_pk);
|
||||||
|
let mut engine_bob = create_bob_engine(&db_path, bob_pk);
|
||||||
|
let mut engine_user = create_user_engine(&db_path, user_pk);
|
||||||
|
|
||||||
|
println!("--------------------------");
|
||||||
println!("--- Rhai Authorization Example ---");
|
println!("--- Rhai Authorization Example ---");
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
// Scenario 1: Alice accesses her own collection (Success)
|
|
||||||
let mut db_config = rhai::Map::new();
|
|
||||||
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
|
||||||
engine.set_default_tag(Dynamic::from(db_config)); // Or pass via CallFnOptions
|
|
||||||
|
|
||||||
// Create a Dynamic value holding your DB path or a config object
|
// Create a Dynamic value holding your DB path or a config object
|
||||||
let mut db_config = rhai::Map::new();
|
{
|
||||||
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
let mut tag_dynamic = engine_alice.default_tag_mut().as_map_mut().unwrap();
|
||||||
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
tag_dynamic.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
engine.set_default_tag(Dynamic::from(db_config));
|
}
|
||||||
|
// engine_alice.set_default_tag(Dynamic::from(tag_dynamic.clone()));
|
||||||
|
|
||||||
println!("Alice accessing her collection 1: Success, title"); // Access field directly
|
println!("Alice accessing her collection 1: Success, title"); // Access field directly
|
||||||
let result = engine.eval::<Option<Collection>>("get_collection(1)")?;
|
let result = engine_alice.eval::<Option<Collection>>("get_collection(1)")?;
|
||||||
let result_clone = result.clone().expect("REASON");
|
let result_clone = result.clone().expect("Failed to retrieve collection. It might not exist or you may not have access.");
|
||||||
println!("Alice accessing her collection 1: Success, title = {}", result_clone.title); // Access field directly
|
println!("Alice accessing her collection 1: Success, title = {}", result_clone.title); // Access field directly
|
||||||
assert_eq!(result_clone.id(), 1);
|
assert_eq!(result_clone.id(), 1);
|
||||||
|
|
||||||
// Scenario 2: Bob tries to access Alice's collection (Failure)
|
// Scenario 2: Bob tries to access Alice's collection (Failure)
|
||||||
let mut db_config = rhai::Map::new();
|
{
|
||||||
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
let mut tag_dynamic = engine_bob.default_tag_mut().as_map_mut().unwrap();
|
||||||
db_config.insert("CALLER_PUBLIC_KEY".into(), "bob_pk".into());
|
tag_dynamic.insert("CALLER_PUBLIC_KEY".into(), "bob_pk".into());
|
||||||
engine.set_default_tag(Dynamic::from(db_config));
|
}
|
||||||
let result = engine.eval_with_scope::<Dynamic>(&mut scope, "get_collection(1)");
|
let result = engine_alice.eval_with_scope::<Option<Collection>>(&mut scope, "get_collection(1)")?;
|
||||||
println!("Bob accessing Alice's collection 1: {:?}", result);
|
println!("Bob accessing Alice's collection 1: Failure as expected ({:?})", result);
|
||||||
let result_clone = result.expect("REASON");
|
assert!(result.is_none());
|
||||||
println!("Bob accessing Alice's collection 1: {:?}", result_clone);
|
|
||||||
// assert!(result_clone.is_none());
|
|
||||||
|
|
||||||
// Scenario 3: Alice accesses Bob's collection (Success)
|
// Scenario 3: Alice accesses Bob's collection (Success)
|
||||||
let mut db_config = rhai::Map::new();
|
let mut db_config = rhai::Map::new();
|
||||||
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
|
||||||
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
engine.set_default_tag(Dynamic::from(db_config));
|
engine_bob.set_default_tag(Dynamic::from(db_config));
|
||||||
let result = engine.eval_with_scope::<Collection>(&mut scope, "get_collection(2)")?;
|
let result: Option<Collection> = engine_bob.eval_with_scope::<Option<Collection>>(&mut scope, "get_collection(2)")?;
|
||||||
println!("Alice accessing Bob's collection 2: Success, title = {}", result.title); // Access field directly
|
let collection = result.expect("Alice should have access to Bob's collection");
|
||||||
assert_eq!(result.id(), 2);
|
println!("Alice accessing Bob's collection 2: Success, title = {}", collection.title); // Access field directly
|
||||||
|
assert_eq!(collection.id(), 2);
|
||||||
|
|
||||||
// Scenario 4: General user lists collections (Sees 1)
|
// Scenario 4: General user lists collections (Sees 1)
|
||||||
let mut db_config = rhai::Map::new();
|
let mut db_config = rhai::Map::new();
|
||||||
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
db_config.insert("CALLER_PUBLIC_KEY".into(), "general_user_pk".into());
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "general_user_pk".into());
|
||||||
engine.set_default_tag(Dynamic::from(db_config));
|
engine_user.set_default_tag(Dynamic::from(db_config));
|
||||||
let result = engine.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()").unwrap();
|
let result = engine_user.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()").unwrap();
|
||||||
println!("General user listing collections: Found {}", result.0.len());
|
println!("General user listing collections: Found {}", result.0.len());
|
||||||
assert_eq!(result.0.len(), 1);
|
assert_eq!(result.0.len(), 1);
|
||||||
assert_eq!(result.0[0].id(), 3);
|
assert_eq!(result.0[0].id(), 3);
|
||||||
@ -155,8 +182,8 @@ fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
|||||||
let mut db_config = rhai::Map::new();
|
let mut db_config = rhai::Map::new();
|
||||||
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
engine.set_default_tag(Dynamic::from(db_config));
|
engine_alice.set_default_tag(Dynamic::from(db_config));
|
||||||
let collections = engine.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()").unwrap();
|
let collections = engine_alice.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()").unwrap();
|
||||||
println!("Alice listing collections: Found {}", collections.0.len());
|
println!("Alice listing collections: Found {}", collections.0.len());
|
||||||
assert_eq!(collections.0.len(), 2);
|
assert_eq!(collections.0.len(), 2);
|
||||||
let ids: Vec<u32> = collections.0.iter().map(|c| c.id()).collect();
|
let ids: Vec<u32> = collections.0.iter().map(|c| c.id()).collect();
|
||||||
|
@ -0,0 +1,395 @@
|
|||||||
|
#![feature(prelude_import)]
|
||||||
|
#[prelude_import]
|
||||||
|
use std::prelude::rust_2021::*;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate std;
|
||||||
|
use rhai::{Engine, Module, Position, Scope, Dynamic};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use heromodels::db::{Db, Collection as DbCollection};
|
||||||
|
use heromodels::{
|
||||||
|
db::hero::OurDB, models::library::collection::Collection,
|
||||||
|
models::library::rhai::RhaiCollectionArray, models::access::access::Access,
|
||||||
|
};
|
||||||
|
use rhailib_dsl::{register_authorized_get_by_id_fn, register_authorized_list_fn};
|
||||||
|
use rhai::{FuncRegistration, EvalAltResult};
|
||||||
|
fn grant_access(db: &Arc<OurDB>, user_pk: &str, resource_type: &str, resource_id: u32) {
|
||||||
|
let access_record = Access::new()
|
||||||
|
.circle_pk(user_pk.to_string())
|
||||||
|
.object_type(resource_type.to_string())
|
||||||
|
.object_id(resource_id)
|
||||||
|
.contact_id(0)
|
||||||
|
.group_id(0);
|
||||||
|
db.set(&access_record).expect("Failed to set access record");
|
||||||
|
}
|
||||||
|
fn register_example_module(engine: &mut Engine, db: Arc<OurDB>) {
|
||||||
|
let mut module = Module::new();
|
||||||
|
FuncRegistration::new("get_collection")
|
||||||
|
.set_into_module(
|
||||||
|
&mut module,
|
||||||
|
move |
|
||||||
|
context: rhai::NativeCallContext,
|
||||||
|
id_val: i64,
|
||||||
|
| -> Result<
|
||||||
|
Option<heromodels::models::library::collection::Collection>,
|
||||||
|
Box<EvalAltResult>,
|
||||||
|
> {
|
||||||
|
let actual_id: u32 = ::macros::id_from_i64_to_u32(id_val)?;
|
||||||
|
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_PUBLIC_KEY")
|
||||||
|
.ok_or_else(|| Box::new(
|
||||||
|
EvalAltResult::ErrorRuntime(
|
||||||
|
"'CALLER_PUBLIC_KEY' not found in context tag Map.".into(),
|
||||||
|
context.position(),
|
||||||
|
),
|
||||||
|
))?;
|
||||||
|
let circle_pk = tag_map
|
||||||
|
.get("CIRCLE_PUBLIC_KEY")
|
||||||
|
.ok_or_else(|| Box::new(
|
||||||
|
EvalAltResult::ErrorRuntime(
|
||||||
|
"'CIRCLE_PUBLIC_KEY' not found in context tag Map.".into(),
|
||||||
|
context.position(),
|
||||||
|
),
|
||||||
|
))?;
|
||||||
|
let circle_pk = circle_pk.clone().into_string()?;
|
||||||
|
let db_path = ::alloc::__export::must_use({
|
||||||
|
let res = ::alloc::fmt::format(
|
||||||
|
format_args!("~/hero/{0}", circle_pk),
|
||||||
|
);
|
||||||
|
res
|
||||||
|
});
|
||||||
|
let db = Arc::new(
|
||||||
|
OurDB::new(db_path, false).expect("Failed to create DB"),
|
||||||
|
);
|
||||||
|
let caller_pk_str = pk_dynamic.clone().into_string()?;
|
||||||
|
{
|
||||||
|
::std::io::_print(
|
||||||
|
format_args!(
|
||||||
|
"Checking access for public key: {0}\n",
|
||||||
|
caller_pk_str,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if circle_pk != caller_pk_str {
|
||||||
|
let has_access = heromodels::models::access::access::can_access_resource(
|
||||||
|
db.clone(),
|
||||||
|
&caller_pk_str,
|
||||||
|
actual_id,
|
||||||
|
"Collection",
|
||||||
|
);
|
||||||
|
if !has_access {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = db
|
||||||
|
.get_by_id(actual_id)
|
||||||
|
.map_err(|e| {
|
||||||
|
Box::new(
|
||||||
|
EvalAltResult::ErrorRuntime(
|
||||||
|
::alloc::__export::must_use({
|
||||||
|
let res = ::alloc::fmt::format(
|
||||||
|
format_args!(
|
||||||
|
"Database error fetching {0}: {1:?}",
|
||||||
|
"Collection",
|
||||||
|
e,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
res
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
context.position(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok(result)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let db_instance_auth_outer = db.clone().clone();
|
||||||
|
let db_instance_fetch = db.clone().clone();
|
||||||
|
FuncRegistration::new("list_all_collections")
|
||||||
|
.set_into_module(
|
||||||
|
&mut module,
|
||||||
|
move |
|
||||||
|
context: rhai::NativeCallContext,
|
||||||
|
| -> Result<
|
||||||
|
heromodels::models::library::rhai::RhaiCollectionArray,
|
||||||
|
Box<EvalAltResult>,
|
||||||
|
> {
|
||||||
|
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_PUBLIC_KEY")
|
||||||
|
.ok_or_else(|| Box::new(
|
||||||
|
EvalAltResult::ErrorRuntime(
|
||||||
|
"'CALLER_PUBLIC_KEY' not found in context tag Map.".into(),
|
||||||
|
context.position(),
|
||||||
|
),
|
||||||
|
))?;
|
||||||
|
let caller_pk_str = pk_dynamic.clone().into_string()?;
|
||||||
|
let all_items: Vec<
|
||||||
|
heromodels::models::library::collection::Collection,
|
||||||
|
> = db_instance_fetch
|
||||||
|
.collection::<heromodels::models::library::collection::Collection>()
|
||||||
|
.map_err(|e| Box::new(
|
||||||
|
EvalAltResult::ErrorRuntime(
|
||||||
|
::alloc::__export::must_use({
|
||||||
|
let res = ::alloc::fmt::format(format_args!("{0:?}", e));
|
||||||
|
res
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
Position::NONE,
|
||||||
|
),
|
||||||
|
))?
|
||||||
|
.get_all()
|
||||||
|
.map_err(|e| Box::new(
|
||||||
|
EvalAltResult::ErrorRuntime(
|
||||||
|
::alloc::__export::must_use({
|
||||||
|
let res = ::alloc::fmt::format(format_args!("{0:?}", e));
|
||||||
|
res
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
Position::NONE,
|
||||||
|
),
|
||||||
|
))?;
|
||||||
|
let authorized_items: Vec<
|
||||||
|
heromodels::models::library::collection::Collection,
|
||||||
|
> = all_items
|
||||||
|
.into_iter()
|
||||||
|
.filter(|item| {
|
||||||
|
let resource_id = item.id();
|
||||||
|
heromodels::models::access::access::can_access_resource(
|
||||||
|
db_instance_auth_outer.clone(),
|
||||||
|
&caller_pk_str,
|
||||||
|
resource_id,
|
||||||
|
"Collection",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(authorized_items.into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
engine.register_global_module(module.into());
|
||||||
|
}
|
||||||
|
fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let db = Arc::new(OurDB::new(temp_dir.path(), false).expect("Failed to create DB"));
|
||||||
|
register_example_module(&mut engine, db.clone());
|
||||||
|
{
|
||||||
|
::std::io::_print(format_args!("--- Registered Functions ---\n"));
|
||||||
|
};
|
||||||
|
for metadata_clone in engine
|
||||||
|
.collect_fn_metadata(
|
||||||
|
None,
|
||||||
|
|info: rhai::FuncInfo<'_>| Some(info.metadata.clone()),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if metadata_clone.name == "get_collection" {
|
||||||
|
{
|
||||||
|
::std::io::_print(
|
||||||
|
format_args!(
|
||||||
|
"Found get_collection function, args: {0:?}\n",
|
||||||
|
metadata_clone.param_types,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
::std::io::_print(format_args!("--------------------------\n"));
|
||||||
|
};
|
||||||
|
let coll = Collection::new()
|
||||||
|
.title("My new collection")
|
||||||
|
.description("This is a new collection");
|
||||||
|
db.set(&coll).expect("Failed to set collection");
|
||||||
|
let coll1 = Collection::new()
|
||||||
|
.title("Alice's Private Collection")
|
||||||
|
.description("This is Alice's private collection");
|
||||||
|
let coll2 = Collection::new()
|
||||||
|
.title("Bob's Shared Collection")
|
||||||
|
.description("This is Bob's shared collection");
|
||||||
|
let coll3 = Collection::new()
|
||||||
|
.title("General Collection")
|
||||||
|
.description("This is a general collection");
|
||||||
|
db.set(&coll1).expect("Failed to set collection");
|
||||||
|
db.set(&coll2).expect("Failed to set collection");
|
||||||
|
db.set(&coll3).expect("Failed to set collection");
|
||||||
|
grant_access(&db, "alice_pk", "Collection", coll1.id());
|
||||||
|
grant_access(&db, "bob_pk", "Collection", coll2.id());
|
||||||
|
grant_access(&db, "alice_pk", "Collection", coll2.id());
|
||||||
|
grant_access(&db, "general_user_pk", "Collection", coll3.id());
|
||||||
|
{
|
||||||
|
::std::io::_print(format_args!("--- Rhai Authorization Example ---\n"));
|
||||||
|
};
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
{
|
||||||
|
::std::io::_print(
|
||||||
|
format_args!("Alice accessing her collection 1: Success, title\n"),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let result = engine.eval::<Option<Collection>>("get_collection(1)")?;
|
||||||
|
let result_clone = result.clone().expect("REASON");
|
||||||
|
{
|
||||||
|
::std::io::_print(
|
||||||
|
format_args!(
|
||||||
|
"Alice accessing her collection 1: Success, title = {0}\n",
|
||||||
|
result_clone.title,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
match (&result_clone.id(), &1) {
|
||||||
|
(left_val, right_val) => {
|
||||||
|
if !(*left_val == *right_val) {
|
||||||
|
let kind = ::core::panicking::AssertKind::Eq;
|
||||||
|
::core::panicking::assert_failed(
|
||||||
|
kind,
|
||||||
|
&*left_val,
|
||||||
|
&*right_val,
|
||||||
|
::core::option::Option::None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "bob_pk".into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
let result = engine
|
||||||
|
.eval_with_scope::<Option<Collection>>(&mut scope, "get_collection(1)")?;
|
||||||
|
{
|
||||||
|
::std::io::_print(
|
||||||
|
format_args!(
|
||||||
|
"Bob accessing Alice\'s collection 1: Failure as expected ({0:?})\n",
|
||||||
|
result,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if !result.is_none() {
|
||||||
|
::core::panicking::panic("assertion failed: result.is_none()")
|
||||||
|
}
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), "bob_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
let result: Option<Collection> = engine
|
||||||
|
.eval_with_scope::<Option<Collection>>(&mut scope, "get_collection(2)")?;
|
||||||
|
let collection = result.expect("Alice should have access to Bob's collection");
|
||||||
|
{
|
||||||
|
::std::io::_print(
|
||||||
|
format_args!(
|
||||||
|
"Alice accessing Bob\'s collection 2: Success, title = {0}\n",
|
||||||
|
collection.title,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
match (&collection.id(), &2) {
|
||||||
|
(left_val, right_val) => {
|
||||||
|
if !(*left_val == *right_val) {
|
||||||
|
let kind = ::core::panicking::AssertKind::Eq;
|
||||||
|
::core::panicking::assert_failed(
|
||||||
|
kind,
|
||||||
|
&*left_val,
|
||||||
|
&*right_val,
|
||||||
|
::core::option::Option::None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "general_user_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
let result = engine
|
||||||
|
.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()")
|
||||||
|
.unwrap();
|
||||||
|
{
|
||||||
|
::std::io::_print(
|
||||||
|
format_args!("General user listing collections: Found {0}\n", result.0.len()),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
match (&result.0.len(), &1) {
|
||||||
|
(left_val, right_val) => {
|
||||||
|
if !(*left_val == *right_val) {
|
||||||
|
let kind = ::core::panicking::AssertKind::Eq;
|
||||||
|
::core::panicking::assert_failed(
|
||||||
|
kind,
|
||||||
|
&*left_val,
|
||||||
|
&*right_val,
|
||||||
|
::core::option::Option::None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match (&result.0[0].id(), &3) {
|
||||||
|
(left_val, right_val) => {
|
||||||
|
if !(*left_val == *right_val) {
|
||||||
|
let kind = ::core::panicking::AssertKind::Eq;
|
||||||
|
::core::panicking::assert_failed(
|
||||||
|
kind,
|
||||||
|
&*left_val,
|
||||||
|
&*right_val,
|
||||||
|
::core::option::Option::None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
let collections = engine
|
||||||
|
.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()")
|
||||||
|
.unwrap();
|
||||||
|
{
|
||||||
|
::std::io::_print(
|
||||||
|
format_args!("Alice listing collections: Found {0}\n", collections.0.len()),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
match (&collections.0.len(), &2) {
|
||||||
|
(left_val, right_val) => {
|
||||||
|
if !(*left_val == *right_val) {
|
||||||
|
let kind = ::core::panicking::AssertKind::Eq;
|
||||||
|
::core::panicking::assert_failed(
|
||||||
|
kind,
|
||||||
|
&*left_val,
|
||||||
|
&*right_val,
|
||||||
|
::core::option::Option::None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let ids: Vec<u32> = collections.0.iter().map(|c| c.id()).collect();
|
||||||
|
if !(ids.contains(&1) && ids.contains(&2)) {
|
||||||
|
::core::panicking::panic(
|
||||||
|
"assertion failed: ids.contains(&1) && ids.contains(&2)",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
pub mod library;
|
pub mod library;
|
||||||
pub mod access;
|
pub mod access;
|
||||||
|
|
||||||
pub use authorization::register_authorized_get_by_id_fn;
|
pub use macros::register_authorized_get_by_id_fn;
|
||||||
pub use authorization::register_authorized_list_fn;
|
pub use macros::register_authorized_list_fn;
|
||||||
pub use authorization::id_from_i64_to_u32;
|
pub use macros::id_from_i64_to_u32;
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "authorization"
|
name = "macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
191
src/macros/examples/access_control.rs
Normal file
191
src/macros/examples/access_control.rs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
use macros::{register_authorized_get_by_id_fn, register_authorized_list_fn};
|
||||||
|
use rhai::{Engine, Module, Position, Scope, Dynamic};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// Import DB traits with an alias for the Collection trait to avoid naming conflicts.
|
||||||
|
// Import DB traits from heromodels::db as suggested by compiler errors.
|
||||||
|
use heromodels::db::{Db, Collection as DbCollection};
|
||||||
|
use heromodels::{
|
||||||
|
db::hero::OurDB,
|
||||||
|
models::library::collection::Collection, // Actual data model for single items
|
||||||
|
models::library::rhai::RhaiCollectionArray, // Wrapper for arrays of collections
|
||||||
|
models::access::access::Access,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rhai::{FuncRegistration, EvalAltResult}; // For macro expansion
|
||||||
|
|
||||||
|
// Rewritten to match the new `Access` model structure.
|
||||||
|
fn grant_access(db: &Arc<OurDB>, user_pk: &str, resource_type: &str, resource_id: u32) {
|
||||||
|
let access_record = Access::new()
|
||||||
|
.circle_pk(user_pk.to_string())
|
||||||
|
.object_type(resource_type.to_string())
|
||||||
|
.object_id(resource_id)
|
||||||
|
.contact_id(0)
|
||||||
|
.group_id(0);
|
||||||
|
|
||||||
|
db.set(&access_record).expect("Failed to set access record");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No changes needed here, but it relies on the new imports to compile.
|
||||||
|
fn register_example_module(engine: &mut Engine, db: Arc<OurDB>) {
|
||||||
|
let mut module = Module::new();
|
||||||
|
|
||||||
|
register_authorized_get_by_id_fn!(
|
||||||
|
module: &mut module,
|
||||||
|
rhai_fn_name: "get_collection",
|
||||||
|
resource_type_str: "Collection",
|
||||||
|
rhai_return_rust_type: heromodels::models::library::collection::Collection // Use Collection struct
|
||||||
|
);
|
||||||
|
|
||||||
|
register_authorized_list_fn!(
|
||||||
|
module: &mut module,
|
||||||
|
db_clone: db.clone(),
|
||||||
|
rhai_fn_name: "list_all_collections",
|
||||||
|
resource_type_str: "Collection",
|
||||||
|
rhai_return_rust_type: heromodels::models::library::collection::Collection, // Use Collection struct
|
||||||
|
item_id_accessor: id, // Assumes Collection has an id() method that returns u32
|
||||||
|
rhai_return_wrapper_type: heromodels::models::library::rhai::RhaiCollectionArray // Wrapper type for Rhai
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_global_module(module.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_alice_engine(db_dir: &str, alice_pk: &str) -> Engine {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let db_path = format!("{}/{}", db_dir, alice_pk);
|
||||||
|
let db = Arc::new(OurDB::new(&db_path, false).expect("Failed to create DB"));
|
||||||
|
|
||||||
|
// Populate DB using the new `create_collection` helper.
|
||||||
|
// Ownership is no longer on the collection itself, so we don't need owner_pk here.
|
||||||
|
let coll = Collection::new()
|
||||||
|
.title("My new collection")
|
||||||
|
.description("This is a new collection");
|
||||||
|
|
||||||
|
db.set(&coll).expect("Failed to set collection");
|
||||||
|
|
||||||
|
let coll1 = Collection::new()
|
||||||
|
.title("Alice's Private Collection")
|
||||||
|
.description("This is Alice's private collection");
|
||||||
|
let coll3 = Collection::new()
|
||||||
|
.title("General Collection")
|
||||||
|
.description("This is a general collection");
|
||||||
|
|
||||||
|
db.set(&coll1).expect("Failed to set collection");
|
||||||
|
db.set(&coll3).expect("Failed to set collection");
|
||||||
|
|
||||||
|
// Grant access based on the new model.
|
||||||
|
grant_access(&db, "alice_pk", "Collection", coll1.id());
|
||||||
|
grant_access(&db, "user_pk", "Collection", coll3.id());
|
||||||
|
|
||||||
|
register_example_module(&mut engine, db.clone());
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("DB_PATH".into(), db_dir.clone().into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
engine
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_bob_engine(db_dir: &str, bob_pk: &str) -> Engine {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let db_path = format!("{}/{}", db_dir, bob_pk);
|
||||||
|
let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB"));
|
||||||
|
|
||||||
|
let coll2 = Collection::new()
|
||||||
|
.title("Bob's Shared Collection")
|
||||||
|
.description("This is Bob's shared collection Alice has access.");
|
||||||
|
|
||||||
|
db.set(&coll2).expect("Failed to set collection");
|
||||||
|
grant_access(&db, "alice_pk", "Collection", coll2.id());
|
||||||
|
|
||||||
|
register_example_module(&mut engine, db.clone());
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("DB_PATH".into(), db_dir.clone().into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), "bob_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
engine
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_user_engine(db_dir: &str, user_pk: &str) -> Engine {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let db_path = format!("{}/{}", db_dir, user_pk);
|
||||||
|
let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB"));
|
||||||
|
register_example_module(&mut engine, db.clone());
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("DB_PATH".into(), db_dir.clone().into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), "user_pk".into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
engine
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
let db_path = format!("{}/hero/db", std::env::var("HOME").unwrap());
|
||||||
|
let alice_pk = "alice_pk";
|
||||||
|
let bob_pk = "bob_pk";
|
||||||
|
let user_pk = "user_pk";
|
||||||
|
|
||||||
|
let mut engine_alice = create_alice_engine(&db_path, alice_pk);
|
||||||
|
let mut engine_bob = create_bob_engine(&db_path, bob_pk);
|
||||||
|
let mut engine_user = create_user_engine(&db_path, user_pk);
|
||||||
|
|
||||||
|
println!("--------------------------");
|
||||||
|
println!("--- Rhai Authorization Example ---");
|
||||||
|
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
|
// Create a Dynamic value holding your DB path or a config object
|
||||||
|
{
|
||||||
|
let mut tag_dynamic = engine_alice.default_tag_mut().as_map_mut().unwrap();
|
||||||
|
tag_dynamic.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
}
|
||||||
|
// engine_alice.set_default_tag(Dynamic::from(tag_dynamic.clone()));
|
||||||
|
|
||||||
|
println!("Alice accessing her collection 1: Success, title"); // Access field directly
|
||||||
|
let result = engine_alice.eval::<Option<Collection>>("get_collection(1)")?;
|
||||||
|
let result_clone = result.clone().expect("Failed to retrieve collection. It might not exist or you may not have access.");
|
||||||
|
println!("Alice accessing her collection 1: Success, title = {}", result_clone.title); // Access field directly
|
||||||
|
assert_eq!(result_clone.id(), 1);
|
||||||
|
|
||||||
|
// Scenario 2: Bob tries to access Alice's collection (Failure)
|
||||||
|
{
|
||||||
|
let mut tag_dynamic = engine_bob.default_tag_mut().as_map_mut().unwrap();
|
||||||
|
tag_dynamic.insert("CALLER_PUBLIC_KEY".into(), "bob_pk".into());
|
||||||
|
}
|
||||||
|
let result = engine_alice.eval_with_scope::<Option<Collection>>(&mut scope, "get_collection(1)")?;
|
||||||
|
println!("Bob accessing Alice's collection 1: Failure as expected ({:?})", result);
|
||||||
|
assert!(result.is_none());
|
||||||
|
|
||||||
|
// Scenario 3: Alice accesses Bob's collection (Success)
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
engine_bob.set_default_tag(Dynamic::from(db_config));
|
||||||
|
let result: Option<Collection> = engine_bob.eval_with_scope::<Option<Collection>>(&mut scope, "get_collection(2)")?;
|
||||||
|
let collection = result.expect("Alice should have access to Bob's collection");
|
||||||
|
println!("Alice accessing Bob's collection 2: Success, title = {}", collection.title); // Access field directly
|
||||||
|
assert_eq!(collection.id(), 2);
|
||||||
|
|
||||||
|
// Scenario 4: General user lists collections (Sees 1)
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "general_user_pk".into());
|
||||||
|
engine_user.set_default_tag(Dynamic::from(db_config));
|
||||||
|
let result = engine_user.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()").unwrap();
|
||||||
|
println!("General user listing collections: Found {}", result.0.len());
|
||||||
|
assert_eq!(result.0.len(), 1);
|
||||||
|
assert_eq!(result.0[0].id(), 3);
|
||||||
|
|
||||||
|
// Scenario 5: Alice lists collections (Sees 2)
|
||||||
|
let mut db_config = rhai::Map::new();
|
||||||
|
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
|
||||||
|
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
|
||||||
|
engine_alice.set_default_tag(Dynamic::from(db_config));
|
||||||
|
let collections = engine_alice.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()").unwrap();
|
||||||
|
println!("Alice listing collections: Found {}", collections.0.len());
|
||||||
|
assert_eq!(collections.0.len(), 2);
|
||||||
|
let ids: Vec<u32> = collections.0.iter().map(|c| c.id()).collect();
|
||||||
|
assert!(ids.contains(&1) && ids.contains(&2));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -14,9 +14,7 @@
|
|||||||
//! 2. The macros internally use `can_access_resource` for authorization checks.
|
//! 2. The macros internally use `can_access_resource` for authorization checks.
|
||||||
//! 3. Ensure `CALLER_PUBLIC_KEY` is set in the Rhai engine's scope before calling authorized functions.
|
//! 3. Ensure `CALLER_PUBLIC_KEY` is set in the Rhai engine's scope before calling authorized functions.
|
||||||
|
|
||||||
use heromodels::models::access::access::can_access_resource;
|
use rhai::{EvalAltResult, Position, FuncRegistration};
|
||||||
use heromodels_core::Model;
|
|
||||||
use rhai::{EvalAltResult, NativeCallContext, Position};
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
/// Extracts the `CALLER_PUBLIC_KEY` string constant from the Rhai `NativeCallContext`.
|
/// Extracts the `CALLER_PUBLIC_KEY` string constant from the Rhai `NativeCallContext`.
|
||||||
@ -78,14 +76,10 @@ pub fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
|
|||||||
macro_rules! register_authorized_get_by_id_fn {
|
macro_rules! register_authorized_get_by_id_fn {
|
||||||
(
|
(
|
||||||
module: $module:expr,
|
module: $module:expr,
|
||||||
db_clone: $db_clone:expr, // Cloned Arc<OurDB> for database access
|
|
||||||
rhai_fn_name: $rhai_fn_name:expr, // String literal for the Rhai function name (e.g., "get_collection")
|
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")
|
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`)
|
rhai_return_rust_type: $rhai_return_rust_type:ty // Rust type of the resource returned (e.g., `RhaiCollection`)
|
||||||
) => {
|
) => {
|
||||||
let db_instance_auth = $db_clone.clone();
|
|
||||||
let db_instance_fetch = $db_clone.clone();
|
|
||||||
|
|
||||||
FuncRegistration::new($rhai_fn_name).set_into_module(
|
FuncRegistration::new($rhai_fn_name).set_into_module(
|
||||||
$module,
|
$module,
|
||||||
move |context: rhai::NativeCallContext, id_val: i64| -> Result<Option<$rhai_return_rust_type>, Box<EvalAltResult>> {
|
move |context: rhai::NativeCallContext, id_val: i64| -> Result<Option<$rhai_return_rust_type>, Box<EvalAltResult>> {
|
||||||
@ -100,11 +94,28 @@ macro_rules! register_authorized_get_by_id_fn {
|
|||||||
let pk_dynamic = tag_map.get("CALLER_PUBLIC_KEY")
|
let pk_dynamic = tag_map.get("CALLER_PUBLIC_KEY")
|
||||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CALLER_PUBLIC_KEY' not found in context tag Map.".into(), context.position())))?;
|
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CALLER_PUBLIC_KEY' 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()?;
|
||||||
|
|
||||||
|
println!("DB Path: {}", db_path);
|
||||||
|
|
||||||
|
let circle_pk = tag_map.get("CIRCLE_PUBLIC_KEY")
|
||||||
|
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CIRCLE_PUBLIC_KEY' 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()?;
|
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
|
// Use the standalone can_access_resource function from heromodels
|
||||||
let has_access = heromodels::models::access::access::can_access_resource(
|
let has_access = heromodels::models::access::access::can_access_resource(
|
||||||
db_instance_auth.clone(),
|
db.clone(),
|
||||||
&caller_pk_str,
|
&caller_pk_str,
|
||||||
actual_id,
|
actual_id,
|
||||||
$resource_type_str,
|
$resource_type_str,
|
||||||
@ -113,13 +124,29 @@ macro_rules! register_authorized_get_by_id_fn {
|
|||||||
if !has_access {
|
if !has_access {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let result = db_instance_fetch.get_by_id(actual_id).map_err(|e| {
|
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)))?;
|
||||||
|
|
||||||
|
for item in all_items {
|
||||||
|
println!("{} with ID: {}", $resource_type_str, item.id());
|
||||||
|
}
|
||||||
|
println!("Fetching {} with ID: {}", $resource_type_str, actual_id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let result = db.get_by_id(actual_id).map_err(|e| {
|
||||||
|
println!("Database error fetching {} with ID: {}", $resource_type_str, actual_id);
|
||||||
Box::new(EvalAltResult::ErrorRuntime(
|
Box::new(EvalAltResult::ErrorRuntime(
|
||||||
format!("Database error fetching {}: {:?}", $resource_type_str, e).into(),
|
format!("Database error fetching {}: {:?}", $resource_type_str, e).into(),
|
||||||
context.position(),
|
context.position(),
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
println!("Database fetched");
|
||||||
Ok(result)
|
Ok(result)
|
||||||
},
|
},
|
||||||
);
|
);
|
@ -1,7 +1,7 @@
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use redis::AsyncCommands;
|
use redis::AsyncCommands;
|
||||||
use rhai::{Engine, Scope};
|
use rhai::{Dynamic, Engine, Scope};
|
||||||
use rhai_client::RhaiTaskDetails; // Import for constructing the reply message
|
use rhai_client::RhaiTaskDetails; // Import for constructing the reply message
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -44,7 +44,8 @@ async fn update_task_status_in_redis(
|
|||||||
pub fn spawn_rhai_worker(
|
pub fn spawn_rhai_worker(
|
||||||
_circle_id: u32, // For logging or specific logic if needed in the future
|
_circle_id: u32, // For logging or specific logic if needed in the future
|
||||||
circle_public_key: String,
|
circle_public_key: String,
|
||||||
engine: Engine,
|
db_path: String,
|
||||||
|
mut engine: Engine,
|
||||||
redis_url: String,
|
redis_url: String,
|
||||||
mut shutdown_rx: mpsc::Receiver<()>, // Add shutdown receiver
|
mut shutdown_rx: mpsc::Receiver<()>, // Add shutdown receiver
|
||||||
preserve_tasks: bool, // Flag to control task cleanup
|
preserve_tasks: bool, // Flag to control task cleanup
|
||||||
@ -86,12 +87,12 @@ pub fn spawn_rhai_worker(
|
|||||||
tokio::select! {
|
tokio::select! {
|
||||||
// Listen for shutdown signal
|
// Listen for shutdown signal
|
||||||
_ = shutdown_rx.recv() => {
|
_ = shutdown_rx.recv() => {
|
||||||
info!("Worker for Circle Public Key '{}': Shutdown signal received. Terminating loop.", circle_public_key);
|
info!("Worker for Circle Public Key '{}': Shutdown signal received. Terminating loop.", circle_public_key.clone());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Listen for tasks from Redis
|
// Listen for tasks from Redis
|
||||||
blpop_result = redis_conn.blpop(&blpop_keys, BLPOP_TIMEOUT_SECONDS as f64) => {
|
blpop_result = redis_conn.blpop(&blpop_keys, BLPOP_TIMEOUT_SECONDS as f64) => {
|
||||||
debug!("Worker for Circle Public Key '{}': Attempting BLPOP on queue: {}", circle_public_key, queue_key);
|
debug!("Worker for Circle Public Key '{}': Attempting BLPOP on queue: {}", circle_public_key.clone(), queue_key);
|
||||||
let response: Option<(String, String)> = match blpop_result {
|
let response: Option<(String, String)> = match blpop_result {
|
||||||
Ok(resp) => resp,
|
Ok(resp) => resp,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -127,23 +128,19 @@ pub fn spawn_rhai_worker(
|
|||||||
debug!("Worker for Circle Public Key '{}', Task {}: Status updated to 'processing'.", circle_public_key, task_id);
|
debug!("Worker for Circle Public Key '{}', Task {}: Status updated to 'processing'.", circle_public_key, task_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
let mut db_config = rhai::Map::new();
|
||||||
scope.push_constant("CIRCLE_PUBLIC_KEY", circle_public_key.clone());
|
db_config.insert("DB_PATH".into(), db_path.clone().into());
|
||||||
debug!("Worker for Circle Public Key '{}', Task {}: Injected CIRCLE_PUBLIC_KEY into scope.", circle_public_key, task_id);
|
db_config.insert("CALLER_PUBLIC_KEY".into(), public_key_opt.unwrap_or_default().into());
|
||||||
|
db_config.insert("CIRCLE_PUBLIC_KEY".into(), circle_public_key.clone().into());
|
||||||
|
engine.set_default_tag(Dynamic::from(db_config)); // Or pass via CallFnOptions
|
||||||
|
|
||||||
if let Some(public_key) = public_key_opt.as_deref() {
|
|
||||||
if !public_key.is_empty() {
|
|
||||||
scope.push_constant("CALLER_PUBLIC_KEY", public_key.to_string());
|
|
||||||
debug!("Worker for Circle Public Key '{}', Task {}: Injected CALLER_PUBLIC_KEY into scope.", circle_public_key, task_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug!("Worker for Circle Public Key '{}', Task {}: Evaluating script with Rhai engine.", circle_public_key, task_id);
|
debug!("Worker for Circle Public Key '{}', Task {}: Evaluating script with Rhai engine.", circle_public_key, task_id);
|
||||||
|
|
||||||
let mut final_status = "error".to_string(); // Default to error
|
let mut final_status = "error".to_string(); // Default to error
|
||||||
let mut final_output: Option<String> = None;
|
let mut final_output: Option<String> = None;
|
||||||
let mut final_error_msg: Option<String> = None;
|
let mut final_error_msg: Option<String> = None;
|
||||||
|
|
||||||
match engine.eval_with_scope::<rhai::Dynamic>(&mut scope, &script_content) {
|
match engine.eval::<rhai::Dynamic>(&script_content) {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let output_str = if result.is::<String>() {
|
let output_str = if result.is::<String>() {
|
||||||
// If the result is a string, we can unwrap it directly.
|
// If the result is a string, we can unwrap it directly.
|
||||||
@ -194,7 +191,7 @@ pub fn spawn_rhai_worker(
|
|||||||
created_at, // Original creation time
|
created_at, // Original creation time
|
||||||
updated_at: Utc::now(), // Time of this final update/reply
|
updated_at: Utc::now(), // Time of this final update/reply
|
||||||
// reply_to_queue is no longer a field
|
// reply_to_queue is no longer a field
|
||||||
public_key: public_key_opt,
|
public_key: public_key_opt.clone(),
|
||||||
};
|
};
|
||||||
match serde_json::to_string(&reply_details) {
|
match serde_json::to_string(&reply_details) {
|
||||||
Ok(reply_json) => {
|
Ok(reply_json) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user