RBAC: Fix dashboard filter in SQLBuilder (#53379)

* Reuse DasbhoardPermissionFilter

*  Use rbac dashboard filter if enabled
This commit is contained in:
Karl Persson
2022-08-10 10:32:03 +02:00
committed by GitHub
parent 6d495a6a8e
commit aa484a60c9
7 changed files with 48 additions and 69 deletions

View File

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/db" "github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/setting"
) )
// AlertStore is a subset of SQLStore API to satisfy the needs of the alerting service. // AlertStore is a subset of SQLStore API to satisfy the needs of the alerting service.
@ -34,15 +35,17 @@ type sqlStore struct {
db db.DB db db.DB
cache *localcache.CacheService cache *localcache.CacheService
log *log.ConcreteLogger log *log.ConcreteLogger
cfg *setting.Cfg
} }
func ProvideAlertStore( func ProvideAlertStore(
db db.DB, db db.DB,
cacheService *localcache.CacheService) AlertStore { cacheService *localcache.CacheService, cfg *setting.Cfg) AlertStore {
return &sqlStore{ return &sqlStore{
db: db, db: db,
cache: cacheService, cache: cacheService,
log: log.New("alerting.store"), log: log.New("alerting.store"),
cfg: cfg,
} }
} }
@ -99,7 +102,7 @@ func deleteAlertByIdInternal(alertId int64, reason string, sess *sqlstore.DBSess
func (ss *sqlStore) HandleAlertsQuery(ctx context.Context, query *models.GetAlertsQuery) error { func (ss *sqlStore) HandleAlertsQuery(ctx context.Context, query *models.GetAlertsQuery) error {
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
builder := sqlstore.SQLBuilder{} builder := sqlstore.NewSqlBuilder(ss.cfg)
builder.Write(`SELECT builder.Write(`SELECT
alert.id, alert.id,

View File

@ -12,6 +12,7 @@ import (
dashver "github.com/grafana/grafana/pkg/services/dashboardversion" dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/db" "github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -46,6 +47,7 @@ func TestIntegrationAlertingDataAccess(t *testing.T) {
store = &sqlStore{ store = &sqlStore{
db: sqlstore.InitTestDB(t), db: sqlstore.InitTestDB(t),
log: log.New(), log: log.New(),
cfg: setting.NewCfg(),
} }
testDash = insertTestDashboard(t, store.db, "dashboard with alerts", 1, 0, false, "alert") testDash = insertTestDashboard(t, store.db, "dashboard with alerts", 1, 0, false, "alert")

View File

@ -102,7 +102,7 @@ func (d *DashboardStore) HasEditPermissionInFolders(ctx context.Context, query *
return nil return nil
} }
builder := &sqlstore.SQLBuilder{} builder := sqlstore.NewSqlBuilder(d.sqlStore.Cfg)
builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?",
query.SignedInUser.OrgId, d.dialect.BooleanStr(true)) query.SignedInUser.OrgId, d.dialect.BooleanStr(true))
builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_EDIT) builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_EDIT)
@ -130,7 +130,7 @@ func (d *DashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Con
return nil return nil
} }
builder := &sqlstore.SQLBuilder{} builder := sqlstore.NewSqlBuilder(d.sqlStore.Cfg)
builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ?", query.SignedInUser.OrgId) builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ?", query.SignedInUser.OrgId)
builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_ADMIN) builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_ADMIN)

View File

