CVE-2022-39342 (Go, published 2022-10-25T17:15Z)
Description: 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.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: NVD-CWE-Other \u2014 Other
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.
Repository: https://github.com/openfga/openfga
Files changed:
pkg/typesystem/typesystem.go
server/test/write_authzmodel.go
Commit message: Merge pull request from GHSA-f4mm-2r69-mg5f



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"),
			),