github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/typesystem/typesystem.go (about) 1 package typesystem 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/authzed/spicedb/pkg/datastore" 8 "github.com/authzed/spicedb/pkg/genutil/mapz" 9 "github.com/authzed/spicedb/pkg/graph" 10 nspkg "github.com/authzed/spicedb/pkg/namespace" 11 core "github.com/authzed/spicedb/pkg/proto/core/v1" 12 iv1 "github.com/authzed/spicedb/pkg/proto/impl/v1" 13 "github.com/authzed/spicedb/pkg/spiceerrors" 14 "github.com/authzed/spicedb/pkg/tuple" 15 ) 16 17 // AllowedDirectRelation indicates whether a relation is allowed on the right side of another relation. 18 type AllowedDirectRelation int 19 20 const ( 21 // UnknownIfRelationAllowed indicates that no type information is defined for 22 // this relation. 23 UnknownIfRelationAllowed AllowedDirectRelation = iota 24 25 // DirectRelationValid indicates that the specified subject relation is valid as 26 // part of a *direct* tuple on the relation. 27 DirectRelationValid 28 29 // DirectRelationNotValid indicates that the specified subject relation is not 30 // valid as part of a *direct* tuple on the relation. 31 DirectRelationNotValid 32 ) 33 34 // AllowedPublicSubject indicates whether a public subject of a particular kind is allowed on the right side of another relation. 35 type AllowedPublicSubject int 36 37 const ( 38 // UnknownIfPublicAllowed indicates that no type information is defined for 39 // this relation. 40 UnknownIfPublicAllowed AllowedPublicSubject = iota 41 42 // PublicSubjectAllowed indicates that the specified subject wildcard is valid as 43 // part of a *direct* tuple on the relation. 44 PublicSubjectAllowed 45 46 // PublicSubjectNotAllowed indicates that the specified subject wildcard is not 47 // valid as part of a *direct* tuple on the relation. 48 PublicSubjectNotAllowed 49 ) 50 51 // AllowedRelationOption indicates whether an allowed relation of a particular kind is allowed on the right side of another relation. 52 type AllowedRelationOption int 53 54 const ( 55 // UnknownIfAllowed indicates that no type information is defined for 56 // this relation. 57 UnknownIfAllowed AllowedRelationOption = iota 58 59 // AllowedRelationValid indicates that the specified subject relation is valid. 60 AllowedRelationValid 61 62 // AllowedRelationNotValid indicates that the specified subject relation is not valid. 63 AllowedRelationNotValid 64 ) 65 66 // AllowedNamespaceOption indicates whether an allowed namespace of a particular kind is allowed on the right side of another relation. 67 type AllowedNamespaceOption int 68 69 const ( 70 // UnknownIfAllowedNamespace indicates that no type information is defined for 71 // this relation. 72 UnknownIfAllowedNamespace AllowedNamespaceOption = iota 73 74 // AllowedNamespaceValid indicates that the specified subject namespace is valid. 75 AllowedNamespaceValid 76 77 // AllowedNamespaceNotValid indicates that the specified subject namespace is not valid. 78 AllowedNamespaceNotValid 79 ) 80 81 // NewNamespaceTypeSystem returns a new type system for the given namespace. Note that the type 82 // system is not validated until Validate is called. 83 func NewNamespaceTypeSystem(nsDef *core.NamespaceDefinition, resolver Resolver) (*TypeSystem, error) { 84 relationMap := make(map[string]*core.Relation, len(nsDef.GetRelation())) 85 for _, relation := range nsDef.GetRelation() { 86 _, existing := relationMap[relation.Name] 87 if existing { 88 return nil, NewTypeErrorWithSource( 89 NewDuplicateRelationError(nsDef.Name, relation.Name), 90 relation, 91 relation.Name, 92 ) 93 } 94 95 relationMap[relation.Name] = relation 96 } 97 98 return &TypeSystem{ 99 resolver: resolver, 100 nsDef: nsDef, 101 relationMap: relationMap, 102 wildcardCheckCache: nil, 103 }, nil 104 } 105 106 // TypeSystem represents typing information found in a namespace. 107 type TypeSystem struct { 108 resolver Resolver 109 nsDef *core.NamespaceDefinition 110 relationMap map[string]*core.Relation 111 wildcardCheckCache map[string]*WildcardTypeReference 112 } 113 114 // Namespace is the namespace for which the type system was constructed. 115 func (nts *TypeSystem) Namespace() *core.NamespaceDefinition { 116 return nts.nsDef 117 } 118 119 // HasTypeInformation returns true if the relation with the given name exists and has type 120 // information defined. 121 func (nts *TypeSystem) HasTypeInformation(relationName string) bool { 122 rel, ok := nts.relationMap[relationName] 123 return ok && rel.GetTypeInformation() != nil 124 } 125 126 // HasRelation returns true if the namespace has the given relation defined. 127 func (nts *TypeSystem) HasRelation(relationName string) bool { 128 _, ok := nts.relationMap[relationName] 129 return ok 130 } 131 132 // GetRelation returns the relation that's defined with the give name in the type system or returns false. 133 func (nts *TypeSystem) GetRelation(relationName string) (*core.Relation, bool) { 134 rel, ok := nts.relationMap[relationName] 135 return rel, ok 136 } 137 138 // MustGetRelation returns the relation that's defined with the give name in the type system or panics. 139 func (nts *TypeSystem) MustGetRelation(relationName string) *core.Relation { 140 rel, ok := nts.relationMap[relationName] 141 if !ok { 142 panic("Missing relation") 143 } 144 return rel 145 } 146 147 // GetRelationOrError returns the relation with the givne name defined on the namespace, or RelationNotFoundErr if 148 // not found. 149 func (nts *TypeSystem) GetRelationOrError(relationName string) (*core.Relation, error) { 150 relation, ok := nts.relationMap[relationName] 151 if !ok { 152 return nil, NewRelationNotFoundErr(nts.nsDef.Name, relationName) 153 } 154 return relation, nil 155 } 156 157 // IsPermission returns true if the namespace has the given relation defined and it is 158 // a permission. 159 func (nts *TypeSystem) IsPermission(relationName string) bool { 160 found, ok := nts.relationMap[relationName] 161 if !ok { 162 return false 163 } 164 165 return nspkg.GetRelationKind(found) == iv1.RelationMetadata_PERMISSION 166 } 167 168 // GetAllowedDirectNamespaceSubjectRelations returns the subject relations for the target namespace, if it is defined as appearing 169 // somewhere on the right side of a relation (except public). Returns nil if there is no type information or it is not allowed. 170 func (nts *TypeSystem) GetAllowedDirectNamespaceSubjectRelations(sourceRelationName string, targetNamespaceName string) (*mapz.Set[string], error) { 171 found, ok := nts.relationMap[sourceRelationName] 172 if !ok { 173 return nil, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName)) 174 } 175 176 typeInfo := found.GetTypeInformation() 177 if typeInfo == nil { 178 return nil, nil 179 } 180 181 allowedRelations := typeInfo.GetAllowedDirectRelations() 182 allowedSubjectRelations := mapz.NewSet[string]() 183 for _, allowedRelation := range allowedRelations { 184 if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetPublicWildcard() == nil { 185 allowedSubjectRelations.Add(allowedRelation.GetRelation()) 186 } 187 } 188 189 return allowedSubjectRelations, nil 190 } 191 192 // IsAllowedDirectNamespace returns whether the target namespace is defined as appearing somewhere on the 193 // right side of a relation (except public). 194 func (nts *TypeSystem) IsAllowedDirectNamespace(sourceRelationName string, targetNamespaceName string) (AllowedNamespaceOption, error) { 195 found, ok := nts.relationMap[sourceRelationName] 196 if !ok { 197 return UnknownIfAllowedNamespace, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName)) 198 } 199 200 typeInfo := found.GetTypeInformation() 201 if typeInfo == nil { 202 return UnknownIfAllowedNamespace, nil 203 } 204 205 allowedRelations := typeInfo.GetAllowedDirectRelations() 206 for _, allowedRelation := range allowedRelations { 207 if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetPublicWildcard() == nil { 208 return AllowedNamespaceValid, nil 209 } 210 } 211 212 return AllowedNamespaceNotValid, nil 213 } 214 215 // IsAllowedPublicNamespace returns whether the target namespace is defined as public on the source relation. 216 func (nts *TypeSystem) IsAllowedPublicNamespace(sourceRelationName string, targetNamespaceName string) (AllowedPublicSubject, error) { 217 found, ok := nts.relationMap[sourceRelationName] 218 if !ok { 219 return UnknownIfPublicAllowed, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName)) 220 } 221 222 typeInfo := found.GetTypeInformation() 223 if typeInfo == nil { 224 return UnknownIfPublicAllowed, nil 225 } 226 227 allowedRelations := typeInfo.GetAllowedDirectRelations() 228 for _, allowedRelation := range allowedRelations { 229 if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetPublicWildcard() != nil { 230 return PublicSubjectAllowed, nil 231 } 232 } 233 234 return PublicSubjectNotAllowed, nil 235 } 236 237 // IsAllowedDirectRelation returns whether the subject relation is allowed to appear on the right 238 // hand side of a tuple placed in the source relation with the given name. 239 func (nts *TypeSystem) IsAllowedDirectRelation(sourceRelationName string, targetNamespaceName string, targetRelationName string) (AllowedDirectRelation, error) { 240 found, ok := nts.relationMap[sourceRelationName] 241 if !ok { 242 return UnknownIfRelationAllowed, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName)) 243 } 244 245 typeInfo := found.GetTypeInformation() 246 if typeInfo == nil { 247 return UnknownIfRelationAllowed, nil 248 } 249 250 allowedRelations := typeInfo.GetAllowedDirectRelations() 251 for _, allowedRelation := range allowedRelations { 252 if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetRelation() == targetRelationName { 253 return DirectRelationValid, nil 254 } 255 } 256 257 return DirectRelationNotValid, nil 258 } 259 260 // HasAllowedRelation returns whether the source relation has the given allowed relation. 261 func (nts *TypeSystem) HasAllowedRelation(sourceRelationName string, toCheck *core.AllowedRelation) (AllowedRelationOption, error) { 262 found, ok := nts.relationMap[sourceRelationName] 263 if !ok { 264 return UnknownIfAllowed, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName)) 265 } 266 267 typeInfo := found.GetTypeInformation() 268 if typeInfo == nil { 269 return UnknownIfAllowed, nil 270 } 271 272 allowedRelations := typeInfo.GetAllowedDirectRelations() 273 for _, allowedRelation := range allowedRelations { 274 if SourceForAllowedRelation(allowedRelation) == SourceForAllowedRelation(toCheck) { 275 return AllowedRelationValid, nil 276 } 277 } 278 279 return AllowedRelationNotValid, nil 280 } 281 282 // AllowedDirectRelationsAndWildcards returns the allowed subject relations for a source relation. Note that this function will return 283 // wildcards. 284 func (nts *TypeSystem) AllowedDirectRelationsAndWildcards(sourceRelationName string) ([]*core.AllowedRelation, error) { 285 found, ok := nts.relationMap[sourceRelationName] 286 if !ok { 287 return []*core.AllowedRelation{}, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName)) 288 } 289 290 typeInfo := found.GetTypeInformation() 291 if typeInfo == nil { 292 return []*core.AllowedRelation{}, nil 293 } 294 295 return typeInfo.GetAllowedDirectRelations(), nil 296 } 297 298 // AllowedSubjectRelations returns the allowed subject relations for a source relation. Note that this function will *not* 299 // return wildcards. 300 func (nts *TypeSystem) AllowedSubjectRelations(sourceRelationName string) ([]*core.RelationReference, error) { 301 allowedDirect, err := nts.AllowedDirectRelationsAndWildcards(sourceRelationName) 302 if err != nil { 303 return []*core.RelationReference{}, asTypeError(err) 304 } 305 306 filtered := make([]*core.RelationReference, 0, len(allowedDirect)) 307 for _, allowed := range allowedDirect { 308 if allowed.GetPublicWildcard() != nil { 309 continue 310 } 311 312 if allowed.GetRelation() == "" { 313 return nil, spiceerrors.MustBugf("got an empty relation for a non-wildcard type definition under namespace") 314 } 315 316 filtered = append(filtered, &core.RelationReference{ 317 Namespace: allowed.GetNamespace(), 318 Relation: allowed.GetRelation(), 319 }) 320 } 321 return filtered, nil 322 } 323 324 // HasIndirectSubjects returns true if and only if there exists at least one non-ellipsis (i.e. indirect) subject 325 // allowed on the specified relation. 326 func (nts *TypeSystem) HasIndirectSubjects(sourceRelationName string) (bool, error) { 327 allowedRelations, err := nts.AllowedDirectRelationsAndWildcards(sourceRelationName) 328 if err != nil { 329 return false, asTypeError(err) 330 } 331 332 for _, allowedRelation := range allowedRelations { 333 if allowedRelation.GetPublicWildcard() != nil { 334 continue 335 } 336 337 if allowedRelation.GetRelation() != tuple.Ellipsis { 338 return true, nil 339 } 340 } 341 342 return false, nil 343 } 344 345 // WildcardTypeReference represents a relation that references a wildcard type. 346 type WildcardTypeReference struct { 347 // ReferencingRelation is the relation referencing the wildcard type. 348 ReferencingRelation *core.RelationReference 349 350 // WildcardType is the wildcard type referenced. 351 WildcardType *core.AllowedRelation 352 } 353 354 // referencesWildcardType returns true if the relation references a wildcard type, either directly or via 355 // another relation. 356 func (nts *TypeSystem) referencesWildcardType(ctx context.Context, relationName string) (*WildcardTypeReference, error) { 357 return nts.referencesWildcardTypeWithEncountered(ctx, relationName, map[string]bool{}) 358 } 359 360 func (nts *TypeSystem) referencesWildcardTypeWithEncountered(ctx context.Context, relationName string, encountered map[string]bool) (*WildcardTypeReference, error) { 361 if nts.wildcardCheckCache == nil { 362 nts.wildcardCheckCache = make(map[string]*WildcardTypeReference, 1) 363 } 364 365 cached, isCached := nts.wildcardCheckCache[relationName] 366 if isCached { 367 return cached, nil 368 } 369 370 computed, err := nts.computeReferencesWildcardType(ctx, relationName, encountered) 371 if err != nil { 372 return nil, err 373 } 374 375 nts.wildcardCheckCache[relationName] = computed 376 return computed, nil 377 } 378 379 func (nts *TypeSystem) computeReferencesWildcardType(ctx context.Context, relationName string, encountered map[string]bool) (*WildcardTypeReference, error) { 380 relString := tuple.JoinRelRef(nts.nsDef.Name, relationName) 381 if _, ok := encountered[relString]; ok { 382 return nil, nil 383 } 384 encountered[relString] = true 385 386 allowedRels, err := nts.AllowedDirectRelationsAndWildcards(relationName) 387 if err != nil { 388 return nil, asTypeError(err) 389 } 390 391 for _, allowedRelation := range allowedRels { 392 if allowedRelation.GetPublicWildcard() != nil { 393 return &WildcardTypeReference{ 394 ReferencingRelation: &core.RelationReference{ 395 Namespace: nts.nsDef.Name, 396 Relation: relationName, 397 }, 398 WildcardType: allowedRelation, 399 }, nil 400 } 401 402 if allowedRelation.GetRelation() != tuple.Ellipsis { 403 if allowedRelation.GetNamespace() == nts.nsDef.Name { 404 found, err := nts.referencesWildcardTypeWithEncountered(ctx, allowedRelation.GetRelation(), encountered) 405 if err != nil { 406 return nil, asTypeError(err) 407 } 408 409 if found != nil { 410 return found, nil 411 } 412 continue 413 } 414 415 subjectTS, err := nts.TypeSystemForNamespace(ctx, allowedRelation.GetNamespace()) 416 if err != nil { 417 return nil, asTypeError(err) 418 } 419 420 found, err := subjectTS.referencesWildcardTypeWithEncountered(ctx, allowedRelation.GetRelation(), encountered) 421 if err != nil { 422 return nil, asTypeError(err) 423 } 424 425 if found != nil { 426 return found, nil 427 } 428 } 429 } 430 431 return nil, nil 432 } 433 434 // Validate runs validation on the type system for the namespace to ensure it is consistent. 435 func (nts *TypeSystem) Validate(ctx context.Context) (*ValidatedNamespaceTypeSystem, error) { 436 for _, relation := range nts.relationMap { 437 relation := relation 438 439 // Validate the usersets's. 440 usersetRewrite := relation.GetUsersetRewrite() 441 rerr, err := graph.WalkRewrite(usersetRewrite, func(childOneof *core.SetOperation_Child) interface{} { 442 switch child := childOneof.ChildType.(type) { 443 case *core.SetOperation_Child_ComputedUserset: 444 relationName := child.ComputedUserset.GetRelation() 445 _, ok := nts.relationMap[relationName] 446 if !ok { 447 return NewTypeErrorWithSource( 448 NewRelationNotFoundErr(nts.nsDef.Name, relationName), 449 childOneof, 450 relationName, 451 ) 452 } 453 case *core.SetOperation_Child_TupleToUserset: 454 ttu := child.TupleToUserset 455 if ttu == nil { 456 return nil 457 } 458 459 tupleset := ttu.GetTupleset() 460 if tupleset == nil { 461 return nil 462 } 463 464 relationName := tupleset.GetRelation() 465 found, ok := nts.relationMap[relationName] 466 if !ok { 467 return NewTypeErrorWithSource( 468 NewRelationNotFoundErr(nts.nsDef.Name, relationName), 469 childOneof, 470 relationName, 471 ) 472 } 473 474 if nspkg.GetRelationKind(found) == iv1.RelationMetadata_PERMISSION { 475 return NewTypeErrorWithSource( 476 NewPermissionUsedOnLeftOfArrowErr(nts.nsDef.Name, relation.Name, relationName), 477 childOneof, relationName) 478 } 479 480 // Ensure the tupleset relation doesn't itself import wildcard. 481 referencedWildcard, err := nts.referencesWildcardType(ctx, relationName) 482 if err != nil { 483 return err 484 } 485 486 if referencedWildcard != nil { 487 return NewTypeErrorWithSource( 488 NewWildcardUsedInArrowErr( 489 nts.nsDef.Name, 490 relation.Name, 491 relationName, 492 referencedWildcard.WildcardType.GetNamespace(), 493 tuple.StringRR(referencedWildcard.ReferencingRelation), 494 ), 495 childOneof, relationName, 496 ) 497 } 498 } 499 return nil 500 }) 501 if rerr != nil { 502 return nil, asTypeError(rerr.(error)) 503 } 504 if err != nil { 505 return nil, err 506 } 507 508 // Validate type information. 509 typeInfo := relation.TypeInformation 510 if typeInfo == nil { 511 continue 512 } 513 514 allowedDirectRelations := typeInfo.GetAllowedDirectRelations() 515 516 // Check for a _this or the lack of a userset_rewrite. If either is found, 517 // then the allowed list must have at least one type. 518 hasThis, err := graph.HasThis(usersetRewrite) 519 if err != nil { 520 return nil, err 521 } 522 523 if usersetRewrite == nil || hasThis { 524 if len(allowedDirectRelations) == 0 { 525 return nil, NewTypeErrorWithSource( 526 NewMissingAllowedRelationsErr(nts.nsDef.Name, relation.Name), 527 relation, relation.Name, 528 ) 529 } 530 } else { 531 if len(allowedDirectRelations) != 0 { 532 // NOTE: This is a legacy error and should never really occur with schema. 533 return nil, NewTypeErrorWithSource( 534 fmt.Errorf("direct relations are not allowed under relation `%s`", relation.Name), 535 relation, relation.Name) 536 } 537 } 538 539 // Allowed relations verification: 540 // 1) that all allowed relations are not this very relation 541 // 2) that they exist within the referenced namespace 542 // 3) that they are not duplicated in any way 543 // 4) that if they have a caveat reference, the caveat is valid 544 encountered := mapz.NewSet[string]() 545 546 for _, allowedRelation := range allowedDirectRelations { 547 source := SourceForAllowedRelation(allowedRelation) 548 if !encountered.Add(source) { 549 return nil, NewTypeErrorWithSource( 550 NewDuplicateAllowedRelationErr(nts.nsDef.Name, relation.Name, source), 551 allowedRelation, 552 source, 553 ) 554 } 555 556 // Check the namespace. 557 if allowedRelation.GetNamespace() == nts.nsDef.Name { 558 if allowedRelation.GetPublicWildcard() == nil && allowedRelation.GetRelation() != tuple.Ellipsis { 559 _, ok := nts.relationMap[allowedRelation.GetRelation()] 560 if !ok { 561 return nil, NewTypeErrorWithSource( 562 NewRelationNotFoundErr(allowedRelation.GetNamespace(), allowedRelation.GetRelation()), 563 allowedRelation, 564 allowedRelation.GetRelation(), 565 ) 566 } 567 } 568 } else { 569 subjectTS, err := nts.TypeSystemForNamespace(ctx, allowedRelation.GetNamespace()) 570 if err != nil { 571 return nil, NewTypeErrorWithSource( 572 fmt.Errorf("could not lookup definition `%s` for relation `%s`: %w", allowedRelation.GetNamespace(), relation.Name, err), 573 allowedRelation, 574 allowedRelation.GetNamespace(), 575 ) 576 } 577 578 // Check for relations. 579 if allowedRelation.GetPublicWildcard() == nil && allowedRelation.GetRelation() != tuple.Ellipsis { 580 // Ensure the relation exists. 581 ok := subjectTS.HasRelation(allowedRelation.GetRelation()) 582 if !ok { 583 return nil, NewTypeErrorWithSource( 584 NewRelationNotFoundErr(allowedRelation.GetNamespace(), allowedRelation.GetRelation()), 585 allowedRelation, 586 allowedRelation.GetRelation(), 587 ) 588 } 589 590 // Ensure the relation doesn't itself import wildcard. 591 referencedWildcard, err := subjectTS.referencesWildcardType(ctx, allowedRelation.GetRelation()) 592 if err != nil { 593 return nil, err 594 } 595 596 if referencedWildcard != nil { 597 return nil, NewTypeErrorWithSource( 598 NewTransitiveWildcardErr( 599 nts.nsDef.Name, 600 relation.GetName(), 601 allowedRelation.Namespace, 602 allowedRelation.GetRelation(), 603 referencedWildcard.WildcardType.GetNamespace(), 604 tuple.StringRR(referencedWildcard.ReferencingRelation), 605 ), 606 allowedRelation, 607 tuple.JoinRelRef(allowedRelation.GetNamespace(), allowedRelation.GetRelation()), 608 ) 609 } 610 } 611 } 612 613 // Check the caveat, if any. 614 if allowedRelation.GetRequiredCaveat() != nil { 615 _, err := nts.resolver.LookupCaveat(ctx, allowedRelation.GetRequiredCaveat().CaveatName) 616 if err != nil { 617 return nil, NewTypeErrorWithSource( 618 fmt.Errorf("could not lookup caveat `%s` for relation `%s`: %w", allowedRelation.GetRequiredCaveat().CaveatName, relation.Name, err), 619 allowedRelation, 620 source, 621 ) 622 } 623 } 624 } 625 } 626 627 return &ValidatedNamespaceTypeSystem{nts}, nil 628 } 629 630 // SourceForAllowedRelation returns the source code representation of an allowed relation. 631 func SourceForAllowedRelation(allowedRelation *core.AllowedRelation) string { 632 caveatStr := "" 633 634 if allowedRelation.GetRequiredCaveat() != nil { 635 caveatStr = " with " + allowedRelation.RequiredCaveat.CaveatName 636 } 637 638 if allowedRelation.GetPublicWildcard() != nil { 639 return tuple.JoinObjectRef(allowedRelation.Namespace, "*") + caveatStr 640 } 641 642 if rel := allowedRelation.GetRelation(); rel != tuple.Ellipsis { 643 return tuple.JoinRelRef(allowedRelation.Namespace, rel) + caveatStr 644 } 645 646 return allowedRelation.Namespace + caveatStr 647 } 648 649 // TypeSystemForNamespace returns a type system for the given namespace. 650 func (nts *TypeSystem) TypeSystemForNamespace(ctx context.Context, namespaceName string) (*TypeSystem, error) { 651 if nts.nsDef.Name == namespaceName { 652 return nts, nil 653 } 654 655 nsDef, err := nts.resolver.LookupNamespace(ctx, namespaceName) 656 if err != nil { 657 return nil, err 658 } 659 660 return NewNamespaceTypeSystem(nsDef, nts.resolver) 661 } 662 663 // RelationDoesNotAllowCaveatsForSubject returns true if and only if it can be conclusively determined that 664 // the given subject type does not accept any caveats on the given relation. If the relation does not have type information, 665 // returns an error. 666 func (nts *TypeSystem) RelationDoesNotAllowCaveatsForSubject(relationName string, subjectTypeName string) (bool, error) { 667 relation, ok := nts.relationMap[relationName] 668 if !ok { 669 return false, NewRelationNotFoundErr(nts.nsDef.Name, relationName) 670 } 671 672 typeInfo := relation.GetTypeInformation() 673 if typeInfo == nil { 674 return false, NewTypeErrorWithSource( 675 fmt.Errorf("relation `%s` does not have type information", relationName), 676 relation, relationName, 677 ) 678 } 679 680 foundSubjectType := false 681 for _, allowedRelation := range typeInfo.GetAllowedDirectRelations() { 682 if allowedRelation.GetNamespace() == subjectTypeName { 683 foundSubjectType = true 684 if allowedRelation.GetRequiredCaveat() != nil && allowedRelation.GetRequiredCaveat().CaveatName != "" { 685 return false, nil 686 } 687 } 688 } 689 690 if !foundSubjectType { 691 return false, NewTypeErrorWithSource( 692 fmt.Errorf("relation `%s` does not allow subject type `%s`", relationName, subjectTypeName), 693 relation, relationName, 694 ) 695 } 696 697 return true, nil 698 } 699 700 // ValidatedNamespaceTypeSystem is validated type system for a namespace. 701 type ValidatedNamespaceTypeSystem struct { 702 *TypeSystem 703 } 704 705 // NewTypeErrorWithSource creates a new type error at the specific position and with source code, wrapping the underlying 706 // error. 707 func NewTypeErrorWithSource(wrapped error, withSource nspkg.WithSourcePosition, sourceCodeString string) error { 708 sourcePosition := withSource.GetSourcePosition() 709 if sourcePosition != nil { 710 return asTypeError(spiceerrors.NewErrorWithSource( 711 wrapped, 712 sourceCodeString, 713 sourcePosition.ZeroIndexedLineNumber+1, // +1 to make 1-indexed 714 sourcePosition.ZeroIndexedColumnPosition+1, // +1 to make 1-indexed 715 )) 716 } 717 718 return asTypeError(spiceerrors.NewErrorWithSource( 719 wrapped, 720 sourceCodeString, 721 0, 722 0, 723 )) 724 } 725 726 // ReadNamespaceAndTypes reads a namespace definition, version, and type system and returns it if found. 727 func ReadNamespaceAndTypes( 728 ctx context.Context, 729 nsName string, 730 ds datastore.Reader, 731 ) (*core.NamespaceDefinition, *ValidatedNamespaceTypeSystem, error) { 732 nsDef, _, err := ds.ReadNamespaceByName(ctx, nsName) 733 if err != nil { 734 return nil, nil, err 735 } 736 737 ts, terr := NewNamespaceTypeSystem(nsDef, ResolverForDatastoreReader(ds)) 738 if terr != nil { 739 return nil, nil, terr 740 } 741 742 // NOTE: since the type system was read from the datastore, it must have been validated 743 // on the way in, so it is safe for us to return it as a validated type system. 744 return nsDef, &ValidatedNamespaceTypeSystem{ts}, nil 745 }