github.com/openfga/openfga@v1.5.4-rc1/pkg/server/commands/listusers/list_users_rpc_test.go (about) 1 package listusers 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/oklog/ulid/v2" 10 openfgav1 "github.com/openfga/api/proto/openfga/v1" 11 parser "github.com/openfga/language/pkg/go/transformer" 12 "github.com/stretchr/testify/require" 13 "go.uber.org/goleak" 14 "go.uber.org/mock/gomock" 15 16 "github.com/openfga/openfga/pkg/storage" 17 18 "github.com/openfga/openfga/internal/graph" 19 "github.com/openfga/openfga/internal/mocks" 20 21 "github.com/openfga/openfga/pkg/storage/memory" 22 "github.com/openfga/openfga/pkg/storage/storagewrappers" 23 "github.com/openfga/openfga/pkg/testutils" 24 "github.com/openfga/openfga/pkg/tuple" 25 "github.com/openfga/openfga/pkg/typesystem" 26 ) 27 28 type ListUsersTests []struct { 29 name string 30 req *openfgav1.ListUsersRequest 31 model string 32 tuples []*openfgav1.TupleKey 33 expectedUsers []string 34 expectedErrorMsg string 35 } 36 37 const maximumRecursiveDepth = 25 38 39 func TestListUsersDirectRelationship(t *testing.T) { 40 t.Cleanup(func() { 41 goleak.VerifyNone(t) 42 }) 43 model := `model 44 schema 1.1 45 type user 46 type document 47 relations 48 define viewer: [user]` 49 50 tests := ListUsersTests{ 51 { 52 name: "direct_relationship", 53 req: &openfgav1.ListUsersRequest{ 54 Object: &openfgav1.Object{Type: "document", Id: "1"}, 55 Relation: "viewer", 56 UserFilters: []*openfgav1.UserTypeFilter{ 57 { 58 Type: "user", 59 }, 60 }, 61 }, 62 model: model, 63 tuples: []*openfgav1.TupleKey{ 64 tuple.NewTupleKey("document:1", "viewer", "user:will"), 65 tuple.NewTupleKey("document:1", "viewer", "user:maria"), 66 tuple.NewTupleKey("document:2", "viewer", "user:jon"), 67 }, 68 expectedUsers: []string{"user:will", "user:maria"}, 69 }, 70 { 71 name: "direct_relationship_with_userset_subjects_and_userset_filter", 72 req: &openfgav1.ListUsersRequest{ 73 Object: &openfgav1.Object{Type: "group", Id: "eng"}, 74 Relation: "member", 75 UserFilters: []*openfgav1.UserTypeFilter{ 76 { 77 Type: "group", 78 Relation: "member", 79 }, 80 }, 81 }, 82 model: `model 83 schema 1.1 84 type user 85 type group 86 relations 87 define member: [user, group#member]`, 88 tuples: []*openfgav1.TupleKey{ 89 tuple.NewTupleKey("group:eng", "member", "group:fga#member"), 90 tuple.NewTupleKey("group:fga", "member", "group:fga-backend#member"), 91 }, 92 expectedUsers: []string{"group:fga#member", "group:fga-backend#member", "group:eng#member"}, 93 }, 94 { 95 name: "direct_relationship_no_tuples", 96 req: &openfgav1.ListUsersRequest{ 97 Object: &openfgav1.Object{Type: "document", Id: "1"}, 98 Relation: "viewer", 99 UserFilters: []*openfgav1.UserTypeFilter{ 100 { 101 Type: "user", 102 }, 103 }, 104 }, 105 model: model, 106 tuples: []*openfgav1.TupleKey{}, 107 expectedUsers: []string{}, 108 }, 109 { 110 name: "direct_relationship_unapplicable_tuples", 111 req: &openfgav1.ListUsersRequest{ 112 Object: &openfgav1.Object{Type: "document", Id: "1"}, 113 Relation: "viewer", 114 UserFilters: []*openfgav1.UserTypeFilter{ 115 { 116 Type: "user", 117 }, 118 }, 119 }, 120 model: model, 121 tuples: []*openfgav1.TupleKey{ 122 tuple.NewTupleKey("document:2", "viewer", "user:will"), 123 tuple.NewTupleKey("document:3", "viewer", "user:will"), 124 tuple.NewTupleKey("document:4", "viewer", "user:will"), 125 }, 126 expectedUsers: []string{}, 127 }, 128 { 129 name: "direct_relationship_contextual_tuples", 130 req: &openfgav1.ListUsersRequest{ 131 Object: &openfgav1.Object{Type: "document", Id: "1"}, 132 ContextualTuples: []*openfgav1.TupleKey{ 133 tuple.NewTupleKey("document:1", "viewer", "user:will"), 134 tuple.NewTupleKey("document:1", "viewer", "user:maria"), 135 tuple.NewTupleKey("document:2", "viewer", "user:jon"), 136 }, 137 Relation: "viewer", 138 UserFilters: []*openfgav1.UserTypeFilter{ 139 { 140 Type: "user", 141 }, 142 }, 143 }, 144 model: model, 145 tuples: []*openfgav1.TupleKey{}, 146 expectedUsers: []string{"user:will", "user:maria"}, 147 }, 148 } 149 tests.runListUsersTestCases(t) 150 } 151 152 func TestListUsersComputedRelationship(t *testing.T) { 153 t.Cleanup(func() { 154 goleak.VerifyNone(t) 155 }) 156 tests := ListUsersTests{ 157 { 158 name: "computed_relationship", 159 req: &openfgav1.ListUsersRequest{ 160 Object: &openfgav1.Object{Type: "document", Id: "1"}, 161 Relation: "viewer", 162 UserFilters: []*openfgav1.UserTypeFilter{ 163 { 164 Type: "user", 165 }, 166 }, 167 }, 168 model: `model 169 schema 1.1 170 type user 171 type document 172 relations 173 define owner: [user] 174 define viewer: owner`, 175 tuples: []*openfgav1.TupleKey{ 176 tuple.NewTupleKey("document:1", "owner", "user:will"), 177 tuple.NewTupleKey("document:1", "owner", "user:maria"), 178 tuple.NewTupleKey("document:2", "viewer", "user:jon"), 179 }, 180 expectedUsers: []string{"user:will", "user:maria"}, 181 }, 182 { 183 name: "computed_relationship_with_possible_direct_relationship", 184 req: &openfgav1.ListUsersRequest{ 185 Object: &openfgav1.Object{Type: "document", Id: "1"}, 186 Relation: "viewer", 187 UserFilters: []*openfgav1.UserTypeFilter{ 188 { 189 Type: "user", 190 }, 191 }, 192 }, 193 model: `model 194 schema 1.1 195 type user 196 type document 197 relations 198 define owner: [user] 199 define editor: [user] or owner 200 define viewer: owner or editor`, 201 tuples: []*openfgav1.TupleKey{ 202 tuple.NewTupleKey("document:1", "owner", "user:will"), 203 tuple.NewTupleKey("document:1", "editor", "user:maria"), 204 tuple.NewTupleKey("document:2", "viewer", "user:jon"), 205 }, 206 expectedUsers: []string{"user:will", "user:maria"}, 207 }, 208 { 209 name: "computed_relationship_with_contextual_tuples", 210 req: &openfgav1.ListUsersRequest{ 211 Object: &openfgav1.Object{Type: "document", Id: "1"}, 212 Relation: "viewer", 213 UserFilters: []*openfgav1.UserTypeFilter{ 214 { 215 Type: "user", 216 }, 217 }, 218 ContextualTuples: []*openfgav1.TupleKey{ 219 tuple.NewTupleKey("document:1", "owner", "user:will"), 220 tuple.NewTupleKey("document:1", "owner", "user:maria"), 221 tuple.NewTupleKey("document:2", "viewer", "user:jon"), 222 }, 223 }, 224 model: `model 225 schema 1.1 226 type user 227 type document 228 relations 229 define owner: [user] 230 define viewer: owner`, 231 tuples: []*openfgav1.TupleKey{}, 232 expectedUsers: []string{"user:will", "user:maria"}, 233 }, 234 } 235 tests.runListUsersTestCases(t) 236 } 237 238 func TestListUsersUsersets(t *testing.T) { 239 t.Cleanup(func() { 240 goleak.VerifyNone(t) 241 }) 242 model := `model 243 schema 1.1 244 type user 245 type group 246 relations 247 define member: [user] 248 type document 249 relations 250 define viewer: [group#member]` 251 252 tests := ListUsersTests{ 253 { 254 name: "userset_user_granularity", 255 req: &openfgav1.ListUsersRequest{ 256 Object: &openfgav1.Object{Type: "document", Id: "1"}, 257 Relation: "viewer", 258 UserFilters: []*openfgav1.UserTypeFilter{ 259 { 260 Type: "user", 261 }, 262 }, 263 }, 264 model: model, 265 tuples: []*openfgav1.TupleKey{ 266 tuple.NewTupleKey("group:eng", "member", "user:will"), 267 tuple.NewTupleKey("group:eng", "member", "user:maria"), 268 tuple.NewTupleKey("group:marketing", "viewer", "user:jon"), 269 tuple.NewTupleKey("document:1", "viewer", "group:eng#member"), 270 }, 271 expectedUsers: []string{"user:will", "user:maria"}, 272 }, 273 { 274 name: "userset_group_granularity", 275 req: &openfgav1.ListUsersRequest{ 276 Object: &openfgav1.Object{Type: "document", Id: "1"}, 277 Relation: "viewer", 278 UserFilters: []*openfgav1.UserTypeFilter{ 279 { 280 Type: "group", 281 Relation: "member", 282 }, 283 }, 284 }, 285 model: model, 286 tuples: []*openfgav1.TupleKey{ 287 tuple.NewTupleKey("group:eng", "member", "user:will"), 288 tuple.NewTupleKey("group:eng", "member", "user:maria"), 289 tuple.NewTupleKey("group:marketing", "viewer", "user:jon"), 290 tuple.NewTupleKey("document:1", "viewer", "group:eng#member"), 291 }, 292 expectedUsers: []string{"group:eng#member"}, 293 }, 294 { 295 name: "userset_group_granularity_with_incorrect_user_filter", 296 req: &openfgav1.ListUsersRequest{ 297 Object: &openfgav1.Object{Type: "document", Id: "1"}, 298 Relation: "viewer", 299 UserFilters: []*openfgav1.UserTypeFilter{ 300 { 301 Type: "group", 302 Relation: "", // Would return results if "member" 303 }, 304 }, 305 }, 306 model: model, 307 tuples: []*openfgav1.TupleKey{ 308 tuple.NewTupleKey("group:eng", "member", "user:will"), 309 tuple.NewTupleKey("group:eng", "member", "user:maria"), 310 tuple.NewTupleKey("group:marketing", "member", "user:jon"), 311 tuple.NewTupleKey("document:1", "viewer", "group:eng#member"), 312 }, 313 expectedUsers: []string{}, 314 }, 315 { 316 name: "userset_group_granularity_with_direct_user_relationships", 317 req: &openfgav1.ListUsersRequest{ 318 Object: &openfgav1.Object{Type: "document", Id: "1"}, 319 Relation: "viewer", 320 UserFilters: []*openfgav1.UserTypeFilter{ 321 { 322 Type: "group", 323 Relation: "member", 324 }, 325 }, 326 }, 327 model: `model 328 schema 1.1 329 type user 330 type group 331 relations 332 define member: [user] 333 type document 334 relations 335 define viewer: [ user, group#member ]`, 336 tuples: []*openfgav1.TupleKey{ 337 tuple.NewTupleKey("group:eng", "member", "user:will"), 338 tuple.NewTupleKey("group:eng", "member", "user:maria"), 339 tuple.NewTupleKey("group:marketing", "member", "user:jon"), 340 tuple.NewTupleKey("document:1", "viewer", "group:eng#member"), 341 342 tuple.NewTupleKey("document:1", "viewer", "user:poovam"), 343 }, 344 expectedUsers: []string{"group:eng#member"}, 345 }, 346 { 347 name: "userset_multiple_usersets", 348 req: &openfgav1.ListUsersRequest{ 349 Object: &openfgav1.Object{Type: "document", Id: "1"}, 350 Relation: "viewer", 351 UserFilters: []*openfgav1.UserTypeFilter{ 352 { 353 Type: "user", 354 }, 355 }, 356 }, 357 model: `model 358 schema 1.1 359 type user 360 type group 361 relations 362 define member: [user, group#member] 363 type document 364 relations 365 define viewer: [group#member]`, 366 tuples: []*openfgav1.TupleKey{ 367 tuple.NewTupleKey("group:eng", "member", "user:hawker"), 368 tuple.NewTupleKey("group:fga", "member", "user:jon"), 369 tuple.NewTupleKey("group:eng", "member", "group:fga#member"), 370 tuple.NewTupleKey("document:1", "viewer", "group:eng#member"), 371 tuple.NewTupleKey("document:1", "viewer", "group:other#member"), 372 tuple.NewTupleKey("group:other", "member", "user:will"), 373 }, 374 expectedUsers: []string{"user:jon", "user:hawker", "user:will"}, 375 }, 376 { 377 name: "userset_multiple_usersets_group_granularity", 378 req: &openfgav1.ListUsersRequest{ 379 Object: &openfgav1.Object{Type: "document", Id: "1"}, 380 Relation: "viewer", 381 UserFilters: []*openfgav1.UserTypeFilter{ 382 { 383 Type: "group", 384 Relation: "member", 385 }, 386 }, 387 }, 388 model: `model 389 schema 1.1 390 type user 391 type group 392 relations 393 define member: [user, group#member] 394 type document 395 relations 396 define viewer: [group#member]`, 397 tuples: []*openfgav1.TupleKey{ 398 tuple.NewTupleKey("group:eng", "member", "user:hawker"), 399 tuple.NewTupleKey("group:eng", "member", "group:fga#member"), 400 tuple.NewTupleKey("document:1", "viewer", "group:eng#member"), 401 tuple.NewTupleKey("document:1", "viewer", "group:other#member"), 402 }, 403 expectedUsers: []string{"group:fga#member", "group:eng#member", "group:other#member"}, 404 }, 405 { 406 name: "userset_user_granularity_with_contextual_tuples", 407 req: &openfgav1.ListUsersRequest{ 408 Object: &openfgav1.Object{Type: "document", Id: "1"}, 409 Relation: "viewer", 410 UserFilters: []*openfgav1.UserTypeFilter{ 411 { 412 Type: "user", 413 }, 414 }, 415 ContextualTuples: []*openfgav1.TupleKey{ 416 tuple.NewTupleKey("group:marketing", "member", "user:jon"), 417 tuple.NewTupleKey("document:1", "viewer", "group:eng#member"), 418 }, 419 }, 420 model: model, 421 tuples: []*openfgav1.TupleKey{ 422 tuple.NewTupleKey("group:eng", "member", "user:will"), 423 tuple.NewTupleKey("group:eng", "member", "user:maria"), 424 }, 425 expectedUsers: []string{"user:will", "user:maria"}, 426 }, 427 { 428 name: "userset_user_assigned_multiple_groups", 429 req: &openfgav1.ListUsersRequest{ 430 Object: &openfgav1.Object{Type: "document", Id: "1"}, 431 Relation: "viewer", 432 UserFilters: []*openfgav1.UserTypeFilter{ 433 { 434 Type: "user", 435 }, 436 }, 437 ContextualTuples: []*openfgav1.TupleKey{}, 438 }, 439 model: model, 440 tuples: []*openfgav1.TupleKey{ 441 tuple.NewTupleKey("group:eng", "member", "user:maria"), 442 tuple.NewTupleKey("group:eng", "member", "user:will"), 443 tuple.NewTupleKey("group:fga", "member", "user:will"), 444 tuple.NewTupleKey("document:1", "viewer", "group:eng#member"), 445 tuple.NewTupleKey("document:1", "viewer", "group:fga#member"), 446 }, 447 expectedUsers: []string{"user:will", "user:maria"}, 448 }, 449 { 450 name: "tuple_defines_itself", 451 req: &openfgav1.ListUsersRequest{ 452 Object: &openfgav1.Object{Type: "document", Id: "1"}, 453 Relation: "viewer", 454 UserFilters: []*openfgav1.UserTypeFilter{ 455 { 456 Type: "document", 457 }, 458 }, 459 }, 460 model: `model 461 schema 1.1 462 type user 463 type document 464 relations 465 define viewer: [user] 466 `, 467 tuples: []*openfgav1.TupleKey{}, 468 expectedUsers: []string{}, 469 }, 470 { 471 name: "userset_defines_itself", 472 req: &openfgav1.ListUsersRequest{ 473 Object: &openfgav1.Object{Type: "document", Id: "1"}, 474 Relation: "viewer", 475 UserFilters: []*openfgav1.UserTypeFilter{ 476 { 477 Type: "document", 478 Relation: "viewer", 479 }, 480 }, 481 }, 482 model: `model 483 schema 1.1 484 type user 485 type document 486 relations 487 define viewer: [user] 488 `, 489 tuples: []*openfgav1.TupleKey{}, 490 expectedUsers: []string{"document:1#viewer"}, 491 }, 492 { 493 name: "evaluate_userset_in_computed_relation_of_ttu", 494 req: &openfgav1.ListUsersRequest{ 495 Object: &openfgav1.Object{Type: "repo", Id: "fga"}, 496 Relation: "reader", 497 UserFilters: []*openfgav1.UserTypeFilter{ 498 { 499 Type: "org", 500 Relation: "member", 501 }, 502 }, 503 }, 504 model: `model 505 schema 1.1 506 type user 507 508 type org 509 relations 510 define member: [user] 511 define admin: [org#member] 512 513 type repo 514 relations 515 define owner: [org] 516 define reader: admin from owner`, 517 tuples: []*openfgav1.TupleKey{ 518 tuple.NewTupleKey("repo:fga", "owner", "org:x"), 519 tuple.NewTupleKey("org:x", "admin", "org:x#member"), 520 tuple.NewTupleKey("org:x", "member", "user:will"), 521 }, 522 expectedUsers: []string{"org:x#member"}, 523 }, 524 { 525 name: "userset_with_intersection_in_computed_relation_of_ttu", 526 req: &openfgav1.ListUsersRequest{ 527 Object: &openfgav1.Object{Type: "repo", Id: "fga"}, 528 Relation: "reader", 529 UserFilters: []*openfgav1.UserTypeFilter{ 530 { 531 Type: "org", 532 Relation: "member", 533 }, 534 }, 535 }, 536 model: `model 537 schema 1.1 538 type user 539 540 type org 541 relations 542 define member: [user] 543 define admin: [org#member] 544 type repo 545 relations 546 define owner: [org] 547 define allowed: [user] 548 define reader: admin from owner and allowed`, 549 tuples: []*openfgav1.TupleKey{ 550 tuple.NewTupleKey("repo:x", "owner", "org:fga"), 551 tuple.NewTupleKey("org:fga", "admin", "org:fga#member"), 552 tuple.NewTupleKey("repo:x", "allowed", "user:will"), 553 }, 554 expectedUsers: []string{}, 555 }, 556 } 557 tests.runListUsersTestCases(t) 558 } 559 560 func TestListUsersTTU(t *testing.T) { 561 t.Cleanup(func() { 562 goleak.VerifyNone(t) 563 }) 564 model := `model 565 schema 1.1 566 type user 567 568 type folder 569 relations 570 define viewer: [user] 571 572 type document 573 relations 574 define parent: [folder] 575 define viewer: viewer from parent` 576 577 tests := ListUsersTests{ 578 { 579 name: "ttu_user_granularity", 580 req: &openfgav1.ListUsersRequest{ 581 Object: &openfgav1.Object{Type: "document", Id: "1"}, 582 Relation: "viewer", 583 UserFilters: []*openfgav1.UserTypeFilter{ 584 { 585 Type: "user", 586 }, 587 }, 588 }, 589 model: model, 590 tuples: []*openfgav1.TupleKey{ 591 tuple.NewTupleKey("document:1", "parent", "folder:x"), 592 tuple.NewTupleKey("folder:x", "viewer", "user:maria"), 593 tuple.NewTupleKey("folder:no-doc", "viewer", "user:maria"), 594 tuple.NewTupleKey("document:1", "parent", "folder:no-user"), 595 }, 596 expectedUsers: []string{"user:maria"}, 597 }, 598 { 599 name: "ttu_folder_granularity", 600 req: &openfgav1.ListUsersRequest{ 601 Object: &openfgav1.Object{Type: "document", Id: "1"}, 602 Relation: "viewer", 603 UserFilters: []*openfgav1.UserTypeFilter{ 604 { 605 Type: "folder", 606 }, 607 }, 608 }, 609 model: model, 610 tuples: []*openfgav1.TupleKey{ 611 tuple.NewTupleKey("document:1", "parent", "folder:x"), 612 tuple.NewTupleKey("folder:x", "viewer", "user:maria"), 613 }, 614 expectedUsers: []string{}, 615 }, 616 { 617 name: "ttu_with_computed_relation_user_granularity", 618 req: &openfgav1.ListUsersRequest{ 619 Object: &openfgav1.Object{Type: "document", Id: "1"}, 620 Relation: "viewer", 621 UserFilters: []*openfgav1.UserTypeFilter{ 622 { 623 Type: "user", 624 }, 625 }, 626 }, 627 model: `model 628 schema 1.1 629 type user 630 631 type folder 632 relations 633 define owner: [user] 634 define editor: [user] or owner 635 define viewer: [user] or owner or editor 636 define unrelated_not_computed: [user] 637 638 type document 639 relations 640 define parent: [folder] 641 define viewer: viewer from parent`, 642 643 tuples: []*openfgav1.TupleKey{ 644 tuple.NewTupleKey("document:1", "parent", "folder:x"), 645 tuple.NewTupleKey("folder:x", "viewer", "user:maria"), 646 tuple.NewTupleKey("folder:x", "editor", "user:will"), 647 tuple.NewTupleKey("folder:x", "owner", "user:jon"), 648 tuple.NewTupleKey("folder:x", "unrelated_not_computed", "user:poovam"), 649 650 tuple.NewTupleKey("folder:no-doc", "viewer", "user:maria"), 651 tuple.NewTupleKey("document:1", "parent", "folder:no-user"), 652 }, 653 expectedUsers: []string{"user:maria", "user:will", "user:jon"}, 654 }, 655 { 656 name: "ttu_multiple_levels", 657 req: &openfgav1.ListUsersRequest{ 658 Object: &openfgav1.Object{Type: "folder", Id: "c"}, 659 Relation: "viewer", 660 UserFilters: []*openfgav1.UserTypeFilter{ 661 { 662 Type: "user", 663 }, 664 }, 665 }, 666 model: `model 667 schema 1.1 668 type user 669 type folder 670 relations 671 define parent: [folder] 672 define viewer: [user] or viewer from parent`, 673 tuples: []*openfgav1.TupleKey{ 674 tuple.NewTupleKey("folder:a", "viewer", "user:will"), 675 tuple.NewTupleKey("folder:b", "parent", "folder:a"), 676 tuple.NewTupleKey("folder:c", "parent", "folder:b"), 677 678 tuple.NewTupleKey("folder:c", "parent", "folder:other"), 679 tuple.NewTupleKey("folder:other", "viewer", "user:jon"), 680 }, 681 expectedUsers: []string{"user:will", "user:jon"}, 682 }, 683 } 684 685 tests.runListUsersTestCases(t) 686 } 687 688 func TestListUsersCycles(t *testing.T) { 689 t.Cleanup(func() { 690 goleak.VerifyNone(t) 691 }) 692 tests := ListUsersTests{ 693 { 694 name: "cycle_materialized_by_tuples", 695 req: &openfgav1.ListUsersRequest{ 696 Object: &openfgav1.Object{Type: "document", Id: "1"}, 697 Relation: "viewer", 698 UserFilters: []*openfgav1.UserTypeFilter{ 699 { 700 Type: "user", 701 }, 702 }, 703 }, 704 model: `model 705 schema 1.1 706 type user 707 type document 708 relations 709 define viewer: [user, document#viewer]`, 710 tuples: []*openfgav1.TupleKey{ 711 tuple.NewTupleKey("document:1", "viewer", "document:1#viewer"), 712 }, 713 expectedUsers: []string{}, 714 }, 715 { 716 name: "cycle_and_union", 717 req: &openfgav1.ListUsersRequest{ 718 Object: &openfgav1.Object{Type: "document", Id: "1"}, 719 Relation: "can_view", 720 UserFilters: []*openfgav1.UserTypeFilter{ 721 { 722 Type: "user", 723 }, 724 }, 725 }, 726 model: `model 727 schema 1.1 728 type user 729 type document 730 relations 731 define viewer1: [user, document#viewer1] 732 define viewer2: [user, document#viewer2] 733 define can_view: viewer1 or viewer2`, 734 tuples: []*openfgav1.TupleKey{ 735 tuple.NewTupleKey("document:1", "viewer1", "document:1#viewer1"), 736 tuple.NewTupleKey("document:1", "viewer2", "document:1#viewer2"), 737 }, 738 expectedUsers: []string{}, 739 }, 740 { 741 name: "cycle_and_intersection", 742 req: &openfgav1.ListUsersRequest{ 743 Object: &openfgav1.Object{Type: "document", Id: "1"}, 744 Relation: "can_view", 745 UserFilters: []*openfgav1.UserTypeFilter{ 746 { 747 Type: "user", 748 }, 749 }, 750 }, 751 model: `model 752 schema 1.1 753 type user 754 type document 755 relations 756 define viewer1: [user, document#viewer1] 757 define viewer2: [user, document#viewer2] 758 define can_view: viewer1 and viewer2`, 759 tuples: []*openfgav1.TupleKey{ 760 tuple.NewTupleKey("document:1", "viewer1", "document:1#viewer1"), 761 tuple.NewTupleKey("document:1", "viewer2", "document:1#viewer2"), 762 }, 763 expectedUsers: []string{}, 764 }, 765 { 766 name: "cycle_when_model_has_two_parallel_edges", 767 req: &openfgav1.ListUsersRequest{ 768 Object: &openfgav1.Object{Type: "transition", Id: "1"}, 769 Relation: "can_view_3", 770 UserFilters: []*openfgav1.UserTypeFilter{ 771 { 772 Type: "user", 773 }, 774 }, 775 }, 776 model: ` 777 model 778 schema 1.1 779 780 type user 781 782 type state 783 relations 784 define can_view: [user] or can_view_3 from associated_transition 785 define associated_transition: [transition] 786 787 type transition 788 relations 789 define start: [state] 790 define end: [state] 791 define can_view: can_view from start or can_view from end 792 define can_view_2: can_view 793 define can_view_3: can_view_2`, 794 tuples: []*openfgav1.TupleKey{}, 795 expectedUsers: []string{}, 796 }, 797 } 798 tests.runListUsersTestCases(t) 799 } 800 801 func TestListUsersConditions(t *testing.T) { 802 t.Cleanup(func() { 803 goleak.VerifyNone(t) 804 }) 805 model := ` 806 model 807 schema 1.1 808 type user 809 810 type document 811 relations 812 define viewer: [user with isTrue] 813 814 condition isTrue(param: bool) { 815 param 816 }` 817 818 tests := ListUsersTests{ 819 { 820 name: "conditions_with_true_evaluation", 821 req: &openfgav1.ListUsersRequest{ 822 Object: &openfgav1.Object{Type: "document", Id: "1"}, 823 Relation: "viewer", 824 UserFilters: []*openfgav1.UserTypeFilter{ 825 { 826 Type: "user", 827 }, 828 }, 829 Context: testutils.MustNewStruct(t, map[string]interface{}{"param": true}), 830 }, 831 model: model, 832 tuples: []*openfgav1.TupleKey{ 833 tuple.NewTupleKey("document:1", "viewer", "user:will"), 834 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:jon", "isTrue", nil), 835 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isTrue", nil), 836 }, 837 expectedUsers: []string{"user:jon", "user:maria"}, 838 }, 839 { 840 name: "conditions_with_false_evaluation", 841 req: &openfgav1.ListUsersRequest{ 842 Object: &openfgav1.Object{Type: "document", Id: "1"}, 843 Relation: "viewer", 844 UserFilters: []*openfgav1.UserTypeFilter{ 845 { 846 Type: "user", 847 }, 848 }, 849 Context: testutils.MustNewStruct(t, map[string]interface{}{"param": false}), 850 }, 851 model: model, 852 tuples: []*openfgav1.TupleKey{ 853 tuple.NewTupleKey("document:1", "viewer", "user:will"), 854 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:jon", "isTrue", nil), 855 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isTrue", nil), 856 }, 857 expectedUsers: []string{}, 858 }, 859 { 860 name: "conditions_with_usersets", 861 req: &openfgav1.ListUsersRequest{ 862 Object: &openfgav1.Object{Type: "document", Id: "1"}, 863 Relation: "viewer", 864 UserFilters: []*openfgav1.UserTypeFilter{ 865 { 866 Type: "group", 867 Relation: "member", 868 }, 869 }, 870 Context: testutils.MustNewStruct(t, map[string]interface{}{"param": true}), 871 }, 872 model: ` 873 model 874 schema 1.1 875 876 type user 877 878 type group 879 relations 880 define member: [user] 881 882 type document 883 relations 884 define viewer: [group#member with isTrue, user] 885 886 condition isTrue(param: bool) { 887 param 888 }`, 889 tuples: []*openfgav1.TupleKey{ 890 tuple.NewTupleKeyWithCondition("document:1", "viewer", "group:eng#member", "isTrue", nil), 891 tuple.NewTupleKeyWithCondition("document:1", "viewer", "group:fga#member", "isTrue", nil), 892 tuple.NewTupleKey("group:eng", "member", "user:jon"), 893 tuple.NewTupleKey("group:eng", "member", "user:maria"), 894 tuple.NewTupleKey("document:1", "viewer", "user:will"), 895 }, 896 expectedUsers: []string{"group:eng#member", "group:fga#member"}, 897 }, 898 { 899 name: "conditions_with_computed_relationships", 900 req: &openfgav1.ListUsersRequest{ 901 Object: &openfgav1.Object{Type: "document", Id: "1"}, 902 Relation: "viewer", 903 UserFilters: []*openfgav1.UserTypeFilter{ 904 { 905 Type: "user", 906 }, 907 }, 908 Context: testutils.MustNewStruct(t, map[string]interface{}{"param": true}), 909 }, 910 model: ` 911 model 912 schema 1.1 913 914 type user 915 916 type group 917 relations 918 define member: [user] 919 920 type document 921 relations 922 define owner: [user] 923 define editor: [user] or owner 924 define viewer: [user with isTrue] or editor or owner 925 926 condition isTrue(param: bool) { 927 param 928 }`, 929 tuples: []*openfgav1.TupleKey{ 930 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isTrue", nil), 931 tuple.NewTupleKey("document:1", "owner", "user:will"), 932 tuple.NewTupleKey("document:1", "editor", "user:poovam"), 933 }, 934 expectedUsers: []string{"user:will", "user:poovam", "user:maria"}, 935 }, 936 { 937 name: "conditions_with_ttu", 938 req: &openfgav1.ListUsersRequest{ 939 Object: &openfgav1.Object{Type: "document", Id: "1"}, 940 Relation: "viewer", 941 UserFilters: []*openfgav1.UserTypeFilter{ 942 { 943 Type: "user", 944 }, 945 }, 946 Context: testutils.MustNewStruct(t, map[string]interface{}{"param": true}), 947 }, 948 model: ` 949 model 950 schema 1.1 951 type user 952 953 type folder 954 relations 955 define viewer: [user] 956 957 type document 958 relations 959 define parent: [folder with isTrue] 960 define viewer: viewer from parent 961 962 condition isTrue(param: bool) { 963 param 964 }`, 965 tuples: []*openfgav1.TupleKey{ 966 tuple.NewTupleKeyWithCondition("document:1", "parent", "folder:x", "isTrue", nil), 967 tuple.NewTupleKey("folder:x", "viewer", "user:jon"), 968 tuple.NewTupleKeyWithCondition("document:1", "parent", "folder:y", "isTrue", nil), 969 tuple.NewTupleKey("folder:y", "viewer", "user:maria"), 970 }, 971 expectedUsers: []string{"user:jon", "user:maria"}, 972 }, 973 { 974 name: "multiple_conditions_no_param_provided", 975 req: &openfgav1.ListUsersRequest{ 976 Object: &openfgav1.Object{Type: "document", Id: "1"}, 977 Relation: "viewer", 978 UserFilters: []*openfgav1.UserTypeFilter{ 979 { 980 Type: "user", 981 }, 982 }, 983 Context: testutils.MustNewStruct(t, map[string]interface{}{}), 984 }, 985 model: ` 986 model 987 schema 1.1 988 989 type user 990 991 type document 992 relations 993 define viewer: [user] 994 995 condition isEqualToFive(param1: int) { 996 param1 == 5 997 } 998 999 condition isEqualToTen(param2: int) { 1000 param2 == 10 1001 }`, 1002 tuples: []*openfgav1.TupleKey{ 1003 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:will", "isEqualToFive", nil), 1004 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isEqualToTen", nil), 1005 }, 1006 expectedUsers: []string{}, 1007 }, 1008 { 1009 name: "multiple_conditions_some_params_provided", 1010 req: &openfgav1.ListUsersRequest{ 1011 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1012 Relation: "viewer", 1013 UserFilters: []*openfgav1.UserTypeFilter{ 1014 { 1015 Type: "user", 1016 }, 1017 }, 1018 Context: testutils.MustNewStruct(t, map[string]interface{}{"param1": 5}), 1019 }, 1020 model: ` 1021 model 1022 schema 1.1 1023 type user 1024 1025 type document 1026 relations 1027 define viewer: [user with isEqualToFive, user with isEqualToTen] 1028 1029 condition isEqualToFive(param1: int) { 1030 param1 == 5 1031 } 1032 1033 condition isEqualToTen(param2: int) { 1034 param2 == 10 1035 }`, 1036 tuples: []*openfgav1.TupleKey{ 1037 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:will", "isEqualToFive", nil), 1038 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isEqualToTen", nil), 1039 }, 1040 expectedErrorMsg: "failed to evaluate relationship condition: 'isEqualToTen' - context is missing parameters '[param2]'", 1041 }, 1042 { 1043 name: "multiple_conditions_all_params_provided", 1044 req: &openfgav1.ListUsersRequest{ 1045 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1046 Relation: "viewer", 1047 UserFilters: []*openfgav1.UserTypeFilter{ 1048 { 1049 Type: "user", 1050 }, 1051 }, 1052 Context: testutils.MustNewStruct(t, map[string]interface{}{"param1": 5, "param2": 10}), 1053 }, 1054 model: ` 1055 model 1056 schema 1.1 1057 type user 1058 1059 type document 1060 relations 1061 define viewer: [user with isEqualToFive, user with isEqualToTen] 1062 1063 condition isEqualToFive(param1: int) { 1064 param1 == 5 1065 } 1066 1067 condition isEqualToTen(param2: int) { 1068 param2 == 10 1069 }`, 1070 tuples: []*openfgav1.TupleKey{ 1071 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:will", "isEqualToFive", nil), 1072 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isEqualToTen", nil), 1073 }, 1074 expectedUsers: []string{"user:will", "user:maria"}, 1075 }, 1076 { 1077 name: "error_in_direct_eval", 1078 req: &openfgav1.ListUsersRequest{ 1079 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1080 Relation: "viewer", 1081 UserFilters: []*openfgav1.UserTypeFilter{ 1082 { 1083 Type: "user", 1084 }, 1085 }, 1086 Context: testutils.MustNewStruct(t, map[string]interface{}{"x": "1.79769313486231570814527423731704356798070e+309"}), 1087 }, 1088 model: ` 1089 model 1090 schema 1.1 1091 type user 1092 1093 type document 1094 relations 1095 define viewer: [user with condFloat] 1096 1097 condition condFloat(x: double) { 1098 x > 0.0 1099 }`, 1100 tuples: []*openfgav1.TupleKey{ 1101 tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "condFloat", nil), 1102 }, 1103 expectedErrorMsg: "failed to evaluate relationship condition: parameter type error on condition 'condFloat' - failed to convert context parameter 'x': number cannot be represented as a float64: 1.797693135e+309", 1104 }, 1105 { 1106 name: "error_in_ttu_eval", 1107 req: &openfgav1.ListUsersRequest{ 1108 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1109 Relation: "viewer", 1110 UserFilters: []*openfgav1.UserTypeFilter{ 1111 { 1112 Type: "user", 1113 }, 1114 }, 1115 Context: testutils.MustNewStruct(t, map[string]interface{}{"x": "1.79769313486231570814527423731704356798070e+309"}), 1116 }, 1117 model: ` 1118 model 1119 schema 1.1 1120 type user 1121 type folder 1122 relations 1123 define viewer: [user with condFloat] 1124 type document 1125 relations 1126 define parent: [folder with condFloat] 1127 define viewer: viewer from parent 1128 1129 condition condFloat(x: double) { 1130 x > 0.0 1131 }`, 1132 tuples: []*openfgav1.TupleKey{ 1133 tuple.NewTupleKeyWithCondition("document:1", "parent", "folder:x", "condFloat", nil), 1134 }, 1135 expectedErrorMsg: "failed to evaluate relationship condition: parameter type error on condition 'condFloat' - failed to convert context parameter 'x': number cannot be represented as a float64: 1.797693135e+309", 1136 }, 1137 } 1138 tests.runListUsersTestCases(t) 1139 } 1140 1141 func TestListUsersIntersection(t *testing.T) { 1142 t.Cleanup(func() { 1143 goleak.VerifyNone(t) 1144 }) 1145 tests := ListUsersTests{ 1146 { 1147 name: "intersection", 1148 req: &openfgav1.ListUsersRequest{ 1149 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1150 Relation: "viewer", 1151 UserFilters: []*openfgav1.UserTypeFilter{ 1152 { 1153 Type: "user", 1154 }, 1155 }, 1156 }, 1157 model: `model 1158 schema 1.1 1159 type user 1160 type document 1161 relations 1162 define required: [user] 1163 define required_other: [user] 1164 define viewer: required and required_other`, 1165 1166 tuples: []*openfgav1.TupleKey{ 1167 tuple.NewTupleKey("document:1", "required", "user:will"), 1168 tuple.NewTupleKey("document:1", "required_other", "user:will"), 1169 1170 tuple.NewTupleKey("document:1", "required", "user:jon"), 1171 tuple.NewTupleKey("document:1", "required_other", "user:maria"), 1172 }, 1173 expectedUsers: []string{"user:will"}, 1174 }, 1175 { 1176 name: "intersection_multiple", 1177 req: &openfgav1.ListUsersRequest{ 1178 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1179 Relation: "viewer", 1180 UserFilters: []*openfgav1.UserTypeFilter{ 1181 { 1182 Type: "user", 1183 }, 1184 }, 1185 }, 1186 model: `model 1187 schema 1.1 1188 type user 1189 type document 1190 relations 1191 define required_1: [user] 1192 define required_2: [user] 1193 define required_3: [user] 1194 define viewer: [user] and required_1 and required_2 and required_3`, 1195 1196 tuples: []*openfgav1.TupleKey{ 1197 tuple.NewTupleKey("document:1", "viewer", "user:will"), 1198 tuple.NewTupleKey("document:1", "required_1", "user:will"), 1199 tuple.NewTupleKey("document:1", "required_2", "user:will"), 1200 tuple.NewTupleKey("document:1", "required_3", "user:will"), 1201 1202 tuple.NewTupleKey("document:1", "viewer", "user:jon"), 1203 tuple.NewTupleKey("document:1", "required_1", "user:jon"), 1204 tuple.NewTupleKey("document:1", "required_2", "user:jon"), 1205 1206 tuple.NewTupleKey("document:1", "viewer", "user:maria"), 1207 tuple.NewTupleKey("document:1", "required_1", "user:maria"), 1208 1209 tuple.NewTupleKey("document:1", "viewer", "user:poovam"), 1210 }, 1211 expectedUsers: []string{"user:will"}, 1212 }, 1213 { 1214 name: "intersection_at_multiple_levels", 1215 req: &openfgav1.ListUsersRequest{ 1216 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1217 Relation: "viewer", 1218 UserFilters: []*openfgav1.UserTypeFilter{ 1219 { 1220 Type: "user", 1221 }, 1222 }, 1223 }, 1224 model: `model 1225 schema 1.1 1226 type user 1227 type document 1228 relations 1229 define required: [user] 1230 define owner: [user] and required 1231 define editor: [user] and owner 1232 define viewer: [user] and editor`, 1233 tuples: []*openfgav1.TupleKey{ 1234 tuple.NewTupleKey("document:1", "required", "user:will"), 1235 tuple.NewTupleKey("document:1", "owner", "user:will"), 1236 tuple.NewTupleKey("document:1", "editor", "user:will"), 1237 tuple.NewTupleKey("document:1", "viewer", "user:will"), 1238 1239 tuple.NewTupleKey("document:1", "viewer", "user:jon"), 1240 tuple.NewTupleKey("document:1", "owner", "user:jon"), 1241 tuple.NewTupleKey("document:1", "editor", "user:jon"), 1242 1243 tuple.NewTupleKey("document:1", "viewer", "user:maria"), 1244 tuple.NewTupleKey("document:1", "owner", "user:maria"), 1245 tuple.NewTupleKey("document:1", "required", "user:maria"), 1246 }, 1247 expectedUsers: []string{"user:will"}, 1248 }, 1249 { 1250 name: "intersection_and_ttu", 1251 req: &openfgav1.ListUsersRequest{ 1252 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1253 Relation: "viewer", 1254 UserFilters: []*openfgav1.UserTypeFilter{ 1255 { 1256 Type: "user", 1257 }, 1258 }, 1259 }, 1260 model: `model 1261 schema 1.1 1262 type user 1263 type folder 1264 relations 1265 define viewer: [user] 1266 type document 1267 relations 1268 define required: [user] 1269 define parent: [folder] 1270 define viewer: (viewer from parent) and required`, 1271 1272 tuples: []*openfgav1.TupleKey{ 1273 tuple.NewTupleKey("document:1", "required", "user:will"), 1274 tuple.NewTupleKey("folder:x", "viewer", "user:will"), 1275 tuple.NewTupleKey("document:1", "parent", "folder:x"), 1276 1277 tuple.NewTupleKey("document:1", "required", "user:maria"), 1278 tuple.NewTupleKey("folder:x", "viewer", "user:jon"), 1279 }, 1280 expectedUsers: []string{"user:will"}, 1281 }, 1282 } 1283 tests.runListUsersTestCases(t) 1284 } 1285 1286 func TestListUsersUnion(t *testing.T) { 1287 t.Cleanup(func() { 1288 goleak.VerifyNone(t) 1289 }) 1290 tests := ListUsersTests{ 1291 { 1292 name: "union", 1293 req: &openfgav1.ListUsersRequest{ 1294 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1295 Relation: "viewer", 1296 UserFilters: []*openfgav1.UserTypeFilter{ 1297 { 1298 Type: "user", 1299 }, 1300 }, 1301 }, 1302 model: `model 1303 schema 1.1 1304 type user 1305 type document 1306 relations 1307 define optional_1: [user] 1308 define optional_2: [user] 1309 define viewer: optional_1 or optional_2`, 1310 1311 tuples: []*openfgav1.TupleKey{ 1312 tuple.NewTupleKey("document:1", "optional_1", "user:will"), 1313 tuple.NewTupleKey("document:1", "optional_2", "user:will"), 1314 1315 tuple.NewTupleKey("document:1", "optional_1", "user:jon"), 1316 tuple.NewTupleKey("document:1", "optional_2", "user:maria"), 1317 }, 1318 expectedUsers: []string{"user:will", "user:jon", "user:maria"}, 1319 }, 1320 { 1321 name: "union_and_ttu", 1322 req: &openfgav1.ListUsersRequest{ 1323 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1324 Relation: "viewer", 1325 UserFilters: []*openfgav1.UserTypeFilter{ 1326 { 1327 Type: "user", 1328 }, 1329 }, 1330 }, 1331 model: `model 1332 schema 1.1 1333 type user 1334 type folder 1335 relations 1336 define viewer: [user] 1337 type document 1338 relations 1339 define optional: [user] 1340 define parent: [folder] 1341 define viewer: (viewer from parent) or optional`, 1342 1343 tuples: []*openfgav1.TupleKey{ 1344 tuple.NewTupleKey("document:1", "optional", "user:will"), 1345 tuple.NewTupleKey("folder:x", "viewer", "user:will"), 1346 tuple.NewTupleKey("document:1", "parent", "folder:x"), 1347 1348 tuple.NewTupleKey("document:1", "optional", "user:maria"), 1349 tuple.NewTupleKey("folder:x", "viewer", "user:jon"), 1350 }, 1351 expectedUsers: []string{"user:will", "user:maria", "user:jon"}, 1352 }, 1353 { 1354 name: "union_all_possible_rewrites", 1355 req: &openfgav1.ListUsersRequest{ 1356 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1357 Relation: "viewer", 1358 UserFilters: []*openfgav1.UserTypeFilter{ 1359 { 1360 Type: "user", 1361 }, 1362 }, 1363 }, 1364 model: `model 1365 schema 1.1 1366 type user 1367 type folder 1368 relations 1369 define viewer: [user] 1370 type document 1371 relations 1372 define parent: [folder] 1373 define editor: [user] 1374 define viewer: [user] or editor or viewer from parent`, 1375 tuples: []*openfgav1.TupleKey{ 1376 tuple.NewTupleKey("folder:x", "viewer", "user:will"), 1377 tuple.NewTupleKey("document:1", "parent", "folder:x"), 1378 tuple.NewTupleKey("document:1", "editor", "user:maria"), 1379 tuple.NewTupleKey("document:1", "viewer", "user:jon"), 1380 tuple.NewTupleKey("folder:other", "viewer", "user:poovam"), 1381 }, 1382 expectedUsers: []string{"user:jon", "user:maria", "user:will"}, 1383 }, 1384 } 1385 tests.runListUsersTestCases(t) 1386 } 1387 1388 func TestListUsersExclusion(t *testing.T) { 1389 t.Cleanup(func() { 1390 goleak.VerifyNone(t) 1391 }) 1392 tests := ListUsersTests{ 1393 { 1394 name: "exclusion", 1395 req: &openfgav1.ListUsersRequest{ 1396 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1397 Relation: "viewer", 1398 UserFilters: []*openfgav1.UserTypeFilter{ 1399 { 1400 Type: "user", 1401 }, 1402 }, 1403 }, 1404 model: `model 1405 schema 1.1 1406 type user 1407 type document 1408 relations 1409 define blocked: [user] 1410 define viewer: [user] but not blocked`, 1411 1412 tuples: []*openfgav1.TupleKey{ 1413 tuple.NewTupleKey("document:1", "viewer", "user:blocked_user"), 1414 tuple.NewTupleKey("document:1", "blocked", "user:blocked_user"), 1415 tuple.NewTupleKey("document:1", "viewer", "user:will"), 1416 tuple.NewTupleKey("document:1", "blocked", "user:another_blocked_user"), 1417 }, 1418 expectedUsers: []string{"user:will"}, 1419 }, 1420 { 1421 name: "exclusion_multiple", 1422 req: &openfgav1.ListUsersRequest{ 1423 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1424 Relation: "viewer", 1425 UserFilters: []*openfgav1.UserTypeFilter{ 1426 { 1427 Type: "user", 1428 }, 1429 }, 1430 }, 1431 model: `model 1432 schema 1.1 1433 type user 1434 type document 1435 relations 1436 define blocked_1: [user] 1437 define blocked_2: [user] 1438 define viewer: ([user] but not blocked_1) but not blocked_2`, 1439 1440 tuples: []*openfgav1.TupleKey{ 1441 tuple.NewTupleKey("document:1", "viewer", "user:will"), 1442 1443 tuple.NewTupleKey("document:1", "viewer", "user:maria"), 1444 tuple.NewTupleKey("document:1", "blocked_1", "user:maria"), 1445 1446 tuple.NewTupleKey("document:1", "viewer", "user:jon"), 1447 tuple.NewTupleKey("document:1", "blocked_2", "user:jon"), 1448 1449 tuple.NewTupleKey("document:1", "viewer", "user:poovam"), 1450 tuple.NewTupleKey("document:1", "blocked_1", "user:poovam"), 1451 tuple.NewTupleKey("document:1", "blocked_2", "user:poovam"), 1452 }, 1453 expectedUsers: []string{"user:will"}, 1454 }, 1455 { 1456 name: "exclusion_chained_computed", 1457 req: &openfgav1.ListUsersRequest{ 1458 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1459 Relation: "viewer", 1460 UserFilters: []*openfgav1.UserTypeFilter{ 1461 { 1462 Type: "user", 1463 }, 1464 }, 1465 }, 1466 model: `model 1467 schema 1.1 1468 1469 type org 1470 relations 1471 define blocked: [user] 1472 1473 type user 1474 1475 type document 1476 relations 1477 define parent: [org] 1478 define owner: [user] 1479 define blocked: blocked from parent 1480 define editor: owner but not blocked 1481 define viewer: editor`, 1482 1483 tuples: []*openfgav1.TupleKey{ 1484 tuple.NewTupleKey("document:1", "parent", "org:x"), 1485 tuple.NewTupleKey("document:1", "owner", "user:will"), 1486 tuple.NewTupleKey("document:1", "owner", "user:poovam"), 1487 1488 tuple.NewTupleKey("org:x", "blocked", "user:poovam"), 1489 }, 1490 expectedUsers: []string{"user:will"}, 1491 }, 1492 { 1493 name: "exclusion_and_ttu", 1494 req: &openfgav1.ListUsersRequest{ 1495 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1496 Relation: "viewer", 1497 UserFilters: []*openfgav1.UserTypeFilter{ 1498 { 1499 Type: "user", 1500 }, 1501 }, 1502 }, 1503 model: `model 1504 schema 1.1 1505 1506 type user 1507 1508 type org 1509 relations 1510 define blocked: [user] 1511 1512 type folder 1513 relations 1514 define blocked: blocked from org 1515 define org: [org] 1516 define viewer: [user] 1517 1518 type document 1519 relations 1520 define parent: [folder] 1521 define viewer: viewer from parent but not blocked from parent 1522 `, 1523 1524 tuples: []*openfgav1.TupleKey{ 1525 tuple.NewTupleKey("folder:x", "org", "org:x"), 1526 1527 tuple.NewTupleKey("document:1", "parent", "folder:x"), 1528 tuple.NewTupleKey("folder:x", "viewer", "user:will"), 1529 1530 tuple.NewTupleKey("folder:x", "viewer", "user:maria"), 1531 tuple.NewTupleKey("org:x", "blocked", "user:maria"), 1532 }, 1533 expectedUsers: []string{"user:will"}, 1534 }, 1535 { 1536 name: "exclusion_and_self_referential_tuples_1", 1537 req: &openfgav1.ListUsersRequest{ 1538 Object: &openfgav1.Object{Type: "group", Id: "1"}, 1539 Relation: "member", 1540 UserFilters: []*openfgav1.UserTypeFilter{ 1541 { 1542 Type: "user", 1543 }, 1544 }, 1545 }, 1546 model: `model 1547 schema 1.1 1548 1549 type user 1550 1551 type group 1552 relations 1553 define member: [user, group#member] but not blocked 1554 define blocked: [user, group#member]`, 1555 1556 tuples: []*openfgav1.TupleKey{ 1557 tuple.NewTupleKey("group:1", "blocked", "group:1#member"), 1558 tuple.NewTupleKey("group:1", "member", "user:will"), 1559 }, 1560 expectedUsers: []string{}, 1561 }, 1562 { 1563 name: "exclusion_with_chained_negation", 1564 req: &openfgav1.ListUsersRequest{ 1565 Object: &openfgav1.Object{Type: "document", Id: "2"}, 1566 Relation: "viewer", 1567 UserFilters: []*openfgav1.UserTypeFilter{ 1568 { 1569 Type: "user", 1570 }, 1571 }, 1572 }, 1573 model: `model 1574 schema 1.1 1575 1576 type user 1577 1578 type document 1579 relations 1580 define unblocked: [user] 1581 define blocked: [user, document#viewer] but not unblocked 1582 define viewer: [user, document#blocked] but not blocked 1583 `, 1584 tuples: []*openfgav1.TupleKey{ 1585 tuple.NewTupleKey("document:1", "viewer", "document:2#blocked"), 1586 tuple.NewTupleKey("document:2", "blocked", "document:1#viewer"), 1587 tuple.NewTupleKey("document:2", "viewer", "user:jon"), 1588 tuple.NewTupleKey("document:2", "unblocked", "user:jon"), 1589 }, 1590 expectedUsers: []string{"user:jon"}, 1591 }, 1592 { 1593 name: "non_stratifiable_exclusion_containing_cycle_1", 1594 req: &openfgav1.ListUsersRequest{ 1595 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1596 Relation: "viewer", 1597 UserFilters: []*openfgav1.UserTypeFilter{ 1598 { 1599 Type: "document", 1600 Relation: "blocked", 1601 }, 1602 }, 1603 }, 1604 model: `model 1605 schema 1.1 1606 1607 type user 1608 1609 type document 1610 relations 1611 define blocked: [user, document#viewer] 1612 define viewer: [user, document#blocked] but not blocked 1613 `, 1614 tuples: []*openfgav1.TupleKey{ 1615 tuple.NewTupleKey("document:1", "viewer", "document:2#blocked"), 1616 tuple.NewTupleKey("document:2", "blocked", "document:1#viewer"), 1617 }, 1618 expectedUsers: []string{"document:2#blocked"}, 1619 }, 1620 } 1621 tests.runListUsersTestCases(t) 1622 } 1623 1624 func TestListUsersExclusionWildcards(t *testing.T) { 1625 t.Cleanup(func() { 1626 goleak.VerifyNone(t) 1627 }) 1628 1629 model := `model 1630 schema 1.1 1631 1632 type user 1633 1634 type document 1635 relations 1636 define blocked: [user:*,user] 1637 define viewer: [user:*,user] but not blocked` 1638 1639 tests := ListUsersTests{ 1640 { 1641 name: "exclusion_and_wildcards_1", 1642 req: &openfgav1.ListUsersRequest{ 1643 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1644 Relation: "viewer", 1645 UserFilters: []*openfgav1.UserTypeFilter{ 1646 { 1647 Type: "user", 1648 }, 1649 }, 1650 }, 1651 model: model, 1652 tuples: []*openfgav1.TupleKey{ 1653 tuple.NewTupleKey("document:1", "viewer", "user:will"), 1654 tuple.NewTupleKey("document:1", "blocked", "user:*"), 1655 }, 1656 expectedUsers: []string{}, 1657 }, 1658 { 1659 name: "exclusion_and_wildcards_2", 1660 req: &openfgav1.ListUsersRequest{ 1661 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1662 Relation: "viewer", 1663 UserFilters: []*openfgav1.UserTypeFilter{ 1664 { 1665 Type: "user", 1666 }, 1667 }, 1668 }, 1669 model: model, 1670 tuples: []*openfgav1.TupleKey{ 1671 tuple.NewTupleKey("document:1", "viewer", "user:will"), 1672 tuple.NewTupleKey("document:1", "blocked", "user:will"), 1673 tuple.NewTupleKey("document:1", "blocked", "user:*"), 1674 }, 1675 expectedUsers: []string{}, 1676 }, 1677 { 1678 name: "exclusion_and_wildcards_3", 1679 req: &openfgav1.ListUsersRequest{ 1680 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1681 Relation: "viewer", 1682 UserFilters: []*openfgav1.UserTypeFilter{ 1683 { 1684 Type: "user", 1685 }, 1686 }, 1687 }, 1688 model: model, 1689 tuples: []*openfgav1.TupleKey{ 1690 tuple.NewTupleKey("document:1", "viewer", "user:will"), 1691 tuple.NewTupleKey("document:1", "viewer", "user:*"), 1692 tuple.NewTupleKey("document:1", "blocked", "user:will"), 1693 tuple.NewTupleKey("document:1", "blocked", "user:*"), 1694 }, 1695 expectedUsers: []string{}, 1696 }, 1697 { 1698 name: "exclusion_and_wildcards_4", 1699 req: &openfgav1.ListUsersRequest{ 1700 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1701 Relation: "viewer", 1702 UserFilters: []*openfgav1.UserTypeFilter{ 1703 { 1704 Type: "user", 1705 }, 1706 }, 1707 }, 1708 model: model, 1709 tuples: []*openfgav1.TupleKey{ 1710 tuple.NewTupleKey("document:1", "viewer", "user:*"), 1711 tuple.NewTupleKey("document:1", "blocked", "user:maria"), 1712 }, 1713 expectedUsers: []string{"user:*"}, 1714 }, 1715 { 1716 name: "exclusion_and_wildcards_5", 1717 req: &openfgav1.ListUsersRequest{ 1718 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1719 Relation: "viewer", 1720 UserFilters: []*openfgav1.UserTypeFilter{ 1721 { 1722 Type: "user", 1723 }, 1724 }, 1725 }, 1726 model: model, 1727 tuples: []*openfgav1.TupleKey{ 1728 tuple.NewTupleKey("document:1", "viewer", "user:*"), 1729 tuple.NewTupleKey("document:1", "viewer", "user:maria"), 1730 tuple.NewTupleKey("document:1", "blocked", "user:*"), 1731 }, 1732 expectedUsers: []string{}, 1733 }, 1734 { 1735 name: "exclusion_and_wildcards_6", 1736 req: &openfgav1.ListUsersRequest{ 1737 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1738 Relation: "viewer", 1739 UserFilters: []*openfgav1.UserTypeFilter{ 1740 { 1741 Type: "user", 1742 }, 1743 }, 1744 }, 1745 model: model, 1746 tuples: []*openfgav1.TupleKey{ 1747 tuple.NewTupleKey("document:1", "viewer", "user:*"), 1748 tuple.NewTupleKey("document:1", "viewer", "user:maria"), 1749 tuple.NewTupleKey("document:1", "viewer", "user:jon"), 1750 tuple.NewTupleKey("document:1", "blocked", "user:jon"), 1751 tuple.NewTupleKey("document:1", "blocked", "user:will"), 1752 }, 1753 expectedUsers: []string{"user:*", "user:maria"}, 1754 }, 1755 } 1756 tests.runListUsersTestCases(t) 1757 } 1758 1759 func TestListUsersWildcards(t *testing.T) { 1760 t.Cleanup(func() { 1761 goleak.VerifyNone(t) 1762 }) 1763 tests := ListUsersTests{ 1764 { 1765 name: "direct_relationship_wildcard", 1766 req: &openfgav1.ListUsersRequest{ 1767 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1768 Relation: "viewer", 1769 UserFilters: []*openfgav1.UserTypeFilter{ 1770 { 1771 Type: "user", 1772 }, 1773 }, 1774 }, 1775 model: `model 1776 schema 1.1 1777 type user 1778 type document 1779 relations 1780 define viewer: [user:*]`, 1781 tuples: []*openfgav1.TupleKey{ 1782 tuple.NewTupleKey("document:1", "viewer", "user:*"), 1783 tuple.NewTupleKey("document:2", "viewer", "user:*"), 1784 }, 1785 expectedUsers: []string{"user:*"}, 1786 }, 1787 { 1788 name: "direct_relationship_wildcard_with_direct_relationships_also", 1789 req: &openfgav1.ListUsersRequest{ 1790 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1791 Relation: "viewer", 1792 UserFilters: []*openfgav1.UserTypeFilter{ 1793 { 1794 Type: "user", 1795 }, 1796 }, 1797 }, 1798 model: `model 1799 schema 1.1 1800 type user 1801 type document 1802 relations 1803 define viewer: [user:*,user]`, 1804 tuples: []*openfgav1.TupleKey{ 1805 tuple.NewTupleKey("document:1", "viewer", "user:*"), 1806 tuple.NewTupleKey("document:1", "viewer", "user:will"), 1807 tuple.NewTupleKey("document:2", "viewer", "user:maria"), 1808 }, 1809 expectedUsers: []string{"user:*", "user:will"}, 1810 }, 1811 { 1812 name: "multiple_possible_wildcards_user_granularity", 1813 req: &openfgav1.ListUsersRequest{ 1814 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1815 Relation: "viewer", 1816 UserFilters: []*openfgav1.UserTypeFilter{ 1817 { 1818 Type: "user", 1819 }, 1820 }, 1821 }, 1822 model: `model 1823 schema 1.1 1824 type user 1825 type group 1826 type document 1827 relations 1828 define viewer: [ group:*, user:*]`, 1829 1830 tuples: []*openfgav1.TupleKey{ 1831 tuple.NewTupleKey("document:1", "viewer", "user:*"), 1832 tuple.NewTupleKey("document:1", "viewer", "group:*"), 1833 }, 1834 expectedUsers: []string{"user:*"}, 1835 }, 1836 { 1837 name: "wildcard_with_indirection", 1838 req: &openfgav1.ListUsersRequest{ 1839 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1840 Relation: "viewer", 1841 UserFilters: []*openfgav1.UserTypeFilter{ 1842 { 1843 Type: "user", 1844 }, 1845 }, 1846 }, 1847 model: `model 1848 schema 1.1 1849 type user 1850 type group 1851 relations 1852 define member: [user:*] 1853 type document 1854 relations 1855 define viewer: [group#member]`, 1856 tuples: []*openfgav1.TupleKey{ 1857 tuple.NewTupleKey("document:1", "viewer", "group:eng#member"), 1858 tuple.NewTupleKey("group:eng", "member", "user:*"), 1859 }, 1860 expectedUsers: []string{"user:*"}, 1861 }, 1862 { 1863 name: "wildcard_computed_ttu", 1864 req: &openfgav1.ListUsersRequest{ 1865 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1866 Relation: "viewer", 1867 UserFilters: []*openfgav1.UserTypeFilter{ 1868 { 1869 Type: "user", 1870 }, 1871 }, 1872 }, 1873 model: `model 1874 schema 1.1 1875 type user 1876 type group 1877 relations 1878 define member: [user:*] 1879 type folder 1880 relations 1881 define can_view: viewer or can_view from parent 1882 define parent: [folder] 1883 define viewer: [group#member] 1884 type document 1885 relations 1886 define parent: [folder] 1887 define viewer: can_view from parent`, 1888 tuples: []*openfgav1.TupleKey{ 1889 tuple.NewTupleKey("group:eng", "member", "user:*"), 1890 tuple.NewTupleKey("folder:eng", "viewer", "group:eng#member"), 1891 tuple.NewTupleKey("folder:x", "parent", "folder:eng"), 1892 tuple.NewTupleKey("document:1", "parent", "folder:x"), 1893 tuple.NewTupleKey("document:1", "parent", "folder:eng"), 1894 }, 1895 expectedUsers: []string{"user:*"}, 1896 }, 1897 } 1898 tests.runListUsersTestCases(t) 1899 } 1900 1901 func TestListUsersEdgePruning(t *testing.T) { 1902 t.Cleanup(func() { 1903 goleak.VerifyNone(t) 1904 }) 1905 tests := ListUsersTests{ 1906 { 1907 name: "valid_edges", 1908 req: &openfgav1.ListUsersRequest{ 1909 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1910 Relation: "viewer", 1911 UserFilters: []*openfgav1.UserTypeFilter{ 1912 { 1913 Type: "user", 1914 }, 1915 }, 1916 }, 1917 model: `model 1918 schema 1.1 1919 type user 1920 1921 type document 1922 relations 1923 define viewer: [user]`, 1924 tuples: []*openfgav1.TupleKey{ 1925 tuple.NewTupleKey("document:1", "viewer", "user:maria"), 1926 }, 1927 expectedUsers: []string{"user:maria"}, 1928 }, 1929 { 1930 name: "valid_edge_several_computed_relations_away", 1931 req: &openfgav1.ListUsersRequest{ 1932 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1933 Relation: "viewer", 1934 UserFilters: []*openfgav1.UserTypeFilter{ 1935 { 1936 Type: "user", 1937 }, 1938 }, 1939 }, 1940 model: `model 1941 schema 1.1 1942 type user 1943 type document 1944 relations 1945 define parent: [user] 1946 define owner: parent 1947 define editor: owner 1948 define viewer: editor`, 1949 tuples: []*openfgav1.TupleKey{ 1950 tuple.NewTupleKey("document:1", "parent", "user:maria"), 1951 }, 1952 expectedUsers: []string{"user:maria"}, 1953 }, 1954 1955 { 1956 name: "user_filter_has_invalid_edge", 1957 req: &openfgav1.ListUsersRequest{ 1958 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1959 Relation: "viewer", 1960 UserFilters: []*openfgav1.UserTypeFilter{ 1961 { 1962 Type: "folder", 1963 }, 1964 }, 1965 }, 1966 model: `model 1967 schema 1.1 1968 type user 1969 type folder 1970 relations 1971 define viewer: [user] 1972 type document 1973 relations 1974 define parent: [folder] 1975 define viewer: viewer from parent`, 1976 tuples: []*openfgav1.TupleKey{ 1977 tuple.NewTupleKey("document:1", "parent", "folder:x"), 1978 tuple.NewTupleKey("folder:x", "viewer", "user:1"), 1979 }, 1980 expectedUsers: []string{}, 1981 }, 1982 { 1983 name: "user_filter_has_valid_edge", 1984 req: &openfgav1.ListUsersRequest{ 1985 Object: &openfgav1.Object{Type: "document", Id: "1"}, 1986 Relation: "viewer", 1987 UserFilters: []*openfgav1.UserTypeFilter{ 1988 { 1989 Type: "user", 1990 }, 1991 }, 1992 }, 1993 model: `model 1994 schema 1.1 1995 type user 1996 type folder 1997 relations 1998 define viewer: [user] 1999 type document 2000 relations 2001 define parent: [folder] 2002 define viewer: viewer from parent`, 2003 tuples: []*openfgav1.TupleKey{ 2004 tuple.NewTupleKey("document:1", "parent", "folder:x"), 2005 tuple.NewTupleKey("folder:x", "viewer", "user:maria"), 2006 }, 2007 expectedUsers: []string{"user:maria"}, 2008 }, 2009 { 2010 name: "user_filter_has_invalid_edge_because_relation", 2011 req: &openfgav1.ListUsersRequest{ 2012 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2013 Relation: "INVALID_RELATION", 2014 UserFilters: []*openfgav1.UserTypeFilter{ 2015 { 2016 Type: "user", 2017 }, 2018 }, 2019 }, 2020 model: `model 2021 schema 1.1 2022 type user 2023 type folder 2024 relations 2025 define viewer: [user] 2026 type document 2027 relations 2028 define parent: [folder] 2029 define viewer: viewer from parent`, 2030 expectedErrorMsg: "'document#INVALID_RELATION' relation is undefined", 2031 }, 2032 } 2033 2034 tests.runListUsersTestCases(t) 2035 } 2036 2037 func TestListUsersWildcardsAndIntersection(t *testing.T) { 2038 t.Cleanup(func() { 2039 goleak.VerifyNone(t) 2040 }) 2041 tests := ListUsersTests{ 2042 { 2043 name: "wildcard_and_intersection", 2044 req: &openfgav1.ListUsersRequest{ 2045 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2046 Relation: "can_view", 2047 UserFilters: []*openfgav1.UserTypeFilter{ 2048 { 2049 Type: "user", 2050 }, 2051 }, 2052 }, 2053 model: `model 2054 schema 1.1 2055 type user 2056 type document 2057 relations 2058 define allowed: [user] 2059 define viewer: [user:*,user] and allowed 2060 define can_view: viewer`, 2061 2062 tuples: []*openfgav1.TupleKey{ 2063 tuple.NewTupleKey("document:1", "allowed", "user:jon"), 2064 tuple.NewTupleKey("document:1", "viewer", "user:*"), 2065 tuple.NewTupleKey("document:1", "viewer", "user:jon"), 2066 }, 2067 expectedUsers: []string{"user:jon"}, 2068 }, 2069 { 2070 name: "with_multiple_wildcards_1", 2071 req: &openfgav1.ListUsersRequest{ 2072 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2073 Relation: "is_public", 2074 UserFilters: []*openfgav1.UserTypeFilter{ 2075 { 2076 Type: "user", 2077 }, 2078 }, 2079 }, 2080 model: `model 2081 schema 1.1 2082 type user 2083 type document 2084 relations 2085 define public_1: [user:*,user] 2086 define public_2: [user:*,user] 2087 define is_public: public_1 and public_2`, 2088 2089 tuples: []*openfgav1.TupleKey{ 2090 tuple.NewTupleKey("document:1", "public_1", "user:maria"), 2091 tuple.NewTupleKey("document:1", "public_2", "user:maria"), 2092 2093 tuple.NewTupleKey("document:1", "public_1", "user:*"), 2094 tuple.NewTupleKey("document:1", "public_2", "user:*"), 2095 2096 tuple.NewTupleKey("document:1", "public_1", "user:jon"), 2097 }, 2098 expectedUsers: []string{"user:maria", "user:*", "user:jon"}, 2099 }, 2100 { 2101 name: "with_multiple_wildcards_2", 2102 req: &openfgav1.ListUsersRequest{ 2103 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2104 Relation: "can_view", 2105 UserFilters: []*openfgav1.UserTypeFilter{ 2106 { 2107 Type: "user", 2108 }, 2109 }, 2110 }, 2111 model: `model 2112 schema 1.1 2113 type user 2114 type document 2115 relations 2116 define viewer: [user:*,user] 2117 define this_is_not_assigned_to_any_user: [user] 2118 define can_view: viewer and this_is_not_assigned_to_any_user`, 2119 2120 tuples: []*openfgav1.TupleKey{ 2121 tuple.NewTupleKey("document:1", "viewer", "user:*"), 2122 tuple.NewTupleKey("document:1", "viewer", "user:will"), 2123 }, 2124 expectedUsers: []string{}, 2125 }, 2126 { 2127 name: "with_multiple_wildcards_3", 2128 req: &openfgav1.ListUsersRequest{ 2129 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2130 Relation: "can_view", 2131 UserFilters: []*openfgav1.UserTypeFilter{ 2132 { 2133 Type: "user", 2134 }, 2135 }, 2136 }, 2137 model: `model 2138 schema 1.1 2139 type user 2140 type document 2141 relations 2142 define viewer: [user:*,user] 2143 define required: [user:*,user] 2144 define can_view: viewer and required`, 2145 2146 tuples: []*openfgav1.TupleKey{ 2147 tuple.NewTupleKey("document:1", "viewer", "user:*"), 2148 tuple.NewTupleKey("document:1", "required", "user:*"), 2149 tuple.NewTupleKey("document:1", "viewer", "user:will"), 2150 tuple.NewTupleKey("document:1", "required", "user:will"), 2151 }, 2152 expectedUsers: []string{"user:*", "user:will"}, 2153 }, 2154 { 2155 name: "with_multiple_wildcards_4", 2156 req: &openfgav1.ListUsersRequest{ 2157 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2158 Relation: "can_view", 2159 UserFilters: []*openfgav1.UserTypeFilter{ 2160 { 2161 Type: "user", 2162 }, 2163 }, 2164 }, 2165 model: `model 2166 schema 1.1 2167 type user 2168 type document 2169 relations 2170 define required_1: [user] 2171 define required_2: [user] 2172 define can_view: required_1 and required_2`, 2173 2174 tuples: []*openfgav1.TupleKey{ 2175 tuple.NewTupleKey("document:1", "required_1", "user:*"), // Invalid tuple, wildcard not allowed 2176 tuple.NewTupleKey("document:1", "required_2", "user:*"), // Invalid tuple, wildcard not allowed 2177 tuple.NewTupleKey("document:1", "required_1", "user:maria"), 2178 tuple.NewTupleKey("document:1", "required_2", "user:maria"), 2179 }, 2180 expectedUsers: []string{"user:maria"}, 2181 }, 2182 2183 { 2184 name: "with_multiple_wildcards_5", 2185 req: &openfgav1.ListUsersRequest{ 2186 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2187 Relation: "can_view", 2188 UserFilters: []*openfgav1.UserTypeFilter{ 2189 { 2190 Type: "user", 2191 }, 2192 }, 2193 }, 2194 model: `model 2195 schema 1.1 2196 type user 2197 type document 2198 relations 2199 define required_1: [user] 2200 define required_2: [user:*] 2201 define can_view: required_1 and required_2`, 2202 2203 tuples: []*openfgav1.TupleKey{ 2204 tuple.NewTupleKey("document:1", "required_1", "user:maria"), 2205 tuple.NewTupleKey("document:1", "required_2", "user:*"), 2206 }, 2207 expectedUsers: []string{"user:maria"}, 2208 }, 2209 { 2210 name: "with_multiple_wildcards_6", 2211 req: &openfgav1.ListUsersRequest{ 2212 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2213 Relation: "can_view", 2214 UserFilters: []*openfgav1.UserTypeFilter{ 2215 { 2216 Type: "user", 2217 }, 2218 }, 2219 }, 2220 model: `model 2221 schema 1.1 2222 type user 2223 type document 2224 relations 2225 define required_1: [user] 2226 define required_2: [user] 2227 define can_view: required_1 and required_2`, 2228 2229 tuples: []*openfgav1.TupleKey{ 2230 tuple.NewTupleKey("document:1", "required_1", "user:maria"), 2231 tuple.NewTupleKey("document:1", "required_1", "user:jon"), 2232 2233 tuple.NewTupleKey("document:1", "required_1", "user:will"), 2234 tuple.NewTupleKey("document:1", "required_2", "user:will"), 2235 2236 tuple.NewTupleKey("document:1", "required_1", "user:*"), // Invalid tuple, wildcard not allowed 2237 tuple.NewTupleKey("document:1", "required_2", "user:*"), // Invalid tuple, wildcard not allowed 2238 }, 2239 expectedUsers: []string{"user:will"}, 2240 }, 2241 { 2242 name: "with_multiple_wildcards_7", 2243 req: &openfgav1.ListUsersRequest{ 2244 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2245 Relation: "can_view", 2246 UserFilters: []*openfgav1.UserTypeFilter{ 2247 { 2248 Type: "user", 2249 }, 2250 }, 2251 }, 2252 model: `model 2253 schema 1.1 2254 type user 2255 type document 2256 relations 2257 define required_1: [user,user:*] 2258 define required_2: [user,user:*] 2259 define can_view: required_1 and required_2`, 2260 2261 tuples: []*openfgav1.TupleKey{ 2262 tuple.NewTupleKey("document:1", "required_1", "user:maria"), 2263 tuple.NewTupleKey("document:1", "required_1", "user:jon"), 2264 tuple.NewTupleKey("document:1", "required_1", "user:*"), 2265 tuple.NewTupleKey("document:1", "required_2", "user:will"), 2266 }, 2267 expectedUsers: []string{"user:will"}, 2268 }, 2269 { 2270 name: "wildcard_intermediate_expansion", 2271 req: &openfgav1.ListUsersRequest{ 2272 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2273 Relation: "can_view", 2274 UserFilters: []*openfgav1.UserTypeFilter{ 2275 { 2276 Type: "user", 2277 }, 2278 }, 2279 }, 2280 model: `model 2281 schema 1.1 2282 2283 type user 2284 2285 type group 2286 relations 2287 define member: [user:*, user] 2288 2289 type document 2290 relations 2291 define group: [group] 2292 define viewer: [group#member] and member from group 2293 define can_view: viewer`, 2294 2295 tuples: []*openfgav1.TupleKey{ 2296 tuple.NewTupleKey("document:1", "viewer", "group:y#member"), 2297 tuple.NewTupleKey("document:1", "group", "group:x"), 2298 tuple.NewTupleKey("group:x", "member", "user:*"), 2299 tuple.NewTupleKey("group:y", "member", "user:jon"), 2300 }, 2301 expectedUsers: []string{"user:jon"}, 2302 }, 2303 } 2304 tests.runListUsersTestCases(t) 2305 } 2306 2307 func TestListUsersCycleDetection(t *testing.T) { 2308 t.Cleanup(func() { 2309 goleak.VerifyNone(t) 2310 }) 2311 storeID := ulid.Make().String() 2312 modelID := ulid.Make().String() 2313 2314 mockController := gomock.NewController(t) 2315 defer mockController.Finish() 2316 2317 mockDatastore := mocks.NewMockOpenFGADatastore(mockController) 2318 2319 // Times(0) ensures that we exit quickly 2320 mockDatastore.EXPECT().Read(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) 2321 2322 l := NewListUsersQuery(mockDatastore, WithResolveNodeLimit(maximumRecursiveDepth)) 2323 channelDone := make(chan struct{}) 2324 channelWithResults := make(chan *openfgav1.User) 2325 channelWithError := make(chan error, 1) 2326 model := testutils.MustTransformDSLToProtoWithID(` 2327 model 2328 schema 1.1 2329 type user 2330 type document 2331 relations 2332 define viewer: [user] 2333 `) 2334 typesys := typesystem.New(model) 2335 ctx := typesystem.ContextWithTypesystem(context.Background(), typesys) 2336 2337 t.Run("enters_loop_detection", func(t *testing.T) { 2338 visitedUserset := &openfgav1.UsersetUser{ 2339 Type: "document", 2340 Id: "1", 2341 Relation: "viewer", 2342 } 2343 visitedUsersetKey := fmt.Sprintf("%s:%s#%s", visitedUserset.GetType(), visitedUserset.GetId(), visitedUserset.GetRelation()) 2344 visitedUsersets := make(map[string]struct{}) 2345 visitedUsersets[visitedUsersetKey] = struct{}{} 2346 2347 go func() { 2348 resp := l.expand(ctx, &internalListUsersRequest{ 2349 ListUsersRequest: &openfgav1.ListUsersRequest{ 2350 StoreId: storeID, 2351 AuthorizationModelId: modelID, 2352 Object: &openfgav1.Object{ 2353 Type: visitedUserset.GetType(), 2354 Id: visitedUserset.GetId(), 2355 }, 2356 Relation: visitedUserset.GetRelation(), 2357 UserFilters: []*openfgav1.UserTypeFilter{{ 2358 Type: "user", 2359 }}, 2360 }, 2361 visitedUsersetsMap: visitedUsersets, 2362 }, channelWithResults) 2363 if resp.err != nil { 2364 channelWithError <- resp.err 2365 return 2366 } 2367 channelDone <- struct{}{} 2368 }() 2369 2370 select { 2371 case <-channelWithError: 2372 require.FailNow(t, "expected 0 errors") 2373 case <-channelWithResults: 2374 require.FailNow(t, "expected 0 results") 2375 case <-channelDone: 2376 break 2377 } 2378 }) 2379 } 2380 2381 func TestListUsersDepthExceeded(t *testing.T) { 2382 t.Cleanup(func() { 2383 goleak.VerifyNone(t) 2384 }) 2385 model := `model 2386 schema 1.1 2387 type user 2388 2389 type folder 2390 relations 2391 define parent: [folder] 2392 define viewer: [user] or viewer from parent` 2393 2394 tuples := []*openfgav1.TupleKey{ 2395 tuple.NewTupleKey("folder:26", "viewer", "user:maria"), 2396 tuple.NewTupleKey("folder:25", "parent", "folder:26"), 2397 tuple.NewTupleKey("folder:24", "parent", "folder:25"), 2398 tuple.NewTupleKey("folder:23", "parent", "folder:24"), 2399 tuple.NewTupleKey("folder:22", "parent", "folder:23"), 2400 tuple.NewTupleKey("folder:21", "parent", "folder:22"), 2401 tuple.NewTupleKey("folder:20", "parent", "folder:21"), 2402 tuple.NewTupleKey("folder:19", "parent", "folder:20"), 2403 tuple.NewTupleKey("folder:18", "parent", "folder:19"), 2404 tuple.NewTupleKey("folder:17", "parent", "folder:18"), 2405 tuple.NewTupleKey("folder:16", "parent", "folder:17"), 2406 tuple.NewTupleKey("folder:15", "parent", "folder:16"), 2407 tuple.NewTupleKey("folder:14", "parent", "folder:15"), 2408 tuple.NewTupleKey("folder:13", "parent", "folder:14"), 2409 tuple.NewTupleKey("folder:12", "parent", "folder:13"), 2410 tuple.NewTupleKey("folder:11", "parent", "folder:12"), 2411 tuple.NewTupleKey("folder:10", "parent", "folder:11"), 2412 tuple.NewTupleKey("folder:9", "parent", "folder:10"), 2413 tuple.NewTupleKey("folder:8", "parent", "folder:9"), 2414 tuple.NewTupleKey("folder:7", "parent", "folder:8"), 2415 tuple.NewTupleKey("folder:6", "parent", "folder:7"), 2416 tuple.NewTupleKey("folder:5", "parent", "folder:6"), 2417 tuple.NewTupleKey("folder:4", "parent", "folder:5"), 2418 tuple.NewTupleKey("folder:3", "parent", "folder:4"), 2419 tuple.NewTupleKey("folder:2", "parent", "folder:3"), // folder:2 will not exceed depth limit of 25 2420 tuple.NewTupleKey("folder:1", "parent", "folder:2"), // folder:1 will exceed depth limit of 25 2421 } 2422 2423 tests := ListUsersTests{ 2424 { 2425 name: "depth_should_exceed_limit", 2426 req: &openfgav1.ListUsersRequest{ 2427 Object: &openfgav1.Object{ 2428 Type: "folder", 2429 Id: "1", // Exceeded because we expand up until folder:1, beyond 25 allowable levels 2430 }, 2431 Relation: "viewer", 2432 UserFilters: []*openfgav1.UserTypeFilter{ 2433 { 2434 Type: "user", 2435 }, 2436 }, 2437 }, 2438 model: model, 2439 tuples: tuples, 2440 expectedErrorMsg: graph.ErrResolutionDepthExceeded.Error(), 2441 }, 2442 { 2443 name: "depth_should_not_exceed_limit", 2444 req: &openfgav1.ListUsersRequest{ 2445 Object: &openfgav1.Object{ 2446 Type: "folder", 2447 Id: "2", // Does not exceed limit because we expand up until folder:2, up to the allowable 25 levels 2448 }, 2449 Relation: "viewer", 2450 UserFilters: []*openfgav1.UserTypeFilter{ 2451 { 2452 Type: "user", 2453 }, 2454 }, 2455 }, 2456 model: model, 2457 tuples: tuples, 2458 expectedUsers: []string{"user:maria"}, 2459 }, 2460 } 2461 2462 tests.runListUsersTestCases(t) 2463 } 2464 2465 func TestListUsersStorageErrors(t *testing.T) { 2466 t.Cleanup(func() { 2467 goleak.VerifyNone(t) 2468 }) 2469 testCases := map[string]struct { 2470 req *openfgav1.ListUsersRequest 2471 }{ 2472 `union`: { 2473 req: &openfgav1.ListUsersRequest{ 2474 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2475 Relation: "union", 2476 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2477 }, 2478 }, 2479 `exclusion`: { 2480 req: &openfgav1.ListUsersRequest{ 2481 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2482 Relation: "exclusion", 2483 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2484 }, 2485 }, 2486 `intersection`: { 2487 req: &openfgav1.ListUsersRequest{ 2488 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2489 Relation: "intersection", 2490 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2491 }, 2492 }, 2493 } 2494 for name, test := range testCases { 2495 t.Run(name, func(t *testing.T) { 2496 mockController := gomock.NewController(t) 2497 t.Cleanup(func() { 2498 mockController.Finish() 2499 }) 2500 mockDatastore := mocks.NewMockOpenFGADatastore(mockController) 2501 mockDatastore.EXPECT(). 2502 Read(gomock.Any(), gomock.Any(), gomock.Any()). 2503 Return(nil, fmt.Errorf("storage err")). 2504 Times(2) // each relation consists of two handlers 2505 2506 model := testutils.MustTransformDSLToProtoWithID(` 2507 model 2508 schema 1.1 2509 type user 2510 2511 type document 2512 relations 2513 define a: [user] 2514 define b: [user] 2515 define union: a or b 2516 define exclusion: a but not b 2517 define intersection: a and b`) 2518 typesys := typesystem.New(model) 2519 2520 l := NewListUsersQuery(mockDatastore) 2521 2522 ctx := typesystem.ContextWithTypesystem(context.Background(), typesys) 2523 resp, err := l.ListUsers(ctx, test.req) 2524 require.Nil(t, resp) 2525 require.ErrorContains(t, err, "storage err") 2526 }) 2527 } 2528 } 2529 2530 func (testCases ListUsersTests) runListUsersTestCases(t *testing.T) { 2531 storeID := ulid.Make().String() 2532 2533 for _, test := range testCases { 2534 ds := memory.New() 2535 t.Cleanup(ds.Close) 2536 model := testutils.MustTransformDSLToProtoWithID(test.model) 2537 2538 t.Run(test.name, func(t *testing.T) { 2539 typesys, err := typesystem.NewAndValidate(context.Background(), model) 2540 require.NoError(t, err) 2541 2542 err = ds.WriteAuthorizationModel(context.Background(), storeID, model) 2543 require.NoError(t, err) 2544 2545 if len(test.tuples) > 0 { 2546 err = ds.Write(context.Background(), storeID, nil, test.tuples) 2547 require.NoError(t, err) 2548 } 2549 2550 l := NewListUsersQuery(ds, WithResolveNodeLimit(maximumRecursiveDepth)) 2551 2552 ctx := typesystem.ContextWithTypesystem(context.Background(), typesys) 2553 2554 test.req.AuthorizationModelId = model.GetId() 2555 test.req.StoreId = storeID 2556 2557 resp, err := l.ListUsers(ctx, test.req) 2558 2559 actualErrorMsg := "" 2560 if err != nil { 2561 actualErrorMsg = err.Error() 2562 } 2563 require.Equal(t, test.expectedErrorMsg, actualErrorMsg) 2564 2565 actualUsers := resp.GetUsers() 2566 2567 actualCompare := make([]string, len(actualUsers)) 2568 for i, u := range resp.GetUsers() { 2569 actualCompare[i] = tuple.UserProtoToString(u) 2570 } 2571 2572 require.ElementsMatch(t, actualCompare, test.expectedUsers) 2573 }) 2574 } 2575 } 2576 2577 func TestListUsersReadFails_NoLeaks(t *testing.T) { 2578 t.Cleanup(func() { 2579 goleak.VerifyNone(t) 2580 }) 2581 2582 store := ulid.Make().String() 2583 model := testutils.MustTransformDSLToProtoWithID(` 2584 model 2585 schema 1.1 2586 type user 2587 type group 2588 relations 2589 define member: [user] 2590 type document 2591 relations 2592 define viewer: [group#member]`) 2593 2594 mockController := gomock.NewController(t) 2595 defer mockController.Finish() 2596 2597 mockDatastore := mocks.NewMockOpenFGADatastore(mockController) 2598 gomock.InOrder( 2599 mockDatastore.EXPECT().Read(gomock.Any(), store, &openfgav1.TupleKey{ 2600 Relation: "viewer", 2601 Object: "document:1", 2602 }).DoAndReturn(func(_ context.Context, _ string, _ *openfgav1.TupleKey) (storage.TupleIterator, error) { 2603 return mocks.NewErrorTupleIterator([]*openfgav1.Tuple{ 2604 {Key: tuple.NewTupleKey("document:1", "viewer", "group:fga#member")}, 2605 {Key: tuple.NewTupleKey("document:1", "viewer", "group:eng#member")}, 2606 }), nil 2607 }), 2608 mockDatastore.EXPECT().Read(gomock.Any(), store, gomock.Any()). 2609 DoAndReturn(func(_ context.Context, _ string, _ *openfgav1.TupleKey) (storage.TupleIterator, error) { 2610 return storage.NewStaticTupleIterator([]*openfgav1.Tuple{}), nil 2611 }), 2612 ) 2613 2614 typesys, err := typesystem.NewAndValidate(context.Background(), model) 2615 require.NoError(t, err) 2616 ctx := typesystem.ContextWithTypesystem(context.Background(), typesys) 2617 resp, err := NewListUsersQuery(mockDatastore).ListUsers(ctx, &openfgav1.ListUsersRequest{ 2618 StoreId: store, 2619 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2620 Relation: "viewer", 2621 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2622 }) 2623 2624 require.ErrorContains(t, err, "simulated errors") 2625 require.Nil(t, resp) 2626 } 2627 2628 func TestListUsersReadFails_NoLeaks_TTU(t *testing.T) { 2629 t.Cleanup(func() { 2630 goleak.VerifyNone(t) 2631 }) 2632 2633 store := ulid.Make().String() 2634 model := testutils.MustTransformDSLToProtoWithID(` 2635 model 2636 schema 1.1 2637 type user 2638 type folder 2639 relations 2640 define viewer: [user] 2641 type document 2642 relations 2643 define parent: [folder] 2644 define viewer: viewer from parent`) 2645 2646 mockController := gomock.NewController(t) 2647 defer mockController.Finish() 2648 2649 mockDatastore := mocks.NewMockOpenFGADatastore(mockController) 2650 gomock.InOrder( 2651 mockDatastore.EXPECT().Read(gomock.Any(), store, &openfgav1.TupleKey{ 2652 Object: "document:1", 2653 Relation: "parent", 2654 }).DoAndReturn(func(_ context.Context, _ string, _ *openfgav1.TupleKey) (storage.TupleIterator, error) { 2655 return mocks.NewErrorTupleIterator([]*openfgav1.Tuple{ 2656 {Key: tuple.NewTupleKey("document:1", "parent", "folder:1")}, 2657 {Key: tuple.NewTupleKey("document:1", "parent", "folder:2")}, 2658 }), nil 2659 }), 2660 mockDatastore.EXPECT().Read(gomock.Any(), store, &openfgav1.TupleKey{ 2661 Object: "folder:1", 2662 Relation: "viewer", 2663 }).DoAndReturn(func(_ context.Context, _ string, _ *openfgav1.TupleKey) (storage.TupleIterator, error) { 2664 return storage.NewStaticTupleIterator([]*openfgav1.Tuple{}), nil 2665 }), 2666 ) 2667 2668 typesys, err := typesystem.NewAndValidate(context.Background(), model) 2669 require.NoError(t, err) 2670 ctx := typesystem.ContextWithTypesystem(context.Background(), typesys) 2671 resp, err := NewListUsersQuery(mockDatastore).ListUsers(ctx, &openfgav1.ListUsersRequest{ 2672 StoreId: store, 2673 Object: &openfgav1.Object{Type: "document", Id: "1"}, 2674 Relation: "viewer", 2675 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2676 }) 2677 2678 require.ErrorContains(t, err, "simulated errors") 2679 require.Nil(t, resp) 2680 } 2681 2682 func TestListUsersDatastoreQueryCount(t *testing.T) { 2683 t.Cleanup(func() { 2684 goleak.VerifyNone(t) 2685 }) 2686 ds := memory.New() 2687 defer ds.Close() 2688 2689 storeID := ulid.Make().String() 2690 2691 err := ds.Write(context.Background(), storeID, nil, []*openfgav1.TupleKey{ 2692 tuple.NewTupleKey("document:x", "a", "user:jon"), 2693 tuple.NewTupleKey("document:x", "a", "user:maria"), 2694 tuple.NewTupleKey("document:x", "b", "user:maria"), 2695 tuple.NewTupleKey("document:x", "parent", "org:fga"), 2696 tuple.NewTupleKey("org:fga", "member", "user:maria"), 2697 tuple.NewTupleKey("company:fga", "member", "user:maria"), 2698 tuple.NewTupleKey("document:x", "userset", "org:fga#member"), 2699 tuple.NewTupleKey("document:x", "multiple_userset", "org:fga#member"), 2700 tuple.NewTupleKey("document:x", "multiple_userset", "company:fga#member"), 2701 tuple.NewTupleKey("document:public", "wildcard", "user:*"), 2702 }) 2703 require.NoError(t, err) 2704 2705 model := parser.MustTransformDSLToProto(`model 2706 schema 1.1 2707 type user 2708 2709 type company 2710 relations 2711 define member: [user] 2712 2713 type org 2714 relations 2715 define member: [user] 2716 2717 type document 2718 relations 2719 define wildcard: [user:*] 2720 define userset: [org#member] 2721 define multiple_userset: [org#member, company#member] 2722 define a: [user] 2723 define b: [user] 2724 define union: a or b 2725 define union_rewrite: union 2726 define intersection: a and b 2727 define difference: a but not b 2728 define ttu: member from parent 2729 define union_and_ttu: union and ttu 2730 define union_or_ttu: union or ttu or union_rewrite 2731 define intersection_of_ttus: union_or_ttu and union_and_ttu 2732 define parent: [org] 2733 `) 2734 2735 ctx := typesystem.ContextWithTypesystem( 2736 context.Background(), 2737 typesystem.New(model), 2738 ) 2739 2740 tests := []struct { 2741 name string 2742 relation string 2743 object *openfgav1.Object 2744 userFilters []*openfgav1.UserTypeFilter 2745 contextualTuples []*openfgav1.TupleKey 2746 dbReads uint32 2747 }{ 2748 { 2749 name: "no_direct_access", 2750 relation: "a", 2751 object: &openfgav1.Object{Type: "document", Id: "1"}, 2752 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2753 dbReads: 1, 2754 }, 2755 { 2756 name: "direct_access", 2757 relation: "a", 2758 object: &openfgav1.Object{Type: "document", Id: "1"}, 2759 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2760 dbReads: 1, 2761 }, 2762 { 2763 name: "direct_access_thanks_to_contextual_tuple", 2764 relation: "a", 2765 contextualTuples: []*openfgav1.TupleKey{tuple.NewTupleKey("document:x", "a", "user:unknown")}, 2766 object: &openfgav1.Object{Type: "document", Id: "1"}, 2767 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2768 dbReads: 1, 2769 }, 2770 { 2771 name: "union", 2772 relation: "union", 2773 object: &openfgav1.Object{Type: "document", Id: "1"}, 2774 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2775 dbReads: 2, 2776 }, 2777 { 2778 name: "union_no_access", 2779 relation: "union", 2780 object: &openfgav1.Object{Type: "document", Id: "1"}, 2781 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2782 dbReads: 2, 2783 }, 2784 { 2785 name: "intersection", 2786 relation: "intersection", 2787 object: &openfgav1.Object{Type: "document", Id: "1"}, 2788 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2789 dbReads: 2, 2790 }, 2791 { 2792 name: "intersection_no_access", 2793 relation: "intersection", 2794 object: &openfgav1.Object{Type: "document", Id: "1"}, 2795 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2796 dbReads: 2, 2797 }, 2798 { 2799 name: "difference", 2800 relation: "difference", 2801 object: &openfgav1.Object{Type: "document", Id: "1"}, 2802 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2803 dbReads: 2, 2804 }, 2805 { 2806 name: "difference_no_access", 2807 relation: "difference", 2808 object: &openfgav1.Object{Type: "document", Id: "1"}, 2809 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2810 dbReads: 2, 2811 }, 2812 { 2813 name: "ttu", 2814 relation: "ttu", 2815 object: &openfgav1.Object{Type: "document", Id: "1"}, 2816 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2817 dbReads: 1, 2818 }, 2819 { 2820 name: "ttu_no_access", 2821 relation: "ttu", 2822 object: &openfgav1.Object{Type: "document", Id: "1"}, 2823 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2824 dbReads: 1, 2825 }, 2826 { 2827 name: "userset_no_access_1", 2828 relation: "userset", 2829 object: &openfgav1.Object{Type: "document", Id: "1"}, 2830 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2831 dbReads: 1, 2832 }, 2833 { 2834 name: "userset_no_access_2", 2835 relation: "userset", 2836 object: &openfgav1.Object{Type: "document", Id: "1"}, 2837 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2838 dbReads: 1, 2839 }, 2840 { 2841 name: "userset_access", 2842 relation: "userset", 2843 object: &openfgav1.Object{Type: "document", Id: "1"}, 2844 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2845 dbReads: 1, 2846 }, 2847 { 2848 name: "multiple_userset_no_access", 2849 relation: "multiple_userset", 2850 object: &openfgav1.Object{Type: "document", Id: "1"}, 2851 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2852 dbReads: 1, 2853 }, 2854 { 2855 name: "multiple_userset_access", 2856 relation: "multiple_userset", 2857 object: &openfgav1.Object{Type: "document", Id: "1"}, 2858 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2859 dbReads: 1, 2860 }, 2861 { 2862 name: "wildcard_no_access", 2863 relation: "wildcard", 2864 object: &openfgav1.Object{Type: "document", Id: "1"}, 2865 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2866 dbReads: 1, 2867 }, 2868 { 2869 name: "wildcard_access", 2870 relation: "wildcard", 2871 object: &openfgav1.Object{Type: "document", Id: "1"}, 2872 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2873 dbReads: 1, 2874 }, 2875 2876 { 2877 name: "union_and_ttu", 2878 relation: "union_and_ttu", 2879 object: &openfgav1.Object{Type: "document", Id: "1"}, 2880 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2881 dbReads: 3, 2882 }, 2883 { 2884 name: "union_and_ttu_no_access", 2885 relation: "union_and_ttu", 2886 object: &openfgav1.Object{Type: "document", Id: "1"}, 2887 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2888 dbReads: 3, 2889 }, 2890 { 2891 name: "union_or_ttu", 2892 relation: "union_or_ttu", 2893 object: &openfgav1.Object{Type: "document", Id: "1"}, 2894 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2895 dbReads: 5, 2896 }, 2897 { 2898 name: "union_or_ttu_no_access", 2899 relation: "union_or_ttu", 2900 object: &openfgav1.Object{Type: "document", Id: "1"}, 2901 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2902 dbReads: 5, 2903 }, 2904 { 2905 name: "intersection_of_ttus", 2906 relation: "intersection_of_ttus", 2907 object: &openfgav1.Object{Type: "document", Id: "1"}, 2908 userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2909 dbReads: 8, 2910 }, 2911 } 2912 2913 // run the test many times to exercise all the possible DBReads 2914 2915 for _, test := range tests { 2916 test := test 2917 t.Run(test.name, func(t *testing.T) { 2918 ctx := storage.ContextWithRelationshipTupleReader( 2919 ctx, 2920 storagewrappers.NewCombinedTupleReader( 2921 ds, 2922 test.contextualTuples, 2923 ), 2924 ) 2925 2926 l := NewListUsersQuery(ds) 2927 resp, err := l.ListUsers(ctx, &openfgav1.ListUsersRequest{ 2928 Relation: test.relation, 2929 Object: test.object, 2930 UserFilters: test.userFilters, 2931 ContextualTuples: test.contextualTuples, 2932 }) 2933 require.NoError(t, err) 2934 require.Equal(t, test.dbReads, resp.GetMetadata().DatastoreQueryCount) 2935 }) 2936 } 2937 } 2938 2939 func TestListUsersConfig_MaxResults(t *testing.T) { 2940 t.Cleanup(func() { 2941 goleak.VerifyNone(t) 2942 }) 2943 2944 ds := memory.New() 2945 t.Cleanup(ds.Close) 2946 2947 testCases := map[string]struct { 2948 inputTuples []*openfgav1.TupleKey 2949 inputModel string 2950 inputRequest *openfgav1.ListUsersRequest 2951 inputConfigMaxResults uint32 2952 allResults []*openfgav1.User // all the results. the server may return less 2953 expectMinResults uint32 2954 }{ 2955 `max_results_infinite`: { 2956 inputModel: ` 2957 model 2958 schema 1.1 2959 type user 2960 type repo 2961 relations 2962 define admin: [user]`, 2963 inputTuples: []*openfgav1.TupleKey{ 2964 tuple.NewTupleKey("repo:target", "admin", "user:1"), 2965 tuple.NewTupleKey("repo:target", "admin", "user:2"), 2966 }, 2967 inputRequest: &openfgav1.ListUsersRequest{ 2968 ContextualTuples: []*openfgav1.TupleKey{ 2969 tuple.NewTupleKey("repo:target", "admin", "user:3"), 2970 }, 2971 Object: &openfgav1.Object{Type: "repo", Id: "target"}, 2972 Relation: "admin", 2973 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 2974 }, 2975 inputConfigMaxResults: 0, 2976 allResults: []*openfgav1.User{ 2977 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}}, 2978 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "2"}}}, 2979 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "3"}}}, 2980 }, 2981 expectMinResults: 3, 2982 }, 2983 `max_results_less_than_actual_results`: { 2984 inputModel: ` 2985 model 2986 schema 1.1 2987 type user 2988 type repo 2989 relations 2990 define admin: [user]`, 2991 inputTuples: []*openfgav1.TupleKey{ 2992 tuple.NewTupleKey("repo:target", "admin", "user:1"), 2993 tuple.NewTupleKey("repo:target", "admin", "user:2"), 2994 }, 2995 inputRequest: &openfgav1.ListUsersRequest{ 2996 ContextualTuples: []*openfgav1.TupleKey{ 2997 tuple.NewTupleKey("repo:target", "admin", "user:3"), 2998 }, 2999 Object: &openfgav1.Object{Type: "repo", Id: "target"}, 3000 Relation: "admin", 3001 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 3002 }, 3003 inputConfigMaxResults: 2, 3004 allResults: []*openfgav1.User{ 3005 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}}, 3006 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "2"}}}, 3007 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "3"}}}, 3008 }, 3009 expectMinResults: 2, 3010 }, 3011 `max_results_more_than_actual_results`: { 3012 inputModel: ` 3013 model 3014 schema 1.1 3015 type user 3016 type repo 3017 relations 3018 define admin: [user]`, 3019 inputTuples: []*openfgav1.TupleKey{ 3020 tuple.NewTupleKey("repo:target", "admin", "user:1"), 3021 }, 3022 inputRequest: &openfgav1.ListUsersRequest{ 3023 Object: &openfgav1.Object{Type: "repo", Id: "target"}, 3024 Relation: "admin", 3025 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 3026 }, 3027 inputConfigMaxResults: 2, 3028 allResults: []*openfgav1.User{ 3029 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}}, 3030 }, 3031 expectMinResults: 1, 3032 }, 3033 } 3034 for name, test := range testCases { 3035 t.Run(name, func(t *testing.T) { 3036 ctx := context.Background() 3037 3038 // arrange: write model 3039 model := testutils.MustTransformDSLToProtoWithID(test.inputModel) 3040 3041 storeID := ulid.Make().String() 3042 3043 err := ds.WriteAuthorizationModel(ctx, storeID, model) 3044 require.NoError(t, err) 3045 3046 // arrange: write tuples 3047 err = ds.Write(context.Background(), storeID, nil, test.inputTuples) 3048 require.NoError(t, err) 3049 3050 typesys, err := typesystem.NewAndValidate(context.Background(), model) 3051 require.NoError(t, err) 3052 ctx = typesystem.ContextWithTypesystem(context.Background(), typesys) 3053 3054 // assertions 3055 test.inputRequest.StoreId = storeID 3056 res, err := NewListUsersQuery(ds, 3057 WithListUsersMaxResults(test.inputConfigMaxResults), 3058 WithListUsersDeadline(10*time.Second), 3059 ).ListUsers(ctx, test.inputRequest) 3060 3061 require.NotNil(t, res) 3062 require.NoError(t, err) 3063 if test.inputConfigMaxResults != 0 { // don't get all results 3064 require.LessOrEqual(t, len(res.GetUsers()), int(test.inputConfigMaxResults)) 3065 } 3066 require.GreaterOrEqual(t, len(res.GetUsers()), int(test.expectMinResults)) 3067 require.Subset(t, test.allResults, res.GetUsers()) 3068 }) 3069 } 3070 } 3071 3072 func TestListUsersConfig_Deadline(t *testing.T) { 3073 t.Cleanup(func() { 3074 goleak.VerifyNone(t) 3075 }) 3076 3077 ds := memory.New() 3078 t.Cleanup(ds.Close) 3079 3080 testCases := map[string]struct { 3081 inputTuples []*openfgav1.TupleKey 3082 inputModel string 3083 inputRequest *openfgav1.ListUsersRequest 3084 inputConfigDeadline time.Duration // request can only take this time 3085 inputReadDelay time.Duration // to be able to hit the deadline at a predictable time 3086 allResults []*openfgav1.User // all the results. the server may return less 3087 expectMinResults uint32 3088 expectError string 3089 }{ 3090 `deadline_very_small_returns_nothing`: { 3091 inputModel: ` 3092 model 3093 schema 1.1 3094 type user 3095 type repo 3096 relations 3097 define admin: [user]`, 3098 inputTuples: []*openfgav1.TupleKey{ 3099 tuple.NewTupleKey("repo:target", "admin", "user:1"), 3100 }, 3101 inputRequest: &openfgav1.ListUsersRequest{ 3102 Object: &openfgav1.Object{Type: "repo", Id: "target"}, 3103 Relation: "admin", 3104 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 3105 }, 3106 inputConfigDeadline: 1 * time.Millisecond, 3107 inputReadDelay: 50 * time.Millisecond, 3108 allResults: []*openfgav1.User{ 3109 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}}, 3110 }, 3111 expectError: "context deadline exceeded", 3112 }, 3113 `deadline_very_high_returns_everything`: { 3114 inputModel: ` 3115 model 3116 schema 1.1 3117 type user 3118 type repo 3119 relations 3120 define admin: [user]`, 3121 inputTuples: []*openfgav1.TupleKey{ 3122 tuple.NewTupleKey("repo:target", "admin", "user:1"), 3123 }, 3124 inputRequest: &openfgav1.ListUsersRequest{ 3125 Object: &openfgav1.Object{Type: "repo", Id: "target"}, 3126 Relation: "admin", 3127 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 3128 }, 3129 inputConfigDeadline: 1 * time.Second, 3130 inputReadDelay: 0 * time.Second, 3131 allResults: []*openfgav1.User{ 3132 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}}, 3133 }, 3134 expectMinResults: 1, 3135 }, 3136 } 3137 for name, test := range testCases { 3138 t.Run(name, func(t *testing.T) { 3139 ctx := context.Background() 3140 3141 // arrange: write model 3142 model := testutils.MustTransformDSLToProtoWithID(test.inputModel) 3143 3144 storeID := ulid.Make().String() 3145 3146 err := ds.WriteAuthorizationModel(ctx, storeID, model) 3147 require.NoError(t, err) 3148 3149 // arrange: write tuples 3150 err = ds.Write(context.Background(), storeID, nil, test.inputTuples) 3151 require.NoError(t, err) 3152 3153 typesys, err := typesystem.NewAndValidate(context.Background(), model) 3154 require.NoError(t, err) 3155 ctx = typesystem.ContextWithTypesystem(context.Background(), typesys) 3156 3157 // assertions 3158 t.Run("regular_endpoint", func(t *testing.T) { 3159 test.inputRequest.StoreId = storeID 3160 res, err := NewListUsersQuery( 3161 mocks.NewMockSlowDataStorage(ds, test.inputReadDelay), 3162 WithListUsersDeadline(test.inputConfigDeadline), 3163 ).ListUsers(ctx, test.inputRequest) 3164 3165 if test.expectError != "" { 3166 require.ErrorContains(t, err, test.expectError) 3167 } else { 3168 require.NoError(t, err) 3169 require.GreaterOrEqual(t, len(res.GetUsers()), int(test.expectMinResults)) 3170 require.Subset(t, test.allResults, res.GetUsers()) 3171 } 3172 }) 3173 }) 3174 } 3175 } 3176 3177 func TestListUsersConfig_MaxConcurrency(t *testing.T) { 3178 t.Cleanup(func() { 3179 goleak.VerifyNone(t) 3180 }) 3181 3182 ds := memory.New() 3183 t.Cleanup(ds.Close) 3184 3185 testCases := map[string]struct { 3186 inputTuples []*openfgav1.TupleKey 3187 inputModel string 3188 inputRequest *openfgav1.ListUsersRequest 3189 inputConfigMaxConcurrentReads uint32 3190 inputReadDelay time.Duration // to be able to hit the deadline at a predictable time 3191 allResults []*openfgav1.User // all the results. the server may return less 3192 expectMinResults uint32 3193 expectMinExecutionTime time.Duration 3194 }{ 3195 `max_concurrent_reads_does_not_delay_response_if_only_contextual_tuples_are_in_place`: { 3196 inputModel: ` 3197 model 3198 schema 1.1 3199 type user 3200 type repo 3201 relations 3202 define admin: [user]`, 3203 inputTuples: []*openfgav1.TupleKey{}, 3204 inputRequest: &openfgav1.ListUsersRequest{ 3205 Object: &openfgav1.Object{Type: "repo", Id: "target"}, 3206 Relation: "admin", 3207 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 3208 ContextualTuples: []*openfgav1.TupleKey{ 3209 tuple.NewTupleKey("repo:target", "admin", "user:1"), 3210 }, 3211 }, 3212 inputConfigMaxConcurrentReads: 1, 3213 allResults: []*openfgav1.User{ 3214 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}}, 3215 }, 3216 expectMinResults: 1, 3217 expectMinExecutionTime: 0 * time.Millisecond, 3218 }, 3219 `max_concurrent_reads_delays_response`: { 3220 inputModel: ` 3221 model 3222 schema 1.1 3223 type user 3224 type folder 3225 relations 3226 define admin: [user] 3227 type repo 3228 relations 3229 define parent: [folder] 3230 define admin: [user] or admin from parent`, // two parallel reads will have to be made 3231 inputTuples: []*openfgav1.TupleKey{ 3232 tuple.NewTupleKey("repo:target", "admin", "user:1"), 3233 }, 3234 inputRequest: &openfgav1.ListUsersRequest{ 3235 Object: &openfgav1.Object{Type: "repo", Id: "target"}, 3236 Relation: "admin", 3237 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 3238 }, 3239 inputReadDelay: 1 * time.Second, 3240 inputConfigMaxConcurrentReads: 1, 3241 allResults: []*openfgav1.User{ 3242 {User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}}, 3243 }, 3244 expectMinExecutionTime: 2 * time.Second, 3245 }, 3246 } 3247 for name, test := range testCases { 3248 t.Run(name, func(t *testing.T) { 3249 ctx := context.Background() 3250 3251 // arrange: write model 3252 model := testutils.MustTransformDSLToProtoWithID(test.inputModel) 3253 3254 storeID := ulid.Make().String() 3255 3256 err := ds.WriteAuthorizationModel(ctx, storeID, model) 3257 require.NoError(t, err) 3258 3259 // arrange: write tuples 3260 err = ds.Write(context.Background(), storeID, nil, test.inputTuples) 3261 require.NoError(t, err) 3262 3263 typesys, err := typesystem.NewAndValidate(context.Background(), model) 3264 require.NoError(t, err) 3265 ctx = typesystem.ContextWithTypesystem(context.Background(), typesys) 3266 3267 // assertions 3268 t.Run("regular_endpoint", func(t *testing.T) { 3269 test.inputRequest.StoreId = storeID 3270 start := time.Now() 3271 res, err := NewListUsersQuery( 3272 mocks.NewMockSlowDataStorage(ds, test.inputReadDelay), 3273 WithListUsersMaxConcurrentReads(test.inputConfigMaxConcurrentReads), 3274 ).ListUsers(ctx, test.inputRequest) 3275 3276 require.NoError(t, err) 3277 require.GreaterOrEqual(t, len(res.GetUsers()), int(test.expectMinResults)) 3278 require.Subset(t, test.allResults, res.GetUsers()) 3279 require.GreaterOrEqual(t, time.Since(start), test.expectMinExecutionTime) 3280 }) 3281 }) 3282 } 3283 }