github.com/demisto/mattermost-server@v4.9.0-rc3+incompatible/cmd/commands/user.go (about) 1 // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package commands 5 6 import ( 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io/ioutil" 11 12 l4g "github.com/alecthomas/log4go" 13 "github.com/mattermost/mattermost-server/app" 14 "github.com/mattermost/mattermost-server/cmd" 15 "github.com/mattermost/mattermost-server/model" 16 "github.com/spf13/cobra" 17 ) 18 19 var UserCmd = &cobra.Command{ 20 Use: "user", 21 Short: "Management of users", 22 } 23 24 var UserActivateCmd = &cobra.Command{ 25 Use: "activate [emails, usernames, userIds]", 26 Short: "Activate users", 27 Long: "Activate users that have been deactivated.", 28 Example: ` user activate user@example.com 29 user activate username`, 30 RunE: userActivateCmdF, 31 } 32 33 var UserDeactivateCmd = &cobra.Command{ 34 Use: "deactivate [emails, usernames, userIds]", 35 Short: "Deactivate users", 36 Long: "Deactivate users. Deactivated users are immediately logged out of all sessions and are unable to log back in.", 37 Example: ` user deactivate user@example.com 38 user deactivate username`, 39 RunE: userDeactivateCmdF, 40 } 41 42 var UserCreateCmd = &cobra.Command{ 43 Use: "create", 44 Short: "Create a user", 45 Long: "Create a user", 46 Example: ` user create --email user@example.com --username userexample --password Password1`, 47 RunE: userCreateCmdF, 48 } 49 50 var UserInviteCmd = &cobra.Command{ 51 Use: "invite [email] [teams]", 52 Short: "Send user an email invite to a team.", 53 Long: `Send user an email invite to a team. 54 You can invite a user to multiple teams by listing them. 55 You can specify teams by name or ID.`, 56 Example: ` user invite user@example.com myteam 57 user invite user@example.com myteam1 myteam2`, 58 RunE: userInviteCmdF, 59 } 60 61 var ResetUserPasswordCmd = &cobra.Command{ 62 Use: "password [user] [password]", 63 Short: "Set a user's password", 64 Long: "Set a user's password", 65 Example: " user password user@example.com Password1", 66 RunE: resetUserPasswordCmdF, 67 } 68 69 var updateUserEmailCmd = &cobra.Command{ 70 Use: "email [user] [new email]", 71 Short: "Change email of the user", 72 Long: "Change email of the user.", 73 Example: ` user email test user@example.com 74 user activate username`, 75 RunE: updateUserEmailCmdF, 76 } 77 78 var ResetUserMfaCmd = &cobra.Command{ 79 Use: "resetmfa [users]", 80 Short: "Turn off MFA", 81 Long: `Turn off multi-factor authentication for a user. 82 If MFA enforcement is enabled, the user will be forced to re-enable MFA as soon as they login.`, 83 Example: " user resetmfa user@example.com", 84 RunE: resetUserMfaCmdF, 85 } 86 87 var DeleteUserCmd = &cobra.Command{ 88 Use: "delete [users]", 89 Short: "Delete users and all posts", 90 Long: "Permanently delete user and all related information including posts.", 91 Example: " user delete user@example.com", 92 RunE: deleteUserCmdF, 93 } 94 95 var DeleteAllUsersCmd = &cobra.Command{ 96 Use: "deleteall", 97 Short: "Delete all users and all posts", 98 Long: "Permanently delete all users and all related information including posts.", 99 Example: " user deleteall", 100 RunE: deleteAllUsersCommandF, 101 } 102 103 var MigrateAuthCmd = &cobra.Command{ 104 Use: "migrate_auth [from_auth] [to_auth] [migration-options]", 105 Short: "Mass migrate user accounts authentication type", 106 Long: `Migrates accounts from one authentication provider to another. For example, you can upgrade your authentication provider from email to ldap.`, 107 Example: " user migrate_auth email saml users.json", 108 Args: func(cmd *cobra.Command, args []string) error { 109 if len(args) < 2 { 110 return errors.New("Auth migration requires at least 2 arguments.") 111 } 112 113 toAuth := args[1] 114 115 if toAuth != "ldap" && toAuth != "saml" { 116 return errors.New("Invalid to_auth parameter, must be saml or ldap.") 117 } 118 119 if toAuth == "ldap" && len(args) != 3 { 120 return errors.New("Ldap migration requires 3 arguments.") 121 } 122 123 autoFlag, _ := cmd.Flags().GetBool("auto") 124 125 if toAuth == "saml" && autoFlag { 126 if len(args) != 2 { 127 return errors.New("Saml migration requires two arguments when using the --auto flag. See help text for details.") 128 } 129 } 130 131 if toAuth == "saml" && !autoFlag { 132 if len(args) != 3 { 133 return errors.New("Saml migration requires three arguments when not using the --auto flag. See help text for details.") 134 } 135 } 136 return nil 137 }, 138 RunE: migrateAuthCmdF, 139 } 140 141 var VerifyUserCmd = &cobra.Command{ 142 Use: "verify [users]", 143 Short: "Verify email of users", 144 Long: "Verify the emails of some users.", 145 Example: " user verify user1", 146 RunE: verifyUserCmdF, 147 } 148 149 var SearchUserCmd = &cobra.Command{ 150 Use: "search [users]", 151 Short: "Search for users", 152 Long: "Search for users based on username, email, or user ID.", 153 Example: " user search user1@mail.com user2@mail.com", 154 RunE: searchUserCmdF, 155 } 156 157 func init() { 158 UserCreateCmd.Flags().String("username", "", "Required. Username for the new user account.") 159 UserCreateCmd.Flags().String("email", "", "Required. The email address for the new user account.") 160 UserCreateCmd.Flags().String("password", "", "Required. The password for the new user account.") 161 UserCreateCmd.Flags().String("nickname", "", "Optional. The nickname for the new user account.") 162 UserCreateCmd.Flags().String("firstname", "", "Optional. The first name for the new user account.") 163 UserCreateCmd.Flags().String("lastname", "", "Optional. The last name for the new user account.") 164 UserCreateCmd.Flags().String("locale", "", "Optional. The locale (ex: en, fr) for the new user account.") 165 UserCreateCmd.Flags().Bool("system_admin", false, "Optional. If supplied, the new user will be a system administrator. Defaults to false.") 166 167 DeleteUserCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.") 168 169 DeleteAllUsersCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.") 170 171 MigrateAuthCmd.Flags().Bool("force", false, "Force the migration to occur even if there are duplicates on the LDAP server. Duplicates will not be migrated. (ldap only)") 172 MigrateAuthCmd.Flags().Bool("auto", false, "Automatically migrate all users. Assumes the usernames and emails are identical between Mattermost and SAML services. (saml only)") 173 MigrateAuthCmd.Flags().Bool("dryRun", false, "Run a simulation of the migration process without changing the database.") 174 MigrateAuthCmd.SetUsageTemplate(`Usage: 175 platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags] 176 177 Examples: 178 {{.Example}} 179 180 Arguments: 181 from_auth: 182 The authentication service to migrate users accounts from. 183 Supported options: email, gitlab, ldap, saml. 184 185 to_auth: 186 The authentication service to migrate users to. 187 Supported options: ldap, saml. 188 189 migration-options: 190 Migration specific options, full command help for more information. 191 192 Flags: 193 {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}} 194 195 Global Flags: 196 {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}} 197 `) 198 MigrateAuthCmd.SetHelpTemplate(`Usage: 199 platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags] 200 201 Examples: 202 {{.Example}} 203 204 Arguments: 205 from_auth: 206 The authentication service to migrate users accounts from. 207 Supported options: email, gitlab, ldap, saml. 208 209 to_auth: 210 The authentication service to migrate users to. 211 Supported options: ldap, saml. 212 213 migration-options (ldap): 214 match_field: 215 The field that is guaranteed to be the same in both authentication services. For example, if the users emails are consistent set to email. 216 Supported options: email, username. 217 218 migration-options (saml): 219 users_file: 220 The path of a json file with the usernames and emails of all users to migrate to SAML. The username and email must be the same that the SAML service provider store. And the email must match with the email in mattermost database. 221 222 Example json content: 223 { 224 "usr1@email.com": "usr.one", 225 "usr2@email.com": "usr.two" 226 } 227 228 Flags: 229 {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}} 230 231 Global Flags: 232 {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}} 233 `) 234 235 UserCmd.AddCommand( 236 UserActivateCmd, 237 UserDeactivateCmd, 238 UserCreateCmd, 239 UserInviteCmd, 240 ResetUserPasswordCmd, 241 updateUserEmailCmd, 242 ResetUserMfaCmd, 243 DeleteUserCmd, 244 DeleteAllUsersCmd, 245 MigrateAuthCmd, 246 VerifyUserCmd, 247 SearchUserCmd, 248 ) 249 cmd.RootCmd.AddCommand(UserCmd) 250 } 251 252 func userActivateCmdF(command *cobra.Command, args []string) error { 253 a, err := cmd.InitDBCommandContextCobra(command) 254 if err != nil { 255 return err 256 } 257 258 if len(args) < 1 { 259 return errors.New("Expected at least one argument. See help text for details.") 260 } 261 262 changeUsersActiveStatus(a, args, true) 263 return nil 264 } 265 266 func changeUsersActiveStatus(a *app.App, userArgs []string, active bool) { 267 users := getUsersFromUserArgs(a, userArgs) 268 for i, user := range users { 269 err := changeUserActiveStatus(a, user, userArgs[i], active) 270 271 if err != nil { 272 cmd.CommandPrintErrorln(err.Error()) 273 } 274 } 275 } 276 277 func changeUserActiveStatus(a *app.App, user *model.User, userArg string, activate bool) error { 278 if user == nil { 279 return fmt.Errorf("Can't find user '%v'", userArg) 280 } 281 if user.IsSSOUser() { 282 fmt.Println("You must also deactivate this user in the SSO provider or they will be reactivated on next login or sync.") 283 } 284 if _, err := a.UpdateActive(user, activate); err != nil { 285 return fmt.Errorf("Unable to change activation status of user: %v", userArg) 286 } 287 288 return nil 289 } 290 291 func userDeactivateCmdF(command *cobra.Command, args []string) error { 292 a, err := cmd.InitDBCommandContextCobra(command) 293 if err != nil { 294 return err 295 } 296 297 if len(args) < 1 { 298 return errors.New("Expected at least one argument. See help text for details.") 299 } 300 301 changeUsersActiveStatus(a, args, false) 302 return nil 303 } 304 305 func userCreateCmdF(command *cobra.Command, args []string) error { 306 a, err := cmd.InitDBCommandContextCobra(command) 307 if err != nil { 308 return err 309 } 310 311 username, erru := command.Flags().GetString("username") 312 if erru != nil || username == "" { 313 return errors.New("Username is required") 314 } 315 email, erre := command.Flags().GetString("email") 316 if erre != nil || email == "" { 317 return errors.New("Email is required") 318 } 319 password, errp := command.Flags().GetString("password") 320 if errp != nil || password == "" { 321 return errors.New("Password is required") 322 } 323 nickname, _ := command.Flags().GetString("nickname") 324 firstname, _ := command.Flags().GetString("firstname") 325 lastname, _ := command.Flags().GetString("lastname") 326 locale, _ := command.Flags().GetString("locale") 327 systemAdmin, _ := command.Flags().GetBool("system_admin") 328 329 user := &model.User{ 330 Username: username, 331 Email: email, 332 Password: password, 333 Nickname: nickname, 334 FirstName: firstname, 335 LastName: lastname, 336 Locale: locale, 337 } 338 339 if ruser, err := a.CreateUser(user); err != nil { 340 return errors.New("Unable to create user. Error: " + err.Error()) 341 } else if systemAdmin { 342 a.UpdateUserRoles(ruser.Id, "system_user system_admin", false) 343 } 344 345 cmd.CommandPrettyPrintln("Created User") 346 347 return nil 348 } 349 350 func userInviteCmdF(command *cobra.Command, args []string) error { 351 a, err := cmd.InitDBCommandContextCobra(command) 352 if err != nil { 353 return err 354 } 355 356 if len(args) < 2 { 357 return errors.New("Expected at least two arguments. See help text for details.") 358 } 359 360 email := args[0] 361 if !model.IsValidEmail(email) { 362 return errors.New("Invalid email") 363 } 364 365 teams := getTeamsFromTeamArgs(a, args[1:]) 366 for i, team := range teams { 367 err := inviteUser(a, email, team, args[i+1]) 368 369 if err != nil { 370 cmd.CommandPrintErrorln(err.Error()) 371 } 372 } 373 374 return nil 375 } 376 377 func inviteUser(a *app.App, email string, team *model.Team, teamArg string) error { 378 invites := []string{email} 379 if team == nil { 380 return fmt.Errorf("Can't find team '%v'", teamArg) 381 } 382 383 a.SendInviteEmails(team, "Administrator", invites, *a.Config().ServiceSettings.SiteURL) 384 cmd.CommandPrettyPrintln("Invites may or may not have been sent.") 385 386 return nil 387 } 388 389 func resetUserPasswordCmdF(command *cobra.Command, args []string) error { 390 a, err := cmd.InitDBCommandContextCobra(command) 391 if err != nil { 392 return err 393 } 394 395 if len(args) != 2 { 396 return errors.New("Expected two arguments. See help text for details.") 397 } 398 399 user := getUserFromUserArg(a, args[0]) 400 if user == nil { 401 return errors.New("Unable to find user '" + args[0] + "'") 402 } 403 password := args[1] 404 405 if result := <-a.Srv.Store.User().UpdatePassword(user.Id, model.HashPassword(password)); result.Err != nil { 406 return result.Err 407 } 408 409 return nil 410 } 411 412 func updateUserEmailCmdF(command *cobra.Command, args []string) error { 413 a, err := cmd.InitDBCommandContextCobra(command) 414 if err != nil { 415 return err 416 } 417 418 newEmail := args[1] 419 420 if !model.IsValidEmail(newEmail) { 421 return errors.New("Invalid email: '" + newEmail + "'") 422 } 423 424 if len(args) != 2 { 425 return errors.New("Expected two arguments. See help text for details.") 426 } 427 428 user := getUserFromUserArg(a, args[0]) 429 if user == nil { 430 return errors.New("Unable to find user '" + args[0] + "'") 431 } 432 433 user.Email = newEmail 434 _, errUpdate := a.UpdateUser(user, true) 435 if err != nil { 436 return errUpdate 437 } 438 439 return nil 440 } 441 442 func resetUserMfaCmdF(command *cobra.Command, args []string) error { 443 a, err := cmd.InitDBCommandContextCobra(command) 444 if err != nil { 445 return err 446 } 447 448 if len(args) < 1 { 449 return errors.New("Expected at least one argument. See help text for details.") 450 } 451 452 users := getUsersFromUserArgs(a, args) 453 454 for i, user := range users { 455 if user == nil { 456 return errors.New("Unable to find user '" + args[i] + "'") 457 } 458 459 if err := a.DeactivateMfa(user.Id); err != nil { 460 return err 461 } 462 } 463 464 return nil 465 } 466 467 func deleteUserCmdF(command *cobra.Command, args []string) error { 468 a, err := cmd.InitDBCommandContextCobra(command) 469 if err != nil { 470 return err 471 } 472 473 if len(args) < 1 { 474 return errors.New("Expected at least one argument. See help text for details.") 475 } 476 477 confirmFlag, _ := command.Flags().GetBool("confirm") 478 if !confirmFlag { 479 var confirm string 480 cmd.CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ") 481 fmt.Scanln(&confirm) 482 483 if confirm != "YES" { 484 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 485 } 486 cmd.CommandPrettyPrintln("Are you sure you want to permanently delete the specified users? (YES/NO): ") 487 fmt.Scanln(&confirm) 488 if confirm != "YES" { 489 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 490 } 491 } 492 493 users := getUsersFromUserArgs(a, args) 494 495 for i, user := range users { 496 if user == nil { 497 return errors.New("Unable to find user '" + args[i] + "'") 498 } 499 500 if err := a.PermanentDeleteUser(user); err != nil { 501 return err 502 } 503 } 504 505 return nil 506 } 507 508 func deleteAllUsersCommandF(command *cobra.Command, args []string) error { 509 a, err := cmd.InitDBCommandContextCobra(command) 510 if err != nil { 511 return err 512 } 513 514 if len(args) > 0 { 515 return errors.New("Expected zero arguments.") 516 } 517 518 confirmFlag, _ := command.Flags().GetBool("confirm") 519 if !confirmFlag { 520 var confirm string 521 cmd.CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ") 522 fmt.Scanln(&confirm) 523 524 if confirm != "YES" { 525 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 526 } 527 cmd.CommandPrettyPrintln("Are you sure you want to permanently delete all user accounts? (YES/NO): ") 528 fmt.Scanln(&confirm) 529 if confirm != "YES" { 530 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 531 } 532 } 533 534 if err := a.PermanentDeleteAllUsers(); err != nil { 535 return err 536 } 537 538 cmd.CommandPrettyPrintln("All user accounts successfully deleted.") 539 return nil 540 } 541 542 func migrateAuthCmdF(command *cobra.Command, args []string) error { 543 if args[1] == "saml" { 544 return migrateAuthToSamlCmdF(command, args) 545 } 546 return migrateAuthToLdapCmdF(command, args) 547 } 548 549 func migrateAuthToLdapCmdF(command *cobra.Command, args []string) error { 550 a, err := cmd.InitDBCommandContextCobra(command) 551 if err != nil { 552 return err 553 } 554 555 fromAuth := args[0] 556 matchField := args[2] 557 558 if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "saml") { 559 return errors.New("Invalid from_auth argument") 560 } 561 562 // Email auth in Mattermost system is represented by "" 563 if fromAuth == "email" { 564 fromAuth = "" 565 } 566 567 if len(matchField) == 0 || (matchField != "email" && matchField != "username") { 568 return errors.New("Invalid match_field argument") 569 } 570 571 forceFlag, _ := command.Flags().GetBool("force") 572 dryRunFlag, _ := command.Flags().GetBool("dryRun") 573 574 if migrate := a.AccountMigration; migrate != nil { 575 if err := migrate.MigrateToLdap(fromAuth, matchField, forceFlag, dryRunFlag); err != nil { 576 return errors.New("Error while migrating users: " + err.Error()) 577 } 578 579 cmd.CommandPrettyPrintln("Successfully migrated accounts.") 580 } 581 582 return nil 583 } 584 585 func migrateAuthToSamlCmdF(command *cobra.Command, args []string) error { 586 a, err := cmd.InitDBCommandContextCobra(command) 587 if err != nil { 588 return err 589 } 590 591 dryRunFlag, _ := command.Flags().GetBool("dryRun") 592 autoFlag, _ := command.Flags().GetBool("auto") 593 594 matchesFile := "" 595 matches := map[string]string{} 596 if !autoFlag { 597 matchesFile = args[2] 598 599 file, e := ioutil.ReadFile(matchesFile) 600 if e != nil { 601 return errors.New("Invalid users file.") 602 } 603 if json.Unmarshal(file, &matches) != nil { 604 return errors.New("Invalid users file.") 605 } 606 } 607 608 fromAuth := args[0] 609 610 if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "ldap") { 611 return errors.New("Invalid from_auth argument") 612 } 613 614 if autoFlag && !dryRunFlag { 615 var confirm string 616 cmd.CommandPrettyPrintln("You are about to perform an automatic \"" + fromAuth + " to saml\" migration. This must only be done if your current Mattermost users with " + fromAuth + " auth have the same username and email in your SAML service. Otherwise, provide the usernames and emails from your SAML Service using the \"users file\" without the \"--auto\" option.\n\nDo you want to proceed with automatic migration anyway? (YES/NO):") 617 fmt.Scanln(&confirm) 618 619 if confirm != "YES" { 620 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 621 } 622 } 623 624 // Email auth in Mattermost system is represented by "" 625 if fromAuth == "email" { 626 fromAuth = "" 627 } 628 629 if migrate := a.AccountMigration; migrate != nil { 630 if err := migrate.MigrateToSaml(fromAuth, matches, autoFlag, dryRunFlag); err != nil { 631 return errors.New("Error while migrating users: " + err.Error()) 632 } 633 l4g.Close() 634 cmd.CommandPrettyPrintln("Successfully migrated accounts.") 635 } 636 637 return nil 638 } 639 640 func verifyUserCmdF(command *cobra.Command, args []string) error { 641 a, err := cmd.InitDBCommandContextCobra(command) 642 if err != nil { 643 return err 644 } 645 646 if len(args) < 1 { 647 return errors.New("Expected at least one argument. See help text for details.") 648 } 649 650 users := getUsersFromUserArgs(a, args) 651 652 for i, user := range users { 653 if user == nil { 654 cmd.CommandPrintErrorln("Unable to find user '" + args[i] + "'") 655 continue 656 } 657 if cresult := <-a.Srv.Store.User().VerifyEmail(user.Id); cresult.Err != nil { 658 cmd.CommandPrintErrorln("Unable to verify '" + args[i] + "' email. Error: " + cresult.Err.Error()) 659 } 660 } 661 662 return nil 663 } 664 665 func searchUserCmdF(command *cobra.Command, args []string) error { 666 a, err := cmd.InitDBCommandContextCobra(command) 667 if err != nil { 668 return err 669 } 670 671 if len(args) < 1 { 672 return errors.New("Expected at least one argument. See help text for details.") 673 } 674 675 users := getUsersFromUserArgs(a, args) 676 677 for i, user := range users { 678 if i > 0 { 679 cmd.CommandPrettyPrintln("------------------------------") 680 } 681 if user == nil { 682 cmd.CommandPrintErrorln("Unable to find user '" + args[i] + "'") 683 continue 684 } 685 686 cmd.CommandPrettyPrintln("id: " + user.Id) 687 cmd.CommandPrettyPrintln("username: " + user.Username) 688 cmd.CommandPrettyPrintln("nickname: " + user.Nickname) 689 cmd.CommandPrettyPrintln("position: " + user.Position) 690 cmd.CommandPrettyPrintln("first_name: " + user.FirstName) 691 cmd.CommandPrettyPrintln("last_name: " + user.LastName) 692 cmd.CommandPrettyPrintln("email: " + user.Email) 693 cmd.CommandPrettyPrintln("auth_service: " + user.AuthService) 694 } 695 696 return nil 697 }