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:
co-authored by
Ieva
Emil Tullstedt
parent
2648fcb833
commit
988a120d6d
@@ -44,6 +44,9 @@ func (b *Builder) ToSQL(limit, page int64) (string, []interface{}) {
|
||||
}
|
||||
|
||||
func (b *Builder) buildSelect() {
|
||||
var recQuery string
|
||||
var recQueryParams []interface{}
|
||||
|
||||
b.sql.WriteString(
|
||||
`SELECT
|
||||
dashboard.id,
|
||||
@@ -61,9 +64,25 @@ func (b *Builder) buildSelect() {
|
||||
if f, ok := f.(FilterSelect); ok {
|
||||
b.sql.WriteString(fmt.Sprintf(", %s", f.Select()))
|
||||
}
|
||||
|
||||
if f, ok := f.(FilterWith); ok {
|
||||
recQuery, recQueryParams = f.With()
|
||||
}
|
||||
}
|
||||
|
||||
b.sql.WriteString(` FROM `)
|
||||
|
||||
if recQuery == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// prepend recursive queries
|
||||
var bf bytes.Buffer
|
||||
bf.WriteString(recQuery)
|
||||
bf.WriteString(b.sql.String())
|
||||
|
||||
b.sql = bf
|
||||
b.params = append(recQueryParams, b.params...)
|
||||
}
|
||||
|
||||
func (b *Builder) applyFilters() (ordering string) {
|
||||
|
||||
@@ -14,6 +14,12 @@ type FilterWhere interface {
|
||||
Where() (string, []interface{})
|
||||
}
|
||||
|
||||
// FilterWith returns any recursive CTE queries (if supported)
|
||||
// and their parameters
|
||||
type FilterWith interface {
|
||||
With() (string, []interface{})
|
||||
}
|
||||
|
||||
// FilterGroupBy should be used after performing an outer join on the
|
||||
// search result to ensure there is only one of each ID in the results.
|
||||
// The id column must be present in the result.
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"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/org"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
@@ -148,6 +150,145 @@ func TestBuilder_Permissions(t *testing.T) {
|
||||
assert.Len(t, res, 0)
|
||||
}
|
||||
|
||||
func TestBuilder_RBAC(t *testing.T) {
|
||||
testsCases := []struct {
|
||||
desc string
|
||||
userPermissions []accesscontrol.Permission
|
||||
features featuremgmt.FeatureToggles
|
||||
expectedParams []interface{}
|
||||
}{
|
||||
{
|
||||
desc: "no user permissions",
|
||||
features: featuremgmt.WithFeatures(),
|
||||
expectedParams: []interface{}{
|
||||
int64(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "user with view permission",
|
||||
userPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:1"},
|
||||
},
|
||||
features: featuremgmt.WithFeatures(),
|
||||
expectedParams: []interface{}{
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"dashboards:write",
|
||||
2,
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"dashboards:write",
|
||||
2,
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"folders:read",
|
||||
"dashboards:create",
|
||||
2,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "user with view permission with nesting",
|
||||
userPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:1"},
|
||||
},
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
expectedParams: []interface{}{
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"dashboards:write",
|
||||
2,
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"folders:read",
|
||||
"dashboards:create",
|
||||
2,
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"dashboards:write",
|
||||
2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
user := &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: 1,
|
||||
OrgRole: org.RoleViewer,
|
||||
}
|
||||
|
||||
store := setupTestEnvironment(t)
|
||||
createDashboards(t, store, 0, 1, user.OrgID)
|
||||
|
||||
recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported()
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range testsCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if len(tc.userPermissions) > 0 {
|
||||
user.Permissions = map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tc.userPermissions)}
|
||||
}
|
||||
|
||||
level := dashboards.PERMISSION_EDIT
|
||||
|
||||
builder := &searchstore.Builder{
|
||||
Filters: []interface{}{
|
||||
searchstore.OrgFilter{OrgId: user.OrgID},
|
||||
searchstore.TitleSorter{},
|
||||
permissions.NewAccessControlDashboardPermissionFilter(
|
||||
user,
|
||||
level,
|
||||
"",
|
||||
tc.features,
|
||||
recursiveQueriesAreSupported,
|
||||
),
|
||||
},
|
||||
Dialect: store.GetDialect(),
|
||||
}
|
||||
|
||||
res := []dashboards.DashboardSearchProjection{}
|
||||
err := store.WithDbSession(context.Background(), func(sess *db.Session) error {
|
||||
sql, params := builder.ToSQL(limit, page)
|
||||
// TODO: replace with a proper test
|
||||
assert.Equal(t, tc.expectedParams, params)
|
||||
return sess.SQL(sql, params...).Find(&res)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, res, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupTestEnvironment(t *testing.T) db.DB {
|
||||
t.Helper()
|
||||
store := db.InitTestDB(t)
|
||||
|
||||
Reference in New Issue
Block a user