@ -223,7 +223,7 @@ func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedIn
func getLibraryElements(c context.Context, store *sqlstore.SQLStore, signedInUser *models.SignedInUser, params []Pair) ([]LibraryElementDTO, error) { func getLibraryElements(c context.Context, store *sqlstore.SQLStore, signedInUser *models.SignedInUser, params []Pair) ([]LibraryElementDTO, error) {
libraryElements := make([]LibraryElementWithMeta, 0) libraryElements := make([]LibraryElementWithMeta, 0)
err := store.WithDbSession(c, func(session *sqlstore.DBSession) error { err := store.WithDbSession(c, func(session *sqlstore.DBSession) error {
builder := sqlstore.SQLBuilder{} builder := sqlstore.NewSqlBuilder(store.Cfg)
builder.Write(selectLibraryElementDTOWithMeta) builder.Write(selectLibraryElementDTOWithMeta)
builder.Write(", 'General' as folder_name ") builder.Write(", 'General' as folder_name ")
builder.Write(", '' as folder_uid ") builder.Write(", '' as folder_uid ")
@ -327,7 +327,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI
return LibraryElementSearchResult{}, folderFilter.parseError return LibraryElementSearchResult{}, folderFilter.parseError
} }
err := l.SQLStore.WithDbSession(c, func(session *sqlstore.DBSession) error { err := l.SQLStore.WithDbSession(c, func(session *sqlstore.DBSession) error {
builder := sqlstore.SQLBuilder{} builder := sqlstore.NewSqlBuilder(l.Cfg)
if folderFilter.includeGeneralFolder { if folderFilter.includeGeneralFolder {
builder.Write(selectLibraryElementDTOWithMeta) builder.Write(selectLibraryElementDTOWithMeta)
builder.Write(", 'General' as folder_name ") builder.Write(", 'General' as folder_name ")
@ -561,7 +561,7 @@ func (l *LibraryElementService) getConnections(c context.Context, signedInUser *
return err return err
} }
var libraryElementConnections []libraryElementConnectionWithMeta var libraryElementConnections []libraryElementConnectionWithMeta
builder := sqlstore.SQLBuilder{} builder := sqlstore.NewSqlBuilder(l.Cfg)
builder.Write("SELECT lec.*, u1.login AS created_by_name, u1.email AS created_by_email, dashboard.uid AS connection_uid") builder.Write("SELECT lec.*, u1.login AS created_by_name, u1.email AS created_by_email, dashboard.uid AS connection_uid")
builder.Write(" FROM " + models.LibraryElementConnectionTableName + " AS lec") builder.Write(" FROM " + models.LibraryElementConnectionTableName + " AS lec")
builder.Write(" LEFT JOIN " + l.SQLStore.Dialect.Quote("user") + " AS u1 ON lec.created_by = u1.id") builder.Write(" LEFT JOIN " + l.SQLStore.Dialect.Quote("user") + " AS u1 ON lec.created_by = u1.id")

View File

@ -403,21 +403,21 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
sqlStore := sqlstore.InitTestDB(t) sqlStore := sqlstore.InitTestDB(t)
dashboardStore := database.ProvideDashboardStore(sqlStore, featuremgmt.WithFeatures()) dashboardStore := database.ProvideDashboardStore(sqlStore, featuremgmt.WithFeatures())
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
cfg := setting.NewCfg()
cfg.IsFeatureToggleEnabled = features.IsEnabled
ac := acmock.New() ac := acmock.New()
// TODO: Update tests to work with rbac
sqlStore.Cfg.RBACEnabled = false
folderPermissions := acmock.NewMockedPermissionsService() folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService()
dashboardService := dashboardservice.ProvideDashboardService( dashboardService := dashboardservice.ProvideDashboardService(
cfg, dashboardStore, nil, sqlStore.Cfg, dashboardStore, nil,
features, folderPermissions, dashboardPermissions, ac, features, folderPermissions, dashboardPermissions, ac,
) )
guardian.InitLegacyGuardian(sqlStore, dashboardService) guardian.InitLegacyGuardian(sqlStore, dashboardService)
service := LibraryElementService{ service := LibraryElementService{
Cfg: cfg, Cfg: sqlStore.Cfg,
SQLStore: sqlStore, SQLStore: sqlStore,
folderService: dashboardservice.ProvideFolderService( folderService: dashboardservice.ProvideFolderService(
cfg, dashboardService, dashboardStore, nil, sqlStore.Cfg, dashboardService, dashboardStore, nil,
features, folderPermissions, ac, busmock.New(), features, folderPermissions, ac, busmock.New(),
), ),
} }

View File

@ -2,12 +2,19 @@ package sqlstore
import ( import (
"bytes" "bytes"
"strings"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/setting"
) )
func NewSqlBuilder(cfg *setting.Cfg) SQLBuilder {
return SQLBuilder{cfg: cfg}
}
type SQLBuilder struct { type SQLBuilder struct {
cfg *setting.Cfg
sql bytes.Buffer sql bytes.Buffer
params []interface{} params []interface{}
} }
@ -33,61 +40,22 @@ func (sb *SQLBuilder) AddParams(params ...interface{}) {
} }
func (sb *SQLBuilder) WriteDashboardPermissionFilter(user *models.SignedInUser, permission models.PermissionType) { func (sb *SQLBuilder) WriteDashboardPermissionFilter(user *models.SignedInUser, permission models.PermissionType) {
if user.OrgRole == models.ROLE_ADMIN { var (
return sql string
params []interface{}
)
if !ac.IsDisabled(sb.cfg) {
sql, params = permissions.NewAccessControlDashboardPermissionFilter(user, permission, "").Where()
} else {
sql, params = permissions.DashboardPermissionFilter{
OrgRole: user.OrgRole,
Dialect: dialect,
UserId: user.UserId,
OrgId: user.OrgId,
PermissionLevel: permission,
}.Where()
} }
okRoles := []interface{}{user.OrgRole} sb.sql.WriteString(" AND " + sql)
sb.params = append(sb.params, params...)
if user.OrgRole == models.ROLE_EDITOR {
okRoles = append(okRoles, models.ROLE_VIEWER)
}
falseStr := dialect.BooleanStr(false)
sb.sql.WriteString(` AND
(
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
)
)`)
sb.params = append(sb.params, user.OrgId, permission, user.UserId, user.UserId)
sb.params = append(sb.params, okRoles...)
sb.params = append(sb.params, user.OrgId, permission, user.UserId)
sb.params = append(sb.params, okRoles...)
} }

View File

@ -299,7 +299,13 @@ func createDummyACL(t *testing.T, sqlStore *SQLStore, dashboardPermission *Dashb
func getDashboards(t *testing.T, sqlStore *SQLStore, search Search, aclUserID int64) []*dashboardResponse { func getDashboards(t *testing.T, sqlStore *SQLStore, search Search, aclUserID int64) []*dashboardResponse {
t.Helper() t.Helper()
builder := &SQLBuilder{} old := sqlStore.Cfg.RBACEnabled
sqlStore.Cfg.RBACEnabled = false
defer func() {
sqlStore.Cfg.RBACEnabled = old
}()
builder := NewSqlBuilder(sqlStore.Cfg)
signedInUser := &models.SignedInUser{ signedInUser := &models.SignedInUser{
UserId: 9999999999, UserId: 9999999999,
} }