github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datasets/basesubjectset.go (about) 1 package datasets 2 3 import ( 4 "golang.org/x/exp/maps" 5 6 "github.com/authzed/spicedb/internal/caveats" 7 core "github.com/authzed/spicedb/pkg/proto/core/v1" 8 "github.com/authzed/spicedb/pkg/spiceerrors" 9 "github.com/authzed/spicedb/pkg/tuple" 10 ) 11 12 var ( 13 caveatAnd = caveats.And 14 caveatOr = caveats.Or 15 caveatInvert = caveats.Invert 16 shortcircuitedOr = caveats.ShortcircuitedOr 17 ) 18 19 // Subject is a subject that can be placed into a BaseSubjectSet. It is defined in a generic 20 // manner to allow implementations that wrap BaseSubjectSet to add their own additional bookkeeping 21 // to the base implementation. 22 type Subject[T any] interface { 23 // GetSubjectId returns the ID of the subject. For wildcards, this should be `*`. 24 GetSubjectId() string 25 26 // GetCaveatExpression returns the caveat expression for this subject, if it is conditional. 27 GetCaveatExpression() *core.CaveatExpression 28 29 // GetExcludedSubjects returns the list of subjects excluded. Must only have values 30 // for wildcards and must never be nested. 31 GetExcludedSubjects() []T 32 } 33 34 // BaseSubjectSet defines a set that tracks accessible subjects, their exclusions (if wildcards), 35 // and all conditional expressions applied due to caveats. 36 // 37 // It is generic to allow other implementations to define the kind of tracking information 38 // associated with each subject. 39 // 40 // NOTE: Unlike a traditional set, unions between wildcards and a concrete subject will result 41 // in *both* being present in the set, to maintain the proper set semantics around wildcards. 42 type BaseSubjectSet[T Subject[T]] struct { 43 constructor constructor[T] 44 concrete map[string]T 45 wildcard *handle[T] 46 } 47 48 // NewBaseSubjectSet creates a new base subject set for use underneath well-typed implementation. 49 // 50 // The constructor function returns a new instance of type T for a particular subject ID. 51 func NewBaseSubjectSet[T Subject[T]](constructor constructor[T]) BaseSubjectSet[T] { 52 return BaseSubjectSet[T]{ 53 constructor: constructor, 54 concrete: map[string]T{}, 55 wildcard: newHandle[T](), 56 } 57 } 58 59 // constructor defines a function for constructing a new instance of the Subject type T for 60 // a subject ID, its (optional) conditional expression, any excluded subjects, and any sources 61 // for bookkeeping. The sources are those other subjects that were combined to create the current 62 // subject. 63 type constructor[T Subject[T]] func(subjectID string, conditionalExpression *core.CaveatExpression, excludedSubjects []T, sources ...T) T 64 65 // MustAdd adds the found subject to the set. This is equivalent to a Union operation between the 66 // existing set of subjects and a set containing the single subject, but modifies the set 67 // *in place*. 68 func (bss BaseSubjectSet[T]) MustAdd(foundSubject T) { 69 err := bss.Add(foundSubject) 70 if err != nil { 71 panic(err) 72 } 73 } 74 75 // Add adds the found subject to the set. This is equivalent to a Union operation between the 76 // existing set of subjects and a set containing the single subject, but modifies the set 77 // *in place*. 78 func (bss BaseSubjectSet[T]) Add(foundSubject T) error { 79 if foundSubject.GetSubjectId() == tuple.PublicWildcard { 80 existing := bss.wildcard.getOrNil() 81 updated, err := unionWildcardWithWildcard(existing, foundSubject, bss.constructor) 82 if err != nil { 83 return err 84 } 85 86 bss.wildcard.setOrNil(updated) 87 88 for _, concrete := range bss.concrete { 89 updated = unionWildcardWithConcrete(updated, concrete, bss.constructor) 90 } 91 bss.wildcard.setOrNil(updated) 92 return nil 93 } 94 95 var updatedOrNil *T 96 if updated, ok := bss.concrete[foundSubject.GetSubjectId()]; ok { 97 updatedOrNil = &updated 98 } 99 bss.setConcrete(foundSubject.GetSubjectId(), unionConcreteWithConcrete(updatedOrNil, &foundSubject, bss.constructor)) 100 101 wildcard := bss.wildcard.getOrNil() 102 wildcard = unionWildcardWithConcrete(wildcard, foundSubject, bss.constructor) 103 bss.wildcard.setOrNil(wildcard) 104 return nil 105 } 106 107 func (bss BaseSubjectSet[T]) setConcrete(subjectID string, subjectOrNil *T) { 108 if subjectOrNil == nil { 109 delete(bss.concrete, subjectID) 110 return 111 } 112 113 subject := *subjectOrNil 114 bss.concrete[subject.GetSubjectId()] = subject 115 } 116 117 // Subtract subtracts the given subject found the set. 118 func (bss BaseSubjectSet[T]) Subtract(toRemove T) { 119 if toRemove.GetSubjectId() == tuple.PublicWildcard { 120 for _, concrete := range bss.concrete { 121 bss.setConcrete(concrete.GetSubjectId(), subtractWildcardFromConcrete(concrete, toRemove, bss.constructor)) 122 } 123 124 existing := bss.wildcard.getOrNil() 125 updatedWildcard, concretesToAdd := subtractWildcardFromWildcard(existing, toRemove, bss.constructor) 126 bss.wildcard.setOrNil(updatedWildcard) 127 for _, concrete := range concretesToAdd { 128 concrete := concrete 129 bss.setConcrete(concrete.GetSubjectId(), &concrete) 130 } 131 return 132 } 133 134 if existing, ok := bss.concrete[toRemove.GetSubjectId()]; ok { 135 bss.setConcrete(toRemove.GetSubjectId(), subtractConcreteFromConcrete(existing, toRemove, bss.constructor)) 136 } 137 138 wildcard, ok := bss.wildcard.get() 139 if ok { 140 bss.wildcard.setOrNil(subtractConcreteFromWildcard(wildcard, toRemove, bss.constructor)) 141 } 142 } 143 144 // SubtractAll subtracts the other set of subjects from this set of subtracts, modifying this 145 // set *in place*. 146 func (bss BaseSubjectSet[T]) SubtractAll(other BaseSubjectSet[T]) { 147 for _, otherSubject := range other.AsSlice() { 148 bss.Subtract(otherSubject) 149 } 150 } 151 152 // MustIntersectionDifference performs an intersection between this set and the other set, modifying 153 // this set *in place*. 154 func (bss BaseSubjectSet[T]) MustIntersectionDifference(other BaseSubjectSet[T]) { 155 err := bss.IntersectionDifference(other) 156 if err != nil { 157 panic(err) 158 } 159 } 160 161 // IntersectionDifference performs an intersection between this set and the other set, modifying 162 // this set *in place*. 163 func (bss BaseSubjectSet[T]) IntersectionDifference(other BaseSubjectSet[T]) error { 164 // Intersect the wildcards of the sets, if any. 165 existingWildcard := bss.wildcard.getOrNil() 166 otherWildcard := other.wildcard.getOrNil() 167 168 intersection, err := intersectWildcardWithWildcard(existingWildcard, otherWildcard, bss.constructor) 169 if err != nil { 170 return err 171 } 172 173 bss.wildcard.setOrNil(intersection) 174 175 // Intersect the concretes of each set, as well as with the wildcards. 176 updatedConcretes := make(map[string]T, len(bss.concrete)) 177 178 for _, concreteSubject := range bss.concrete { 179 var otherConcreteOrNil *T 180 if otherConcrete, ok := other.concrete[concreteSubject.GetSubjectId()]; ok { 181 otherConcreteOrNil = &otherConcrete 182 } 183 184 concreteIntersected := intersectConcreteWithConcrete(concreteSubject, otherConcreteOrNil, bss.constructor) 185 otherWildcardIntersected, err := intersectConcreteWithWildcard(concreteSubject, otherWildcard, bss.constructor) 186 if err != nil { 187 return err 188 } 189 190 result := unionConcreteWithConcrete(concreteIntersected, otherWildcardIntersected, bss.constructor) 191 if result != nil { 192 updatedConcretes[concreteSubject.GetSubjectId()] = *result 193 } 194 } 195 196 if existingWildcard != nil { 197 for _, otherSubject := range other.concrete { 198 existingWildcardIntersect, err := intersectConcreteWithWildcard(otherSubject, existingWildcard, bss.constructor) 199 if err != nil { 200 return err 201 } 202 203 if existingUpdated, ok := updatedConcretes[otherSubject.GetSubjectId()]; ok { 204 result := unionConcreteWithConcrete(&existingUpdated, existingWildcardIntersect, bss.constructor) 205 updatedConcretes[otherSubject.GetSubjectId()] = *result 206 } else if existingWildcardIntersect != nil { 207 updatedConcretes[otherSubject.GetSubjectId()] = *existingWildcardIntersect 208 } 209 } 210 } 211 212 clear(bss.concrete) 213 maps.Copy(bss.concrete, updatedConcretes) 214 return nil 215 } 216 217 // UnionWith adds the given subjects to this set, via a union call. 218 func (bss BaseSubjectSet[T]) UnionWith(foundSubjects []T) error { 219 for _, fs := range foundSubjects { 220 err := bss.Add(fs) 221 if err != nil { 222 return err 223 } 224 } 225 return nil 226 } 227 228 // UnionWithSet performs a union operation between this set and the other set, modifying this 229 // set *in place*. 230 func (bss BaseSubjectSet[T]) UnionWithSet(other BaseSubjectSet[T]) error { 231 return bss.UnionWith(other.AsSlice()) 232 } 233 234 // MustUnionWithSet performs a union operation between this set and the other set, modifying this 235 // set *in place*. 236 func (bss BaseSubjectSet[T]) MustUnionWithSet(other BaseSubjectSet[T]) { 237 err := bss.UnionWithSet(other) 238 if err != nil { 239 panic(err) 240 } 241 } 242 243 // Get returns the found subject with the given ID in the set, if any. 244 func (bss BaseSubjectSet[T]) Get(id string) (T, bool) { 245 if id == tuple.PublicWildcard { 246 return bss.wildcard.get() 247 } 248 249 found, ok := bss.concrete[id] 250 return found, ok 251 } 252 253 // IsEmpty returns whether the subject set is empty. 254 func (bss BaseSubjectSet[T]) IsEmpty() bool { 255 return bss.wildcard.getOrNil() == nil && len(bss.concrete) == 0 256 } 257 258 // AsSlice returns the contents of the subject set as a slice of found subjects. 259 func (bss BaseSubjectSet[T]) AsSlice() []T { 260 values := maps.Values(bss.concrete) 261 if wildcard, ok := bss.wildcard.get(); ok { 262 values = append(values, wildcard) 263 } 264 return values 265 } 266 267 // SubjectCount returns the number of subjects in the set. 268 func (bss BaseSubjectSet[T]) SubjectCount() int { 269 if bss.HasWildcard() { 270 return bss.ConcreteSubjectCount() + 1 271 } 272 return bss.ConcreteSubjectCount() 273 } 274 275 // ConcreteSubjectCount returns the number of concrete subjects in the set. 276 func (bss BaseSubjectSet[T]) ConcreteSubjectCount() int { 277 return len(bss.concrete) 278 } 279 280 // HasWildcard returns true if the subject set contains the specialized wildcard subject. 281 func (bss BaseSubjectSet[T]) HasWildcard() bool { 282 _, ok := bss.wildcard.get() 283 return ok 284 } 285 286 // Clone returns a clone of this subject set. Note that this is a shallow clone. 287 // NOTE: Should only be used when performance is not a concern. 288 func (bss BaseSubjectSet[T]) Clone() BaseSubjectSet[T] { 289 return BaseSubjectSet[T]{ 290 constructor: bss.constructor, 291 concrete: maps.Clone(bss.concrete), 292 wildcard: bss.wildcard.clone(), 293 } 294 } 295 296 // UnsafeRemoveExact removes the *exact* matching subject, with no wildcard handling. 297 // This should ONLY be used for testing. 298 func (bss BaseSubjectSet[T]) UnsafeRemoveExact(foundSubject T) { 299 if foundSubject.GetSubjectId() == tuple.PublicWildcard { 300 bss.wildcard.clear() 301 return 302 } 303 304 delete(bss.concrete, foundSubject.GetSubjectId()) 305 } 306 307 // WithParentCaveatExpression returns a copy of the subject set with the parent caveat expression applied 308 // to all members of this set. 309 func (bss BaseSubjectSet[T]) WithParentCaveatExpression(parentCaveatExpr *core.CaveatExpression) BaseSubjectSet[T] { 310 clone := bss.Clone() 311 312 // Apply the parent caveat expression to the wildcard, if any. 313 if wildcard, ok := clone.wildcard.get(); ok { 314 constructed := bss.constructor( 315 tuple.PublicWildcard, 316 caveatAnd(parentCaveatExpr, wildcard.GetCaveatExpression()), 317 wildcard.GetExcludedSubjects(), 318 wildcard, 319 ) 320 clone.wildcard.setOrNil(&constructed) 321 } 322 323 // Apply the parent caveat expression to each concrete. 324 for subjectID, concrete := range clone.concrete { 325 clone.concrete[subjectID] = bss.constructor( 326 subjectID, 327 caveatAnd(parentCaveatExpr, concrete.GetCaveatExpression()), 328 nil, 329 concrete, 330 ) 331 } 332 333 return clone 334 } 335 336 // unionWildcardWithWildcard performs a union operation over two wildcards, returning the updated 337 // wildcard (if any). 338 func unionWildcardWithWildcard[T Subject[T]](existing *T, adding T, constructor constructor[T]) (*T, error) { 339 // If there is no existing wildcard, return the added one. 340 if existing == nil { 341 return &adding, nil 342 } 343 344 // Otherwise, union together the conditionals for the wildcards and *intersect* their exclusion 345 // sets. 346 existingWildcard := *existing 347 expression := shortcircuitedOr(existingWildcard.GetCaveatExpression(), adding.GetCaveatExpression()) 348 349 // Exclusion sets are intersected because if an exclusion is missing from one wildcard 350 // but not the other, the missing element will be, by definition, in that other wildcard. 351 // 352 // Examples: 353 // 354 // {*} + {*} => {*} 355 // {* - {user:tom}} + {*} => {*} 356 // {* - {user:tom}} + {* - {user:sarah}} => {*} 357 // {* - {user:tom, user:sarah}} + {* - {user:sarah}} => {* - {user:sarah}} 358 // {*}[c1] + {*} => {*} 359 // {*}[c1] + {*}[c2] => {*}[c1 || c2] 360 361 // NOTE: since we're only using concretes here, it is safe to reuse the BaseSubjectSet itself. 362 exisingConcreteExclusions := NewBaseSubjectSet(constructor) 363 for _, excludedSubject := range existingWildcard.GetExcludedSubjects() { 364 if excludedSubject.GetSubjectId() == tuple.PublicWildcard { 365 return nil, spiceerrors.MustBugf("wildcards are not allowed in exclusions") 366 } 367 368 err := exisingConcreteExclusions.Add(excludedSubject) 369 if err != nil { 370 return nil, err 371 } 372 } 373 374 foundConcreteExclusions := NewBaseSubjectSet(constructor) 375 for _, excludedSubject := range adding.GetExcludedSubjects() { 376 if excludedSubject.GetSubjectId() == tuple.PublicWildcard { 377 return nil, spiceerrors.MustBugf("wildcards are not allowed in exclusions") 378 } 379 380 err := foundConcreteExclusions.Add(excludedSubject) 381 if err != nil { 382 return nil, err 383 } 384 } 385 386 err := exisingConcreteExclusions.IntersectionDifference(foundConcreteExclusions) 387 if err != nil { 388 return nil, err 389 } 390 391 constructed := constructor( 392 tuple.PublicWildcard, 393 expression, 394 exisingConcreteExclusions.AsSlice(), 395 *existing, 396 adding) 397 return &constructed, nil 398 } 399 400 // unionWildcardWithConcrete performs a union operation between a wildcard and a concrete subject 401 // being added to the set, returning the updated wildcard (if applciable). 402 func unionWildcardWithConcrete[T Subject[T]](existing *T, adding T, constructor constructor[T]) *T { 403 // If there is no existing wildcard, nothing more to do. 404 if existing == nil { 405 return nil 406 } 407 408 // If the concrete is in the exclusion set, remove it if not conditional. Otherwise, mark 409 // it as conditional. 410 // 411 // Examples: 412 // {*} | {user:tom} => {*} (and user:tom in the concrete) 413 // {* - {user:tom}} | {user:tom} => {*} (and user:tom in the concrete) 414 // {* - {user:tom}[c1]} | {user:tom}[c2] => {* - {user:tom}[c1 && !c2]} (and user:tom in the concrete) 415 existingWildcard := *existing 416 updatedExclusions := make([]T, 0, len(existingWildcard.GetExcludedSubjects())) 417 for _, existingExclusion := range existingWildcard.GetExcludedSubjects() { 418 if existingExclusion.GetSubjectId() == adding.GetSubjectId() { 419 // If the conditional on the concrete is empty, then the concrete is always present, so 420 // we remove the exclusion entirely. 421 if adding.GetCaveatExpression() == nil { 422 continue 423 } 424 425 // Otherwise, the conditional expression for the new exclusion is the existing expression && 426 // the *inversion* of the concrete's expression, as the exclusion will only apply if the 427 // concrete subject is not present and the exclusion's expression is true. 428 exclusionConditionalExpression := caveatAnd( 429 existingExclusion.GetCaveatExpression(), 430 caveatInvert(adding.GetCaveatExpression()), 431 ) 432 433 updatedExclusions = append(updatedExclusions, constructor( 434 adding.GetSubjectId(), 435 exclusionConditionalExpression, 436 nil, 437 existingExclusion, 438 adding), 439 ) 440 } else { 441 updatedExclusions = append(updatedExclusions, existingExclusion) 442 } 443 } 444 445 constructed := constructor( 446 tuple.PublicWildcard, 447 existingWildcard.GetCaveatExpression(), 448 updatedExclusions, 449 existingWildcard) 450 return &constructed 451 } 452 453 // unionConcreteWithConcrete performs a union operation between two concrete subjects and returns 454 // the concrete subject produced, if any. 455 func unionConcreteWithConcrete[T Subject[T]](existing *T, adding *T, constructor constructor[T]) *T { 456 // Check for union with other concretes. 457 if existing == nil { 458 return adding 459 } 460 461 if adding == nil { 462 return existing 463 } 464 465 existingConcrete := *existing 466 addingConcrete := *adding 467 468 // A union of a concrete subjects has the conditionals of each concrete merged. 469 constructed := constructor( 470 existingConcrete.GetSubjectId(), 471 shortcircuitedOr( 472 existingConcrete.GetCaveatExpression(), 473 addingConcrete.GetCaveatExpression(), 474 ), 475 nil, 476 existingConcrete, addingConcrete) 477 return &constructed 478 } 479 480 // subtractWildcardFromWildcard performs a subtraction operation of wildcard from another, returning 481 // the updated wildcard (if any), as well as any concrete subjects produced by the subtraction 482 // operation due to exclusions. 483 func subtractWildcardFromWildcard[T Subject[T]](existing *T, toRemove T, constructor constructor[T]) (*T, []T) { 484 // If there is no existing wildcard, nothing more to do. 485 if existing == nil { 486 return nil, nil 487 } 488 489 // If there is no condition on the wildcard and the new wildcard has no exclusions, then this wildcard goes away. 490 // Example: {*} - {*} => {} 491 if toRemove.GetCaveatExpression() == nil && len(toRemove.GetExcludedSubjects()) == 0 { 492 return nil, nil 493 } 494 495 // Otherwise, we construct a new wildcard and return any concrete subjects that might result from this subtraction. 496 existingWildcard := *existing 497 existingExclusions := exclusionsMapFor(existingWildcard) 498 499 // Calculate the exclusions which turn into concrete subjects. 500 // This occurs when a wildcard with exclusions is subtracted from a wildcard 501 // (with, or without *matching* exclusions). 502 // 503 // Example: 504 // Given the two wildcards `* - {user:sarah}` and `* - {user:tom, user:amy, user:sarah}`, 505 // the resulting concrete subjects are {user:tom, user:amy} because the first set contains 506 // `tom` and `amy` (but not `sarah`) and the second set contains all three. 507 resultingConcreteSubjects := make([]T, 0, len(toRemove.GetExcludedSubjects())) 508 for _, excludedSubject := range toRemove.GetExcludedSubjects() { 509 if existingExclusion, isExistingExclusion := existingExclusions[excludedSubject.GetSubjectId()]; !isExistingExclusion || existingExclusion.GetCaveatExpression() != nil { 510 // The conditional expression for the now-concrete subject type is the conditional on the provided exclusion 511 // itself. 512 // 513 // As an example, subtracting the wildcards 514 // {*[caveat1] - {user:tom}} 515 // - 516 // {*[caveat3] - {user:sarah[caveat4]}} 517 // 518 // the resulting expression to produce a *concrete* `user:sarah` is 519 // `caveat1 && caveat3 && caveat4`, because the concrete subject only appears if the first 520 // wildcard applies, the *second* wildcard applies and its exclusion applies. 521 exclusionConditionalExpression := caveatAnd( 522 caveatAnd( 523 existingWildcard.GetCaveatExpression(), 524 toRemove.GetCaveatExpression(), 525 ), 526 excludedSubject.GetCaveatExpression(), 527 ) 528 529 // If there is an existing exclusion, then its caveat expression is added as well, but inverted. 530 // 531 // As an example, subtracting the wildcards 532 // {*[caveat1] - {user:tom[caveat2]}} 533 // - 534 // {*[caveat3] - {user:sarah[caveat4]}} 535 // 536 // the resulting expression to produce a *concrete* `user:sarah` is 537 // `caveat1 && !caveat2 && caveat3 && caveat4`, because the concrete subject only appears 538 // if the first wildcard applies, the *second* wildcard applies, the first exclusion 539 // does *not* apply (ensuring the concrete is in the first wildcard) and the second exclusion 540 // *does* apply (ensuring it is not in the second wildcard). 541 if existingExclusion.GetCaveatExpression() != nil { 542 exclusionConditionalExpression = caveatAnd( 543 caveatAnd( 544 caveatAnd( 545 existingWildcard.GetCaveatExpression(), 546 toRemove.GetCaveatExpression(), 547 ), 548 caveatInvert(existingExclusion.GetCaveatExpression()), 549 ), 550 excludedSubject.GetCaveatExpression(), 551 ) 552 } 553 554 resultingConcreteSubjects = append(resultingConcreteSubjects, constructor( 555 excludedSubject.GetSubjectId(), 556 exclusionConditionalExpression, 557 nil, excludedSubject)) 558 } 559 } 560 561 // Create the combined conditional: the wildcard can only exist when it is present and the other wildcard is not. 562 combinedConditionalExpression := caveatAnd(existingWildcard.GetCaveatExpression(), caveatInvert(toRemove.GetCaveatExpression())) 563 if combinedConditionalExpression != nil { 564 constructed := constructor( 565 tuple.PublicWildcard, 566 combinedConditionalExpression, 567 existingWildcard.GetExcludedSubjects(), 568 existingWildcard, 569 toRemove) 570 return &constructed, resultingConcreteSubjects 571 } 572 573 return nil, resultingConcreteSubjects 574 } 575 576 // subtractWildcardFromConcrete subtracts a wildcard from a concrete element, returning the updated 577 // concrete subject, if any. 578 func subtractWildcardFromConcrete[T Subject[T]](existingConcrete T, wildcardToRemove T, constructor constructor[T]) *T { 579 // Subtraction of a wildcard removes *all* elements of the concrete set, except those that 580 // are found in the excluded list. If the wildcard *itself* is conditional, then instead of 581 // items being removed, they are made conditional on the inversion of the wildcard's expression, 582 // and the exclusion's conditional, if any. 583 // 584 // Examples: 585 // {user:sarah, user:tom} - {*} => {} 586 // {user:sarah, user:tom} - {*[somecaveat]} => {user:sarah[!somecaveat], user:tom[!somecaveat]} 587 // {user:sarah, user:tom} - {* - {user:tom}} => {user:tom} 588 // {user:sarah, user:tom} - {*[somecaveat] - {user:tom}} => {user:sarah[!somecaveat], user:tom} 589 // {user:sarah, user:tom} - {* - {user:tom[c2]}}[somecaveat] => {user:sarah[!somecaveat], user:tom[c2]} 590 // {user:sarah[c1], user:tom} - {*[somecaveat] - {user:tom}} => {user:sarah[c1 && !somecaveat], user:tom} 591 exclusions := exclusionsMapFor(wildcardToRemove) 592 exclusion, isExcluded := exclusions[existingConcrete.GetSubjectId()] 593 if !isExcluded { 594 // If the subject was not excluded within the wildcard, it is either removed directly 595 // (in the case where the wildcard is not conditional), or has its condition updated to 596 // reflect that it is only present when the condition for the wildcard is *false*. 597 if wildcardToRemove.GetCaveatExpression() == nil { 598 return nil 599 } 600 601 constructed := constructor( 602 existingConcrete.GetSubjectId(), 603 caveatAnd(existingConcrete.GetCaveatExpression(), caveatInvert(wildcardToRemove.GetCaveatExpression())), 604 nil, 605 existingConcrete) 606 return &constructed 607 } 608 609 // If the exclusion is not conditional, then the subject is always present. 610 if exclusion.GetCaveatExpression() == nil { 611 return &existingConcrete 612 } 613 614 // The conditional of the exclusion is that of the exclusion itself OR the caveatInverted case of 615 // the wildcard, which would mean the wildcard itself does not apply. 616 exclusionConditional := caveatOr(caveatInvert(wildcardToRemove.GetCaveatExpression()), exclusion.GetCaveatExpression()) 617 618 constructed := constructor( 619 existingConcrete.GetSubjectId(), 620 caveatAnd(existingConcrete.GetCaveatExpression(), exclusionConditional), 621 nil, 622 existingConcrete) 623 return &constructed 624 } 625 626 // subtractConcreteFromConcrete subtracts a concrete subject from another concrete subject. 627 func subtractConcreteFromConcrete[T Subject[T]](existingConcrete T, toRemove T, constructor constructor[T]) *T { 628 // Subtraction of a concrete type removes the entry from the concrete list 629 // *unless* the subtraction is conditional, in which case the conditional is updated 630 // to remove the element when it is true. 631 // 632 // Examples: 633 // {user:sarah} - {user:tom} => {user:sarah} 634 // {user:tom} - {user:tom} => {} 635 // {user:tom[c1]} - {user:tom} => {user:tom} 636 // {user:tom} - {user:tom[c2]} => {user:tom[!c2]} 637 // {user:tom[c1]} - {user:tom[c2]} => {user:tom[c1 && !c2]} 638 if toRemove.GetCaveatExpression() == nil { 639 return nil 640 } 641 642 // Otherwise, adjust the conditional of the existing item to remove it if it is true. 643 expression := caveatAnd( 644 existingConcrete.GetCaveatExpression(), 645 caveatInvert( 646 toRemove.GetCaveatExpression(), 647 ), 648 ) 649 650 constructed := constructor( 651 existingConcrete.GetSubjectId(), 652 expression, 653 nil, 654 existingConcrete, toRemove) 655 return &constructed 656 } 657 658 // subtractConcreteFromWildcard subtracts a concrete element from a wildcard. 659 func subtractConcreteFromWildcard[T Subject[T]](wildcard T, concreteToRemove T, constructor constructor[T]) *T { 660 // Subtracting a concrete type from a wildcard adds the concrete to the exclusions for the wildcard. 661 // Examples: 662 // {*} - {user:tom} => {* - {user:tom}} 663 // {*} - {user:tom[c1]} => {* - {user:tom[c1]}} 664 // {* - {user:tom[c1]}} - {user:tom} => {* - {user:tom}} 665 // {* - {user:tom[c1]}} - {user:tom[c2]} => {* - {user:tom[c1 || c2]}} 666 updatedExclusions := make([]T, 0, len(wildcard.GetExcludedSubjects())+1) 667 wasFound := false 668 for _, existingExclusion := range wildcard.GetExcludedSubjects() { 669 if existingExclusion.GetSubjectId() == concreteToRemove.GetSubjectId() { 670 // The conditional expression for the exclusion is a combination on the existing exclusion or 671 // the new expression. The caveat is short-circuited here because if either the exclusion or 672 // the concrete is non-caveated, then the whole exclusion is non-caveated. 673 exclusionConditionalExpression := shortcircuitedOr( 674 existingExclusion.GetCaveatExpression(), 675 concreteToRemove.GetCaveatExpression(), 676 ) 677 678 updatedExclusions = append(updatedExclusions, constructor( 679 concreteToRemove.GetSubjectId(), 680 exclusionConditionalExpression, 681 nil, 682 existingExclusion, 683 concreteToRemove), 684 ) 685 wasFound = true 686 } else { 687 updatedExclusions = append(updatedExclusions, existingExclusion) 688 } 689 } 690 691 if !wasFound { 692 updatedExclusions = append(updatedExclusions, concreteToRemove) 693 } 694 695 constructed := constructor( 696 tuple.PublicWildcard, 697 wildcard.GetCaveatExpression(), 698 updatedExclusions, 699 wildcard) 700 return &constructed 701 } 702 703 // intersectConcreteWithConcrete performs intersection between two concrete subjects, returning the 704 // resolved concrete subject, if any. 705 func intersectConcreteWithConcrete[T Subject[T]](first T, second *T, constructor constructor[T]) *T { 706 // Intersection of concrete subjects is a standard intersection operation, where subjects 707 // must be in both sets, with a combination of the two elements into one for conditionals. 708 // Otherwise, `and` together conditionals. 709 if second == nil { 710 return nil 711 } 712 713 secondConcrete := *second 714 constructed := constructor( 715 first.GetSubjectId(), 716 caveatAnd(first.GetCaveatExpression(), secondConcrete.GetCaveatExpression()), 717 nil, 718 first, 719 secondConcrete) 720 721 return &constructed 722 } 723 724 // intersectWildcardWithWildcard performs intersection between two wildcards, returning the resolved 725 // wildcard subject, if any. 726 func intersectWildcardWithWildcard[T Subject[T]](first *T, second *T, constructor constructor[T]) (*T, error) { 727 // If either wildcard does not exist, then no wildcard is placed into the resulting set. 728 if first == nil || second == nil { 729 return nil, nil 730 } 731 732 // If the other wildcard exists, then the intersection between the two wildcards is an && of 733 // their conditionals, and a *union* of their exclusions. 734 firstWildcard := *first 735 secondWildcard := *second 736 737 concreteExclusions := NewBaseSubjectSet(constructor) 738 for _, excludedSubject := range firstWildcard.GetExcludedSubjects() { 739 if excludedSubject.GetSubjectId() == tuple.PublicWildcard { 740 return nil, spiceerrors.MustBugf("wildcards are not allowed in exclusions") 741 } 742 743 err := concreteExclusions.Add(excludedSubject) 744 if err != nil { 745 return nil, err 746 } 747 } 748 749 for _, excludedSubject := range secondWildcard.GetExcludedSubjects() { 750 if excludedSubject.GetSubjectId() == tuple.PublicWildcard { 751 return nil, spiceerrors.MustBugf("wildcards are not allowed in exclusions") 752 } 753 754 err := concreteExclusions.Add(excludedSubject) 755 if err != nil { 756 return nil, err 757 } 758 } 759 760 constructed := constructor( 761 tuple.PublicWildcard, 762 caveatAnd(firstWildcard.GetCaveatExpression(), secondWildcard.GetCaveatExpression()), 763 concreteExclusions.AsSlice(), 764 firstWildcard, 765 secondWildcard) 766 return &constructed, nil 767 } 768 769 // intersectConcreteWithWildcard performs intersection between a concrete subject and a wildcard 770 // subject, returning the concrete, if any. 771 func intersectConcreteWithWildcard[T Subject[T]](concrete T, wildcard *T, constructor constructor[T]) (*T, error) { 772 // If no wildcard exists, then the concrete cannot exist (for this branch) 773 if wildcard == nil { 774 return nil, nil 775 } 776 777 wildcardToIntersect := *wildcard 778 exclusionsMap := exclusionsMapFor(wildcardToIntersect) 779 exclusion, isExcluded := exclusionsMap[concrete.GetSubjectId()] 780 781 // Cases: 782 // - The concrete subject is not excluded and the wildcard is not conditional => concrete is kept 783 // - The concrete subject is excluded and the wildcard is not conditional but the exclusion *is* conditional => concrete is made conditional 784 // - The concrete subject is excluded and the wildcard is not conditional => concrete is removed 785 // - The concrete subject is not excluded but the wildcard is conditional => concrete is kept, but made conditional 786 // - The concrete subject is excluded and the wildcard is conditional => concrete is removed, since it is always excluded 787 // - The concrete subject is excluded and the wildcard is conditional and the exclusion is conditional => combined conditional 788 switch { 789 case !isExcluded && wildcardToIntersect.GetCaveatExpression() == nil: 790 // If the concrete is not excluded and the wildcard conditional is empty, then the concrete is always found. 791 // Example: {user:tom} & {*} => {user:tom} 792 return &concrete, nil 793 794 case !isExcluded && wildcardToIntersect.GetCaveatExpression() != nil: 795 // The concrete subject is only included if the wildcard's caveat is true. 796 // Example: {user:tom}[acaveat] & {* - user:tom}[somecaveat] => {user:tom}[acaveat && somecaveat] 797 constructed := constructor( 798 concrete.GetSubjectId(), 799 caveatAnd(concrete.GetCaveatExpression(), wildcardToIntersect.GetCaveatExpression()), 800 nil, 801 concrete, 802 wildcardToIntersect) 803 return &constructed, nil 804 805 case isExcluded && exclusion.GetCaveatExpression() == nil: 806 // If the concrete is excluded and the exclusion is not conditional, then the concrete can never show up, 807 // regardless of whether the wildcard is conditional. 808 // Example: {user:tom} & {* - user:tom}[somecaveat] => {} 809 return nil, nil 810 811 case isExcluded && exclusion.GetCaveatExpression() != nil: 812 // NOTE: whether the wildcard is itself conditional or not is handled within the expression combinators below. 813 // The concrete subject is included if the wildcard's caveat is true and the exclusion's caveat is *false*. 814 // Example: {user:tom}[acaveat] & {* - user:tom[ecaveat]}[wcaveat] => {user:tom[acaveat && wcaveat && !ecaveat]} 815 constructed := constructor( 816 concrete.GetSubjectId(), 817 caveatAnd( 818 concrete.GetCaveatExpression(), 819 caveatAnd( 820 wildcardToIntersect.GetCaveatExpression(), 821 caveatInvert(exclusion.GetCaveatExpression()), 822 )), 823 nil, 824 concrete, 825 wildcardToIntersect, 826 exclusion) 827 return &constructed, nil 828 829 default: 830 return nil, spiceerrors.MustBugf("unhandled case in basesubjectset intersectConcreteWithWildcard: %v & %v", concrete, wildcardToIntersect) 831 } 832 } 833 834 type handle[T any] struct { 835 value *T 836 } 837 838 func newHandle[T any]() *handle[T] { 839 return &handle[T]{} 840 } 841 842 func (h *handle[T]) getOrNil() *T { 843 return h.value 844 } 845 846 func (h *handle[T]) setOrNil(value *T) { 847 h.value = value 848 } 849 850 func (h *handle[T]) get() (T, bool) { 851 if h.value != nil { 852 return *h.value, true 853 } 854 855 return *new(T), false 856 } 857 858 func (h *handle[T]) clear() { 859 h.value = nil 860 } 861 862 func (h *handle[T]) clone() *handle[T] { 863 return &handle[T]{ 864 value: h.value, 865 } 866 } 867 868 // exclusionsMapFor creates a map of all the exclusions on a wildcard, by subject ID. 869 func exclusionsMapFor[T Subject[T]](wildcard T) map[string]T { 870 exclusions := make(map[string]T, len(wildcard.GetExcludedSubjects())) 871 for _, excludedSubject := range wildcard.GetExcludedSubjects() { 872 exclusions[excludedSubject.GetSubjectId()] = excludedSubject 873 } 874 return exclusions 875 }