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