github.com/openfga/openfga@v1.5.4-rc1/internal/graph/graph_test.go (about) 1 package graph 2 3 import ( 4 "context" 5 "sort" 6 "testing" 7 8 "github.com/google/go-cmp/cmp" 9 "github.com/google/go-cmp/cmp/cmpopts" 10 openfgav1 "github.com/openfga/api/proto/openfga/v1" 11 "github.com/stretchr/testify/require" 12 13 "github.com/openfga/openfga/pkg/testutils" 14 "github.com/openfga/openfga/pkg/typesystem" 15 ) 16 17 var ( 18 RelationshipEdgeTransformer = cmp.Transformer("Sort", func(in []*RelationshipEdge) []*RelationshipEdge { 19 out := append([]*RelationshipEdge(nil), in...) // Copy input to avoid mutating it 20 21 // Sort by Type and then by edge and then by tupleset relation 22 sort.SliceStable(out, func(i, j int) bool { 23 if out[i].Type != out[j].Type { 24 return out[i].Type < out[j].Type 25 } 26 27 if typesystem.GetRelationReferenceAsString(out[i].TargetReference) != typesystem.GetRelationReferenceAsString(out[j].TargetReference) { 28 return typesystem.GetRelationReferenceAsString(out[i].TargetReference) < typesystem.GetRelationReferenceAsString(out[j].TargetReference) 29 } 30 31 if out[i].TuplesetRelation != out[j].TuplesetRelation { 32 return out[i].TuplesetRelation < out[j].TuplesetRelation 33 } 34 35 return true 36 }) 37 38 return out 39 }) 40 ) 41 42 func TestRelationshipEdge_String(t *testing.T) { 43 for _, tc := range []struct { 44 name string 45 expected string 46 relationshipEdge RelationshipEdge 47 }{ 48 { 49 name: "TupleToUsersetEdge", 50 expected: "userset type:\"document\" relation:\"viewer\", type ttu, tupleset parent", 51 relationshipEdge: RelationshipEdge{ 52 Type: TupleToUsersetEdge, 53 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 54 TuplesetRelation: "parent", 55 TargetReferenceInvolvesIntersectionOrExclusion: false, 56 }, 57 }, 58 { 59 name: "ComputedUsersetEdge", 60 expected: "userset type:\"document\" relation:\"viewer\", type computed_userset", 61 relationshipEdge: RelationshipEdge{ 62 Type: ComputedUsersetEdge, 63 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 64 TargetReferenceInvolvesIntersectionOrExclusion: false, 65 }, 66 }, 67 { 68 name: "DirectEdge", 69 expected: "userset type:\"document\" relation:\"viewer\", type direct", 70 relationshipEdge: RelationshipEdge{ 71 Type: DirectEdge, 72 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 73 TargetReferenceInvolvesIntersectionOrExclusion: false, 74 }, 75 }, 76 } { 77 t.Run(tc.name, func(t *testing.T) { 78 require.Equal(t, tc.expected, tc.relationshipEdge.String()) 79 }) 80 } 81 } 82 83 func TestRelationshipEdgeType_String(t *testing.T) { 84 require.Equal(t, "direct", DirectEdge.String()) 85 require.Equal(t, "computed_userset", ComputedUsersetEdge.String()) 86 require.Equal(t, "ttu", TupleToUsersetEdge.String()) 87 require.Equal(t, "undefined", RelationshipEdgeType(4).String()) 88 } 89 90 func TestPrunedRelationshipEdges(t *testing.T) { 91 tests := []struct { 92 name string 93 model string 94 target *openfgav1.RelationReference 95 source *openfgav1.RelationReference 96 expected []*RelationshipEdge 97 }{ 98 { 99 name: "basic_intersection", 100 model: `model 101 schema 1.1 102 type user 103 104 type document 105 relations 106 define allowed: [user] 107 define viewer: [user] and allowed`, 108 target: typesystem.DirectRelationReference("document", "viewer"), 109 source: typesystem.DirectRelationReference("user", ""), 110 expected: []*RelationshipEdge{ 111 { 112 Type: DirectEdge, 113 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 114 TargetReferenceInvolvesIntersectionOrExclusion: true, 115 }, 116 }, 117 }, 118 { 119 name: "basic_intersection_through_ttu_1", 120 model: `model 121 schema 1.1 122 type user 123 124 type folder 125 relations 126 define allowed: [user] 127 define viewer: [user] and allowed 128 129 type document 130 relations 131 define parent: [folder] 132 define viewer: viewer from parent`, 133 target: typesystem.DirectRelationReference("document", "viewer"), 134 source: typesystem.DirectRelationReference("user", ""), 135 expected: []*RelationshipEdge{ 136 { 137 Type: DirectEdge, 138 TargetReference: typesystem.DirectRelationReference("folder", "viewer"), 139 TargetReferenceInvolvesIntersectionOrExclusion: true, 140 }, 141 }, 142 }, 143 { 144 name: "basic_intersection_through_ttu_2", 145 model: `model 146 schema 1.1 147 type user 148 149 type organization 150 relations 151 define allowed: [user] 152 define viewer: [user] and allowed 153 154 type folder 155 relations 156 define parent: [organization] 157 define viewer: viewer from parent 158 159 type document 160 relations 161 define parent: [folder] 162 define viewer: viewer from parent`, 163 target: typesystem.DirectRelationReference("document", "viewer"), 164 source: typesystem.DirectRelationReference("folder", "viewer"), 165 expected: []*RelationshipEdge{ 166 { 167 Type: TupleToUsersetEdge, 168 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 169 TuplesetRelation: "parent", 170 TargetReferenceInvolvesIntersectionOrExclusion: true, 171 }, 172 }, 173 }, 174 { 175 name: "basic_exclusion_through_ttu_1", 176 model: `model 177 schema 1.1 178 type user 179 180 type folder 181 relations 182 define writer: [user] 183 define editor: [user] 184 define viewer: writer but not editor 185 186 type document 187 relations 188 define parent: [folder] 189 define viewer: viewer from parent`, 190 target: typesystem.DirectRelationReference("document", "viewer"), 191 source: typesystem.DirectRelationReference("user", ""), 192 expected: []*RelationshipEdge{ 193 { 194 Type: DirectEdge, 195 TargetReference: typesystem.DirectRelationReference("folder", "writer"), 196 TargetReferenceInvolvesIntersectionOrExclusion: true, 197 }, 198 }, 199 }, 200 { 201 name: "basic_exclusion_through_ttu_2", 202 model: `model 203 schema 1.1 204 type user 205 206 type folder 207 relations 208 define writer: [user] 209 define editor: [user] 210 define viewer: writer but not editor 211 212 type document 213 relations 214 define parent: [folder] 215 define viewer: viewer from parent`, 216 target: typesystem.DirectRelationReference("document", "viewer"), 217 source: typesystem.DirectRelationReference("folder", "viewer"), 218 expected: []*RelationshipEdge{ 219 { 220 Type: TupleToUsersetEdge, 221 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 222 TuplesetRelation: "parent", 223 TargetReferenceInvolvesIntersectionOrExclusion: true, 224 }, 225 }, 226 }, 227 { 228 name: "ttu_with_indirect", 229 model: `model 230 schema 1.1 231 type user 232 type repo 233 relations 234 define admin: [user] or repo_admin from owner 235 define owner: [organization] 236 type organization 237 relations 238 define member: [user] or owner 239 define owner: [user] 240 define repo_admin: [user, organization#member]`, 241 target: typesystem.DirectRelationReference("repo", "admin"), 242 source: typesystem.DirectRelationReference("organization", "member"), 243 expected: []*RelationshipEdge{ 244 { 245 Type: DirectEdge, 246 TargetReference: typesystem.DirectRelationReference("organization", "repo_admin"), 247 TargetReferenceInvolvesIntersectionOrExclusion: false, 248 }, 249 }, 250 }, 251 } 252 253 for _, test := range tests { 254 t.Run(test.name, func(t *testing.T) { 255 model := testutils.MustTransformDSLToProtoWithID(test.model) 256 typesys := typesystem.New(model) 257 258 g := New(typesys) 259 260 edges, err := g.GetPrunedRelationshipEdges(test.target, test.source) 261 require.NoError(t, err) 262 263 cmpOpts := []cmp.Option{ 264 cmpopts.IgnoreUnexported(openfgav1.RelationReference{}), 265 RelationshipEdgeTransformer, 266 } 267 if diff := cmp.Diff(test.expected, edges, cmpOpts...); diff != "" { 268 t.Errorf("mismatch (-want +got):\n%s", diff) 269 } 270 }) 271 } 272 } 273 274 func TestRelationshipEdges(t *testing.T) { 275 tests := []struct { 276 name string 277 model string 278 authModel *openfgav1.AuthorizationModel // for models that have "self" or "this" at the end of the relation definition 279 target *openfgav1.RelationReference 280 source *openfgav1.RelationReference 281 expected []*RelationshipEdge 282 }{ 283 { 284 name: "direct_edge_through_ComputedUserset_with_multiple_type_restrictions", 285 model: `model 286 schema 1.1 287 type user 288 289 type group 290 relations 291 define member: [user, group#member] 292 293 type document 294 relations 295 define editor: [user, group#member] 296 define viewer: editor`, 297 target: typesystem.DirectRelationReference("document", "viewer"), 298 source: typesystem.DirectRelationReference("user", ""), 299 expected: []*RelationshipEdge{ 300 { 301 Type: DirectEdge, 302 TargetReference: typesystem.DirectRelationReference("document", "editor"), 303 TargetReferenceInvolvesIntersectionOrExclusion: false, 304 }, 305 { 306 Type: DirectEdge, 307 TargetReference: typesystem.DirectRelationReference("group", "member"), 308 TargetReferenceInvolvesIntersectionOrExclusion: false, 309 }, 310 }, 311 }, 312 { 313 name: "direct_edge_through_ComputedUserset", 314 model: `model 315 schema 1.1 316 type user 317 318 type document 319 relations 320 define editor: [user] 321 define viewer: editor`, 322 target: typesystem.DirectRelationReference("document", "viewer"), 323 source: typesystem.DirectRelationReference("user", ""), 324 expected: []*RelationshipEdge{ 325 { 326 Type: DirectEdge, 327 TargetReference: typesystem.DirectRelationReference("document", "editor"), 328 TargetReferenceInvolvesIntersectionOrExclusion: false, 329 }, 330 }, 331 }, 332 { 333 name: "direct_edge_through_TupleToUserset_with_multiple_type_restrictions", 334 model: `model 335 schema 1.1 336 type user 337 338 type group 339 relations 340 define member: [user] 341 342 type folder 343 relations 344 define viewer: [user, group#member] 345 346 type document 347 relations 348 define parent: [folder] 349 define viewer: [user] or viewer from parent`, 350 target: typesystem.DirectRelationReference("document", "viewer"), 351 source: typesystem.DirectRelationReference("user", ""), 352 expected: []*RelationshipEdge{ 353 { 354 Type: DirectEdge, 355 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 356 TargetReferenceInvolvesIntersectionOrExclusion: false, 357 }, 358 { 359 Type: DirectEdge, 360 TargetReference: typesystem.DirectRelationReference("folder", "viewer"), 361 TargetReferenceInvolvesIntersectionOrExclusion: false, 362 }, 363 { 364 Type: DirectEdge, 365 TargetReference: typesystem.DirectRelationReference("group", "member"), 366 TargetReferenceInvolvesIntersectionOrExclusion: false, 367 }, 368 }, 369 }, 370 { 371 name: "direct_edge_with_union_involving_self_and_computed_userset", 372 model: `model 373 schema 1.1 374 type user 375 376 type group 377 relations 378 define member: [user, group#member] 379 380 type document 381 relations 382 define editor: [user, group#member] 383 define viewer: [user] or editor`, 384 target: typesystem.DirectRelationReference("document", "viewer"), 385 source: typesystem.DirectRelationReference("user", ""), 386 expected: []*RelationshipEdge{ 387 { 388 Type: DirectEdge, 389 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 390 TargetReferenceInvolvesIntersectionOrExclusion: false, 391 }, 392 { 393 Type: DirectEdge, 394 TargetReference: typesystem.DirectRelationReference("document", "editor"), 395 TargetReferenceInvolvesIntersectionOrExclusion: false, 396 }, 397 { 398 Type: DirectEdge, 399 TargetReference: typesystem.DirectRelationReference("group", "member"), 400 TargetReferenceInvolvesIntersectionOrExclusion: false, 401 }, 402 }, 403 }, 404 { 405 name: "circular_reference", 406 model: `model 407 schema 1.1 408 type user 409 410 type team 411 relations 412 define member: [group#member] 413 414 type group 415 relations 416 define member: [user, team#member]`, 417 target: typesystem.DirectRelationReference("team", "member"), 418 source: typesystem.DirectRelationReference("user", ""), 419 expected: []*RelationshipEdge{ 420 { 421 Type: DirectEdge, 422 TargetReference: typesystem.DirectRelationReference("group", "member"), 423 TargetReferenceInvolvesIntersectionOrExclusion: false, 424 }, 425 }, 426 }, 427 { 428 name: "cyclical_parent/child_definition", 429 model: `model 430 schema 1.1 431 type user 432 433 type folder 434 relations 435 define parent: [folder] 436 define viewer: [user] or viewer from parent`, 437 target: typesystem.DirectRelationReference("folder", "viewer"), 438 source: typesystem.DirectRelationReference("user", ""), 439 expected: []*RelationshipEdge{ 440 { 441 Type: DirectEdge, 442 TargetReference: typesystem.DirectRelationReference("folder", "viewer"), 443 TargetReferenceInvolvesIntersectionOrExclusion: false, 444 }, 445 }, 446 }, 447 { 448 name: "no_graph_relationship_connectivity", 449 model: `model 450 schema 1.1 451 type user 452 453 type team 454 relations 455 define member: [team#member]`, 456 target: typesystem.DirectRelationReference("team", "member"), 457 source: typesystem.DirectRelationReference("user", ""), 458 expected: []*RelationshipEdge{}, 459 }, 460 { 461 name: "test1", 462 model: `model 463 schema 1.1 464 type user 465 466 type group 467 relations 468 define member: [user] 469 470 type folder 471 relations 472 define viewer: [user, group#member] 473 474 type document 475 relations 476 define parent: [folder] 477 define viewer: viewer from parent`, 478 target: typesystem.DirectRelationReference("document", "viewer"), 479 source: typesystem.DirectRelationReference("user", ""), 480 expected: []*RelationshipEdge{ 481 { 482 Type: DirectEdge, 483 TargetReference: typesystem.DirectRelationReference("folder", "viewer"), 484 TargetReferenceInvolvesIntersectionOrExclusion: false, 485 }, 486 { 487 Type: DirectEdge, 488 TargetReference: typesystem.DirectRelationReference("group", "member"), 489 TargetReferenceInvolvesIntersectionOrExclusion: false, 490 }, 491 }, 492 }, 493 { 494 name: "test2", 495 model: `model 496 schema 1.1 497 type user 498 499 type group 500 relations 501 define member: [user] 502 503 type folder 504 relations 505 define viewer: [user, group#member] 506 507 type document 508 relations 509 define parent: [folder] 510 define viewer: viewer from parent`, 511 target: typesystem.DirectRelationReference("document", "viewer"), 512 source: typesystem.DirectRelationReference("group", "member"), 513 expected: []*RelationshipEdge{ 514 { 515 Type: DirectEdge, 516 TargetReference: typesystem.DirectRelationReference("folder", "viewer"), 517 TargetReferenceInvolvesIntersectionOrExclusion: false, 518 }, 519 }, 520 }, 521 { 522 name: "test3", 523 model: `model 524 schema 1.1 525 type user 526 527 type folder 528 relations 529 define viewer: [user] 530 531 type document 532 relations 533 define parent: [folder] 534 define viewer: viewer from parent`, 535 target: typesystem.DirectRelationReference("document", "viewer"), 536 source: typesystem.DirectRelationReference("folder", "viewer"), 537 expected: []*RelationshipEdge{ 538 { 539 Type: TupleToUsersetEdge, 540 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 541 TuplesetRelation: "parent", 542 TargetReferenceInvolvesIntersectionOrExclusion: false, 543 }, 544 }, 545 }, 546 { 547 name: "undefined_relation_on_one_type_involved_in_a_ttu", 548 model: `model 549 schema 1.1 550 type user 551 type organization 552 553 type folder 554 relations 555 define viewer: [user] 556 557 type document 558 relations 559 define parent: [folder, organization] 560 define viewer: viewer from parent`, 561 target: typesystem.DirectRelationReference("document", "viewer"), 562 source: typesystem.DirectRelationReference("user", ""), 563 expected: []*RelationshipEdge{ 564 { 565 Type: DirectEdge, 566 TargetReference: typesystem.DirectRelationReference("folder", "viewer"), 567 TargetReferenceInvolvesIntersectionOrExclusion: false, 568 }, 569 }, 570 }, 571 { 572 name: "nested_group_membership_returns_only_top-level_edge", 573 model: `model 574 schema 1.1 575 type user 576 577 type group 578 relations 579 define member: [user, group#member]`, 580 target: typesystem.DirectRelationReference("group", "member"), 581 source: typesystem.DirectRelationReference("user", ""), 582 expected: []*RelationshipEdge{ 583 { 584 Type: DirectEdge, 585 TargetReference: typesystem.DirectRelationReference("group", "member"), 586 TargetReferenceInvolvesIntersectionOrExclusion: false, 587 }, 588 }, 589 }, 590 { 591 name: "edges_for_non-assignable_relation", 592 model: `model 593 schema 1.1 594 type organization 595 relations 596 define viewer: [organization] 597 define can_view: viewer 598 599 type document 600 relations 601 define parent: [organization] 602 define view: can_view from parent`, 603 target: typesystem.DirectRelationReference("document", "view"), 604 source: typesystem.DirectRelationReference("organization", ""), 605 expected: []*RelationshipEdge{ 606 { 607 Type: DirectEdge, 608 TargetReference: typesystem.DirectRelationReference("organization", "viewer"), 609 TargetReferenceInvolvesIntersectionOrExclusion: false, 610 }, 611 }, 612 }, 613 { 614 name: "user_is_a_subset_of_user_*", 615 model: `model 616 schema 1.1 617 type user 618 619 type document 620 relations 621 define viewer: [user:*]`, 622 target: typesystem.DirectRelationReference("document", "viewer"), 623 source: typesystem.DirectRelationReference("user", ""), 624 expected: []*RelationshipEdge{ 625 { 626 Type: DirectEdge, 627 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 628 TargetReferenceInvolvesIntersectionOrExclusion: false, 629 }, 630 }, 631 }, 632 { 633 name: "user_*_is_not_a_subset_of_user", 634 model: `model 635 schema 1.1 636 type user 637 638 type document 639 relations 640 define viewer: [user]`, 641 target: typesystem.DirectRelationReference("document", "viewer"), 642 source: typesystem.WildcardRelationReference("user"), 643 expected: []*RelationshipEdge{}, 644 }, 645 { 646 name: "user_*_is_related_to_user_*", 647 model: `model 648 schema 1.1 649 type user 650 651 type document 652 relations 653 define viewer: [user:*]`, 654 target: typesystem.DirectRelationReference("document", "viewer"), 655 source: typesystem.WildcardRelationReference("user"), 656 expected: []*RelationshipEdge{ 657 { 658 Type: DirectEdge, 659 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 660 TargetReferenceInvolvesIntersectionOrExclusion: false, 661 }, 662 }, 663 }, 664 { 665 name: "edges_involving_wildcard_in_types", 666 model: `model 667 schema 1.1 668 type user 669 670 type document 671 relations 672 define editor: [user:*] 673 define viewer: editor`, 674 target: typesystem.DirectRelationReference("document", "viewer"), 675 source: typesystem.DirectRelationReference("user", ""), 676 expected: []*RelationshipEdge{ 677 { 678 Type: DirectEdge, 679 TargetReference: typesystem.DirectRelationReference("document", "editor"), 680 TargetReferenceInvolvesIntersectionOrExclusion: false, 681 }, 682 }, 683 }, 684 { 685 name: "edges_involving_wildcard_in_source", 686 model: `model 687 schema 1.1 688 type user 689 690 type document 691 relations 692 define editor: [user] 693 define viewer: editor`, 694 target: typesystem.DirectRelationReference("document", "viewer"), 695 source: typesystem.WildcardRelationReference("user"), 696 expected: []*RelationshipEdge{}, 697 }, 698 { 699 name: "edges_involving_wildcards_1", 700 model: `model 701 schema 1.1 702 type user 703 type employee 704 type group 705 706 type document 707 relations 708 define relation1: [user:*] or relation2 or relation3 or relation4 709 define relation2: [group:*] 710 define relation3: [employee:*] 711 define relation4: [user]`, 712 target: typesystem.DirectRelationReference("document", "relation1"), 713 source: typesystem.DirectRelationReference("user", ""), 714 expected: []*RelationshipEdge{ 715 { 716 Type: DirectEdge, 717 TargetReference: typesystem.DirectRelationReference("document", "relation1"), 718 TargetReferenceInvolvesIntersectionOrExclusion: false, 719 }, 720 { 721 Type: DirectEdge, 722 TargetReference: typesystem.DirectRelationReference("document", "relation4"), 723 TargetReferenceInvolvesIntersectionOrExclusion: false, 724 }, 725 }, 726 }, 727 { 728 name: "edges_involving_wildcards_2", 729 model: `model 730 schema 1.1 731 type user 732 733 type document 734 relations 735 define relation1: [user] or relation2 736 define relation2: [user:*]`, 737 target: typesystem.DirectRelationReference("document", "relation1"), 738 source: typesystem.WildcardRelationReference("user"), 739 expected: []*RelationshipEdge{ 740 { 741 Type: DirectEdge, 742 TargetReference: typesystem.DirectRelationReference("document", "relation2"), 743 TargetReferenceInvolvesIntersectionOrExclusion: false, 744 }, 745 }, 746 }, 747 { 748 name: "indirect_typed_wildcard", 749 model: `model 750 schema 1.1 751 type user 752 753 type group 754 relations 755 define member: [user:*] 756 757 type document 758 relations 759 define viewer: [group#member]`, 760 target: typesystem.DirectRelationReference("document", "viewer"), 761 source: typesystem.DirectRelationReference("user", ""), 762 expected: []*RelationshipEdge{ 763 { 764 Type: DirectEdge, 765 TargetReference: typesystem.DirectRelationReference("group", "member"), 766 TargetReferenceInvolvesIntersectionOrExclusion: false, 767 }, 768 }, 769 }, 770 { 771 name: "indirect_relationship_multiple_levels_deep", 772 model: `model 773 schema 1.1 774 type user 775 776 type team 777 relations 778 define member: [user] 779 780 type group 781 relations 782 define member: [user, team#member] 783 784 type document 785 relations 786 define viewer: [user:*, group#member]`, 787 target: typesystem.DirectRelationReference("document", "viewer"), 788 source: typesystem.DirectRelationReference("user", ""), 789 expected: []*RelationshipEdge{ 790 { 791 Type: DirectEdge, 792 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 793 TargetReferenceInvolvesIntersectionOrExclusion: false, 794 }, 795 { 796 Type: DirectEdge, 797 TargetReference: typesystem.DirectRelationReference("group", "member"), 798 TargetReferenceInvolvesIntersectionOrExclusion: false, 799 }, 800 { 801 Type: DirectEdge, 802 TargetReference: typesystem.DirectRelationReference("team", "member"), 803 TargetReferenceInvolvesIntersectionOrExclusion: false, 804 }, 805 }, 806 }, 807 { 808 name: "indirect_relationship_multiple_levels_deep_no_connectivity", 809 model: `model 810 schema 1.1 811 type user 812 type employee 813 814 type team 815 relations 816 define member: [employee] 817 818 type group 819 relations 820 define member: [team#member] 821 822 type document 823 relations 824 define viewer: [group#member]`, 825 target: typesystem.DirectRelationReference("document", "viewer"), 826 source: typesystem.DirectRelationReference("user", ""), 827 expected: []*RelationshipEdge{}, 828 }, 829 { 830 name: "edge_through_ttu_on_non-assignable_relation", 831 model: `model 832 schema 1.1 833 type organization 834 relations 835 define viewer: [organization] 836 define can_view: viewer 837 838 type document 839 relations 840 define parent: [organization] 841 define view: can_view from parent`, 842 target: typesystem.DirectRelationReference("document", "view"), 843 source: typesystem.DirectRelationReference("organization", "can_view"), 844 expected: []*RelationshipEdge{ 845 { 846 Type: TupleToUsersetEdge, 847 TargetReference: typesystem.DirectRelationReference("document", "view"), 848 TuplesetRelation: "parent", 849 TargetReferenceInvolvesIntersectionOrExclusion: false, 850 }, 851 }, 852 }, 853 { 854 name: "indirect_relation_through_ttu_on_non-assignable_relation", 855 model: `model 856 schema 1.1 857 type organization 858 relations 859 define viewer: [organization] 860 define can_view: viewer 861 862 type document 863 relations 864 define parent: [organization] 865 define view: can_view from parent`, 866 target: typesystem.DirectRelationReference("document", "view"), 867 source: typesystem.DirectRelationReference("organization", "viewer"), 868 expected: []*RelationshipEdge{ 869 { 870 Type: ComputedUsersetEdge, 871 TargetReference: typesystem.DirectRelationReference("organization", "can_view"), 872 TargetReferenceInvolvesIntersectionOrExclusion: false, 873 }, 874 }, 875 }, 876 { 877 name: "ttu_on_non-assignable_relation", 878 model: `model 879 schema 1.1 880 type organization 881 relations 882 define viewer: [organization] 883 define can_view: viewer 884 885 type document 886 relations 887 define parent: [organization] 888 define view: can_view from parent`, 889 target: typesystem.DirectRelationReference("document", "view"), 890 source: typesystem.DirectRelationReference("organization", "can_view"), 891 expected: []*RelationshipEdge{ 892 { 893 Type: TupleToUsersetEdge, 894 TargetReference: typesystem.DirectRelationReference("document", "view"), 895 TuplesetRelation: "parent", 896 TargetReferenceInvolvesIntersectionOrExclusion: false, 897 }, 898 }, 899 }, 900 { 901 name: "multiple_indirect_non-assignable_relations_through_ttu", 902 model: `model 903 schema 1.1 904 type organization 905 relations 906 define viewer: [organization] 907 define view: viewer 908 909 type folder 910 relations 911 define parent: [organization] 912 define view: view from parent 913 914 type other 915 916 type document 917 relations 918 define parent: [folder, other] 919 define view: view from parent`, 920 target: typesystem.DirectRelationReference("document", "view"), 921 source: typesystem.DirectRelationReference("organization", ""), 922 expected: []*RelationshipEdge{ 923 { 924 Type: DirectEdge, 925 TargetReference: typesystem.DirectRelationReference("organization", "viewer"), 926 TargetReferenceInvolvesIntersectionOrExclusion: false, 927 }, 928 }, 929 }, 930 { 931 name: "multiple_directly_assignable_relationships_through_unions", 932 model: `model 933 schema 1.1 934 type user 935 936 type team 937 relations 938 define admin: [user] 939 define member: [user, team#member] or admin 940 941 type trial 942 relations 943 define editor: [user, team#member] or owner 944 define owner: [user] 945 define viewer: [user, team#member] or editor`, 946 target: typesystem.DirectRelationReference("trial", "viewer"), 947 source: typesystem.DirectRelationReference("user", ""), 948 expected: []*RelationshipEdge{ 949 { 950 Type: DirectEdge, 951 TargetReference: typesystem.DirectRelationReference("trial", "viewer"), 952 TargetReferenceInvolvesIntersectionOrExclusion: false, 953 }, 954 { 955 Type: DirectEdge, 956 TargetReference: typesystem.DirectRelationReference("trial", "editor"), 957 TargetReferenceInvolvesIntersectionOrExclusion: false, 958 }, 959 { 960 Type: DirectEdge, 961 TargetReference: typesystem.DirectRelationReference("trial", "owner"), 962 TargetReferenceInvolvesIntersectionOrExclusion: false, 963 }, 964 { 965 Type: DirectEdge, 966 TargetReference: typesystem.DirectRelationReference("team", "member"), 967 TargetReferenceInvolvesIntersectionOrExclusion: false, 968 }, 969 { 970 Type: DirectEdge, 971 TargetReference: typesystem.DirectRelationReference("team", "admin"), 972 TargetReferenceInvolvesIntersectionOrExclusion: false, 973 }, 974 }, 975 }, 976 { 977 name: "multiple_assignable_and_non-assignable_computed_usersets", 978 model: `model 979 schema 1.1 980 type user 981 982 type team 983 relations 984 define admin: [user] 985 define member: [user, team#member] or admin 986 987 type trial 988 relations 989 define editor: [user, team#member] or owner 990 define owner: [user] 991 define viewer: [user, team#member] or editor`, 992 target: typesystem.DirectRelationReference("trial", "viewer"), 993 source: typesystem.DirectRelationReference("team", "admin"), 994 expected: []*RelationshipEdge{ 995 { 996 Type: ComputedUsersetEdge, 997 TargetReference: typesystem.DirectRelationReference("team", "member"), 998 TargetReferenceInvolvesIntersectionOrExclusion: false, 999 }, 1000 }, 1001 }, 1002 { 1003 name: "indirect_relationship_through_assignable_computed_userset", 1004 model: `model 1005 schema 1.1 1006 type user 1007 1008 type team 1009 relations 1010 define admin: [user] 1011 define member: [team#member] or admin`, 1012 target: typesystem.DirectRelationReference("team", "member"), 1013 source: typesystem.DirectRelationReference("team", "admin"), 1014 expected: []*RelationshipEdge{ 1015 { 1016 Type: ComputedUsersetEdge, 1017 TargetReference: typesystem.DirectRelationReference("team", "member"), 1018 TargetReferenceInvolvesIntersectionOrExclusion: false, 1019 }, 1020 }, 1021 }, 1022 { 1023 name: "indirect_relationship_through_non-assignable_computed_userset", 1024 model: `model 1025 schema 1.1 1026 type user 1027 1028 type group 1029 relations 1030 define manager: [user] 1031 define member: manager 1032 1033 type document 1034 relations 1035 define viewer: [group#member]`, 1036 target: typesystem.DirectRelationReference("document", "viewer"), 1037 source: typesystem.DirectRelationReference("group", "manager"), 1038 expected: []*RelationshipEdge{ 1039 { 1040 Type: ComputedUsersetEdge, 1041 TargetReference: typesystem.DirectRelationReference("group", "member"), 1042 TargetReferenceInvolvesIntersectionOrExclusion: false, 1043 }, 1044 }, 1045 }, 1046 { 1047 name: "indirect_relationship_through_non-assignable_ttu_1", 1048 model: `model 1049 schema 1.1 1050 type user 1051 1052 type org 1053 relations 1054 define dept: [group] 1055 define dept_member: member from dept 1056 1057 type group 1058 relations 1059 define member: [user] 1060 1061 type resource 1062 relations 1063 define writer: [org#dept_member]`, 1064 target: typesystem.DirectRelationReference("resource", "writer"), 1065 source: typesystem.DirectRelationReference("user", ""), 1066 expected: []*RelationshipEdge{ 1067 { 1068 Type: DirectEdge, 1069 TargetReference: typesystem.DirectRelationReference("group", "member"), 1070 TargetReferenceInvolvesIntersectionOrExclusion: false, 1071 }, 1072 }, 1073 }, 1074 { 1075 name: "indirect_relationship_through_non-assignable_ttu_2", 1076 model: `model 1077 schema 1.1 1078 type user 1079 1080 type org 1081 relations 1082 define dept: [group] 1083 define dept_member: member from dept 1084 1085 type group 1086 relations 1087 define member: [user] 1088 1089 type resource 1090 relations 1091 define writer: [org#dept_member]`, 1092 target: typesystem.DirectRelationReference("resource", "writer"), 1093 source: typesystem.DirectRelationReference("group", "member"), 1094 expected: []*RelationshipEdge{ 1095 { 1096 Type: TupleToUsersetEdge, 1097 TargetReference: typesystem.DirectRelationReference("org", "dept_member"), 1098 TuplesetRelation: "dept", 1099 TargetReferenceInvolvesIntersectionOrExclusion: false, 1100 }, 1101 }, 1102 }, 1103 { 1104 name: "indirect_relationship_through_non-assignable_ttu_3", 1105 model: `model 1106 schema 1.1 1107 type user 1108 1109 type org 1110 relations 1111 define dept: [group] 1112 define dept_member: member from dept 1113 1114 type group 1115 relations 1116 define member: [user] 1117 1118 type resource 1119 relations 1120 define writer: [org#dept_member]`, 1121 target: typesystem.DirectRelationReference("resource", "writer"), 1122 source: typesystem.DirectRelationReference("org", "dept_member"), 1123 expected: []*RelationshipEdge{ 1124 { 1125 Type: DirectEdge, 1126 TargetReference: typesystem.DirectRelationReference("resource", "writer"), 1127 TargetReferenceInvolvesIntersectionOrExclusion: false, 1128 }, 1129 }, 1130 }, 1131 { 1132 name: "unrelated_source_and_target_relationship_involving_ttu", 1133 model: `model 1134 schema 1.1 1135 type user 1136 1137 type folder 1138 relations 1139 define viewer: [user] 1140 1141 type document 1142 relations 1143 define can_read: viewer from parent 1144 define parent: [document,folder] 1145 define viewer: [user]`, 1146 target: typesystem.DirectRelationReference("document", "can_read"), 1147 source: typesystem.DirectRelationReference("document", ""), 1148 expected: []*RelationshipEdge{}, 1149 }, 1150 { 1151 name: "simple_computeduserset_indirect_ref", 1152 model: `model 1153 schema 1.1 1154 type user 1155 1156 type document 1157 relations 1158 define parent: [document] 1159 define viewer: [user] or viewer from parent 1160 define can_view: viewer`, 1161 target: typesystem.DirectRelationReference("document", "can_view"), 1162 source: typesystem.DirectRelationReference("document", "viewer"), 1163 expected: []*RelationshipEdge{ 1164 { 1165 Type: ComputedUsersetEdge, 1166 TargetReference: typesystem.DirectRelationReference("document", "can_view"), 1167 TargetReferenceInvolvesIntersectionOrExclusion: false, 1168 }, 1169 { 1170 Type: TupleToUsersetEdge, 1171 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 1172 TuplesetRelation: "parent", 1173 TargetReferenceInvolvesIntersectionOrExclusion: false, 1174 }, 1175 }, 1176 }, 1177 { 1178 name: "follow_computed_relation_of_ttu_to_computed_userset", 1179 model: `model 1180 schema 1.1 1181 type user 1182 type folder 1183 relations 1184 define owner: [user] 1185 define viewer: [user] or owner 1186 type document 1187 relations 1188 define can_read: viewer from parent 1189 define parent: [document, folder] 1190 define viewer: [user]`, 1191 target: typesystem.DirectRelationReference("document", "can_read"), 1192 source: typesystem.DirectRelationReference("folder", "owner"), 1193 expected: []*RelationshipEdge{ 1194 { 1195 Type: ComputedUsersetEdge, 1196 TargetReference: typesystem.DirectRelationReference("folder", "viewer"), 1197 TargetReferenceInvolvesIntersectionOrExclusion: false, 1198 }, 1199 }, 1200 }, 1201 { 1202 name: "computed_target_of_ttu_related_to_same_type", 1203 model: `model 1204 schema 1.1 1205 type folder 1206 relations 1207 define viewer: [folder] 1208 1209 type document 1210 relations 1211 define parent: [folder] 1212 define viewer: viewer from parent`, 1213 target: typesystem.DirectRelationReference("document", "viewer"), 1214 source: typesystem.DirectRelationReference("folder", "viewer"), 1215 expected: []*RelationshipEdge{ 1216 { 1217 Type: TupleToUsersetEdge, 1218 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 1219 TuplesetRelation: "parent", 1220 TargetReferenceInvolvesIntersectionOrExclusion: false, 1221 }, 1222 }, 1223 }, 1224 { 1225 name: "basic_relation_with_intersection_1", 1226 model: `model 1227 schema 1.1 1228 type user 1229 1230 type document 1231 relations 1232 define allowed: [user] 1233 define viewer: [user] and allowed`, 1234 target: typesystem.DirectRelationReference("document", "viewer"), 1235 source: typesystem.DirectRelationReference("user", ""), 1236 expected: []*RelationshipEdge{ 1237 { 1238 Type: DirectEdge, 1239 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 1240 TargetReferenceInvolvesIntersectionOrExclusion: true, 1241 }, 1242 { 1243 Type: DirectEdge, 1244 TargetReference: typesystem.DirectRelationReference("document", "allowed"), 1245 TargetReferenceInvolvesIntersectionOrExclusion: false, 1246 }, 1247 }, 1248 }, 1249 { 1250 name: "basic_relation_with_intersection_2", 1251 model: `model 1252 schema 1.1 1253 type user 1254 1255 type document 1256 relations 1257 define allowed: [user] 1258 define editor: [user] 1259 define viewer: editor and allowed`, 1260 target: typesystem.DirectRelationReference("document", "viewer"), 1261 source: typesystem.DirectRelationReference("user", ""), 1262 expected: []*RelationshipEdge{ 1263 { 1264 Type: DirectEdge, 1265 TargetReference: typesystem.DirectRelationReference("document", "editor"), 1266 TargetReferenceInvolvesIntersectionOrExclusion: true, 1267 }, 1268 { 1269 Type: DirectEdge, 1270 TargetReference: typesystem.DirectRelationReference("document", "allowed"), 1271 TargetReferenceInvolvesIntersectionOrExclusion: false, 1272 }, 1273 }, 1274 }, 1275 { 1276 name: "basic_relation_with_intersection_3", 1277 authModel: &openfgav1.AuthorizationModel{ 1278 SchemaVersion: typesystem.SchemaVersion1_1, 1279 TypeDefinitions: []*openfgav1.TypeDefinition{ 1280 { 1281 Type: "user", 1282 }, 1283 { 1284 Type: "document", 1285 Relations: map[string]*openfgav1.Userset{ 1286 "allowed": typesystem.This(), 1287 "editor": typesystem.This(), 1288 "viewer": typesystem.Intersection( 1289 typesystem.ComputedUserset("allowed"), 1290 typesystem.This(), 1291 ), 1292 }, 1293 Metadata: &openfgav1.Metadata{ 1294 Relations: map[string]*openfgav1.RelationMetadata{ 1295 "allowed": { 1296 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1297 {Type: "user"}, 1298 }, 1299 }, 1300 "editor": { 1301 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1302 {Type: "user"}, 1303 }, 1304 }, 1305 "viewer": { 1306 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1307 {Type: "user"}, 1308 }, 1309 }, 1310 }, 1311 }, 1312 }, 1313 }, 1314 }, 1315 target: typesystem.DirectRelationReference("document", "viewer"), 1316 source: typesystem.DirectRelationReference("user", ""), 1317 expected: []*RelationshipEdge{ 1318 { 1319 Type: DirectEdge, 1320 TargetReference: typesystem.DirectRelationReference("document", "allowed"), 1321 TargetReferenceInvolvesIntersectionOrExclusion: true, 1322 }, 1323 { 1324 Type: DirectEdge, 1325 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 1326 TargetReferenceInvolvesIntersectionOrExclusion: false, 1327 }, 1328 }, 1329 }, 1330 { 1331 name: "basic_relation_with_exclusion_1", 1332 model: `model 1333 schema 1.1 1334 type user 1335 1336 type document 1337 relations 1338 define restricted: [user] 1339 define viewer: [user] but not restricted`, 1340 target: typesystem.DirectRelationReference("document", "viewer"), 1341 source: typesystem.DirectRelationReference("user", ""), 1342 expected: []*RelationshipEdge{ 1343 { 1344 Type: DirectEdge, 1345 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 1346 TargetReferenceInvolvesIntersectionOrExclusion: true, 1347 }, 1348 { 1349 Type: DirectEdge, 1350 TargetReference: typesystem.DirectRelationReference("document", "restricted"), 1351 TargetReferenceInvolvesIntersectionOrExclusion: false, 1352 }, 1353 }, 1354 }, 1355 { 1356 name: "basic_relation_with_exclusion_2", 1357 model: `model 1358 schema 1.1 1359 type user 1360 1361 type document 1362 relations 1363 define restricted: [user] 1364 define editor: [user] 1365 define viewer: editor but not restricted`, 1366 target: typesystem.DirectRelationReference("document", "viewer"), 1367 source: typesystem.DirectRelationReference("user", ""), 1368 expected: []*RelationshipEdge{ 1369 { 1370 Type: DirectEdge, 1371 TargetReference: typesystem.DirectRelationReference("document", "editor"), 1372 TargetReferenceInvolvesIntersectionOrExclusion: true, 1373 }, 1374 { 1375 Type: DirectEdge, 1376 TargetReference: typesystem.DirectRelationReference("document", "restricted"), 1377 TargetReferenceInvolvesIntersectionOrExclusion: false, 1378 }, 1379 }, 1380 }, 1381 { 1382 name: "basic_relation_with_exclusion_3", 1383 authModel: &openfgav1.AuthorizationModel{ 1384 SchemaVersion: typesystem.SchemaVersion1_1, 1385 TypeDefinitions: []*openfgav1.TypeDefinition{ 1386 { 1387 Type: "user", 1388 }, 1389 { 1390 Type: "document", 1391 Relations: map[string]*openfgav1.Userset{ 1392 "allowed": typesystem.This(), 1393 "viewer": typesystem.Difference( 1394 typesystem.ComputedUserset("allowed"), 1395 typesystem.This()), 1396 }, 1397 Metadata: &openfgav1.Metadata{ 1398 Relations: map[string]*openfgav1.RelationMetadata{ 1399 "allowed": { 1400 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1401 {Type: "user"}, 1402 }, 1403 }, 1404 "viewer": { 1405 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1406 {Type: "user"}, 1407 }, 1408 }, 1409 }, 1410 }, 1411 }, 1412 }, 1413 }, 1414 target: typesystem.DirectRelationReference("document", "viewer"), 1415 source: typesystem.DirectRelationReference("user", ""), 1416 expected: []*RelationshipEdge{ 1417 { 1418 Type: DirectEdge, 1419 TargetReference: typesystem.DirectRelationReference("document", "allowed"), 1420 TargetReferenceInvolvesIntersectionOrExclusion: true, 1421 }, 1422 { 1423 Type: DirectEdge, 1424 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 1425 TargetReferenceInvolvesIntersectionOrExclusion: false, 1426 }, 1427 }, 1428 }, 1429 { 1430 name: "ttu_through_direct_rewrite_1", 1431 model: `model 1432 schema 1.1 1433 type folder 1434 relations 1435 define viewer: [folder] 1436 1437 type document 1438 relations 1439 define parent: [folder] 1440 define viewer: viewer from parent`, 1441 target: typesystem.DirectRelationReference("document", "viewer"), 1442 source: typesystem.DirectRelationReference("folder", "viewer"), 1443 expected: []*RelationshipEdge{ 1444 { 1445 Type: TupleToUsersetEdge, 1446 TargetReference: typesystem.DirectRelationReference("document", "viewer"), 1447 TuplesetRelation: "parent", 1448 TargetReferenceInvolvesIntersectionOrExclusion: false, 1449 }, 1450 }, 1451 }, 1452 { 1453 name: "ttu_through_direct_rewrite_2", 1454 model: `model 1455 schema 1.1 1456 type folder 1457 relations 1458 define viewer: [folder] 1459 1460 type document 1461 relations 1462 define parent: [folder] 1463 define viewer: viewer from parent`, 1464 target: typesystem.DirectRelationReference("document", "viewer"), 1465 source: typesystem.DirectRelationReference("folder", ""), 1466 expected: []*RelationshipEdge{ 1467 { 1468 Type: DirectEdge, 1469 TargetReference: typesystem.DirectRelationReference("folder", "viewer"), 1470 TargetReferenceInvolvesIntersectionOrExclusion: false, 1471 }, 1472 }, 1473 }, 1474 } 1475 1476 for _, test := range tests { 1477 t.Run(test.name, func(t *testing.T) { 1478 var typesys *typesystem.TypeSystem 1479 if test.model == "" { 1480 typesys = typesystem.New(test.authModel) 1481 } else { 1482 model := testutils.MustTransformDSLToProtoWithID(test.model) 1483 typesys = typesystem.New(model) 1484 } 1485 1486 g := New(typesys) 1487 1488 edges, err := g.GetRelationshipEdges(test.target, test.source) 1489 require.NoError(t, err) 1490 1491 cmpOpts := []cmp.Option{ 1492 cmpopts.IgnoreUnexported(openfgav1.RelationReference{}), 1493 RelationshipEdgeTransformer, 1494 } 1495 if diff := cmp.Diff(test.expected, edges, cmpOpts...); diff != "" { 1496 t.Errorf("mismatch (-want +got):\n%s", diff) 1497 } 1498 }) 1499 } 1500 } 1501 1502 func TestResolutionDepthContext(t *testing.T) { 1503 ctx := ContextWithResolutionDepth(context.Background(), 2) 1504 1505 depth, ok := ResolutionDepthFromContext(ctx) 1506 require.True(t, ok) 1507 require.Equal(t, uint32(2), depth) 1508 1509 depth, ok = ResolutionDepthFromContext(context.Background()) 1510 require.False(t, ok) 1511 require.Equal(t, uint32(0), depth) 1512 }