diff --git a/pkg/plugins/manager/manager_integration_test.go b/pkg/plugins/manager/manager_integration_test.go index a79742404c2..f27b77a9cff 100644 --- a/pkg/plugins/manager/manager_integration_test.go +++ b/pkg/plugins/manager/manager_integration_test.go @@ -86,7 +86,7 @@ func TestPluginManager_int_init(t *testing.T) { pg := postgres.ProvideService(cfg) my := mysql.ProvideService(cfg, hcp) ms := mssql.ProvideService(cfg) - sv2 := searchV2.ProvideService(cfg, sqlstore.InitTestDB(t), nil) + sv2 := searchV2.ProvideService(cfg, sqlstore.InitTestDB(t), nil, nil) graf := grafanads.ProvideService(cfg, sv2, nil) coreRegistry := coreplugin.ProvideCoreRegistry(am, cw, cm, es, grap, idb, lk, otsdb, pr, tmpo, td, pg, my, ms, graf) diff --git a/pkg/services/searchV2/auth.go b/pkg/services/searchV2/auth.go index 4f2d8cb6706..0ef8ffc8849 100644 --- a/pkg/services/searchV2/auth.go +++ b/pkg/services/searchV2/auth.go @@ -4,8 +4,10 @@ import ( "context" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/permissions" + "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" ) // ResourceFilter checks if a given a uid (resource identifier) check if we have the requested permission @@ -16,26 +18,33 @@ type FutureAuthService interface { GetDashboardReadFilter(user *models.SignedInUser) (ResourceFilter, error) } +var _ FutureAuthService = (*simpleSQLAuthService)(nil) + type simpleSQLAuthService struct { sql *sqlstore.SQLStore + ac accesscontrol.AccessControl } type dashIdQueryResult struct { UID string `xorm:"uid"` } -func (a *simpleSQLAuthService) GetDashboardReadFilter(user *models.SignedInUser) (ResourceFilter, error) { - // this filter works on the legacy `dashboard_acl` table - // we will also need to use `accesscontrol` after https://github.com/grafana/grafana/pull/44702/files is merged - // see https://github.com/grafana/grafana/blob/e355bd6d3a04111b8c9959f85e81beabbeb746bf/pkg/services/sqlstore/permissions/dashboard.go#L84 - filter := permissions.DashboardPermissionFilter{ - OrgRole: user.OrgRole, - OrgId: user.OrgId, - Dialect: a.sql.Dialect, - UserId: user.UserId, - PermissionLevel: models.PERMISSION_VIEW, +func (a *simpleSQLAuthService) getDashboardTableAuthFilter(user *models.SignedInUser) searchstore.FilterWhere { + if a.ac.IsDisabled() { + return permissions.DashboardPermissionFilter{ + OrgRole: user.OrgRole, + OrgId: user.OrgId, + Dialect: a.sql.Dialect, + UserId: user.UserId, + PermissionLevel: models.PERMISSION_VIEW, + } } + return permissions.NewAccessControlDashboardPermissionFilter(user, models.PERMISSION_VIEW, searchstore.TypeDashboard) +} + +func (a *simpleSQLAuthService) GetDashboardReadFilter(user *models.SignedInUser) (ResourceFilter, error) { + filter := a.getDashboardTableAuthFilter(user) rows := make([]*dashIdQueryResult, 0) err := a.sql.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { diff --git a/pkg/services/searchV2/service.go b/pkg/services/searchV2/service.go index c08ff51d2a3..3147adccfe8 100644 --- a/pkg/services/searchV2/service.go +++ b/pkg/services/searchV2/service.go @@ -3,12 +3,14 @@ package searchV2 import ( "context" "encoding/json" + "errors" "fmt" "strconv" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/searchV2/extract" "github.com/grafana/grafana/pkg/services/sqlstore" @@ -25,17 +27,20 @@ type StandardSearchService struct { cfg *setting.Cfg sql *sqlstore.SQLStore auth FutureAuthService // eventually injected from elsewhere + ac accesscontrol.AccessControl logger log.Logger dashboardIndex *dashboardIndex } -func ProvideService(cfg *setting.Cfg, sql *sqlstore.SQLStore, entityEventStore store.EntityEventsService) SearchService { +func ProvideService(cfg *setting.Cfg, sql *sqlstore.SQLStore, entityEventStore store.EntityEventsService, ac accesscontrol.AccessControl) SearchService { return &StandardSearchService{ cfg: cfg, sql: sql, + ac: ac, auth: &simpleSQLAuthService{ sql: sql, + ac: ac, }, dashboardIndex: newDashboardIndex(&sqlDashboardLoader{sql: sql}, entityEventStore), logger: log.New("searchV2"), @@ -53,6 +58,52 @@ func (s *StandardSearchService) Run(ctx context.Context) error { return s.dashboardIndex.run(ctx) } +func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backend.User, orgId int64) (*models.SignedInUser, error) { + // TODO: get user & user's permissions from the request context + + getSignedInUserQuery := &models.GetSignedInUserQuery{ + Login: backendUser.Login, + Email: backendUser.Email, + OrgId: orgId, + } + + err := s.sql.GetSignedInUser(ctx, getSignedInUserQuery) + if err != nil { + s.logger.Error("Error while retrieving user", "error", err, "email", backendUser.Email) + return nil, errors.New("auth error") + } + + if getSignedInUserQuery.Result == nil { + s.logger.Error("No user found", "email", backendUser.Email) + return nil, errors.New("auth error") + } + + user := getSignedInUserQuery.Result + + if s.ac.IsDisabled() { + return user, nil + } + + if user.Permissions == nil { + user.Permissions = make(map[int64]map[string][]string) + } + + if _, ok := user.Permissions[orgId]; ok { + // permissions as part of the `s.sql.GetSignedInUser` query - return early + return user, nil + } + + permissions, err := s.ac.GetUserPermissions(ctx, user, + accesscontrol.Options{ReloadCache: false}) + if err != nil { + s.logger.Error("failed to retrieve user permissions", "error", err, "email", backendUser.Email) + return nil, errors.New("auth error") + } + + user.Permissions[orgId] = accesscontrol.GroupScopesByAction(permissions) + return user, nil +} + func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *backend.User, orgId int64, _ DashboardQuery) *backend.DataResponse { rsp := &backend.DataResponse{} @@ -62,27 +113,13 @@ func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *back return rsp } - // TODO - get user from context? - getSignedInUserQuery := &models.GetSignedInUserQuery{ - Login: user.Login, - Email: user.Email, - OrgId: orgId, - } - - err = s.sql.GetSignedInUser(ctx, getSignedInUserQuery) + signedInUser, err := s.getUser(ctx, user, orgId) if err != nil { - s.logger.Error("Error while retrieving user", "error", err) - rsp.Error = fmt.Errorf("auth error") + rsp.Error = err return rsp } - if getSignedInUserQuery.Result == nil { - s.logger.Error("No user found", "email", user.Email) - rsp.Error = fmt.Errorf("auth error") - return rsp - } - - dash, err = s.applyAuthFilter(getSignedInUserQuery.Result, dash) + dash, err = s.applyAuthFilter(signedInUser, dash) if err != nil { rsp.Error = err return rsp