Files
grafana/pkg/services/authz/zanzana/server/server_mutate_folder.go
T
Alexander Zobnin 8a0fa93aec Zanzana: Fix duplicated writes in one request (#114900)
* Zanzana: Fix duplicated writes

* add tests
2025-12-05 13:55:56 +01:00

126 lines
3.7 KiB
Go

package server
import (
"context"
"fmt"
"strings"
openfgav1 "github.com/openfga/api/proto/openfga/v1"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
zanzana "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
)
func (s *Server) mutateFolders(ctx context.Context, store *storeInfo, operations []*authzextv1.MutateOperation) error {
ctx, span := s.tracer.Start(ctx, "server.mutateFolder")
defer span.End()
writeTuples := make([]*openfgav1.TupleKey, 0)
deleteTuples := make([]*openfgav1.TupleKeyWithoutCondition, 0)
for _, operation := range operations {
switch op := operation.Operation.(type) {
case *authzextv1.MutateOperation_SetFolderParent:
tuple, err := s.getFolderWriteTuple(ctx, store, op.SetFolderParent)
if err != nil {
return err
}
if tuple != nil {
writeTuples = append(writeTuples, tuple)
}
// Delete existing parent tuples
if op.SetFolderParent.GetDeleteExisting() {
tuples, err := s.getFolderDeleteTuples(ctx, store, op.SetFolderParent.GetFolder(), op.SetFolderParent.GetParent(), true)
if err != nil {
return err
}
deleteTuples = append(deleteTuples, tuples...)
}
case *authzextv1.MutateOperation_DeleteFolder:
tuples, err := s.getFolderDeleteTuples(ctx, store, op.DeleteFolder.GetFolder(), op.DeleteFolder.GetParent(), op.DeleteFolder.GetDeleteExisting())
if err != nil {
return err
}
deleteTuples = append(deleteTuples, tuples...)
default:
s.logger.Debug("unsupported mutate operation", "operation", op)
}
}
if len(writeTuples) == 0 && len(deleteTuples) == 0 {
return nil
}
err := s.writeTuples(ctx, store, writeTuples, deleteTuples)
if err != nil {
s.logger.Error("failed to write folder tuples", "error", err)
return err
}
return nil
}
func (s *Server) getFolderWriteTuple(ctx context.Context, store *storeInfo, req *authzextv1.SetFolderParentOperation) (*openfgav1.TupleKey, error) {
// Folder is at the root level
if req.GetParent() == "" {
return nil, nil
}
if strings.ContainsAny(req.GetFolder(), "#:") {
return nil, fmt.Errorf("folder UID contains invalid characters: %s", req.GetFolder())
}
tuple := zanzana.NewFolderParentTuple(req.GetFolder(), req.GetParent())
return tuple, nil
}
func (s *Server) getFolderDeleteTuples(ctx context.Context, store *storeInfo, folderUID string, parentUID string, deleteExisting bool) ([]*openfgav1.TupleKeyWithoutCondition, error) {
tupleKeysToDelete := make([]*openfgav1.TupleKeyWithoutCondition, 0)
if folderUID != "" && parentUID != "" && !deleteExisting {
tuple := zanzana.NewFolderParentTuple(folderUID, parentUID)
tupleKeysToDelete = append(tupleKeysToDelete, &openfgav1.TupleKeyWithoutCondition{
User: tuple.GetUser(),
Relation: tuple.GetRelation(),
Object: tuple.GetObject(),
})
}
if deleteExisting {
parentTuples, err := s.listFolderParents(ctx, store, folderUID)
if err != nil {
return nil, fmt.Errorf("failed to list folder parents: %w", err)
}
for _, tuple := range parentTuples {
tupleKeysToDelete = append(tupleKeysToDelete, &openfgav1.TupleKeyWithoutCondition{
User: tuple.Key.User,
Relation: tuple.Key.Relation,
Object: tuple.Key.Object,
})
}
}
return tupleKeysToDelete, nil
}
func (s *Server) listFolderParents(ctx context.Context, store *storeInfo, folderUID string) ([]*openfgav1.Tuple, error) {
ctx, span := s.tracer.Start(ctx, "server.listFolderParents")
defer span.End()
object := zanzana.NewFolderIdent(folderUID)
resp, err := s.openfga.Read(ctx, &openfgav1.ReadRequest{
StoreId: store.ID,
TupleKey: &openfgav1.ReadRequestTupleKey{
Object: object,
Relation: zanzana.RelationParent,
},
})
if err != nil {
return nil, fmt.Errorf("failed to list folder parents: %w", err)
}
return resp.Tuples, nil
}