use cedar_policy::*;
use std::{collections::BTreeSet, error::Error, str::FromStr};
#[test]
fn authorize_custom_request() -> Result<(), Box<dyn Error>> {
let auth = Authorizer::new();
let mut policies = PolicySet::from_str(
r#"
forbid(principal,action,resource)
when{ context has suspicion };
permit(
principal == Account::"jane",
action,
resource in Album::"jane_vacation"
);
"#,
)?;
let alice_view = Policy::parse(
Some(PolicyId::new("added policy")),
r#"
permit(
principal == User::"alice",
action == Action::"view",
resource == Photo::"VacationPhoto94.jpg"
);"#,
)?;
let alice_view_id = alice_view.id().clone();
policies.add(alice_view)?;
let entity_json = r#"
[
{
"uid": {
"__entity": {
"type": "User",
"id": "alice"
}
},
"attrs": {},
"parents": [
{
"__entity": {
"type": "UserGroup",
"id": "jane_friends"
}
}
]
},
{
"uid": {
"__entity": {
"type": "UserGroup",
"id": "jane_friends"
}
},
"attrs": {},
"parents": []
},
{
"uid": {
"__entity": {
"type": "Action",
"id": "view"
}
},
"attrs": {},
"parents": []
},
{
"uid": {
"__entity": {
"type": "Photo",
"id": "VacationPhoto94.jpg"
}
},
"attrs": {},
"parents": [
{
"__entity": {
"type": "Album",
"id": "jane_vacation"
}
}
]
},
{
"uid": {
"__entity": {
"type": "Album",
"id": "jane_vacation"
}
},
"attrs": {},
"parents": [
{
"__entity": {
"type": "Account",
"id": "jane"
}
}
]
},
{
"uid": {
"__entity": {
"type": "Account",
"id": "jane"
}
},
"attrs": {},
"parents": []
}
]
"#;
let entities = Entities::from_json_str(entity_json, None)?;
let principal = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("User").unwrap(),
EntityId::from_str("alice").unwrap(),
);
let action = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("Action").unwrap(),
EntityId::from_str("view").unwrap(),
);
let resource = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("Photo").unwrap(),
EntityId::from_str("VacationPhoto94.jpg").unwrap(),
);
let context = Context::from_pairs([
(
"host_os".to_string(),
RestrictedExpression::from_str(r#""Windows 10""#)?,
),
(
"suspicion".to_string(),
RestrictedExpression::from_str("4")?,
),
])
.unwrap();
let request = Request::new(
principal.clone(),
action.clone(),
resource.clone(),
context,
None,
)
.unwrap();
assert_eq!(
auth.is_authorized(&request, &policies, &entities)
.decision(),
Decision::Deny
);
let request2 = Request::new(
principal,
action.clone(),
resource.clone(),
Context::empty(),
None,
)
.unwrap();
assert_eq!(
auth.is_authorized(&request2, &policies, &entities),
Response::new(Decision::Allow, [alice_view_id].into(), Vec::new())
);
let principal = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("Account").unwrap(),
EntityId::from_str("jane").unwrap(),
);
let request3 = Request::new(
principal.clone(),
r#"Ghost::"ghost""#.parse().unwrap(),
resource.clone(),
Context::empty(),
None,
)
.unwrap();
assert_eq!(
auth.is_authorized(&request3, &policies, &entities)
.decision(),
Decision::Allow
);
let request4 = Request::new(
r#"Ghost::"ghost""#.parse().unwrap(),
action.clone(),
resource,
Context::empty(),
None,
)
.unwrap();
assert_eq!(
auth.is_authorized(&request4, &policies, &entities)
.decision(),
Decision::Deny
);
let request5 = Request::new(
principal,
action,
r#"Ghost::"ghost""#.parse().unwrap(),
Context::empty(),
None,
)
.unwrap();
assert_eq!(
auth.is_authorized(&request5, &policies, &entities)
.decision(),
Decision::Deny
);
let result = eval_expression(&request2, &entities, &Expression::from_str(r#"10 < 100"#)?)?;
assert_eq!(result, EvalResult::Bool(true));
Ok(())
}
#[test]
fn expression_eval_1() -> Result<(), Box<dyn Error>> {
let entity_json = r#"[ ]"#;
let entities = Entities::from_json_str(entity_json, None)?;
let principal = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("User").unwrap(),
EntityId::from_str("alice").unwrap(),
);
let action = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("Action").unwrap(),
EntityId::from_str("view").unwrap(),
);
let resource = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("Photo").unwrap(),
EntityId::from_str("trip.jpg").unwrap(),
);
let request = Request::new(principal, action, resource, Context::empty(), None).unwrap();
let result = eval_expression(
&request,
&entities,
&Expression::from_str("if 301 > 10 then 100 else 200")?,
)?;
assert_eq!(result, EvalResult::Long(100));
Ok(())
}
#[test]
fn expression_eval_attr() -> Result<(), Box<dyn Error>> {
let entity_json = r#"[
{
"uid": { "__entity" : { "type" : "User", "id" : "alice" } },
"attrs": {"age":19},
"parents": []
}
]"#;
let entities = Entities::from_json_str(entity_json, None)?;
let principal = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("User").unwrap(),
EntityId::from_str("alice").unwrap(),
);
let action = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("Action").unwrap(),
EntityId::from_str("view").unwrap(),
);
let resource = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("Photo").unwrap(),
EntityId::from_str("trip.jpg").unwrap(),
);
let request = Request::new(principal, action, resource, Context::empty(), None).unwrap();
let result = eval_expression(
&request,
&entities,
&Expression::from_str("if principal.age > 18 then 100 else 200")?,
)?;
assert_eq!(result, EvalResult::Long(100));
Ok(())
}
#[test]
fn expression_eval_context() -> Result<(), Box<dyn Error>> {
let entity_json = r#"[
{
"uid": { "__entity" : { "type" : "User", "id" : "alice" }},
"attrs": {"age":19},
"parents": []
}
]"#;
let entities = Entities::from_json_str(entity_json, None)?;
let principal = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("User").unwrap(),
EntityId::from_str("alice").unwrap(),
);
let action = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("Action").unwrap(),
EntityId::from_str("view").unwrap(),
);
let resource = EntityUid::from_type_name_and_id(
EntityTypeName::from_str("Photo").unwrap(),
EntityId::from_str("trip.jpg").unwrap(),
);
let context = Context::from_pairs([
(
"location".to_string(),
RestrictedExpression::from_str(r#""VA""#)?,
),
(
"suspicion".to_string(),
RestrictedExpression::from_str("4")?,
),
])
.unwrap();
let request = Request::new(principal, action, resource, context, None).unwrap();
let result = eval_expression(
&request,
&entities,
&Expression::from_str(
"if principal.age > 18 && context.location == \"VA\" then 100 else 200",
)?,
)?;
assert_eq!(result, EvalResult::Long(100));
Ok(())
}
#[test]
fn policy_annotations() {
let p: Policy = r#"@anno("good annotation")permit(principal, action, resource);"#
.parse()
.unwrap();
assert_eq!(p.annotation("anno"), Some("good annotation"));
assert_eq!(p.annotations().next(), Some(("anno", "good annotation")));
let t: Template =
r#"@tanno("good annotation")permit(principal == ?principal, action, resource);"#
.parse()
.unwrap();
let t = t.new_id(PolicyId::new("new_template_id"));
assert_eq!(t.annotation("tanno"), Some("good annotation"));
assert_eq!(t.annotations().next(), Some(("tanno", "good annotation")));
let pid = p.id().clone();
let tid = t.id().clone();
let mut s = PolicySet::new();
s.add(p).unwrap();
s.add_template(t).unwrap();
assert_eq!(s.annotation(&pid, "anno"), Some("good annotation"));
assert_eq!(
s.template_annotation(&tid, "tanno"),
Some("good annotation")
);
}
#[test]
fn policy_annotation_without_value() {
let p: Policy = r#"@anno permit(principal, action, resource);"#.parse().unwrap();
assert_eq!(p.annotation("anno"), Some(""));
assert_eq!(p.annotations().next(), Some(("anno", "")));
let t: Template = r#"@tanno permit(principal == ?principal, action, resource);"#
.parse()
.unwrap();
let t = t.new_id(PolicyId::new("new_template_id"));
assert_eq!(t.annotation("tanno"), Some(""));
assert_eq!(t.annotations().next(), Some(("tanno", "")));
let pid = p.id().clone();
let tid = t.id().clone();
let mut s = PolicySet::new();
s.add(p).unwrap();
s.add_template(t).unwrap();
assert_eq!(s.annotation(&pid, "anno"), Some(""));
assert_eq!(s.template_annotation(&tid, "tanno"), Some(""));
}
#[test]
fn change_ids() {
let ps: PolicySet = r#"
@id("first")
permit(principal, action, resource);
@id("second")
permit(principal, action, resource);
"#
.parse()
.unwrap();
let mut new_ps = PolicySet::new();
for p in ps
.policies()
.map(|p| p.new_id(p.annotation("id").unwrap().parse().unwrap()))
{
new_ps.add(p).expect("valid policy choice");
}
assert!(new_ps.policy(&"first".parse().unwrap()).is_some());
}
#[test]
fn get_valid_request_pars_tests() {
let policy = Policy::from_str(
r#"
@id("E1,E2 a,a2 R2")
permit (principal, action, resource is NS::R2);
"#,
)
.unwrap();
let schema = Schema::from_cedarschema_str(
r#"
namespace NS {
entity E;
entity R1 in [R] = {"p1": String};
entity R;
entity R2 in [R] = {"p1": Long};
entity E1 in [E] = {"p1": String};
entity E2 in [E] = {"p1": Long};
action "as";
action "a" in [Action::"as"] appliesTo {
principal: [E1, E2],
resource: [R1, R2],
context: {"c1": Long}
};
action "a1" in [Action::"as"] appliesTo {
principal: [E1],
resource: [R1],
context: {"c1": Long}
};
action "a2" in [Action::"as"] appliesTo {
principal: [E2],
resource: [R2],
context: {"c1": Long}
};
}
"#,
)
.unwrap()
.0;
assert_eq!(
BTreeSet::from_iter(policy.get_valid_request_envs(&schema)),
BTreeSet::from_iter([
RequestEnv::new(
"NS::E1".parse().unwrap(),
"NS::Action::\"a\"".parse().unwrap(),
"NS::R2".parse().unwrap()
),
RequestEnv::new(
"NS::E2".parse().unwrap(),
"NS::Action::\"a\"".parse().unwrap(),
"NS::R2".parse().unwrap()
),
RequestEnv::new(
"NS::E2".parse().unwrap(),
"NS::Action::\"a2\"".parse().unwrap(),
"NS::R2".parse().unwrap()
),
])
);
let policy = Policy::from_str(
r#"
@id("E1,E2 a,a2 R2")
permit (principal, action, resource is NS::R3);
"#,
)
.unwrap();
assert_eq!(
BTreeSet::from_iter(policy.get_valid_request_envs(&schema)),
BTreeSet::new(),
);
}