1use super::Policy;
18use super::PolicySetFromJsonError;
19use crate::ast::{self, EntityUID, PolicyID, SlotId};
20use crate::entities::json::err::JsonDeserializationErrorContext;
21use crate::entities::json::EntityUidJson;
22use crate::parser::cst::Policies;
23use crate::parser::err::ParseErrors;
24use crate::parser::Node;
25use serde::{Deserialize, Serialize};
26use serde_with::serde_as;
27use std::collections::HashMap;
28
29#[serde_as]
31#[derive(Debug, Clone, Serialize, Deserialize, Default)]
32#[serde(rename_all = "camelCase")]
33#[serde(deny_unknown_fields)]
34pub struct PolicySet {
35 #[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
37 pub templates: HashMap<PolicyID, Policy>,
38 #[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
40 pub static_policies: HashMap<PolicyID, Policy>,
41 pub template_links: Vec<TemplateLink>,
43}
44
45impl PolicySet {
46 pub fn get_policy(&self, id: &PolicyID) -> Option<Policy> {
51 let maybe_static_policy = self.static_policies.get(id).cloned();
52
53 let maybe_link = self
54 .template_links
55 .iter()
56 .filter_map(|link| {
57 if &link.new_id == id {
58 self.get_template(&link.template_id).and_then(|template| {
59 let unwrapped_est_vals: HashMap<SlotId, EntityUidJson> =
60 link.values.iter().map(|(k, v)| (*k, v.into())).collect();
61 template.link(&unwrapped_est_vals).ok()
62 })
63 } else {
64 None
65 }
66 })
67 .next();
68
69 maybe_static_policy.or(maybe_link)
70 }
71
72 pub fn get_template(&self, id: &PolicyID) -> Option<Policy> {
77 self.templates.get(id).cloned()
78 }
79}
80
81#[serde_as]
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85#[serde(deny_unknown_fields)]
86pub struct TemplateLink {
87 pub template_id: PolicyID,
89 pub new_id: PolicyID,
91 #[serde_as(as = "serde_with::MapPreventDuplicates<_,EntityUidJson<TemplateLinkContext>>")]
93 pub values: HashMap<SlotId, EntityUID>,
94}
95
96struct TemplateLinkContext;
98
99impl crate::entities::json::DeserializationContext for TemplateLinkContext {
100 fn static_context() -> Option<JsonDeserializationErrorContext> {
101 Some(JsonDeserializationErrorContext::TemplateLink)
102 }
103}
104
105impl TryFrom<PolicySet> for ast::PolicySet {
106 type Error = PolicySetFromJsonError;
107
108 fn try_from(value: PolicySet) -> Result<Self, Self::Error> {
109 let mut ast_pset = ast::PolicySet::default();
110
111 for (id, policy) in value.templates {
112 let ast = policy.try_into_ast_policy_or_template(Some(id))?;
113 ast_pset.add_template(ast)?;
114 }
115
116 for (id, policy) in value.static_policies {
117 let ast = policy.try_into_ast_policy(Some(id))?;
118 ast_pset.add(ast)?;
119 }
120
121 for TemplateLink {
122 template_id,
123 new_id,
124 values,
125 } in value.template_links
126 {
127 ast_pset.link(template_id, new_id, values)?;
128 }
129
130 Ok(ast_pset)
131 }
132}
133
134impl TryFrom<Node<Option<Policies>>> for PolicySet {
135 type Error = ParseErrors;
136
137 fn try_from(policies: Node<Option<Policies>>) -> Result<Self, Self::Error> {
138 let mut templates = HashMap::new();
139 let mut static_policies = HashMap::new();
140 let mut all_errs: Vec<ParseErrors> = vec![];
141 for (policy_id, policy) in policies.with_generated_policyids()? {
142 match policy.try_as_inner() {
143 Ok(cst) => match Policy::try_from(cst.clone()) {
144 Ok(est) => {
145 if est.is_template() {
146 templates.insert(policy_id, est);
147 } else {
148 static_policies.insert(policy_id, est);
149 }
150 }
151 Err(e) => {
152 all_errs.push(e);
153 }
154 },
155 Err(e) => {
156 all_errs.push(e.into());
157 }
158 };
159 }
160 if let Some(errs) = ParseErrors::flatten(all_errs) {
162 Err(errs)
163 } else {
164 Ok(PolicySet {
165 templates,
166 static_policies,
167 template_links: Vec::new(),
168 })
169 }
170 }
171}
172
173#[cfg(test)]
174mod test {
175 use serde_json::json;
176
177 use super::*;
178
179 #[test]
180 fn valid_example() {
181 let json = json!({
182 "staticPolicies": {
183 "policy1": {
184 "effect": "permit",
185 "principal": {
186 "op": "==",
187 "entity": { "type": "User", "id": "alice" }
188 },
189 "action": {
190 "op": "==",
191 "entity": { "type": "Action", "id": "view" }
192 },
193 "resource": {
194 "op": "in",
195 "entity": { "type": "Folder", "id": "foo" }
196 },
197 "conditions": []
198 }
199 },
200 "templates": {
201 "template": {
202 "effect" : "permit",
203 "principal" : {
204 "op" : "==",
205 "slot" : "?principal"
206 },
207 "action" : {
208 "op" : "all"
209 },
210 "resource" : {
211 "op" : "all",
212 },
213 "conditions": []
214 }
215 },
216 "templateLinks" : [
217 {
218 "newId" : "link",
219 "templateId" : "template",
220 "values" : {
221 "?principal" : { "type" : "User", "id" : "bob" }
222 }
223 }
224 ]
225 });
226
227 let est_policy_set: PolicySet =
228 serde_json::from_value(json).expect("failed to parse from JSON");
229 let ast_policy_set: ast::PolicySet =
230 est_policy_set.try_into().expect("failed to convert to AST");
231 assert_eq!(ast_policy_set.policies().count(), 2);
232 assert_eq!(ast_policy_set.templates().count(), 1);
233 assert!(ast_policy_set
234 .get_template_arc(&PolicyID::from_string("template"))
235 .is_some());
236 let link = ast_policy_set.get(&PolicyID::from_string("link")).unwrap();
237 assert_eq!(link.template().id(), &PolicyID::from_string("template"));
238 assert_eq!(
239 link.env(),
240 &HashMap::from_iter([(SlotId::principal(), r#"User::"bob""#.parse().unwrap())])
241 );
242 assert_eq!(
243 ast_policy_set
244 .get_linked_policies(&PolicyID::from_string("template"))
245 .unwrap()
246 .count(),
247 1
248 );
249 }
250
251 #[test]
252 fn unknown_field() {
253 let json = json!({
254 "staticPolicies": {
255 "policy1": {
256 "effect": "permit",
257 "principal": {
258 "op": "==",
259 "entity": { "type": "User", "id": "alice" }
260 },
261 "action": {
262 "op" : "all"
263 },
264 "resource": {
265 "op" : "all"
266 },
267 "conditions": []
268 }
269 },
270 "templates": {},
271 "links" : []
272 });
273
274 let err = serde_json::from_value::<PolicySet>(json)
275 .expect_err("should have failed to parse from JSON");
276 assert_eq!(
277 err.to_string(),
278 "unknown field `links`, expected one of `templates`, `staticPolicies`, `templateLinks`"
279 );
280 }
281
282 #[test]
283 fn duplicate_policy_ids() {
284 let str = r#"{
285 "staticPolicies" : {
286 "policy0": {
287 "effect": "permit",
288 "principal": {
289 "op": "==",
290 "entity": { "type": "User", "id": "alice" }
291 },
292 "action": {
293 "op" : "all"
294 },
295 "resource": {
296 "op" : "all"
297 },
298 "conditions": []
299 },
300 "policy0": {
301 "effect": "permit",
302 "principal": {
303 "op": "==",
304 "entity": { "type": "User", "id": "alice" }
305 },
306 "action": {
307 "op" : "all"
308 },
309 "resource": {
310 "op" : "all"
311 },
312 "conditions": []
313 }
314 },
315 "templates" : {},
316 "templateLinks" : []
317 }"#;
318 let err = serde_json::from_str::<PolicySet>(str)
319 .expect_err("should have failed to parse from JSON");
320 assert_eq!(
321 err.to_string(),
322 "invalid entry: found duplicate key at line 31 column 13"
323 );
324 }
325
326 #[test]
327 fn duplicate_slot_ids() {
328 let str = r#"{
329 "newId" : "foo",
330 "templateId" : "bar",
331 "values" : {
332 "?principal" : { "type" : "User", "id" : "John" },
333 "?principal" : { "type" : "User", "id" : "John" },
334 }
335 }"#;
336 let err = serde_json::from_str::<TemplateLink>(str)
337 .expect_err("should have failed to parse from JSON");
338 assert_eq!(
339 err.to_string(),
340 "invalid entry: found duplicate key at line 6 column 65"
341 );
342 }
343
344 #[test]
345 fn try_from_policies_static_only() {
346 let src = r#"
347 permit(principal == User::"alice", action, resource);
348 permit(principal, action == Action::"view", resource);
349 "#;
350 let node = crate::parser::text_to_cst::parse_policies(src).expect("Policies should parse");
351 let policy_set =
352 PolicySet::try_from(node).expect("Conversion to policy set should succeed");
353 assert_eq!(policy_set.static_policies.len(), 2);
354 assert!(policy_set.templates.is_empty());
355 assert!(policy_set.template_links.is_empty());
356 }
357
358 #[test]
359 fn try_from_policies_static_and_templates() {
360 let src = r#"
361 permit(principal == User::"alice", action, resource);
362 permit(principal == ?principal, action == Action::"view", resource);
363 "#;
364 let node = crate::parser::text_to_cst::parse_policies(src).expect("Policies should parse");
365 let policy_set =
366 PolicySet::try_from(node).expect("Conversion to policy set should succeed");
367 assert_eq!(policy_set.static_policies.len(), 1);
368 assert_eq!(policy_set.templates.len(), 1);
369 assert!(policy_set.template_links.is_empty());
370 }
371
372 #[test]
373 fn try_from_policies_with_parse_error() {
374 let src = r#"principal(p, action, resource);"#;
375 let node = crate::parser::text_to_cst::parse_policies(src).expect("policies should parse");
376 PolicySet::try_from(node).expect_err("Expected parse error to result in err");
377 }
378}