treetop-core 0.0.17

Core library for Treetop, a Cedar policy engine implementation.
Documentation
use std::str::FromStr;

use super::*;
use crate::labels::{LabelRegistryBuilder, RegexLabeler};
use crate::snapshot_decision;
use crate::types::AttrValue;
use crate::types::{Decision::Allow, Decision::Deny, Group, Resource};
use crate::{Action, PolicyEffectFilter, PolicyMatchReason, RequestContext, User};
use cedar_policy::{EntityUid, Schema};
use regex::Regex;
use yare::parameterized;

mod reload;
mod schema;

macro_rules! snapshot_decision_engine {
    ($decision:expr) => {{
        let mut settings = insta::Settings::clone_current();
        settings.set_snapshot_path("../../snapshots");
        settings.bind(|| {
            snapshot_decision!($decision);
        });
    }};
}

const TEST_POLICY: &str = r#"
permit (
    principal == User::"alice",
    action in [Action::"view", Action::"edit", Action::"delete"],
    resource == Photo::"VacationPhoto94.jpg"
);

permit (
    principal == User::"bob",
    action == Action::"view",
    resource == Photo::"VacationPhoto94.jpg"
);
"#;

const TEST_POLICY_WITHOUT_BOB: &str = r#"
permit (
    principal == User::"alice",
    action in [Action::"view", Action::"edit", Action::"delete"],
    resource == Photo::"VacationPhoto94.jpg"
);
"#;

const TEST_POLICY_WITH_CONTEXT: &str = r#"
permit (
    principal == User::"alice",
    action == Action::"create_host",
    resource is Host
) when {
    resource.name like "web*" &&
    resource.ip.isInRange(ip("192.0.1.0/24"))
};

permit (
    principal == User::"bob",
    action == Action::"create_host",
    resource is Host
) when {
    resource.name like "bob*" &&
    resource.ip.isInRange(ip("192.0.0.0/24"))
};
"#;

const TEST_PERMISSION_POLICY: &str = r#"
permit (
    principal == User::"alice",
    action in [Action::"view", Action::"edit", Action::"delete"],
    resource == Photo::"VacationPhoto94.jpg"
);

permit (
    principal == User::"alice",
    action == Action::"create_host",
    resource is Host
);

permit (
    principal == User::"bob",
    action == Action::"view",
    resource == Photo::"VacationPhoto94.jpg"
);
"#;

const TEST_POLICY_WITH_FORBID: &str = r#"
permit (
    principal == User::"alice",
    action in [Action::"view", Action::"edit", Action::"delete"],
    resource == Photo::"VacationPhoto94.jpg"
);
forbid (
    principal == User::"alice",
    action == Action::"edit",
    resource == Photo::"VacationPhoto94.jpg"
);
forbid (
    principal,
    action == Action::"delete",
    resource == Photo::"VacationPhoto94.jpg"
);
"#;

const TEST_POLICY_WITH_HOST_PATTERNS: &str = r#"
permit (
    principal == User::"alice",
    action == Action::"create_host",
    resource is Host
) when {
    resource.nameLabels.contains("example_domain")
};

permit (
    principal == User::"bob",
    action == Action::"create_host",
    resource is Host
) when {
    resource.nameLabels.contains("valid_web_name") &&
    resource.nameLabels.contains("example_domain")
};
"#;

const TEST_POLICY_ACTION_ONLY_HERE: &str = r#"
permit (
    principal == User::"alice",
    action == Action::"only_here",
    resource
);
"#;

const TEST_POLICY_GENERIC_RESOURCE: &str = r#"
permit (
    principal == User::"alice",
    action == Action::"assign_gateway",
    resource is Gateway
) when {
    resource.id == "mygateway"
};
"#;

const TEST_POLICY_WITH_GROUPS: &str = r#"
permit (
    principal in Group::"admins",
    action in [Action::"delete", Action::"view"],
    resource is Photo
);

permit (
    principal in Group::"users",
    action == Action::"view",
    resource is Photo
);
"#;

const TEST_POLICY_BY_ID: &str = r#"
@id("id_of_policy")
permit (
    principal == User::"alice",
    action, 
    resource
);
"#;

