github.com/rajatvaryani/mattermost-server@v5.11.1+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 ruser, err := a.CreateUser(user) 343 if ruser == nil { 344 return errors.New("Unable to create user. Error: " + err.Error()) 345 } 346 347 if systemAdmin { 348 if _, err := a.UpdateUserRoles(ruser.Id, "system_user system_admin", false); err != nil { 349 return errors.New("Unable to make user system admin. Error: " + err.Error()) 350 } 351 } else { 352 // This else case exists to prevent the first user created from being 353 // created as a system admin unless explicity specified. 354 if _, err := a.UpdateUserRoles(ruser.Id, "system_user", false); err != nil { 355 return errors.New("If this is the first user: Unable to prevent user from being system admin. Error: " + err.Error()) 356 } 357 } 358 359 CommandPrettyPrintln("id: " + ruser.Id) 360 CommandPrettyPrintln("username: " + ruser.Username) 361 CommandPrettyPrintln("nickname: " + ruser.Nickname) 362 CommandPrettyPrintln("position: " + ruser.Position) 363 CommandPrettyPrintln("first_name: " + ruser.FirstName) 364 CommandPrettyPrintln("last_name: " + ruser.LastName) 365 CommandPrettyPrintln("email: " + ruser.Email) 366 CommandPrettyPrintln("auth_service: " + ruser.AuthService) 367 368 return nil 369 } 370 371 func userInviteCmdF(command *cobra.Command, args []string) error { 372 a, err := InitDBCommandContextCobra(command) 373 if err != nil { 374 return err 375 } 376 defer a.Shutdown() 377 378 if len(args) < 2 { 379 return errors.New("Expected at least two arguments. See help text for details.") 380 } 381 382 email := args[0] 383 if !model.IsValidEmail(email) { 384 return errors.New("Invalid email") 385 } 386 387 teams := getTeamsFromTeamArgs(a, args[1:]) 388 for i, team := range teams { 389 err := inviteUser(a, email, team, args[i+1]) 390 391 if err != nil { 392 CommandPrintErrorln(err.Error()) 393 } 394 } 395 396 return nil 397 } 398 399 func inviteUser(a *app.App, email string, team *model.Team, teamArg string) error { 400 invites := []string{email} 401 if team == nil { 402 return fmt.Errorf("Can't find team '%v'", teamArg) 403 } 404 405 if !*a.Config().ServiceSettings.EnableEmailInvitations { 406 return fmt.Errorf("Email invites are disabled.") 407 } 408 409 a.SendInviteEmails(team, "Administrator", "Mattermost CLI "+model.NewId(), invites, *a.Config().ServiceSettings.SiteURL) 410 CommandPrettyPrintln("Invites may or may not have been sent.") 411 412 return nil 413 } 414 415 func resetUserPasswordCmdF(command *cobra.Command, args []string) error { 416 a, err := InitDBCommandContextCobra(command) 417 if err != nil { 418 return err 419 } 420 defer a.Shutdown() 421 422 if len(args) != 2 { 423 return errors.New("Expected two arguments. See help text for details.") 424 } 425 426 user := getUserFromUserArg(a, args[0]) 427 if user == nil { 428 return errors.New("Unable to find user '" + args[0] + "'") 429 } 430 password := args[1] 431 432 if result := <-a.Srv.Store.User().UpdatePassword(user.Id, model.HashPassword(password)); result.Err != nil { 433 return result.Err 434 } 435 436 return nil 437 } 438 439 func updateUserEmailCmdF(command *cobra.Command, args []string) error { 440 a, err := InitDBCommandContextCobra(command) 441 if err != nil { 442 return err 443 } 444 defer a.Shutdown() 445 446 if len(args) != 2 { 447 return errors.New("Expected two arguments. See help text for details.") 448 } 449 450 newEmail := args[1] 451 452 if !model.IsValidEmail(newEmail) { 453 return errors.New("Invalid email: '" + newEmail + "'") 454 } 455 456 if len(args) != 2 { 457 return errors.New("Expected two arguments. See help text for details.") 458 } 459 460 user := getUserFromUserArg(a, args[0]) 461 if user == nil { 462 return errors.New("Unable to find user '" + args[0] + "'") 463 } 464 465 user.Email = newEmail 466 _, errUpdate := a.UpdateUser(user, true) 467 if errUpdate != nil { 468 return errors.New(errUpdate.Message) 469 } 470 471 return nil 472 } 473 474 func resetUserMfaCmdF(command *cobra.Command, args []string) error { 475 a, err := InitDBCommandContextCobra(command) 476 if err != nil { 477 return err 478 } 479 defer a.Shutdown() 480 481 if len(args) < 1 { 482 return errors.New("Expected at least one argument. See help text for details.") 483 } 484 485 users := getUsersFromUserArgs(a, args) 486 487 for i, user := range users { 488 if user == nil { 489 return errors.New("Unable to find user '" + args[i] + "'") 490 } 491 492 if err := a.DeactivateMfa(user.Id); err != nil { 493 return err 494 } 495 } 496 497 return nil 498 } 499 500 func deleteUserCmdF(command *cobra.Command, args []string) error { 501 a, err := InitDBCommandContextCobra(command) 502 if err != nil { 503 return err 504 } 505 defer a.Shutdown() 506 507 if len(args) < 1 { 508 return errors.New("Expected at least one argument. See help text for details.") 509 } 510 511 confirmFlag, _ := command.Flags().GetBool("confirm") 512 if !confirmFlag { 513 var confirm string 514 CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ") 515 fmt.Scanln(&confirm) 516 517 if confirm != "YES" { 518 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 519 } 520 CommandPrettyPrintln("Are you sure you want to permanently delete the specified users? (YES/NO): ") 521 fmt.Scanln(&confirm) 522 if confirm != "YES" { 523 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 524 } 525 } 526 527 users := getUsersFromUserArgs(a, args) 528 529 for i, user := range users { 530 if user == nil { 531 return errors.New("Unable to find user '" + args[i] + "'") 532 } 533 534 if err := a.PermanentDeleteUser(user); err != nil { 535 return err 536 } 537 } 538 539 return nil 540 } 541 542 func deleteAllUsersCommandF(command *cobra.Command, args []string) error { 543 a, err := InitDBCommandContextCobra(command) 544 if err != nil { 545 return err 546 } 547 defer a.Shutdown() 548 549 if len(args) > 0 { 550 return errors.New("Expected zero arguments.") 551 } 552 553 confirmFlag, _ := command.Flags().GetBool("confirm") 554 if !confirmFlag { 555 var confirm string 556 CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ") 557 fmt.Scanln(&confirm) 558 559 if confirm != "YES" { 560 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 561 } 562 CommandPrettyPrintln("Are you sure you want to permanently delete all user accounts? (YES/NO): ") 563 fmt.Scanln(&confirm) 564 if confirm != "YES" { 565 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 566 } 567 } 568 569 if err := a.PermanentDeleteAllUsers(); err != nil { 570 return err 571 } 572 573 CommandPrettyPrintln("All user accounts successfully deleted.") 574 575 return nil 576 } 577 578 func migrateAuthCmdF(command *cobra.Command, args []string) error { 579 if args[1] == "saml" { 580 return migrateAuthToSamlCmdF(command, args) 581 } 582 return migrateAuthToLdapCmdF(command, args) 583 } 584 585 func migrateAuthToLdapCmdF(command *cobra.Command, args []string) error { 586 a, err := InitDBCommandContextCobra(command) 587 if err != nil { 588 return err 589 } 590 defer a.Shutdown() 591 592 fromAuth := args[0] 593 matchField := args[2] 594 595 if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "saml") { 596 return errors.New("Invalid from_auth argument") 597 } 598 599 // Email auth in Mattermost system is represented by "" 600 if fromAuth == "email" { 601 fromAuth = "" 602 } 603 604 if len(matchField) == 0 || (matchField != "email" && matchField != "username") { 605 return errors.New("Invalid match_field argument") 606 } 607 608 forceFlag, _ := command.Flags().GetBool("force") 609 dryRunFlag, _ := command.Flags().GetBool("dryRun") 610 611 if migrate := a.AccountMigration; migrate != nil { 612 if err := migrate.MigrateToLdap(fromAuth, matchField, forceFlag, dryRunFlag); err != nil { 613 return errors.New("Error while migrating users: " + err.Error()) 614 } 615 616 CommandPrettyPrintln("Successfully migrated accounts.") 617 } 618 619 return nil 620 } 621 622 func migrateAuthToSamlCmdF(command *cobra.Command, args []string) error { 623 a, err := InitDBCommandContextCobra(command) 624 if err != nil { 625 return err 626 } 627 defer a.Shutdown() 628 629 dryRunFlag, _ := command.Flags().GetBool("dryRun") 630 autoFlag, _ := command.Flags().GetBool("auto") 631 632 matchesFile := "" 633 matches := map[string]string{} 634 if !autoFlag { 635 matchesFile = args[2] 636 637 file, e := ioutil.ReadFile(matchesFile) 638 if e != nil { 639 return errors.New("Invalid users file.") 640 } 641 if json.Unmarshal(file, &matches) != nil { 642 return errors.New("Invalid users file.") 643 } 644 } 645 646 fromAuth := args[0] 647 648 if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "ldap") { 649 return errors.New("Invalid from_auth argument") 650 } 651 652 if autoFlag && !dryRunFlag { 653 var confirm string 654 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):") 655 fmt.Scanln(&confirm) 656 657 if confirm != "YES" { 658 return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") 659 } 660 } 661 662 // Email auth in Mattermost system is represented by "" 663 if fromAuth == "email" { 664 fromAuth = "" 665 } 666 667 if migrate := a.AccountMigration; migrate != nil { 668 if err := migrate.MigrateToSaml(fromAuth, matches, autoFlag, dryRunFlag); err != nil { 669 return errors.New("Error while migrating users: " + err.Error()) 670 } 671 672 CommandPrettyPrintln("Successfully migrated accounts.") 673 } 674 675 return nil 676 } 677 678 func verifyUserCmdF(command *cobra.Command, args []string) error { 679 a, err := InitDBCommandContextCobra(command) 680 if err != nil { 681 return err 682 } 683 defer a.Shutdown() 684 685 if len(args) < 1 { 686 return errors.New("Expected at least one argument. See help text for details.") 687 } 688 689 users := getUsersFromUserArgs(a, args) 690 691 for i, user := range users { 692 if user == nil { 693 CommandPrintErrorln("Unable to find user '" + args[i] + "'") 694 continue 695 } 696 if cresult := <-a.Srv.Store.User().VerifyEmail(user.Id, user.Email); cresult.Err != nil { 697 CommandPrintErrorln("Unable to verify '" + args[i] + "' email. Error: " + cresult.Err.Error()) 698 } 699 } 700 701 return nil 702 } 703 704 func searchUserCmdF(command *cobra.Command, args []string) error { 705 a, err := InitDBCommandContextCobra(command) 706 if err != nil { 707 return err 708 } 709 defer a.Shutdown() 710 711 if len(args) < 1 { 712 return errors.New("Expected at least one argument. See help text for details.") 713 } 714 715 users := getUsersFromUserArgs(a, args) 716 717 for i, user := range users { 718 if i > 0 { 719 CommandPrettyPrintln("------------------------------") 720 } 721 if user == nil { 722 CommandPrintErrorln("Unable to find user '" + args[i] + "'") 723 continue 724 } 725 726 CommandPrettyPrintln("id: " + user.Id) 727 CommandPrettyPrintln("username: " + user.Username) 728 CommandPrettyPrintln("nickname: " + user.Nickname) 729 CommandPrettyPrintln("position: " + user.Position) 730 CommandPrettyPrintln("first_name: " + user.FirstName) 731 CommandPrettyPrintln("last_name: " + user.LastName) 732 CommandPrettyPrintln("email: " + user.Email) 733 CommandPrettyPrintln("auth_service: " + user.AuthService) 734 } 735 736 return nil 737 }