1use serde::{Deserialize, Serialize};
2
3use std::collections::{HashMap, HashSet};
4
5use super::error::{invalid_state, PolarError, PolarResult, ValidationError};
6use super::kb::KnowledgeBase;
7use super::rules::*;
8use super::terms::*;
9
10pub const ACTOR_UNION_NAME: &str = "Actor";
11pub const RESOURCE_UNION_NAME: &str = "Resource";
12
13#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum Production {
25 Declaration((Term, Term)), ShorthandRule(Term, (Term, Option<(Term, Term)>)), }
28
29fn validate_relation_keyword(keyword: &Term) -> PolarResult<()> {
30 if keyword.as_symbol()?.0 != "on" {
31 return Err(ValidationError::ResourceBlock {
32 msg: format!(
33 "Unexpected relation keyword '{}'. Did you mean 'on'?",
34 keyword
35 ),
36 term: keyword.clone(),
37 }
38 .into());
39 }
40 Ok(())
41}
42
43#[derive(Debug)]
44pub enum ParsedDeclaration {
45 Roles(Term), Permissions(Term), Relations(Term), }
49
50pub fn validate_parsed_declaration((name, term): (Term, Term)) -> PolarResult<ParsedDeclaration> {
51 match (name.as_symbol()?.0.as_ref(), term.value()) {
52 ("roles", Value::List(_)) => Ok(ParsedDeclaration::Roles(term)),
53 ("permissions", Value::List(_)) => Ok(ParsedDeclaration::Permissions(term)),
54 ("relations", Value::Dictionary(_)) => Ok(ParsedDeclaration::Relations(term)),
55
56 ("roles", Value::Dictionary(_)) | ("permissions", Value::Dictionary(_)) => Err(ValidationError::ResourceBlock {
57 msg: format!("Expected '{}' declaration to be a list of strings; found a dictionary", name),
58 term
59 }),
60 ("relations", Value::List(_)) => Err(ValidationError::ResourceBlock {
61 msg: "Expected 'relations' declaration to be a dictionary; found a list".to_owned(),
62 term
63 }),
64
65 (_, Value::List(_)) => Err(ValidationError::ResourceBlock {
66 msg: format!("Unexpected declaration '{}'. Did you mean for this to be 'roles = [ ... ];' or 'permissions = [ ... ];'?", name),
67 term
68 }),
69 (_, Value::Dictionary(_)) => Err(ValidationError::ResourceBlock {
70 msg: format!("Unexpected declaration '{}'. Did you mean for this to be 'relations = {{ ... }};'?", name),
71 term
72 }),
73 _ => unreachable!(),
74 }.map_err(Into::into)
75}
76
77pub fn block_type_from_keyword(keyword: Option<Term>, resource: &Term) -> PolarResult<BlockType> {
78 if let Some(keyword) = keyword {
79 match keyword.as_symbol()?.0.as_ref() {
80 "actor" => Ok(BlockType::Actor),
81 "resource" => Ok(BlockType::Resource),
82 other => Err(ValidationError::ResourceBlock {
83 msg: format!("Expected 'actor' or 'resource' but found '{}'.", other),
84 term: keyword.clone(),
85 }
86 .into()),
87 }
88 } else {
89 Err(ValidationError::ResourceBlock {
92 msg: "Expected 'actor' or 'resource' but found nothing.".to_owned(),
93 term: resource.clone(),
94 }
95 .into())
96 }
97}
98
99pub fn resource_block_from_productions(
100 keyword: Option<Term>,
101 resource: Term,
102 productions: Vec<Production>,
103) -> (ResourceBlock, Vec<PolarError>) {
104 let mut errors = vec![];
105
106 let block_type = match block_type_from_keyword(keyword, &resource) {
107 Ok(block_type) => block_type,
108 Err(e) => {
109 errors.push(e);
110 BlockType::Resource
118 }
119 };
120
121 let mut roles: Option<Term> = None;
122 let mut permissions: Option<Term> = None;
123 let mut relations: Option<Term> = None;
124 let mut shorthand_rules = vec![];
125
126 let make_error = |name: &str, _previous: &Term, new: &Term| {
128 ValidationError::ResourceBlock {
129 msg: format!(
130 "Multiple '{}' declarations in '{}' resource block.",
131 name, resource
132 ),
133 term: new.clone(),
134 }
135 .into()
136 };
137
138 for production in productions {
139 match production {
140 Production::Declaration(declaration) => {
141 match validate_parsed_declaration(declaration) {
142 Ok(ParsedDeclaration::Roles(new)) => {
143 if let Some(previous) = roles {
147 errors.push(make_error("roles", &previous, &new));
148 }
149 roles = Some(new);
150 }
151 Ok(ParsedDeclaration::Permissions(new)) => {
152 if let Some(previous) = permissions {
153 errors.push(make_error("permissions", &previous, &new));
154 }
155 permissions = Some(new);
156 }
157 Ok(ParsedDeclaration::Relations(new)) => {
158 if let Some(previous) = relations {
159 errors.push(make_error("relations", &previous, &new));
160 }
161 relations = Some(new);
162 }
163 Err(e) => errors.push(e),
164 }
165 }
166 Production::ShorthandRule(head, body) => {
167 shorthand_rules.push(ShorthandRule { head, body });
169 }
170 }
171 }
172
173 (
174 ResourceBlock {
175 block_type,
176 resource,
177 roles,
178 permissions,
179 relations,
180 shorthand_rules,
181 },
182 errors,
183 )
184}
185
186#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
187pub enum Declaration {
188 Role,
189 Permission,
190 Relation(Term),
192}
193
194#[derive(Clone, Debug, Hash, PartialEq, Eq)]
195pub struct ShorthandRule {
196 pub head: Term,
198 pub body: (Term, Option<(Term, Term)>),
202}
203
204impl ShorthandRule {
205 pub fn as_rule(&self, resource_name: &Term, blocks: &ResourceBlocks) -> PolarResult<Rule> {
206 let Self { head, body } = self;
207 Ok(Rule {
208 name: blocks.get_rule_name_for_declaration_in_resource_block(head, resource_name)?,
209 params: shorthand_rule_head_to_params(head, resource_name)?,
210 body: shorthand_rule_body_to_rule_body(body, resource_name, blocks)?,
211 source_info: head.source_info().clone(),
213 required: false,
214 })
215 }
216}
217
218type Declarations = HashMap<Term, Declaration>;
219
220impl Declaration {
221 fn as_rule_name(&self) -> Symbol {
222 sym!(&format!("has_{}", self))
223 }
224}
225
226#[derive(Clone, Debug, PartialEq, Eq)]
229pub enum BlockType {
230 Actor,
231 Resource,
232}
233
234#[derive(Clone, Debug, PartialEq, Eq)]
236pub struct ResourceBlock {
237 pub block_type: BlockType,
238 pub resource: Term,
239 pub roles: Option<Term>,
240 pub permissions: Option<Term>,
241 pub relations: Option<Term>,
242 pub shorthand_rules: Vec<ShorthandRule>,
243}
244
245#[derive(Clone, Default)]
246pub struct ResourceBlocks {
247 declarations: HashMap<Term, Declarations>,
249 pub shorthand_rules: HashMap<Term, Vec<ShorthandRule>>,
251 pub actors: HashSet<Term>,
254 pub resources: HashSet<Term>,
257}
258
259impl ResourceBlocks {
260 pub fn new() -> Self {
261 Self {
262 declarations: HashMap::new(),
263 shorthand_rules: HashMap::new(),
264 actors: HashSet::new(),
265 resources: HashSet::new(),
266 }
267 }
268
269 pub fn clear(&mut self) {
270 self.declarations.clear();
271 self.shorthand_rules.clear();
272 self.actors.clear();
273 self.resources.clear();
274 }
275
276 fn add(
277 &mut self,
278 block_type: BlockType,
279 resource: Term,
280 declarations: Declarations,
281 shorthand_rules: Vec<ShorthandRule>,
282 ) -> Vec<PolarError> {
283 let mut errors = vec![];
284
285 if let Some(existing) = self.declarations.get_mut(&resource) {
287 for (key, new) in declarations {
288 if let Some(existing) = existing.insert(key.clone(), new.clone()) {
289 if existing != new {
290 errors.push(
291 ValidationError::DuplicateResourceBlockDeclaration {
292 resource: resource.clone(),
293 declaration: key.clone(),
294 existing,
295 new: new.clone(),
296 }
297 .into(),
298 );
299 }
300 }
301 }
302 } else {
303 self.declarations.insert(resource.clone(), declarations);
305 }
306
307 self.shorthand_rules
309 .entry(resource.clone())
310 .and_modify(|existing| existing.extend(shorthand_rules.clone()))
311 .or_insert(shorthand_rules);
312
313 match block_type {
314 BlockType::Actor => {
315 self.actors.insert(resource.clone());
316 self.resources.insert(resource)
317 }
318 BlockType::Resource => self.resources.insert(resource),
319 };
320
321 errors
322 }
323
324 fn get_declaration_in_resource_block(
328 &self,
329 declaration: &Term,
330 resource_name: &Term,
331 ) -> PolarResult<&Declaration> {
332 let maybe_declarations = self.declarations.get(resource_name);
333 let maybe_declaration = maybe_declarations.and_then(|ds| ds.get(declaration));
334 if let Some(declaration) = maybe_declaration {
335 Ok(declaration)
336 } else {
337 Err(ValidationError::ResourceBlock {
338 msg: format!("Undeclared term {} referenced in rule in '{}' resource block. Did you mean to declare it as a role, permission, or relation?", declaration, resource_name),
339 term: declaration.clone(),
340 }.into())
341 }
342 }
343
344 pub fn get_relation_type_in_resource_block(
346 &self,
347 relation: &Term,
348 resource: &Term,
349 ) -> PolarResult<&Term> {
350 let declaration = self.get_declaration_in_resource_block(relation, resource)?;
351 if let Declaration::Relation(related_type) = declaration {
352 Ok(related_type)
353 } else {
354 invalid_state(format!("Expected Relation; got: {:?}", declaration))
355 }
356 }
357
358 fn get_rule_name_for_declaration_in_resource_block(
361 &self,
362 declaration: &Term,
363 resource_name: &Term,
364 ) -> PolarResult<Symbol> {
365 Ok(self
366 .get_declaration_in_resource_block(declaration, resource_name)?
367 .as_rule_name())
368 }
369
370 fn get_rule_name_for_declaration_in_related_resource_block(
373 &self,
374 declaration: &Term,
375 relation: &Term,
376 resource: &Term,
377 ) -> PolarResult<Symbol> {
378 let related_block = self.get_relation_type_in_resource_block(relation, resource)?;
379
380 if let Some(declarations) = self.declarations.get(related_block) {
381 if let Some(declaration) = declarations.get(declaration) {
382 Ok(declaration.as_rule_name())
383 } else {
384 Err(ValidationError::ResourceBlock {
385 msg: format!("{}: Term {} not declared in related resource block '{}'. Did you mean to declare it as a role, permission, or relation in the '{}' resource block?", resource, declaration, related_block, related_block),
386 term: declaration.clone(),
387 }.into())
388 }
389 } else {
390 Err(ValidationError::ResourceBlock {
391 msg: format!("{}: Relation {} in rule body `{} on {}` has type '{}', but no such resource block exists. Try declaring one: `resource {} {{}}`", resource, relation, declaration, relation, related_block, related_block),
392 term: related_block.clone(),
393 }.into())
394 }
395 }
396
397 pub fn declarations(&self) -> &HashMap<Term, Declarations> {
398 &self.declarations
399 }
400
401 pub fn has_roles(&self) -> bool {
402 let mut declarations = self.declarations().values().flat_map(HashMap::values);
403 declarations.any(|d| matches!(d, Declaration::Role))
404 }
405
406 pub fn relation_tuples(&self) -> Vec<(&Term, &Term, &Term)> {
407 let mut tuples = vec![];
408 for (object, declarations) in self.declarations() {
409 for (name, declaration) in declarations {
410 if let Declaration::Relation(subject) = declaration {
411 tuples.push((subject, name, object));
412 }
413 }
414 }
415 tuples
416 }
417}
418
419fn index_declarations(
421 roles: Option<Term>,
422 permissions: Option<Term>,
423 relations: Option<Term>,
424 resource: &Term,
425) -> PolarResult<HashMap<Term, Declaration>> {
426 let mut declarations = HashMap::new();
427
428 if let Some(roles) = roles {
429 for role in roles.as_list()? {
430 if let Some(existing) = declarations.insert(role.clone(), Declaration::Role) {
431 return Err(ValidationError::DuplicateResourceBlockDeclaration {
432 resource: resource.clone(),
433 declaration: role.clone(),
434 existing,
435 new: Declaration::Role,
436 }
437 .into());
438 }
439 }
440 }
441
442 if let Some(permissions) = permissions {
443 for permission in permissions.as_list()? {
444 if let Some(existing) = declarations.insert(permission.clone(), Declaration::Permission)
445 {
446 return Err(ValidationError::DuplicateResourceBlockDeclaration {
447 resource: resource.clone(),
448 declaration: permission.clone(),
449 existing,
450 new: Declaration::Permission,
451 }
452 .into());
453 }
454 }
455 }
456
457 if let Some(relations) = relations {
458 for (relation, relation_type) in &relations.as_dict()?.fields {
459 let stringified_relation = relation_type.clone_with_value(value!(relation.0.as_str()));
465 let declaration = Declaration::Relation(relation_type.clone());
466
467 if let Some(existing) =
468 declarations.insert(stringified_relation.clone(), declaration.clone())
469 {
470 return Err(ValidationError::DuplicateResourceBlockDeclaration {
471 resource: resource.clone(),
472 declaration: stringified_relation,
473 existing,
474 new: declaration,
475 }
476 .into());
477 }
478 }
479 }
480 Ok(declarations)
481}
482
483fn resource_name_as_var(resource_name: &Term, related: bool) -> PolarResult<Value> {
484 let name = &resource_name.as_symbol()?.0;
485 let mut lowercased = name.to_lowercase();
486
487 if &lowercased == name {
491 lowercased += "_instance";
492 }
493
494 if related {
498 lowercased.insert_str(0, "related_");
499 }
500
501 Ok(value!(sym!(lowercased)))
502}
503
504fn shorthand_rule_body_to_rule_body(
507 (implier, relation): &(Term, Option<(Term, Term)>),
508 resource_name: &Term,
509 blocks: &ResourceBlocks,
510) -> PolarResult<Term> {
511 let resource_var = implier.clone_with_value(resource_name_as_var(resource_name, false)?);
514
515 let actor_var = implier.clone_with_value(value!(sym!("actor")));
517
518 if let Some((keyword, relation)) = relation {
520 validate_relation_keyword(keyword)?;
522
523 let relation_type = blocks.get_relation_type_in_resource_block(relation, resource_name)?;
527 let relation_type_var =
528 relation.clone_with_value(resource_name_as_var(relation_type, true)?);
529
530 let relation_call = relation.clone_with_value(value!(Call {
535 name: sym!("has_relation"),
536 args: vec![relation_type_var.clone(), relation.clone(), resource_var],
537 kwargs: None
538 }));
539
540 let implier_call = implier.clone_with_value(value!(Call {
549 name: blocks.get_rule_name_for_declaration_in_related_resource_block(
550 implier,
551 relation,
552 resource_name
553 )?,
554 args: vec![actor_var, implier.clone(), relation_type_var],
555 kwargs: None
556 }));
557
558 Ok(implier.clone_with_value(value!(op!(And, relation_call, implier_call))))
560 } else {
561 let implier_call = implier.clone_with_value(value!(Call {
567 name: blocks.get_rule_name_for_declaration_in_resource_block(implier, resource_name)?,
568 args: vec![actor_var, implier.clone(), resource_var],
569 kwargs: None
570 }));
571
572 Ok(implier.clone_with_value(value!(op!(And, implier_call))))
574 }
575}
576
577fn shorthand_rule_head_to_params(head: &Term, resource: &Term) -> PolarResult<Vec<Parameter>> {
579 let resource_name = &resource.as_symbol()?.0;
580 let params = vec![
581 Parameter {
582 parameter: head.clone_with_value(value!(sym!("actor"))),
583 specializer: Some(head.clone_with_value(value!(pattern!(instance!(ACTOR_UNION_NAME))))),
584 },
585 Parameter {
586 parameter: head.clone(),
587 specializer: None,
588 },
589 Parameter {
590 parameter: head.clone_with_value(resource_name_as_var(resource, false)?),
591 specializer: Some(
592 resource.clone_with_value(value!(pattern!(instance!(resource_name)))),
593 ),
594 },
595 ];
596 Ok(params)
597}
598
599impl ResourceBlock {
600 pub fn add_to_kb(self, kb: &mut KnowledgeBase) -> Vec<PolarError> {
601 let mut errors = vec![];
602 errors.extend(kb.get_registered_class(&self.resource).err());
604
605 let ResourceBlock {
606 block_type,
607 resource,
608 roles,
609 permissions,
610 relations,
611 shorthand_rules,
612 } = self;
613
614 match index_declarations(roles, permissions, relations, &resource) {
615 Ok(declarations) => {
616 errors.extend(kb.resource_blocks.add(
617 block_type,
618 resource,
619 declarations,
620 shorthand_rules,
621 ));
622 }
623 Err(e) => errors.push(e),
624 }
625
626 errors
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use std::collections::HashSet;
633
634 use permutohedron::Heap;
635
636 use super::*;
637 use crate::error::{
638 ErrorKind::{Runtime, Validation},
639 RuntimeError, ValidationError,
640 };
641 use crate::events::QueryEvent;
642 use crate::parser::{parse_lines, Line};
643 use crate::polar::Polar;
644 use crate::sources::Source;
645
646 #[track_caller]
647 fn expect_error(p: &Polar, policy: &str, expected: &str) {
648 let error = p.load_str(policy).unwrap_err().unwrap_validation();
649 let msg = match error {
650 ValidationError::ResourceBlock { msg, .. } => msg,
651 ValidationError::UnregisteredClass { .. }
652 | ValidationError::DuplicateResourceBlockDeclaration { .. } => error.to_string(),
653 _ => panic!("Unexpected error: {}", error),
654 };
655 assert!(msg.contains(expected));
656 }
657
658 #[test]
659 fn test_resource_block_rewrite_shorthand_rules_with_lowercase_resource_specializer() {
660 let repo_resource = term!(sym!("repo"));
661 let repo_roles = term!(["reader"]);
662 let repo_relations = term!(btreemap! { sym!("parent") => term!(sym!("org")) });
663 let repo_declarations =
664 index_declarations(Some(repo_roles), None, Some(repo_relations), &repo_resource);
665
666 let org_resource = term!(sym!("org"));
667 let org_roles = term!(["member"]);
668 let org_declarations = index_declarations(Some(org_roles), None, None, &org_resource);
669
670 let mut blocks = ResourceBlocks::new();
671 blocks.add(
672 BlockType::Resource,
673 repo_resource,
674 repo_declarations.unwrap(),
675 vec![],
676 );
677 blocks.add(
678 BlockType::Resource,
679 org_resource,
680 org_declarations.unwrap(),
681 vec![],
682 );
683 let shorthand_rule = ShorthandRule {
684 head: term!("reader"),
685 body: (term!("member"), Some((term!(sym!("on")), term!("parent")))),
686 };
687 let rewritten_role_role = shorthand_rule
688 .as_rule(&term!(sym!("repo")), &blocks)
689 .unwrap();
690 assert_eq!(
691 rewritten_role_role.to_string(),
692 format!("has_role(actor: {}{{}}, \"reader\", repo_instance: repo{{}}) if has_relation(related_org_instance, \"parent\", repo_instance) and has_role(actor, \"member\", related_org_instance);", ACTOR_UNION_NAME),
693 );
694 }
695
696 #[test]
697 fn test_resource_block_rewrite_shorthand_rules_with_recursive_relation() {
698 let resource = term!(sym!("Dir"));
699 let permissions = term!(["read"]);
700 let relations = term!(btreemap! { sym!("parent") => resource.clone() });
701 let declarations = index_declarations(None, Some(permissions), Some(relations), &resource);
702
703 let mut blocks = ResourceBlocks::new();
704 blocks.add(
705 BlockType::Resource,
706 resource.clone(),
707 declarations.unwrap(),
708 vec![],
709 );
710
711 let shorthand_rule = ShorthandRule {
712 head: term!("read"),
713 body: (term!("read"), Some((term!(sym!("on")), term!("parent")))),
714 };
715 let rewritten_role_role = shorthand_rule.as_rule(&resource, &blocks).unwrap();
716
717 assert_eq!(
718 rewritten_role_role.to_string(),
719 format!("has_permission(actor: {}{{}}, \"read\", dir: Dir{{}}) if has_relation(related_dir, \"parent\", dir) and has_permission(actor, \"read\", related_dir);", ACTOR_UNION_NAME),
720 );
721 }
722
723 #[test]
724 fn test_resource_block_local_rewrite_shorthand_rules() {
725 let resource = term!(sym!("Org"));
726 let roles = term!(["owner", "member"]);
727 let permissions = term!(["invite", "create_repo"]);
728 let declarations = index_declarations(Some(roles), Some(permissions), None, &resource);
729 let mut blocks = ResourceBlocks::new();
730 blocks.add(BlockType::Resource, resource, declarations.unwrap(), vec![]);
731 let shorthand_rule = ShorthandRule {
732 head: term!("member"),
733 body: (term!("owner"), None),
734 };
735 let rewritten_role_role = shorthand_rule
736 .as_rule(&term!(sym!("Org")), &blocks)
737 .unwrap();
738 assert_eq!(
739 rewritten_role_role.to_string(),
740 format!("has_role(actor: {}{{}}, \"member\", org: Org{{}}) if has_role(actor, \"owner\", org);", ACTOR_UNION_NAME),
741 );
742
743 let shorthand_rule = ShorthandRule {
744 head: term!("invite"),
745 body: (term!("owner"), None),
746 };
747 let rewritten_permission_role = shorthand_rule
748 .as_rule(&term!(sym!("Org")), &blocks)
749 .unwrap();
750 assert_eq!(
751 rewritten_permission_role.to_string(),
752 format!("has_permission(actor: {}{{}}, \"invite\", org: Org{{}}) if has_role(actor, \"owner\", org);", ACTOR_UNION_NAME),
753 );
754
755 let shorthand_rule = ShorthandRule {
756 head: term!("create_repo"),
757 body: (term!("invite"), None),
758 };
759 let rewritten_permission_permission = shorthand_rule
760 .as_rule(&term!(sym!("Org")), &blocks)
761 .unwrap();
762 assert_eq!(
763 rewritten_permission_permission.to_string(),
764 format!("has_permission(actor: {}{{}}, \"create_repo\", org: Org{{}}) if has_permission(actor, \"invite\", org);", ACTOR_UNION_NAME),
765 );
766 }
767
768 #[test]
769 fn test_resource_block_nonlocal_rewrite_shorthand_rules() {
770 let repo_resource = term!(sym!("Repo"));
771 let repo_roles = term!(["reader"]);
772 let repo_relations = term!(btreemap! { sym!("parent") => term!(sym!("Org")) });
773 let repo_declarations =
774 index_declarations(Some(repo_roles), None, Some(repo_relations), &repo_resource);
775 let org_resource = term!(sym!("Org"));
776 let org_roles = term!(["member"]);
777 let org_declarations = index_declarations(Some(org_roles), None, None, &org_resource);
778 let mut blocks = ResourceBlocks::new();
779 blocks.add(
780 BlockType::Resource,
781 repo_resource,
782 repo_declarations.unwrap(),
783 vec![],
784 );
785 blocks.add(
786 BlockType::Resource,
787 org_resource,
788 org_declarations.unwrap(),
789 vec![],
790 );
791 let shorthand_rule = ShorthandRule {
792 head: term!("reader"),
793 body: (term!("member"), Some((term!(sym!("on")), term!("parent")))),
794 };
795 let rewritten_role_role = shorthand_rule
796 .as_rule(&term!(sym!("Repo")), &blocks)
797 .unwrap();
798 assert_eq!(
799 rewritten_role_role.to_string(),
800 format!("has_role(actor: {}{{}}, \"reader\", repo: Repo{{}}) if has_relation(related_org, \"parent\", repo) and has_role(actor, \"member\", related_org);", ACTOR_UNION_NAME),
801 );
802 }
803
804 #[test]
805 fn test_resource_block_resource_must_be_registered() {
806 let p = Polar::new();
807 let valid_policy = "resource Org{}";
808 expect_error(&p, valid_policy, "Unregistered class: Org");
809 p.register_constant(sym!("Org"), term!("unimportant"))
810 .unwrap();
811 assert!(p.load_str(valid_policy).is_ok());
812 }
813
814 #[test]
815 fn test_resource_block_declarations_spread_over_multiple_resource_blocks() {
816 let p = Polar::new();
817
818 let repo_instance = ExternalInstance {
819 instance_id: 1,
820 constructor: None,
821 repr: None,
822 class_repr: None,
823 class_id: None,
824 };
825 let repo_term = term!(Value::ExternalInstance(repo_instance.clone()));
826 let repo_name = sym!("Repo");
827 p.register_constant(repo_name.clone(), repo_term).unwrap();
828 p.register_mro(repo_name, vec![repo_instance.instance_id])
829 .unwrap();
830
831 let valid_policy = r#"
833 resource Repo {
834 relations = { readable_parent: Repo };
835 roles = ["reader"];
836 permissions = ["read"];
837
838 "write" if "writer";
839 "write" if "write" on "writable_parent";
840 }
841
842 resource Repo {
843 relations = { writable_parent: Repo };
844 roles = ["writer"];
845 permissions = ["write"];
846
847 "read" if "reader";
848 "read" if "read" on "readable_parent";
849 }
850
851 has_role(_: Actor, _: String, _: Resource);
852 has_relation(subject: Repo, "writable_parent", object: Repo) if
853 object.parent_id = subject.id;
854 has_relation(subject: Repo, "readable_parent", object: Repo) if
855 object.parent_id = subject.id;
856 "#;
857
858 p.load_str(valid_policy).unwrap();
859 {
862 let blocks = &p.kb.read().unwrap().resource_blocks;
863 let declarations = blocks.declarations.get(&term!(sym!("Repo"))).unwrap();
864 assert_eq!(declarations.len(), 6);
865 let shorthand_rules = blocks.shorthand_rules.get(&term!(sym!("Repo"))).unwrap();
866 assert_eq!(shorthand_rules.len(), 4);
867 }
868 p.clear_rules();
869
870 let invalid_policy = r#"
873 resource Repo {
874 permissions = ["write"];
875 }
876
877 resource Repo {
878 roles = ["reader"];
879 "read" if "reader";
880 }
881
882 has_role(actor: Actor, _role: String, repo: Repo) if
883 repo in actor.repos;
884 "#;
885 expect_error(
886 &p,
887 invalid_policy,
888 r#"Undeclared term "read" referenced in rule in 'Repo' resource block"#,
889 );
890
891 let valid_policy = r#"
893 resource Repo {
894 relations = { readable_parent: Repo };
895 roles = ["reader"];
896 permissions = ["read"];
897
898 "read" if "reader";
899 "read" if "read" on "readable_parent";
900 }
901
902 resource Repo {
903 relations = { writable_parent: Repo };
904 roles = ["writer"];
905 permissions = ["write"];
906
907 "write" if "writer";
908 "write" if "write" on "writable_parent";
909 }
910
911 has_role(_: Actor, _: String, _: Resource);
912 has_relation(subject: Repo, "writable_parent", object: Repo) if
913 object.parent_id = subject.id;
914 has_relation(subject: Repo, "readable_parent", object: Repo) if
915 object.parent_id = subject.id;
916 "#;
917
918 p.load_str(valid_policy).unwrap();
919 {
922 let blocks = &p.kb.read().unwrap().resource_blocks;
923 let declarations = blocks.declarations.get(&term!(sym!("Repo"))).unwrap();
924 assert_eq!(declarations.len(), 6);
925 let shorthand_rules = blocks.shorthand_rules.get(&term!(sym!("Repo"))).unwrap();
926 assert_eq!(shorthand_rules.len(), 4);
927 }
928 p.clear_rules();
929
930 let valid_policy = r#"
932 resource Repo {
933 relations = { readable_parent: Repo, writable_parent: Repo };
934 roles = ["reader", "writer"];
935 permissions = ["read", "write"];
936
937 "read" if "reader";
938 "write" if "writer";
939
940 "read" if "read" on "readable_parent";
941 "write" if "write" on "writable_parent";
942 }
943
944 resource Repo {
945 relations = { readable_parent: Repo, writable_parent: Repo };
946 roles = ["reader", "writer"];
947 permissions = ["read", "write"];
948
949 "reader" if "writer";
950
951 "read" if "reader" on "readable_parent";
952 "write" if "writer" on "writable_parent";
953 }
954
955 has_role(_: Actor, _: String, _: Resource);
956 has_relation(subject: Repo, "writable_parent", object: Repo) if
957 object.parent_id = subject.id;
958 has_relation(subject: Repo, "readable_parent", object: Repo) if
959 object.parent_id = subject.id;
960 "#;
961 p.load_str(valid_policy).unwrap();
962 {
963 let blocks = &p.kb.read().unwrap().resource_blocks;
964 let declarations = blocks.declarations.get(&term!(sym!("Repo"))).unwrap();
965 assert_eq!(declarations.len(), 6);
966 let shorthand_rules = blocks.shorthand_rules.get(&term!(sym!("Repo"))).unwrap();
967 assert_eq!(shorthand_rules.len(), 7);
968 }
969 }
970
971 #[test]
972 fn test_resource_block_declarations_overwriting() {
973 let p = Polar::new();
974
975 let repo_instance = ExternalInstance {
976 instance_id: 1,
977 constructor: None,
978 repr: None,
979 class_repr: None,
980 class_id: None,
981 };
982 let repo_term = term!(Value::ExternalInstance(repo_instance.clone()));
983 let repo_name = sym!("Repo");
984 p.register_constant(repo_name.clone(), repo_term).unwrap();
985 p.register_mro(repo_name, vec![repo_instance.instance_id])
986 .unwrap();
987
988 let valid_policy = r#"
990 resource Repo {
991 roles = ["reader"];
992 }
993
994 resource Repo {
995 roles = ["reader"];
996 }
997
998 has_role(actor: Actor, _role: String, repo: Repo) if
999 repo in actor.repos;
1000 "#;
1001 assert!(p.load_str(valid_policy).is_ok());
1002 p.clear_rules();
1003
1004 let invalid_policy = r#"
1006 resource Repo {
1007 roles = ["reader"];
1008 }
1009
1010 resource Repo {
1011 permissions = ["reader"];
1012 }
1013
1014 has_role(actor: Actor, _role: String, repo: Repo) if
1015 repo in actor.repos;
1016 "#;
1017
1018 expect_error(
1019 &p,
1020 invalid_policy,
1021 r#"Cannot overwrite existing role declaration "reader" in resource Repo with permission"#,
1022 );
1023
1024 let invalid_policy = r#"
1026 resource Repo {
1027 roles = ["reader"];
1028 }
1029
1030 resource Repo {
1031 relations = { reader: Repo };
1032 }
1033
1034 has_role(actor: Actor, _role: String, repo: Repo) if
1035 repo in actor.repos;
1036 has_relation(subject: Repo, "reader", object: Repo) if
1037 object.parent_id = subject.id;
1038 "#;
1039
1040 expect_error(
1041 &p,
1042 invalid_policy,
1043 r#"Cannot overwrite existing role declaration "reader" in resource Repo with relation"#,
1044 );
1045
1046 let invalid_policy = r#"
1048 actor User {}
1049 resource Repo {
1050 relations = { reader: Repo };
1051 }
1052
1053 resource Repo {
1054 relations = { reader: User };
1055 }
1056
1057 has_role(actor: Actor, _role: String, repo: Repo) if
1058 repo in actor.repos;
1059 has_relation(subject: Repo, "reader", object: Repo) if
1060 object.parent_id = subject.id;
1061 "#;
1062 let user_instance = ExternalInstance {
1063 instance_id: 2,
1064 constructor: None,
1065 repr: None,
1066 class_repr: None,
1067 class_id: None,
1068 };
1069 let user_term = term!(Value::ExternalInstance(user_instance.clone()));
1070 let user_name = sym!("User");
1071 p.register_constant(user_name.clone(), user_term).unwrap();
1072 p.register_mro(user_name, vec![user_instance.instance_id])
1073 .unwrap();
1074 expect_error(
1075 &p,
1076 invalid_policy,
1077 r#"Cannot overwrite existing relation declaration "reader" in resource Repo with relation"#,
1078 );
1079 }
1080
1081 #[test]
1082 fn test_resource_block_with_undeclared_local_shorthand_rule_head() {
1083 let p = Polar::new();
1084 p.register_constant(sym!("Org"), term!("unimportant"))
1085 .unwrap();
1086 expect_error(
1087 &p,
1088 r#"resource Org{"member" if "owner";}"#,
1089 r#"Undeclared term "member" referenced in rule in 'Org' resource block. Did you mean to declare it as a role, permission, or relation?"#,
1090 );
1091 }
1092
1093 #[test]
1094 fn test_resource_block_with_undeclared_local_shorthand_rule_body() {
1095 let p = Polar::new();
1096 p.register_constant(sym!("Org"), term!("unimportant"))
1097 .unwrap();
1098 expect_error(
1099 &p,
1100 r#"resource Org {
1101 roles=["member"];
1102 "member" if "owner";
1103 }"#,
1104 r#"Undeclared term "owner" referenced in rule in 'Org' resource block. Did you mean to declare it as a role, permission, or relation?"#,
1105 );
1106 }
1107
1108 #[test]
1109 fn test_resource_block_with_undeclared_nonlocal_shorthand_rule_body() {
1110 let p = Polar::new();
1111 p.register_constant(sym!("Repo"), term!("unimportant"))
1112 .unwrap();
1113 p.register_constant(sym!("Org"), term!("unimportant"))
1114 .unwrap();
1115
1116 expect_error(
1117 &p,
1118 r#"resource Repo {
1119 roles = ["writer"];
1120 relations = { parent: Org };
1121 "writer" if "owner" on "parent";
1122 }"#,
1123 r#"Repo: Relation "parent" in rule body `"owner" on "parent"` has type 'Org', but no such resource block exists. Try declaring one: `resource Org {}`"#,
1124 );
1125
1126 expect_error(
1127 &p,
1128 r#"resource Repo {
1129 roles = ["writer"];
1130 relations = { parent: Org };
1131 "writer" if "owner" on "parent";
1132 }
1133 resource Org {}"#,
1134 r#"Repo: Term "owner" not declared in related resource block 'Org'. Did you mean to declare it as a role, permission, or relation in the 'Org' resource block?"#,
1135 );
1136 }
1137
1138 #[test]
1139 #[ignore = "probably easier after the entity PR goes in"]
1140 fn test_resource_block_resource_relations_can_only_appear_after_on() {
1141 let p = Polar::new();
1142 p.register_constant(sym!("Repo"), term!("unimportant"))
1143 .unwrap();
1144 expect_error(
1145 &p,
1146 r#"resource Repo {
1147 roles = ["owner"];
1148 relations = { parent: Org };
1149 "parent" if "owner";
1150 }"#,
1151 r#"Repo: resource relation "parent" can only appear in a rule body following the keyword 'on'."#,
1152 );
1153 }
1154
1155 #[test]
1156 #[ignore = "outside scope of current task; leaving test here to be implemented later"]
1157 fn test_resource_block_resource_relations_are_the_only_thing_that_can_appear_after_on() {
1158 let p = Polar::new();
1159 p.register_constant(sym!("Repo"), term!("unimportant"))
1160 .unwrap();
1161 expect_error(
1162 &p,
1163 r#"resource Repo {
1164 roles = ["writer"];
1165 "writer" if "owner" on "parent";
1166 }"#,
1167 r#"Repo: term "parent" must be declared as a relation: `relations = { parent: <SomeResource> };`"#,
1168 );
1169 }
1170
1171 #[test]
1172 #[ignore = "not yet implemented"]
1173 fn test_resource_block_with_circular_shorthand_rules() {
1174 let p = Polar::new();
1175 p.register_constant(sym!("Repo"), term!("unimportant"))
1176 .unwrap();
1177 let policy = r#"resource Repo {
1178 roles = [ "writer" ];
1179 "writer" if "writer";
1180 }"#;
1181 panic!("{}", p.load_str(policy).unwrap_err());
1182
1183 }
1198
1199 #[test]
1200 fn test_resource_block_with_unregistered_relation_type() {
1201 let p = Polar::new();
1202 p.register_constant(sym!("Repo"), term!("unimportant"))
1203 .unwrap();
1204 let policy = r#"resource Repo { relations = { parent: Org }; }"#;
1205 expect_error(&p, policy, "Unregistered class: Org");
1206 p.register_constant(sym!("Org"), term!("unimportant"))
1207 .unwrap();
1208 p.load_str(policy).unwrap();
1209 }
1210
1211 #[test]
1212 fn test_resource_block_with_clashing_declarations() {
1213 let p = Polar::new();
1214 p.register_constant(sym!("Org"), term!("unimportant"))
1215 .unwrap();
1216
1217 expect_error(
1218 &p,
1219 r#"resource Org{
1220 roles = ["egg","egg"];
1221 "egg" if "egg";
1222 }"#,
1223 r#"Cannot overwrite existing role declaration "egg" in resource Org with role"#,
1224 );
1225
1226 expect_error(
1227 &p,
1228 r#"resource Org{
1229 roles = ["egg","tootsie"];
1230 permissions = ["spring","egg"];
1231
1232 "egg" if "tootsie";
1233 "tootsie" if "spring";
1234 }"#,
1235 r#"Cannot overwrite existing role declaration "egg" in resource Org with permission"#,
1236 );
1237
1238 expect_error(
1239 &p,
1240 r#"resource Org{
1241 permissions = [ "egg" ];
1242 relations = { egg: Roll };
1243 }"#,
1244 r#"Cannot overwrite existing permission declaration "egg" in resource Org with relation"#,
1245 );
1246 }
1247
1248 #[test]
1249 fn test_resource_block_parsing_permutations() {
1250 let roles = r#"roles = ["writer", "reader"];"#;
1252 let permissions = r#"permissions = ["push", "pull"];"#;
1253 let relations = r#"relations = { creator: User, parent: Org };"#;
1254 let shorthand_rules = vec![
1255 r#""pull" if "reader";"#,
1256 r#""push" if "writer";"#,
1257 r#""writer" if "creator";"#,
1258 r#""reader" if "member" on "parent";"#,
1259 ];
1260
1261 let block = ResourceBlock {
1263 block_type: BlockType::Resource,
1264 resource: term!(sym!("Repo")),
1265 roles: Some(term!(["writer", "reader"])),
1266 permissions: Some(term!(["push", "pull"])),
1267 relations: Some(term!(btreemap! {
1268 sym!("creator") => term!(sym!("User")),
1269 sym!("parent") => term!(sym!("Org")),
1270 })),
1271 shorthand_rules: vec![
1272 ShorthandRule {
1274 head: term!("pull"),
1275 body: (term!("reader"), None),
1276 },
1277 ShorthandRule {
1278 head: term!("push"),
1279 body: (term!("writer"), None),
1280 },
1281 ShorthandRule {
1282 head: term!("writer"),
1283 body: (term!("creator"), None),
1284 },
1285 ShorthandRule {
1286 head: term!("reader"),
1287 body: (term!("member"), Some((term!(sym!("on")), term!("parent")))),
1288 },
1289 ],
1290 };
1291
1292 let equal = |line: &Line, expected: &ResourceBlock| match line {
1295 Line::ResourceBlock {
1296 keyword,
1297 resource,
1298 productions,
1299 } => {
1300 let (parsed, _) = resource_block_from_productions(
1301 keyword.clone(),
1302 resource.clone(),
1303 productions.clone(),
1304 );
1305 let parsed_shorthand_rules: HashSet<&ShorthandRule> =
1306 HashSet::from_iter(&parsed.shorthand_rules);
1307 let expected_shorthand_rules = HashSet::from_iter(&expected.shorthand_rules);
1308 parsed.resource == expected.resource
1309 && parsed.roles == expected.roles
1310 && parsed.permissions == expected.permissions
1311 && parsed.relations == expected.relations
1312 && parsed_shorthand_rules == expected_shorthand_rules
1313 }
1314 _ => false,
1315 };
1316
1317 let test_case = |mut parts: Vec<&str>, expected: &ResourceBlock| {
1318 for permutation in Heap::new(&mut parts) {
1319 let mut policy = "resource Repo {\n".to_owned();
1320 policy += &permutation.join("\n");
1321 policy += "}";
1322 assert!(equal(
1323 &parse_lines(Source::new(policy)).unwrap()[0],
1324 expected
1325 ));
1326 }
1327 };
1328
1329 let test_cases = |parts: Vec<&str>, expected: &ResourceBlock| {
1331 let mut parts_with_shorthand_rules = parts.clone();
1332 parts_with_shorthand_rules.append(&mut shorthand_rules.clone());
1333 test_case(parts_with_shorthand_rules, expected);
1334
1335 let expected_without_shorthand_rules = ResourceBlock {
1336 shorthand_rules: vec![],
1337 ..expected.clone()
1338 };
1339 test_case(parts, &expected_without_shorthand_rules);
1340 };
1341
1342 test_cases(vec![roles, permissions, relations], &block);
1346
1347 let expected = ResourceBlock {
1349 relations: None,
1350 ..block.clone()
1351 };
1352 test_cases(vec![roles, permissions], &expected);
1353
1354 let expected = ResourceBlock {
1356 permissions: None,
1357 ..block.clone()
1358 };
1359 test_cases(vec![roles, relations], &expected);
1360
1361 let expected = ResourceBlock {
1363 roles: None,
1364 ..block.clone()
1365 };
1366 test_cases(vec![permissions, relations], &expected);
1367
1368 let expected = ResourceBlock {
1370 permissions: None,
1371 relations: None,
1372 ..block.clone()
1373 };
1374 test_cases(vec![roles], &expected);
1375
1376 let expected = ResourceBlock {
1378 roles: None,
1379 relations: None,
1380 ..block.clone()
1381 };
1382 test_cases(vec![permissions], &expected);
1383
1384 let expected = ResourceBlock {
1386 roles: None,
1387 permissions: None,
1388 ..block.clone()
1389 };
1390 test_cases(vec![relations], &expected);
1391
1392 let expected = ResourceBlock {
1394 roles: None,
1395 permissions: None,
1396 relations: None,
1397 ..block
1398 };
1399 test_cases(vec![], &expected);
1400 }
1401
1402 #[test]
1403 fn test_resource_block_declaration_keywords() {
1404 let p = Polar::new();
1405 expect_error(
1406 &p,
1407 r#"resource Org{roles={};}"#,
1408 r#"Expected 'roles' declaration to be a list of strings; found a dictionary"#,
1409 );
1410 expect_error(
1411 &p,
1412 r#"resource Org{relations=[];}"#,
1413 r#"Expected 'relations' declaration to be a dictionary; found a list"#,
1414 );
1415 expect_error(
1416 &p,
1417 r#"resource Org{foo=[];}"#,
1418 r#"Unexpected declaration 'foo'. Did you mean for this to be 'roles = [ ... ];' or 'permissions = [ ... ];'?"#,
1419 );
1420 expect_error(
1421 &p,
1422 r#"resource Org{foo={};}"#,
1423 r#"Unexpected declaration 'foo'. Did you mean for this to be 'relations = { ... };'?"#,
1424 );
1425 }
1426
1427 #[test]
1428 fn test_resource_block_relation_keywords() {
1429 let p = Polar::new();
1430 p.register_constant(sym!("Org"), term!("unimportant"))
1431 .unwrap();
1432 expect_error(
1433 &p,
1434 r#"resource Org {
1435 roles = ["foo", "bar"];
1436 relations = { baz: Org };
1437 "foo" if "bar" onn "baz";
1438 }"#,
1439 "Unexpected relation keyword 'onn'. Did you mean 'on'?",
1440 );
1441 }
1442
1443 #[test]
1444 fn test_resource_block_types() {
1445 let p = Polar::new();
1446
1447 expect_error(
1448 &p,
1449 "Org{}",
1450 "Expected 'actor' or 'resource' but found nothing.",
1451 );
1452
1453 expect_error(
1454 &p,
1455 "seahorse Org{}",
1456 "Expected 'actor' or 'resource' but found 'seahorse'.",
1457 );
1458 }
1459
1460 #[test]
1461 fn test_resource_block_declaration_keywords_are_not_reserved_words() {
1462 let p = Polar::new();
1463 p.load_str(
1464 "on(actor, resource, roles, permissions, relations) if on(actor, resource, roles, permissions, relations);",
1465 )
1466 .unwrap();
1467 }
1468
1469 #[test]
1473 #[ignore = "unimplemented"]
1474 fn test_resource_block_union_types_are_not_constructable() {
1475 let p = Polar::new();
1476 let q = p.new_query(&format!("new {}()", ACTOR_UNION_NAME), false);
1477 let msg = match q {
1478 Err(PolarError(Validation(ValidationError::ResourceBlock { msg, .. }))) => msg,
1479 Err(e) => panic!("{}", e),
1480 _ => panic!("succeeded when I should've failed"),
1481 };
1482 assert_eq!(msg, "hi");
1483 }
1484
1485 #[test]
1486 fn test_union_type_matches() {
1487 let polar = Polar::new();
1489 polar
1490 .register_constant(sym!("User"), term!("unimportant"))
1491 .unwrap();
1492 polar.load_str("actor User {}").unwrap();
1493 let query = polar.new_query(
1494 &format!("{} matches {}", ACTOR_UNION_NAME, ACTOR_UNION_NAME),
1495 false,
1496 );
1497 let next_event = query.unwrap().next_event().unwrap();
1498 assert!(matches!(next_event, QueryEvent::Result { .. }));
1499
1500 let polar = Polar::new();
1503 polar
1504 .register_constant(sym!("User"), term!("unimportant"))
1505 .unwrap();
1506 polar
1507 .register_constant(sym!("Repo"), term!("unimportant"))
1508 .unwrap();
1509 polar.load_str("actor User {} resource Repo {}").unwrap();
1510 let query = polar.new_query(
1511 &format!("not {} matches {}", ACTOR_UNION_NAME, RESOURCE_UNION_NAME),
1512 false,
1513 );
1514 let next_event = query.unwrap().next_event().unwrap();
1515 assert!(matches!(next_event, QueryEvent::Result { .. }));
1516 }
1517
1518 #[test]
1519 fn test_union_type_names_are_reserved() {
1520 let polar = Polar::new();
1521 let err = polar
1522 .register_constant(sym!(ACTOR_UNION_NAME), term!("unimportant"))
1523 .expect_err("Expected register_constant to throw error.");
1524 assert!(matches!(
1525 err.0,
1526 Runtime(RuntimeError::InvalidRegistration { .. })
1527 ));
1528 }
1529
1530 #[test]
1531 fn test_validate_rules_with_union_type_specializers() {
1532 let mut kb = KnowledgeBase::new();
1533 kb.register_constant(
1534 sym!("Fruit"),
1535 term!(Value::ExternalInstance(ExternalInstance {
1536 instance_id: 1,
1537 constructor: None,
1538 repr: None,
1539 class_repr: None,
1540 class_id: None,
1541 })),
1542 )
1543 .unwrap();
1544 kb.register_constant(
1545 sym!("Citrus"),
1546 term!(Value::ExternalInstance(ExternalInstance {
1547 instance_id: 2,
1548 constructor: None,
1549 repr: None,
1550 class_repr: None,
1551 class_id: None,
1552 })),
1553 )
1554 .unwrap();
1555 kb.register_constant(
1556 sym!("Orange"),
1557 term!(Value::ExternalInstance(ExternalInstance {
1558 instance_id: 3,
1559 constructor: None,
1560 repr: None,
1561 class_repr: None,
1562 class_id: None,
1563 })),
1564 )
1565 .unwrap();
1566 kb.add_mro(sym!("Fruit"), vec![1]).unwrap();
1567 kb.add_mro(sym!("Citrus"), vec![2, 1]).unwrap();
1569 kb.add_mro(sym!("Orange"), vec![3, 2, 1]).unwrap();
1571
1572 kb.register_constant(
1573 sym!("User"),
1574 term!(Value::ExternalInstance(ExternalInstance {
1575 instance_id: 4,
1576 constructor: None,
1577 repr: None,
1578 class_repr: None,
1579 class_id: None,
1580 })),
1581 )
1582 .unwrap();
1583 kb.add_mro(sym!("User"), vec![4]).unwrap();
1584
1585 kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1587 kb.resource_blocks.actors.insert(term!(sym!("User")));
1589
1590 kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1592 kb.add_rule(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1593 assert!(kb.validate_rules().is_empty());
1594
1595 kb.clear_rules();
1596 kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1597 kb.resource_blocks.actors.insert(term!(sym!("User")));
1598
1599 kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1604 kb.add_rule(rule!("f", ["x"; instance!(sym!(ACTOR_UNION_NAME))]));
1605 let diagnostic = kb.validate_rules().into_iter().next().unwrap();
1606 let error = diagnostic.unwrap_error().unwrap_validation();
1607 assert!(matches!(error, ValidationError::InvalidRule { .. }));
1608
1609 kb.clear_rules();
1610 kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1611 kb.resource_blocks.actors.insert(term!(sym!("User")));
1612
1613 kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1615 kb.add_rule(rule!("f", ["x"; instance!(sym!("Citrus"))]));
1616 assert!(kb.validate_rules().is_empty());
1617
1618 kb.clear_rules();
1619 kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1620 kb.resource_blocks.actors.insert(term!(sym!("User")));
1621
1622 kb.add_rule_type(rule!("f", ["x"; instance!(sym!(ACTOR_UNION_NAME))]));
1627 kb.add_rule(rule!("f", ["x"; instance!(sym!("Citrus"))]));
1628 let diagnostic = kb.validate_rules().into_iter().next().unwrap();
1629 let error = diagnostic.unwrap_error().unwrap_validation();
1630 assert!(matches!(error, ValidationError::InvalidRule { .. }));
1631
1632 kb.clear_rules();
1633 kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1634 kb.resource_blocks.actors.insert(term!(sym!("User")));
1635
1636 kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1638 kb.add_rule(rule!("f", ["x"; instance!(sym!("Orange"))]));
1639 assert!(kb.validate_rules().is_empty());
1640
1641 kb.clear_rules();
1642 kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1643 kb.resource_blocks.actors.insert(term!(sym!("User")));
1644
1645 kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1647 kb.add_rule(rule!("f", ["x"; instance!(sym!("Fruit"))]));
1648 let diagnostic = kb.validate_rules().into_iter().next().unwrap();
1649 let error = diagnostic.unwrap_error().unwrap_validation();
1650 assert!(matches!(error, ValidationError::InvalidRule { .. }));
1651
1652 }
1678
1679 #[test]
1688 fn test_create_resource_specific_rule_types() -> PolarResult<()> {
1689 let policy = r#"
1690 resource Organization {
1691 roles = ["member"];
1692 }
1693
1694 resource Repository {
1695 roles = ["reader"];
1696 relations = {parent: Organization};
1697 "reader" if "member" on "parent";
1698 }
1699
1700 has_relation(organization: Organization, "parent", repository: Repository) if
1701 repository.org_id = organization.id;
1702
1703 has_role(user: Actor, _role: String, organization: Organization) if
1704 organization.id in user.org_ids;
1705 "#;
1706
1707 let polar = Polar::new();
1708
1709 let repo_instance = ExternalInstance {
1710 instance_id: 1,
1711 constructor: None,
1712 repr: None,
1713 class_repr: None,
1714 class_id: None,
1715 };
1716 let repo_term = term!(Value::ExternalInstance(repo_instance.clone()));
1717 let repo_name = sym!("Repository");
1718 polar.register_constant(repo_name.clone(), repo_term)?;
1719 polar.register_mro(repo_name.clone(), vec![repo_instance.instance_id])?;
1720
1721 let org_instance = ExternalInstance {
1722 instance_id: 2,
1723 constructor: None,
1724 repr: None,
1725 class_repr: None,
1726 class_id: None,
1727 };
1728 let org_term = term!(Value::ExternalInstance(org_instance.clone()));
1729 let org_name = sym!("Organization");
1730 polar.register_constant(org_name.clone(), org_term)?;
1731 polar.register_mro(org_name.clone(), vec![org_instance.instance_id])?;
1732
1733 polar.load_str(policy)?;
1734
1735 let kb = polar.kb.read().unwrap();
1736
1737 let has_role_rule_types = kb.get_rule_types(&sym!("has_role")).unwrap();
1738 let expected = rule!("has_role", ["actor"; instance!(ACTOR_UNION_NAME), "role"; instance!("String"), "resource"; instance!(RESOURCE_UNION_NAME)]);
1740 assert_eq!(1, has_role_rule_types.len());
1741 assert_eq!(has_role_rule_types[0], expected);
1742
1743 let has_relation_rule_types = kb.get_rule_types(&sym!("has_relation")).unwrap();
1744 let expected = rule!("has_relation", ["subject"; instance!(org_name), "parent", "object"; instance!(repo_name)]);
1746 assert_eq!(1, has_relation_rule_types.len());
1747 assert_eq!(has_relation_rule_types[0], expected,);
1748
1749 Ok(())
1750 }
1751
1752 #[test]
1755 fn test_create_resource_specific_rule_types_actor_roles() -> PolarResult<()> {
1756 let policy = r#"
1757 actor Team {
1758 roles = ["member", "owner"];
1759
1760 "member" if "owner";
1761 }
1762 "#;
1763
1764 let polar = Polar::new();
1765
1766 let team_instance = ExternalInstance {
1767 instance_id: 1,
1768 constructor: None,
1769 repr: None,
1770 class_repr: None,
1771 class_id: None,
1772 };
1773 let team_term = term!(Value::ExternalInstance(team_instance.clone()));
1774 let team_name = sym!("Team");
1775 polar.register_constant(team_name.clone(), team_term)?;
1776 polar.register_mro(team_name, vec![team_instance.instance_id])?;
1777
1778 polar.load_str(policy)?;
1779
1780 let kb = polar.kb.read().unwrap();
1781
1782 let has_role_rule_types = kb.get_rule_types(&sym!("has_role")).unwrap();
1783 let expected = rule!("has_role", ["actor"; instance!(ACTOR_UNION_NAME), "role"; instance!("String"), "resource"; instance!(RESOURCE_UNION_NAME)]);
1785 assert_eq!(1, has_role_rule_types.len());
1786 assert_eq!(has_role_rule_types[0], expected);
1787
1788 Ok(())
1789 }
1790}