github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/developmentmembership/membership_test.go (about) 1 package developmentmembership 2 3 import ( 4 "sort" 5 "testing" 6 7 "github.com/authzed/spicedb/internal/caveats" 8 9 "github.com/stretchr/testify/require" 10 11 core "github.com/authzed/spicedb/pkg/proto/core/v1" 12 13 "github.com/authzed/spicedb/pkg/graph" 14 "github.com/authzed/spicedb/pkg/testutil" 15 "github.com/authzed/spicedb/pkg/tuple" 16 ) 17 18 var ( 19 ONR = tuple.ObjectAndRelation 20 Ellipsis = "..." 21 ) 22 23 func DS(objectType string, objectID string, objectRelation string) *core.DirectSubject { 24 return &core.DirectSubject{ 25 Subject: ONR(objectType, objectID, objectRelation), 26 } 27 } 28 29 func CaveatedDS(objectType string, objectID string, objectRelation string, caveatName string) *core.DirectSubject { 30 return &core.DirectSubject{ 31 Subject: ONR(objectType, objectID, objectRelation), 32 CaveatExpression: caveats.CaveatExprForTesting(caveatName), 33 } 34 } 35 36 var ( 37 _this *core.ObjectAndRelation 38 39 companyOwner = graph.Leaf(ONR("folder", "company", "owner"), 40 (DS("user", "owner", Ellipsis)), 41 ) 42 companyEditor = graph.Union(ONR("folder", "company", "editor"), 43 graph.Leaf(_this, (DS("user", "writer", Ellipsis))), 44 companyOwner, 45 ) 46 47 auditorsOwner = graph.Leaf(ONR("folder", "auditors", "owner")) 48 49 auditorsEditor = graph.Union(ONR("folder", "auditors", "editor"), 50 graph.Leaf(_this), 51 auditorsOwner, 52 ) 53 54 auditorsViewerRecursive = graph.Union(ONR("folder", "auditors", "viewer"), 55 graph.Leaf(_this, 56 (DS("user", "auditor", "...")), 57 ), 58 auditorsEditor, 59 graph.Union(ONR("folder", "auditors", "viewer")), 60 ) 61 62 companyViewerRecursive = graph.Union(ONR("folder", "company", "viewer"), 63 graph.Union(ONR("folder", "company", "viewer"), 64 auditorsViewerRecursive, 65 graph.Leaf(_this, 66 (DS("user", "legal", "...")), 67 (DS("folder", "auditors", "viewer")), 68 ), 69 ), 70 companyEditor, 71 graph.Union(ONR("folder", "company", "viewer")), 72 ) 73 ) 74 75 func TestMembershipSetBasic(t *testing.T) { 76 require := require.New(t) 77 ms := NewMembershipSet() 78 79 // Add some expansion trees. 80 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "owner"), companyOwner) 81 require.True(ok) 82 require.NoError(err) 83 verifySubjects(t, require, fso, "user:owner") 84 85 fse, ok, err := ms.AddExpansion(ONR("folder", "company", "editor"), companyEditor) 86 require.True(ok) 87 require.NoError(err) 88 verifySubjects(t, require, fse, "user:owner", "user:writer") 89 90 fsv, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), companyViewerRecursive) 91 require.True(ok) 92 require.NoError(err) 93 verifySubjects(t, require, fsv, "folder:auditors#viewer", "user:auditor", "user:legal", "user:owner", "user:writer") 94 } 95 96 func TestMembershipSetIntersectionBasic(t *testing.T) { 97 require := require.New(t) 98 ms := NewMembershipSet() 99 100 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 101 graph.Leaf(_this, 102 (DS("user", "legal", "...")), 103 ), 104 graph.Leaf(_this, 105 (DS("user", "owner", "...")), 106 (DS("user", "legal", "...")), 107 ), 108 ) 109 110 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 111 require.True(ok) 112 require.NoError(err) 113 verifySubjects(t, require, fso, "user:legal") 114 } 115 116 func TestMembershipSetIntersectionWithDifferentTypesOneMissingLeft(t *testing.T) { 117 require := require.New(t) 118 ms := NewMembershipSet() 119 120 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 121 graph.Leaf(_this, 122 (DS("user", "legal", "...")), 123 (DS("folder", "foobar", "...")), 124 ), 125 graph.Leaf(_this, 126 (DS("user", "owner", "...")), 127 (DS("user", "legal", "...")), 128 ), 129 ) 130 131 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 132 require.True(ok) 133 require.NoError(err) 134 verifySubjects(t, require, fso, "user:legal") 135 } 136 137 func TestMembershipSetIntersectionWithDifferentTypesOneMissingRight(t *testing.T) { 138 require := require.New(t) 139 ms := NewMembershipSet() 140 141 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 142 graph.Leaf(_this, 143 (DS("user", "legal", "...")), 144 ), 145 graph.Leaf(_this, 146 (DS("user", "owner", "...")), 147 (DS("user", "legal", "...")), 148 (DS("folder", "foobar", "...")), 149 ), 150 ) 151 152 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 153 require.True(ok) 154 require.NoError(err) 155 verifySubjects(t, require, fso, "user:legal") 156 } 157 158 func TestMembershipSetIntersectionWithDifferentTypes(t *testing.T) { 159 require := require.New(t) 160 ms := NewMembershipSet() 161 162 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 163 graph.Leaf(_this, 164 (DS("user", "legal", "...")), 165 (DS("folder", "foobar", "...")), 166 (DS("folder", "barbaz", "...")), 167 ), 168 graph.Leaf(_this, 169 (DS("user", "owner", "...")), 170 (DS("user", "legal", "...")), 171 (DS("folder", "barbaz", "...")), 172 ), 173 ) 174 175 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 176 require.True(ok) 177 require.NoError(err) 178 verifySubjects(t, require, fso, "folder:barbaz", "user:legal") 179 } 180 181 func TestMembershipSetExclusion(t *testing.T) { 182 require := require.New(t) 183 ms := NewMembershipSet() 184 185 exclusion := graph.Exclusion(ONR("folder", "company", "viewer"), 186 graph.Leaf(_this, 187 (DS("user", "owner", "...")), 188 (DS("user", "legal", "...")), 189 ), 190 graph.Leaf(_this, 191 (DS("user", "legal", "...")), 192 ), 193 ) 194 195 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion) 196 require.True(ok) 197 require.NoError(err) 198 verifySubjects(t, require, fso, "user:owner") 199 } 200 201 func TestMembershipSetExclusionMultiple(t *testing.T) { 202 require := require.New(t) 203 ms := NewMembershipSet() 204 205 exclusion := graph.Exclusion(ONR("folder", "company", "viewer"), 206 graph.Leaf(_this, 207 (DS("user", "owner", "...")), 208 (DS("user", "legal", "...")), 209 (DS("user", "third", "...")), 210 ), 211 graph.Leaf(_this, 212 (DS("user", "legal", "...")), 213 ), 214 graph.Leaf(_this, 215 (DS("user", "owner", "...")), 216 ), 217 ) 218 219 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion) 220 require.True(ok) 221 require.NoError(err) 222 verifySubjects(t, require, fso, "user:third") 223 } 224 225 func TestMembershipSetExclusionMultipleWithWildcard(t *testing.T) { 226 require := require.New(t) 227 ms := NewMembershipSet() 228 229 exclusion := graph.Exclusion(ONR("folder", "company", "viewer"), 230 graph.Leaf(_this, 231 (DS("user", "owner", "...")), 232 (DS("user", "legal", "...")), 233 ), 234 graph.Leaf(_this, 235 (DS("user", "legal", "...")), 236 ), 237 graph.Leaf(_this, 238 (DS("user", "*", "...")), 239 ), 240 ) 241 242 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion) 243 require.True(ok) 244 require.NoError(err) 245 verifySubjects(t, require, fso) 246 } 247 248 func TestMembershipSetExclusionMultipleMiddle(t *testing.T) { 249 require := require.New(t) 250 ms := NewMembershipSet() 251 252 exclusion := graph.Exclusion(ONR("folder", "company", "viewer"), 253 graph.Leaf(_this, 254 (DS("user", "owner", "...")), 255 (DS("user", "legal", "...")), 256 (DS("user", "third", "...")), 257 ), 258 graph.Leaf(_this, 259 (DS("user", "another", "...")), 260 ), 261 graph.Leaf(_this, 262 (DS("user", "owner", "...")), 263 ), 264 ) 265 266 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion) 267 require.True(ok) 268 require.NoError(err) 269 verifySubjects(t, require, fso, "user:third", "user:legal") 270 } 271 272 func TestMembershipSetIntersectionWithOneWildcard(t *testing.T) { 273 require := require.New(t) 274 ms := NewMembershipSet() 275 276 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 277 graph.Leaf(_this, 278 (DS("user", "owner", "...")), 279 (DS("user", "*", "...")), 280 ), 281 graph.Leaf(_this, 282 (DS("user", "legal", "...")), 283 ), 284 ) 285 286 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 287 require.True(ok) 288 require.NoError(err) 289 verifySubjects(t, require, fso, "user:legal") 290 } 291 292 func TestMembershipSetIntersectionWithAllWildcardLeft(t *testing.T) { 293 require := require.New(t) 294 ms := NewMembershipSet() 295 296 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 297 graph.Leaf(_this, 298 (DS("user", "owner", "...")), 299 (DS("user", "*", "...")), 300 ), 301 graph.Leaf(_this, 302 (DS("user", "*", "...")), 303 ), 304 ) 305 306 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 307 require.True(ok) 308 require.NoError(err) 309 verifySubjects(t, require, fso, "user:*", "user:owner") 310 } 311 312 func TestMembershipSetIntersectionWithAllWildcardRight(t *testing.T) { 313 require := require.New(t) 314 ms := NewMembershipSet() 315 316 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 317 graph.Leaf(_this, 318 (DS("user", "*", "...")), 319 ), 320 graph.Leaf(_this, 321 (DS("user", "owner", "...")), 322 (DS("user", "*", "...")), 323 ), 324 ) 325 326 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 327 require.True(ok) 328 require.NoError(err) 329 verifySubjects(t, require, fso, "user:*", "user:owner") 330 } 331 332 func TestMembershipSetExclusionWithLeftWildcard(t *testing.T) { 333 require := require.New(t) 334 ms := NewMembershipSet() 335 336 exclusion := graph.Exclusion(ONR("folder", "company", "viewer"), 337 graph.Leaf(_this, 338 (DS("user", "owner", "...")), 339 (DS("user", "*", "...")), 340 ), 341 graph.Leaf(_this, 342 (DS("user", "legal", "...")), 343 ), 344 ) 345 346 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion) 347 require.True(ok) 348 require.NoError(err) 349 verifySubjects(t, require, fso, "user:*", "user:owner") 350 } 351 352 func TestMembershipSetExclusionWithRightWildcard(t *testing.T) { 353 require := require.New(t) 354 ms := NewMembershipSet() 355 356 exclusion := graph.Exclusion(ONR("folder", "company", "viewer"), 357 graph.Leaf(_this, 358 (DS("user", "owner", "...")), 359 (DS("user", "legal", "...")), 360 ), 361 graph.Leaf(_this, 362 (DS("user", "*", "...")), 363 ), 364 ) 365 366 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion) 367 require.True(ok) 368 require.NoError(err) 369 verifySubjects(t, require, fso) 370 } 371 372 func TestMembershipSetIntersectionWithThreeWildcards(t *testing.T) { 373 require := require.New(t) 374 ms := NewMembershipSet() 375 376 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 377 graph.Leaf(_this, 378 (DS("user", "owner", "...")), 379 (DS("user", "legal", "...")), 380 ), 381 graph.Leaf(_this, 382 (DS("user", "*", "...")), 383 ), 384 graph.Leaf(_this, 385 (DS("user", "*", "...")), 386 ), 387 ) 388 389 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 390 require.True(ok) 391 require.NoError(err) 392 verifySubjects(t, require, fso, "user:owner", "user:legal") 393 } 394 395 func TestMembershipSetIntersectionWithOneBranchMissingWildcards(t *testing.T) { 396 require := require.New(t) 397 ms := NewMembershipSet() 398 399 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 400 graph.Leaf(_this, 401 (DS("user", "owner", "...")), 402 (DS("user", "legal", "...")), 403 (DS("user", "*", "...")), 404 ), 405 graph.Leaf(_this, 406 (DS("user", "owner", "...")), 407 ), 408 graph.Leaf(_this, 409 (DS("user", "*", "...")), 410 ), 411 ) 412 413 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 414 require.True(ok) 415 require.NoError(err) 416 verifySubjects(t, require, fso, "user:owner") 417 } 418 419 func TestMembershipSetIntersectionWithTwoBranchesMissingWildcards(t *testing.T) { 420 require := require.New(t) 421 ms := NewMembershipSet() 422 423 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 424 graph.Leaf(_this, 425 (DS("user", "owner", "...")), 426 (DS("user", "legal", "...")), 427 ), 428 graph.Leaf(_this, 429 (DS("user", "another", "...")), 430 ), 431 graph.Leaf(_this, 432 (DS("user", "*", "...")), 433 ), 434 ) 435 436 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 437 require.True(ok) 438 require.NoError(err) 439 verifySubjects(t, require, fso) 440 } 441 442 func TestMembershipSetWithCaveats(t *testing.T) { 443 require := require.New(t) 444 ms := NewMembershipSet() 445 446 intersection := graph.Intersection(ONR("folder", "company", "viewer"), 447 graph.Leaf(_this, 448 (DS("user", "owner", "...")), 449 (DS("user", "legal", "...")), 450 (DS("user", "*", "...")), 451 ), 452 graph.Leaf(_this, 453 (CaveatedDS("user", "owner", "...", "somecaveat")), 454 ), 455 graph.Leaf(_this, 456 (DS("user", "*", "...")), 457 ), 458 ) 459 intersection.CaveatExpression = caveats.CaveatExprForTesting("anothercaveat") 460 461 fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) 462 require.True(ok) 463 require.NoError(err) 464 verifySubjects(t, require, fso, "user:owner") 465 466 // Verify the caveat on the user:owner. 467 subject, ok := fso.LookupSubject(ONR("user", "owner", "...")) 468 require.True(ok) 469 470 testutil.RequireProtoEqual(t, subject.GetCaveatExpression(), caveats.And( 471 caveats.CaveatExprForTesting("anothercaveat"), 472 caveats.CaveatExprForTesting("somecaveat"), 473 ), "found invalid caveat expr for subject") 474 } 475 476 func verifySubjects(t *testing.T, require *require.Assertions, fs FoundSubjects, expected ...string) { 477 foundSubjects := []*core.ObjectAndRelation{} 478 for _, found := range fs.ListFound() { 479 foundSubjects = append(foundSubjects, found.Subject()) 480 481 _, ok := fs.LookupSubject(found.Subject()) 482 require.True(ok, "Could not find expected subject %s", found.Subject()) 483 } 484 485 found := tuple.StringsONRs(foundSubjects) 486 sort.Strings(expected) 487 sort.Strings(found) 488 489 testutil.RequireEqualEmptyNil(t, expected, found) 490 }