RBAC: Include action sets in dashboard and folder permission filter (#89133)

take action sets into account in dashboard and folder permission filter
This commit is contained in:
Ieva 2024-06-13 19:40:47 +03:00 committed by GitHub
parent 627d77c365
commit 3853f90528
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 258 additions and 93 deletions

View File

@ -2,6 +2,7 @@ package permissions
import (
"bytes"
"context"
"fmt"
"slices"
"strings"
@ -25,10 +26,12 @@ type clause struct {
}
type accessControlDashboardPermissionFilter struct {
user identity.Requester
dashboardAction string
folderAction string
features featuremgmt.FeatureToggles
user identity.Requester
dashboardAction string
dashboardActionSets []string
folderAction string
folderActionSets []string
features featuremgmt.FeatureToggles
where clause
// any recursive CTE queries (if supported)
@ -53,30 +56,63 @@ func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissi
var folderAction string
var dashboardAction string
var folderActionSets []string
var dashboardActionSets []string
if queryType == searchstore.TypeFolder {
folderAction = dashboards.ActionFoldersRead
//folderAction = append(folderAction, dashboards.ActionFoldersRead)
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
}
if needEdit {
folderAction = dashboards.ActionDashboardsCreate
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:edit", "folders:admin"}
}
}
} else if queryType == searchstore.TypeDashboard {
dashboardAction = dashboards.ActionDashboardsRead
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"}
}
if needEdit {
dashboardAction = dashboards.ActionDashboardsWrite
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:edit", "folders:admin"}
dashboardActionSets = []string{"dashboards:edit", "dashboards:admin"}
}
}
} else if queryType == searchstore.TypeAlertFolder {
folderAction = accesscontrol.ActionAlertingRuleRead
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
}
if needEdit {
folderAction = accesscontrol.ActionAlertingRuleCreate
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:edit", "folders:admin"}
}
}
} else if queryType == searchstore.TypeAnnotation {
dashboardAction = accesscontrol.ActionAnnotationsRead
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"}
}
} else {
folderAction = dashboards.ActionFoldersRead
dashboardAction = dashboards.ActionDashboardsRead
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"}
}
if needEdit {
folderAction = dashboards.ActionDashboardsCreate
dashboardAction = dashboards.ActionDashboardsWrite
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:edit", "folders:admin"}
dashboardActionSets = []string{"dashboards:edit", "dashboards:admin"}
}
}
}
@ -84,13 +120,13 @@ func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissi
if features.IsEnabledGlobally(featuremgmt.FlagPermissionsFilterRemoveSubquery) {
f = &accessControlDashboardPermissionFilterNoFolderSubquery{
accessControlDashboardPermissionFilter: accessControlDashboardPermissionFilter{
user: user, folderAction: folderAction, dashboardAction: dashboardAction, features: features,
recursiveQueriesAreSupported: recursiveQueriesAreSupported,
user: user, folderAction: folderAction, folderActionSets: folderActionSets, dashboardAction: dashboardAction, dashboardActionSets: dashboardActionSets,
features: features, recursiveQueriesAreSupported: recursiveQueriesAreSupported,
},
}
} else {
f = &accessControlDashboardPermissionFilter{user: user, folderAction: folderAction, dashboardAction: dashboardAction, features: features,
recursiveQueriesAreSupported: recursiveQueriesAreSupported,
f = &accessControlDashboardPermissionFilter{user: user, folderAction: folderAction, folderActionSets: folderActionSets, dashboardAction: dashboardAction, dashboardActionSets: dashboardActionSets,
features: features, recursiveQueriesAreSupported: recursiveQueriesAreSupported,
}
}
f.buildClauses()
@ -149,20 +185,24 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
// currently it's used for the extended JWT module (when the user is authenticated via a JWT token generated by Grafana)
useSelfContainedPermissions := f.user.IsAuthenticatedBy(login.ExtendedJWTModule)
if len(f.dashboardAction) > 0 {
toCheck := actionsToCheck(f.dashboardAction, f.user.GetPermissions(), dashWildcards, folderWildcards)
if f.dashboardAction != "" {
toCheckDashboards := actionsToCheck(f.dashboardAction, f.dashboardActionSets, f.user.GetPermissions(), dashWildcards, folderWildcards)
toCheckFolders := actionsToCheck(f.dashboardAction, f.folderActionSets, f.user.GetPermissions(), dashWildcards, folderWildcards)
if len(toCheck) > 0 {
if len(toCheckDashboards) > 0 {
if !useSelfContainedPermissions {
builder.WriteString("(dashboard.uid IN (SELECT identifier FROM permission WHERE kind = 'dashboards' AND attribute = 'uid'")
builder.WriteString(rolesFilter)
args = append(args, params...)
builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)")
args = append(args, toCheck[0])
if len(toCheckDashboards) == 1 {
builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)")
args = append(args, toCheckDashboards[0])
} else {
builder.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheckDashboards)-1) + ")) AND NOT dashboard.is_folder)")
args = append(args, toCheckDashboards...)
}
} else {
actions := parseStringSliceFromInterfaceSlice(toCheck)
args = getAllowedUIDs(actions, f.user, dashboards.ScopeDashboardsPrefix)
args = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeDashboardsPrefix)
// Only add the IN clause if we have any dashboards to check
if len(args) > 0 {
@ -179,12 +219,15 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'")
permSelector.WriteString(rolesFilter)
permSelectorArgs = append(permSelectorArgs, params...)
permSelector.WriteString(" AND action = ?")
permSelectorArgs = append(permSelectorArgs, toCheck[0])
if len(toCheckDashboards) == 1 {
permSelector.WriteString(" AND action = ?")
permSelectorArgs = append(permSelectorArgs, toCheckDashboards[0])
} else {
permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheckDashboards)-1) + ")")
permSelectorArgs = append(permSelectorArgs, toCheckFolders...)
}
} else {
actions := parseStringSliceFromInterfaceSlice(toCheck)
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
permSelectorArgs = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeFoldersPrefix)
// Only add the IN clause if we have any folders to check
if len(permSelectorArgs) > 0 {
@ -229,7 +272,7 @@ 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) {
if hasAccessToRoot(f.dashboardAction, f.user) {
builder.WriteString(" OR (dashboard.folder_id = 0 AND NOT dashboard.is_folder)")
}
} else {
@ -241,23 +284,27 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
permSelector.Reset()
permSelectorArgs = permSelectorArgs[:0]
if len(f.folderAction) > 0 {
if len(f.dashboardAction) > 0 {
if f.folderAction != "" {
if f.dashboardAction != "" {
builder.WriteString(" OR ")
}
toCheck := actionsToCheck(f.folderAction, f.user.GetPermissions(), folderWildcards)
toCheck := actionsToCheck(f.folderAction, f.folderActionSets, f.user.GetPermissions(), folderWildcards)
if len(toCheck) > 0 {
if !useSelfContainedPermissions {
permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'")
permSelector.WriteString(rolesFilter)
permSelectorArgs = append(permSelectorArgs, params...)
permSelector.WriteString(" AND action = ?")
permSelectorArgs = append(permSelectorArgs, toCheck[0])
if len(toCheck) == 1 {
permSelector.WriteString(" AND action = ?")
permSelectorArgs = append(permSelectorArgs, toCheck[0])
} else {
permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ")")
permSelectorArgs = append(permSelectorArgs, toCheck...)
}
} else {
actions := parseStringSliceFromInterfaceSlice(toCheck)
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
permSelectorArgs = getAllowedUIDs(f.folderAction, f.user, dashboards.ScopeFoldersPrefix)
if len(permSelectorArgs) > 0 {
permSelector.WriteString("(?" + strings.Repeat(", ?", len(permSelectorArgs)-1) + "")
@ -342,7 +389,7 @@ func (f *accessControlDashboardPermissionFilter) addRecQry(queryName string, whe
})
}
func actionsToCheck(action string, permissions map[string][]string, wildcards ...accesscontrol.Wildcards) []any {
func actionsToCheck(action string, actionSets []string, permissions map[string][]string, wildcards ...accesscontrol.Wildcards) []any {
for _, scope := range permissions[action] {
for _, w := range wildcards {
if w.Contains(scope) {
@ -351,7 +398,12 @@ func actionsToCheck(action string, permissions map[string][]string, wildcards ..
}
}
return []any{action}
toCheck := []any{action}
for _, a := range actionSets {
toCheck = append(toCheck, a)
}
return toCheck
}
func (f *accessControlDashboardPermissionFilter) nestedFoldersSelectors(permSelector string, permSelectorArgs []any, leftTable string, leftCol string, rightTableCol string, orgID int64) (string, []any) {
@ -382,46 +434,21 @@ func (f *accessControlDashboardPermissionFilter) nestedFoldersSelectors(permSele
return strings.Join(wheres, ") OR "), args
}
func parseStringSliceFromInterfaceSlice(slice []any) []string {
result := make([]string, 0, len(slice))
for _, s := range slice {
result = append(result, s.(string))
}
return result
}
func getAllowedUIDs(actions []string, user identity.Requester, scopePrefix string) []any {
uidToActions := make(map[string]map[string]struct{})
for _, action := range actions {
for _, uidScope := range user.GetPermissions()[action] {
if !strings.HasPrefix(uidScope, scopePrefix) {
continue
}
uid := strings.TrimPrefix(uidScope, scopePrefix)
if _, exists := uidToActions[uid]; !exists {
uidToActions[uid] = make(map[string]struct{})
}
uidToActions[uid][action] = struct{}{}
func getAllowedUIDs(action string, user identity.Requester, scopePrefix string) []any {
var args []any
for _, uidScope := range user.GetPermissions()[action] {
if !strings.HasPrefix(uidScope, scopePrefix) {
continue
}
uid := strings.TrimPrefix(uidScope, scopePrefix)
args = append(args, uid)
}
// args max capacity is the length of the different uids
args := make([]any, 0, len(uidToActions))
for uid, assignedActions := range uidToActions {
if len(assignedActions) == len(actions) {
args = append(args, uid)
}
}
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 {
func hasAccessToRoot(actionToCheck string, 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
return slices.Contains(user.GetPermissions()[actionToCheck], generalFolderScope)
}

View File

@ -53,20 +53,24 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
// currently it's used for the extended JWT module (when the user is authenticated via a JWT token generated by Grafana)
useSelfContainedPermissions := f.user.GetAuthenticatedBy() == login.ExtendedJWTModule
if len(f.dashboardAction) > 0 {
toCheck := actionsToCheck(f.dashboardAction, f.user.GetPermissions(), dashWildcards, folderWildcards)
if f.dashboardAction != "" {
toCheckDashboards := actionsToCheck(f.dashboardAction, f.dashboardActionSets, f.user.GetPermissions(), dashWildcards, folderWildcards)
toCheckFolders := actionsToCheck(f.dashboardAction, f.folderActionSets, f.user.GetPermissions(), dashWildcards, folderWildcards)
if len(toCheck) > 0 {
if len(toCheckDashboards) > 0 {
if !useSelfContainedPermissions {
builder.WriteString("(dashboard.uid IN (SELECT identifier FROM permission WHERE kind = 'dashboards' AND attribute = 'uid'")
builder.WriteString(rolesFilter)
args = append(args, params...)
builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)")
args = append(args, toCheck[0])
if len(toCheckDashboards) == 1 {
builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)")
args = append(args, toCheckDashboards[0])
} else {
builder.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheckDashboards)-1) + ")) AND NOT dashboard.is_folder)")
args = append(args, toCheckDashboards...)
}
} else {
actions := parseStringSliceFromInterfaceSlice(toCheck)
args = getAllowedUIDs(actions, f.user, dashboards.ScopeDashboardsPrefix)
args = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeDashboardsPrefix)
// Only add the IN clause if we have any dashboards to check
if len(args) > 0 {
@ -83,12 +87,15 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'")
permSelector.WriteString(rolesFilter)
permSelectorArgs = append(permSelectorArgs, params...)
permSelector.WriteString(" AND action = ?")
permSelectorArgs = append(permSelectorArgs, toCheck[0])
if len(toCheckFolders) == 1 {
permSelector.WriteString(" AND action = ?")
permSelectorArgs = append(permSelectorArgs, toCheckFolders[0])
} else {
permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheckFolders)-1) + ")")
permSelectorArgs = append(permSelectorArgs, toCheckFolders...)
}
} else {
actions := parseStringSliceFromInterfaceSlice(toCheck)
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
permSelectorArgs = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeFoldersPrefix)
// Only add the IN clause if we have any folders to check
if len(permSelectorArgs) > 0 {
@ -133,7 +140,7 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
}
// 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) {
if hasAccessToRoot(f.dashboardAction, f.user) {
builder.WriteString(" OR (dashboard.folder_id = 0 AND NOT dashboard.is_folder)")
}
} else {
@ -145,23 +152,26 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
permSelector.Reset()
permSelectorArgs = permSelectorArgs[:0]
if len(f.folderAction) > 0 {
if len(f.dashboardAction) > 0 {
if f.folderAction != "" {
if f.dashboardAction != "" {
builder.WriteString(" OR ")
}
toCheck := actionsToCheck(f.folderAction, f.user.GetPermissions(), folderWildcards)
toCheck := actionsToCheck(f.folderAction, f.folderActionSets, f.user.GetPermissions(), folderWildcards)
if len(toCheck) > 0 {
if !useSelfContainedPermissions {
permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'")
permSelector.WriteString(rolesFilter)
permSelectorArgs = append(permSelectorArgs, params...)
permSelector.WriteString(" AND action = ?")
permSelectorArgs = append(permSelectorArgs, toCheck[0])
if len(toCheck) == 1 {
permSelector.WriteString(" AND action = ?")
permSelectorArgs = append(permSelectorArgs, toCheck[0])
} else {
permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ")")
permSelectorArgs = append(permSelectorArgs, toCheck...)
}
} else {
actions := parseStringSliceFromInterfaceSlice(toCheck)
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
permSelectorArgs = getAllowedUIDs(f.folderAction, f.user, dashboards.ScopeFoldersPrefix)
if len(permSelectorArgs) > 0 {
permSelector.WriteString("(?" + strings.Repeat(", ?", len(permSelectorArgs)-1) + "")

View File

@ -416,8 +416,8 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
},
features: []any{featuremgmt.FlagNestedFolders},
expectedResult: []string{"dashboard under parent folder", "dashboard under subfolder"},
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
expectedResult: []string{"dashboard under the root", "dashboard under parent folder", "dashboard under subfolder"},
},
{
desc: "Should be able to view inherited folders if nested folders are enabled",
@ -458,7 +458,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
})
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.permissions)}}
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagAccessActionSets)...), featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m))
for k := range m {
@ -530,7 +530,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
},
features: []any{featuremgmt.FlagNestedFolders},
expectedResult: []string{"dashboard under parent folder", "dashboard under subfolder"},
expectedResult: []string{"dashboard under the root", "dashboard under parent folder", "dashboard under subfolder"},
},
{
desc: "Should be able to view inherited folders if nested folders are enabled",
@ -608,6 +608,125 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
}
}
func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T) {
testCases := []struct {
desc string
queryType string
permission dashboardaccess.PermissionType
signedInUserPermissions []accesscontrol.Permission
expectedResult []string
features []any
}{
{
desc: "Should not list any dashboards if user has no permissions",
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: nil,
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
expectedResult: nil,
},
{
desc: "Should not list any folders if user has no permissions",
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: nil,
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
expectedResult: nil,
},
{
desc: "Should be able to view folders if user has `folders:read` access to them",
queryType: searchstore.TypeFolder,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll},
},
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
expectedResult: []string{"parent", "subfolder"},
},
{
desc: "Should be able to view folders if user has action set access to them",
queryType: searchstore.TypeFolder,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: "folders:view", Scope: "folders:uid:parent", Kind: "folders", Identifier: "parent"},
},
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
expectedResult: []string{"parent", "subfolder"},
},
{
desc: "Should be able to view only the subfolder if user has action set access to it",
queryType: searchstore.TypeFolder,
permission: dashboardaccess.PERMISSION_VIEW,
signedInUserPermissions: []accesscontrol.Permission{
{Action: "folders:admin", Scope: "folders:uid:subfolder", Kind: "folders", Identifier: "subfolder"},
},
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
expectedResult: []string{"subfolder"},
},
{
desc: "Should be able to filter for folders that user has write access to",
queryType: searchstore.TypeFolder,
permission: dashboardaccess.PERMISSION_EDIT,
signedInUserPermissions: []accesscontrol.Permission{
{Action: "folders:edit", Scope: "folders:uid:subfolder", Kind: "folders", Identifier: "subfolder"},
{Action: "folders:view", Scope: "folders:uid:parent", Kind: "folders", Identifier: "parent"},
},
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
expectedResult: []string{"subfolder"},
},
}
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true})
t.Cleanup(func() {
guardian.New = origNewGuardian
})
var orgID int64 = 1
for _, tc := range testCases {
tc.signedInUserPermissions = append(tc.signedInUserPermissions, accesscontrol.Permission{
Action: dashboards.ActionFoldersCreate,
}, accesscontrol.Permission{
Action: dashboards.ActionFoldersWrite,
Scope: dashboards.ScopeFoldersAll,
}, accesscontrol.Permission{
Action: dashboards.ActionFoldersRead,
Scope: "folders:uid:unrelated"}, accesscontrol.Permission{
Action: dashboards.ActionDashboardsCreate,
Scope: "folders:uid:unrelated"})
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.signedInUserPermissions)}}
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
t.Run(tc.desc+" with features "+strings.Join(keys, ","), func(t *testing.T) {
db := setupNestedTest(t, usr, tc.signedInUserPermissions, orgID, features)
recursiveQueriesAreSupported, err := db.RecursiveQueriesAreSupported()
require.NoError(t, err)
filter := permissions.NewAccessControlDashboardPermissionFilter(usr, tc.permission, tc.queryType, features, recursiveQueriesAreSupported)
var result []string
err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
q, params := filter.Where()
recQry, recQryParams := filter.With()
params = append(recQryParams, params...)
s := recQry + "\nSELECT dashboard.title FROM dashboard WHERE " + q
leftJoin := filter.LeftJoin()
if leftJoin != "" {
s = recQry + "\nSELECT dashboard.title FROM dashboard LEFT OUTER JOIN " + leftJoin + " WHERE " + q + "ORDER BY dashboard.id ASC"
}
err := sess.SQL(s, params...).Find(&result)
return err
})
require.NoError(t, err)
assert.Equal(t, tc.expectedResult, result)
})
}
}
}
func setupTest(t *testing.T, numFolders, numDashboards int, permissions []accesscontrol.Permission) db.DB {
t.Helper()
@ -724,6 +843,15 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol
})
require.NoError(t, err)
// create a root level dashboard
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID,
Dashboard: simplejson.NewFromAny(map[string]any{
"title": "dashboard under the root",
}),
})
require.NoError(t, err)
// create dashboard under parent folder
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID,