github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/typesystem/typesystem_test.go (about) 1 package typesystem 2 3 import ( 4 "context" 5 "sort" 6 "testing" 7 8 "github.com/stretchr/testify/require" 9 10 "github.com/authzed/spicedb/pkg/genutil/mapz" 11 core "github.com/authzed/spicedb/pkg/proto/core/v1" 12 13 "github.com/authzed/spicedb/internal/datastore/memdb" 14 datastoremw "github.com/authzed/spicedb/internal/middleware/datastore" 15 "github.com/authzed/spicedb/pkg/caveats" 16 "github.com/authzed/spicedb/pkg/datastore" 17 ns "github.com/authzed/spicedb/pkg/namespace" 18 "github.com/authzed/spicedb/pkg/schemadsl/compiler" 19 "github.com/authzed/spicedb/pkg/schemadsl/input" 20 "github.com/authzed/spicedb/pkg/tuple" 21 ) 22 23 func TestTypeSystem(t *testing.T) { 24 emptyEnv := caveats.NewEnvironment() 25 26 testCases := []struct { 27 name string 28 toCheck *core.NamespaceDefinition 29 otherNamespaces []*core.NamespaceDefinition 30 caveats []*core.CaveatDefinition 31 expectedError string 32 }{ 33 { 34 "invalid relation in computed_userset", 35 ns.Namespace( 36 "document", 37 ns.MustRelation("owner", nil), 38 ns.MustRelation("editor", ns.Union( 39 ns.ComputedUserset("owner"), 40 )), 41 ns.MustRelation("parent", nil), 42 ns.MustRelation("lock", nil), 43 ns.MustRelation("viewer", ns.Union( 44 ns.ComputedUserset("editors"), 45 ns.TupleToUserset("parent", "viewer"), 46 )), 47 ), 48 []*core.NamespaceDefinition{}, 49 nil, 50 "relation/permission `editors` not found under definition `document`", 51 }, 52 { 53 "invalid relation in tuple_to_userset", 54 ns.Namespace( 55 "document", 56 ns.MustRelation("owner", nil), 57 ns.MustRelation("editor", ns.Union( 58 ns.ComputedUserset("owner"), 59 )), 60 ns.MustRelation("parent", nil), 61 ns.MustRelation("lock", nil), 62 ns.MustRelation("viewer", ns.Union( 63 ns.ComputedUserset("editor"), 64 ns.TupleToUserset("parents", "viewer"), 65 )), 66 ), 67 []*core.NamespaceDefinition{}, 68 nil, 69 "relation/permission `parents` not found under definition `document`", 70 }, 71 { 72 "use of permission in tuple_to_userset", 73 ns.Namespace( 74 "document", 75 ns.MustRelation("owner", nil), 76 ns.MustRelation("editor", ns.Union( 77 ns.ComputedUserset("owner"), 78 )), 79 ns.MustRelation("parent", nil), 80 ns.MustRelation("lock", nil), 81 ns.MustRelation("viewer", ns.Union( 82 ns.TupleToUserset("editor", "viewer"), 83 )), 84 ), 85 []*core.NamespaceDefinition{}, 86 nil, 87 "under permission `viewer` under definition `document`: permissions cannot be used on the left hand side of an arrow (found `editor`)", 88 }, 89 { 90 "rewrite without this and types", 91 ns.Namespace( 92 "document", 93 ns.MustRelation("owner", nil), 94 ns.MustRelation("editor", ns.Union( 95 ns.ComputedUserset("owner"), 96 ), ns.AllowedRelation("document", "owner")), 97 ), 98 []*core.NamespaceDefinition{}, 99 nil, 100 "direct relations are not allowed under relation `editor`", 101 }, 102 { 103 "relation in relation types has invalid namespace", 104 ns.Namespace( 105 "document", 106 ns.MustRelation("owner", nil, ns.AllowedRelation("someinvalidns", "...")), 107 ns.MustRelation("editor", ns.Union( 108 ns.ComputedUserset("owner"), 109 )), 110 ns.MustRelation("parent", nil), 111 ns.MustRelation("lock", nil), 112 ns.MustRelation("viewer", ns.Union( 113 ns.ComputedUserset("editor"), 114 ns.TupleToUserset("parent", "viewer"), 115 )), 116 ), 117 []*core.NamespaceDefinition{}, 118 nil, 119 "could not lookup definition `someinvalidns` for relation `owner`: object definition `someinvalidns` not found", 120 }, 121 { 122 "relation in relation types has invalid relation", 123 ns.Namespace( 124 "document", 125 ns.MustRelation("owner", nil, ns.AllowedRelation("anotherns", "foobar")), 126 ns.MustRelation("editor", ns.Union( 127 ns.ComputedUserset("owner"), 128 )), 129 ns.MustRelation("parent", nil), 130 ns.MustRelation("lock", nil), 131 ns.MustRelation("viewer", ns.Union( 132 ns.ComputedUserset("editor"), 133 ns.TupleToUserset("parent", "viewer"), 134 )), 135 ), 136 []*core.NamespaceDefinition{ 137 ns.Namespace( 138 "anotherns", 139 ), 140 }, 141 nil, 142 "relation/permission `foobar` not found under definition `anotherns`", 143 }, 144 { 145 "full type check", 146 ns.Namespace( 147 "document", 148 ns.MustRelation("owner", nil, ns.AllowedRelation("user", "...")), 149 ns.MustRelation("can_comment", 150 nil, 151 ns.AllowedRelation("user", "..."), 152 ns.AllowedRelation("folder", "can_comment"), 153 ), 154 ns.MustRelation("editor", 155 ns.Union( 156 ns.ComputedUserset("owner"), 157 ), 158 ), 159 ns.MustRelation("parent", nil, ns.AllowedRelation("folder", "...")), 160 ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedPublicNamespace("user")), 161 ns.MustRelation("view", ns.Union( 162 ns.ComputedUserset("viewer"), 163 ns.ComputedUserset("editor"), 164 ns.TupleToUserset("parent", "view"), 165 )), 166 ), 167 []*core.NamespaceDefinition{ 168 ns.Namespace("user"), 169 ns.Namespace( 170 "folder", 171 ns.MustRelation("can_comment", nil, ns.AllowedRelation("user", "...")), 172 ns.MustRelation("parent", nil, ns.AllowedRelation("folder", "...")), 173 ), 174 }, 175 nil, 176 "", 177 }, 178 { 179 "transitive wildcard type check", 180 ns.Namespace( 181 "document", 182 ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedRelation("group", "member")), 183 ), 184 []*core.NamespaceDefinition{ 185 ns.Namespace("user"), 186 ns.Namespace( 187 "group", 188 ns.MustRelation("member", nil, ns.AllowedRelation("user", "..."), ns.AllowedPublicNamespace("user")), 189 ), 190 }, 191 nil, 192 "for relation `viewer`: relation/permission `group#member` includes wildcard type `user` via relation `group#member`: wildcard relations cannot be transitively included", 193 }, 194 { 195 "ttu wildcard type check", 196 ns.Namespace( 197 "folder", 198 ns.MustRelation("parent", nil, ns.AllowedRelation("folder", "..."), ns.AllowedPublicNamespace("folder")), 199 ns.MustRelation("viewer", ns.Union( 200 ns.TupleToUserset("parent", "viewer"), 201 )), 202 ), 203 []*core.NamespaceDefinition{ 204 ns.Namespace("user"), 205 }, 206 nil, 207 "for arrow under permission `viewer`: relation `folder#parent` includes wildcard type `folder` via relation `folder#parent`: wildcard relations cannot be used on the left side of arrows", 208 }, 209 { 210 "recursive transitive wildcard type check", 211 ns.Namespace( 212 "document", 213 ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedRelation("group", "member")), 214 ), 215 []*core.NamespaceDefinition{ 216 ns.Namespace("user"), 217 ns.Namespace( 218 "group", 219 ns.MustRelation("member", nil, ns.AllowedRelation("group", "manager"), ns.AllowedRelation("user", "...")), 220 ns.MustRelation("manager", nil, ns.AllowedRelation("group", "member"), ns.AllowedPublicNamespace("user")), 221 ), 222 }, 223 nil, 224 "for relation `viewer`: relation/permission `group#member` includes wildcard type `user` via relation `group#manager`: wildcard relations cannot be transitively included", 225 }, 226 { 227 "redefinition of allowed relation", 228 ns.Namespace( 229 "document", 230 ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedRelation("user", "...")), 231 ), 232 []*core.NamespaceDefinition{ 233 ns.Namespace("user"), 234 }, 235 nil, 236 "found duplicate allowed subject type `user` on relation `viewer` under definition `document`", 237 }, 238 { 239 "redefinition of allowed public relation", 240 ns.Namespace( 241 "document", 242 ns.MustRelation("viewer", nil, ns.AllowedPublicNamespace("user"), ns.AllowedPublicNamespace("user")), 243 ), 244 []*core.NamespaceDefinition{ 245 ns.Namespace("user"), 246 }, 247 nil, 248 "found duplicate allowed subject type `user:*` on relation `viewer` under definition `document`", 249 }, 250 { 251 "no redefinition of allowed relation", 252 ns.Namespace( 253 "document", 254 ns.MustRelation("viewer", nil, ns.AllowedPublicNamespace("user"), ns.AllowedRelation("user", "..."), ns.AllowedRelation("user", "viewer")), 255 ), 256 []*core.NamespaceDefinition{ 257 ns.Namespace("user", ns.MustRelation("viewer", nil)), 258 }, 259 nil, 260 "", 261 }, 262 { 263 "unknown caveat", 264 ns.Namespace( 265 "document", 266 ns.MustRelation("viewer", nil, ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("unknown"))), 267 ), 268 []*core.NamespaceDefinition{ 269 ns.Namespace("user"), 270 }, 271 nil, 272 "could not lookup caveat `unknown` for relation `viewer`: caveat with name `unknown` not found", 273 }, 274 { 275 "valid caveat", 276 ns.Namespace( 277 "document", 278 ns.MustRelation("viewer", nil, ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat"))), 279 ), 280 []*core.NamespaceDefinition{ 281 ns.Namespace("user"), 282 }, 283 []*core.CaveatDefinition{ 284 ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"), 285 }, 286 "", 287 }, 288 { 289 "valid optional caveat", 290 ns.Namespace( 291 "document", 292 ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat"))), 293 ), 294 []*core.NamespaceDefinition{ 295 ns.Namespace("user"), 296 }, 297 []*core.CaveatDefinition{ 298 ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"), 299 }, 300 "", 301 }, 302 { 303 "duplicate caveat", 304 ns.Namespace( 305 "document", 306 ns.MustRelation("viewer", nil, ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat")), ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat"))), 307 ), 308 []*core.NamespaceDefinition{ 309 ns.Namespace("user"), 310 }, 311 []*core.CaveatDefinition{ 312 ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"), 313 }, 314 "found duplicate allowed subject type `user with definedcaveat` on relation `viewer` under definition `document`", 315 }, 316 { 317 "valid wildcard caveat", 318 ns.Namespace( 319 "document", 320 ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedPublicNamespaceWithCaveat("user", ns.AllowedCaveat("definedcaveat"))), 321 ), 322 []*core.NamespaceDefinition{ 323 ns.Namespace("user"), 324 }, 325 []*core.CaveatDefinition{ 326 ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"), 327 }, 328 "", 329 }, 330 { 331 "valid all the caveats", 332 ns.Namespace( 333 "document", 334 ns.MustRelation("viewer", nil, 335 ns.AllowedRelation("user", "..."), 336 ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat")), 337 ns.AllowedPublicNamespaceWithCaveat("user", ns.AllowedCaveat("definedcaveat")), 338 ns.AllowedRelationWithCaveat("team", "member", ns.AllowedCaveat("definedcaveat")), 339 ), 340 ), 341 []*core.NamespaceDefinition{ 342 ns.Namespace("user"), 343 ns.Namespace("team", 344 ns.MustRelation("member", nil), 345 ), 346 }, 347 []*core.CaveatDefinition{ 348 ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"), 349 }, 350 "", 351 }, 352 } 353 354 for _, tc := range testCases { 355 tc := tc 356 t.Run(tc.name, func(t *testing.T) { 357 require := require.New(t) 358 359 ds, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC) 360 require.NoError(err) 361 362 ctx := context.Background() 363 364 lastRevision, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 365 for _, otherNS := range tc.otherNamespaces { 366 if err := rwt.WriteNamespaces(ctx, otherNS); err != nil { 367 return err 368 } 369 } 370 cw := rwt.(datastore.CaveatStorer) 371 return cw.WriteCaveats(ctx, tc.caveats) 372 }) 373 require.NoError(err) 374 375 ts, err := NewNamespaceTypeSystem(tc.toCheck, ResolverForDatastoreReader(ds.SnapshotReader(lastRevision))) 376 require.NoError(err) 377 378 _, terr := ts.Validate(ctx) 379 if tc.expectedError == "" { 380 require.NoError(terr) 381 } else { 382 require.Error(terr) 383 require.Equal(tc.expectedError, terr.Error()) 384 } 385 }) 386 } 387 } 388 389 type tsTester func(t *testing.T, ts *ValidatedNamespaceTypeSystem) 390 391 func noError[T any](result T, err error) T { 392 if err != nil { 393 panic(err) 394 } 395 396 return result 397 } 398 399 func requireSameAllowedRelations(t *testing.T, found []*core.AllowedRelation, expected ...*core.AllowedRelation) { 400 foundSet := mapz.NewSet[string]() 401 for _, f := range found { 402 foundSet.Add(SourceForAllowedRelation(f)) 403 } 404 405 expectSet := mapz.NewSet[string]() 406 for _, e := range expected { 407 expectSet.Add(SourceForAllowedRelation(e)) 408 } 409 410 foundSlice := foundSet.AsSlice() 411 expectedSlice := expectSet.AsSlice() 412 413 sort.Strings(foundSlice) 414 sort.Strings(expectedSlice) 415 416 require.Equal(t, expectedSlice, foundSlice) 417 } 418 419 func requireSameSubjectRelations(t *testing.T, found []*core.RelationReference, expected ...*core.RelationReference) { 420 foundSet := mapz.NewSet[string]() 421 for _, f := range found { 422 foundSet.Add(tuple.StringRR(f)) 423 } 424 425 expectSet := mapz.NewSet[string]() 426 for _, e := range expected { 427 expectSet.Add(tuple.StringRR(e)) 428 } 429 430 foundSlice := foundSet.AsSlice() 431 expectedSlice := expectSet.AsSlice() 432 433 sort.Strings(foundSlice) 434 sort.Strings(expectedSlice) 435 436 require.Equal(t, expectedSlice, foundSlice) 437 } 438 439 func TestTypeSystemAccessors(t *testing.T) { 440 tcs := []struct { 441 name string 442 schema string 443 namespaces map[string]tsTester 444 }{ 445 { 446 "basic schema", 447 `definition user {} 448 449 definition resource { 450 relation editor: user 451 relation viewer: user 452 453 permission edit = editor 454 permission view = viewer + edit 455 }`, 456 map[string]tsTester{ 457 "user": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) { 458 require.False(t, vts.IsPermission("somenonpermission")) 459 }, 460 "resource": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) { 461 t.Run("GetRelationOrError", func(t *testing.T) { 462 require.NotNil(t, noError(vts.GetRelationOrError("editor"))) 463 require.NotNil(t, noError(vts.GetRelationOrError("viewer"))) 464 465 _, err := vts.GetRelationOrError("someunknownrel") 466 require.Error(t, err) 467 require.ErrorAs(t, err, &ErrRelationNotFound{}) 468 require.ErrorContains(t, err, "relation/permission `someunknownrel` not found") 469 }) 470 471 t.Run("HasIndirectSubjects", func(t *testing.T) { 472 require.False(t, noError(vts.HasIndirectSubjects("editor"))) 473 require.False(t, noError(vts.HasIndirectSubjects("viewer"))) 474 }) 475 476 t.Run("IsPermission", func(t *testing.T) { 477 require.False(t, vts.IsPermission("somenonpermission")) 478 479 require.False(t, vts.IsPermission("viewer")) 480 require.False(t, vts.IsPermission("editor")) 481 482 require.True(t, vts.IsPermission("view")) 483 require.True(t, vts.IsPermission("edit")) 484 }) 485 486 t.Run("RelationDoesNotAllowCaveatsForSubject", func(t *testing.T) { 487 ok, err := vts.RelationDoesNotAllowCaveatsForSubject("viewer", "user") 488 require.NoError(t, err) 489 require.True(t, ok) 490 491 ok, err = vts.RelationDoesNotAllowCaveatsForSubject("editor", "user") 492 require.NoError(t, err) 493 require.True(t, ok) 494 }) 495 496 t.Run("IsAllowedPublicNamespace", func(t *testing.T) { 497 require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("editor", "user"))) 498 require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("viewer", "user"))) 499 500 _, err := vts.IsAllowedPublicNamespace("unknown", "user") 501 require.Error(t, err) 502 }) 503 504 t.Run("IsAllowedDirectNamespace", func(t *testing.T) { 505 require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("editor", "user"))) 506 require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("viewer", "user"))) 507 508 _, err := vts.IsAllowedPublicNamespace("unknown", "user") 509 require.Error(t, err) 510 }) 511 512 t.Run("GetAllowedDirectNamespaceSubjectRelations", func(t *testing.T) { 513 require.Equal(t, []string{"..."}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("editor", "user")).AsSlice()) 514 _, err := vts.GetAllowedDirectNamespaceSubjectRelations("unknown", "user") 515 require.Error(t, err) 516 }) 517 518 t.Run("IsAllowedDirectRelation", func(t *testing.T) { 519 require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("editor", "user", "..."))) 520 require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("viewer", "user", "..."))) 521 522 require.Equal(t, DirectRelationNotValid, noError(vts.IsAllowedDirectRelation("editor", "user", "other"))) 523 require.Equal(t, DirectRelationNotValid, noError(vts.IsAllowedDirectRelation("viewer", "user", "other"))) 524 525 _, err := vts.IsAllowedDirectRelation("unknown", "user", "...") 526 require.Error(t, err) 527 }) 528 529 t.Run("HasAllowedRelation", func(t *testing.T) { 530 userDirect := ns.AllowedRelation("user", "...") 531 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("editor", userDirect))) 532 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", userDirect))) 533 534 userWithCaveat := ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("somecaveat")) 535 require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("editor", userWithCaveat))) 536 require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("viewer", userWithCaveat))) 537 538 _, err := vts.HasAllowedRelation("unknown", userDirect) 539 require.Error(t, err) 540 }) 541 542 t.Run("AllowedDirectRelationsAndWildcards", func(t *testing.T) { 543 userDirect := ns.AllowedRelation("user", "...") 544 allowed := noError(vts.AllowedDirectRelationsAndWildcards("editor")) 545 requireSameAllowedRelations(t, allowed, userDirect) 546 547 _, err := vts.AllowedDirectRelationsAndWildcards("unknown") 548 require.Error(t, err) 549 }) 550 551 t.Run("AllowedSubjectRelations", func(t *testing.T) { 552 userDirect := ns.RelationReference("user", "...") 553 allowed := noError(vts.AllowedSubjectRelations("editor")) 554 requireSameSubjectRelations(t, allowed, userDirect) 555 556 _, err := vts.AllowedSubjectRelations("unknown") 557 require.Error(t, err) 558 }) 559 }, 560 }, 561 }, 562 { 563 "schema with wildcards", 564 `definition user {} 565 566 definition resource { 567 relation editor: user 568 relation viewer: user | user:* 569 permission view = viewer + editor 570 }`, 571 map[string]tsTester{ 572 "resource": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) { 573 t.Run("IsPermission", func(t *testing.T) { 574 require.False(t, vts.IsPermission("viewer")) 575 require.True(t, vts.IsPermission("view")) 576 }) 577 578 t.Run("RelationDoesNotAllowCaveatsForSubject", func(t *testing.T) { 579 ok, err := vts.RelationDoesNotAllowCaveatsForSubject("viewer", "user") 580 require.NoError(t, err) 581 require.True(t, ok) 582 583 ok, err = vts.RelationDoesNotAllowCaveatsForSubject("editor", "user") 584 require.NoError(t, err) 585 require.True(t, ok) 586 }) 587 588 t.Run("HasIndirectSubjects", func(t *testing.T) { 589 require.False(t, noError(vts.HasIndirectSubjects("editor"))) 590 require.False(t, noError(vts.HasIndirectSubjects("viewer"))) 591 }) 592 593 t.Run("IsAllowedPublicNamespace", func(t *testing.T) { 594 require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("editor", "user"))) 595 require.Equal(t, PublicSubjectAllowed, noError(vts.IsAllowedPublicNamespace("viewer", "user"))) 596 }) 597 598 t.Run("IsAllowedDirectNamespace", func(t *testing.T) { 599 require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("editor", "user"))) 600 require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("viewer", "user"))) 601 }) 602 603 t.Run("GetAllowedDirectNamespaceSubjectRelations", func(t *testing.T) { 604 require.Equal(t, []string{"..."}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("viewer", "user")).AsSlice()) 605 _, err := vts.GetAllowedDirectNamespaceSubjectRelations("unknown", "user") 606 require.Error(t, err) 607 }) 608 609 t.Run("IsAllowedDirectRelation", func(t *testing.T) { 610 require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("editor", "user", "..."))) 611 require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("viewer", "user", "..."))) 612 }) 613 614 t.Run("HasAllowedRelation", func(t *testing.T) { 615 userDirect := ns.AllowedRelation("user", "...") 616 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("editor", userDirect))) 617 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", userDirect))) 618 619 userWildcard := ns.AllowedPublicNamespace("user") 620 require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("editor", userWildcard))) 621 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", userWildcard))) 622 }) 623 624 t.Run("AllowedDirectRelationsAndWildcards", func(t *testing.T) { 625 userDirect := ns.AllowedRelation("user", "...") 626 userWildcard := ns.AllowedPublicNamespace("user") 627 628 allowed := noError(vts.AllowedDirectRelationsAndWildcards("editor")) 629 requireSameAllowedRelations(t, allowed, userDirect) 630 631 allowed = noError(vts.AllowedDirectRelationsAndWildcards("viewer")) 632 requireSameAllowedRelations(t, allowed, userDirect, userWildcard) 633 }) 634 635 t.Run("AllowedSubjectRelations", func(t *testing.T) { 636 userDirect := ns.RelationReference("user", "...") 637 allowed := noError(vts.AllowedSubjectRelations("viewer")) 638 requireSameSubjectRelations(t, allowed, userDirect) 639 }) 640 }, 641 }, 642 }, 643 { 644 "schema with subject relations", 645 `definition user {} 646 647 definition thirdtype {} 648 649 definition group { 650 relation member: user | group#member 651 relation other: user 652 relation three: user | group#member | group#other 653 }`, 654 map[string]tsTester{ 655 "group": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) { 656 t.Run("IsPermission", func(t *testing.T) { 657 require.False(t, vts.IsPermission("member")) 658 }) 659 660 t.Run("RelationDoesNotAllowCaveatsForSubject", func(t *testing.T) { 661 ok, err := vts.RelationDoesNotAllowCaveatsForSubject("member", "user") 662 require.NoError(t, err) 663 require.True(t, ok) 664 665 ok, err = vts.RelationDoesNotAllowCaveatsForSubject("member", "group") 666 require.NoError(t, err) 667 require.True(t, ok) 668 }) 669 670 t.Run("HasIndirectSubjects", func(t *testing.T) { 671 require.True(t, noError(vts.HasIndirectSubjects("member"))) 672 }) 673 674 t.Run("IsAllowedPublicNamespace", func(t *testing.T) { 675 require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("member", "user"))) 676 }) 677 678 t.Run("IsAllowedDirectNamespace", func(t *testing.T) { 679 require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("member", "user"))) 680 require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("member", "group"))) 681 require.Equal(t, AllowedNamespaceNotValid, noError(vts.IsAllowedDirectNamespace("member", "thirdtype"))) 682 }) 683 684 t.Run("GetAllowedDirectNamespaceSubjectRelations", func(t *testing.T) { 685 require.Equal(t, []string{"..."}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("member", "user")).AsSlice()) 686 require.Equal(t, []string{"member"}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("member", "group")).AsSlice()) 687 require.ElementsMatch(t, []string{"member", "other"}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("three", "group")).AsSlice()) 688 _, err := vts.GetAllowedDirectNamespaceSubjectRelations("unknown", "user") 689 require.Error(t, err) 690 }) 691 692 t.Run("IsAllowedDirectRelation", func(t *testing.T) { 693 require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("member", "user", "..."))) 694 require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("member", "group", "member"))) 695 require.Equal(t, DirectRelationNotValid, noError(vts.IsAllowedDirectRelation("member", "group", "..."))) 696 }) 697 698 t.Run("HasAllowedRelation", func(t *testing.T) { 699 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("member", ns.AllowedRelation("user", "...")))) 700 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("member", ns.AllowedRelation("group", "member")))) 701 require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("member", ns.AllowedRelation("group", "...")))) 702 }) 703 704 t.Run("AllowedDirectRelationsAndWildcards", func(t *testing.T) { 705 userDirect := ns.AllowedRelation("user", "...") 706 groupMember := ns.AllowedRelation("group", "member") 707 708 allowed := noError(vts.AllowedDirectRelationsAndWildcards("member")) 709 requireSameAllowedRelations(t, allowed, userDirect, groupMember) 710 }) 711 712 t.Run("AllowedSubjectRelations", func(t *testing.T) { 713 userDirect := ns.RelationReference("user", "...") 714 groupMember := ns.RelationReference("group", "member") 715 716 allowed := noError(vts.AllowedSubjectRelations("member")) 717 requireSameSubjectRelations(t, allowed, userDirect, groupMember) 718 }) 719 }, 720 }, 721 }, 722 { 723 "schema with caveats", 724 `definition user {} 725 726 caveat somecaveat(somecondition int) { 727 somecondition == 42 728 } 729 730 definition resource { 731 relation editor: user 732 relation viewer: user | user with somecaveat 733 relation onlycaveated: user with somecaveat 734 }`, 735 map[string]tsTester{ 736 "resource": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) { 737 t.Run("IsPermission", func(t *testing.T) { 738 require.False(t, vts.IsPermission("editor")) 739 require.False(t, vts.IsPermission("viewer")) 740 require.False(t, vts.IsPermission("onlycaveated")) 741 }) 742 743 t.Run("RelationDoesNotAllowCaveatsForSubject", func(t *testing.T) { 744 ok, err := vts.RelationDoesNotAllowCaveatsForSubject("viewer", "user") 745 require.NoError(t, err) 746 require.False(t, ok) 747 748 ok, err = vts.RelationDoesNotAllowCaveatsForSubject("editor", "user") 749 require.NoError(t, err) 750 require.True(t, ok) 751 752 ok, err = vts.RelationDoesNotAllowCaveatsForSubject("onlycaveated", "user") 753 require.NoError(t, err) 754 require.False(t, ok) 755 }) 756 757 t.Run("HasIndirectSubjects", func(t *testing.T) { 758 require.False(t, noError(vts.HasIndirectSubjects("editor"))) 759 require.False(t, noError(vts.HasIndirectSubjects("viewer"))) 760 require.False(t, noError(vts.HasIndirectSubjects("onlycaveated"))) 761 }) 762 763 t.Run("IsAllowedPublicNamespace", func(t *testing.T) { 764 require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("editor", "user"))) 765 require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("viewer", "user"))) 766 require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("onlycaveated", "user"))) 767 }) 768 769 t.Run("IsAllowedDirectNamespace", func(t *testing.T) { 770 require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("editor", "user"))) 771 require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("viewer", "user"))) 772 require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("onlycaveated", "user"))) 773 }) 774 775 t.Run("IsAllowedDirectRelation", func(t *testing.T) { 776 require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("editor", "user", "..."))) 777 require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("viewer", "user", "..."))) 778 require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("onlycaveated", "user", "..."))) 779 }) 780 781 t.Run("HasAllowedRelation", func(t *testing.T) { 782 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("editor", ns.AllowedRelation("user", "...")))) 783 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", ns.AllowedRelation("user", "...")))) 784 require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("onlycaveated", ns.AllowedRelation("user", "...")))) 785 786 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("somecaveat"))))) 787 require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("onlycaveated", ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("somecaveat"))))) 788 }) 789 790 t.Run("AllowedDirectRelationsAndWildcards", func(t *testing.T) { 791 userDirect := ns.AllowedRelation("user", "...") 792 caveatedUser := ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("somecaveat")) 793 794 allowed := noError(vts.AllowedDirectRelationsAndWildcards("editor")) 795 requireSameAllowedRelations(t, allowed, userDirect) 796 797 allowed = noError(vts.AllowedDirectRelationsAndWildcards("viewer")) 798 requireSameAllowedRelations(t, allowed, userDirect, caveatedUser) 799 800 allowed = noError(vts.AllowedDirectRelationsAndWildcards("onlycaveated")) 801 requireSameAllowedRelations(t, allowed, caveatedUser) 802 }) 803 804 t.Run("AllowedSubjectRelations", func(t *testing.T) { 805 userDirect := ns.RelationReference("user", "...") 806 807 allowed := noError(vts.AllowedSubjectRelations("editor")) 808 requireSameSubjectRelations(t, allowed, userDirect) 809 810 allowed = noError(vts.AllowedSubjectRelations("viewer")) 811 requireSameSubjectRelations(t, allowed, userDirect) 812 813 allowed = noError(vts.AllowedSubjectRelations("onlycaveated")) 814 requireSameSubjectRelations(t, allowed, userDirect) 815 }) 816 }, 817 }, 818 }, 819 } 820 821 for _, tc := range tcs { 822 tc := tc 823 t.Run(tc.name, func(t *testing.T) { 824 require := require.New(t) 825 826 ds, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC) 827 require.NoError(err) 828 829 ctx := datastoremw.ContextWithDatastore(context.Background(), ds) 830 831 compiled, err := compiler.Compile(compiler.InputSchema{ 832 Source: input.Source("schema"), 833 SchemaString: tc.schema, 834 }, compiler.AllowUnprefixedObjectType()) 835 require.NoError(err) 836 837 lastRevision, err := ds.HeadRevision(context.Background()) 838 require.NoError(err) 839 840 for _, nsDef := range compiled.ObjectDefinitions { 841 reader := ds.SnapshotReader(lastRevision) 842 ts, err := NewNamespaceTypeSystem(nsDef, 843 ResolverForDatastoreReader(reader).WithPredefinedElements(PredefinedElements{ 844 Namespaces: compiled.ObjectDefinitions, 845 Caveats: compiled.CaveatDefinitions, 846 })) 847 require.NoError(err) 848 849 vts, terr := ts.Validate(ctx) 850 require.NoError(terr) 851 852 require.Equal(vts.Namespace(), nsDef) 853 854 tester, ok := tc.namespaces[nsDef.Name] 855 if ok { 856 tester := tester 857 vts := vts 858 t.Run(nsDef.Name, func(t *testing.T) { 859 for _, relation := range nsDef.Relation { 860 require.True(vts.IsPermission(relation.Name) || vts.HasTypeInformation(relation.Name)) 861 } 862 863 tester(t, vts) 864 }) 865 } 866 } 867 }) 868 } 869 }