UniStore: Evaluate Folder DTO attributes (#93968)

* UniStore: Evaluate Folder DTO attributes

* Handle AccessControl

* Reduce the number of parameters to newToFolderDto

* Detach Metadata helpers from HTTPServer

* Add tests

---------

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>
This commit is contained in:
maicon
2024-10-07 12:08:16 +02:00
committed by GitHub
parent 5a7d6c104e
commit 261be0facd
8 changed files with 271 additions and 30 deletions
+146 -16
View File
@@ -1,12 +1,14 @@
package api
import (
"context"
"errors"
"net/http"
"strconv"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
@@ -28,6 +30,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errhttp"
"github.com/grafana/grafana/pkg/web"
@@ -448,7 +451,7 @@ func (hs *HTTPServer) getFolderACMetadata(c *contextmodel.ReqContext, f *folder.
folderIDs[p.UID] = true
}
allMetadata := hs.getMultiAccessControlMetadata(c, dashboards.ScopeFoldersPrefix, folderIDs)
allMetadata := getMultiAccessControlMetadata(c, dashboards.ScopeFoldersPrefix, folderIDs)
metadata := map[string]bool{}
// Flatten metadata - if any parent has a permission, the child folder inherits it
for _, md := range allMetadata {
@@ -629,6 +632,7 @@ type folderK8sHandler struct {
clientConfigProvider grafanaapiserver.DirectRestConfigProvider
// #TODO check if it makes more sense to move this to FolderAPIBuilder
accesscontrolService accesscontrol.Service
userService user.Service
}
//-----------------------------------------------------------------------------------------
@@ -641,6 +645,7 @@ func newFolderK8sHandler(hs *HTTPServer) *folderK8sHandler {
namespacer: request.GetNamespaceMapper(hs.Cfg),
clientConfigProvider: hs.clientConfigProvider,
accesscontrolService: hs.accesscontrolService,
userService: hs.userService,
}
}
@@ -692,13 +697,13 @@ func (fk8s *folderK8sHandler) createFolder(c *contextmodel.ReqContext) {
return
}
fk8s.accesscontrolService.ClearUserPermissionCache(c.SignedInUser)
f, err := internalfolders.UnstructuredToLegacyFolderDTO(*out)
folderDTO, err := fk8s.newToFolderDto(c, *out)
if err != nil {
fk8s.writeError(c, err)
return
}
c.JSON(http.StatusOK, f)
c.JSON(http.StatusOK, folderDTO)
}
// func (fk8s *folderK8sHandler) getFolder(c *contextmodel.ReqContext) {
@@ -713,13 +718,13 @@ func (fk8s *folderK8sHandler) createFolder(c *contextmodel.ReqContext) {
// return
// }
// f, err := internalfolders.UnstructuredToLegacyFolderDTO(*out)
// if err != nil {
// fk8s.writeError(c, err)
// return
// }
// folderDTO, err := fk8s.newToFolderDto(c, *out)
// if err != nil {
// fk8s.writeError(c, err)
// return
// }
// c.JSON(http.StatusOK, f)
// c.JSON(http.StatusOK, folderDTO)
// }
// func (fk8s *folderK8sHandler) deleteFolder(c *contextmodel.ReqContext) {
@@ -755,13 +760,13 @@ func (fk8s *folderK8sHandler) createFolder(c *contextmodel.ReqContext) {
// return
// }
// f, err := internalfolders.UnstructuredToLegacyFolderDTO(*out)
// if err != nil {
// fk8s.writeError(c, err)
// return
// }
// folderDTO, err := fk8s.newToFolderDto(c, *out)
// if err != nil {
// fk8s.writeError(c, err)
// return
// }
// c.JSON(http.StatusOK, f)
// c.JSON(http.StatusOK, folderDTO)
// }
//-----------------------------------------------------------------------------------------
@@ -786,3 +791,128 @@ func (fk8s *folderK8sHandler) writeError(c *contextmodel.ReqContext, err error)
}
errhttp.Write(c.Req.Context(), err, c.Resp)
}
func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item unstructured.Unstructured) (dtos.Folder, error) {
ctx := c.Req.Context()
f := internalfolders.UnstructuredToLegacyFolder(item)
fDTO, err := internalfolders.UnstructuredToLegacyFolderDTO(item)
if err != nil {
return dtos.Folder{}, err
}
toDTO := func(f *folder.Folder, checkCanView bool) (dtos.Folder, error) {
g, err := guardian.NewByFolder(c.Req.Context(), f, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return dtos.Folder{}, err
}
canEdit, _ := g.CanEdit()
canSave, _ := g.CanSave()
canAdmin, _ := g.CanAdmin()
canDelete, _ := g.CanDelete()
// Finding creator and last updater of the folder
updater, creator := anonString, anonString
if f.CreatedBy > 0 {
creator = fk8s.getUserLogin(ctx, f.CreatedBy)
}
if f.UpdatedBy > 0 {
updater = fk8s.getUserLogin(ctx, f.UpdatedBy)
}
acMetadata, _ := fk8s.getFolderACMetadata(c, f)
if checkCanView {
canView, _ := g.CanView()
if !canView {
return dtos.Folder{
UID: REDACTED,
Title: REDACTED,
}, nil
}
}
metrics.MFolderIDsAPICount.WithLabelValues(metrics.NewToFolderDTO).Inc()
fDTO.CanSave = canSave
fDTO.CanEdit = canEdit
fDTO.CanAdmin = canAdmin
fDTO.CanDelete = canDelete
fDTO.CreatedBy = creator
fDTO.UpdatedBy = updater
fDTO.AccessControl = acMetadata
return *fDTO, nil
}
// no need to check view permission for the starting folder since it's already checked by the callers
folderDTO, err := toDTO(f, false)
if err != nil {
return dtos.Folder{}, err
}
// TODO: handle parents
/*
parents, err := fk8s.folderService.GetParents(ctx, folder.GetParentsQuery{UID: f.UID, OrgID: f.OrgID})
if err != nil {
// log the error instead of failing
fk8s.log.Error("failed to fetch folder parents", "folder", f.UID, "org", f.OrgID, "error", err)
}
folderDTO.Parents = make([]dtos.Folder, 0, len(parents))
for _, f := range parents {
DTO, err := toDTO(f, true)
if err != nil {
// fk8s.log.Error("failed to convert folder to DTO", "folder", f.UID, "org", f.OrgID, "error", err)
continue
}
folderDTO.Parents = append(folderDTO.Parents, DTO)
}
*/
return folderDTO, nil
}
func (fk8s *folderK8sHandler) getUserLogin(ctx context.Context, userID int64) string {
ctx, span := tracer.Start(ctx, "api.getUserLogin")
defer span.End()
query := user.GetUserByIDQuery{ID: userID}
user, err := fk8s.userService.GetByID(ctx, &query)
if err != nil {
return anonString
}
return user.Login
}
func (fk8s *folderK8sHandler) getFolderACMetadata(c *contextmodel.ReqContext, f *folder.Folder) (accesscontrol.Metadata, error) {
if !c.QueryBool("accesscontrol") {
return nil, nil
}
folderIDs := map[string]bool{f.UID: true}
// TODO: handle parents
/*
parents, err := fk8s.folderService.GetParents(c.Req.Context(), folder.GetParentsQuery{UID: f.UID, OrgID: c.SignedInUser.GetOrgID()})
if err != nil {
return nil, err
}
folderIDs := map[string]bool{f.UID: true}
for _, p := range parents {
folderIDs[p.UID] = true
}
*/
allMetadata := getMultiAccessControlMetadata(c, dashboards.ScopeFoldersPrefix, folderIDs)
metadata := map[string]bool{}
// Flatten metadata - if any parent has a permission, the child folder inherits it
for _, md := range allMetadata {
for action := range md {
metadata[action] = true
}
}
return metadata, nil
}