CVE-2022-39342: Other (NVD-CWE-Other)
value: OpenFGA is an authorization/permission engine. Versions prior to version 0.2.4 are vulnerable to authorization bypass under certain conditions. Users whose model has a relation defined as a tupleset (the right hand side of a ‘from’ statement) that involves anything other than a direct relationship (e.g. ‘as self’) are vulnerable. Version 0.2.4 contains a patch for this issue.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 9.8
Published: 2022-10-25T17:15Z
Repository: https://github.com/openfga/openfga
Language: Go
Files changed: pkg/typesystem/typesystem.go, server/test/write_authzmodel.go
Commit date: 2022-10-21 10:18:07 -0600
Commit message: Merge pull request from GHSA-f4mm-2r69-mg5f


Diff: diff --git a/pkg/typesystem/typesystem.go b/pkg/typesystem/typesystem.go
index 70ff53f..e9d569b 100644
--- a/pkg/typesystem/typesystem.go
+++ b/pkg/typesystem/typesystem.go
@@ -1,9 +1,10 @@
 package typesystem
 
 import (
 	"fmt"
+	"reflect"
 
 	"github.com/go-errors/errors"
 	openfgapb "go.buf.build/openfga/go/openfga/api/openfga/v1"
 )
 
@@ -231,25 +232,45 @@ func containsDuplicateType(model *openfgapb.AuthorizationModel) bool {
 }
 
 func validateRelationRewrites(model *openfgapb.AuthorizationModel) error {
 	typeDefinitions := model.GetTypeDefinitions()
 
-	allRelations := map[string]struct{}{}
-	typeToRelations := map[string]map[string]struct{}{}
+	relations := map[string]*openfgapb.Relation{}
+	typerels := map[string]map[string]*openfgapb.Relation{}
+
 	for _, td := range typeDefinitions {
 		objectType := td.GetType()
-		typeToRelations[objectType] = map[string]struct{}{}
-		for relation := range td.GetRelations() {
-			typeToRelations[objectType][relation] = struct{}{}
-			allRelations[relation] = struct{}{}
+
+		typerels[objectType] = map[string]*openfgapb.Relation{}
+
+		for relation, rewrite := range td.GetRelations() {
+			relationMetadata := td.GetMetadata().GetRelations()
+			md, ok := relationMetadata[relation]
+
+			var typeinfo *openfgapb.RelationTypeInfo
+			if ok {
+				typeinfo = &openfgapb.RelationTypeInfo{
+					DirectlyRelatedUserTypes: md.GetDirectlyRelatedUserTypes(),
+				}
+			}
+
+			r := &openfgapb.Relation{
+				Name:     relation,
+				Rewrite:  rewrite,
+				TypeInfo: typeinfo,
+			}
+
+			typerels[objectType][relation] = r
+			relations[relation] = r
 		}
 	}
 
 	for _, td := range typeDefinitions {
 		objectType := td.GetType()
+
 		for relation, rewrite := range td.GetRelations() {
-			err := isUsersetRewriteValid(allRelations, typeToRelations[objectType], objectType, relation, rewrite)
+			err := isUsersetRewriteValid(relations, typerels[objectType], objectType, relation, rewrite)
 			if err != nil {
 				return err
 			}
 		}
 	}
@@ -257,11 +278,16 @@ func validateRelationRewrites(model *openfgapb.AuthorizationModel) error {
 	return nil
 }
 
 // isUsersetRewriteValid checks if a particular userset rewrite is valid. The first argument is all the relations in
 // the typeSystem, the second argument is the subset of relations on the type where the rewrite occurs.
-func isUsersetRewriteValid(allRelations map[string]struct{}, relationsOnType map[string]struct{}, objectType, relation string, rewrite *openfgapb.Userset) error {
+func isUsersetRewriteValid(
+	allRelations map[string]*openfgapb.Relation,
+	relationsOnType map[string]*openfgapb.Relation,
+	objectType, relation string,
+	rewrite *openfgapb.Userset,
+) error {
 	if rewrite.GetUserset() == nil {
 		return InvalidRelationError(objectType, relation)
 	}
 
 	switch t := rewrite.GetUserset().(type) {
@@ -273,14 +299,23 @@ func isUsersetRewriteValid(allRelations map[string]struct{}, relationsOnType map
 		if _, ok := relationsOnType[computedUserset]; !ok {
 			return RelationDoesNotExistError(objectType, computedUserset)
 		}
 	case *openfgapb.Userset_TupleToUserset:
 		tupleset := t.TupleToUserset.GetTupleset().GetRelation()
-		if _, ok := relationsOnType[tupleset]; !ok {
+
+		tuplesetRelation, ok := relationsOnType[tupleset]
+		if !ok {
 			return RelationDoesNotExistError(objectType, tupleset)
 		}
 
+		// tupleset relations must only be direct relationships, no rewrites
+		// are allowed on them
+		tuplesetRewrite := tuplesetRelation.GetRewrite()
+		if reflect.TypeOf(tuplesetRewrite.GetUserset()) != reflect.TypeOf(&openfgapb.Userset_This{}) {
+			return errors.Errorf("the '%s#%s' relation is referenced in at least one tupleset and thus must be a direct relation", objectType, tupleset)
+		}
+
 		computedUserset := t.TupleToUserset.GetComputedUserset().GetRelation()
 		if _, ok := allRelations[computedUserset]; !ok {
 			return RelationDoesNotExistError("", computedUserset)
 		}
 	case *openfgapb.Userset_Union:
diff --git a/server/test/write_authzmodel.go b/server/test/write_authzmodel.go
index 0eee8d3..11ad7bd 100644
--- a/server/test/write_authzmodel.go
+++ b/server/test/write_authzmodel.go
@@ -3,15 +3,16 @@ package test
 import (
 	"context"
 	"fmt"
 	"testing"
 
+	"github.com/go-errors/errors"
 	"github.com/openfga/openfga/pkg/id"
 	"github.com/openfga/openfga/pkg/logger"
 	"github.com/openfga/openfga/pkg/typesystem"
 	"github.com/openfga/openfga/server/commands"
-	"github.com/openfga/openfga/server/errors"
+	serverErrors "github.com/openfga/openfga/server/errors"
 	"github.com/openfga/openfga/storage"
 	"github.com/stretchr/testify/require"
 	openfgapb "go.buf.build/openfga/go/openfga/api/openfga/v1"
 )
 
@@ -107,11 +108,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 			name: "fails if too many types",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId:         storeID,
 				TypeDefinitions: items,
 			},
-			err: errors.ExceededEntityLimit("type definitions in an authorization model", datastore.MaxTypesInTypeDefinition()),
+			err: serverErrors.ExceededEntityLimit("type definitions in an authorization model", datastore.MaxTypesInTypeDefinition()),
 		},
 		{
 			name: "empty relations is valid",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				TypeDefinitions: []*openfgapb.TypeDefinition{
@@ -149,11 +150,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							"admin": {Userset: &openfgapb.Userset_This{}},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.ErrDuplicateTypes),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.ErrDuplicateTypes),
 		},
 		{
 			name: "ExecuteWriteFailsIfEmptyRewrites",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -164,11 +165,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							"owner": {},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "owner")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "owner")),
 		},
 		{
 			name: "ExecuteWriteFailsIfUnknownRelationInComputedUserset",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -186,11 +187,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
 		},
 		{
 			name: "ExecuteWriteFailsIfUnknownRelationInTupleToUserset",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -213,11 +214,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")),
 		},
 		{
 			name: "ExecuteWriteFailsIfUnknownRelationInUnion",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -248,11 +249,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
 		},
 		{
 			name: "ExecuteWriteFailsIfUnknownRelationInDifferenceBaseArgument",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -283,11 +284,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
 		},
 		{
 			name: "ExecuteWriteFailsIfUnknownRelationInDifferenceSubtractArgument",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -318,11 +319,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
 		},
 		{
 			name: "ExecuteWriteFailsIfUnknownRelationInTupleToUsersetTupleset",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -347,11 +348,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
 		},
 		{
 			name: "ExecuteWriteFailsIfUnknownRelationInTupleToUsersetComputedUserset",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -376,11 +377,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")),
 		},
 		{
 			name: "ExecuteWriteFailsIfTupleToUsersetReferencesUnknownRelation",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -412,11 +413,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("bar", "writer")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("bar", "writer")),
 		},
 		{
 			name: "ExecuteWriteFailsIfUnknownRelationInIntersection",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -445,11 +446,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
 		},
 		{
 			name: "ExecuteWriteFailsIfDifferenceIncludesSameRelationTwice",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -475,11 +476,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
 		},
 		{
 			name: "ExecuteWriteFailsIfUnionIncludesSameRelationTwice",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -502,11 +503,11 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
 		},
 		{
 			name: "ExecuteWriteFailsIfIntersectionIncludesSameRelationTwice",
 			request: &openfgapb.WriteAuthorizationModelRequest{
 				StoreId: storeID,
@@ -528,11 +529,175 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor
 							},
 						},
 					},
 				},
 			},
