mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Search Service to support search for folders available for alerting (#46483)
* support new query type "alert-folder" * move action calculation to the constructor of the filter * update filter to support query type `dash-folder-alerting` and empty dashboard actions * require folders:read to access alert rules
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
@@ -92,9 +93,10 @@ func (ss *SQLStore) FindDashboards(ctx context.Context, query *search.FindPersis
|
||||
},
|
||||
}
|
||||
|
||||
if ss.Cfg.IsFeatureToggleEnabled("accesscontrol") {
|
||||
if ss.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) {
|
||||
// if access control is enabled, overwrite the filters so far
|
||||
filters = []interface{}{
|
||||
permissions.AccessControlDashboardPermissionFilter{User: query.SignedInUser, PermissionLevel: query.Permission},
|
||||
permissions.NewAccessControlDashboardPermissionFilter(query.SignedInUser, query.Permission, query.Type),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
)
|
||||
|
||||
type DashboardPermissionFilter struct {
|
||||
@@ -78,36 +79,60 @@ func (d DashboardPermissionFilter) Where() (string, []interface{}) {
|
||||
|
||||
type AccessControlDashboardPermissionFilter struct {
|
||||
User *models.SignedInUser
|
||||
PermissionLevel models.PermissionType
|
||||
dashboardActions []string
|
||||
folderActions []string
|
||||
}
|
||||
|
||||
func (f AccessControlDashboardPermissionFilter) Where() (string, []interface{}) {
|
||||
// NewAccessControlDashboardPermissionFilter creates a new AccessControlDashboardPermissionFilter that is configured with specific actions calculated based on the models.PermissionType and query type
|
||||
func NewAccessControlDashboardPermissionFilter(user *models.SignedInUser, permissionLevel models.PermissionType, queryType string) AccessControlDashboardPermissionFilter {
|
||||
needEdit := permissionLevel > models.PERMISSION_VIEW
|
||||
folderActions := []string{dashboards.ActionFoldersRead}
|
||||
dashboardActions := []string{accesscontrol.ActionDashboardsRead}
|
||||
if f.PermissionLevel == models.PERMISSION_EDIT {
|
||||
var dashboardActions []string
|
||||
if queryType == searchstore.TypeAlertFolder {
|
||||
folderActions = append(folderActions, accesscontrol.ActionAlertingRuleRead)
|
||||
if needEdit {
|
||||
folderActions = append(folderActions, accesscontrol.ActionAlertingRuleUpdate)
|
||||
}
|
||||
} else {
|
||||
dashboardActions = append(dashboardActions, accesscontrol.ActionDashboardsRead)
|
||||
if needEdit {
|
||||
folderActions = append(folderActions, accesscontrol.ActionDashboardsCreate)
|
||||
dashboardActions = append(dashboardActions, accesscontrol.ActionDashboardsWrite)
|
||||
}
|
||||
}
|
||||
return AccessControlDashboardPermissionFilter{User: user, folderActions: folderActions, dashboardActions: dashboardActions}
|
||||
}
|
||||
|
||||
func (f AccessControlDashboardPermissionFilter) Where() (string, []interface{}) {
|
||||
var args []interface{}
|
||||
builder := strings.Builder{}
|
||||
builder.WriteString("(((")
|
||||
builder.WriteString("(")
|
||||
|
||||
dashFilter, _ := accesscontrol.Filter(f.User, "dashboard.id", "dashboards", dashboardActions...)
|
||||
if len(f.dashboardActions) > 0 {
|
||||
builder.WriteString("((")
|
||||
dashFilter, _ := accesscontrol.Filter(f.User, "dashboard.id", "dashboards", f.dashboardActions...)
|
||||
builder.WriteString(dashFilter.Where)
|
||||
args = append(args, dashFilter.Args...)
|
||||
|
||||
builder.WriteString(" OR ")
|
||||
|
||||
dashFolderFilter, _ := accesscontrol.Filter(f.User, "dashboard.folder_id", "folders", dashboardActions...)
|
||||
dashFolderFilter, _ := accesscontrol.Filter(f.User, "dashboard.folder_id", "folders", f.dashboardActions...)
|
||||
builder.WriteString(dashFolderFilter.Where)
|
||||
builder.WriteString(") AND NOT dashboard.is_folder) OR (")
|
||||
builder.WriteString(") AND NOT dashboard.is_folder)")
|
||||
args = append(args, dashFolderFilter.Args...)
|
||||
}
|
||||
|
||||
folderFilter, _ := accesscontrol.Filter(f.User, "dashboard.id", "folders", folderActions...)
|
||||
if len(f.folderActions) > 0 {
|
||||
if len(f.dashboardActions) > 0 {
|
||||
builder.WriteString(" OR ")
|
||||
}
|
||||
builder.WriteString("(")
|
||||
folderFilter, _ := accesscontrol.Filter(f.User, "dashboard.id", "folders", f.folderActions...)
|
||||
builder.WriteString(folderFilter.Where)
|
||||
builder.WriteString(" AND dashboard.is_folder))")
|
||||
builder.WriteString(" AND dashboard.is_folder)")
|
||||
args = append(args, folderFilter.Args...)
|
||||
}
|
||||
|
||||
builder.WriteString(")")
|
||||
return builder.String(), args
|
||||
}
|
||||
|
147
pkg/services/sqlstore/permissions/dashboard_test.go
Normal file
147
pkg/services/sqlstore/permissions/dashboard_test.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package permissions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func TestNewAccessControlDashboardPermissionFilter(t *testing.T) {
|
||||
randomType := "random_" + util.GenerateShortUID()
|
||||
testCases := []struct {
|
||||
permission models.PermissionType
|
||||
queryType string
|
||||
expectedDashboardActions []string
|
||||
expectedFolderActions []string
|
||||
}{
|
||||
{
|
||||
queryType: searchstore.TypeAlertFolder,
|
||||
permission: models.PERMISSION_ADMIN,
|
||||
expectedDashboardActions: nil,
|
||||
expectedFolderActions: []string{
|
||||
dashboards.ActionFoldersRead,
|
||||
accesscontrol.ActionAlertingRuleRead,
|
||||
accesscontrol.ActionAlertingRuleUpdate,
|
||||
},
|
||||
},
|
||||
{
|
||||
queryType: searchstore.TypeAlertFolder,
|
||||
permission: models.PERMISSION_EDIT,
|
||||
expectedDashboardActions: nil,
|
||||
expectedFolderActions: []string{
|
||||
dashboards.ActionFoldersRead,
|
||||
accesscontrol.ActionAlertingRuleRead,
|
||||
accesscontrol.ActionAlertingRuleUpdate,
|
||||
},
|
||||
},
|
||||
{
|
||||
queryType: searchstore.TypeAlertFolder,
|
||||
permission: models.PERMISSION_VIEW,
|
||||
expectedDashboardActions: nil,
|
||||
expectedFolderActions: []string{
|
||||
dashboards.ActionFoldersRead,
|
||||
accesscontrol.ActionAlertingRuleRead,
|
||||
},
|
||||
},
|
||||
{
|
||||
queryType: randomType,
|
||||
permission: models.PERMISSION_ADMIN,
|
||||
expectedDashboardActions: []string{
|
||||
accesscontrol.ActionDashboardsRead,
|
||||
accesscontrol.ActionDashboardsWrite,
|
||||
},
|
||||
expectedFolderActions: []string{
|
||||
dashboards.ActionFoldersRead,
|
||||
accesscontrol.ActionDashboardsCreate,
|
||||
},
|
||||
},
|
||||
{
|
||||
queryType: randomType,
|
||||
permission: models.PERMISSION_EDIT,
|
||||
expectedDashboardActions: []string{
|
||||
accesscontrol.ActionDashboardsRead,
|
||||
accesscontrol.ActionDashboardsWrite,
|
||||
},
|
||||
expectedFolderActions: []string{
|
||||
dashboards.ActionFoldersRead,
|
||||
accesscontrol.ActionDashboardsCreate,
|
||||
},
|
||||
},
|
||||
{
|
||||
queryType: randomType,
|
||||
permission: models.PERMISSION_VIEW,
|
||||
expectedDashboardActions: []string{
|
||||
accesscontrol.ActionDashboardsRead,
|
||||
},
|
||||
expectedFolderActions: []string{
|
||||
dashboards.ActionFoldersRead,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(fmt.Sprintf("query type %s, permissions %s", testCase.queryType, testCase.permission), func(t *testing.T) {
|
||||
filters := NewAccessControlDashboardPermissionFilter(&models.SignedInUser{}, testCase.permission, testCase.queryType)
|
||||
|
||||
require.Equal(t, testCase.expectedDashboardActions, filters.dashboardActions)
|
||||
require.Equal(t, testCase.expectedFolderActions, filters.folderActions)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccessControlDashboardPermissionFilter_Where(t *testing.T) {
|
||||
testCases := []struct {
|
||||
title string
|
||||
dashboardActions []string
|
||||
folderActions []string
|
||||
expectedResult string
|
||||
}{
|
||||
{
|
||||
title: "folder and dashboard actions are defined",
|
||||
dashboardActions: []string{"test"},
|
||||
folderActions: []string{"test"},
|
||||
expectedResult: "((( 1 = 0 OR 1 = 0) AND NOT dashboard.is_folder) OR ( 1 = 0 AND dashboard.is_folder))",
|
||||
},
|
||||
{
|
||||
title: "folder actions are defined but not dashboard actions",
|
||||
dashboardActions: nil,
|
||||
folderActions: []string{"test"},
|
||||
expectedResult: "(( 1 = 0 AND dashboard.is_folder))",
|
||||
},
|
||||
{
|
||||
title: "dashboard actions are defined but not folder actions",
|
||||
dashboardActions: []string{"test"},
|
||||
folderActions: nil,
|
||||
expectedResult: "((( 1 = 0 OR 1 = 0) AND NOT dashboard.is_folder))",
|
||||
},
|
||||
{
|
||||
title: "dashboard actions are defined but not folder actions",
|
||||
dashboardActions: nil,
|
||||
folderActions: nil,
|
||||
expectedResult: "()",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.title, func(t *testing.T) {
|
||||
filter := AccessControlDashboardPermissionFilter{
|
||||
User: &models.SignedInUser{Permissions: map[int64]map[string][]string{}},
|
||||
dashboardActions: testCase.dashboardActions,
|
||||
folderActions: testCase.folderActions,
|
||||
}
|
||||
|
||||
query, args := filter.Where()
|
||||
|
||||
assert.Empty(t, args)
|
||||
assert.Equal(t, testCase.expectedResult, query)
|
||||
})
|
||||
}
|
||||
}
|
@@ -40,6 +40,7 @@ type FilterSelect interface {
|
||||
const (
|
||||
TypeFolder = "dash-folder"
|
||||
TypeDashboard = "dash-db"
|
||||
TypeAlertFolder = "dash-folder-alerting"
|
||||
)
|
||||
|
||||
type TypeFilter struct {
|
||||
@@ -48,7 +49,7 @@ type TypeFilter struct {
|
||||
}
|
||||
|
||||
func (f TypeFilter) Where() (string, []interface{}) {
|
||||
if f.Type == TypeFolder {
|
||||
if f.Type == TypeFolder || f.Type == TypeAlertFolder {
|
||||
return "dashboard.is_folder = " + f.Dialect.BooleanStr(true), nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user