mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Allow scoping access to root level dashboards (#76987)
* correctly check permissions to list dashboards on the root * correctly display the access inherited from general folder for dashboards * Update pkg/services/sqlstore/permissions/dashboard.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * Update dashboard_filter_no_subquery.go --------- Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
@@ -163,7 +163,7 @@ func ProvideDashboardPermissions(
|
||||
}
|
||||
return append([]string{parentScope}, nestedScopes...), nil
|
||||
}
|
||||
return []string{}, nil
|
||||
return []string{dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)}, nil
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@@ -229,6 +231,11 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
|
||||
}
|
||||
}
|
||||
builder.WriteString(") AND NOT dashboard.is_folder)")
|
||||
|
||||
// Include all the dashboards under the root if the user has the required permissions on the root (used to be the General folder)
|
||||
if hasAccessToRoot(toCheck, f.user) {
|
||||
builder.WriteString(" OR (dashboard.folder_id = 0 AND NOT dashboard.is_folder)")
|
||||
}
|
||||
} else {
|
||||
builder.WriteString("NOT dashboard.is_folder")
|
||||
}
|
||||
@@ -423,3 +430,14 @@ func getAllowedUIDs(actions []string, user identity.Requester, scopePrefix strin
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// Checks if the user has the required permissions on the root (used to be the General folder)
|
||||
func hasAccessToRoot(actionsToCheck []any, user identity.Requester) bool {
|
||||
generalFolderScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)
|
||||
for _, action := range actionsToCheck {
|
||||
if !slices.Contains(user.GetPermissions()[action.(string)], generalFolderScope) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -146,6 +146,11 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
|
||||
}
|
||||
builder.WriteString(" AND NOT dashboard.is_folder)")
|
||||
}
|
||||
|
||||
// Include all the dashboards under the root if the user has the required permissions on the root (used to be the General folder)
|
||||
if hasAccessToRoot(toCheck, f.user) {
|
||||
builder.WriteString(" OR (dashboard.folder_id = 0 AND NOT dashboard.is_folder)")
|
||||
}
|
||||
} else {
|
||||
builder.WriteString("NOT dashboard.is_folder")
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeDashboardsAll},
|
||||
},
|
||||
expectedResult: 100,
|
||||
expectedResult: 110,
|
||||
},
|
||||
{
|
||||
desc: "Should be able to view all dashboards with folder wildcard scope",
|
||||
@@ -60,7 +60,32 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
|
||||
},
|
||||
expectedResult: 100,
|
||||
expectedResult: 110,
|
||||
},
|
||||
{
|
||||
desc: "Should be able to view dashboards under the root with folders:uid:general scope",
|
||||
permission: dashboards.PERMISSION_VIEW,
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
|
||||
},
|
||||
expectedResult: 10,
|
||||
},
|
||||
{
|
||||
desc: "Should not be able to view editable dashboards under the root with folders:uid:general scope if missing write action",
|
||||
permission: dashboards.PERMISSION_EDIT,
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
|
||||
},
|
||||
expectedResult: 0,
|
||||
},
|
||||
{
|
||||
desc: "Should be able to view editable dashboards under the root with folders:uid:general scope if has write action",
|
||||
permission: dashboards.PERMISSION_EDIT,
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
|
||||
{Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
|
||||
},
|
||||
expectedResult: 10,
|
||||
},
|
||||
{
|
||||
desc: "Should be able to view a subset of dashboards with dashboard scopes",
|
||||
@@ -163,7 +188,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
store := setupTest(t, 10, 100, tt.permissions)
|
||||
store := setupTest(t, 10, 110, tt.permissions)
|
||||
recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -219,7 +244,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeDashboardsAll},
|
||||
},
|
||||
expectedResult: 100,
|
||||
expectedResult: 110,
|
||||
},
|
||||
{
|
||||
desc: "Should be able to view all dashboards with folder wildcard scope",
|
||||
@@ -227,7 +252,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
|
||||
},
|
||||
expectedResult: 100,
|
||||
expectedResult: 110,
|
||||
},
|
||||
{
|
||||
desc: "Should not be able to view any dashboards or folders without any permissions",
|
||||
@@ -258,6 +283,31 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
|
||||
},
|
||||
expectedResult: 20,
|
||||
},
|
||||
{
|
||||
desc: "Should be able to view dashboards under the root with folders:uid:general scope",
|
||||
permission: dashboards.PERMISSION_VIEW,
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
|
||||
},
|
||||
expectedResult: 10,
|
||||
},
|
||||
{
|
||||
desc: "Should not be able to view editable dashboards under the root with folders:uid:general scope if missing write action",
|
||||
permission: dashboards.PERMISSION_EDIT,
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
|
||||
},
|
||||
expectedResult: 0,
|
||||
},
|
||||
{
|
||||
desc: "Should be able to view editable dashboards under the root with folders:uid:general scope if has write action",
|
||||
permission: dashboards.PERMISSION_EDIT,
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
|
||||
{Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)},
|
||||
},
|
||||
expectedResult: 10,
|
||||
},
|
||||
{
|
||||
desc: "Should be able to view all folders with folder wildcard",
|
||||
permission: dashboards.PERMISSION_VIEW,
|
||||
@@ -337,7 +387,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
store := setupTest(t, 10, 100, []accesscontrol.Permission{})
|
||||
store := setupTest(t, 10, 110, []accesscontrol.Permission{})
|
||||
recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -717,12 +767,12 @@ func setupTest(t *testing.T, numFolders, numDashboards int, permissions []access
|
||||
Updated: time.Now(),
|
||||
})
|
||||
}
|
||||
// Seed 100 dashboard
|
||||
// Seed dashboards
|
||||
for i := numFolders + 1; i <= numFolders+numDashboards; i++ {
|
||||
str := strconv.Itoa(i)
|
||||
folderID := numFolders
|
||||
if i%numFolders != 0 {
|
||||
folderID = i % numFolders
|
||||
folderID := 0
|
||||
if i%(numFolders+1) != 0 {
|
||||
folderID = i % (numFolders + 1)
|
||||
}
|
||||
dashes = append(dashes, dashboards.Dashboard{
|
||||
OrgID: 1,
|
||||
|
||||
Reference in New Issue
Block a user