Zanzana: Fix dashboard access evaluation in folders (#114718)
* Zanzana: Fix dashboard access evaluation in folders * add negative test * Fix listing
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana/common"
|
||||
)
|
||||
|
||||
@@ -148,6 +149,18 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation string, res
|
||||
folderRelation = common.SubresourceRelation(relation)
|
||||
)
|
||||
|
||||
if isFolderPermissionBasedResource(resource.GroupResource()) {
|
||||
// Check if resource inherits permissions from the folder (like dashboards in a folder)
|
||||
res, err := s.openfgaCheck(ctx, store, subject, relation, folderIdent, contextuals, resourceCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.GetAllowed() {
|
||||
return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if folderIdent != "" && common.IsSubresourceRelation(folderRelation) {
|
||||
// Check if subject has access as a sub resource for the folder
|
||||
res, err := s.openfgaCheck(ctx, store, subject, folderRelation, folderIdent, contextuals, resourceCtx)
|
||||
@@ -196,3 +209,12 @@ func (s *Server) openfgaCheck(ctx context.Context, store *storeInfo, subject, re
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
var folderPermissionBasedResources = map[string]bool{
|
||||
// dashboard.grafana.app/dashboards
|
||||
common.FormatGroupResource(dashboardV1.DashboardResourceInfo.GroupResource().Group, dashboardV1.DashboardResourceInfo.GroupResource().Resource, ""): true,
|
||||
}
|
||||
|
||||
func isFolderPermissionBasedResource(resource string) bool {
|
||||
return folderPermissionBasedResources[resource]
|
||||
}
|
||||
|
||||
@@ -184,4 +184,36 @@ func testCheck(t *testing.T, server *Server) {
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:17 should be able to view dashboards in folder 4 and all subfolders", func(t *testing.T) {
|
||||
// Check for folders
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, folderGroup, folderResource, "", "", "4"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, folderGroup, folderResource, "", "", "5"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, folderGroup, folderResource, "", "", "6"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
// Check for dashboards
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, dashboardGroup, dashboardResource, "", "4", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed(), "user should be able to view dashboards in folder 4")
|
||||
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, dashboardGroup, dashboardResource, "", "5", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed(), "user should be able to view dashboards in folder 5")
|
||||
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, dashboardGroup, dashboardResource, "", "6", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed(), "user should be able to view dashboards in folder 6")
|
||||
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, "foo.grafana.app", "bar", "", "4", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed(), "user should not be able to view other resources in folder 4")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -153,6 +153,25 @@ func (s *Server) listGeneric(ctx context.Context, subject, relation string, reso
|
||||
folders = res.GetObjects()
|
||||
}
|
||||
|
||||
// Special case for folder permission based resources (like dashboards in a folder)
|
||||
if isFolderPermissionBasedResource(resource.GroupResource()) {
|
||||
res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: store.ID,
|
||||
AuthorizationModelId: store.ModelID,
|
||||
Type: common.TypeFolder,
|
||||
Relation: relation,
|
||||
User: subject,
|
||||
Context: resourceCtx,
|
||||
ContextualTuples: contextuals,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
folders = append(folders, res.GetObjects()...)
|
||||
}
|
||||
|
||||
// 2. List all resource directly assigned to subject
|
||||
var objects []string
|
||||
if resource.IsValidRelation(relation) {
|
||||
|
||||
@@ -155,4 +155,15 @@ func testList(t *testing.T, server *Server) {
|
||||
|
||||
assert.Contains(t, res.GetItems(), "1")
|
||||
})
|
||||
|
||||
t.Run("user:17 should be able to list all dashboards in folder 4 and all subfolders", func(t *testing.T) {
|
||||
res, err := server.List(newContextWithNamespace(), newList("user:17", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 3)
|
||||
|
||||
assert.Contains(t, res.GetFolders(), "4")
|
||||
assert.Contains(t, res.GetFolders(), "5")
|
||||
assert.Contains(t, res.GetFolders(), "6")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ func setup(t *testing.T, srv *Server) *Server {
|
||||
common.NewFolderResourceTuple("user:5", common.RelationSetEdit, dashboardGroup, dashboardResource, "", "1"),
|
||||
common.NewFolderTuple("user:6", common.RelationGet, "1"),
|
||||
common.NewGroupResourceTuple("user:7", common.RelationGet, folderGroup, folderResource, ""),
|
||||
// folder-4 -> folder-5 -> folder-6
|
||||
common.NewFolderParentTuple("5", "4"),
|
||||
common.NewFolderParentTuple("6", "5"),
|
||||
common.NewFolderResourceTuple("user:8", common.RelationSetEdit, dashboardGroup, dashboardResource, "", "5"),
|
||||
@@ -69,6 +70,7 @@ func setup(t *testing.T, srv *Server) *Server {
|
||||
common.NewTypedResourceTuple("user:14", common.RelationGet, common.TypeTeam, teamGroup, teamResource, statusSubresource, "1"),
|
||||
common.NewTypedResourceTuple("user:15", common.RelationGet, common.TypeUser, userGroup, userResource, statusSubresource, "1"),
|
||||
common.NewTypedResourceTuple("user:16", common.RelationGet, common.TypeServiceAccount, serviceAccountGroup, serviceAccountResource, statusSubresource, "1"),
|
||||
common.NewFolderTuple("user:17", common.RelationSetView, "4"),
|
||||
}
|
||||
|
||||
return setupOpenFGADatabase(t, srv, tuples)
|
||||
|
||||
Reference in New Issue
Block a user