1package sqlstore
2
3import (
4	"context"
5	"fmt"
6	"sort"
7	"strconv"
8	"strings"
9	"time"
10
11	"github.com/google/uuid"
12	"github.com/grafana/grafana/pkg/bus"
13	"github.com/grafana/grafana/pkg/events"
14	"github.com/grafana/grafana/pkg/models"
15	"github.com/grafana/grafana/pkg/setting"
16	"github.com/grafana/grafana/pkg/util"
17	"github.com/pkg/errors"
18)
19
20func (ss *SQLStore) addUserQueryAndCommandHandlers() {
21	ss.Bus.AddHandlerCtx(ss.GetSignedInUserWithCacheCtx)
22
23	bus.AddHandlerCtx("sql", GetUserById)
24	bus.AddHandlerCtx("sql", UpdateUser)
25	bus.AddHandlerCtx("sql", ChangeUserPassword)
26	bus.AddHandlerCtx("sql", ss.GetUserByLogin)
27	bus.AddHandlerCtx("sql", ss.GetUserByEmail)
28	bus.AddHandlerCtx("sql", SetUsingOrg)
29	bus.AddHandlerCtx("sql", UpdateUserLastSeenAt)
30	bus.AddHandlerCtx("sql", ss.GetUserProfile)
31	bus.AddHandlerCtx("sql", SearchUsers)
32	bus.AddHandlerCtx("sql", GetUserOrgList)
33	bus.AddHandlerCtx("sql", DisableUser)
34	bus.AddHandlerCtx("sql", BatchDisableUsers)
35	bus.AddHandlerCtx("sql", DeleteUser)
36	bus.AddHandlerCtx("sql", SetUserHelpFlag)
37}
38
39func getOrgIdForNewUser(sess *DBSession, cmd models.CreateUserCommand) (int64, error) {
40	if cmd.SkipOrgSetup {
41		return -1, nil
42	}
43
44	if setting.AutoAssignOrg && cmd.OrgId != 0 {
45		err := verifyExistingOrg(sess, cmd.OrgId)
46		if err != nil {
47			return -1, err
48		}
49		return cmd.OrgId, nil
50	}
51
52	orgName := cmd.OrgName
53	if len(orgName) == 0 {
54		orgName = util.StringsFallback2(cmd.Email, cmd.Login)
55	}
56
57	return getOrCreateOrg(sess, orgName)
58}
59
60type userCreationArgs struct {
61	Login          string
62	Email          string
63	Name           string
64	Company        string
65	Password       string
66	IsAdmin        bool
67	IsDisabled     bool
68	EmailVerified  bool
69	OrgID          int64
70	OrgName        string
71	DefaultOrgRole string
72}
73
74func (ss *SQLStore) getOrgIDForNewUser(sess *DBSession, args userCreationArgs) (int64, error) {
75	if ss.Cfg.AutoAssignOrg && args.OrgID != 0 {
76		if err := verifyExistingOrg(sess, args.OrgID); err != nil {
77			return -1, err
78		}
79		return args.OrgID, nil
80	}
81
82	orgName := args.OrgName
83	if orgName == "" {
84		orgName = util.StringsFallback2(args.Email, args.Login)
85	}
86
87	return ss.getOrCreateOrg(sess, orgName)
88}
89
90// createUser creates a user in the database.
91func (ss *SQLStore) createUser(ctx context.Context, sess *DBSession, args userCreationArgs, skipOrgSetup bool) (models.User, error) {
92	var user models.User
93	var orgID int64 = -1
94	if !skipOrgSetup {
95		var err error
96		orgID, err = ss.getOrgIDForNewUser(sess, args)
97		if err != nil {
98			return user, err
99		}
100	}
101
102	if args.Email == "" {
103		args.Email = args.Login
104	}
105
106	exists, err := sess.Where("email=? OR login=?", args.Email, args.Login).Get(&models.User{})
107	if err != nil {
108		return user, err
109	}
110	if exists {
111		return user, models.ErrUserAlreadyExists
112	}
113
114	// create user
115	user = models.User{
116		Email:         args.Email,
117		Name:          args.Name,
118		Login:         args.Login,
119		Company:       args.Company,
120		IsAdmin:       args.IsAdmin,
121		IsDisabled:    args.IsDisabled,
122		OrgId:         orgID,
123		EmailVerified: args.EmailVerified,
124		Created:       time.Now(),
125		Updated:       time.Now(),
126		LastSeenAt:    time.Now().AddDate(-10, 0, 0),
127	}
128
129	salt, err := util.GetRandomString(10)
130	if err != nil {
131		return user, err
132	}
133	user.Salt = salt
134	rands, err := util.GetRandomString(10)
135	if err != nil {
136		return user, err
137	}
138	user.Rands = rands
139
140	if len(args.Password) > 0 {
141		encodedPassword, err := util.EncodePassword(args.Password, user.Salt)
142		if err != nil {
143			return user, err
144		}
145		user.Password = encodedPassword
146	}
147
148	sess.UseBool("is_admin")
149
150	if _, err := sess.Insert(&user); err != nil {
151		return user, err
152	}
153
154	sess.publishAfterCommit(&events.UserCreated{
155		Timestamp: user.Created,
156		Id:        user.Id,
157		Name:      user.Name,
158		Login:     user.Login,
159		Email:     user.Email,
160	})
161
162	// create org user link
163	if !skipOrgSetup {
164		orgUser := models.OrgUser{
165			OrgId:   orgID,
166			UserId:  user.Id,
167			Role:    models.ROLE_ADMIN,
168			Created: time.Now(),
169			Updated: time.Now(),
170		}
171
172		if ss.Cfg.AutoAssignOrg && !user.IsAdmin {
173			if len(args.DefaultOrgRole) > 0 {
174				orgUser.Role = models.RoleType(args.DefaultOrgRole)
175			} else {
176				orgUser.Role = models.RoleType(ss.Cfg.AutoAssignOrgRole)
177			}
178		}
179
180		if _, err = sess.Insert(&orgUser); err != nil {
181			return user, err
182		}
183	}
184
185	return user, nil
186}
187
188func (ss *SQLStore) CloneUserToServiceAccount(ctx context.Context, siUser *models.SignedInUser) (*models.User, error) {
189	cmd := models.CreateUserCommand{
190		Login:            "Service-Account-" + uuid.New().String(),
191		Email:            uuid.New().String(),
192		Password:         "Password-" + uuid.New().String(),
193		Name:             siUser.Name + "-Service-Account-" + uuid.New().String(),
194		OrgId:            siUser.OrgId,
195		IsServiceAccount: true,
196	}
197
198	newuser, err := ss.CreateUser(ctx, cmd)
199	if err != nil {
200		return nil, errors.Errorf("Failed to create user: %v", err)
201	}
202
203	return newuser, err
204}
205
206func (ss *SQLStore) CreateUser(ctx context.Context, cmd models.CreateUserCommand) (*models.User, error) {
207	var user *models.User
208	err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error {
209		orgId, err := getOrgIdForNewUser(sess, cmd)
210		if err != nil {
211			return err
212		}
213
214		if cmd.Email == "" {
215			cmd.Email = cmd.Login
216		}
217
218		exists, err := sess.Where("email=? OR login=?", cmd.Email, cmd.Login).Get(&models.User{})
219		if err != nil {
220			return err
221		}
222		if exists {
223			return models.ErrUserAlreadyExists
224		}
225
226		// create user
227		user = &models.User{
228			Email:            cmd.Email,
229			Name:             cmd.Name,
230			Login:            cmd.Login,
231			Company:          cmd.Company,
232			IsAdmin:          cmd.IsAdmin,
233			IsDisabled:       cmd.IsDisabled,
234			OrgId:            orgId,
235			EmailVerified:    cmd.EmailVerified,
236			Created:          time.Now(),
237			Updated:          time.Now(),
238			LastSeenAt:       time.Now().AddDate(-10, 0, 0),
239			IsServiceAccount: cmd.IsServiceAccount,
240		}
241
242		salt, err := util.GetRandomString(10)
243		if err != nil {
244			return err
245		}
246		user.Salt = salt
247		rands, err := util.GetRandomString(10)
248		if err != nil {
249			return err
250		}
251		user.Rands = rands
252
253		if len(cmd.Password) > 0 {
254			encodedPassword, err := util.EncodePassword(cmd.Password, user.Salt)
255			if err != nil {
256				return err
257			}
258			user.Password = encodedPassword
259		}
260
261		sess.UseBool("is_admin")
262
263		if _, err := sess.Insert(user); err != nil {
264			return err
265		}
266
267		sess.publishAfterCommit(&events.UserCreated{
268			Timestamp: user.Created,
269			Id:        user.Id,
270			Name:      user.Name,
271			Login:     user.Login,
272			Email:     user.Email,
273		})
274
275		// create org user link
276		if !cmd.SkipOrgSetup {
277			orgUser := models.OrgUser{
278				OrgId:   orgId,
279				UserId:  user.Id,
280				Role:    models.ROLE_ADMIN,
281				Created: time.Now(),
282				Updated: time.Now(),
283			}
284
285			if setting.AutoAssignOrg && !user.IsAdmin {
286				if len(cmd.DefaultOrgRole) > 0 {
287					orgUser.Role = models.RoleType(cmd.DefaultOrgRole)
288				} else {
289					orgUser.Role = models.RoleType(setting.AutoAssignOrgRole)
290				}
291			}
292
293			if _, err = sess.Insert(&orgUser); err != nil {
294				return err
295			}
296		}
297
298		return nil
299	})
300
301	return user, err
302}
303
304func GetUserById(ctx context.Context, query *models.GetUserByIdQuery) error {
305	return withDbSession(ctx, x, func(sess *DBSession) error {
306		user := new(models.User)
307		has, err := sess.ID(query.Id).Get(user)
308
309		if err != nil {
310			return err
311		} else if !has {
312			return models.ErrUserNotFound
313		}
314
315		query.Result = user
316
317		return nil
318	})
319}
320
321func (ss *SQLStore) GetUserByLogin(ctx context.Context, query *models.GetUserByLoginQuery) error {
322	return ss.WithDbSession(ctx, func(sess *DBSession) error {
323		if query.LoginOrEmail == "" {
324			return models.ErrUserNotFound
325		}
326
327		// Try and find the user by login first.
328		// It's not sufficient to assume that a LoginOrEmail with an "@" is an email.
329		user := &models.User{Login: query.LoginOrEmail}
330		has, err := sess.Get(user)
331
332		if err != nil {
333			return err
334		}
335
336		if !has && strings.Contains(query.LoginOrEmail, "@") {
337			// If the user wasn't found, and it contains an "@" fallback to finding the
338			// user by email.
339			user = &models.User{Email: query.LoginOrEmail}
340			has, err = sess.Get(user)
341		}
342
343		if err != nil {
344			return err
345		} else if !has {
346			return models.ErrUserNotFound
347		}
348
349		query.Result = user
350
351		return nil
352	})
353}
354
355func (ss *SQLStore) GetUserByEmail(ctx context.Context, query *models.GetUserByEmailQuery) error {
356	return ss.WithDbSession(ctx, func(sess *DBSession) error {
357		if query.Email == "" {
358			return models.ErrUserNotFound
359		}
360
361		user := &models.User{Email: query.Email}
362		has, err := sess.Get(user)
363
364		if err != nil {
365			return err
366		} else if !has {
367			return models.ErrUserNotFound
368		}
369
370		query.Result = user
371
372		return nil
373	})
374}
375
376func UpdateUser(ctx context.Context, cmd *models.UpdateUserCommand) error {
377	return inTransaction(func(sess *DBSession) error {
378		user := models.User{
379			Name:    cmd.Name,
380			Email:   cmd.Email,
381			Login:   cmd.Login,
382			Theme:   cmd.Theme,
383			Updated: time.Now(),
384		}
385
386		if _, err := sess.ID(cmd.UserId).Update(&user); err != nil {
387			return err
388		}
389
390		sess.publishAfterCommit(&events.UserUpdated{
391			Timestamp: user.Created,
392			Id:        user.Id,
393			Name:      user.Name,
394			Login:     user.Login,
395			Email:     user.Email,
396		})
397
398		return nil
399	})
400}
401
402func ChangeUserPassword(ctx context.Context, cmd *models.ChangeUserPasswordCommand) error {
403	return inTransaction(func(sess *DBSession) error {
404		user := models.User{
405			Password: cmd.NewPassword,
406			Updated:  time.Now(),
407		}
408
409		_, err := sess.ID(cmd.UserId).Update(&user)
410		return err
411	})
412}
413
414func UpdateUserLastSeenAt(ctx context.Context, cmd *models.UpdateUserLastSeenAtCommand) error {
415	return inTransaction(func(sess *DBSession) error {
416		user := models.User{
417			Id:         cmd.UserId,
418			LastSeenAt: time.Now(),
419		}
420
421		_, err := sess.ID(cmd.UserId).Update(&user)
422		return err
423	})
424}
425
426func SetUsingOrg(ctx context.Context, cmd *models.SetUsingOrgCommand) error {
427	getOrgsForUserCmd := &models.GetUserOrgListQuery{UserId: cmd.UserId}
428	if err := GetUserOrgList(ctx, getOrgsForUserCmd); err != nil {
429		return err
430	}
431
432	valid := false
433	for _, other := range getOrgsForUserCmd.Result {
434		if other.OrgId == cmd.OrgId {
435			valid = true
436		}
437	}
438	if !valid {
439		return fmt.Errorf("user does not belong to org")
440	}
441
442	return inTransaction(func(sess *DBSession) error {
443		return setUsingOrgInTransaction(sess, cmd.UserId, cmd.OrgId)
444	})
445}
446
447func setUsingOrgInTransaction(sess *DBSession, userID int64, orgID int64) error {
448	user := models.User{
449		Id:    userID,
450		OrgId: orgID,
451	}
452
453	_, err := sess.ID(userID).Update(&user)
454	return err
455}
456
457func (ss *SQLStore) GetUserProfile(ctx context.Context, query *models.GetUserProfileQuery) error {
458	return ss.WithDbSession(ctx, func(sess *DBSession) error {
459		var user models.User
460		has, err := sess.ID(query.UserId).Get(&user)
461
462		if err != nil {
463			return err
464		} else if !has {
465			return models.ErrUserNotFound
466		}
467
468		query.Result = models.UserProfileDTO{
469			Id:             user.Id,
470			Name:           user.Name,
471			Email:          user.Email,
472			Login:          user.Login,
473			Theme:          user.Theme,
474			IsGrafanaAdmin: user.IsAdmin,
475			IsDisabled:     user.IsDisabled,
476			OrgId:          user.OrgId,
477			UpdatedAt:      user.Updated,
478			CreatedAt:      user.Created,
479		}
480
481		return err
482	})
483}
484
485type byOrgName []*models.UserOrgDTO
486
487// Len returns the length of an array of organisations.
488func (o byOrgName) Len() int {
489	return len(o)
490}
491
492// Swap swaps two indices of an array of organizations.
493func (o byOrgName) Swap(i, j int) {
494	o[i], o[j] = o[j], o[i]
495}
496
497// Less returns whether element i of an array of organizations is less than element j.
498func (o byOrgName) Less(i, j int) bool {
499	if strings.ToLower(o[i].Name) < strings.ToLower(o[j].Name) {
500		return true
501	}
502
503	return o[i].Name < o[j].Name
504}
505
506func GetUserOrgList(ctx context.Context, query *models.GetUserOrgListQuery) error {
507	query.Result = make([]*models.UserOrgDTO, 0)
508	sess := x.Table("org_user")
509	sess.Join("INNER", "org", "org_user.org_id=org.id")
510	sess.Where("org_user.user_id=?", query.UserId)
511	sess.Cols("org.name", "org_user.role", "org_user.org_id")
512	sess.OrderBy("org.name")
513	err := sess.Find(&query.Result)
514	sort.Sort(byOrgName(query.Result))
515	return err
516}
517
518func newSignedInUserCacheKey(orgID, userID int64) string {
519	return fmt.Sprintf("signed-in-user-%d-%d", userID, orgID)
520}
521
522func (ss *SQLStore) GetSignedInUserWithCacheCtx(ctx context.Context, query *models.GetSignedInUserQuery) error {
523	cacheKey := newSignedInUserCacheKey(query.OrgId, query.UserId)
524	if cached, found := ss.CacheService.Get(cacheKey); found {
525		cachedUser := cached.(models.SignedInUser)
526		query.Result = &cachedUser
527		return nil
528	}
529
530	err := GetSignedInUser(ctx, query)
531	if err != nil {
532		return err
533	}
534
535	cacheKey = newSignedInUserCacheKey(query.Result.OrgId, query.UserId)
536	ss.CacheService.Set(cacheKey, *query.Result, time.Second*5)
537	return nil
538}
539
540func GetSignedInUser(ctx context.Context, query *models.GetSignedInUserQuery) error {
541	orgId := "u.org_id"
542	if query.OrgId > 0 {
543		orgId = strconv.FormatInt(query.OrgId, 10)
544	}
545
546	var rawSQL = `SELECT
547		u.id             as user_id,
548		u.is_admin       as is_grafana_admin,
549		u.email          as email,
550		u.login          as login,
551		u.name           as name,
552		u.help_flags1    as help_flags1,
553		u.last_seen_at   as last_seen_at,
554		(SELECT COUNT(*) FROM org_user where org_user.user_id = u.id) as org_count,
555		org.name         as org_name,
556		org_user.role    as org_role,
557		org.id           as org_id
558		FROM ` + dialect.Quote("user") + ` as u
559		LEFT OUTER JOIN org_user on org_user.org_id = ` + orgId + ` and org_user.user_id = u.id
560		LEFT OUTER JOIN org on org.id = org_user.org_id `
561
562	sess := x.Table("user")
563	sess = sess.Context(ctx)
564	switch {
565	case query.UserId > 0:
566		sess.SQL(rawSQL+"WHERE u.id=?", query.UserId)
567	case query.Login != "":
568		sess.SQL(rawSQL+"WHERE u.login=?", query.Login)
569	case query.Email != "":
570		sess.SQL(rawSQL+"WHERE u.email=?", query.Email)
571	}
572
573	var user models.SignedInUser
574	has, err := sess.Get(&user)
575	if err != nil {
576		return err
577	} else if !has {
578		return models.ErrUserNotFound
579	}
580
581	if user.OrgRole == "" {
582		user.OrgId = -1
583		user.OrgName = "Org missing"
584	}
585
586	getTeamsByUserQuery := &models.GetTeamsByUserQuery{OrgId: user.OrgId, UserId: user.UserId}
587	err = GetTeamsByUser(ctx, getTeamsByUserQuery)
588	if err != nil {
589		return err
590	}
591
592	user.Teams = make([]int64, len(getTeamsByUserQuery.Result))
593	for i, t := range getTeamsByUserQuery.Result {
594		user.Teams[i] = t.Id
595	}
596
597	query.Result = &user
598	return err
599}
600
601func SearchUsers(ctx context.Context, query *models.SearchUsersQuery) error {
602	query.Result = models.SearchUserQueryResult{
603		Users: make([]*models.UserSearchHitDTO, 0),
604	}
605
606	queryWithWildcards := "%" + query.Query + "%"
607
608	whereConditions := make([]string, 0)
609	whereParams := make([]interface{}, 0)
610	sess := x.Table("user").Alias("u")
611
612	// TODO: add to chore, for cleaning up after we have created
613	// service accounts table in the modelling
614	whereConditions = append(whereConditions, "u.is_service_account = false")
615
616	// Join with only most recent auth module
617	joinCondition := `(
618		SELECT id from user_auth
619			WHERE user_auth.user_id = u.id
620			ORDER BY user_auth.created DESC `
621	joinCondition = "user_auth.id=" + joinCondition + dialect.Limit(1) + ")"
622	sess.Join("LEFT", "user_auth", joinCondition)
623	if query.OrgId > 0 {
624		whereConditions = append(whereConditions, "org_id = ?")
625		whereParams = append(whereParams, query.OrgId)
626	}
627
628	if query.Query != "" {
629		whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
630		whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
631	}
632
633	if query.IsDisabled != nil {
634		whereConditions = append(whereConditions, "is_disabled = ?")
635		whereParams = append(whereParams, query.IsDisabled)
636	}
637
638	if query.AuthModule != "" {
639		whereConditions = append(whereConditions, `auth_module=?`)
640		whereParams = append(whereParams, query.AuthModule)
641	}
642
643	if len(whereConditions) > 0 {
644		sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
645	}
646
647	for _, filter := range query.Filters {
648		if jc := filter.JoinCondition(); jc != nil {
649			sess.Join(jc.Operator, jc.Table, jc.Params)
650		}
651		if ic := filter.InCondition(); ic != nil {
652			sess.In(ic.Condition, ic.Params)
653		}
654		if wc := filter.WhereCondition(); wc != nil {
655			sess.Where(wc.Condition, wc.Params)
656		}
657	}
658
659	if query.Limit > 0 {
660		offset := query.Limit * (query.Page - 1)
661		sess.Limit(query.Limit, offset)
662	}
663
664	sess.Cols("u.id", "u.email", "u.name", "u.login", "u.is_admin", "u.is_disabled", "u.last_seen_at", "user_auth.auth_module")
665	sess.Asc("u.login", "u.email")
666	if err := sess.Find(&query.Result.Users); err != nil {
667		return err
668	}
669
670	// get total
671	user := models.User{}
672	countSess := x.Table("user").Alias("u")
673
674	// Join with user_auth table if users filtered by auth_module
675	if query.AuthModule != "" {
676		countSess.Join("LEFT", "user_auth", joinCondition)
677	}
678
679	if len(whereConditions) > 0 {
680		countSess.Where(strings.Join(whereConditions, " AND "), whereParams...)
681	}
682
683	for _, filter := range query.Filters {
684		if jc := filter.JoinCondition(); jc != nil {
685			countSess.Join(jc.Operator, jc.Table, jc.Params)
686		}
687		if ic := filter.InCondition(); ic != nil {
688			countSess.In(ic.Condition, ic.Params)
689		}
690		if wc := filter.WhereCondition(); wc != nil {
691			countSess.Where(wc.Condition, wc.Params)
692		}
693	}
694
695	count, err := countSess.Count(&user)
696	query.Result.TotalCount = count
697
698	for _, user := range query.Result.Users {
699		user.LastSeenAtAge = util.GetAgeString(user.LastSeenAt)
700	}
701
702	return err
703}
704
705func DisableUser(ctx context.Context, cmd *models.DisableUserCommand) error {
706	user := models.User{}
707	sess := x.Table("user")
708
709	if has, err := sess.ID(cmd.UserId).Get(&user); err != nil {
710		return err
711	} else if !has {
712		return models.ErrUserNotFound
713	}
714
715	user.IsDisabled = cmd.IsDisabled
716	sess.UseBool("is_disabled")
717
718	_, err := sess.ID(cmd.UserId).Update(&user)
719	return err
720}
721
722func BatchDisableUsers(ctx context.Context, cmd *models.BatchDisableUsersCommand) error {
723	return inTransaction(func(sess *DBSession) error {
724		userIds := cmd.UserIds
725
726		if len(userIds) == 0 {
727			return nil
728		}
729
730		user_id_params := strings.Repeat(",?", len(userIds)-1)
731		disableSQL := "UPDATE " + dialect.Quote("user") + " SET is_disabled=? WHERE Id IN (?" + user_id_params + ")"
732
733		disableParams := []interface{}{disableSQL, cmd.IsDisabled}
734		for _, v := range userIds {
735			disableParams = append(disableParams, v)
736		}
737
738		_, err := sess.Exec(disableParams...)
739		if err != nil {
740			return err
741		}
742
743		return nil
744	})
745}
746
747func DeleteUser(ctx context.Context, cmd *models.DeleteUserCommand) error {
748	return inTransaction(func(sess *DBSession) error {
749		return deleteUserInTransaction(sess, cmd)
750	})
751}
752
753func deleteUserInTransaction(sess *DBSession, cmd *models.DeleteUserCommand) error {
754	// Check if user exists
755	user := models.User{Id: cmd.UserId}
756	has, err := sess.Get(&user)
757	if err != nil {
758		return err
759	}
760	if !has {
761		return models.ErrUserNotFound
762	}
763	for _, sql := range userDeletions() {
764		_, err := sess.Exec(sql, cmd.UserId)
765		if err != nil {
766			return err
767		}
768	}
769	return nil
770}
771
772func userDeletions() []string {
773	deletes := []string{
774		"DELETE FROM star WHERE user_id = ?",
775		"DELETE FROM " + dialect.Quote("user") + " WHERE id = ?",
776		"DELETE FROM org_user WHERE user_id = ?",
777		"DELETE FROM dashboard_acl WHERE user_id = ?",
778		"DELETE FROM preferences WHERE user_id = ?",
779		"DELETE FROM team_member WHERE user_id = ?",
780		"DELETE FROM user_auth WHERE user_id = ?",
781		"DELETE FROM user_auth_token WHERE user_id = ?",
782		"DELETE FROM quota WHERE user_id = ?",
783	}
784	return deletes
785}
786
787func ServiceAccountDeletions() []string {
788	deletes := []string{
789		"DELETE FROM api_key WHERE service_account_id = ?",
790	}
791	deletes = append(deletes, userDeletions()...)
792	return deletes
793}
794
795func (ss *SQLStore) UpdateUserPermissions(userID int64, isAdmin bool) error {
796	return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
797		var user models.User
798		if _, err := sess.ID(userID).Get(&user); err != nil {
799			return err
800		}
801
802		user.IsAdmin = isAdmin
803		sess.UseBool("is_admin")
804
805		_, err := sess.ID(user.Id).Update(&user)
806		if err != nil {
807			return err
808		}
809
810		// validate that after update there is at least one server admin
811		if err := validateOneAdminLeft(sess); err != nil {
812			return err
813		}
814
815		return nil
816	})
817}
818
819func SetUserHelpFlag(ctx context.Context, cmd *models.SetUserHelpFlagCommand) error {
820	return inTransaction(func(sess *DBSession) error {
821		user := models.User{
822			Id:         cmd.UserId,
823			HelpFlags1: cmd.HelpFlags1,
824			Updated:    time.Now(),
825		}
826
827		_, err := sess.ID(cmd.UserId).Cols("help_flags1").Update(&user)
828		return err
829	})
830}
831
832func validateOneAdminLeft(sess *DBSession) error {
833	// validate that there is an admin user left
834	count, err := sess.Where("is_admin=?", true).Count(&models.User{})
835	if err != nil {
836		return err
837	}
838
839	if count == 0 {
840		return models.ErrLastGrafanaAdmin
841	}
842
843	return nil
844}
845