-			err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
+			err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
+		},
+		{
+			name: "Union Rewrite Contains Repeated Definitions",
+			request: &openfgapb.WriteAuthorizationModelRequest{
+				StoreId: storeID,
+				TypeDefinitions: []*openfgapb.TypeDefinition{
+					{
+						Type: "document",
+						Relations: map[string]*openfgapb.Userset{
+							"parent": typesystem.This(),
+							"viewer": typesystem.Union(
+								typesystem.ComputedUserset("editor"),
+								typesystem.ComputedUserset("editor"),
+							),
+							"editor": typesystem.Union(typesystem.This(), typesystem.This()),
+							"manage": typesystem.Union(
+								typesystem.TupleToUserset("parent", "manage"),
+								typesystem.TupleToUserset("parent", "manage"),
+							),
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Intersection Rewrite Contains Repeated Definitions",
+			request: &openfgapb.WriteAuthorizationModelRequest{
+				StoreId: storeID,
+				TypeDefinitions: []*openfgapb.TypeDefinition{
+					{
+						Type: "document",
+						Relations: map[string]*openfgapb.Userset{
+							"parent": typesystem.This(),
+							"viewer": typesystem.Intersection(
+								typesystem.ComputedUserset("editor"),
+								typesystem.ComputedUserset("editor"),
+							),
+							"editor": typesystem.Intersection(typesystem.This(), typesystem.This()),
+							"manage": typesystem.Intersection(
+								typesystem.TupleToUserset("parent", "manage"),
+								typesystem.TupleToUserset("parent", "manage"),
+							),
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Exclusion Rewrite Contains Repeated Definitions",
+			request: &openfgapb.WriteAuthorizationModelRequest{
+				StoreId: storeID,
+				TypeDefinitions: []*openfgapb.TypeDefinition{
+					{
+						Type: "document",
+						Relations: map[string]*openfgapb.Userset{
+							"parent": typesystem.This(),
+							"viewer": typesystem.Difference(
+								typesystem.ComputedUserset("editor"),
+								typesystem.ComputedUserset("editor"),
+							),
+							"editor": typesystem.Difference(typesystem.This(), typesystem.This()),
+							"manage": typesystem.Difference(
+								typesystem.TupleToUserset("parent", "manage"),
+								typesystem.TupleToUserset("parent", "manage"),
+							),
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Tupleset relation involves ComputedUserset rewrite",
+			request: &openfgapb.WriteAuthorizationModelRequest{
+				StoreId: storeID,
+				TypeDefinitions: []*openfgapb.TypeDefinition{
+					{
+						Type: "document",
+						Relations: map[string]*openfgapb.Userset{
+							"ancestor": typesystem.This(),
+							"parent":   typesystem.ComputedUserset("ancestor"),
+							"viewer":   typesystem.TupleToUserset("parent", "viewer"),
+						},
+					},
+				},
+			},
+			err: serverErrors.InvalidAuthorizationModelInput(
+				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
+			),
+		},
+		{
+			name: "Tupleset relation involves Union rewrite",
+			request: &openfgapb.WriteAuthorizationModelRequest{
+				StoreId: storeID,
+				TypeDefinitions: []*openfgapb.TypeDefinition{
+					{
+						Type: "document",
+						Relations: map[string]*openfgapb.Userset{
+							"ancestor": typesystem.This(),
+							"parent":   typesystem.Union(typesystem.This(), typesystem.ComputedUserset("ancestor")),
+							"viewer":   typesystem.TupleToUserset("parent", "viewer"),
+						},
+					},
+				},
+			},
+			err: serverErrors.InvalidAuthorizationModelInput(
+				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
+			),
+		},
+		{
+			name: "Tupleset relation involves Intersection rewrite",
+			request: &openfgapb.WriteAuthorizationModelRequest{
+				StoreId: storeID,
+				TypeDefinitions: []*openfgapb.TypeDefinition{
+					{
+						Type: "document",
+						Relations: map[string]*openfgapb.Userset{
+							"ancestor": typesystem.This(),
+							"parent":   typesystem.Intersection(typesystem.This(), typesystem.ComputedUserset("ancestor")),
+							"viewer":   typesystem.TupleToUserset("parent", "viewer"),
+						},
+					},
+				},
+			},
+			err: serverErrors.InvalidAuthorizationModelInput(
+				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
+			),
+		},
+		{
+			name: "Tupleset relation involves Exclusion rewrite",
+			request: &openfgapb.WriteAuthorizationModelRequest{
+				StoreId: storeID,
+				TypeDefinitions: []*openfgapb.TypeDefinition{
+					{
+						Type: "document",
+						Relations: map[string]*openfgapb.Userset{
+							"restricted": typesystem.This(),
+							"parent":     typesystem.Difference(typesystem.This(), typesystem.ComputedUserset("restricted")),
+							"viewer":     typesystem.TupleToUserset("parent", "viewer"),
+						},
+					},
+				},
+			},
+			err: serverErrors.InvalidAuthorizationModelInput(
+				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
+			),
+		},
+		{
+			name: "Tupleset relation involves TupleToUserset rewrite",
+			request: &openfgapb.WriteAuthorizationModelRequest{
+				StoreId: storeID,
+				TypeDefinitions: []*openfgapb.TypeDefinition{
+					{
+						Type: "document",
+						Relations: map[string]*openfgapb.Userset{
+							"ancestor": typesystem.This(),
+							"parent":   typesystem.TupleToUserset("ancestor", "viewer"),
+							"viewer":   typesystem.TupleToUserset("parent", "viewer"),
+						},
+					},
+				},
+			},
+			err: serverErrors.InvalidAuthorizationModelInput(
+				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
+			),
 		},
 	}
 
 	ctx := context.Background()
 	logger := logger.NewNoopLogger()

Vulnerable code: 	allRelations := map[string]struct{}{}
	typeToRelations := map[string]map[string]struct{}{}
		typeToRelations[objectType] = map[string]struct{}{}
		for relation := range td.GetRelations() {
			typeToRelations[objectType][relation] = struct{}{}
			allRelations[relation] = struct{}{}
			err := isUsersetRewriteValid(allRelations, typeToRelations[objectType], objectType, relation, rewrite)
func isUsersetRewriteValid(allRelations map[string]struct{}, relationsOnType map[string]struct{}, objectType, relation string, rewrite *openfgapb.Userset) error {
		if _, ok := relationsOnType[tupleset]; !ok {
	"github.com/openfga/openfga/server/errors"
			err: errors.ExceededEntityLimit("type definitions in an authorization model", datastore.MaxTypesInTypeDefinition()),
			err: errors.InvalidAuthorizationModelInput(typesystem.ErrDuplicateTypes),
			err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "owner")),
			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")),
			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")),
			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("bar", "writer")),
			err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
			err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
			err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
Fixed code: 	"reflect"
	relations := map[string]*openfgapb.Relation{}
	typerels := map[string]map[string]*openfgapb.Relation{}


		typerels[objectType] = map[string]*openfgapb.Relation{}

		for relation, rewrite := range td.GetRelations() {
			relationMetadata := td.GetMetadata().GetRelations()
			md, ok := relationMetadata[relation]

			var typeinfo *openfgapb.RelationTypeInfo
			if ok {
				typeinfo = &openfgapb.RelationTypeInfo{
					DirectlyRelatedUserTypes: md.GetDirectlyRelatedUserTypes(),
				}
			}

			r := &openfgapb.Relation{
				Name:     relation,
				Rewrite:  rewrite,
				TypeInfo: typeinfo,
			}

			typerels[objectType][relation] = r
			relations[relation] = r

			err := isUsersetRewriteValid(relations, typerels[objectType], objectType, relation, rewrite)
func isUsersetRewriteValid(
	allRelations map[string]*openfgapb.Relation,
	relationsOnType map[string]*openfgapb.Relation,
	objectType, relation string,
	rewrite *openfgapb.Userset,
) error {

		tuplesetRelation, ok := relationsOnType[tupleset]
		if !ok {
		// tupleset relations must only be direct relationships, no rewrites
		// are allowed on them
		tuplesetRewrite := tuplesetRelation.GetRewrite()
		if reflect.TypeOf(tuplesetRewrite.GetUserset()) != reflect.TypeOf(&openfgapb.Userset_This{}) {
			return errors.Errorf("the '%s#%s' relation is referenced in at least one tupleset and thus must be a direct relation", objectType, tupleset)
		}

	"github.com/go-errors/errors"
	serverErrors "github.com/openfga/openfga/server/errors"
			err: serverErrors.ExceededEntityLimit("type definitions in an authorization model", datastore.MaxTypesInTypeDefinition()),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.ErrDuplicateTypes),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "owner")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("bar", "writer")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
			err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")),
		},
		{
			name: "Union Rewrite Contains Repeated Definitions",
			request: &openfgapb.WriteAuthorizationModelRequest{
				StoreId: storeID,
				TypeDefinitions: []*openfgapb.TypeDefinition{
					{
						Type: "document",
						Relations: map[string]*openfgapb.Userset{
							"parent": typesystem.This(),
							"viewer": typesystem.Union(
								typesystem.ComputedUserset("editor"),
								typesystem.ComputedUserset("editor"),
							),
							"editor": typesystem.Union(typesystem.This(), typesystem.This()),
							"manage": typesystem.Union(
								typesystem.TupleToUserset("parent", "manage"),
								typesystem.TupleToUserset("parent", "manage"),
							),
						},
					},
				},
			},
		},
		{
			name: "Intersection Rewrite Contains Repeated Definitions",
			request: &openfgapb.WriteAuthorizationModelRequest{
				StoreId: storeID,
				TypeDefinitions: []*openfgapb.TypeDefinition{
					{
						Type: "document",
						Relations: map[string]*openfgapb.Userset{
							"parent": typesystem.This(),
							"viewer": typesystem.Intersection(
								typesystem.ComputedUserset("editor"),
								typesystem.ComputedUserset("editor"),
							),
							"editor": typesystem.Intersection(typesystem.This(), typesystem.This()),
							"manage": typesystem.Intersection(
								typesystem.TupleToUserset("parent", "manage"),
								typesystem.TupleToUserset("parent", "manage"),
							),
						},
					},
				},
			},
		},
		{
			name: "Exclusion Rewrite Contains Repeated Definitions",
			request: &openfgapb.WriteAuthorizationModelRequest{
				StoreId: storeID,
				TypeDefinitions: []*openfgapb.TypeDefinition{
					{
						Type: "document",
						Relations: map[string]*openfgapb.Userset{
							"parent": typesystem.This(),
							"viewer": typesystem.Difference(
								typesystem.ComputedUserset("editor"),
								typesystem.ComputedUserset("editor"),
							),
							"editor": typesystem.Difference(typesystem.This(), typesystem.This()),
							"manage": typesystem.Difference(
								typesystem.TupleToUserset("parent", "manage"),
								typesystem.TupleToUserset("parent", "manage"),
							),
						},
					},
				},
			},
		},
		{
			name: "Tupleset relation involves ComputedUserset rewrite",
			request: &openfgapb.WriteAuthorizationModelRequest{
				StoreId: storeID,
				TypeDefinitions: []*openfgapb.TypeDefinition{
					{
						Type: "document",
						Relations: map[string]*openfgapb.Userset{
							"ancestor": typesystem.This(),
							"parent":   typesystem.ComputedUserset("ancestor"),
							"viewer":   typesystem.TupleToUserset("parent", "viewer"),
						},
					},
				},
			},
			err: serverErrors.InvalidAuthorizationModelInput(
				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
			),
		},
		{
			name: "Tupleset relation involves Union rewrite",
			request: &openfgapb.WriteAuthorizationModelRequest{
				StoreId: storeID,
				TypeDefinitions: []*openfgapb.TypeDefinition{
					{
						Type: "document",
						Relations: map[string]*openfgapb.Userset{
							"ancestor": typesystem.This(),
							"parent":   typesystem.Union(typesystem.This(), typesystem.ComputedUserset("ancestor")),
							"viewer":   typesystem.TupleToUserset("parent", "viewer"),
						},
					},
				},
			},
			err: serverErrors.InvalidAuthorizationModelInput(
				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
			),
		},
		{
			name: "Tupleset relation involves Intersection rewrite",
			request: &openfgapb.WriteAuthorizationModelRequest{
				StoreId: storeID,
				TypeDefinitions: []*openfgapb.TypeDefinition{
					{
						Type: "document",
						Relations: map[string]*openfgapb.Userset{
							"ancestor": typesystem.This(),
							"parent":   typesystem.Intersection(typesystem.This(), typesystem.ComputedUserset("ancestor")),
							"viewer":   typesystem.TupleToUserset("parent", "viewer"),
						},
					},
				},
			},
			err: serverErrors.InvalidAuthorizationModelInput(
				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
			),
		},
		{
			name: "Tupleset relation involves Exclusion rewrite",
			request: &openfgapb.WriteAuthorizationModelRequest{
				StoreId: storeID,
				TypeDefinitions: []*openfgapb.TypeDefinition{
					{
						Type: "document",
						Relations: map[string]*openfgapb.Userset{
							"restricted": typesystem.This(),
							"parent":     typesystem.Difference(typesystem.This(), typesystem.ComputedUserset("restricted")),
							"viewer":     typesystem.TupleToUserset("parent", "viewer"),
						},
					},
				},
			},
			err: serverErrors.InvalidAuthorizationModelInput(
				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
			),
		},
		{
			name: "Tupleset relation involves TupleToUserset rewrite",
			request: &openfgapb.WriteAuthorizationModelRequest{
				StoreId: storeID,
				TypeDefinitions: []*openfgapb.TypeDefinition{
					{
						Type: "document",
						Relations: map[string]*openfgapb.Userset{
							"ancestor": typesystem.This(),
							"parent":   typesystem.TupleToUserset("ancestor", "viewer"),
							"viewer":   typesystem.TupleToUserset("parent", "viewer"),
						},
					},
				},
			},
			err: serverErrors.InvalidAuthorizationModelInput(
				errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"),
			),