diff --git a/api/users.go b/api/users.go index 15ff924b..f615cbc2 100644 --- a/api/users.go +++ b/api/users.go @@ -7,10 +7,22 @@ type CreateUserRequest struct { Roleids []string `json:"roleIds"` } +type DisableUserMfa struct { + Email string `json:"email"` + Password string `json:"password"` +} + type UpdateUserEmail struct { Email string `json:"email"` } +type UpdateUserRequest struct { + Firstname string `json:"firstName"` + Lastname string `json:"lastName"` + IsActive bool `json:"isActive"` + Roleids []string `json:"roleIds"` +} + type UserResponse struct { Firstname string `json:"firstName"` Lastname string `json:"lastName"` diff --git a/pkg/cmd/roles/update/update.go b/pkg/cmd/roles/update/update.go index f101066b..89d0b535 100644 --- a/pkg/cmd/roles/update/update.go +++ b/pkg/cmd/roles/update/update.go @@ -30,7 +30,7 @@ func NewCmdRoleUpdate() *cobra.Command { cmd := &cobra.Command{ Use: "update", - Short: "Updates a Sumo Logic role", + Short: "Updates a Sumo Logic role.", Run: func(cmd *cobra.Command, args []string) { logger := logging.GetLoggerForCommand(cmd) logger.Debug().Msg("Role update request started.") @@ -39,25 +39,26 @@ func NewCmdRoleUpdate() *cobra.Command { }, } - cmd.Flags().StringVar(&id, "id", "", "Specify the id of the role to update") - cmd.Flags().StringVar(&name, "name", "", "Specify the name for the role") - cmd.Flags().StringVar(&description, "description", "", "Specify the role description") - cmd.Flags().StringVar(&filter, "filter", "", "Search filter for the role") - cmd.Flags().StringSliceVar(&users, "users", []string{}, "Comma deliminated list of user ids to add to the role") - cmd.Flags().StringSliceVar(&capabilities, "capabilities", []string{}, "Comma deliminated list of capabilities") + cmd.Flags().StringVar(&id, "id", "", "Specify the id of the role to update.") + cmd.Flags().StringVar(&name, "name", "", "Specify the name for the role.") + cmd.Flags().StringVar(&description, "description", "", "Specify the role description.") + cmd.Flags().StringVar(&filter, "filter", "", "Search filter for the role.") + cmd.Flags().StringSliceVar(&users, "users", []string{}, "Comma deliminated list of user ids to add to the role.") + cmd.Flags().StringSliceVar(&capabilities, "capabilities", []string{}, "Comma deliminated list of capabilities.") cmd.Flags().BoolVar(&autofill, "autofill", true, "Is set to true by default.") - cmd.Flags().BoolVar(&merge, "merge", true, "Is set to true by default, if set to false it will overwrite the role") - cmd.Flags().StringVar(&output, "output", "", "Specify the field to export the value from") - + cmd.Flags().BoolVar(&merge, "merge", true, "Is set to true by default, if set to false it will overwrite the role.") + cmd.Flags().StringVar(&output, "output", "", "Specify the field to export the value from.") + cmd.MarkFlagRequired("id") + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("description") + cmd.MarkFlagRequired("filter") + cmd.MarkFlagRequired("users") + cmd.MarkFlagRequired("capabilities") return cmd } func updateRole(id string, name string, description string, filter string, users []string, capabilities []string, autofill bool, merge bool, output string, logger zerolog.Logger) { var roleInfo api.RoleData - if id == "" { - fmt.Println("--id field needs to be set.") - os.Exit(0) - } if merge == true { requestUrl := "v1/roles/" + id diff --git a/pkg/cmd/users/change/change.go b/pkg/cmd/users/change/change.go index b79dce84..94a3337a 100644 --- a/pkg/cmd/users/change/change.go +++ b/pkg/cmd/users/change/change.go @@ -9,7 +9,6 @@ import ( "github.com/wizedkyle/sumocli/pkg/cmd/factory" "github.com/wizedkyle/sumocli/pkg/logging" "io/ioutil" - "os" ) func NewCmdUserChangeEmail() *cobra.Command { @@ -31,20 +30,13 @@ func NewCmdUserChangeEmail() *cobra.Command { cmd.Flags().StringVar(&id, "id", "", "Specify the id of the user that needs to have the email changed.") cmd.Flags().StringVar(&email, "email", "", "Specify the users new email address.") + cmd.MarkFlagRequired("id") + cmd.MarkFlagRequired("email") return cmd } func userChangeEmail(id string, email string, logger zerolog.Logger) { - if id == "" { - fmt.Println("--id field needs to be set.") - os.Exit(0) - } - if email == "" { - fmt.Println("--email field needs to be set.") - os.Exit(0) - } - requestBodySchema := &api.UpdateUserEmail{ Email: email, } diff --git a/pkg/cmd/users/create/create.go b/pkg/cmd/users/create/create.go index d47fe217..3a4eeafb 100644 --- a/pkg/cmd/users/create/create.go +++ b/pkg/cmd/users/create/create.go @@ -37,6 +37,10 @@ func NewCmdUserCreate() *cobra.Command { cmd.Flags().StringVar(&emailAddress, "email", "", "Email address of the user") cmd.Flags().StringSliceVar(&roleIds, "roleids", []string{}, "Comma deliminated list of Role Ids") cmd.Flags().StringVar(&output, "output", "", "Specify the field to export the value from") + cmd.MarkFlagRequired("firstname") + cmd.MarkFlagRequired("lastname") + cmd.MarkFlagRequired("email") + cmd.MarkFlagRequired("roleids") return cmd } diff --git a/pkg/cmd/users/delete/delete.go b/pkg/cmd/users/delete/delete.go index 663cefc3..bd328aaa 100644 --- a/pkg/cmd/users/delete/delete.go +++ b/pkg/cmd/users/delete/delete.go @@ -9,7 +9,6 @@ import ( "github.com/wizedkyle/sumocli/pkg/cmd/factory" "github.com/wizedkyle/sumocli/pkg/logging" "io/ioutil" - "os" ) func NewCmdUserDelete() *cobra.Command { @@ -31,16 +30,11 @@ func NewCmdUserDelete() *cobra.Command { cmd.Flags().StringVar(&id, "id", "", "Specify the id of the user to delete.") cmd.Flags().StringVar(&transferTo, "transferto", "", "Specify the id of the user to transfer data to.") - + cmd.MarkFlagRequired("id") return cmd } func deleteUser(id string, transferTo string, logger zerolog.Logger) { - if id == "" { - fmt.Println("--id field needs to be set.") - os.Exit(0) - } - requestUrl := "v1/users/" + id client, request := factory.NewHttpRequest("DELETE", requestUrl) response, err := client.Do(request) diff --git a/pkg/cmd/users/disable/disable.go b/pkg/cmd/users/disable/disable.go index dcf7f345..6959285d 100644 --- a/pkg/cmd/users/disable/disable.go +++ b/pkg/cmd/users/disable/disable.go @@ -1,25 +1,28 @@ package disable import ( + "encoding/json" + "errors" + "fmt" + "github.com/manifoldco/promptui" "github.com/rs/zerolog" "github.com/spf13/cobra" + "github.com/wizedkyle/sumocli/api" + "github.com/wizedkyle/sumocli/pkg/cmd/factory" "github.com/wizedkyle/sumocli/pkg/logging" + "io/ioutil" + "os" ) func NewCmdUserDisableMFA() *cobra.Command { - var ( - id string - email string - password string - ) cmd := &cobra.Command{ Use: "disable mfa", - Short: "Disables MFA for a Sumo Logic user.", + Short: "Disables MFA for a Sumo Logic user (this command only works interactively).", Run: func(cmd *cobra.Command, args []string) { logger := logging.GetLoggerForCommand(cmd) logger.Debug().Msg("User disable mfa request started.") - userDisableMFA(id, email, password, logger) + userDisableMFA(logger) logger.Debug().Msg("User disable mfa request finished.") }, } @@ -27,6 +30,69 @@ func NewCmdUserDisableMFA() *cobra.Command { return cmd } -func userDisableMFA(id string, email string, password string, logger zerolog.Logger) { +func userDisableMFA(logger zerolog.Logger) { + validate := func(input string) error { + if input == "" { + return errors.New("Value is empty") + } + return nil + } + + promptId := promptui.Prompt{ + Label: "Please enter the Sumo Logic id for the user", + Validate: validate, + } + + promptEmail := promptui.Prompt{ + Label: "Please enter the Sumo Logic users email address", + Validate: validate, + } + + promptPassword := promptui.Prompt{ + Label: "Please enter the Sumo Logic users password", + Mask: '*', + Validate: validate, + } + + promptConfirm := promptui.Prompt{ + Label: "Confirm that you want to disable MFA? Removing MFA can be a security risk!", + IsConfirm: true, + } + + idResult, err := promptId.Run() + emailResult, err := promptEmail.Run() + passwordResult, err := promptPassword.Run() + _, err = promptConfirm.Run() + + if err != nil { + logging.LogError(err, logger) + os.Exit(0) + } + requestBodySchema := &api.DisableUserMfa{ + Email: emailResult, + Password: passwordResult, + } + requestBody, _ := json.Marshal(requestBodySchema) + requestUrl := "v1/users/" + idResult + "/mfa/disable" + client, request := factory.NewHttpRequestWithBody("PUT", requestUrl, requestBody) + response, err := client.Do(request) + logging.LogError(err, logger) + + defer response.Body.Close() + responseBody, err := ioutil.ReadAll(response.Body) + logging.LogError(err, logger) + + if response.StatusCode != 204 { + var responseError api.ResponseError + jsonErr := json.Unmarshal(responseBody, &responseError) + logging.LogError(jsonErr, logger) + if responseError.Errors[0].Message != "" { + fmt.Println(responseError.Errors[0].Message) + } else if responseError.Errors[0].Code == "auth1:mfa_not_allowed" { + fmt.Println("MFA is not enabled on user " + emailResult) + } + } else { + fmt.Println("MFA removed from user " + emailResult) + } } diff --git a/pkg/cmd/users/get/get.go b/pkg/cmd/users/get/get.go index cb0d605e..8970d06b 100644 --- a/pkg/cmd/users/get/get.go +++ b/pkg/cmd/users/get/get.go @@ -10,7 +10,6 @@ import ( "github.com/wizedkyle/sumocli/pkg/cmd/factory" "github.com/wizedkyle/sumocli/pkg/logging" "io/ioutil" - "os" "strings" ) @@ -44,18 +43,13 @@ lastLoginTimestamp cmd.Flags().StringVar(&id, "id", "", "Specify the id of the user to get") cmd.Flags().StringVar(&output, "output", "", "Specify the field to export the value from") - + cmd.MarkFlagRequired("id") return cmd } func getUser(id string, output string, logger zerolog.Logger) { var userInfo api.UserResponse - if id == "" { - fmt.Println("--id field needs to be specified.") - os.Exit(0) - } - requestUrl := "v1/users/" + id client, request := factory.NewHttpRequest("GET", requestUrl) response, err := client.Do(request) diff --git a/pkg/cmd/users/list/list.go b/pkg/cmd/users/list/list.go index 6db01e28..0678f1ef 100644 --- a/pkg/cmd/users/list/list.go +++ b/pkg/cmd/users/list/list.go @@ -90,7 +90,8 @@ func listUsers(email string, numberOfResults string, sortBy string, output strin } else { if factory.ValidateUserOutput(output) == true { value := gjson.Get(string(userInfoJson), "#."+output) - formattedValue := strings.Trim(value.String(), `"[]"`) + formattedValue := strings.Trim(value.String(), `[]`) + formattedValue = strings.ReplaceAll(formattedValue, "\"", "") fmt.Println(formattedValue) } else { fmt.Println(string(userInfoJson)) diff --git a/pkg/cmd/users/reset/reset.go b/pkg/cmd/users/reset/reset.go new file mode 100644 index 00000000..d333bbfc --- /dev/null +++ b/pkg/cmd/users/reset/reset.go @@ -0,0 +1,55 @@ +package reset + +import ( + "encoding/json" + "fmt" + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/wizedkyle/sumocli/api" + "github.com/wizedkyle/sumocli/pkg/cmd/factory" + "github.com/wizedkyle/sumocli/pkg/logging" + "io/ioutil" +) + +func NewCmdUserResetPassword() *cobra.Command { + var id string + + cmd := &cobra.Command{ + Use: "reset password", + Short: "Initiates a password reset for a Sumo Logic user.", + Run: func(cmd *cobra.Command, args []string) { + logger := logging.GetLoggerForCommand(cmd) + logger.Debug().Msg("User reset password request started.") + userResetPassword(id, logger) + logger.Debug().Msg("User reset password request finished.") + }, + } + + cmd.Flags().StringVar(&id, "id", "", "Specify the id of the user which requires a password reset.") + cmd.MarkFlagRequired("id") + + return cmd +} + +func userResetPassword(id string, logger zerolog.Logger) { + requestUrl := "v1/users/" + id + "/password/reset" + client, request := factory.NewHttpRequest("POST", requestUrl) + response, err := client.Do(request) + logging.LogError(err, logger) + + defer response.Body.Close() + responseBody, err := ioutil.ReadAll(response.Body) + logging.LogError(err, logger) + + if response.StatusCode != 204 { + var responseError api.ResponseError + jsonErr := json.Unmarshal(responseBody, &responseError) + fmt.Println(responseBody) + logging.LogError(jsonErr, logger) + if responseError.Errors[0].Message != "" { + fmt.Println(responseError.Errors[0].Message) + } + } else { + fmt.Println("Password reset request completed.") + } +} diff --git a/pkg/cmd/users/unlock/unlock.go b/pkg/cmd/users/unlock/unlock.go index d6226130..3786c5c4 100644 --- a/pkg/cmd/users/unlock/unlock.go +++ b/pkg/cmd/users/unlock/unlock.go @@ -9,7 +9,6 @@ import ( "github.com/wizedkyle/sumocli/pkg/cmd/factory" "github.com/wizedkyle/sumocli/pkg/logging" "io/ioutil" - "os" ) func NewCmdUnlockUser() *cobra.Command { @@ -27,16 +26,11 @@ func NewCmdUnlockUser() *cobra.Command { } cmd.Flags().StringVar(&id, "id", "", "Specify the id of the user account to unlock") - + cmd.MarkFlagRequired("id") return cmd } func unlockUser(id string, logger zerolog.Logger) { - if id == "" { - fmt.Println("--id field needs to be specified.") - os.Exit(0) - } - requestUrl := "v1/users/" + id + "/unlock" client, request := factory.NewHttpRequest("POST", requestUrl) response, err := client.Do(request) diff --git a/pkg/cmd/users/update/update.go b/pkg/cmd/users/update/update.go new file mode 100644 index 00000000..6b5fd143 --- /dev/null +++ b/pkg/cmd/users/update/update.go @@ -0,0 +1,159 @@ +package update + +import ( + "encoding/json" + "fmt" + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/tidwall/gjson" + "github.com/wizedkyle/sumocli/api" + "github.com/wizedkyle/sumocli/pkg/cmd/factory" + "github.com/wizedkyle/sumocli/pkg/logging" + "io/ioutil" + "os" + "reflect" + "strings" +) + +func NewCmdUserUpdate() *cobra.Command { + var ( + id string + firstName string + lastName string + isActive bool + roleIds []string + merge bool + output string + ) + + cmd := &cobra.Command{ + Use: "update", + Short: "Updates a Sumo Logic user.", + Run: func(cmd *cobra.Command, args []string) { + logger := logging.GetLoggerForCommand(cmd) + logger.Debug().Msg("Update user request started.") + updateUser(id, firstName, lastName, isActive, roleIds, merge, output, logger) + logger.Debug().Msg("Update user request finished.") + }, + } + + cmd.Flags().StringVar(&id, "id", "", "Specify the id of the user to update.") + cmd.Flags().StringVar(&firstName, "firstname", "", "First name of the user.") + cmd.Flags().StringVar(&lastName, "lastname", "", "Last name for the user.") + cmd.Flags().BoolVar(&isActive, "isactive", true, "True if the account is active, false if it is deactivated") + cmd.Flags().StringSliceVar(&roleIds, "roleids", []string{}, "Comma deliminated list of Role Ids.") + cmd.Flags().BoolVar(&merge, "merge", true, "Is set to true by default, if set to false it will overwrite the role.") + cmd.Flags().StringVar(&output, "output", "", "Specify the field to export the value from.") + cmd.MarkFlagRequired("id") + cmd.MarkFlagRequired("firstname") + cmd.MarkFlagRequired("lastname") + cmd.MarkFlagRequired("isactive") + cmd.MarkFlagRequired("roleids") + return cmd +} + +func updateUser(id string, firstName string, lastName string, isActive bool, roleIds []string, merge bool, output string, logger zerolog.Logger) { + var userInfo api.UserResponse + + if merge == true { + requestUrl := "v1/users/" + id + client, request := factory.NewHttpRequest("GET", requestUrl) + response, err := client.Do(request) + logging.LogError(err, logger) + + defer response.Body.Close() + responseBody, err := ioutil.ReadAll(response.Body) + logging.LogError(err, logger) + + jsonErr := json.Unmarshal(responseBody, &userInfo) + logging.LogError(jsonErr, logger) + + if response.StatusCode != 200 { + factory.HttpError(response.StatusCode, responseBody, logger) + os.Exit(0) + } + + // Building body payload to update the user based on the differences + // between the current role settings and the desired settings + requestBodySchema := &api.UpdateUserRequest{} + requestBodySchema.IsActive = userInfo.IsActive + if strings.EqualFold(userInfo.Firstname, firstName) { + requestBodySchema.Firstname = userInfo.Firstname + } else { + requestBodySchema.Firstname = firstName + } + + if strings.EqualFold(userInfo.Lastname, lastName) { + requestBodySchema.Lastname = userInfo.Lastname + } else { + requestBodySchema.Lastname = lastName + } + + if reflect.DeepEqual(userInfo.RoleIds, roleIds) { + requestBodySchema.Roleids = userInfo.RoleIds + } else { + requestBodySchema.Roleids = append(requestBodySchema.Roleids, userInfo.RoleIds...) + requestBodySchema.Roleids = append(requestBodySchema.Roleids, roleIds...) + } + + requestBody, _ := json.Marshal(requestBodySchema) + client, request = factory.NewHttpRequestWithBody("PUT", requestUrl, requestBody) + response, err = client.Do(request) + logging.LogError(err, logger) + + defer response.Body.Close() + responseBody, err = ioutil.ReadAll(response.Body) + + jsonErr = json.Unmarshal(responseBody, &userInfo) + logging.LogError(jsonErr, logger) + + userInfoJson, err := json.MarshalIndent(userInfo, "", " ") + logging.LogError(jsonErr, logger) + + if response.StatusCode != 200 { + factory.HttpError(response.StatusCode, responseBody, logger) + } else { + if factory.ValidateUserOutput(output) == true { + value := gjson.Get(string(userInfoJson), output) + formattedValue := strings.Trim(value.String(), `"[]"`) + fmt.Println(formattedValue) + } else { + fmt.Println(string(userInfoJson)) + } + } + } else { + requestBodySchema := &api.UpdateUserRequest{ + Firstname: firstName, + Lastname: lastName, + IsActive: isActive, + Roleids: roleIds, + } + requestBody, _ := json.Marshal(requestBodySchema) + + requestUrl := "v1/users/" + id + client, request := factory.NewHttpRequestWithBody("PUT", requestUrl, requestBody) + response, err := client.Do(request) + logging.LogError(err, logger) + + defer response.Body.Close() + responseBody, err := ioutil.ReadAll(response.Body) + + jsonErr := json.Unmarshal(responseBody, &userInfo) + logging.LogError(jsonErr, logger) + + userInfoJson, err := json.MarshalIndent(userInfo, "", " ") + logging.LogError(err, logger) + + if response.StatusCode != 200 { + factory.HttpError(response.StatusCode, responseBody, logger) + } else { + if factory.ValidateUserOutput(output) == true { + value := gjson.Get(string(userInfoJson), output) + formattedValue := strings.Trim(value.String(), `"[]"`) + fmt.Println(formattedValue) + } else { + fmt.Println(string(userInfoJson)) + } + } + } +} diff --git a/pkg/cmd/users/users.go b/pkg/cmd/users/users.go index 346c2225..b811fec0 100644 --- a/pkg/cmd/users/users.go +++ b/pkg/cmd/users/users.go @@ -5,9 +5,12 @@ import ( cmdUserChange "github.com/wizedkyle/sumocli/pkg/cmd/users/change" cmdUserCreate "github.com/wizedkyle/sumocli/pkg/cmd/users/create" cmdUserDelete "github.com/wizedkyle/sumocli/pkg/cmd/users/delete" + cmdUserDisable "github.com/wizedkyle/sumocli/pkg/cmd/users/disable" cmdUserGet "github.com/wizedkyle/sumocli/pkg/cmd/users/get" cmdUserList "github.com/wizedkyle/sumocli/pkg/cmd/users/list" + cmduserReset "github.com/wizedkyle/sumocli/pkg/cmd/users/reset" cmdUserUnlock "github.com/wizedkyle/sumocli/pkg/cmd/users/unlock" + cmdUserUpdate "github.com/wizedkyle/sumocli/pkg/cmd/users/update" ) func NewCmdUser() *cobra.Command { @@ -20,8 +23,11 @@ func NewCmdUser() *cobra.Command { cmd.AddCommand(cmdUserChange.NewCmdUserChangeEmail()) cmd.AddCommand(cmdUserCreate.NewCmdUserCreate()) cmd.AddCommand(cmdUserDelete.NewCmdUserDelete()) + cmd.AddCommand(cmdUserDisable.NewCmdUserDisableMFA()) cmd.AddCommand(cmdUserGet.NewCmdGetUser()) cmd.AddCommand(cmdUserList.NewCmdUserList()) + cmd.AddCommand(cmduserReset.NewCmdUserResetPassword()) cmd.AddCommand(cmdUserUnlock.NewCmdUnlockUser()) + cmd.AddCommand(cmdUserUpdate.NewCmdUserUpdate()) return cmd } diff --git a/pkg/cmd/version/version.go b/pkg/cmd/version/version.go index 95137291..c5b89798 100644 --- a/pkg/cmd/version/version.go +++ b/pkg/cmd/version/version.go @@ -9,10 +9,9 @@ import ( func NewCmdVersion() *cobra.Command { cmd := &cobra.Command{ - Use: "version", - Short: "Displays sumocli version", - Long: "Displays the version and build number of sumocli.", - Hidden: true, + Use: "version", + Short: "Displays sumocli version", + Long: "Displays the version and build number of sumocli.", Run: func(cmd *cobra.Command, args []string) { logger := logging.GetLoggerForCommand(cmd) logger.Debug().Msg("Version command started.")