[GH-10800] convert bot account to user account (#10803)

* [10800] convert bot account to user account

* check password validity first

* review comments
This commit is contained in:
Siyuan Liu
2019-05-15 05:23:12 -07:00
committed by Miguel de la Cruz
parent a68ad55151
commit 2130e9f0b1
4 changed files with 223 additions and 18 deletions

View File

@@ -46,12 +46,13 @@ var UserCreateCmd = &cobra.Command{
}
var UserConvertCmd = &cobra.Command{
Use: "convert [emails, usernames, userIds] --bot",
Short: "Convert users to bots",
Long: "Convert users to bots",
Example: ` user convert user@example.com anotherUser --bot`,
Args: cobra.MinimumNArgs(1),
RunE: userConvertCmdF,
Use: "convert [emails, usernames, userIds] --bot",
Short: "Convert users to bots, or a bot to a user",
Long: "Convert users to bots, or a bot to a user",
Example: ` user convert user@example.com anotherUser --bot
user convert botusername --email new.email@email.com --password password --user`,
Args: cobra.MinimumNArgs(1),
RunE: userConvertCmdF,
}
var UserInviteCmd = &cobra.Command{
@@ -172,6 +173,15 @@ func init() {
UserCreateCmd.Flags().Bool("system_admin", false, "Optional. If supplied, the new user will be a system administrator. Defaults to false.")
UserConvertCmd.Flags().Bool("bot", false, "If supplied, convert users to bots.")
UserConvertCmd.Flags().Bool("user", false, "If supplied, convert a bot to a user.")
UserConvertCmd.Flags().String("password", "", "The password for converted new user account. Required when \"user\" flag is set.")
UserConvertCmd.Flags().String("username", "", "Username for the converted user account. Ignored when \"user\" flag is missing.")
UserConvertCmd.Flags().String("email", "", "The email address for the converted user account. Ignored when \"user\" flag is missing.")
UserConvertCmd.Flags().String("nickname", "", "The nickname for the converted user account. Ignored when \"user\" flag is missing.")
UserConvertCmd.Flags().String("firstname", "", "The first name for the converted user account. Ignored when \"user\" flag is missing.")
UserConvertCmd.Flags().String("lastname", "", "The last name for the converted user account. Ignored when \"user\" flag is missing.")
UserConvertCmd.Flags().String("locale", "", "The locale (ex: en, fr) for converted new user account. Ignored when \"user\" flag is missing.")
UserConvertCmd.Flags().Bool("system_admin", false, "If supplied, the converted user will be a system administrator. Defaults to false. Ignored when \"user\" flag is missing.")
DeleteUserCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.")
@@ -398,6 +408,126 @@ func usersToBots(args []string, a *app.App) {
}
}
func getUpdatedPassword(command *cobra.Command, a *app.App, user *model.User) (string, error) {
password, err := command.Flags().GetString("password")
if err != nil {
return "", fmt.Errorf("Unable to read password. Error: %s", err.Error())
}
if password == "" {
return "", errors.New("Password is required.")
}
return password, nil
}
func getUpdatedUserModel(command *cobra.Command, a *app.App, user *model.User) (*model.User, error) {
username, _ := command.Flags().GetString("username")
if username == "" {
if user.Username == "" {
return nil, errors.New("Invalid username. Username is empty.")
}
} else {
user.Username = username
}
email, _ := command.Flags().GetString("email")
if email == "" {
if user.Email == "" {
return nil, errors.New("Invalid email. Email is empty.")
}
} else {
user.Email = email
}
nickname, _ := command.Flags().GetString("nickname")
if nickname != "" {
user.Nickname = nickname
}
firstname, _ := command.Flags().GetString("firstname")
if firstname != "" {
user.FirstName = firstname
}
lastname, _ := command.Flags().GetString("lastname")
if lastname != "" {
user.LastName = lastname
}
locale, _ := command.Flags().GetString("locale")
if locale != "" {
user.Locale = locale
}
if !user.IsLDAPUser() && !user.IsSAMLUser() && !app.CheckUserDomain(user, *a.Config().TeamSettings.RestrictCreationToDomains) {
return nil, errors.New("The email does not belong to an accepted domain.")
}
return user, nil
}
func botToUser(command *cobra.Command, args []string, a *app.App) error {
if len(args) != 1 {
return errors.New("Expect 1 argument. See help text for more details.")
}
user := getUserFromUserArg(a, args[0])
if user == nil {
return errors.New("Unable to find bot.")
}
_, appErr := a.GetBot(user.Id, false)
if appErr != nil {
return fmt.Errorf("Unable to find bot. Error: %s", appErr.Error())
}
password, err := getUpdatedPassword(command, a, user)
if err != nil {
return err
}
user, err = getUpdatedUserModel(command, a, user)
if err != nil {
return err
}
user, appErr = a.UpdateUser(user, false)
if appErr != nil {
return fmt.Errorf("Unable to update user. Error: %s" + appErr.Error())
}
appErr = a.UpdatePassword(user, password)
if appErr != nil {
return fmt.Errorf("Unable to update password. Error: %s", appErr.Error())
}
systemAdmin, _ := command.Flags().GetBool("system_admin")
if systemAdmin && !user.IsInRole(model.SYSTEM_ADMIN_ROLE_ID) {
if _, appErr = a.UpdateUserRoles(
user.Id,
fmt.Sprintf("%s %s", user.Roles, model.SYSTEM_ADMIN_ROLE_ID),
false); appErr != nil {
return fmt.Errorf("Unable to make user system admin. Error: %s" + appErr.Error())
}
}
result := <-a.Srv.Store.Bot().PermanentDelete(user.Id)
if result.Err != nil {
return fmt.Errorf("Unable to delete bot. Error: %s", result.Err.Error())
}
CommandPrettyPrintln("id: " + user.Id)
CommandPrettyPrintln("username: " + user.Username)
CommandPrettyPrintln("email: " + user.Email)
CommandPrettyPrintln("nickname: " + user.Nickname)
CommandPrettyPrintln("first_name: " + user.FirstName)
CommandPrettyPrintln("last_name: " + user.LastName)
CommandPrettyPrintln("roles: " + user.Roles)
CommandPrettyPrintln("locale: " + user.Locale)
return nil
}
func userConvertCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
@@ -410,8 +540,21 @@ func userConvertCmdF(command *cobra.Command, args []string) error {
return errors.New("Invalid command. See help text for details.")
}
if !toBot {
return errors.New("Expect \"bot\" flag to be set. See help text for details.")
toUser, err := command.Flags().GetBool("user")
if err != nil {
return errors.New("Invalid command. See help text for details.")
}
if !(toUser || toBot) {
return errors.New("Expect either \"user\" flag or \"bot\" flag. See help text for details.")
}
if toUser && toBot {
return errors.New("Expect either \"user\" flag or \"bot\" flag but not both. See help text for details.")
}
if toUser {
return botToUser(command, args, a)
}
usersToBots(args, a)

View File

@@ -117,11 +117,77 @@ func TestChangeUserEmail(t *testing.T) {
}
func TestConvertUserToBot(t *testing.T) {
func TestConvertUser(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
th.CheckCommand(t, "user", "convert", th.BasicUser.Username, "anotherinvaliduser", "--bot")
result := <-th.App.Srv.Store.Bot().Get(th.BasicUser.Id, false)
require.Nil(t, result.Err)
t.Run("Invalid command line input", func(t *testing.T) {
err := th.RunCommand(t, "user", "convert", th.BasicUser.Username)
require.NotNil(t, err)
err = th.RunCommand(t, "user", "convert", th.BasicUser.Username, "--user", "--bot")
require.NotNil(t, err)
err = th.RunCommand(t, "user", "convert", "--bot")
require.NotNil(t, err)
})
t.Run("Convert to bot from username", func(t *testing.T) {
th.CheckCommand(t, "user", "convert", th.BasicUser.Username, "anotherinvaliduser", "--bot")
result := <-th.App.Srv.Store.Bot().Get(th.BasicUser.Id, false)
require.Nil(t, result.Err)
})
t.Run("Unable to convert to user with missing password", func(t *testing.T) {
err := th.RunCommand(t, "user", "convert", th.BasicUser.Username, "--user")
require.NotNil(t, err)
})
t.Run("Unable to convert to user with invalid email", func(t *testing.T) {
err := th.RunCommand(t, "user", "convert", th.BasicUser.Username, "--user",
"--password", "password",
"--email", "invalidEmail")
require.NotNil(t, err)
})
t.Run("Convert to user with minimum flags", func(t *testing.T) {
err := th.RunCommand(t, "user", "convert", th.BasicUser.Username, "--user",
"--password", "password")
require.Nil(t, err)
result := <-th.App.Srv.Store.Bot().Get(th.BasicUser.Id, false)
require.NotNil(t, result.Err)
})
t.Run("Convert to bot from email", func(t *testing.T) {
th.CheckCommand(t, "user", "convert", th.BasicUser2.Email, "--bot")
result := <-th.App.Srv.Store.Bot().Get(th.BasicUser2.Id, false)
require.Nil(t, result.Err)
})
t.Run("Convert to user with all flags", func(t *testing.T) {
err := th.RunCommand(t, "user", "convert", th.BasicUser2.Username, "--user",
"--password", "password",
"--username", "newusername",
"--email", "valid@email.com",
"--nickname", "newNickname",
"--firstname", "newFirstName",
"--lastname", "newLastName",
"--locale", "en_CA",
"--system_admin")
require.Nil(t, err)
result := <-th.App.Srv.Store.Bot().Get(th.BasicUser2.Id, false)
require.NotNil(t, result.Err)
user, appErr := th.App.Srv.Store.User().Get(th.BasicUser2.Id)
require.Nil(t, appErr)
require.Equal(t, "newusername", user.Username)
require.Equal(t, "valid@email.com", user.Email)
require.Equal(t, "newNickname", user.Nickname)
require.Equal(t, "newFirstName", user.FirstName)
require.Equal(t, "newLastName", user.LastName)
require.Equal(t, "en_CA", user.Locale)
require.True(t, user.IsInRole("system_admin"))
})
}

View File

@@ -233,12 +233,6 @@ func (us SqlBotStore) Update(bot *model.Bot) store.StoreChannel {
// If the corresponding user is to be deleted, it must be done via the user store.
func (us SqlBotStore) PermanentDelete(botUserId string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
userResult := <-us.User().PermanentDelete(botUserId)
if userResult.Err != nil {
result.Err = userResult.Err
return
}
if _, err := us.GetMaster().Exec(`
DELETE FROM
Bots

View File

@@ -48,6 +48,7 @@ func testBotStoreGet(t *testing.T, ss store.Store) {
DeleteAt: 0,
})
store.Must(ss.Bot().PermanentDelete(permanentlyDeletedBot.UserId))
defer func() { store.Must(ss.User().PermanentDelete(permanentlyDeletedBot.UserId)) }()
b1, _ := makeBotWithUser(ss, &model.Bot{
Username: "b1",
@@ -123,6 +124,7 @@ func testBotStoreGetAll(t *testing.T, ss store.Store) {
DeleteAt: 0,
})
store.Must(ss.Bot().PermanentDelete(permanentlyDeletedBot.UserId))
defer func() { store.Must(ss.User().PermanentDelete(permanentlyDeletedBot.UserId)) }()
b1, _ := makeBotWithUser(ss, &model.Bot{
Username: "b1",