RBAC: cascaded nested folder permissions for search v2 (#62608)

* fetch inherited folder scopes for search v2

* fix error and don't fetch parents for the general folder

* remove feature toggle check and lower log level
This commit is contained in:
Ieva 2023-02-06 17:38:15 +00:00 committed by GitHub
parent 161ff6d310
commit 49ccae7489
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 33 additions and 14 deletions

View File

@ -104,7 +104,7 @@ func TestIntegrationPluginManager(t *testing.T) {
pg := postgres.ProvideService(cfg) pg := postgres.ProvideService(cfg)
my := mysql.ProvideService(cfg, hcp) my := mysql.ProvideService(cfg, hcp)
ms := mssql.ProvideService(cfg) ms := mssql.ProvideService(cfg)
sv2 := searchV2.ProvideService(cfg, db.InitTestDB(t), nil, nil, tracer, features, nil, nil, nil) sv2 := searchV2.ProvideService(cfg, db.InitTestDB(t), nil, nil, tracer, features, nil, nil, nil, nil)
graf := grafanads.ProvideService(sv2, nil) graf := grafanads.ProvideService(sv2, nil)
phlare := phlare.ProvideService(hcp) phlare := phlare.ProvideService(hcp)
parca := parca.ProvideService(hcp) parca := parca.ProvideService(hcp)

View File

@ -86,7 +86,7 @@ var (
func service(t *testing.T) *StandardSearchService { func service(t *testing.T) *StandardSearchService {
service, ok := ProvideService(&setting.Cfg{Search: setting.SearchSettings{}}, service, ok := ProvideService(&setting.Cfg{Search: setting.SearchSettings{}},
nil, nil, accesscontrolmock.New(), tracing.InitializeTracerForTest(), featuremgmt.WithFeatures(), nil, nil, accesscontrolmock.New(), tracing.InitializeTracerForTest(), featuremgmt.WithFeatures(),
nil, nil, nil).(*StandardSearchService) nil, nil, nil, nil).(*StandardSearchService)
require.True(t, ok) require.True(t, ok)
return service return service
} }

View File

@ -4,8 +4,10 @@ import (
"context" "context"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions" "github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
) )
@ -15,28 +17,41 @@ type ResourceFilter func(kind entityKind, uid, parentUID string) bool
// FutureAuthService eventually implemented by the security service // FutureAuthService eventually implemented by the security service
type FutureAuthService interface { type FutureAuthService interface {
GetDashboardReadFilter(user *user.SignedInUser) (ResourceFilter, error) GetDashboardReadFilter(ctx context.Context, orgID int64, user *user.SignedInUser) (ResourceFilter, error)
} }
var _ FutureAuthService = (*simpleAuthService)(nil) var _ FutureAuthService = (*simpleAuthService)(nil)
type simpleAuthService struct { type simpleAuthService struct {
sql db.DB sql db.DB
ac accesscontrol.Service ac accesscontrol.Service
folderService folder.Service
logger log.Logger
} }
type dashIdQueryResult struct { type dashIdQueryResult struct {
UID string `xorm:"uid"` UID string `xorm:"uid"`
} }
func (a *simpleAuthService) GetDashboardReadFilter(user *user.SignedInUser) (ResourceFilter, error) { func (a *simpleAuthService) GetDashboardReadFilter(ctx context.Context, orgID int64, user *user.SignedInUser) (ResourceFilter, error) {
if !a.ac.IsDisabled() { if !a.ac.IsDisabled() {
canReadDashboard, canReadFolder := accesscontrol.Checker(user, dashboards.ActionDashboardsRead), accesscontrol.Checker(user, dashboards.ActionFoldersRead) canReadDashboard, canReadFolder := accesscontrol.Checker(user, dashboards.ActionDashboardsRead), accesscontrol.Checker(user, dashboards.ActionFoldersRead)
return func(kind entityKind, uid, parent string) bool { return func(kind entityKind, uid, parent string) bool {
if kind == entityKindFolder { if kind == entityKindFolder {
return canReadFolder(dashboards.ScopeFoldersProvider.GetResourceScopeUID(uid)) scopes, err := dashboards.GetInheritedScopes(ctx, orgID, uid, a.folderService)
if err != nil {
a.logger.Debug("could not retrieve inherited folder scopes:", "err", err)
}
scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(uid))
return canReadFolder(scopes...)
} else if kind == entityKindDashboard { } else if kind == entityKindDashboard {
return canReadDashboard(dashboards.ScopeDashboardsProvider.GetResourceScopeUID(uid), dashboards.ScopeFoldersProvider.GetResourceScopeUID(parent)) scopes, err := dashboards.GetInheritedScopes(ctx, orgID, parent, a.folderService)
if err != nil {
a.logger.Debug("could not retrieve inherited folder scopes:", "err", err)
}
scopes = append(scopes, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(uid))
scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(parent))
return canReadDashboard(scopes...)
} }
return false return false
}, nil }, nil

View File

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/querylibrary" "github.com/grafana/grafana/pkg/services/querylibrary"
"github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/services/store"
@ -84,15 +85,18 @@ func (s *StandardSearchService) IsReady(ctx context.Context, orgId int64) IsSear
func ProvideService(cfg *setting.Cfg, sql db.DB, entityEventStore store.EntityEventsService, func ProvideService(cfg *setting.Cfg, sql db.DB, entityEventStore store.EntityEventsService,
ac accesscontrol.Service, tracer tracing.Tracer, features featuremgmt.FeatureToggles, orgService org.Service, ac accesscontrol.Service, tracer tracing.Tracer, features featuremgmt.FeatureToggles, orgService org.Service,
userService user.Service, queries querylibrary.Service) SearchService { userService user.Service, queries querylibrary.Service, folderService folder.Service) SearchService {
extender := &NoopExtender{} extender := &NoopExtender{}
logger := log.New("searchV2")
s := &StandardSearchService{ s := &StandardSearchService{
cfg: cfg, cfg: cfg,
sql: sql, sql: sql,
ac: ac, ac: ac,
auth: &simpleAuthService{ auth: &simpleAuthService{
sql: sql, sql: sql,
ac: ac, ac: ac,
folderService: folderService,
logger: logger,
}, },
dashboardIndex: newSearchIndex( dashboardIndex: newSearchIndex(
newSQLDashboardLoader(sql, tracer, cfg.Search), newSQLDashboardLoader(sql, tracer, cfg.Search),
@ -103,7 +107,7 @@ func ProvideService(cfg *setting.Cfg, sql db.DB, entityEventStore store.EntityEv
features, features,
cfg.Search, cfg.Search,
), ),
logger: log.New("searchV2"), logger: logger,
extender: extender, extender: extender,
reIndexCh: make(chan struct{}, 1), reIndexCh: make(chan struct{}, 1),
orgService: orgService, orgService: orgService,
@ -244,7 +248,7 @@ func (s *StandardSearchService) doDashboardQuery(ctx context.Context, signedInUs
rsp := &backend.DataResponse{} rsp := &backend.DataResponse{}
filter, err := s.auth.GetDashboardReadFilter(signedInUser) filter, err := s.auth.GetDashboardReadFilter(ctx, orgID, signedInUser)
if err != nil { if err != nil {
dashboardSearchFailureRequestsCounter.With(prometheus.Labels{ dashboardSearchFailureRequestsCounter.With(prometheus.Labels{
"reason": "get_dashboard_filter_error", "reason": "get_dashboard_filter_error",

View File

@ -39,7 +39,7 @@ func setupBenchEnv(b *testing.B, folderCount, dashboardsPerFolder int) (*Standar
} }
querySvc := querylibraryimpl.ProvideService(cfg, features) querySvc := querylibraryimpl.ProvideService(cfg, features)
searchService, ok := ProvideService(cfg, sqlStore, store.NewDummyEntityEventsService(), actest.FakeService{}, searchService, ok := ProvideService(cfg, sqlStore, store.NewDummyEntityEventsService(), actest.FakeService{},
tracing.InitializeTracerForTest(), features, orgSvc, nil, querySvc).(*StandardSearchService) tracing.InitializeTracerForTest(), features, orgSvc, nil, querySvc, nil).(*StandardSearchService)
require.True(b, ok) require.True(b, ok)
err = runSearchService(searchService) err = runSearchService(searchService)