mirror of
https://github.com/grafana/grafana.git
synced 2025-08-06 09:49:26 +08:00
Search v1: Add support for inherited folder permissions if nested folders are enabled (#63275)
* Add features dependency to SQLBuilder * Add features dependency to AccessControlDashboardPermissionFilter * Add test for folder inheritance * Dashboard permissions: Return recursive query * Recursive query for inherited folders * Modify search builder * Adjust db.SQLBuilder * Pass flag to SQLbuilder if CTEs are supported * Add support for mysql < 8.0 * Add benchmarking for search with nested folders * Set features to AlertStore * Update pkg/infra/db/sqlbuilder.go Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * Set features to LibraryElementService * SQLBuilder tests with nested folder flag set * Apply suggestion from code review Co-authored-by: IevaVasiljeva <ieva.vasiljeva@grafana.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:

committed by
GitHub

parent
2648fcb833
commit
988a120d6d
@ -5,20 +5,26 @@ import (
|
||||
|
||||
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/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func NewSqlBuilder(cfg *setting.Cfg, dialect migrator.Dialect) SQLBuilder {
|
||||
return SQLBuilder{cfg: cfg, dialect: dialect}
|
||||
func NewSqlBuilder(cfg *setting.Cfg, features featuremgmt.FeatureToggles, dialect migrator.Dialect, recursiveQueriesAreSupported bool) SQLBuilder {
|
||||
return SQLBuilder{cfg: cfg, features: features, dialect: dialect, recursiveQueriesAreSupported: recursiveQueriesAreSupported}
|
||||
}
|
||||
|
||||
type SQLBuilder struct {
|
||||
cfg *setting.Cfg
|
||||
sql bytes.Buffer
|
||||
params []interface{}
|
||||
cfg *setting.Cfg
|
||||
features featuremgmt.FeatureToggles
|
||||
sql bytes.Buffer
|
||||
params []interface{}
|
||||
recQry string
|
||||
recQryParams []interface{}
|
||||
recursiveQueriesAreSupported bool
|
||||
|
||||
dialect migrator.Dialect
|
||||
}
|
||||
|
||||
@ -31,10 +37,22 @@ func (sb *SQLBuilder) Write(sql string, params ...interface{}) {
|
||||
}
|
||||
|
||||
func (sb *SQLBuilder) GetSQLString() string {
|
||||
return sb.sql.String()
|
||||
if sb.recQry == "" {
|
||||
return sb.sql.String()
|
||||
}
|
||||
|
||||
var bf bytes.Buffer
|
||||
bf.WriteString(sb.recQry)
|
||||
bf.WriteString(sb.sql.String())
|
||||
return bf.String()
|
||||
}
|
||||
|
||||
func (sb *SQLBuilder) GetParams() []interface{} {
|
||||
if len(sb.recQryParams) == 0 {
|
||||
return sb.params
|
||||
}
|
||||
|
||||
sb.params = append(sb.recQryParams, sb.params...)
|
||||
return sb.params
|
||||
}
|
||||
|
||||
@ -44,11 +62,15 @@ func (sb *SQLBuilder) AddParams(params ...interface{}) {
|
||||
|
||||
func (sb *SQLBuilder) WriteDashboardPermissionFilter(user *user.SignedInUser, permission dashboards.PermissionType) {
|
||||
var (
|
||||
sql string
|
||||
params []interface{}
|
||||
sql string
|
||||
params []interface{}
|
||||
recQry string
|
||||
recQryParams []interface{}
|
||||
)
|
||||
if !ac.IsDisabled(sb.cfg) {
|
||||
sql, params = permissions.NewAccessControlDashboardPermissionFilter(user, permission, "").Where()
|
||||
filterRBAC := permissions.NewAccessControlDashboardPermissionFilter(user, permission, "", sb.features, sb.recursiveQueriesAreSupported)
|
||||
sql, params = filterRBAC.Where()
|
||||
recQry, recQryParams = filterRBAC.With()
|
||||
} else {
|
||||
sql, params = permissions.DashboardPermissionFilter{
|
||||
OrgRole: user.OrgRole,
|
||||
@ -61,4 +83,6 @@ func (sb *SQLBuilder) WriteDashboardPermissionFilter(user *user.SignedInUser, pe
|
||||
|
||||
sb.sql.WriteString(" AND " + sql)
|
||||
sb.params = append(sb.params, params...)
|
||||
sb.recQry = recQry
|
||||
sb.recQryParams = recQryParams
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -24,6 +25,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
t.Run("WriteDashboardPermissionFilter", func(t *testing.T) {
|
||||
t.Run("user ACL", func(t *testing.T) {
|
||||
test(t,
|
||||
@ -31,6 +33,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{User: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -38,6 +41,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{User: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -45,6 +49,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{User: true, Permission: dashboards.PERMISSION_EDIT},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -52,6 +57,41 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{User: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("user ACL with nested folders", func(t *testing.T) {
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{User: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{User: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{User: true, Permission: dashboards.PERMISSION_EDIT},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{User: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
})
|
||||
|
||||
@ -61,6 +101,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{Role: org.RoleViewer, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -68,6 +109,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{Role: org.RoleViewer, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -75,6 +117,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{Role: org.RoleEditor, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -82,6 +125,41 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{Role: org.RoleEditor, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("role ACL with nested folders", func(t *testing.T) {
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{Role: org.RoleViewer, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{Role: org.RoleViewer, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{Role: org.RoleEditor, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{Role: org.RoleEditor, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
})
|
||||
|
||||
@ -91,6 +169,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{Team: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -98,6 +177,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{Team: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -105,6 +185,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{Team: true, Permission: dashboards.PERMISSION_EDIT},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -112,6 +193,41 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
&DashboardPermission{Team: true, Permission: dashboards.PERMISSION_EDIT},
|
||||
Search{UserFromACL: false, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("team ACL with nested folders", func(t *testing.T) {
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{Team: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{Team: true, Permission: dashboards.PERMISSION_VIEW},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{Team: true, Permission: dashboards.PERMISSION_EDIT},
|
||||
Search{UserFromACL: true, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
&DashboardPermission{Team: true, Permission: dashboards.PERMISSION_EDIT},
|
||||
Search{UserFromACL: false, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
})
|
||||
|
||||
@ -121,6 +237,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
nil,
|
||||
Search{OrgId: -1, UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -128,6 +245,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
nil,
|
||||
Search{OrgId: -1, UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -135,6 +253,7 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
nil,
|
||||
Search{OrgId: -1, UsersOrgRole: org.RoleEditor, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
|
||||
test(t,
|
||||
@ -142,6 +261,41 @@ func TestIntegrationSQLBuilder(t *testing.T) {
|
||||
nil,
|
||||
Search{OrgId: -1, UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("defaults for user ACL with nested folders", func(t *testing.T) {
|
||||
test(t,
|
||||
DashboardProps{},
|
||||
nil,
|
||||
Search{OrgId: -1, UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{OrgId: -1},
|
||||
nil,
|
||||
Search{OrgId: -1, UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_VIEW},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{OrgId: -1},
|
||||
nil,
|
||||
Search{OrgId: -1, UsersOrgRole: org.RoleEditor, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
|
||||
test(t,
|
||||
DashboardProps{OrgId: -1},
|
||||
nil,
|
||||
Search{OrgId: -1, UsersOrgRole: org.RoleViewer, RequiredPermission: dashboards.PERMISSION_EDIT},
|
||||
shouldNotFind,
|
||||
featuremgmt.WithFeatures(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)),
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -172,7 +326,7 @@ type dashboardResponse struct {
|
||||
Id int64
|
||||
}
|
||||
|
||||
func test(t *testing.T, dashboardProps DashboardProps, dashboardPermission *DashboardPermission, search Search, shouldFind bool) {
|
||||
func test(t *testing.T, dashboardProps DashboardProps, dashboardPermission *DashboardPermission, search Search, shouldFind bool, features featuremgmt.FeatureToggles) {
|
||||
t.Helper()
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
@ -186,7 +340,7 @@ func test(t *testing.T, dashboardProps DashboardProps, dashboardPermission *Dash
|
||||
aclUserID = createDummyACL(t, sqlStore, dashboardPermission, search, dashboard.ID)
|
||||
t.Logf("Created ACL with user ID %d\n", aclUserID)
|
||||
}
|
||||
dashboards := getDashboards(t, sqlStore, search, aclUserID)
|
||||
dashboards := getDashboards(t, sqlStore, search, aclUserID, features)
|
||||
|
||||
if shouldFind {
|
||||
require.Len(t, dashboards, 1, "Should return one dashboard")
|
||||
@ -292,7 +446,7 @@ func createDummyACL(t *testing.T, sqlStore *sqlstore.SQLStore, dashboardPermissi
|
||||
return 0
|
||||
}
|
||||
|
||||
func getDashboards(t *testing.T, sqlStore *sqlstore.SQLStore, search Search, aclUserID int64) []*dashboardResponse {
|
||||
func getDashboards(t *testing.T, sqlStore *sqlstore.SQLStore, search Search, aclUserID int64, features featuremgmt.FeatureToggles) []*dashboardResponse {
|
||||
t.Helper()
|
||||
|
||||
old := sqlStore.Cfg.RBACEnabled
|
||||
@ -301,7 +455,10 @@ func getDashboards(t *testing.T, sqlStore *sqlstore.SQLStore, search Search, acl
|
||||
sqlStore.Cfg.RBACEnabled = old
|
||||
}()
|
||||
|
||||
builder := NewSqlBuilder(sqlStore.Cfg, sqlStore.GetDialect())
|
||||
recursiveQueriesAreSupported, err := sqlStore.RecursiveQueriesAreSupported()
|
||||
require.NoError(t, err)
|
||||
|
||||
builder := NewSqlBuilder(sqlStore.Cfg, features, sqlStore.GetDialect(), recursiveQueriesAreSupported)
|
||||
signedInUser := &user.SignedInUser{
|
||||
UserID: 9999999999,
|
||||
}
|
||||
@ -325,7 +482,7 @@ func getDashboards(t *testing.T, sqlStore *sqlstore.SQLStore, search Search, acl
|
||||
builder.Write("SELECT * FROM dashboard WHERE true")
|
||||
builder.WriteDashboardPermissionFilter(signedInUser, search.RequiredPermission)
|
||||
t.Logf("Searching for dashboards, SQL: %q\n", builder.GetSQLString())
|
||||
err := sqlStore.GetEngine().SQL(builder.GetSQLString(), builder.params...).Find(&res)
|
||||
err = sqlStore.GetEngine().SQL(builder.GetSQLString(), builder.params...).Find(&res)
|
||||
require.NoError(t, err)
|
||||
return res
|
||||
}
|
||||
|
Reference in New Issue
Block a user