| Crates.io | allow-me |
| lib.rs | allow-me |
| version | 0.1.1 |
| created_at | 2021-01-08 00:39:55.85486+00 |
| updated_at | 2021-01-08 02:18:18.870228+00 |
| description | An authorization library with json-based policy definition. |
| homepage | |
| repository | https://github.com/vadim-kovalyov/allow-me.git |
| max_upload_size | |
| id | 334037 |
| size | 89,009 |
An authorization library with json-based policy definition.
Define your authorization rules in a simple Identity (I), Operation (O), Resource (R) model. Evaluate requests against your policy rules.
[dependencies]
allow-me = "0.1"
A simple example for a policy with one statement and a request evaluated against that policy.
let json = r#"{
"statements": [
{
"effect": "allow",
"identities": [
"actor_a"
],
"operations": [
"write"
],
"resources": [
"resource_1"
]
}
]
}"#;
// Construct the policy.
let policy = PolicyBuilder::from_json(json).build()?;
// Prepare request (e.g. from user input).
let request = Request::new("actor_a", "write", "resource_1")?;
// Evaluate the request.
match policy.evaluate(&request)? {
Decision::Allowed => println!("Allowed"),
Decision::Denied => {
panic!("Denied!")
}
};
cargo run --example json
The following example shows a rule that allows any identity to read/write to it's own resource.
let json = r#"{
"statements": [
{
"effect": "allow",
"identities": [
"{{any}}"
],
"operations": [
"read",
"write"
],
"resources": [
"/home/{{identity}}/"
]
}
]
}"#;
// Construct the policy.
let policy = PolicyBuilder::from_json(json)
// use "starts with" matching for resources.
.with_matcher(matcher::StartsWith)
.with_default_decision(Decision::Denied)
.build()?;
// Prepare request (e.g. from user input).
let request = Request::new("johndoe", "write", "/home/johndoe/my.resource")?;
// Evaluate the request.
match policy.evaluate(&request)? {
Decision::Allowed => println!("Allowed"),
Decision::Denied => {
panic!("Denied!")
}
};
cargo run --example vars
Order of rules matter. In case of conflicting rules, the first rule wins. In the example below, we allow actor_a write to resource_1, and deny write to anything else. Note that any other request will be allowed (default decision).
let json = r#"{
"statements": [
{
"effect": "allow",
"identities": [
"actor_a"
],
"operations": [
"write"
],
"resources": [
"resource_1"
]
},
{
"effect": "deny",
"identities": [
"actor_a"
],
"operations": [
"write"
],
"resources": [
"{{any}}"
]
}
]
}"#;
// Construct the policy.
let policy = PolicyBuilder::from_json(json)
// default to Allow all requests.
.with_default_decision(Decision::Allowed)
.build()?;
// Prepare request (e.g. from user input).
let request = Request::new("actor_a", "write", "resource_1")?;
// Evaluate specific request.
match policy.evaluate(&request)? {
Decision::Allowed => println!("allowed write resource_1"),
Decision::Denied => {
panic!("Denied!")
}
};
let request = Request::new("actor_a", "write", "some_other_resource")?;
// Everything else denies.
assert_matches!(policy.evaluate(&request), Ok(Decision::Denied));
cargo run --example order
There are several extension points in the library:
ResourceMatcher trait - responsible for performing resource matching logic.Substituter trait - you can add custom variables that can be substituted.Validator trait - validates policy definition. If your need custom validation for policy rules.Request. Useful with custom Substituter or ResourceMatcher to implement custom variables or matching logic.Custom ResourceMatcher that implements "start with" matching.
pub struct StartsWith;
impl ResourceMatcher for StartsWith {
type Context = ();
fn do_match(&self, _context: &Request<Self::Context>, input: &str, policy: &str) -> bool {
input.starts_with(policy)
}
}
Custom Substituter that supports {{any}} and {{role}} variables. {{role}} variable substituted with a value from a request context.
// custom context
struct MyContext {
role: String
};
// custom substituter
struct RoleSubstituter;
impl Substituter for RoleSubstituter {
type Context = MyContext;
fn visit_resource(&self, value: &str, context: &Request<Self::Context>) -> Result<String> {
match context.context() {
Some(role_context) => {
let mut result = value.to_owned();
for variable in VariableIter::new(value) {
result = match variable {
"{{any}}" => replace(&result, variable, context.resource()),
"{{role}}" => replace(&result, variable, &role_context.role),
_ => result,
};
}
Ok(result)
}
None => Ok(value.to_owned()),
}
}
...
}
cargo run --example customizations
All contributions and comments are welcome! Don't be afraid to open an issue or PR whenever you find a bug or have an idea to improve this crate.