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