mod common;
use std::sync::Arc;
use typesec_core::{
PolicyResult, RequestContext,
combinator::{CombineStrategy, PolicyEngineBuilder},
};
use typesec_odrl::OdrlEngine;
use typesec_rbac::RbacEngine;
use common::{
ODRL_ALLOW_SHARED_READ, ODRL_PERMIT_READ, ODRL_PROHIBIT_READ, ODRL_PROHIBIT_TRAINING_READ,
RBAC_ALLOW_READ, RBAC_NO_RULES, check_engine, check_engine_with_context,
};
#[tokio::test]
async fn combinator_deny_overrides() {
let rbac: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(RbacEngine::from_yaml(RBAC_ALLOW_READ).expect("rbac"));
let odrl: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(OdrlEngine::from_yaml(ODRL_PROHIBIT_READ).expect("odrl"));
let composed = PolicyEngineBuilder::new()
.add_engine(rbac)
.add_engine(odrl)
.strategy(CombineStrategy::DenyOverrides)
.build();
let result = check_engine(&composed, "agent:combinator", "read", "shared/data");
assert!(
matches!(result, PolicyResult::Deny(_)),
"DenyOverrides must yield Deny when any engine prohibits: {result:?}"
);
}
#[tokio::test]
async fn combinator_allow_if_any() {
let rbac: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(RbacEngine::from_yaml(RBAC_NO_RULES).expect("rbac"));
let odrl: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(OdrlEngine::from_yaml(ODRL_PERMIT_READ).expect("odrl"));
let composed = PolicyEngineBuilder::new()
.add_engine(rbac)
.add_engine(odrl)
.strategy(CombineStrategy::AllowIfAny)
.build();
let result = check_engine(&composed, "agent:odrl-only", "read", "private/data");
assert_eq!(
result,
PolicyResult::Allow,
"AllowIfAny must yield Allow when at least one engine permits: {result:?}"
);
}
#[tokio::test]
async fn deny_overrides_preserves_odrl_request_context() {
let rbac: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(RbacEngine::from_yaml(RBAC_ALLOW_READ).expect("rbac"));
let odrl: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(OdrlEngine::from_yaml(ODRL_PROHIBIT_TRAINING_READ).expect("odrl"));
let composed = PolicyEngineBuilder::new()
.add_engine(rbac)
.add_engine(odrl)
.strategy(CombineStrategy::DenyOverrides)
.build();
let ctx = RequestContext::default().with_purpose("training");
let result =
check_engine_with_context(&composed, "agent:combinator", "read", "shared/data", &ctx);
assert!(
matches!(result, PolicyResult::Deny(_)),
"ODRL purpose prohibition should override RBAC allow"
);
}
#[tokio::test]
async fn priority_order_delegates_to_rbac_allow() {
let odrl: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(OdrlEngine::from_yaml(ODRL_PERMIT_READ).expect("odrl"));
let rbac: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(RbacEngine::from_yaml(RBAC_ALLOW_READ).expect("rbac"));
let composed = PolicyEngineBuilder::new()
.add_engine(odrl)
.add_engine(rbac)
.strategy(CombineStrategy::PriorityOrder)
.build();
let result = check_engine(&composed, "agent:combinator", "read", "shared/data");
assert_eq!(result, PolicyResult::Allow);
}
#[tokio::test]
async fn allow_if_all_requires_both_engines_to_allow() {
let rbac: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(RbacEngine::from_yaml(RBAC_ALLOW_READ).expect("rbac"));
let odrl: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(OdrlEngine::from_yaml(ODRL_ALLOW_SHARED_READ).expect("odrl"));
let both_allow = PolicyEngineBuilder::new()
.add_engine(rbac)
.add_engine(odrl)
.strategy(CombineStrategy::AllowIfAll)
.build();
assert_eq!(
check_engine(&both_allow, "agent:combinator", "read", "shared/data"),
PolicyResult::Allow
);
let rbac_deny: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(RbacEngine::from_yaml(RBAC_NO_RULES).expect("rbac"));
let odrl_allow: Arc<dyn typesec_core::PolicyEngine> =
Arc::new(OdrlEngine::from_yaml(ODRL_PERMIT_READ).expect("odrl"));
let one_denies = PolicyEngineBuilder::new()
.add_engine(rbac_deny)
.add_engine(odrl_allow)
.strategy(CombineStrategy::AllowIfAll)
.build();
assert!(matches!(
check_engine(&one_denies, "agent:odrl-only", "read", "private/data"),
PolicyResult::Deny(_)
));
}