From 4eaab95ff3c2a3a01418471b58598c2eced606a1 Mon Sep 17 00:00:00 2001 From: eleijonmarck Date: Tue, 19 Jul 2022 12:16:37 +0100 Subject: [PATCH] rename files --- ...er_command.go => conflict_user_command.go} | 143 +++++++++++++++--- ..._test.go => conflict_user_command_test.go} | 0 pkg/services/sqlstore/user.go | 24 +++ 3 files changed, 142 insertions(+), 25 deletions(-) rename pkg/cmd/grafana-cli/commands/{duplicate_user_command.go => conflict_user_command.go} (53%) rename pkg/cmd/grafana-cli/commands/{duplicate_user_command_test.go => conflict_user_command_test.go} (100%) diff --git a/pkg/cmd/grafana-cli/commands/duplicate_user_command.go b/pkg/cmd/grafana-cli/commands/conflict_user_command.go similarity index 53% rename from pkg/cmd/grafana-cli/commands/duplicate_user_command.go rename to pkg/cmd/grafana-cli/commands/conflict_user_command.go index 867b72fd528..ec5503b1462 100644 --- a/pkg/cmd/grafana-cli/commands/duplicate_user_command.go +++ b/pkg/cmd/grafana-cli/commands/conflict_user_command.go @@ -1,8 +1,12 @@ package commands import ( + "bufio" "context" "fmt" + "os" + "strconv" + "strings" "github.com/fatih/color" "github.com/grafana/grafana/pkg/bus" @@ -51,8 +55,6 @@ new word for Conflicting : conflicting (prefered now) / multiple if len(users) == 0 { return "no users found" } - // present all users - showUsers() // ask if user want to start merging for u in := range (users) { @@ -79,14 +81,11 @@ func runConflictingUsersCommand() func(context *cli.Context) error { if err != nil { return fmt.Errorf("%v: %w", "failed to load configuration", err) } - tracer, err := tracing.ProvideService(cfg) if err != nil { return fmt.Errorf("%v: %w", "failed to initialize tracer service", err) } - bus := bus.ProvideBus(tracer) - sqlStore, err := sqlstore.ProvideService(cfg, nil, &migrations.OSSMigrations{}, bus, tracer) if err != nil { return fmt.Errorf("%v: %w", "failed to initialize SQL store", err) @@ -96,39 +95,133 @@ func runConflictingUsersCommand() func(context *cli.Context) error { if err != nil { return fmt.Errorf("%v: %w", "failed to get users with conflicting logins", err) } - if len(users) < 1 { logger.Info(color.GreenString("No Conflicting users found.\n\n")) return nil } - // present all users - showUsers() - for _, u := range users { - logger.Infof("id: %v version: %s\n", u.UserIdentifier, u.Ids) + for _, cUser := range users { + logger.Infof("A user conflict found. \n") + + cType := cUser.Conflict() + switch cType { + case Merge: + // pretty print conflicting users + cUser.Print() + + // waiting for user to choose which user to merge to + logger.Infof("Choose which user to merge into:") + scanner := bufio.NewScanner(os.Stdin) + if ok := scanner.Scan(); !ok { + if err := scanner.Err(); err != nil { + return fmt.Errorf("can't read conflict option from stdin: %w", err) + } + return fmt.Errorf("can't read conflict option from stdin") + } + choosenUserToMergeInto := scanner.Text() + if !strings.Contains(cUser.Ids, choosenUserToMergeInto) { + return fmt.Errorf("not a conflicting user id") + } + v, err := strconv.ParseInt(choosenUserToMergeInto, 10, 64) + if err != nil { + return fmt.Errorf("could not parse id from string") + } + otherUsers := cUser.Ids + logger.Infof("this will merge users %d into the choosen user %s\n\n", otherUsers, choosenUserToMergeInto) + if confirm() { + err = mergeUser(v, cUser, sqlStore) + if err != nil { + return fmt.Errorf("couldnt merge user with error %w", err) + } + } + case SameIdentification: + return fmt.Errorf("have not implemented ways to deal with non mergeable users") + default: + logger.Infof("could not identify the conflict resolution for found users %s", cUser.Ids) + continue + } } return nil } } -func showUsers() { - // TODO: present users elegantly :D - logger.Info(color.GreenString("users here")) +// confirm function asks for user input +// returns bool +func confirm() bool { + + var input string + + fmt.Printf("Do you want to continue with this operation? [y|n]: ") + _, err := fmt.Scanln(&input) + if err != nil { + panic(err) + } + input = strings.ToLower(input) + + if input == "y" || input == "yes" { + return true + } + return false + } -type conflictUser struct { +func (c ConflictingUsers) Print() { + ids := strings.Split(c.Ids, ",") + emails := strings.Split(c.ConflictEmails, ",") + logins := strings.Split(c.ConflictLogins, ",") + fmt.Printf("\n") + for i := 0; i < len(ids); i++ { + s := fmt.Sprintf("Id: %s, Email: %s, Login: %s\n---\n", ids[i], emails[i], logins[i]) + logger.Info(color.HiYellowString((s))) + } +} + +type conflictType string + +const ( + Merge conflictType = "merge" + SameIdentification conflictType = "same_identification" +) + +func (cUser ConflictingUsers) Conflict() conflictType { + // FIXME: + // need to make sure that we get sameidentification when that happens instead of email/logins cased + var cType conflictType + if cUser.SameIdentificationConflictIds { + cType = SameIdentification + } else if cUser.ConflictEmails != "" || cUser.ConflictLogins != "" { + cType = Merge + } + return cType +} + +func mergeUser(mergeIntoUser int64, cUser ConflictingUsers, sqlStore *sqlstore.SQLStore) error { + stringIds := strings.Split(cUser.Ids, ",") + fromUserIds := make([]int64, 0, len(stringIds)) + for _, raw := range stringIds { + v, err := strconv.ParseInt(raw, 10, 64) + if err != nil { + return fmt.Errorf("could not parse id from string") + } + fromUserIds = append(fromUserIds, v) + } + return sqlStore.MergeUser(mergeIntoUser, fromUserIds) +} + +type ConflictingUsers struct { Ids string `xorm:"ids"` // IDENTIFIER // userIdentifier = login + email - UserIdentifier string `xorm:"user_identification"` - ConflictEmails string `xorm:"conflicting_emails"` - ConflictLogins string `xorm:"conflicting_logins"` + UserIdentifier string `xorm:"user_identification"` + ConflictEmails string `xorm:"conflicting_emails"` + ConflictLogins string `xorm:"conflicting_logins"` + SameIdentificationConflictIds bool `xorm:"same_identification_conflict_ids"` } -type conflictUsers []conflictUser +type allConflictingUserAggregates []ConflictingUsers -func GetUsersWithConflictingEmailsOrLogins(ctx context.Context, s *sqlstore.SQLStore) (conflictUsers, error) { - var stats conflictUsers +func GetUsersWithConflictingEmailsOrLogins(ctx context.Context, s *sqlstore.SQLStore) (allConflictingUserAggregates, error) { + var stats allConflictingUserAggregates outerErr := s.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { rawSQL := conflictingUserEntriesSQL(s) err := dbSession.SQL(rawSQL).Find(&stats) @@ -156,24 +249,24 @@ func conflictingUserEntriesSQL(s *sqlstore.SQLStore) string { FROM ` + userDialect + ` WHERE (LOWER(u1.email) = LOWER(u2.email)) - AND(u1.email != u2.email)) AS dup_email, ( + AND(u1.email != u2.email)) AS conflict_email, ( SELECT u1.login FROM ` + userDialect + ` WHERE (LOWER(u1.login) = LOWER(u2.login) - AND(u1.login != u2.login))) AS dup_login, ( + AND(u1.login != u2.login))) AS conflict_login, ( SELECT u1.id FROM ` + userDialect + ` WHERE ((u1.login = u2.login) AND(u1.email = u2.email) - AND(u1.id != u2.id))) AS dup_ids + AND(u1.id != u2.id))) AS same_identification_conflict_ids FROM ` + userDialect + ` AS u1, ` + userDialect + ` AS u2 - WHERE (dup_email IS NOT NULL - OR dup_login IS NOT NULL OR dup_ids IS NOT NULL) + WHERE (conflict_email IS NOT NULL + OR conflict_login IS NOT NULL OR same_identification_conflict_ids IS NOT NULL) GROUP BY LOWER(user_identification); ` diff --git a/pkg/cmd/grafana-cli/commands/duplicate_user_command_test.go b/pkg/cmd/grafana-cli/commands/conflict_user_command_test.go similarity index 100% rename from pkg/cmd/grafana-cli/commands/duplicate_user_command_test.go rename to pkg/cmd/grafana-cli/commands/conflict_user_command_test.go diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 9b1cbddc47a..c5d76619b02 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -818,6 +818,30 @@ func UserDeletions() []string { return deletes } +func updateAllTablesForFromUser(intoUser user.User, fromUserId int64) error { + // TODO: + fmt.Printf("merging user %d into user %d", fromUserId, intoUser.ID) + return nil +} + +// UpdateUserPermissions sets the user Server Admin flag +func (ss *SQLStore) MergeUser(intoUserId int64, fromUserIds []int64) error { + return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error { + + var intoUser user.User + if _, err := sess.ID(intoUserId).Where(notServiceAccountFilter(ss)).Get(&intoUser); err != nil { + return err + } + + for _, fromUserId := range fromUserIds { + // update all tables fromUserIds to intoUserIds + updateAllTablesForFromUser(intoUser, fromUserId) + } + + return nil + }) +} + // UpdateUserPermissions sets the user Server Admin flag func (ss *SQLStore) UpdateUserPermissions(userID int64, isAdmin bool) error { return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {