Files

221 lines
5.4 KiB
Go

package legacy
import (
"context"
"database/sql"
"fmt"
"slices"
"strconv"
"time"
authlib "github.com/grafana/authlib/types"
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
type preferenceModel struct {
ID int64
OrgID int64
UserUID sql.NullString
TeamUID sql.NullString
JSONData *pref.PreferenceJSONData
HomeDashboardUID sql.NullString
Timezone sql.NullString
Theme sql.NullString
WeekStart sql.NullString
Created time.Time
Updated time.Time
}
type LegacySQL struct {
db legacysql.LegacyDatabaseProvider
startup time.Time
}
func NewLegacySQL(db legacysql.LegacyDatabaseProvider) *LegacySQL {
return &LegacySQL{db: db, startup: time.Now()}
}
// List all defined preferences in an org (valid for admin users only)
func (s *LegacySQL) listPreferences(ctx context.Context,
ns string, orgId int64,
cb func(req *preferencesQuery) (bool, error),
access func(p *preferenceModel) bool,
) ([]preferences.Preferences, int64, error) {
var results []preferences.Preferences
var rv sql.NullTime
var max sql.NullString
sql, err := s.db(ctx)
if err != nil {
return nil, 0, err
}
req := newPreferencesQueryReq(sql, orgId)
needsRV, err := cb(&req)
if err != nil {
return nil, 0, err
}
q, err := sqltemplate.Execute(sqlPreferencesQuery, req)
if err != nil {
return nil, 0, fmt.Errorf("execute template %q: %w", sqlPreferencesQuery.Name(), err)
}
sess := sql.DB.GetSqlxSession()
rows, err := sess.Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
for rows.Next() {
// SELECT p.id, p.org_id,
// p.json_data,
// p.timezone,
// p.theme,
// p.week_start,
// p.home_dashboard_uid,
// u.uid as user_uid,
// t.uid as team_uid,
// p.created, p.updated
pref := preferenceModel{}
err := rows.Scan(&pref.ID, &pref.OrgID,
&pref.JSONData,
&pref.Timezone,
&pref.Theme,
&pref.WeekStart,
&pref.HomeDashboardUID,
&pref.UserUID, &pref.TeamUID,
&pref.Created, &pref.Updated)
if err != nil {
return nil, 0, err
}
if pref.Updated.After(rv.Time) {
rv.Time = pref.Updated
}
if !access(&pref) {
continue // user does not have access
}
results = append(results, asPreferencesResource(ns, &pref))
}
if needsRV {
req.Reset()
q, err = sqltemplate.Execute(sqlPreferencesRV, req)
if err != nil {
return nil, 0, fmt.Errorf("execute template %q: %w", sqlPreferencesRV.Name(), err)
}
err = sess.Get(ctx, &max, q)
if err != nil {
return nil, 0, fmt.Errorf("unable to get RV %w", err)
}
if max.Valid && max.String != "" {
t, _ := time.Parse(time.RFC3339, max.String)
if !t.IsZero() {
rv.Time = t
}
} else {
rv.Time = s.startup
}
}
return results, rv.Time.UnixMilli(), err
}
func (s *LegacySQL) ListPreferences(ctx context.Context, ns string, user identity.Requester, needsRV bool) (*preferences.PreferencesList, error) {
if ns == "" {
return nil, fmt.Errorf("namespace is required")
}
info, err := authlib.ParseNamespace(ns)
if err != nil {
return nil, err
}
// when the user is nil, it is actually admin and can see everything
var teams []string
found, rv, err := s.listPreferences(ctx, ns, info.OrgID,
func(req *preferencesQuery) (bool, error) {
if user != nil {
req.UserUID = user.GetIdentifier()
teams, err = s.GetTeams(ctx, &identity.StaticRequester{
OrgID: info.OrgID,
UserUID: req.UserUID,
}, false)
req.UserTeams = teams
}
return needsRV, err
},
func(p *preferenceModel) bool {
if user == nil || user.GetIsGrafanaAdmin() {
return true
}
if p.UserUID.String != "" {
return user.GetIdentifier() == p.UserUID.String
}
if p.TeamUID.String != "" {
return slices.Contains(teams, p.TeamUID.String)
}
return true
},
)
if err != nil {
return nil, err
}
list := &preferences.PreferencesList{
Items: found,
}
if rv > 0 {
list.ResourceVersion = strconv.FormatInt(rv, 10)
}
return list, nil
}
func (s *LegacySQL) InTeam(ctx context.Context, id authlib.AuthInfo, team string, admin bool) (bool, error) {
// Could be faster, but find for now
teams, err := s.GetTeams(ctx, id, admin)
if err != nil {
return false, err
}
return slices.Contains(teams, team), nil
}
func (s *LegacySQL) GetTeams(ctx context.Context, id authlib.AuthInfo, admin bool) ([]string, error) {
sql, err := s.db(ctx)
if err != nil {
return nil, err
}
xid, ok := id.(identity.Requester)
if !ok {
return nil, fmt.Errorf("expected identity.Requester")
}
req := newTeamsQueryReq(sql, xid.GetOrgID(), id.GetUID(), admin)
q, err := sqltemplate.Execute(sqlTeams, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlTeams.Name(), err)
}
teams := []string{}
sess := sql.DB.GetSqlxSession()
err = sess.Select(ctx, &teams, q, req.GetArgs()...)
return teams, err
}
func (s *LegacySQL) getLegacyTeamID(ctx context.Context, orgId int64, team string) (int64, error) {
sql, err := s.db(ctx)
if err != nil {
return 0, err
}
var id int64
sess := sql.DB.GetSqlxSession()
err = sess.Select(ctx, &id, "SELECT id FROM team WHERE org_id=? AND uid=?", orgId, team)
return id, err
}