mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
The server module named github.com/hanzoai/team/server/v8 — a path that resolves to the retired workspace's repository, not this one — while server/public still published github.com/mattermost/mattermost/server/public. Both are github.com/hanzoteam/server/... now, which is where this code actually lives. That second half is what made the binary unbootable. Upstream's public path stayed claimed by upstream, mattermost-plugin-ai imported it, and the build linked public/model twice; public/plugin's gob.Register then ran twice under one name and panicked at init, before main. Reverting the rename traded the brand for a working binary. Rebranding the plugin alongside it needs no such trade: the agents fork is github.com/hanzoteam/agents/v2 against our public module, so no module named mattermost/mattermost is in the graph at all. Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and it is ours.
1475 lines
48 KiB
Go
1475 lines
48 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package commands
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoteam/server/server/public/model"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/hanzoteam/server/server/v8/cmd/mmctl/client"
|
|
"github.com/hanzoteam/server/server/v8/cmd/mmctl/printer"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var UserCmd = &cobra.Command{
|
|
Use: "user",
|
|
Short: "Management of users",
|
|
}
|
|
|
|
var UserActivateCmd = &cobra.Command{
|
|
Use: "activate [emails, usernames, userIds]",
|
|
Short: "Activate users",
|
|
Long: "Activate users that have been deactivated.",
|
|
Example: ` user activate [email protected]
|
|
user activate username`,
|
|
ValidArgsFunction: validateArgsWithClient(userActivateCompletionF),
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(userActivateCmdF),
|
|
}
|
|
|
|
var UserDeactivateCmd = &cobra.Command{
|
|
Use: "deactivate [emails, usernames, userIds]",
|
|
Short: "Deactivate users",
|
|
Long: "Deactivate users. Deactivated users are immediately logged out of all sessions and are unable to log back in.",
|
|
Example: ` user deactivate [email protected]
|
|
user deactivate username`,
|
|
ValidArgsFunction: validateArgsWithClient(userDeactivateCompletionF),
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(userDeactivateCmdF),
|
|
}
|
|
|
|
var UserCreateCmd = &cobra.Command{
|
|
Use: "create",
|
|
Short: "Create a user",
|
|
Long: "Create a user",
|
|
Example: ` # You can create a user
|
|
$ mmctl user create --email [email protected] --username userexample --password Password1
|
|
|
|
# You can define optional fields like first name, last name and nick name too
|
|
$ mmctl user create --email [email protected] --username userexample --password Password1 --firstname User --lastname Example --nickname userex
|
|
|
|
# Also you can create the user as system administrator
|
|
$ mmctl user create --email [email protected] --username userexample --password Password1 --system-admin
|
|
|
|
# Finally you can verify user on creation if you have enough permissions
|
|
$ mmctl user create --email [email protected] --username userexample --password Password1 --system-admin --email-verified`,
|
|
RunE: withClient(userCreateCmdF),
|
|
}
|
|
|
|
var UserInviteCmd = &cobra.Command{
|
|
Use: "invite [email] [teams]",
|
|
Short: "Send user an email invite to a team.",
|
|
Long: `Send user an email invite to a team.
|
|
You can invite a user to multiple teams by listing them.
|
|
You can specify teams by name or ID.`,
|
|
Example: ` user invite [email protected] myteam
|
|
user invite [email protected] myteam1 myteam2`,
|
|
RunE: withClient(userInviteCmdF),
|
|
}
|
|
|
|
var SendPasswordResetEmailCmd = &cobra.Command{
|
|
Use: "reset-password [users]",
|
|
Aliases: []string{"reset_password"},
|
|
Short: "Send users an email to reset their password",
|
|
Long: "Send users an email to reset their password",
|
|
Example: " user reset-password [email protected]",
|
|
RunE: withClient(sendPasswordResetEmailCmdF),
|
|
}
|
|
|
|
var ChangePasswordUserCmd = &cobra.Command{
|
|
Use: "change-password <user>",
|
|
Short: "Changes a user's password",
|
|
Long: "Changes the password of a user by a new one provided. If the user is changing their own password, the flag --current must indicate the current password. The flag --hashed can be used to indicate that the new password has been introduced already hashed",
|
|
Example: ` # if you have system permissions, you can change other user's passwords
|
|
$ mmctl user change-password john_doe --password new-password
|
|
|
|
# if you are changing your own password, you need to provide the current one
|
|
$ mmctl user change-password my-username --current current-password --password new-password
|
|
|
|
# you can ommit these flags to introduce them interactively
|
|
$ mmctl user change-password my-username
|
|
Are you changing your own password? (YES/NO): YES
|
|
Current password:
|
|
New password:
|
|
|
|
# if you have system permissions, you can update the password with the already hashed new
|
|
# password. The hashing method should be the same that the server uses internally
|
|
$ mmctl user change-password john_doe --password HASHED_PASSWORD --hashed`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: withClient(changePasswordUserCmdF),
|
|
}
|
|
|
|
var ResetUserMfaCmd = &cobra.Command{
|
|
Use: "resetmfa [users]",
|
|
Short: "Turn off MFA",
|
|
Long: `Turn off multi-factor authentication for a user.
|
|
If MFA enforcement is enabled, the user will be forced to re-enable MFA as soon as they log in.`,
|
|
Example: " user resetmfa [email protected]",
|
|
RunE: withClient(resetUserMfaCmdF),
|
|
}
|
|
|
|
var UserEditCmd = &cobra.Command{
|
|
Use: "edit",
|
|
Short: "Edit user properties",
|
|
Long: "Edit user properties like username, email, or authdata.",
|
|
}
|
|
|
|
var UserEditUsernameCmd = &cobra.Command{
|
|
Use: "username [user] [new username]",
|
|
Short: "Edit user's username",
|
|
Long: "Edit a user's username.",
|
|
Example: "user edit username [email protected] newusername",
|
|
Args: cobra.ExactArgs(2),
|
|
ValidArgsFunction: validateArgsWithClient(userEditCompletionF),
|
|
RunE: withClient(userEditUsernameCmdF),
|
|
}
|
|
|
|
var UserEditEmailCmd = &cobra.Command{
|
|
Use: "email [user] [new email]",
|
|
Short: "Edit user's email",
|
|
Long: "Edit a user's email address.",
|
|
Example: "user edit email [email protected] [email protected]",
|
|
Args: cobra.ExactArgs(2),
|
|
ValidArgsFunction: validateArgsWithClient(userEditCompletionF),
|
|
RunE: withClient(userEditEmailCmdF),
|
|
}
|
|
|
|
var UserEditAuthdataCmd = &cobra.Command{
|
|
Use: "authdata [user] [new authdata]",
|
|
Short: "Edit user's authdata",
|
|
Long: "Edit a user's authentication data. Use empty string to clear authdata.",
|
|
Example: `user edit authdata [email protected] newid123`,
|
|
Args: cobra.ExactArgs(2),
|
|
ValidArgsFunction: validateArgsWithClient(userEditCompletionF),
|
|
RunE: withClient(userEditAuthdataCmdF),
|
|
}
|
|
|
|
var DeleteUsersCmd = &cobra.Command{
|
|
Use: "delete [users]",
|
|
Short: "Delete users",
|
|
Long: `Permanently delete some users.
|
|
Permanently deletes one or multiple users along with all related information including posts from the database.`,
|
|
Example: " user delete [email protected]",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(deleteUsersCmdF),
|
|
}
|
|
|
|
var SearchUserCmd = &cobra.Command{
|
|
Use: "search [users]",
|
|
Short: "Search for users",
|
|
Long: "Search for users based on username, email, or user ID.",
|
|
Example: " user search [email protected] [email protected]",
|
|
RunE: withClient(searchUserCmdF),
|
|
}
|
|
|
|
var ListUsersCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List users",
|
|
Long: "List all users",
|
|
Example: " user list",
|
|
RunE: withClient(listUsersCmdF),
|
|
Args: cobra.NoArgs,
|
|
}
|
|
|
|
var VerifyUserEmailWithoutTokenCmd = &cobra.Command{
|
|
Use: "verify [users]",
|
|
Short: "Mark user's email as verified",
|
|
Long: "Mark user's email as verified without requiring user to complete email verification path.",
|
|
Example: " user verify user1",
|
|
RunE: withClient(verifyUserEmailWithoutTokenCmdF),
|
|
Args: cobra.MinimumNArgs(1),
|
|
}
|
|
|
|
var PromoteGuestToUserCmd = &cobra.Command{
|
|
Use: "promote [guests]",
|
|
Short: "Promote guests to users",
|
|
Long: "Convert a guest into a regular user.",
|
|
Example: " user promote guest1 guest2",
|
|
RunE: withClient(promoteGuestToUserCmdF),
|
|
Args: cobra.MinimumNArgs(1),
|
|
}
|
|
|
|
var DemoteUserToGuestCmd = &cobra.Command{
|
|
Use: "demote [users]",
|
|
Short: "Demote users to guests",
|
|
Long: "Convert a regular user into a guest.",
|
|
Example: " user demote user1 user2",
|
|
RunE: withClient(demoteUserToGuestCmdF),
|
|
Args: cobra.MinimumNArgs(1),
|
|
}
|
|
|
|
var UserStatusCmd = &cobra.Command{
|
|
Use: "status",
|
|
Short: "Get a user's status",
|
|
Long: "Get a user's presence status: online, away, dnd or offline.",
|
|
Example: ` # You can get the status of the currently authenticated user
|
|
$ mmctl user status
|
|
|
|
# You can get the status of a specific user
|
|
$ mmctl user status --user [email protected]
|
|
|
|
# In local mode there is no authenticated user, so the --user flag is required
|
|
$ mmctl --local user status --user [email protected]`,
|
|
Args: cobra.NoArgs,
|
|
RunE: withClient(userStatusGetCmdF),
|
|
}
|
|
|
|
var UserStatusSetCmd = &cobra.Command{
|
|
Use: "set [status]",
|
|
Short: "Set a user's status",
|
|
Long: "Set a user's presence status. Allowed values are online, away, dnd and offline.",
|
|
Example: ` # You can set the status of the currently authenticated user
|
|
$ mmctl user status set away
|
|
|
|
# You can set the status of a specific user
|
|
$ mmctl user status set --user [email protected] dnd
|
|
|
|
# You can set a "dnd" status that expires at a given time (ISO 8601)
|
|
$ mmctl user status set --user [email protected] --dnd-end-time 2100-01-02T15:04:05-07:00 dnd`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: withClient(userStatusSetCmdF),
|
|
}
|
|
|
|
var UserConvertCmd = &cobra.Command{
|
|
Use: "convert (--bot [emails] [usernames] [userIds] | --user <username> --password PASSWORD [--email EMAIL])",
|
|
Short: "Convert users to bots, or a bot to a user",
|
|
Long: "Convert user accounts to bots or convert bots to user accounts.",
|
|
Example: ` # you can convert a user to a bot providing its email, id or username
|
|
$ mmctl user convert [email protected] --bot
|
|
|
|
# or multiple users in one go
|
|
$ mmctl user convert [email protected] anotherUser --bot
|
|
|
|
# you can convert a bot to a user specifying the email and password that the user will have after conversion
|
|
$ mmctl user convert botusername --email [email protected] --password password --user`,
|
|
RunE: withClient(userConvertCmdF),
|
|
Args: cobra.MinimumNArgs(1),
|
|
}
|
|
|
|
const migrateAuthCmdDoc = `Migrates accounts from one authentication provider to either LDAP or SAML. For example, you can upgrade your authentication provider from Email to LDAP.
|
|
|
|
Arguments:
|
|
from_auth:
|
|
The authentication service to migrate users accounts from.
|
|
Supported options: email, gitlab, google, ldap, office365, saml.
|
|
|
|
to_auth:
|
|
The authentication service to migrate users to.
|
|
Supported options: ldap, saml.
|
|
|
|
migration-options (ldap):
|
|
match_field:
|
|
The field that is guaranteed to be the same in both authentication services. For example, if the users emails are consistent set to email.
|
|
Supported options: email, username.
|
|
|
|
migration-options (saml):
|
|
users_file:
|
|
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.
|
|
|
|
Example json content:
|
|
{
|
|
"[email protected]": "usr.one",
|
|
"[email protected]": "usr.two"
|
|
}
|
|
`
|
|
|
|
var MigrateAuthCmd = &cobra.Command{
|
|
Use: "migrate-auth [from_auth] [to_auth] [migration-options]",
|
|
Aliases: []string{"migrate_auth"},
|
|
Short: "Mass migrate user accounts authentication type",
|
|
Long: migrateAuthCmdDoc,
|
|
Example: "user migrate-auth email saml users.json",
|
|
Args: func(command *cobra.Command, args []string) error {
|
|
if len(args) < 2 {
|
|
return errors.New("auth migration requires at least 2 arguments")
|
|
}
|
|
|
|
toAuth := args[1]
|
|
|
|
if toAuth != "ldap" && toAuth != "saml" { // nolint: goconst
|
|
return errors.New("invalid to_auth parameter, must be saml or ldap")
|
|
}
|
|
|
|
if toAuth == "ldap" && len(args) != 3 {
|
|
return errors.New("ldap migration requires 3 arguments")
|
|
}
|
|
|
|
autoFlag, _ := command.Flags().GetBool("auto")
|
|
|
|
if toAuth == "saml" && autoFlag {
|
|
if len(args) != 2 {
|
|
return errors.New("saml migration requires two arguments when using the --auto flag")
|
|
}
|
|
}
|
|
|
|
if toAuth == "saml" && !autoFlag {
|
|
if len(args) != 3 {
|
|
return errors.New("saml migration requires three arguments when not using the --auto flag")
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
RunE: withClient(migrateAuthCmdF),
|
|
}
|
|
|
|
var PreferenceCmd = &cobra.Command{
|
|
Use: "preference",
|
|
Aliases: []string{"pref"},
|
|
Short: "Manage user preferences",
|
|
}
|
|
|
|
var PreferenceListCmd = &cobra.Command{
|
|
Use: "list [--category category] [users]",
|
|
Short: "List user preferences",
|
|
Example: "preference list [email protected]",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(preferencesListCmdF),
|
|
}
|
|
|
|
var PreferenceGetCmd = &cobra.Command{
|
|
Use: "get --category [category] --name [name] [users]",
|
|
Short: "Get a specific user preference",
|
|
Example: "preference get --category display_settings --name use_military_time [email protected]",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(preferencesGetCmdF),
|
|
}
|
|
|
|
var PreferenceUpdateCmd = &cobra.Command{
|
|
Use: "set --category [category] --name [name] --value [value] [users]",
|
|
Aliases: []string{"update"},
|
|
Short: "Set a specific user preference",
|
|
Example: "preference set --category display_settings --name use_military_time --value true [email protected]",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(preferencesUpdateCmdF),
|
|
}
|
|
|
|
var PreferenceDeleteCmd = &cobra.Command{
|
|
Use: "delete --category [category] --name [name] [users]",
|
|
Short: "Delete a specific user preference",
|
|
Example: "preference delete --category display_settings --name use_military_time [email protected]",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(preferencesDeleteCmdF),
|
|
}
|
|
|
|
func init() {
|
|
UserCreateCmd.Flags().String("username", "", "Required. Username for the new user account")
|
|
_ = UserCreateCmd.MarkFlagRequired("username")
|
|
UserCreateCmd.Flags().String("email", "", "Required. The email address for the new user account")
|
|
_ = UserCreateCmd.MarkFlagRequired("email")
|
|
UserCreateCmd.Flags().String("password", "", "Required. The password for the new user account")
|
|
_ = UserCreateCmd.MarkFlagRequired("password")
|
|
UserCreateCmd.Flags().String("nickname", "", "Optional. The nickname for the new user account")
|
|
UserCreateCmd.Flags().String("firstname", "", "Optional. The first name for the new user account")
|
|
UserCreateCmd.Flags().String("lastname", "", "Optional. The last name for the new user account")
|
|
UserCreateCmd.Flags().String("locale", "", "Optional. The locale (ex: en, fr) for the new user account")
|
|
UserCreateCmd.Flags().Bool("system-admin", false, "Optional. If supplied, the new user will be a system administrator. Defaults to false")
|
|
UserCreateCmd.Flags().Bool("guest", false, "Optional. If supplied, the new user will be a guest. Defaults to false")
|
|
UserCreateCmd.Flags().Bool("email-verified", false, "Optional. If supplied, the new user will have the email verified. Defaults to false")
|
|
UserCreateCmd.Flags().Bool("disable-welcome-email", false, "Optional. If supplied, the new user will not receive a welcome email. Defaults to false")
|
|
|
|
DeleteUsersCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed")
|
|
|
|
ListUsersCmd.Flags().Int("page", 0, "Page number to fetch for the list of users")
|
|
ListUsersCmd.Flags().Int("per-page", DefaultPageSize, "Number of users to be fetched")
|
|
ListUsersCmd.Flags().Bool("all", false, "Fetch all users. --page flag will be ignored if provided")
|
|
ListUsersCmd.Flags().String("team", "", "If supplied, only users belonging to this team will be listed")
|
|
ListUsersCmd.Flags().Bool("active", false, "If supplied, only users which are active will be fetched")
|
|
ListUsersCmd.Flags().Bool("inactive", false, "If supplied, only users which are inactive will be fetched")
|
|
ListUsersCmd.Flags().String("role", "", "If supplied, only users with the given role will be fetched")
|
|
|
|
UserConvertCmd.Flags().Bool("bot", false, "If supplied, convert users to bots")
|
|
UserConvertCmd.Flags().Bool("user", false, "If supplied, convert a bot to a user")
|
|
UserConvertCmd.Flags().String("password", "", "The password for converted new user account. Required when \"user\" flag is set")
|
|
UserConvertCmd.Flags().String("username", "", "Username for the converted user account. Required when the \"bot\" flag is set")
|
|
UserConvertCmd.Flags().String("email", "", "The email address for the converted user account. Required when the \"bot\" flag is set")
|
|
UserConvertCmd.Flags().String("nickname", "", "The nickname for the converted user account. Required when the \"bot\" flag is set")
|
|
UserConvertCmd.Flags().String("firstname", "", "The first name for the converted user account. Required when the \"bot\" flag is set")
|
|
UserConvertCmd.Flags().String("lastname", "", "The last name for the converted user account. Required when the \"bot\" flag is set")
|
|
UserConvertCmd.Flags().String("locale", "", "The locale (ex: en, fr) for converted new user account. Required when the \"bot\" flag is set")
|
|
UserConvertCmd.Flags().Bool("system-admin", false, "If supplied, the converted user will be a system administrator. Defaults to false. Required when the \"bot\" flag is set")
|
|
|
|
UserStatusCmd.Flags().String("user", "", "Optional. The user (specified by email, username or ID) whose status to get. Defaults to the currently authenticated user. Required in local mode.")
|
|
UserStatusSetCmd.Flags().String("user", "", "Optional. The user (specified by email, username or ID) whose status to set. Defaults to the currently authenticated user. Required in local mode.")
|
|
UserStatusSetCmd.Flags().String("dnd-end-time", "", "Optional. The time at which a \"dnd\" status expires, formatted as ISO 8601 (e.g. 2006-01-02T15:04:05-07:00). Only valid with the \"dnd\" status.")
|
|
|
|
ChangePasswordUserCmd.Flags().StringP("current", "c", "", "The current password of the user. Use only if changing your own password")
|
|
ChangePasswordUserCmd.Flags().StringP("password", "p", "", "The new password for the user")
|
|
ChangePasswordUserCmd.Flags().Bool("hashed", false, "The supplied password is already hashed")
|
|
|
|
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)")
|
|
MigrateAuthCmd.Flags().Bool("auto", false, "Automatically migrate all users. Assumes the usernames and emails are identical between Mattermost and SAML services. (saml only)")
|
|
MigrateAuthCmd.Flags().Bool("confirm", false, "Confirm you really want to proceed with auto migration. (saml only)")
|
|
MigrateAuthCmd.SetHelpTemplate(`Usage:
|
|
mmctl user migrate-auth [from_auth] [to_auth] [migration-options] [flags]
|
|
|
|
Examples:
|
|
mmctl {{.Example}}
|
|
|
|
` + migrateAuthCmdDoc + `
|
|
|
|
Flags:
|
|
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}
|
|
|
|
Global Flags:
|
|
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
|
|
`)
|
|
|
|
PreferenceListCmd.Flags().StringP("category", "c", "", "The optional category by which to filter")
|
|
PreferenceGetCmd.Flags().StringP("category", "c", "", "The category of the preference")
|
|
PreferenceGetCmd.Flags().StringP("name", "n", "", "The name of the preference")
|
|
_ = PreferenceGetCmd.MarkFlagRequired("category")
|
|
_ = PreferenceGetCmd.MarkFlagRequired("name")
|
|
PreferenceUpdateCmd.Flags().StringP("category", "c", "", "The category of the preference")
|
|
PreferenceUpdateCmd.Flags().StringP("name", "n", "", "The name of the preference")
|
|
PreferenceUpdateCmd.Flags().StringP("value", "v", "", "The value of the preference")
|
|
_ = PreferenceUpdateCmd.MarkFlagRequired("category")
|
|
_ = PreferenceUpdateCmd.MarkFlagRequired("name")
|
|
_ = PreferenceUpdateCmd.MarkFlagRequired("value")
|
|
PreferenceDeleteCmd.Flags().StringP("category", "c", "", "The category of the preference")
|
|
PreferenceDeleteCmd.Flags().StringP("name", "n", "", "The name of the preference")
|
|
_ = PreferenceDeleteCmd.MarkFlagRequired("category")
|
|
_ = PreferenceDeleteCmd.MarkFlagRequired("name")
|
|
|
|
UserCmd.AddCommand(
|
|
UserActivateCmd,
|
|
UserDeactivateCmd,
|
|
UserCreateCmd,
|
|
UserInviteCmd,
|
|
SendPasswordResetEmailCmd,
|
|
ChangePasswordUserCmd,
|
|
ResetUserMfaCmd,
|
|
UserEditCmd,
|
|
DeleteUsersCmd,
|
|
SearchUserCmd,
|
|
ListUsersCmd,
|
|
VerifyUserEmailWithoutTokenCmd,
|
|
UserConvertCmd,
|
|
MigrateAuthCmd,
|
|
PromoteGuestToUserCmd,
|
|
DemoteUserToGuestCmd,
|
|
UserStatusCmd,
|
|
PreferenceCmd,
|
|
)
|
|
UserStatusCmd.AddCommand(
|
|
UserStatusSetCmd,
|
|
)
|
|
PreferenceCmd.AddCommand(
|
|
PreferenceListCmd,
|
|
PreferenceGetCmd,
|
|
PreferenceUpdateCmd,
|
|
PreferenceDeleteCmd,
|
|
)
|
|
UserEditCmd.AddCommand(
|
|
UserEditUsernameCmd,
|
|
UserEditEmailCmd,
|
|
UserEditAuthdataCmd,
|
|
)
|
|
|
|
RootCmd.AddCommand(UserCmd)
|
|
}
|
|
|
|
func userActivateCmdF(c client.Client, command *cobra.Command, args []string) error {
|
|
return changeUsersActiveStatus(c, args, true)
|
|
}
|
|
|
|
func userActivateCompletionF(ctx context.Context, c client.Client, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
return fetchAndComplete(
|
|
func(ctx context.Context, c client.Client, page, perPage int) ([]*model.User, *model.Response, error) {
|
|
return c.GetUsersWithCustomQueryParameters(ctx, page, perPage, "inactive=true", "")
|
|
},
|
|
func(u *model.User) []string { return []string{u.Id, u.Username, u.Email} },
|
|
)(ctx, c, cmd, args, toComplete)
|
|
}
|
|
|
|
func changeUsersActiveStatus(c client.Client, userArgs []string, active bool) error {
|
|
var multiErr *multierror.Error
|
|
users, err := getUsersFromArgs(c, userArgs)
|
|
if err != nil {
|
|
printer.PrintError(err.Error())
|
|
multiErr = multierror.Append(multiErr, err)
|
|
}
|
|
for _, user := range users {
|
|
if err := changeUserActiveStatus(c, user, active); err != nil {
|
|
printer.PrintError(err.Error())
|
|
multiErr = multierror.Append(multiErr, err)
|
|
}
|
|
}
|
|
return multiErr.ErrorOrNil()
|
|
}
|
|
|
|
func changeUserActiveStatus(c client.Client, user *model.User, activate bool) error {
|
|
if !activate && user.IsSSOUser() {
|
|
printer.Print("You must also deactivate user " + user.Id + " in the SSO provider or they will be reactivated on next login or sync.")
|
|
}
|
|
if _, err := c.UpdateUserActive(context.TODO(), user.Id, activate); err != nil {
|
|
return fmt.Errorf("unable to change activation status of user %v: %w", user.Id, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func userDeactivateCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
return changeUsersActiveStatus(c, args, false)
|
|
}
|
|
|
|
func userDeactivateCompletionF(ctx context.Context, c client.Client, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
return fetchAndComplete(
|
|
func(ctx context.Context, c client.Client, page, perPage int) ([]*model.User, *model.Response, error) {
|
|
return c.GetUsersWithCustomQueryParameters(ctx, page, perPage, "active=true", "")
|
|
},
|
|
func(u *model.User) []string { return []string{u.Id, u.Username, u.Email} },
|
|
)(ctx, c, cmd, args, toComplete)
|
|
}
|
|
|
|
func userCreateCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
username, erru := cmd.Flags().GetString("username")
|
|
if erru != nil {
|
|
return errors.Wrap(erru, "Username is required")
|
|
}
|
|
email, erre := cmd.Flags().GetString("email")
|
|
if erre != nil {
|
|
return errors.Wrap(erre, "Email is required")
|
|
}
|
|
password, errp := cmd.Flags().GetString("password")
|
|
if errp != nil {
|
|
return errors.Wrap(errp, "Password is required")
|
|
}
|
|
nickname, _ := cmd.Flags().GetString("nickname")
|
|
firstname, _ := cmd.Flags().GetString("firstname")
|
|
lastname, _ := cmd.Flags().GetString("lastname")
|
|
locale, _ := cmd.Flags().GetString("locale")
|
|
systemAdmin, _ := cmd.Flags().GetBool("system-admin")
|
|
guest, _ := cmd.Flags().GetBool("guest")
|
|
emailVerified, _ := cmd.Flags().GetBool("email-verified")
|
|
disableWelcomeEmail, _ := cmd.Flags().GetBool("disable-welcome-email")
|
|
|
|
user := &model.User{
|
|
Username: username,
|
|
Email: email,
|
|
Password: password,
|
|
Nickname: nickname,
|
|
FirstName: firstname,
|
|
LastName: lastname,
|
|
Locale: locale,
|
|
EmailVerified: emailVerified,
|
|
DisableWelcomeEmail: disableWelcomeEmail,
|
|
}
|
|
|
|
ruser, _, err := c.CreateUser(context.TODO(), user)
|
|
|
|
if err != nil {
|
|
return errors.New("Unable to create user. Error: " + err.Error())
|
|
}
|
|
|
|
if systemAdmin {
|
|
if _, err := c.UpdateUserRoles(context.TODO(), ruser.Id, "system_user system_admin"); err != nil {
|
|
return errors.New("Unable to update user roles. Error: " + err.Error())
|
|
}
|
|
} else if guest {
|
|
if _, err := c.DemoteUserToGuest(context.TODO(), ruser.Id); err != nil {
|
|
return errors.Wrapf(err, "Unable to demote use to guest")
|
|
}
|
|
}
|
|
|
|
printer.PrintT("Created user {{.Username}}", ruser)
|
|
|
|
return nil
|
|
}
|
|
|
|
func userInviteCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
var errs *multierror.Error
|
|
if len(args) < 2 {
|
|
return errors.New("expected at least two arguments. See help text for details")
|
|
}
|
|
|
|
email := args[0]
|
|
if !model.IsValidEmail(email) {
|
|
errs = multierror.Append(errs, fmt.Errorf("invalid email %q", email))
|
|
}
|
|
|
|
teams := getTeamsFromTeamArgs(c, args[1:])
|
|
for i, team := range teams {
|
|
err := inviteUser(c, email, team, args[i+1])
|
|
if err != nil {
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
}
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|
|
|
|
func inviteUser(c client.Client, email string, team *model.Team, teamArg string) error {
|
|
invites := []string{email}
|
|
if team == nil {
|
|
return fmt.Errorf("can't find team '%v'", teamArg)
|
|
}
|
|
|
|
if _, err := c.InviteUsersToTeam(context.TODO(), team.Id, invites); err != nil {
|
|
return errors.New("Unable to invite user with email " + email + " to team " + team.Name + ". Error: " + err.Error())
|
|
}
|
|
|
|
printer.Print("Invites may or may not have been sent.")
|
|
|
|
return nil
|
|
}
|
|
|
|
func sendPasswordResetEmailCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
if len(args) < 1 {
|
|
return errors.New("expected at least one argument. See help text for details")
|
|
}
|
|
|
|
var result *multierror.Error
|
|
|
|
for _, email := range args {
|
|
if !model.IsValidEmail(email) {
|
|
result = multierror.Append(result, fmt.Errorf("invalid email '%s'", email))
|
|
printer.PrintError("Invalid email '" + email + "'")
|
|
continue
|
|
}
|
|
if _, err := c.SendPasswordResetEmail(context.TODO(), email); err != nil {
|
|
result = multierror.Append(result, fmt.Errorf("unable send reset password email to email %s: %w", email, err))
|
|
printer.PrintError("Unable send reset password email to email " + email + ". Error: " + err.Error())
|
|
}
|
|
}
|
|
|
|
return result.ErrorOrNil()
|
|
}
|
|
|
|
func changePasswordUserCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
password, _ := cmd.Flags().GetString("password")
|
|
current, _ := cmd.Flags().GetString("current")
|
|
hashed, _ := cmd.Flags().GetBool("hashed")
|
|
|
|
if password == "" {
|
|
if err := getConfirmation("Are you changing your own password?", false); err == nil {
|
|
fmt.Printf("Current password: ")
|
|
var err error
|
|
current, err = getPasswordFromStdin()
|
|
if err != nil {
|
|
return errors.Wrap(err, "couldn't read password")
|
|
}
|
|
}
|
|
|
|
fmt.Printf("New password: ")
|
|
var err error
|
|
password, err = getPasswordFromStdin()
|
|
if err != nil {
|
|
return errors.Wrap(err, "couldn't read password")
|
|
}
|
|
}
|
|
|
|
user, err := getUserFromArg(c, args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if hashed {
|
|
if _, err := c.UpdateUserHashedPassword(context.TODO(), user.Id, password); err != nil {
|
|
return errors.Wrap(err, "changing user hashed password failed")
|
|
}
|
|
} else {
|
|
if _, err := c.UpdateUserPassword(context.TODO(), user.Id, current, password); err != nil {
|
|
return errors.Wrap(err, "changing user password failed")
|
|
}
|
|
}
|
|
|
|
printer.PrintT("Password for user {{.Username}} successfully changed", user)
|
|
return nil
|
|
}
|
|
|
|
func resetUserMfaCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
if len(args) < 1 {
|
|
return errors.New("expected at least one argument. See help text for details")
|
|
}
|
|
|
|
var result *multierror.Error
|
|
users, err := getUsersFromArgs(c, args)
|
|
if err != nil {
|
|
result = multierror.Append(result, err)
|
|
}
|
|
|
|
for _, user := range users {
|
|
if _, err := c.UpdateUserMfa(context.TODO(), user.Id, "", false); err != nil {
|
|
result = multierror.Append(result, fmt.Errorf("unable to reset user %q MFA. Error: %w", user.Id, err))
|
|
}
|
|
}
|
|
|
|
return result.ErrorOrNil()
|
|
}
|
|
|
|
func deleteUsersCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
confirmFlag, _ := cmd.Flags().GetBool("confirm")
|
|
if !confirmFlag {
|
|
if err := getConfirmation("Are you sure you want to delete the users specified? All data will be permanently deleted?", true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
users, err := getUsersFromArgs(c, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var errs *multierror.Error
|
|
for i, user := range users {
|
|
if user == nil {
|
|
printer.PrintError("Unable to find user '" + args[i] + "'")
|
|
continue
|
|
}
|
|
if res, err := c.PermanentDeleteUser(context.TODO(), user.Id); err != nil {
|
|
errs = multierror.Append(errs,
|
|
fmt.Errorf("unable to delete user %s error: %w", user.Username, err))
|
|
} else {
|
|
// res.StatusCode is checked for 202 to identify issues with file deletion.
|
|
if res.StatusCode == http.StatusAccepted {
|
|
errs = multierror.Append(errs,
|
|
fmt.Errorf("unable to delete the profile image of the user, please delete it manually, id:%s", user.Username))
|
|
continue
|
|
}
|
|
printer.PrintT("Deleted user '{{.Username}}'", user)
|
|
}
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|
|
|
|
// userOut is the output format for users.
|
|
type userOut struct {
|
|
*model.User
|
|
Deactivated bool
|
|
AuthData string
|
|
}
|
|
|
|
func searchUserCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
if len(args) < 1 {
|
|
return errors.New("expected at least one argument. See help text for details")
|
|
}
|
|
|
|
users, err := getUsersFromArgs(c, args)
|
|
if err != nil {
|
|
printer.PrintError(err.Error())
|
|
return err
|
|
}
|
|
|
|
for i, user := range users {
|
|
uout := userOut{
|
|
User: user,
|
|
Deactivated: !(user.DeleteAt == 0),
|
|
}
|
|
if user.AuthData != nil {
|
|
uout.AuthData = *user.AuthData
|
|
}
|
|
|
|
tpl := `id: {{.Id}}
|
|
deactivated: {{.Deactivated}}
|
|
username: {{.Username}}
|
|
nickname: {{.Nickname}}
|
|
position: {{.Position}}
|
|
first_name: {{.FirstName}}
|
|
last_name: {{.LastName}}
|
|
email: {{.Email}}
|
|
auth_service: {{.AuthService}}
|
|
auth_data: {{.AuthData}}
|
|
roles: {{.Roles}}`
|
|
if i > 0 {
|
|
tpl = "------------------------------\n" + tpl
|
|
}
|
|
|
|
printer.PrintT(tpl, uout)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ResetListUsersCmd(t *testing.T) *cobra.Command {
|
|
require.NoError(t, ListUsersCmd.Flags().Set("page", "0"))
|
|
require.NoError(t, ListUsersCmd.Flags().Set("per-page", "200"))
|
|
require.NoError(t, ListUsersCmd.Flags().Set("all", "false"))
|
|
require.NoError(t, ListUsersCmd.Flags().Set("team", ""))
|
|
require.NoError(t, ListUsersCmd.Flags().Set("role", ""))
|
|
require.NoError(t, ListUsersCmd.Flags().Set("active", "false"))
|
|
require.NoError(t, ListUsersCmd.Flags().Set("inactive", "false"))
|
|
|
|
return ListUsersCmd
|
|
}
|
|
|
|
func listUsersCmdF(c client.Client, command *cobra.Command, args []string) error {
|
|
page, err := command.Flags().GetInt("page")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
perPage, err := command.Flags().GetInt("per-page")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
showAll, err := command.Flags().GetBool("all")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
teamName, err := command.Flags().GetString("team")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// if active, DeleteAt == 0; if inactive, DeleteAt != 0
|
|
active, err := command.Flags().GetBool("active")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inactive, err := command.Flags().GetBool("inactive")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if active && inactive {
|
|
return errors.New("--active and --inactive flags cannot be used together")
|
|
}
|
|
|
|
roleName, err := command.Flags().GetString("role")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if showAll {
|
|
page = 0
|
|
}
|
|
|
|
var team *model.Team
|
|
if teamName != "" {
|
|
var err error
|
|
team, _, err = c.GetTeamByName(context.TODO(), teamName, "")
|
|
if err != nil {
|
|
return errors.Wrap(err, fmt.Sprintf("Failed to get team %s", teamName))
|
|
}
|
|
}
|
|
|
|
params := url.Values{}
|
|
if active {
|
|
params.Add("active", "true")
|
|
}
|
|
if inactive {
|
|
params.Add("inactive", "true")
|
|
}
|
|
if team != nil {
|
|
params.Add("in_team", team.Id)
|
|
}
|
|
if roleName != "" {
|
|
params.Add("role", roleName)
|
|
}
|
|
|
|
tpl := `{{.Id}}: {{.Username}} ({{.Email}})`
|
|
|
|
for {
|
|
users, _, err := c.GetUsersWithCustomQueryParameters(context.TODO(), page, perPage, params.Encode(), "")
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to fetch users")
|
|
}
|
|
|
|
if len(users) == 0 {
|
|
break
|
|
}
|
|
|
|
for _, user := range users {
|
|
printer.PrintT(tpl, user)
|
|
}
|
|
|
|
if !showAll {
|
|
break
|
|
}
|
|
page++
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func verifyUserEmailWithoutTokenCmdF(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
var result *multierror.Error
|
|
users, err := getUsersFromArgs(c, userArgs)
|
|
if err != nil {
|
|
result = multierror.Append(result, err)
|
|
}
|
|
|
|
for _, user := range users {
|
|
if newUser, _, err := c.VerifyUserEmailWithoutToken(context.TODO(), user.Id); err != nil {
|
|
result = multierror.Append(result, fmt.Errorf("unable to verify user %s email: %w", user.Id, err))
|
|
} else {
|
|
printer.PrintT("User {{.Username}} verified", newUser)
|
|
}
|
|
}
|
|
return result.ErrorOrNil()
|
|
}
|
|
|
|
func userConvertCmdF(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
toBot, _ := cmd.Flags().GetBool("bot")
|
|
toUser, _ := cmd.Flags().GetBool("user")
|
|
|
|
if !(toUser || toBot) {
|
|
return fmt.Errorf("either %q flag or %q flag should be provided", "user", "bot")
|
|
}
|
|
|
|
if toBot {
|
|
return convertUserToBot(c, cmd, userArgs)
|
|
}
|
|
|
|
return convertBotToUser(c, cmd, userArgs)
|
|
}
|
|
|
|
func convertUserToBot(c client.Client, _ *cobra.Command, userArgs []string) error {
|
|
users, err := getUsersFromArgs(c, userArgs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var multiErr *multierror.Error
|
|
for _, user := range users {
|
|
bot, _, err := c.ConvertUserToBot(context.TODO(), user.Id)
|
|
if err != nil {
|
|
multiErr = multierror.Append(multiErr, err)
|
|
continue
|
|
}
|
|
|
|
printer.PrintT("{{.Username}} converted to bot.", bot)
|
|
}
|
|
return multiErr.ErrorOrNil()
|
|
}
|
|
|
|
func convertBotToUser(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
user, err := getUserFromArg(c, userArgs[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
password, _ := cmd.Flags().GetString("password")
|
|
if password == "" {
|
|
return errors.New("password is required")
|
|
}
|
|
|
|
up := &model.UserPatch{Password: &password}
|
|
|
|
username, _ := cmd.Flags().GetString("username")
|
|
if username == "" {
|
|
if user.Username == "" {
|
|
return errors.New("username is empty")
|
|
}
|
|
} else {
|
|
up.Username = new(username)
|
|
}
|
|
|
|
email, _ := cmd.Flags().GetString("email")
|
|
if email == "" {
|
|
if user.Email == "" {
|
|
return errors.New("email is empty")
|
|
}
|
|
} else {
|
|
up.Email = new(email)
|
|
}
|
|
|
|
nickname, _ := cmd.Flags().GetString("nickname")
|
|
if nickname != "" {
|
|
up.Nickname = new(nickname)
|
|
}
|
|
|
|
firstname, _ := cmd.Flags().GetString("firstname")
|
|
if firstname != "" {
|
|
up.FirstName = new(firstname)
|
|
}
|
|
|
|
lastname, _ := cmd.Flags().GetString("lastname")
|
|
if lastname != "" {
|
|
up.LastName = new(lastname)
|
|
}
|
|
|
|
locale, _ := cmd.Flags().GetString("locale")
|
|
if locale != "" {
|
|
up.Locale = new(locale)
|
|
}
|
|
|
|
systemAdmin, _ := cmd.Flags().GetBool("system-admin")
|
|
|
|
user, _, err = c.ConvertBotToUser(context.TODO(), user.Id, up, systemAdmin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printer.PrintT("{{.Username}} converted to user.", user)
|
|
|
|
return nil
|
|
}
|
|
|
|
func migrateAuthCmdF(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
if userArgs[1] == "saml" {
|
|
return migrateAuthToSamlCmdF(c, cmd, userArgs)
|
|
}
|
|
return migrateAuthToLdapCmdF(c, cmd, userArgs)
|
|
}
|
|
|
|
func migrateAuthToSamlCmdF(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
fromAuth := userArgs[0]
|
|
auto, _ := cmd.Flags().GetBool("auto")
|
|
confirm, _ := cmd.Flags().GetBool("confirm")
|
|
if auto && !confirm {
|
|
if err := getConfirmation("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?", false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
matches := map[string]string{}
|
|
if !auto {
|
|
matchesFile := userArgs[2]
|
|
|
|
file, err := os.ReadFile(matchesFile)
|
|
if err != nil {
|
|
return fmt.Errorf("could not read file: %w", err)
|
|
}
|
|
if err := json.Unmarshal(file, &matches); err != nil {
|
|
return fmt.Errorf("invalid json: %w", err)
|
|
}
|
|
}
|
|
|
|
if fromAuth == "" || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "ldap" && fromAuth != "google" && fromAuth != "office365") {
|
|
return errors.New("invalid from_auth argument")
|
|
}
|
|
|
|
resp, err := c.MigrateAuthToSaml(context.TODO(), fromAuth, matches, auto)
|
|
if err != nil {
|
|
return err
|
|
} else if resp.StatusCode == http.StatusOK {
|
|
printer.Print("Successfully migrated accounts.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func migrateAuthToLdapCmdF(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
fromAuth := userArgs[0]
|
|
if fromAuth == "" || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "saml" && fromAuth != "google" && fromAuth != "office365") { // nolint:goconst
|
|
return errors.New("invalid from_auth argument")
|
|
}
|
|
|
|
matchField := userArgs[2]
|
|
if matchField == "" || (matchField != "email" && matchField != "username") {
|
|
return errors.New("invalid match_field argument")
|
|
}
|
|
|
|
force, _ := cmd.Flags().GetBool("force")
|
|
|
|
resp, err := c.MigrateAuthToLdap(context.TODO(), fromAuth, matchField, force)
|
|
if err != nil {
|
|
return err
|
|
} else if resp.StatusCode == http.StatusOK {
|
|
printer.Print("Successfully migrated accounts.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func promoteGuestToUserCmdF(c client.Client, _ *cobra.Command, userArgs []string) error {
|
|
var errs *multierror.Error
|
|
for i, user := range getUsersFromUserArgs(c, userArgs) {
|
|
if user == nil {
|
|
err := fmt.Errorf("can't find guest '%s'", userArgs[i])
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
if _, err := c.PromoteGuestToUser(context.TODO(), user.Id); err != nil {
|
|
err = fmt.Errorf("unable to promote guest %s: %w", userArgs[i], err)
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
printer.PrintT("User {{.Username}} promoted.", user)
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|
|
|
|
func demoteUserToGuestCmdF(c client.Client, _ *cobra.Command, userArgs []string) error {
|
|
var errs *multierror.Error
|
|
for i, user := range getUsersFromUserArgs(c, userArgs) {
|
|
if user == nil {
|
|
err := fmt.Errorf("can't find user '%s'", userArgs[i])
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
if _, err := c.DemoteUserToGuest(context.TODO(), user.Id); err != nil {
|
|
err = fmt.Errorf("unable to demote user %s: %w", userArgs[i], err)
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
printer.PrintT("User {{.Username}} demoted.", user)
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|
|
|
|
// resolveStatusTargetUser resolves the user whose status a command operates on.
|
|
// When the --user flag is omitted, it falls back to the currently authenticated
|
|
// user, which is unavailable in local mode.
|
|
func resolveStatusTargetUser(c client.Client, cmd *cobra.Command) (*model.User, error) {
|
|
userArg, _ := cmd.Flags().GetString("user")
|
|
if userArg != "" {
|
|
return getUserFromArg(c, userArg)
|
|
}
|
|
|
|
if viper.GetBool("local") {
|
|
return nil, errors.New("the --user flag is required in local mode")
|
|
}
|
|
|
|
me, _, err := c.GetMe(context.TODO(), "")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not retrieve the current user: %w", err)
|
|
}
|
|
return me, nil
|
|
}
|
|
|
|
func userStatusGetCmdF(c client.Client, cmd *cobra.Command, _ []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
user, err := resolveStatusTargetUser(c, cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
status, _, err := c.GetUserStatus(context.TODO(), user.Id, "")
|
|
if err != nil {
|
|
return fmt.Errorf("could not get status for user %s: %w", user.Id, err)
|
|
}
|
|
|
|
printer.PrintT("@"+user.Username+" has status: {{.Status}}", status)
|
|
return nil
|
|
}
|
|
|
|
func userStatusSetCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
newStatus := args[0]
|
|
switch newStatus {
|
|
case model.StatusOnline, model.StatusAway, model.StatusDnd, model.StatusOffline:
|
|
default:
|
|
return fmt.Errorf("invalid status %q, must be one of: %s, %s, %s, %s", newStatus, model.StatusOnline, model.StatusAway, model.StatusDnd, model.StatusOffline)
|
|
}
|
|
|
|
dndEndTimeArg, _ := cmd.Flags().GetString("dnd-end-time")
|
|
if dndEndTimeArg != "" && newStatus != model.StatusDnd {
|
|
return fmt.Errorf("the --dnd-end-time flag can only be used with the %q status", model.StatusDnd)
|
|
}
|
|
|
|
var dndEndTime int64
|
|
if dndEndTimeArg != "" {
|
|
endTime, err := time.Parse(time.RFC3339, dndEndTimeArg)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid dnd-end-time %q, expected RFC3339 format (e.g. 2006-01-02T15:04:05-07:00 or 2006-01-02T15:04:05Z)", dndEndTimeArg)
|
|
}
|
|
if !endTime.After(time.Now()) {
|
|
return errors.New("dnd-end-time must be in the future")
|
|
}
|
|
// DNDEndTime is expressed in seconds rather than milliseconds.
|
|
dndEndTime = endTime.Unix()
|
|
}
|
|
|
|
user, err := resolveStatusTargetUser(c, cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
status := &model.Status{
|
|
UserId: user.Id,
|
|
Status: newStatus,
|
|
DNDEndTime: dndEndTime,
|
|
}
|
|
|
|
updatedStatus, _, err := c.UpdateUserStatus(context.TODO(), user.Id, status)
|
|
if err != nil {
|
|
return fmt.Errorf("could not set status for user %s: %w", user.Id, err)
|
|
}
|
|
|
|
printer.PrintT("Set status of @"+user.Username+" to status: {{.Status}}", updatedStatus)
|
|
return nil
|
|
}
|
|
|
|
func userEditCompletionF(ctx context.Context, c client.Client, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
if len(args) >= 1 {
|
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
|
}
|
|
return fetchAndComplete(
|
|
func(ctx context.Context, c client.Client, page, perPage int) ([]*model.User, *model.Response, error) {
|
|
return c.GetUsers(ctx, page, perPage, "")
|
|
},
|
|
func(u *model.User) []string { return []string{u.Id, u.Username, u.Email} },
|
|
)(ctx, c, cmd, args, toComplete)
|
|
}
|
|
|
|
func userEditCmdF(c client.Client, _ *cobra.Command, args []string, fieldName string) error {
|
|
printer.SetSingle(true)
|
|
|
|
userArg := args[0]
|
|
newValue := args[1]
|
|
|
|
user, err := getUserFromArg(c, userArg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the appropriate field based on fieldName
|
|
switch fieldName {
|
|
case "username":
|
|
if !model.IsValidUsername(newValue) {
|
|
return fmt.Errorf("invalid username: '%s'", newValue)
|
|
}
|
|
user.Username = newValue
|
|
|
|
ruser, _, err := c.UpdateUser(context.TODO(), user)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update user %s: %w", fieldName, err)
|
|
}
|
|
printer.PrintT("User {{.Username}} username updated successfully", ruser)
|
|
|
|
case "email":
|
|
if !model.IsValidEmail(newValue) {
|
|
return fmt.Errorf("invalid email: '%s'", newValue)
|
|
}
|
|
user.Email = newValue
|
|
|
|
ruser, _, err := c.UpdateUser(context.TODO(), user)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update user %s: %w", fieldName, err)
|
|
}
|
|
printer.PrintT("User {{.Username}} email updated successfully", ruser)
|
|
|
|
case "authdata":
|
|
if newValue == "" {
|
|
return fmt.Errorf("cannot clear authdata as the user is using %s to log in", user.AuthService)
|
|
}
|
|
if len(newValue) > model.UserAuthDataMaxLength {
|
|
return fmt.Errorf("authdata too long. Maximum length is %d characters", model.UserAuthDataMaxLength)
|
|
}
|
|
|
|
_, _, err := c.UpdateUserAuth(context.TODO(), user.Id, &model.UserAuth{
|
|
AuthData: &newValue,
|
|
AuthService: user.AuthService,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update user %s: %w", fieldName, err)
|
|
}
|
|
|
|
printer.PrintT("User {{.Username}} authdata updated successfully", user)
|
|
default:
|
|
return fmt.Errorf("unsupported field: %s", fieldName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func userEditUsernameCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
return userEditCmdF(c, cmd, args, "username")
|
|
}
|
|
|
|
func userEditEmailCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
return userEditCmdF(c, cmd, args, "email")
|
|
}
|
|
|
|
func userEditAuthdataCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
return userEditCmdF(c, cmd, args, "authdata")
|
|
}
|
|
|
|
type ByPreference model.Preferences
|
|
|
|
func (p ByPreference) Len() int { return len(p) }
|
|
func (p ByPreference) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
func (p ByPreference) Less(i, j int) bool {
|
|
if p[i].UserId < p[j].UserId {
|
|
return true
|
|
}
|
|
|
|
if p[i].Category < p[j].Category {
|
|
return true
|
|
}
|
|
|
|
if p[i].Name < p[j].Name {
|
|
return true
|
|
}
|
|
|
|
return p[i].Value < p[j].Value
|
|
}
|
|
|
|
func preferencesListCmdF(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
category, _ := cmd.Flags().GetString("category")
|
|
|
|
var errs *multierror.Error
|
|
for i, user := range getUsersFromUserArgs(c, userArgs) {
|
|
if user == nil {
|
|
err := fmt.Errorf("can't find user '%s'", userArgs[i])
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
var preferences model.Preferences
|
|
var err error
|
|
if category == "" {
|
|
preferences, _, err = c.GetPreferences(context.TODO(), user.Id)
|
|
|
|
if err != nil {
|
|
err = fmt.Errorf("unable to list user preferences %s: %w", userArgs[i], err)
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
} else {
|
|
preferences, _, err = c.GetPreferencesByCategory(context.TODO(), user.Id, category)
|
|
|
|
if err != nil {
|
|
err = fmt.Errorf("unable to list user preferences by category %s for %s: %w", category, userArgs[i], err)
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
}
|
|
|
|
sort.Sort(ByPreference(preferences))
|
|
|
|
for j, preference := range preferences {
|
|
tpl := `user_id: {{.UserId}}
|
|
category: {{.Category}}
|
|
name: {{.Name}}
|
|
value: {{.Value}}`
|
|
if j > 0 {
|
|
tpl = "------------------------------\n" + tpl
|
|
}
|
|
|
|
printer.PrintT(tpl, preference)
|
|
}
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|
|
|
|
func preferencesGetCmdF(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
category, _ := cmd.Flags().GetString("category")
|
|
preferenceName, _ := cmd.Flags().GetString("name")
|
|
|
|
var errs *multierror.Error
|
|
for i, user := range getUsersFromUserArgs(c, userArgs) {
|
|
if user == nil {
|
|
err := fmt.Errorf("can't find user '%s'", userArgs[i])
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
preference, _, err := c.GetPreferenceByCategoryAndName(context.TODO(), user.Id, category, preferenceName)
|
|
if err != nil {
|
|
err = fmt.Errorf("unable to get user preference %s %s for %s: %w", category, preferenceName, userArgs[i], err)
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
tpl := `user_id: {{.UserId}}
|
|
category: {{.Category}}
|
|
name: {{.Name}}
|
|
value: {{.Value}}`
|
|
|
|
printer.PrintT(tpl, preference)
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|
|
|
|
func preferencesUpdateCmdF(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
category, _ := cmd.Flags().GetString("category")
|
|
preferenceName, _ := cmd.Flags().GetString("name")
|
|
value, _ := cmd.Flags().GetString("value")
|
|
|
|
var errs *multierror.Error
|
|
for i, user := range getUsersFromUserArgs(c, userArgs) {
|
|
if user == nil {
|
|
err := fmt.Errorf("can't find user '%s'", userArgs[i])
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
preferences := model.Preferences{
|
|
model.Preference{
|
|
UserId: user.Id,
|
|
Category: category,
|
|
Name: preferenceName,
|
|
Value: value,
|
|
},
|
|
}
|
|
|
|
_, err := c.UpdatePreferences(context.TODO(), user.Id, preferences)
|
|
if err != nil {
|
|
err = fmt.Errorf("unable to update user preference %s %s for %s: %w", category, preferenceName, userArgs[i], err)
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
printer.Print(fmt.Sprintf("Preference %s %s for %s updated successfully", category, preferenceName, userArgs[i]))
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|
|
|
|
func preferencesDeleteCmdF(c client.Client, cmd *cobra.Command, userArgs []string) error {
|
|
category, _ := cmd.Flags().GetString("category")
|
|
preferenceName, _ := cmd.Flags().GetString("name")
|
|
|
|
var errs *multierror.Error
|
|
for i, user := range getUsersFromUserArgs(c, userArgs) {
|
|
if user == nil {
|
|
err := fmt.Errorf("can't find user '%s'", userArgs[i])
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
preferences := model.Preferences{
|
|
model.Preference{
|
|
UserId: user.Id,
|
|
Category: category,
|
|
Name: preferenceName,
|
|
},
|
|
}
|
|
|
|
_, err := c.DeletePreferences(context.TODO(), user.Id, preferences)
|
|
if err != nil {
|
|
err = fmt.Errorf("unable to delete user preference %s %s for %s: %w", category, preferenceName, userArgs[i], err)
|
|
errs = multierror.Append(errs, err)
|
|
printer.PrintError(err.Error())
|
|
continue
|
|
}
|
|
|
|
printer.Print(fmt.Sprintf("Preference %s %s for %s deleted successfully", category, preferenceName, userArgs[i]))
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|