const TEST_POLICY_WITH_NAMESPACES: &str = r#"
permit (
    principal == Database::User::"alice",
    action in [Database::Action::"create_table", Database::Action::"view_table"],
    resource is Database::Table
);

permit (
    principal in Database::Group::"dbusers",
    action == Database::Action::"view_table",
    resource is Database::Table
);

permit (
    principal in Furniture::Group::"carpenters",
    action == Furniture::Action::"create_table",
    resource is Furniture::Table
);
"#;

const TEST_POLICY_WITH_IP: &str = r#"
permit (
    principal == User::"alice",
    action == Action::"create_host",
    resource is Host
) when {
    resource.ip.isInRange(ip("192.168.0.0/24"))
};
"#;

const TEST_POLICY_WITH_IS_AND_ISIN: &str = r#"
permit (
    principal is User,
    action == Action::"read",
    resource
);

permit (
    principal is User in Group::"admins",
    action == Action::"write",
    resource
);

permit (
    principal is Group,
    action == Action::"group_read",
    resource
);

permit (
    principal is Group in Group::"admins",
    action == Action::"group_write",
    resource
);
"#;

const TEST_POLICY_WITH_RESOURCE_CONSTRAINTS: &str = r#"
permit (
    principal,
    action == Action::"view",
    resource is Photo
);
permit (
    principal,
    action == Action::"edit",
    resource == Photo::"vacation.jpg"
);
permit (
    principal,
    action == Action::"create",
    resource is Host
);
"#;

const TEST_SCHEMA: &str = r#"
entity User;
entity Group;
entity Document {
    id: String,
    sensitivity: Long
};

action "read" appliesTo {
    principal: [User],
    resource: [Document],
};
"#;

const TEST_SCHEMA_POLICY: &str = r#"
permit (
    principal == User::"alice",
    action == Action::"read",
    resource is Document
);
"#;

const TEST_SCHEMA_WRITE: &str = r#"
entity User;
entity Group;
entity Document {
    id: String,
    sensitivity: Long
};

action "write" appliesTo {
    principal: [User],
    resource: [Document],
};
"#;

const TEST_SCHEMA_POLICY_WRITE: &str = r#"
permit (
    principal == User::"alice",
    action == Action::"write",
    resource is Document
);
"#;

#[derive(Clone)]
struct SharedLogBuffer(Arc<std::sync::Mutex<Vec<u8>>>);

struct SharedLogWriter(Arc<std::sync::Mutex<Vec<u8>>>);

impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for SharedLogBuffer {
    type Writer = SharedLogWriter;

    fn make_writer(&'a self) -> Self::Writer {
        SharedLogWriter(Arc::clone(&self.0))
    }
}

impl std::io::Write for SharedLogWriter {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.0.lock().unwrap().extend_from_slice(buf);
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

fn engine_from_policy(policy_text: &str) -> PolicyEngine {
    PolicyEngine::new_from_str(policy_text).expect("policy should load")
}

fn schema_engine_from_policy(policy_text: &str, schema_text: &str) -> PolicyEngine {
    PolicyEngine::new_from_str_with_cedarschema(policy_text, schema_text)
        .expect("schema + policy should load")
}

fn user_request(user: &str, action: &str, resource: Resource) -> Request {
    Request {
        principal: Principal::User(User::new(user, None, None)),
        action: Action::new(action, None),
        resource,
    }
}

fn group_request(group: &str, action: &str, resource: Resource) -> Request {
    Request {
        principal: Principal::Group(Group::new(group, None)),
        action: Action::new(action, None),
        resource,
    }
}

fn document_with_sensitivity(id: &str, sensitivity: i64) -> Resource {
    Resource::new("Document", id).with_attr("sensitivity", AttrValue::Long(sensitivity))
}

fn assert_allow(decision: &Decision) {
    assert!(matches!(decision, Decision::Allow { .. }));
}

fn assert_deny(decision: &Decision) {
    assert!(matches!(decision, Decision::Deny { .. }));
}

mod core;
mod evaluate;
mod listing;