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:
Ieva
2023-10-24 09:55:38 +01:00
committed by GitHub
parent bd2b4e956b
commit 159bb3c032
4 changed files with 84 additions and 11 deletions

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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,