use std::collections::HashMap;
use rskit_authz::{
AttributeSource, Checker, Condition, Effect, Engine, Operator, Permission, Policy, Request,
Resource, Role, Subject,
};
use serde_json::json;
fn request(role: &str, action: &str, resource_type: &str) -> Request {
Request {
subject: Subject {
id: String::from("user-1"),
roles: vec![role.to_string()],
attributes: HashMap::new(),
},
resource: Resource {
resource_type: resource_type.to_string(),
id: String::from("resource-1"),
attributes: HashMap::new(),
},
action: action.to_string(),
context: HashMap::new(),
}
}
#[test]
fn engine_supports_role_hierarchy_and_default_deny() {
let engine = Engine::new(
vec![
Role {
name: String::from("viewer"),
inherits: vec![],
permissions: vec![Permission {
resource: String::from("article"),
action: String::from("read"),
conditions: vec![],
}],
},
Role {
name: String::from("editor"),
inherits: vec![String::from("viewer")],
permissions: vec![Permission {
resource: String::from("article"),
action: String::from("write"),
conditions: vec![],
}],
},
],
vec![],
)
.unwrap();
assert!(engine.check(&request("editor", "read", "article")));
assert!(engine.check(&request("editor", "write", "article")));
assert!(!engine.check(&request("editor", "delete", "article")));
}
#[test]
fn explicit_deny_overrides_role_grants() {
let engine = Engine::new(
vec![Role {
name: String::from("editor"),
inherits: vec![],
permissions: vec![Permission {
resource: String::from("article"),
action: String::from("*"),
conditions: vec![],
}],
}],
vec![Policy {
name: String::from("deny-delete"),
effect: Effect::Deny,
actions: vec![String::from("delete")],
resources: vec![String::from("article")],
conditions: vec![],
}],
)
.unwrap();
let allow = engine.authorize(&request("editor", "write", "article"));
let deny = engine.authorize(&request("editor", "delete", "article"));
assert!(allow.allowed);
assert!(!deny.allowed);
assert_eq!(deny.reason, "denied by policy");
}
#[test]
fn abac_rules_compare_attributes() {
let engine = Engine::new(
vec![],
vec![Policy {
name: String::from("tenant-match"),
effect: Effect::Allow,
actions: vec![String::from("read")],
resources: vec![String::from("invoice")],
conditions: vec![Condition {
source: AttributeSource::Subject,
key: String::from("tenant_id"),
operator: Operator::Equals,
values: vec![],
compare_source: Some(AttributeSource::Resource),
compare_key: Some(String::from("tenant_id")),
}],
}],
)
.unwrap();
let mut request = request("ignored", "read", "invoice");
request.subject.roles.clear();
request
.subject
.attributes
.insert(String::from("tenant_id"), json!("acme"));
request
.resource
.attributes
.insert(String::from("tenant_id"), json!("acme"));
assert!(engine.check(&request));
request
.resource
.attributes
.insert(String::from("tenant_id"), json!("other"));
assert!(!engine.check(&request));
}
#[test]
fn object_level_deny_policy_prevents_cross_owner_role_access() {
let engine = Engine::new(
vec![Role {
name: String::from("reader"),
inherits: vec![],
permissions: vec![Permission {
resource: String::from("document"),
action: String::from("read"),
conditions: vec![],
}],
}],
vec![Policy {
name: String::from("deny-cross-owner-read"),
effect: Effect::Deny,
actions: vec![String::from("read")],
resources: vec![String::from("document")],
conditions: vec![Condition {
source: AttributeSource::Resource,
key: String::from("owner_id"),
operator: Operator::NotEquals,
values: vec![],
compare_source: Some(AttributeSource::Subject),
compare_key: Some(String::from("id")),
}],
}],
)
.unwrap();
let mut own_document = request("reader", "read", "document");
own_document.subject.id = String::from("user-1");
own_document
.resource
.attributes
.insert(String::from("owner_id"), json!("user-1"));
assert!(engine.check(&own_document));
let mut other_document = own_document.clone();
other_document
.resource
.attributes
.insert(String::from("owner_id"), json!("user-2"));
let decision = engine.authorize(&other_document);
assert!(!decision.allowed);
assert_eq!(decision.reason, "denied by policy");
}
#[test]
fn object_level_role_permission_conditions_scope_grants() {
let engine = Engine::new(
vec![Role {
name: String::from("reader"),
inherits: vec![],
permissions: vec![Permission {
resource: String::from("document"),
action: String::from("read"),
conditions: vec![Condition {
source: AttributeSource::Resource,
key: String::from("owner_id"),
operator: Operator::Equals,
values: vec![],
compare_source: Some(AttributeSource::Subject),
compare_key: Some(String::from("id")),
}],
}],
}],
vec![],
)
.unwrap();
let mut own_document = request("reader", "read", "document");
own_document.subject.id = String::from("user-1");
own_document
.resource
.attributes
.insert(String::from("owner_id"), json!("user-1"));
assert!(engine.check(&own_document));
let mut other_document = own_document.clone();
other_document
.resource
.attributes
.insert(String::from("owner_id"), json!("user-2"));
let decision = engine.authorize(&other_document);
assert!(!decision.allowed);
assert_eq!(decision.reason, "default deny");
}