1use treetop_core::{
4 Decision, EvaluationSession, LabelRegistry, PolicyCandidates, PolicyEngine, PolicyError,
5 PolicyStoreId, PolicyVersion, Request, RequestContext, SchemaEnforcing, SchemaFree,
6};
7
8#[derive(Clone)]
20pub enum PreparedEngine {
21 SchemaFree(PolicyEngine<SchemaFree>),
23 SchemaEnforcing(PolicyEngine<SchemaEnforcing>),
25}
26
27#[derive(Clone)]
29pub enum PreparedEvaluationSession {
30 SchemaFree(EvaluationSession<SchemaFree>),
32 SchemaEnforcing(EvaluationSession<SchemaEnforcing>),
34}
35
36impl From<PolicyEngine<SchemaFree>> for PreparedEngine {
37 fn from(engine: PolicyEngine<SchemaFree>) -> Self {
38 Self::SchemaFree(engine)
39 }
40}
41
42impl From<PolicyEngine<SchemaEnforcing>> for PreparedEngine {
43 fn from(engine: PolicyEngine<SchemaEnforcing>) -> Self {
44 Self::SchemaEnforcing(engine)
45 }
46}
47
48impl PreparedEngine {
49 pub fn with_label_registry(self, registry: LabelRegistry) -> Self {
51 match self {
52 Self::SchemaFree(engine) => Self::SchemaFree(engine.with_label_registry(registry)),
53 Self::SchemaEnforcing(engine) => {
54 Self::SchemaEnforcing(engine.with_label_registry(registry))
55 }
56 }
57 }
58
59 pub fn session(&self) -> PreparedEvaluationSession {
61 match self {
62 Self::SchemaFree(engine) => PreparedEvaluationSession::SchemaFree(engine.session()),
63 Self::SchemaEnforcing(engine) => {
64 PreparedEvaluationSession::SchemaEnforcing(engine.session())
65 }
66 }
67 }
68 pub fn current_version(&self) -> PolicyVersion {
70 match self {
71 Self::SchemaFree(engine) => engine.current_version(),
72 Self::SchemaEnforcing(engine) => engine.current_version(),
73 }
74 }
75
76 pub fn policy_store_ids(&self) -> Option<Vec<PolicyStoreId>> {
78 match self {
79 Self::SchemaFree(engine) => engine.policy_store_ids(),
80 Self::SchemaEnforcing(engine) => engine.policy_store_ids(),
81 }
82 }
83
84 pub fn list_policies_for_user(
88 &self,
89 user: &str,
90 groups: &[&str],
91 namespace: &[&str],
92 ) -> Result<PolicyCandidates, PolicyError> {
93 match self {
94 Self::SchemaFree(engine) => engine.list_policies_for_user(user, groups, namespace),
95 Self::SchemaEnforcing(engine) => engine.list_policies_for_user(user, groups, namespace),
96 }
97 }
98
99 pub fn evaluate(&self, request: &Request) -> Result<Decision, PolicyError> {
101 match self {
102 Self::SchemaFree(engine) => engine.evaluate(request),
103 Self::SchemaEnforcing(engine) => engine.evaluate(request),
104 }
105 }
106
107 pub fn evaluate_with_context(
109 &self,
110 request: &Request,
111 context: &RequestContext,
112 ) -> Result<Decision, PolicyError> {
113 match self {
114 Self::SchemaFree(engine) => engine.evaluate_with_context(request, context),
115 Self::SchemaEnforcing(engine) => engine.evaluate_with_context(request, context),
116 }
117 }
118}
119
120impl PreparedEvaluationSession {
121 pub fn version(&self) -> PolicyVersion {
123 match self {
124 Self::SchemaFree(engine) => engine.version(),
125 Self::SchemaEnforcing(engine) => engine.version(),
126 }
127 }
128
129 pub fn evaluate(&self, request: &Request) -> Result<Decision, PolicyError> {
131 match self {
132 Self::SchemaFree(engine) => engine.evaluate(request),
133 Self::SchemaEnforcing(engine) => engine.evaluate(request),
134 }
135 }
136
137 pub fn evaluate_with_context(
139 &self,
140 request: &Request,
141 context: &RequestContext,
142 ) -> Result<Decision, PolicyError> {
143 match self {
144 Self::SchemaFree(engine) => engine.evaluate_with_context(request, context),
145 Self::SchemaEnforcing(engine) => engine.evaluate_with_context(request, context),
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use treetop_core::{Action, Principal, Resource, User};
154
155 #[test]
156 fn session_retains_policy_generation_after_reload() {
157 let core = PolicyEngine::new_from_str("permit(principal, action, resource);").unwrap();
158 let prepared = PreparedEngine::from(core.clone());
159 let session = prepared.session();
160 let request = Request {
161 principal: Principal::User(User::new("alice", None, None).unwrap()),
162 action: Action::new("read", None).unwrap(),
163 resource: Resource::new("Document", "one").unwrap(),
164 };
165 core.reload_from_str("forbid(principal, action, resource);")
166 .unwrap();
167 let old = session.evaluate(&request).unwrap();
168 assert!(old.is_allowed());
169 assert_eq!(old.version(), &session.version());
170 assert!(!prepared.evaluate(&request).unwrap().is_allowed());
171 assert!(prepared.current_version().generation > session.version().generation);
172 }
173
174 #[test]
175 fn schema_enforcing_variant_retains_validation() {
176 let schema = r#"entity User; entity Document; action "read" appliesTo {
177 principal: [User], resource: [Document], context: {}
178 };"#;
179 let core = PolicyEngine::new_from_str_with_cedarschema(
180 "permit(principal, action, resource);",
181 schema,
182 )
183 .unwrap();
184 let prepared = PreparedEngine::from(core);
185 assert!(matches!(prepared, PreparedEngine::SchemaEnforcing(_)));
186 let request = Request {
187 principal: Principal::User(User::new("alice", None, None).unwrap()),
188 action: Action::new("unknown", None).unwrap(),
189 resource: Resource::new("Document", "one").unwrap(),
190 };
191 assert!(prepared.session().evaluate(&request).is_err());
192 }
193}