RBAC: Improve performance of dashboard filter query (#56813)

* RBAC: Move UserRolesFilter to domain package

* Dashboard Permissions: Rewrite rbac filter to check access in sql

* RBAC: Add break when wildcard is found

* RBAC: Add tests for dashboard filter

* RBAC: Update tests

* RBAC: Cover more test cases

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Karl Persson 2022-10-25 11:14:27 +02:00 committed by GitHub
parent d2a70bc42d
commit 7386f8652c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 525 additions and 199 deletions

View File

@ -9,10 +9,6 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
)
const (
globalOrgID = 0
)
func ProvideService(sql db.DB) *AccessControlStore {
return &AccessControlStore{sql}
}
@ -29,7 +25,7 @@ func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query acces
return nil
}
filter, params := userRolesFilter(query.OrgID, query.UserID, query.TeamIDs, query.Roles)
filter, params := accesscontrol.UserRolesFilter(query.OrgID, query.UserID, query.TeamIDs, query.Roles)
q := `
SELECT
@ -59,57 +55,6 @@ func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query acces
return result, err
}
func userRolesFilter(orgID, userID int64, teamIDs []int64, roles []string) (string, []interface{}) {
var params []interface{}
builder := strings.Builder{}
// This is an additional security. We should never have permissions granted to userID 0.
// Only allow real users to get user/team permissions (anonymous/apikeys)
if userID > 0 {
builder.WriteString(`
SELECT ur.role_id
FROM user_role AS ur
WHERE ur.user_id = ?
AND (ur.org_id = ? OR ur.org_id = ?)
`)
params = []interface{}{userID, orgID, globalOrgID}
}
if len(teamIDs) > 0 {
if builder.Len() > 0 {
builder.WriteString("UNION")
}
builder.WriteString(`
SELECT tr.role_id FROM team_role as tr
WHERE tr.team_id IN(?` + strings.Repeat(", ?", len(teamIDs)-1) + `)
AND tr.org_id = ?
`)
for _, id := range teamIDs {
params = append(params, id)
}
params = append(params, orgID)
}
if len(roles) != 0 {
if builder.Len() > 0 {
builder.WriteString("UNION")
}
builder.WriteString(`
SELECT br.role_id FROM builtin_role AS br
WHERE br.role IN (?` + strings.Repeat(", ?", len(roles)-1) + `)
AND (br.org_id = ? OR br.org_id = ?)
`)
for _, role := range roles {
params = append(params, role)
}
params = append(params, orgID, globalOrgID)
}
return "INNER JOIN (" + builder.String() + ") as all_role ON role.id = all_role.role_id", params
}
func (s *AccessControlStore) DeleteUserPermissions(ctx context.Context, orgID, userID int64) error {
err := s.sql.WithDbSession(ctx, func(sess *db.Session) error {
roleDeleteQuery := "DELETE FROM user_role WHERE user_id = ?"

View File

@ -125,3 +125,54 @@ func SetAcceptListForTest(list map[string]struct{}) func() {
sqlIDAcceptList = original
}
}
func UserRolesFilter(orgID, userID int64, teamIDs []int64, roles []string) (string, []interface{}) {
var params []interface{}
builder := strings.Builder{}
// This is an additional security. We should never have permissions granted to userID 0.
// Only allow real users to get user/team permissions (anonymous/apikeys)
if userID > 0 {
builder.WriteString(`
SELECT ur.role_id
FROM user_role AS ur
WHERE ur.user_id = ?
AND (ur.org_id = ? OR ur.org_id = ?)
`)
params = []interface{}{userID, orgID, GlobalOrgID}
}
if len(teamIDs) > 0 {
if builder.Len() > 0 {
builder.WriteString("UNION")
}
builder.WriteString(`
SELECT tr.role_id FROM team_role as tr
WHERE tr.team_id IN(?` + strings.Repeat(", ?", len(teamIDs)-1) + `)
AND tr.org_id = ?
`)
for _, id := range teamIDs {
params = append(params, id)
}
params = append(params, orgID)
}
if len(roles) != 0 {
if builder.Len() > 0 {
builder.WriteString("UNION")
}
builder.WriteString(`
SELECT br.role_id FROM builtin_role AS br
WHERE br.role IN (?` + strings.Repeat(", ?", len(roles)-1) + `)
AND (br.org_id = ? OR br.org_id = ?)
`)
for _, role := range roles {
params = append(params, role)
}
params = append(params, orgID, GlobalOrgID)
}
return "INNER JOIN (" + builder.String() + ") as all_role ON role.id = all_role.role_id", params
}

View File

@ -5,7 +5,9 @@ import (
"fmt"
"strings"
"testing"
"time"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -407,13 +409,15 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
t.Skip("skipping integration test")
}
sql := db.InitTestDB(t)
var maximumTagsLength int64 = 60
repo := xormRepositoryImpl{db: sql, cfg: setting.NewCfg(), log: log.New("annotation.test"), tagService: tagimpl.ProvideService(sql, sql.Cfg), maximumTagsLength: maximumTagsLength}
dashboardStore := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg))
testDashboard1 := models.SaveDashboardCommand{
UserId: 1,
OrgId: 1,
UserId: 1,
OrgId: 1,
IsFolder: false,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "Dashboard 1",
}),
@ -459,6 +463,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
UserID: 1,
OrgID: 1,
}
role := setupRBACRole(t, repo, user)
type testStruct struct {
description string
@ -519,6 +524,8 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
user.Permissions = map[int64]map[string][]string{1: tc.permissions}
setupRBACPermission(t, repo, role, user)
results, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1,
SignedInUser: user,
@ -535,3 +542,61 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
})
}
}
func setupRBACRole(t *testing.T, repo xormRepositoryImpl, user *user.SignedInUser) *accesscontrol.Role {
t.Helper()
var role *accesscontrol.Role
err := repo.db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
role = &accesscontrol.Role{
OrgID: user.OrgID,
UID: "test_role",
Name: "test:role",
Updated: time.Now(),
Created: time.Now(),
}
_, err := sess.Insert(role)
if err != nil {
return err
}
_, err = sess.Insert(accesscontrol.UserRole{
OrgID: role.OrgID,
RoleID: role.ID,
UserID: user.UserID,
Created: time.Now(),
})
if err != nil {
return err
}
return nil
})
require.NoError(t, err)
return role
}
func setupRBACPermission(t *testing.T, repo xormRepositoryImpl, role *accesscontrol.Role, user *user.SignedInUser) {
t.Helper()
err := repo.db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
if _, err := sess.Exec("DELETE FROM permission WHERE role_id = ?", role.ID); err != nil {
return err
}
var acPermission []accesscontrol.Permission
for action, scopes := range user.Permissions[user.OrgID] {
for _, scope := range scopes {
acPermission = append(acPermission, accesscontrol.Permission{
RoleID: role.ID, Action: action, Scope: scope, Created: time.Now(), Updated: time.Now(),
})
}
}
if _, err := sess.InsertMulti(&acPermission); err != nil {
return err
}
return nil
})
require.NoError(t, err)
}

View File

@ -80,9 +80,9 @@ func (d DashboardPermissionFilter) Where() (string, []interface{}) {
}
type AccessControlDashboardPermissionFilter struct {
User *user.SignedInUser
dashboardActions []string
user *user.SignedInUser
folderActions []string
dashboardActions []string
}
// NewAccessControlDashboardPermissionFilter creates a new AccessControlDashboardPermissionFilter that is configured with specific actions calculated based on the models.PermissionType and query type
@ -102,39 +102,80 @@ func NewAccessControlDashboardPermissionFilter(user *user.SignedInUser, permissi
dashboardActions = append(dashboardActions, dashboards.ActionDashboardsWrite)
}
}
return AccessControlDashboardPermissionFilter{User: user, folderActions: folderActions, dashboardActions: dashboardActions}
return AccessControlDashboardPermissionFilter{user: user, folderActions: folderActions, dashboardActions: dashboardActions}
}
func (f AccessControlDashboardPermissionFilter) Where() (string, []interface{}) {
if f.user == nil || f.user.Permissions == nil || f.user.Permissions[f.user.OrgID] == nil {
return "(1 = 0)", nil
}
dashWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeDashboardsPrefix)
folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix)
filter, params := accesscontrol.UserRolesFilter(f.user.OrgID, f.user.UserID, f.user.Teams, accesscontrol.GetOrgRoles(f.user))
rolesFilter := "AND role_id IN(SELECT distinct id FROM role " + filter + ")"
var args []interface{}
builder := strings.Builder{}
builder.WriteString("(")
builder.WriteRune('(')
if len(f.dashboardActions) > 0 {
builder.WriteString("((")
actionsToCheck := make([]interface{}, 0, len(f.dashboardActions))
for _, action := range f.dashboardActions {
var hasWildcard bool
for _, scope := range f.user.Permissions[f.user.OrgID][action] {
if dashWildcards.Contains(scope) || folderWildcards.Contains(scope) {
hasWildcard = true
break
}
}
if !hasWildcard {
actionsToCheck = append(actionsToCheck, action)
}
}
dashFilter, _ := accesscontrol.Filter(f.User, "dashboard.uid", dashboards.ScopeDashboardsPrefix, f.dashboardActions...)
builder.WriteString(dashFilter.Where)
args = append(args, dashFilter.Args...)
if len(actionsToCheck) > 0 {
builder.WriteString("(dashboard.uid IN (SELECT substr(scope, 16) FROM permission WHERE action IN (?" + strings.Repeat(", ?", len(actionsToCheck)-1) + ") AND scope LIKE 'dashboards:uid:%' " + rolesFilter + " GROUP BY role_id, scope HAVING COUNT(action) = ?) AND NOT dashboard.is_folder)")
args = append(args, actionsToCheck...)
args = append(args, params...)
args = append(args, len(actionsToCheck))
builder.WriteString(" OR dashboard.folder_id IN(SELECT id FROM dashboard WHERE ")
dashFolderFilter, _ := accesscontrol.Filter(f.User, "dashboard.uid", dashboards.ScopeFoldersPrefix, f.dashboardActions...)
builder.WriteString(dashFolderFilter.Where)
builder.WriteString(")) AND NOT dashboard.is_folder)")
args = append(args, dashFolderFilter.Args...)
builder.WriteString(" OR ")
builder.WriteString("(dashboard.folder_id IN (SELECT id FROM dashboard as d WHERE d.uid IN (SELECT substr(scope, 13) FROM permission WHERE action IN (?" + strings.Repeat(", ?", len(actionsToCheck)-1) + ") AND scope LIKE 'folders:uid:%' " + rolesFilter + " GROUP BY role_id, scope HAVING COUNT(action) = ?)) AND NOT dashboard.is_folder)")
args = append(args, actionsToCheck...)
args = append(args, params...)
args = append(args, len(actionsToCheck))
} else {
builder.WriteString("NOT dashboard.is_folder")
}
}
if len(f.folderActions) > 0 {
if len(f.dashboardActions) > 0 {
builder.WriteString(" OR ")
}
builder.WriteString("(")
folderFilter, _ := accesscontrol.Filter(f.User, "dashboard.uid", dashboards.ScopeFoldersPrefix, f.folderActions...)
builder.WriteString(folderFilter.Where)
builder.WriteString(" AND dashboard.is_folder)")
args = append(args, folderFilter.Args...)
actionsToCheck := make([]interface{}, 0, len(f.folderActions))
for _, action := range f.folderActions {
var hasWildcard bool
for _, scope := range f.user.Permissions[f.user.OrgID][action] {
if folderWildcards.Contains(scope) {
hasWildcard = true
break
}
}
if !hasWildcard {
actionsToCheck = append(actionsToCheck, action)
}
}
if len(actionsToCheck) > 0 {
builder.WriteString("(dashboard.uid IN (SELECT substr(scope, 13) FROM permission WHERE action IN (?" + strings.Repeat(", ?", len(actionsToCheck)-1) + ") AND scope LIKE 'folders:uid:%' " + rolesFilter + " GROUP BY role_id, scope HAVING COUNT(action) = ?) AND dashboard.is_folder)")
args = append(args, actionsToCheck...)
args = append(args, params...)
args = append(args, len(actionsToCheck))
} else {
builder.WriteString("dashboard.is_folder")
}
}
builder.WriteString(")")
builder.WriteRune(')')
return builder.String(), args
}

View File

@ -1,148 +1,229 @@
package permissions
package permissions_test
import (
"fmt"
"context"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"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/org"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewAccessControlDashboardPermissionFilter(t *testing.T) {
randomType := "random_" + util.GenerateShortUID()
testCases := []struct {
permission models.PermissionType
queryType string
expectedDashboardActions []string
expectedFolderActions []string
}{
func TestIntegration_DashboardPermissionFilter(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
type testCase struct {
desc string
queryType string
permission models.PermissionType
permissions []accesscontrol.Permission
expectedResult int
}
tests := []testCase{
{
queryType: searchstore.TypeAlertFolder,
permission: models.PERMISSION_ADMIN,
expectedDashboardActions: nil,
expectedFolderActions: []string{
dashboards.ActionFoldersRead,
accesscontrol.ActionAlertingRuleRead,
accesscontrol.ActionAlertingRuleCreate,
},
},
{
queryType: searchstore.TypeAlertFolder,
permission: models.PERMISSION_EDIT,
expectedDashboardActions: nil,
expectedFolderActions: []string{
dashboards.ActionFoldersRead,
accesscontrol.ActionAlertingRuleRead,
accesscontrol.ActionAlertingRuleCreate,
},
},
{
queryType: searchstore.TypeAlertFolder,
permission: models.PERMISSION_VIEW,
expectedDashboardActions: nil,
expectedFolderActions: []string{
dashboards.ActionFoldersRead,
accesscontrol.ActionAlertingRuleRead,
},
},
{
queryType: randomType,
permission: models.PERMISSION_ADMIN,
expectedDashboardActions: []string{
dashboards.ActionDashboardsRead,
dashboards.ActionDashboardsWrite,
},
expectedFolderActions: []string{
dashboards.ActionFoldersRead,
dashboards.ActionDashboardsCreate,
},
},
{
queryType: randomType,
permission: models.PERMISSION_EDIT,
expectedDashboardActions: []string{
dashboards.ActionDashboardsRead,
dashboards.ActionDashboardsWrite,
},
expectedFolderActions: []string{
dashboards.ActionFoldersRead,
dashboards.ActionDashboardsCreate,
},
},
{
queryType: randomType,
desc: "Should be able to view all dashboards with wildcard scope",
permission: models.PERMISSION_VIEW,
expectedDashboardActions: []string{
dashboards.ActionDashboardsRead,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeDashboardsAll},
},
expectedFolderActions: []string{
dashboards.ActionFoldersRead,
expectedResult: 100,
},
{
desc: "Should be able to view all dashboards with folder wildcard scope",
permission: models.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
},
expectedResult: 100,
},
{
desc: "Should be able to view a subset of dashboards with dashboard scopes",
permission: models.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:110"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:40"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:22"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:13"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:55"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:99"},
},
expectedResult: 6,
},
{
desc: "Should be able to view a subset of dashboards with dashboard action and folder scope",
permission: models.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:8"},
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:10"},
},
expectedResult: 20,
},
{
desc: "Should be able to view all folders with folder wildcard",
permission: models.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:*"},
},
expectedResult: 10,
},
{
desc: "Should be able to view a subset folders",
permission: models.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:6"},
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:9"},
},
expectedResult: 3,
},
{
desc: "Should return folders and dashboard with 'edit' permission",
permission: models.PERMISSION_EDIT,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionDashboardsCreate, Scope: "folders:uid:3"},
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:33"},
{Action: dashboards.ActionDashboardsWrite, Scope: "dashboards:uid:33"},
},
expectedResult: 2,
},
{
desc: "Should return folders that users can read alerts from",
permission: models.PERMISSION_VIEW,
queryType: searchstore.TypeAlertFolder,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:3"},
{Action: accesscontrol.ActionAlertingRuleRead, Scope: "folders:uid:3"},
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:8"},
{Action: accesscontrol.ActionAlertingRuleRead, Scope: "folders:uid:8"},
},
expectedResult: 2,
},
{
desc: "Should return folders that users can read alerts when user has read wildcard",
permission: models.PERMISSION_VIEW,
queryType: searchstore.TypeAlertFolder,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "*"},
{Action: accesscontrol.ActionAlertingRuleRead, Scope: "folders:uid:3"},
{Action: accesscontrol.ActionAlertingRuleRead, Scope: "folders:uid:8"},
},
expectedResult: 2,
},
}
for _, testCase := range testCases {
t.Run(fmt.Sprintf("query type %s, permissions %s", testCase.queryType, testCase.permission), func(t *testing.T) {
filters := NewAccessControlDashboardPermissionFilter(&user.SignedInUser{}, testCase.permission, testCase.queryType)
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
store := setupTest(t, 10, 100, tt.permissions)
usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}
filter := permissions.NewAccessControlDashboardPermissionFilter(usr, tt.permission, tt.queryType)
require.Equal(t, testCase.expectedDashboardActions, filters.dashboardActions)
require.Equal(t, testCase.expectedFolderActions, filters.folderActions)
var result int
err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
q, params := filter.Where()
_, err := sess.SQL("SELECT COUNT(*) FROM dashboard WHERE "+q, params...).Get(&result)
return err
})
require.NoError(t, err)
assert.Equal(t, tt.expectedResult, result)
})
}
}
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 dashboard.folder_id IN(SELECT id FROM dashboard WHERE 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 dashboard.folder_id IN(SELECT id FROM dashboard WHERE 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: &user.SignedInUser{Permissions: map[int64]map[string][]string{}},
dashboardActions: testCase.dashboardActions,
folderActions: testCase.folderActions,
func setupTest(t *testing.T, numFolders, numDashboards int, permissions []accesscontrol.Permission) db.DB {
store := db.InitTestDB(t)
err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
dashes := make([]models.Dashboard, 0, numFolders+numDashboards)
for i := 1; i <= numFolders; i++ {
str := strconv.Itoa(i)
dashes = append(dashes, models.Dashboard{
OrgId: 1,
Slug: str,
Uid: str,
Title: str,
IsFolder: true,
Data: simplejson.New(),
Created: time.Now(),
Updated: time.Now(),
})
}
// Seed 100 dashboard
for i := numFolders + 1; i <= numFolders+numDashboards; i++ {
str := strconv.Itoa(i)
folderID := numFolders
if i%numFolders != 0 {
folderID = i % numFolders
}
dashes = append(dashes, models.Dashboard{
OrgId: 1,
IsFolder: false,
FolderId: int64(folderID),
Uid: str,
Slug: str,
Title: str,
Data: simplejson.New(),
Created: time.Now(),
Updated: time.Now(),
})
}
query, args := filter.Where()
_, err := sess.InsertMulti(&dashes)
if err != nil {
return err
}
assert.Empty(t, args)
assert.Equal(t, testCase.expectedResult, query)
role := &accesscontrol.Role{
OrgID: 0,
UID: "basic_viewer",
Name: "basic:viewer",
Updated: time.Now(),
Created: time.Now(),
}
_, err = sess.Insert(role)
if err != nil {
return err
}
_, err = sess.Insert(accesscontrol.BuiltinRole{
OrgID: 0,
RoleID: role.ID,
Role: "Viewer",
Created: time.Now(),
Updated: time.Now(),
})
}
if err != nil {
return err
}
for i := range permissions {
permissions[i].RoleID = role.ID
permissions[i].Created = time.Now()
permissions[i].Updated = time.Now()
}
if len(permissions) > 0 {
_, err = sess.InsertMulti(&permissions)
if err != nil {
return err
}
}
return nil
})
require.NoError(t, err)
return store
}

View File

@ -0,0 +1,143 @@
package permissions_test
import (
"context"
"fmt"
"strconv"
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"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/org"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func benchmarkDashboardPermissionFilter(b *testing.B, numUsers, numDashboards int) {
store := setupBenchMark(b, numUsers, numDashboards)
b.ResetTimer()
for i := 0; i < b.N; i++ {
usr := &user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{1: {}}}
filter := permissions.NewAccessControlDashboardPermissionFilter(usr, models.PERMISSION_VIEW, "")
var result int
err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
q, params := filter.Where()
_, err := sess.SQL("SELECT COUNT(*) FROM dashboard WHERE "+q, params...).Get(&result)
return err
})
require.NoError(b, err)
assert.Equal(b, numDashboards, result)
}
}
func setupBenchMark(b *testing.B, numUsers, numDashboards int) db.DB {
store := db.InitTestDB(b)
now := time.Now()
err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
dashes := make([]models.Dashboard, 0, numDashboards)
for i := 1; i <= numDashboards; i++ {
str := strconv.Itoa(i)
dashes = append(dashes, models.Dashboard{
OrgId: 1,
IsFolder: false,
Uid: str,
Slug: str,
Title: str,
Data: simplejson.New(),
Created: now,
Updated: now,
})
}
err := batch(len(dashes), 1000, func(start, end int) error {
_, err := sess.InsertMulti(dashes[start:end])
return err
})
require.NoError(b, err)
roles := make([]accesscontrol.Role, 0, numUsers)
assignments := make([]accesscontrol.UserRole, 0, numUsers)
permissions := make([]accesscontrol.Permission, 0, numUsers*numDashboards)
for i := 1; i <= numUsers; i++ {
name := fmt.Sprintf("managed_%d", i)
roles = append(roles, accesscontrol.Role{
UID: name,
Name: name,
Updated: now,
Created: now,
})
assignments = append(assignments, accesscontrol.UserRole{
RoleID: int64(i),
UserID: int64(i),
Created: now,
})
for _, dash := range dashes {
permissions = append(permissions, accesscontrol.Permission{
RoleID: int64(i),
Action: dashboards.ActionDashboardsRead,
Scope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dash.Uid),
Updated: now,
Created: now,
})
}
}
err = batch(len(roles), 5000, func(start, end int) error {
_, err := sess.InsertMulti(roles[start:end])
return err
})
require.NoError(b, err)
err = batch(len(assignments), 5000, func(start, end int) error {
_, err := sess.InsertMulti(assignments[start:end])
return err
})
require.NoError(b, err)
err = batch(len(permissions), 5000, func(start, end int) error {
_, err := sess.InsertMulti(permissions[start:end])
return err
})
require.NoError(b, err)
return nil
})
require.NoError(b, err)
return store
}
func BenchmarkDashboardPermissionFilter_100_100(b *testing.B) {
benchmarkDashboardPermissionFilter(b, 100, 100)
}
func BenchmarkDashboardPermissionFilter_100_1000(b *testing.B) {
benchmarkDashboardPermissionFilter(b, 100, 1000)
}
func BenchmarkDashboardPermissionFilter_300_10000(b *testing.B) {
benchmarkDashboardPermissionFilter(b, 300, 10000)
}
func batch(count, batchSize int, eachFn func(start, end int) error) error {
for i := 0; i < count; {
end := i + batchSize
if end > count {
end = count
}
if err := eachFn(i, end); err != nil {
return err
}
i = end
}
return nil
}