Closed xcaptain closed 4 years ago
@xcaptain Could you give us an example of usage? The first and the second. Also are you interested in implementation of this?
@GopherJ A simple example, Trump can access this box of masks only if it has a label says "for USA".
or
Trump can access this box of masks only if it has a label says "for USA" and it has been paid. :smile:
We can simply say
RBAC: a subject can act on an object if this subject has some attributes(role) ABAC: a subject can act on an object if this object has some attributes
I would like to try this issue but I still didn't find any good ideas to solve it.
Thanks I mean some example code. I have no idea on how does it should look like.
Tuple is not a good solution.
Have no clue yet, need to investigate more.
hello @xcaptain could you help on finding a solution for this? yesterday I spent some time on it. I think the main problem is how to register all the types to rhai::Engine
.
I'll take care of #92 #94 and if I have time #79, it'll be good if we can solve this so that we have a good feature for 1.0.0
I have some ideas for this: https://github.com/jonathandturner/rhai/issues/131
Thanks @schungx I have read your ideas and it seems good. I'll give a try recently.
@xcaptain Maybe you would like to have a look at @schungx 's idea
I think in the first argument of enforce
, we should accept also serde_json::Value
, so that we convert it to object-map in rhai:
let a = #{ // object map literal with 3 properties
a: 1,
bar: "hello",
"baz!$@": 123.456, // like JS, you can use any string as property names...
"": false, // even the empty string!
a: 42 // <- syntax error: duplicated property name
};
In PR# https://github.com/jonathandturner/rhai/pull/128 I've added a nice helper method called parse_json
which takes a JSON string and parses it into an object map that you can pass onwards to Rhai.
@schungx we need to bring serde_json::Value
into rhai::Scope
, I think the follow is enough:
use rhai::{Engine, EvalAltResult, Scope};
use serde_json::json;
#[cfg(not(feature = "no_object"))]
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let mut scope = Scope::new();
let my_json: String = serde_json::to_string(&json!({
"age": 19
}))
.unwrap();
// Todo: we should check if it's object
engine.eval_with_scope::<()>(&mut scope, &format!("let my_json = #{}", my_json))?;
let result = engine.eval_with_scope::<bool>(&mut scope, "my_json.age > 18")?;
println!("result: {}", result);
Ok(())
}
is there a better way to bring an user-defined serde_json::Value
into rhai::Scope
?
Concat the JSON into a script is the easiest way, but the problem being you have to create a different script text during each call. I'd suggest making it constant:
let script = format!("const my_json = #{}; my_json.age > 18", my_json);
let result = engine.eval::<bool>(&script)?;
If you want to keep the object in Scope
, you have to create a Map
(basically a HashMap<String, Dynamic>
) for it. This way you don't have to create a new script during each call, and you can reuse the Map
object for other calls. The helper method I just put in the PR will be easiest:
let mut scope = Scope::new();
let map = engine.parse_json(&my_json, false); // true if you need null's
scope.push_constant("my_json", map);
let result = engine.eval_with_scope::<bool>(&mut scope, "my_json.age > 18")?;
If you want to use the existing crate, you can simulate parse_json
by:
my_json.insert('#', 0);
let map = eval_expression::<Map>(&my_json)?;
@GopherJ I don't think it's possible to add serde_json
into rhai
, the parse_json
method is enough.
@schungx Thank you for the awesome work, I will test your pull request now.
@xcaptain we can bring serde_json::Value
into rhai
, I prefer it because I don't want users to write json manually.
Or maybe rhai
can add the support:
allow any data which has implemented serde::Serialize to be added into scope
@schungx Is it possible to add a feature in rhai which is called: serde
?
And in this feature, it allows users to bring Serializable value into scope. Sure we should also consider rhai::Array
not only object map.
Because now it seems we need to serialize the given data (I don't want users to write json manually), and then convert it to rhai
json by adding #
prefix, and then we need to call parse_json
for having a map and push it to Scope.
@xcaptain on our side I think we should define a new trait ToRhai
, we let enforce function accept ToRhai
.
Then we do the implementation for &'static str
or String
so that users can pass &'static str
or String
, and we also implement ToRhai
for all types which have implemented serde::ser::Serialize
.
fn enforce<T: ToRhai>(rvals: &[T])
trait ToRhai {
fn to_rhai(self) -> String;
}
impl ToRhai for String
impl<T> ToRhai for T where T: Serialize
maybe we'll get conflicts because String
has implemented also Serialize
but I want that they can have different results. Because for normal string we shouldn't add #
prefix, but for other serializable types we should add #
prefix
#![feature(specialization)]
will help
I tried the following code but it seems doesn't compile:
#![feature(specialization)]
use serde::Serialize;
trait ToRhai {
fn to_rhai(&self) -> String;
}
impl ToRhai for String {
fn to_rhai(&self) -> String {
self.to_owned()
}
}
impl<T> ToRhai for T
where
T: serde::ser::Serialize,
{
default fn to_rhai(&self) -> String {
format!("#{}", serde_json::to_string(self).unwrap())
}
}
#[derive(Serialize)]
struct MyJson {
age: usize,
}
fn enforce<S: ToRhai>(rvals: &[S]) {
let rvals: Vec<String> = rvals.iter().map(|x| x.to_rhai()).collect();
println!("{:?}", rvals);
}
fn main() {
let j = MyJson { age: 18 };
enforce(&["alice".to_owned(), j, "read".to_owned()]);
}
@schungx I tried parse_json
and it worked well :smile: .
https://github.com/xcaptain/casbin-rs-1/commit/77c41d1b3b6452268577e81d39d7f74d8c608ea3
@GopherJ You idea looks well, I will give it a try.
it works after switching to dynamic dispatcher:
#![feature(specialization)]
use serde::Serialize;
trait ToRhai {
fn to_rhai(&self) -> String;
}
impl ToRhai for String {
fn to_rhai(&self) -> String {
self.to_owned()
}
}
impl<T> ToRhai for T
where
T: serde::ser::Serialize,
{
default fn to_rhai(&self) -> String {
format!("#{}", serde_json::to_string(self).unwrap())
}
}
#[derive(Serialize)]
struct MyJson {
age: usize,
}
fn enforce(rvals: &[Box<dyn ToRhai>]) {
let rvals: Vec<String> = rvals.iter().map(|x| x.to_rhai()).collect();
println!("{:?}", rvals);
}
fn main() {
let j = MyJson { age: 18 };
enforce(&[
Box::new("alice".to_owned()),
Box::new(j),
Box::new("read".to_owned()),
]);
}
@schungx I tried
parse_json
and it worked well.
If you're not going to keep the object around (possibly to evaluate many expressions with that single object), it might be faster simply to splice the JSON into the beginning of the expression text using the const my_json = #...
template. This way, you simply copy and merge the text once, but you avoid the copying of the object, plus all the values inside (which may be strings, etc.).
@GopherJ my suggestion: keep enforce
taking strings. It seems to be a very heavy price to pay to add an additional level of indirection, plus all the boxing and allocations, just to support objects. Especially when you're gonna transparently convert them into JSON text strings anyway!
Therefore, it would be much better to have enforce
take a slice of an enum, which contains either a wrapped &str
or a wrapped Box<dyn Serialize>
- then you only pay the price of indirection if your users use objects.
Finally, I'm quite sure it is OK if enforce
takes only strings, and your user needs to manually serialize to JSON (perhaps with some form of marker to indicate that it is JSON instead of simple text). Then you don't have to depend on serde_json
at all. It is easy for whoever wants to use objects to serialize to JSON.
@schungx Yes we decided to go this way to support just Strings, otherwise we need to bring many overhead like nightly, serde, Box....
closed as #102
Current we are matching directly over object, means subject can act on object, with ABAC we can do matching like: subject can act on object is this object has specific attributes.
ABAC doc
This problem may be a little hard because we are using
Vec<&str>
as the parameter forenforce
, which requires every element in the vector has the same type that is&str
. I have 2 ideas about this issue.Vec<&str>
to tuple, so we don't need to worry about the type.obj
as a string, parsing this string into a struct inrhai
then do the matching.