diff --git a/pkg/services/searchV2/auth.go b/pkg/services/searchV2/auth.go new file mode 100644 index 00000000000..4f2d8cb6706 --- /dev/null +++ b/pkg/services/searchV2/auth.go @@ -0,0 +1,67 @@ +package searchV2 + +import ( + "context" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/permissions" +) + +// ResourceFilter checks if a given a uid (resource identifier) check if we have the requested permission +type ResourceFilter func(uid string) bool + +// FutureAuthService eventually implemented by the security service +type FutureAuthService interface { + GetDashboardReadFilter(user *models.SignedInUser) (ResourceFilter, error) +} + +type simpleSQLAuthService struct { + sql *sqlstore.SQLStore +} + +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, + } + + rows := make([]*dashIdQueryResult, 0) + + err := a.sql.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { + sql, params := filter.Where() + sess.Table("dashboard"). + Where(sql, params...). + Cols("uid") + + err := sess.Find(&rows) + if err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + uids := make(map[string]bool, len(rows)+1) + for i := 0; i < len(rows); i++ { + uids[rows[i].UID] = true + } + + return func(uid string) bool { + return uids[uid] + }, err +} diff --git a/pkg/services/searchV2/extract/dashboard.go b/pkg/services/searchV2/extract/dashboard.go index dac4a12305a..38e174e5b47 100644 --- a/pkg/services/searchV2/extract/dashboard.go +++ b/pkg/services/searchV2/extract/dashboard.go @@ -1,14 +1,13 @@ package extract import ( - "fmt" "io" jsoniter "github.com/json-iterator/go" ) func logf(format string, a ...interface{}) { - fmt.Printf(format, a...) + //fmt.Printf(format, a...) } // nolint:gocyclo diff --git a/pkg/services/searchV2/service.go b/pkg/services/searchV2/service.go index d93d31dc560..5591d627bbb 100644 --- a/pkg/services/searchV2/service.go +++ b/pkg/services/searchV2/service.go @@ -16,12 +16,16 @@ import ( ) type StandardSearchService struct { - sql *sqlstore.SQLStore + sql *sqlstore.SQLStore + auth FutureAuthService // eventually injected from elsewhere } func ProvideService(sql *sqlstore.SQLStore) SearchService { return &StandardSearchService{ sql: sql, + auth: &simpleSQLAuthService{ + sql: sql, + }, } } @@ -37,13 +41,34 @@ type dashMeta struct { func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *backend.User, orgId int64, query DashboardQuery) *backend.DataResponse { rsp := &backend.DataResponse{} - if user == nil || user.Role != string(models.ROLE_ADMIN) { - rsp.Error = fmt.Errorf("search is only supported for admin users while in early development") + // Load and parse all dashboards for given orgId + dash, err := loadDashboards(ctx, orgId, s.sql) + if err != nil { + rsp.Error = err return rsp } - // Load and parse all dashboards for given orgId - dash, err := loadDashboards(ctx, orgId, s.sql) + // TODO - get user from context? + getSignedInUserQuery := &models.GetSignedInUserQuery{ + Login: user.Login, + Email: user.Email, + OrgId: orgId, + } + + err = s.sql.GetSignedInUser(ctx, getSignedInUserQuery) + if err != nil { + fmt.Printf("error while retrieving user %s\n", err) + rsp.Error = fmt.Errorf("auth error") + return rsp + } + + if getSignedInUserQuery.Result == nil { + fmt.Printf("no user %s", user.Email) + rsp.Error = fmt.Errorf("auth error") + return rsp + } + + dash, err = s.applyAuthFilter(getSignedInUserQuery.Result, dash) if err != nil { rsp.Error = err return rsp @@ -54,6 +79,22 @@ func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *back return rsp } +func (s *StandardSearchService) applyAuthFilter(user *models.SignedInUser, dash []dashMeta) ([]dashMeta, error) { + filter, err := s.auth.GetDashboardReadFilter(user) + if err != nil { + return nil, err + } + + // create a list of all viewable dashboards for this user + res := make([]dashMeta, 0, len(dash)) + for _, dash := range dash { + if filter(dash.dash.UID) { + res = append(res, dash) + } + } + return res, nil +} + type dashDataQueryResult struct { Id int64 IsFolder bool `xorm:"is_folder"` diff --git a/public/app/plugins/datasource/grafana/components/QueryEditor.tsx b/public/app/plugins/datasource/grafana/components/QueryEditor.tsx index 57c45634b27..992fcf4f0e9 100644 --- a/public/app/plugins/datasource/grafana/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/grafana/components/QueryEditor.tsx @@ -11,7 +11,6 @@ import { import { GrafanaDatasource } from '../datasource'; import { defaultQuery, GrafanaQuery, GrafanaQueryType } from '../types'; import { config, getBackendSrv, getDataSourceSrv } from '@grafana/runtime'; -import { contextSrv } from 'app/core/services/context_srv'; type Props = QueryEditorProps; @@ -47,7 +46,7 @@ export class QueryEditor extends PureComponent { constructor(props: Props) { super(props); - if (config.featureToggles.panelTitleSearch && contextSrv.isGrafanaAdmin) { + if (config.featureToggles.panelTitleSearch) { this.queryTypes.push({ label: 'Search', value: GrafanaQueryType.Search,