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

take action sets into account in dashboard and folder permission filter
pull/89183/head
Ieva 1 year ago committed by GitHub
parent 627d77c365
commit 3853f90528
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 161
      pkg/services/sqlstore/permissions/dashboard.go
  2. 54
      pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go
  3. 136
      pkg/services/sqlstore/permissions/dashboard_test.go

@ -2,6 +2,7 @@ package permissions
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"slices" "slices"
"strings" "strings"
@ -25,10 +26,12 @@ type clause struct {
} }
type accessControlDashboardPermissionFilter struct { type accessControlDashboardPermissionFilter struct {
user identity.Requester user identity.Requester
dashboardAction string dashboardAction string
folderAction string dashboardActionSets []string
features featuremgmt.FeatureToggles folderAction string
folderActionSets []string
features featuremgmt.FeatureToggles
where clause where clause
// any recursive CTE queries (if supported) // any recursive CTE queries (if supported)
@ -53,30 +56,63 @@ func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissi
var folderAction string var folderAction string
var dashboardAction string var dashboardAction string
var folderActionSets []string
var dashboardActionSets []string
if queryType == searchstore.TypeFolder { if queryType == searchstore.TypeFolder {
folderAction = dashboards.ActionFoldersRead 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 { if needEdit {
folderAction = dashboards.ActionDashboardsCreate folderAction = dashboards.ActionDashboardsCreate
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:edit", "folders:admin"}
}
} }
} else if queryType == searchstore.TypeDashboard { } else if queryType == searchstore.TypeDashboard {
dashboardAction = dashboards.ActionDashboardsRead 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 { if needEdit {
dashboardAction = dashboards.ActionDashboardsWrite 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 { } else if queryType == searchstore.TypeAlertFolder {
folderAction = accesscontrol.ActionAlertingRuleRead folderAction = accesscontrol.ActionAlertingRuleRead
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
}
if needEdit { if needEdit {
folderAction = accesscontrol.ActionAlertingRuleCreate folderAction = accesscontrol.ActionAlertingRuleCreate
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
folderActionSets = []string{"folders:edit", "folders:admin"}
}
} }
} else if queryType == searchstore.TypeAnnotation { } else if queryType == searchstore.TypeAnnotation {
dashboardAction = accesscontrol.ActionAnnotationsRead 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 { } else {
folderAction = dashboards.ActionFoldersRead folderAction = dashboards.ActionFoldersRead
dashboardAction = dashboards.ActionDashboardsRead 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 { if needEdit {
folderAction = dashboards.ActionDashboardsCreate folderAction = dashboards.ActionDashboardsCreate
dashboardAction = dashboards.ActionDashboardsWrite 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) { if features.IsEnabledGlobally(featuremgmt.FlagPermissionsFilterRemoveSubquery) {
f = &accessControlDashboardPermissionFilterNoFolderSubquery{ f = &accessControlDashboardPermissionFilterNoFolderSubquery{
accessControlDashboardPermissionFilter: accessControlDashboardPermissionFilter{ accessControlDashboardPermissionFilter: accessControlDashboardPermissionFilter{
user: user, folderAction: folderAction, dashboardAction: dashboardAction, features: features, user: user, folderAction: folderAction, folderActionSets: folderActionSets, dashboardAction: dashboardAction, dashboardActionSets: dashboardActionSets,
recursiveQueriesAreSupported: recursiveQueriesAreSupported, features: features, recursiveQueriesAreSupported: recursiveQueriesAreSupported,
}, },
} }
} else { } else {
f = &accessControlDashboardPermissionFilter{user: user, folderAction: folderAction, dashboardAction: dashboardAction, features: features, f = &accessControlDashboardPermissionFilter{user: user, folderAction: folderAction, folderActionSets: folderActionSets, dashboardAction: dashboardAction, dashboardActionSets: dashboardActionSets,
recursiveQueriesAreSupported: recursiveQueriesAreSupported, features: features, recursiveQueriesAreSupported: recursiveQueriesAreSupported,
} }
} }
f.buildClauses() 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) // 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) useSelfContainedPermissions := f.user.IsAuthenticatedBy(login.ExtendedJWTModule)
if len(f.dashboardAction) > 0 { if f.dashboardAction != "" {
toCheck := actionsToCheck(f.dashboardAction, f.user.GetPermissions(), dashWildcards, folderWildcards) 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 { if !useSelfContainedPermissions {
builder.WriteString("(dashboard.uid IN (SELECT identifier FROM permission WHERE kind = 'dashboards' AND attribute = 'uid'") builder.WriteString("(dashboard.uid IN (SELECT identifier FROM permission WHERE kind = 'dashboards' AND attribute = 'uid'")
builder.WriteString(rolesFilter) builder.WriteString(rolesFilter)
args = append(args, params...) args = append(args, params...)
builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)") if len(toCheckDashboards) == 1 {
args = append(args, toCheck[0]) 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 { } else {
actions := parseStringSliceFromInterfaceSlice(toCheck) args = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeDashboardsPrefix)
args = getAllowedUIDs(actions, f.user, dashboards.ScopeDashboardsPrefix)
// Only add the IN clause if we have any dashboards to check // Only add the IN clause if we have any dashboards to check
if len(args) > 0 { 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("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'")
permSelector.WriteString(rolesFilter) permSelector.WriteString(rolesFilter)
permSelectorArgs = append(permSelectorArgs, params...) permSelectorArgs = append(permSelectorArgs, params...)
permSelector.WriteString(" AND action = ?") if len(toCheckDashboards) == 1 {
permSelectorArgs = append(permSelectorArgs, toCheck[0]) 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 { } else {
actions := parseStringSliceFromInterfaceSlice(toCheck) permSelectorArgs = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeFoldersPrefix)
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
// Only add the IN clause if we have any folders to check // Only add the IN clause if we have any folders to check
if len(permSelectorArgs) > 0 { if len(permSelectorArgs) > 0 {
@ -229,7 +272,7 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
builder.WriteString(") AND NOT dashboard.is_folder)") 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) // 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)") builder.WriteString(" OR (dashboard.folder_id = 0 AND NOT dashboard.is_folder)")
} }
} else { } else {
@ -241,23 +284,27 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
permSelector.Reset() permSelector.Reset()
permSelectorArgs = permSelectorArgs[:0] permSelectorArgs = permSelectorArgs[:0]
if len(f.folderAction) > 0 { if f.folderAction != "" {
if len(f.dashboardAction) > 0 { if f.dashboardAction != "" {
builder.WriteString(" OR ") 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 len(toCheck) > 0 {
if !useSelfContainedPermissions { if !useSelfContainedPermissions {
permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'") permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'")
permSelector.WriteString(rolesFilter) permSelector.WriteString(rolesFilter)
permSelectorArgs = append(permSelectorArgs, params...) permSelectorArgs = append(permSelectorArgs, params...)
permSelector.WriteString(" AND action = ?") if len(toCheck) == 1 {
permSelectorArgs = append(permSelectorArgs, toCheck[0]) 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 { } else {
actions := parseStringSliceFromInterfaceSlice(toCheck) permSelectorArgs = getAllowedUIDs(f.folderAction, f.user, dashboards.ScopeFoldersPrefix)
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
if len(permSelectorArgs) > 0 { if len(permSelectorArgs) > 0 {
permSelector.WriteString("(?" + strings.Repeat(", ?", len(permSelectorArgs)-1) + "") 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 _, scope := range permissions[action] {
for _, w := range wildcards { for _, w := range wildcards {
if w.Contains(scope) { 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) { 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 return strings.Join(wheres, ") OR "), args
} }
func parseStringSliceFromInterfaceSlice(slice []any) []string { func getAllowedUIDs(action string, user identity.Requester, scopePrefix string) []any {
result := make([]string, 0, len(slice)) var args []any
for _, s := range slice { for _, uidScope := range user.GetPermissions()[action] {
result = append(result, s.(string)) if !strings.HasPrefix(uidScope, scopePrefix) {
} continue
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{}{}
} }
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 return args
} }
// Checks if the user has the required permissions on the root (used to be the General folder) // 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) generalFolderScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)
for _, action := range actionsToCheck { return slices.Contains(user.GetPermissions()[actionToCheck], generalFolderScope)
if !slices.Contains(user.GetPermissions()[action.(string)], generalFolderScope) {
return false
}
}
return true
} }

@ -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) // 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 useSelfContainedPermissions := f.user.GetAuthenticatedBy() == login.ExtendedJWTModule
if len(f.dashboardAction) > 0 { if f.dashboardAction != "" {
toCheck := actionsToCheck(f.dashboardAction, f.user.GetPermissions(), dashWildcards, folderWildcards) 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 { if !useSelfContainedPermissions {
builder.WriteString("(dashboard.uid IN (SELECT identifier FROM permission WHERE kind = 'dashboards' AND attribute = 'uid'") builder.WriteString("(dashboard.uid IN (SELECT identifier FROM permission WHERE kind = 'dashboards' AND attribute = 'uid'")
builder.WriteString(rolesFilter) builder.WriteString(rolesFilter)
args = append(args, params...) args = append(args, params...)
builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)") if len(toCheckDashboards) == 1 {
args = append(args, toCheck[0]) 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 { } else {
actions := parseStringSliceFromInterfaceSlice(toCheck) args = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeDashboardsPrefix)
args = getAllowedUIDs(actions, f.user, dashboards.ScopeDashboardsPrefix)
// Only add the IN clause if we have any dashboards to check // Only add the IN clause if we have any dashboards to check
if len(args) > 0 { 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("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'")
permSelector.WriteString(rolesFilter) permSelector.WriteString(rolesFilter)
permSelectorArgs = append(permSelectorArgs, params...) permSelectorArgs = append(permSelectorArgs, params...)
permSelector.WriteString(" AND action = ?") if len(toCheckFolders) == 1 {
permSelectorArgs = append(permSelectorArgs, toCheck[0]) 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 { } else {
actions := parseStringSliceFromInterfaceSlice(toCheck) permSelectorArgs = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeFoldersPrefix)
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
// Only add the IN clause if we have any folders to check // Only add the IN clause if we have any folders to check
if len(permSelectorArgs) > 0 { 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) // 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)") builder.WriteString(" OR (dashboard.folder_id = 0 AND NOT dashboard.is_folder)")
} }
} else { } else {
@ -145,23 +152,26 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
permSelector.Reset() permSelector.Reset()
permSelectorArgs = permSelectorArgs[:0] permSelectorArgs = permSelectorArgs[:0]
if len(f.folderAction) > 0 { if f.folderAction != "" {
if len(f.dashboardAction) > 0 { if f.dashboardAction != "" {
builder.WriteString(" OR ") 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 len(toCheck) > 0 {
if !useSelfContainedPermissions { if !useSelfContainedPermissions {
permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'") permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'")
permSelector.WriteString(rolesFilter) permSelector.WriteString(rolesFilter)
permSelectorArgs = append(permSelectorArgs, params...) permSelectorArgs = append(permSelectorArgs, params...)
permSelector.WriteString(" AND action = ?") if len(toCheck) == 1 {
permSelectorArgs = append(permSelectorArgs, toCheck[0]) 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 { } else {
actions := parseStringSliceFromInterfaceSlice(toCheck) permSelectorArgs = getAllowedUIDs(f.folderAction, f.user, dashboards.ScopeFoldersPrefix)
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
if len(permSelectorArgs) > 0 { if len(permSelectorArgs) > 0 {
permSelector.WriteString("(?" + strings.Repeat(", ?", len(permSelectorArgs)-1) + "") permSelector.WriteString("(?" + strings.Repeat(", ?", len(permSelectorArgs)-1) + "")

@ -416,8 +416,8 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
permissions: []accesscontrol.Permission{ permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll}, {Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
}, },
features: []any{featuremgmt.FlagNestedFolders}, features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
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", 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)}} 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()) m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m)) keys := make([]string, 0, len(m))
for k := range m { for k := range m {
@ -530,7 +530,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll}, {Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
}, },
features: []any{featuremgmt.FlagNestedFolders}, 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", 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 { func setupTest(t *testing.T, numFolders, numDashboards int, permissions []accesscontrol.Permission) db.DB {
t.Helper() t.Helper()
@ -724,6 +843,15 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol
}) })
require.NoError(t, err) 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 // create dashboard under parent folder
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{ _, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID, OrgID: orgID,

Loading…
Cancel
Save