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