Command: add conflict merge user command

- MergeUser will
	- replace all user_ids from conflicting users to the chosen userId
	- delete users whose user_ids are not the chosen user
- SameIdentification will
	- update chosen user with chosen email,login details
	- delete users whose user_ids are not the chosen user
This commit is contained in:
eleijonmarck
2022-07-20 14:02:15 +01:00
parent 3838a59fa1
commit 29adcd4e48
3 changed files with 145 additions and 30 deletions
@@ -111,32 +111,40 @@ func runConflictingUsersCommand() func(context *cli.Context) error {
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)
chosenUser, err := promptToMerge(cUser)
if err != nil {
return fmt.Errorf("could not parse id from string")
return err
}
otherUsers := cUser.Ids
logger.Infof("this will merge users %d into the choosen user %s\n\n", otherUsers, choosenUserToMergeInto)
logger.Infof("this will merge users %s into the chosen user %d\n\n", otherUsers, chosenUser)
if confirm() {
err = mergeUser(v, cUser, sqlStore)
err = mergeUser(context.Context, chosenUser, cUser, sqlStore)
if err != nil {
return fmt.Errorf("couldnt merge user with error %w", err)
return fmt.Errorf("could not merge user with error %w", err)
}
}
case SameIdentification:
return fmt.Errorf("have not implemented ways to deal with non mergeable users")
// waiting for user to choose which user to merge to
chosenUser, err := promptToMerge(cUser)
if err != nil {
return err
}
err = sqlStore.UpdateUser(context.Context, &models.UpdateUserCommand{UserId: chosenUser})
if err != nil {
return fmt.Errorf("could not update user with details %w", err)
}
otherUsers := strings.Split(cUser.Ids, ",")
for _, oUser := range otherUsers {
oUser, err := strconv.ParseInt(oUser, 10, 64)
if err != nil {
return err
}
err = sqlStore.DeleteUser(context.Context, &models.DeleteUserCommand{UserId: oUser})
if err != nil {
return fmt.Errorf("could not update user with details %w", err)
}
}
default:
logger.Infof("could not identify the conflict resolution for found users %s", cUser.Ids)
continue
@@ -237,7 +245,7 @@ func (cUser ConflictingUsers) Conflict() conflictType {
return cType
}
func mergeUser(mergeIntoUser int64, cUser ConflictingUsers, sqlStore *sqlstore.SQLStore) error {
func mergeUser(ctx context.Context, mergeIntoUser int64, cUser ConflictingUsers, sqlStore *sqlstore.SQLStore) error {
stringIds := strings.Split(cUser.Ids, ",")
fromUserIds := make([]int64, 0, len(stringIds))
for _, raw := range stringIds {
@@ -247,7 +255,7 @@ func mergeUser(mergeIntoUser int64, cUser ConflictingUsers, sqlStore *sqlstore.S
}
fromUserIds = append(fromUserIds, v)
}
return sqlStore.MergeUser(mergeIntoUser, fromUserIds)
return sqlStore.MergeUser(ctx, mergeIntoUser, fromUserIds)
}
type ConflictingUsers struct {
@@ -46,11 +46,74 @@ func TestUserManagerListConflictingUsers(t *testing.T) {
}
_, err = sqlStore.CreateUser(context.Background(), dupUserLogincmd)
require.NoError(t, err)
// require metrics and statistics to be 2
m, err := GetUsersWithConflictingEmailsOrLogins(context.Background(), sqlStore)
t.Logf("%v+", m)
require.NoError(t, err)
require.Equal(t, 1, len(m))
}
})
}
func TestMergeUser(t *testing.T) {
t.Run("should be able to merge user", func(t *testing.T) {
// Restore after destructive operation
sqlStore := sqlstore.InitTestDB(t)
const testOrgID int64 = 1
// "Skipping conflicting users test for mysql as it does make unique constraint case insensitive by default
if sqlStore.GetDialect().DriverName() != "mysql" {
// setup
dupUserEmailcmd := user.CreateUserCommand{
Email: "USERDUPLICATETEST1@TEST.COM",
Name: "user name 1",
Login: "USER_DUPLICATE_TEST_1_LOGIN",
OrgID: testOrgID,
}
userWithUpperCase, err := sqlStore.CreateUser(context.Background(), dupUserEmailcmd)
require.NoError(t, err)
// add additional user with conflicting login where DOMAIN is upper case
dupUserLogincmd := user.CreateUserCommand{
Email: "userduplicatetest1@test.com",
Name: "user name 1",
Login: "user_duplicate_test_1_login",
OrgID: testOrgID,
}
userWithLowerCase, err := sqlStore.CreateUser(context.Background(), dupUserLogincmd)
require.NoError(t, err)
// fromUser should be replaced by userWithLowerCase
team1, err := sqlStore.CreateTeam("group1 name", "test1@test.com", testOrgID)
require.NoError(t, err)
err = sqlStore.AddTeamMember(userWithUpperCase.ID, testOrgID, team1.Id, false, 0)
require.NoError(t, err)
// setup finished
m, err := GetUsersWithConflictingEmailsOrLogins(context.Background(), sqlStore)
require.NoError(t, err)
mergeErr := mergeUser(context.Background(), userWithLowerCase.ID, m[0], sqlStore)
require.NoError(t, mergeErr)
// start test
// fromUser should be deleted after merger
t.Logf("testing getting user")
query := &models.GetUserByIdQuery{Id: userWithUpperCase.ID}
err = sqlStore.GetUserById(context.Background(), query)
require.Error(t, models.ErrUserNotFound, err)
testUser := &models.SignedInUser{
OrgId: testOrgID,
Permissions: map[int64]map[string][]string{
1: {
ac.ActionTeamsRead: []string{ac.ScopeTeamsAll},
ac.ActionOrgUsersRead: []string{ac.ScopeUsersAll},
},
},
}
t.Logf("testing getting team member")
q1 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, SignedInUser: testUser}
err = sqlStore.GetTeamMembers(context.Background(), q1)
require.NoError(t, err)
require.Equal(t, 0, len(q1.Result))
}
})
}
+52 -8
View File
@@ -743,6 +743,10 @@ func (ss *SQLStore) DeleteUser(ctx context.Context, cmd *models.DeleteUserComman
})
}
func (ss *SQLStore) DeleteUserInSession(ctx context.Context, sess *DBSession, cmd *models.DeleteUserCommand) error {
return deleteUserInTransaction(ss, sess, cmd)
}
func deleteUserInTransaction(ss *SQLStore, sess *DBSession, cmd *models.DeleteUserCommand) error {
// Check if user exists
user := user.User{ID: cmd.UserId}
@@ -818,26 +822,66 @@ func UserDeletions() []string {
return deletes
}
func updateAllTablesForFromUser(intoUser user.User, fromUserId int64) error {
func UserUpdates() []string {
deletes := []string{
"UPDATE star set email = ?, user_id = ?, login = ? WHERE user_id = ?",
"UPDATE team_member set user_id = ? WHERE user_id = ?",
"DELETE FROM team_member WHERE user_id = ?",
"UPDATE FROM " + dialect.Quote("user") + " WHERE id = ?",
"UPDATE FROM org_user WHERE user_id = ?",
"DELETE FROM dashboard_acl WHERE user_id = ?",
"DELETE FROM preferences WHERE user_id = ?",
"DELETE FROM team_member WHERE user_id = ?",
"DELETE FROM user_auth WHERE user_id = ?",
"DELETE FROM user_auth_token WHERE user_id = ?",
"DELETE FROM quota WHERE user_id = ?",
}
return deletes
}
func (ss *SQLStore) updateUserIds(intoUser user.User, fromUser user.User, sess *DBSession) error {
// TODO:
fmt.Printf("merging user %d into user %d", fromUserId, intoUser.ID)
sql := `
UPDATE team_member
SET user_id = ?
WHERE user_id = ?;
UPDATE user_role
SET user_id = ?
WHERE user_id = ?;
`
_, err := sess.Exec(sql, intoUser.ID, fromUser.ID)
if err != nil {
return err
}
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 {
// MergeUser sets the user Server Admin flag
func (ss *SQLStore) MergeUser(ctx context.Context, intoUserId int64, fromUserIds []int64) error {
return ss.WithTransactionalDbSession(ctx, 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 {
var fromUser user.User
if _, err := sess.ID(fromUserId).Where(notServiceAccountFilter(ss)).Get(&fromUser); err != nil {
return err
}
// update all tables fromUserIds to intoUserIds
updateAllTablesForFromUser(intoUser, fromUserId)
}
err := ss.updateUserIds(intoUser, fromUser, sess)
if err != nil {
return err
}
// deletes the from user
delErr := ss.DeleteUserInSession(ctx, sess, &models.DeleteUserCommand{UserId: fromUserId})
if delErr != nil {
return delErr
}
}
return nil
})
}