Files
grafana/pkg/services/sqlstore/permissions/dashboard.go
Karl Persson 7386f8652c 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>
2022-10-25 11:14:27 +02:00

182 lines
6.5 KiB
Go

package permissions
import (
"strings"
"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/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/user"
)
type DashboardPermissionFilter struct {
OrgRole org.RoleType
Dialect migrator.Dialect
UserId int64
OrgId int64
PermissionLevel models.PermissionType
}
func (d DashboardPermissionFilter) Where() (string, []interface{}) {
if d.OrgRole == org.RoleAdmin {
return "", nil
}
okRoles := []interface{}{d.OrgRole}
if d.OrgRole == org.RoleEditor {
okRoles = append(okRoles, org.RoleViewer)
}
falseStr := d.Dialect.BooleanStr(false)
sql := `(
dashboard.id IN (
SELECT distinct DashboardId from (
SELECT d.id AS DashboardId
FROM dashboard AS d
LEFT JOIN dashboard_acl AS da ON
da.dashboard_id = d.id OR
da.dashboard_id = d.folder_id
WHERE
d.org_id = ? AND
da.permission >= ? AND
(
da.user_id = ? OR
da.team_id IN (SELECT team_id from team_member AS tm WHERE tm.user_id = ?) OR
da.role IN (?` + strings.Repeat(",?", len(okRoles)-1) + `)
)
UNION
SELECT d.id AS DashboardId
FROM dashboard AS d
LEFT JOIN dashboard AS folder on folder.id = d.folder_id
LEFT JOIN dashboard_acl AS da ON
(
-- include default permissions -->
da.org_id = -1 AND (
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
)
)
WHERE
d.org_id = ? AND
da.permission >= ? AND
(
da.user_id = ? OR
da.role IN (?` + strings.Repeat(",?", len(okRoles)-1) + `)
)
) AS a
)
)
`
params := []interface{}{d.OrgId, d.PermissionLevel, d.UserId, d.UserId}
params = append(params, okRoles...)
params = append(params, d.OrgId, d.PermissionLevel, d.UserId)
params = append(params, okRoles...)
return sql, params
}
type AccessControlDashboardPermissionFilter struct {
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
func NewAccessControlDashboardPermissionFilter(user *user.SignedInUser, permissionLevel models.PermissionType, queryType string) AccessControlDashboardPermissionFilter {
needEdit := permissionLevel > models.PERMISSION_VIEW
folderActions := []string{dashboards.ActionFoldersRead}
var dashboardActions []string
if queryType == searchstore.TypeAlertFolder {
folderActions = append(folderActions, accesscontrol.ActionAlertingRuleRead)
if needEdit {
folderActions = append(folderActions, accesscontrol.ActionAlertingRuleCreate)
}
} else {
dashboardActions = append(dashboardActions, dashboards.ActionDashboardsRead)
if needEdit {
folderActions = append(folderActions, dashboards.ActionDashboardsCreate)
dashboardActions = append(dashboardActions, dashboards.ActionDashboardsWrite)
}
}
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.WriteRune('(')
if len(f.dashboardActions) > 0 {
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)
}
}
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 ")
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 ")
}
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.WriteRune(')')
return builder.String(), args
}