1use super::Clause;
18use super::Policy;
19use super::PolicySetFromJsonError;
20use crate::ast::{self, EntityUID, PolicyID, SlotId};
21use crate::entities::json::err::JsonDeserializationErrorContext;
22use crate::entities::json::EntityUidJson;
23use crate::jsonvalue::deserialize_linked_hash_map_no_duplicates;
24use crate::parser::cst::Policies;
25use crate::parser::err::ParseErrors;
26use crate::parser::Node;
27use linked_hash_map::LinkedHashMap;
28use serde::{Deserialize, Serialize};
29use serde_with::serde_as;
30use std::collections::HashMap;
31
32#[derive(Debug, Clone, Serialize, Deserialize, Default)]
34#[serde(rename_all = "camelCase")]
35#[serde(deny_unknown_fields)]
36pub struct PolicySet {
37 #[serde(deserialize_with = "deserialize_linked_hash_map_no_duplicates")]
39 pub templates: LinkedHashMap<PolicyID, Policy>,
40 #[serde(deserialize_with = "deserialize_linked_hash_map_no_duplicates")]
42 pub static_policies: LinkedHashMap<PolicyID, Policy>,
43 pub template_links: Vec<TemplateLink>,
45}
46
47impl PolicySet {
48 pub fn get_policy(&self, id: &PolicyID) -> Option<Policy> {
53 let maybe_static_policy = self.static_policies.get(id).cloned();
54
55 let maybe_link = self
56 .template_links
57 .iter()
58 .filter_map(|link| {
59 if &link.new_id == id {
60 self.get_template(&link.template_id).and_then(|template| {
61 let unwrapped_est_vals: HashMap<SlotId, EntityUidJson> =
62 link.values.iter().map(|(k, v)| (*k, v.into())).collect();
63 template.link(&unwrapped_est_vals).ok()
64 })
65 } else {
66 None
67 }
68 })
69 .next();
70
71 maybe_static_policy.or(maybe_link)
72 }
73
74 pub fn get_template(&self, id: &PolicyID) -> Option<Policy> {
79 self.templates.get(id).cloned()
80 }
81
82 pub fn max_expr_height(&self) -> Option<usize> {
86 self.static_policies
87 .values()
88 .chain(self.templates.values())
89 .flat_map(|p| &p.conditions)
90 .map(|clause| match clause {
91 Clause::When(e) | Clause::Unless(e) => e.height(),
92 })
93 .max()
94 }
95}
96
97#[serde_as]
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101#[serde(deny_unknown_fields)]
102pub struct TemplateLink {
103 pub template_id: PolicyID,
105 pub new_id: PolicyID,
107 #[serde_as(as = "serde_with::MapPreventDuplicates<_,EntityUidJson<TemplateLinkContext>>")]
109 pub values: HashMap<SlotId, EntityUID>,
110}
111
112struct TemplateLinkContext;
114
115impl crate::entities::json::DeserializationContext for TemplateLinkContext {
116 fn static_context() -> Option<JsonDeserializationErrorContext> {
117 Some(JsonDeserializationErrorContext::TemplateLink)
118 }
119}
120
121impl TryFrom<PolicySet> for ast::PolicySet {
122 type Error = PolicySetFromJsonError;
123
124 fn try_from(value: PolicySet) -> Result<Self, Self::Error> {
125 let mut ast_pset = ast::PolicySet::default();
126
127 for (id, policy) in value.static_policies {
128 let ast = policy.try_into_ast_policy(Some(id))?;
129 ast_pset.add(ast)?;
130 }
131
132 for (id, policy) in value.templates {
133 let ast = policy.try_into_ast_policy_or_template(Some(id))?;
134 ast_pset.add_template(ast)?;
135 }
136
137 for TemplateLink {
138 template_id,
139 new_id,
140 values,
141 } in value.template_links
142 {
143 ast_pset.link(template_id, new_id, values)?;
144 }
145
146 Ok(ast_pset)
147 }
148}
149
150impl TryFrom<Node<Option<Policies>>> for PolicySet {
151 type Error = ParseErrors;
152
153 fn try_from(policies: Node<Option<Policies>>) -> Result<Self, Self::Error> {
154 let mut templates = LinkedHashMap::new();
155 let mut static_policies = LinkedHashMap::new();
156 let mut all_errs: Vec<ParseErrors> = vec![];
157 for (policy_id, policy) in policies.with_generated_policyids()? {
158 match policy.try_as_inner() {
159 Ok(cst) => match Policy::try_from(cst.clone()) {
160 Ok(est) => {
161 if est.is_template() {
162 templates.insert(policy_id, est);
163 } else {
164 static_policies.insert(policy_id, est);
165 }
166 }
167 Err(e) => {
168 all_errs.push(e);
169 }
170 },
171 Err(e) => {
172 all_errs.push(e.into());
173 }
174 };
175 }
176 if let Some(errs) = ParseErrors::flatten(all_errs) {
178 Err(errs)
179 } else {
180 Ok(PolicySet {
181 templates,
182 static_policies,
183 template_links: Vec::new(),
184 })
185 }
186 }
187}
188
189#[cfg(test)]
190mod test {
191 use serde_json::json;
192
193 use super::*;
194
195 #[test]
196 fn valid_example() {
197 let json = json!({
198 "staticPolicies": {
199 "policy1": {
200 "effect": "permit",
201 "principal": {
202 "op": "==",
203 "entity": { "type": "User", "id": "alice" }
204 },
205 "action": {
206 "op": "==",
207 "entity": { "type": "Action", "id": "view" }
208 },
209 "resource": {
210 "op": "in",
211 "entity": { "type": "Folder", "id": "foo" }
212 },
213 "conditions": []
214 }
215 },
216 "templates": {
217 "template": {
218 "effect" : "permit",
219 "principal" : {
220 "op" : "==",
221 "slot" : "?principal"
222 },
223 "action" : {
224 "op" : "all"
225 },
226 "resource" : {
227 "op" : "all",
228 },
229 "conditions": []
230 }
231 },
232 "templateLinks" : [
233 {
234 "newId" : "link",
235 "templateId" : "template",
236 "values" : {
237 "?principal" : { "type" : "User", "id" : "bob" }
238 }
239 }
240 ]
241 });
242
243 let est_policy_set: PolicySet =
244 serde_json::from_value(json).expect("failed to parse from JSON");
245 let ast_policy_set: ast::PolicySet =
246 est_policy_set.try_into().expect("failed to convert to AST");
247 assert_eq!(ast_policy_set.policies().count(), 2);
248 assert_eq!(ast_policy_set.templates().count(), 1);
249 assert!(ast_policy_set
250 .get_template_arc(&PolicyID::from_string("template"))
251 .is_some());
252 let link = ast_policy_set.get(&PolicyID::from_string("link")).unwrap();
253 assert_eq!(link.template().id(), &PolicyID::from_string("template"));
254 assert_eq!(
255 link.env(),
256 &HashMap::from_iter([(SlotId::principal(), r#"User::"bob""#.parse().unwrap())])
257 );
258 assert_eq!(
259 ast_policy_set
260 .get_linked_policies(&PolicyID::from_string("template"))
261 .unwrap()
262 .count(),
263 1
264 );
265 }
266
267 #[test]
268 fn unknown_field() {
269 let json = json!({
270 "staticPolicies": {
271 "policy1": {
272 "effect": "permit",
273 "principal": {
274 "op": "==",
275 "entity": { "type": "User", "id": "alice" }
276 },
277 "action": {
278 "op" : "all"
279 },
280 "resource": {
281 "op" : "all"
282 },
283 "conditions": []
284 }
285 },
286 "templates": {},
287 "links" : []
288 });
289
290 let err = serde_json::from_value::<PolicySet>(json)
291 .expect_err("should have failed to parse from JSON");
292 assert_eq!(
293 err.to_string(),
294 "unknown field `links`, expected one of `templates`, `staticPolicies`, `templateLinks`"
295 );
296 }
297
298 #[test]
299 fn duplicate_policy_ids() {
300 let str = r#"{
301 "staticPolicies" : {
302 "policy0": {
303 "effect": "permit",
304 "principal": {
305 "op": "==",
306 "entity": { "type": "User", "id": "alice" }
307 },
308 "action": {
309 "op" : "all"
310 },
311 "resource": {
312 "op" : "all"
313 },
314 "conditions": []
315 },
316 "policy0": {
317 "effect": "permit",
318 "principal": {
319 "op": "==",
320 "entity": { "type": "User", "id": "alice" }
321 },
322 "action": {
323 "op" : "all"
324 },
325 "resource": {
326 "op" : "all"
327 },
328 "conditions": []
329 }
330 },
331 "templates" : {},
332 "templateLinks" : []
333 }"#;
334 let err = serde_json::from_str::<PolicySet>(str)
335 .expect_err("should have failed to parse from JSON");
336 assert_eq!(
337 err.to_string(),
338 "invalid entry: found duplicate key at line 31 column 13"
339 );
340 }
341
342 #[test]
343 fn duplicate_slot_ids() {
344 let str = r#"{
345 "newId" : "foo",
346 "templateId" : "bar",
347 "values" : {
348 "?principal" : { "type" : "User", "id" : "John" },
349 "?principal" : { "type" : "User", "id" : "John" },
350 }
351 }"#;
352 let err = serde_json::from_str::<TemplateLink>(str)
353 .expect_err("should have failed to parse from JSON");
354 assert_eq!(
355 err.to_string(),
356 "invalid entry: found duplicate key at line 6 column 65"
357 );
358 }
359
360 #[test]
361 fn try_from_policies_static_only() {
362 let src = r#"
363 permit(principal == User::"alice", action, resource);
364 permit(principal, action == Action::"view", resource);
365 "#;
366 let node = crate::parser::text_to_cst::parse_policies(src).expect("Policies should parse");
367 let policy_set =
368 PolicySet::try_from(node).expect("Conversion to policy set should succeed");
369 assert_eq!(policy_set.static_policies.len(), 2);
370 assert!(policy_set.templates.is_empty());
371 assert!(policy_set.template_links.is_empty());
372 }
373
374 #[test]
375 fn try_from_policies_static_and_templates() {
376 let src = r#"
377 permit(principal == User::"alice", action, resource);
378 permit(principal == ?principal, action == Action::"view", resource);
379 "#;
380 let node = crate::parser::text_to_cst::parse_policies(src).expect("Policies should parse");
381 let policy_set =
382 PolicySet::try_from(node).expect("Conversion to policy set should succeed");
383 assert_eq!(policy_set.static_policies.len(), 1);
384 assert_eq!(policy_set.templates.len(), 1);
385 assert!(policy_set.template_links.is_empty());
386 }
387
388 #[test]
389 fn try_from_policies_with_parse_error() {
390 let src = r#"principal(p, action, resource);"#;
391 let node = crate::parser::text_to_cst::parse_policies(src).expect("policies should parse");
392 PolicySet::try_from(node).expect_err("Expected parse error to result in err");
393 }
394
395 #[test]
396 fn max_expr_height_empty_policy_set() {
397 let json = json!({
398 "staticPolicies": {},
399 "templates": {},
400 "templateLinks": []
401 });
402 let policy_set: PolicySet =
403 serde_json::from_value(json).expect("failed to parse from JSON");
404 assert_eq!(policy_set.max_expr_height(), None);
405 }
406
407 #[test]
408 fn max_expr_height_no_conditions() {
409 let json = json!({
410 "staticPolicies": {
411 "policy1": {
412 "effect": "permit",
413 "principal": { "op": "All" },
414 "action": { "op": "All" },
415 "resource": { "op": "All" },
416 "conditions": []
417 }
418 },
419 "templates": {},
420 "templateLinks": []
421 });
422 let policy_set: PolicySet =
423 serde_json::from_value(json).expect("failed to parse from JSON");
424 assert_eq!(policy_set.max_expr_height(), None);
425 }
426
427 #[test]
428 fn max_expr_height_single_literal_condition() {
429 let json = json!({
430 "staticPolicies": {
431 "policy1": {
432 "effect": "permit",
433 "principal": { "op": "All" },
434 "action": { "op": "All" },
435 "resource": { "op": "All" },
436 "conditions": [
437 {
438 "kind": "when",
439 "body": { "Value": true }
440 }
441 ]
442 }
443 },
444 "templates": {},
445 "templateLinks": []
446 });
447 let policy_set: PolicySet =
448 serde_json::from_value(json).expect("failed to parse from JSON");
449 assert_eq!(policy_set.max_expr_height(), Some(0));
450 }
451
452 #[test]
453 fn max_expr_height_nested_expression() {
454 let json = json!({
455 "staticPolicies": {
456 "policy1": {
457 "effect": "permit",
458 "principal": { "op": "All" },
459 "action": { "op": "All" },
460 "resource": { "op": "All" },
461 "conditions": [
462 {
463 "kind": "when",
464 "body": {
465 "==": {
466 "left": { "Value": 1 },
467 "right": { "Value": 2 }
468 }
469 }
470 }
471 ]
472 }
473 },
474 "templates": {},
475 "templateLinks": []
476 });
477 let policy_set: PolicySet =
478 serde_json::from_value(json).expect("failed to parse from JSON");
479 assert_eq!(policy_set.max_expr_height(), Some(1));
480 }
481
482 #[test]
483 fn max_expr_height_multiple_policies_takes_max() {
484 let json = json!({
487 "staticPolicies": {
488 "policy1": {
489 "effect": "permit",
490 "principal": { "op": "All" },
491 "action": { "op": "All" },
492 "resource": { "op": "All" },
493 "conditions": [
494 {
495 "kind": "when",
496 "body": { "Value": true }
497 }
498 ]
499 },
500 "policy2": {
501 "effect": "forbid",
502 "principal": { "op": "All" },
503 "action": { "op": "All" },
504 "resource": { "op": "All" },
505 "conditions": [
506 {
507 "kind": "when",
508 "body": {
509 "!": {
510 "arg": {
511 "==": {
512 "left": { "Value": 1 },
513 "right": { "Value": 2 }
514 }
515 }
516 }
517 }
518 }
519 ]
520 }
521 },
522 "templates": {},
523 "templateLinks": []
524 });
525 let policy_set: PolicySet =
526 serde_json::from_value(json).expect("failed to parse from JSON");
527 assert_eq!(policy_set.max_expr_height(), Some(2));
528 }
529
530 #[test]
531 fn max_expr_height_unless_clause() {
532 let json = json!({
534 "staticPolicies": {
535 "policy1": {
536 "effect": "permit",
537 "principal": { "op": "All" },
538 "action": { "op": "All" },
539 "resource": { "op": "All" },
540 "conditions": [
541 {
542 "kind": "unless",
543 "body": {
544 "==": {
545 "left": { "Value": 1 },
546 "right": { "Value": 2 }
547 }
548 }
549 }
550 ]
551 }
552 },
553 "templates": {},
554 "templateLinks": []
555 });
556 let policy_set: PolicySet =
557 serde_json::from_value(json).expect("failed to parse from JSON");
558 assert_eq!(policy_set.max_expr_height(), Some(1));
559 }
560
561 #[test]
562 fn max_expr_height_templates_included() {
563 let json = json!({
567 "staticPolicies": {
568 "policy1": {
569 "effect": "permit",
570 "principal": { "op": "All" },
571 "action": { "op": "All" },
572 "resource": { "op": "All" },
573 "conditions": [
574 {
575 "kind": "when",
576 "body": { "Value": true }
577 }
578 ]
579 }
580 },
581 "templates": {
582 "template1": {
583 "effect": "permit",
584 "principal": { "op": "==", "slot": "?principal" },
585 "action": { "op": "All" },
586 "resource": { "op": "All" },
587 "conditions": [
588 {
589 "kind": "when",
590 "body": {
591 "!": {
592 "arg": {
593 "==": {
594 "left": { "Value": 1 },
595 "right": { "Value": 2 }
596 }
597 }
598 }
599 }
600 }
601 ]
602 }
603 },
604 "templateLinks": []
605 });
606 let policy_set: PolicySet =
607 serde_json::from_value(json).expect("failed to parse from JSON");
608 assert_eq!(policy_set.max_expr_height(), Some(2));
609 }
610
611 #[test]
612 fn max_expr_height_multiple_conditions_per_policy() {
613 let json = json!({
618 "staticPolicies": {
619 "policy1": {
620 "effect": "permit",
621 "principal": { "op": "All" },
622 "action": { "op": "All" },
623 "resource": { "op": "All" },
624 "conditions": [
625 {
626 "kind": "when",
627 "body": { "Value": true }
628 },
629 {
630 "kind": "when",
631 "body": {
632 "==": {
633 "left": { "Value": 1 },
634 "right": { "Value": 2 }
635 }
636 }
637 }
638 ]
639 }
640 },
641 "templates": {},
642 "templateLinks": []
643 });
644 let policy_set: PolicySet =
645 serde_json::from_value(json).expect("failed to parse from JSON");
646 assert_eq!(policy_set.max_expr_height(), Some(1));
647 }
648
649 #[test]
650 fn max_expr_height_literal_nested_set_matches_non_literal() {
651 let json_literal = json!({
655 "staticPolicies": {
656 "policy1": {
657 "effect": "permit",
658 "principal": { "op": "All" },
659 "action": { "op": "All" },
660 "resource": { "op": "All" },
661 "conditions": [
662 {
663 "kind": "when",
664 "body": {
665 "Value": [[[1]]]
666 }
667 }
668 ]
669 }
670 },
671 "templates": {},
672 "templateLinks": []
673 });
674 let policy_set_literal: PolicySet =
675 serde_json::from_value(json_literal).expect("failed to parse from JSON");
676 assert_eq!(policy_set_literal.max_expr_height(), Some(3));
677 let json_non_literal = json!({
681 "staticPolicies": {
682 "policy1": {
683 "effect": "permit",
684 "principal": { "op": "All" },
685 "action": { "op": "All" },
686 "resource": { "op": "All" },
687 "conditions": [
688 {
689 "kind": "when",
690 "body": {
691 "Set": [
692 { "Set": [
693 { "Set": [
694 { "Value": 1 }
695 ]}
696 ]}
697 ]
698 }
699 }
700 ]
701 }
702 },
703 "templates": {},
704 "templateLinks": []
705 });
706 let policy_set_non_literal: PolicySet =
707 serde_json::from_value(json_non_literal).expect("failed to parse from JSON");
708 assert_eq!(policy_set_non_literal.max_expr_height(), Some(3));
709 assert_eq!(
711 policy_set_literal.max_expr_height(),
712 policy_set_non_literal.max_expr_height()
713 );
714 }
715
716 #[test]
717 fn max_expr_height_deeply_nested() {
718 let json = json!({
720 "staticPolicies": {
721 "policy1": {
722 "effect": "permit",
723 "principal": { "op": "All" },
724 "action": { "op": "All" },
725 "resource": { "op": "All" },
726 "conditions": [
727 {
728 "kind": "when",
729 "body": {
730 "!": {
731 "arg": {
732 "!": {
733 "arg": {
734 "!": {
735 "arg": { "Value": true }
736 }
737 }
738 }
739 }
740 }
741 }
742 }
743 ]
744 }
745 },
746 "templates": {},
747 "templateLinks": []
748 });
749 let policy_set: PolicySet =
750 serde_json::from_value(json).expect("failed to parse from JSON");
751 assert_eq!(policy_set.max_expr_height(), Some(3));
752 }
753}