package sqlstore import ( "context" "strings" "xorm.io/xorm" "github.com/prometheus/client_golang/prometheus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/sqlstore/permissions" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/util" ) var shadowSearchCounter = prometheus.NewCounterVec( prometheus.CounterOpts{ Subsystem: "db_dashboard", Name: "search_shadow", }, []string{"equal", "error"}, ) func init() { prometheus.MustRegister(shadowSearchCounter) } var generateNewUid func() string = util.GenerateShortUID type DashboardSearchProjection struct { ID int64 `xorm:"id"` UID string `xorm:"uid"` Title string Slug string Term string IsFolder bool FolderID int64 `xorm:"folder_id"` FolderUID string `xorm:"folder_uid"` FolderSlug string FolderTitle string SortMeta int64 } func (ss *SQLStore) FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { filters := []interface{}{ permissions.DashboardPermissionFilter{ OrgRole: query.SignedInUser.OrgRole, OrgId: query.SignedInUser.OrgId, Dialect: dialect, UserId: query.SignedInUser.UserId, PermissionLevel: query.Permission, }, } if !accesscontrol.IsDisabled(ss.Cfg) { // if access control is enabled, overwrite the filters so far filters = []interface{}{ permissions.NewAccessControlDashboardPermissionFilter(query.SignedInUser, query.Permission, query.Type), } } for _, filter := range query.Sort.Filter { filters = append(filters, filter) } filters = append(filters, query.Filters...) if query.OrgId != 0 { filters = append(filters, searchstore.OrgFilter{OrgId: query.OrgId}) } else if query.SignedInUser.OrgId != 0 { filters = append(filters, searchstore.OrgFilter{OrgId: query.SignedInUser.OrgId}) } if len(query.Tags) > 0 { filters = append(filters, searchstore.TagsFilter{Tags: query.Tags}) } if len(query.DashboardIds) > 0 { filters = append(filters, searchstore.DashboardFilter{IDs: query.DashboardIds}) } if query.IsStarred { filters = append(filters, searchstore.StarredFilter{UserId: query.SignedInUser.UserId}) } if len(query.Title) > 0 { filters = append(filters, searchstore.TitleFilter{Dialect: dialect, Title: query.Title}) } if len(query.Type) > 0 { filters = append(filters, searchstore.TypeFilter{Dialect: dialect, Type: query.Type}) } if len(query.FolderIds) > 0 { filters = append(filters, searchstore.FolderFilter{IDs: query.FolderIds}) } var res []DashboardSearchProjection sb := &searchstore.Builder{Dialect: dialect, Filters: filters} limit := query.Limit if limit < 1 { limit = 1000 } page := query.Page if page < 1 { page = 1 } sql, params := sb.ToSQL(limit, page) err := ss.WithDbSession(ctx, func(dbSession *DBSession) error { return dbSession.SQL(sql, params...).Find(&res) }) if err != nil { return nil, err } return res, nil } func (ss *SQLStore) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error { res, err := ss.FindDashboards(ctx, query) if err != nil { return err } makeQueryResult(query, res) return nil } func getHitType(item DashboardSearchProjection) models.HitType { var hitType models.HitType if item.IsFolder { hitType = models.DashHitFolder } else { hitType = models.DashHitDB } return hitType } func makeQueryResult(query *models.FindPersistedDashboardsQuery, res []DashboardSearchProjection) { query.Result = make([]*models.Hit, 0) hits := make(map[int64]*models.Hit) for _, item := range res { hit, exists := hits[item.ID] if !exists { hit = &models.Hit{ ID: item.ID, UID: item.UID, Title: item.Title, URI: "db/" + item.Slug, URL: models.GetDashboardFolderUrl(item.IsFolder, item.UID, item.Slug), Type: getHitType(item), FolderID: item.FolderID, FolderUID: item.FolderUID, FolderTitle: item.FolderTitle, Tags: []string{}, } if item.FolderID > 0 { hit.FolderURL = models.GetFolderUrl(item.FolderUID, item.FolderSlug) } if query.Sort.MetaName != "" { hit.SortMeta = item.SortMeta hit.SortMetaName = query.Sort.MetaName } query.Result = append(query.Result, hit) hits[item.ID] = hit } if len(item.Term) > 0 { hit.Tags = append(hit.Tags, item.Term) } } } func (ss *SQLStore) GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error { return ss.WithDbSession(ctx, func(dbSession *DBSession) error { sql := `SELECT COUNT(*) as count, term FROM dashboard INNER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id WHERE dashboard.org_id=? GROUP BY term ORDER BY term` query.Result = make([]*models.DashboardTagCloudItem, 0) sess := dbSession.SQL(sql, query.OrgId) err := sess.Find(&query.Result) return err }) } func (ss *SQLStore) GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error { return ss.WithDbSession(ctx, func(dbSession *DBSession) error { if len(query.DashboardIds) == 0 && len(query.DashboardUIds) == 0 { return models.ErrCommandValidationFailed } var dashboards = make([]*models.Dashboard, 0) var session *xorm.Session if len(query.DashboardIds) > 0 { session = dbSession.In("id", query.DashboardIds) } else { session = dbSession.In("uid", query.DashboardUIds) } err := session.Find(&dashboards) query.Result = dashboards return err }) } // GetDashboardPermissionsForUser returns the maximum permission the specified user has for a dashboard(s) // The function takes in a list of dashboard ids and the user id and role func (ss *SQLStore) GetDashboardPermissionsForUser(ctx context.Context, query *models.GetDashboardPermissionsForUserQuery) error { return ss.WithDbSession(ctx, func(dbSession *DBSession) error { if len(query.DashboardIds) == 0 { return models.ErrCommandValidationFailed } if query.OrgRole == models.ROLE_ADMIN { var permissions = make([]*models.DashboardPermissionForUser, 0) for _, d := range query.DashboardIds { permissions = append(permissions, &models.DashboardPermissionForUser{ DashboardId: d, Permission: models.PERMISSION_ADMIN, PermissionName: models.PERMISSION_ADMIN.String(), }) } query.Result = permissions return nil } params := make([]interface{}, 0) // check dashboards that have ACLs via user id, team id or role sql := `SELECT d.id AS dashboard_id, MAX(COALESCE(da.permission, pt.permission)) AS permission FROM dashboard AS d LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id LEFT JOIN team_member as ugm on ugm.team_id = da.team_id LEFT JOIN org_user ou ON ou.role = da.role AND ou.user_id = ? ` params = append(params, query.UserId) // check the user's role for dashboards that do not have hasAcl set sql += `LEFT JOIN org_user ouRole ON ouRole.user_id = ? AND ouRole.org_id = ?` params = append(params, query.UserId) params = append(params, query.OrgId) sql += ` LEFT JOIN (SELECT 1 AS permission, 'Viewer' AS role UNION SELECT 2 AS permission, 'Editor' AS role UNION SELECT 4 AS permission, 'Admin' AS role) pt ON ouRole.role = pt.role WHERE d.Id IN (?` + strings.Repeat(",?", len(query.DashboardIds)-1) + `) ` for _, id := range query.DashboardIds { params = append(params, id) } sql += ` AND d.org_id = ? AND ( (d.has_acl = ? AND (da.user_id = ? OR ugm.user_id = ? OR ou.id IS NOT NULL)) OR (d.has_acl = ? AND ouRole.id IS NOT NULL) ) group by d.id order by d.id asc` params = append(params, query.OrgId) params = append(params, dialect.BooleanStr(true)) params = append(params, query.UserId) params = append(params, query.UserId) params = append(params, dialect.BooleanStr(false)) err := dbSession.SQL(sql, params...).Find(&query.Result) for _, p := range query.Result { p.PermissionName = p.Permission.String() } return err }) } func (ss *SQLStore) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error { return ss.WithDbSession(ctx, func(dbSession *DBSession) error { var rawSQL = `SELECT uid, slug from dashboard WHERE Id=?` us := &models.DashboardRef{} exists, err := dbSession.SQL(rawSQL, query.Id).Get(us) if err != nil { return err } else if !exists { return models.ErrDashboardNotFound } query.Result = us return nil }) } // HasEditPermissionInFolders validates that an user have access to a certain folder func (ss *SQLStore) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error { return ss.WithDbSession(ctx, func(dbSession *DBSession) error { if query.SignedInUser.HasRole(models.ROLE_EDITOR) { query.Result = true return nil } builder := &SQLBuilder{} builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgId, dialect.BooleanStr(true)) builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_EDIT) type folderCount struct { Count int64 } resp := make([]*folderCount, 0) if err := dbSession.SQL(builder.GetSQLString(), builder.params...).Find(&resp); err != nil { return err } query.Result = len(resp) > 0 && resp[0].Count > 0 return nil }) } func (ss *SQLStore) HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error { return ss.WithDbSession(ctx, func(dbSession *DBSession) error { if query.SignedInUser.HasRole(models.ROLE_ADMIN) { query.Result = true return nil } builder := &SQLBuilder{} builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgId, dialect.BooleanStr(true)) builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_ADMIN) type folderCount struct { Count int64 } resp := make([]*folderCount, 0) if err := dbSession.SQL(builder.GetSQLString(), builder.params...).Find(&resp); err != nil { return err } query.Result = len(resp) > 0 && resp[0].Count > 0 return nil }) }