summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/mattermost-server/v6/model/user.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mattermost/mattermost-server/v6/model/user.go')
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user.go912
1 files changed, 912 insertions, 0 deletions
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user.go
new file mode 100644
index 00000000..2e843ea1
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user.go
@@ -0,0 +1,912 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "regexp"
+ "sort"
+ "strings"
+ "unicode/utf8"
+
+ "golang.org/x/crypto/bcrypt"
+ "golang.org/x/text/language"
+
+ "github.com/mattermost/mattermost-server/v6/services/timezones"
+ "github.com/mattermost/mattermost-server/v6/shared/mlog"
+)
+
+const (
+ Me = "me"
+ UserNotifyAll = "all"
+ UserNotifyHere = "here"
+ UserNotifyMention = "mention"
+ UserNotifyNone = "none"
+ DesktopNotifyProp = "desktop"
+ DesktopSoundNotifyProp = "desktop_sound"
+ MarkUnreadNotifyProp = "mark_unread"
+ PushNotifyProp = "push"
+ PushStatusNotifyProp = "push_status"
+ EmailNotifyProp = "email"
+ ChannelMentionsNotifyProp = "channel"
+ CommentsNotifyProp = "comments"
+ MentionKeysNotifyProp = "mention_keys"
+ CommentsNotifyNever = "never"
+ CommentsNotifyRoot = "root"
+ CommentsNotifyAny = "any"
+ CommentsNotifyCRT = "crt"
+ FirstNameNotifyProp = "first_name"
+ AutoResponderActiveNotifyProp = "auto_responder_active"
+ AutoResponderMessageNotifyProp = "auto_responder_message"
+ DesktopThreadsNotifyProp = "desktop_threads"
+ PushThreadsNotifyProp = "push_threads"
+ EmailThreadsNotifyProp = "email_threads"
+
+ DefaultLocale = "en"
+ UserAuthServiceEmail = "email"
+
+ UserEmailMaxLength = 128
+ UserNicknameMaxRunes = 64
+ UserPositionMaxRunes = 128
+ UserFirstNameMaxRunes = 64
+ UserLastNameMaxRunes = 64
+ UserAuthDataMaxLength = 128
+ UserNameMaxLength = 64
+ UserNameMinLength = 1
+ UserPasswordMaxLength = 72
+ UserLocaleMaxLength = 5
+ UserTimezoneMaxRunes = 256
+)
+
+//msgp:tuple User
+
+// User contains the details about the user.
+// This struct's serializer methods are auto-generated. If a new field is added/removed,
+// please run make gen-serialized.
+type User struct {
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at,omitempty"`
+ UpdateAt int64 `json:"update_at,omitempty"`
+ DeleteAt int64 `json:"delete_at"`
+ Username string `json:"username"`
+ Password string `json:"password,omitempty"`
+ AuthData *string `json:"auth_data,omitempty"`
+ AuthService string `json:"auth_service"`
+ Email string `json:"email"`
+ EmailVerified bool `json:"email_verified,omitempty"`
+ Nickname string `json:"nickname"`
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ Position string `json:"position"`
+ Roles string `json:"roles"`
+ AllowMarketing bool `json:"allow_marketing,omitempty"`
+ Props StringMap `json:"props,omitempty"`
+ NotifyProps StringMap `json:"notify_props,omitempty"`
+ LastPasswordUpdate int64 `json:"last_password_update,omitempty"`
+ LastPictureUpdate int64 `json:"last_picture_update,omitempty"`
+ FailedAttempts int `json:"failed_attempts,omitempty"`
+ Locale string `json:"locale"`
+ Timezone StringMap `json:"timezone"`
+ MfaActive bool `json:"mfa_active,omitempty"`
+ MfaSecret string `json:"mfa_secret,omitempty"`
+ RemoteId *string `json:"remote_id,omitempty"`
+ LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"`
+ IsBot bool `db:"-" json:"is_bot,omitempty"`
+ BotDescription string `db:"-" json:"bot_description,omitempty"`
+ BotLastIconUpdate int64 `db:"-" json:"bot_last_icon_update,omitempty"`
+ TermsOfServiceId string `db:"-" json:"terms_of_service_id,omitempty"`
+ TermsOfServiceCreateAt int64 `db:"-" json:"terms_of_service_create_at,omitempty"`
+ DisableWelcomeEmail bool `db:"-" json:"disable_welcome_email"`
+}
+
+//msgp UserMap
+
+// UserMap is a map from a userId to a user object.
+// It is used to generate methods which can be used for fast serialization/de-serialization.
+type UserMap map[string]*User
+
+//msgp:ignore UserUpdate
+type UserUpdate struct {
+ Old *User
+ New *User
+}
+
+//msgp:ignore UserPatch
+type UserPatch struct {
+ Username *string `json:"username"`
+ Password *string `json:"password,omitempty"`
+ Nickname *string `json:"nickname"`
+ FirstName *string `json:"first_name"`
+ LastName *string `json:"last_name"`
+ Position *string `json:"position"`
+ Email *string `json:"email"`
+ Props StringMap `json:"props,omitempty"`
+ NotifyProps StringMap `json:"notify_props,omitempty"`
+ Locale *string `json:"locale"`
+ Timezone StringMap `json:"timezone"`
+ RemoteId *string `json:"remote_id"`
+}
+
+//msgp:ignore UserAuth
+type UserAuth struct {
+ Password string `json:"password,omitempty"` // DEPRECATED: It is not used.
+ AuthData *string `json:"auth_data,omitempty"`
+ AuthService string `json:"auth_service,omitempty"`
+}
+
+//msgp:ignore UserForIndexing
+type UserForIndexing struct {
+ Id string `json:"id"`
+ Username string `json:"username"`
+ Nickname string `json:"nickname"`
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ Roles string `json:"roles"`
+ CreateAt int64 `json:"create_at"`
+ DeleteAt int64 `json:"delete_at"`
+ TeamsIds []string `json:"team_id"`
+ ChannelsIds []string `json:"channel_id"`
+}
+
+//msgp:ignore ViewUsersRestrictions
+type ViewUsersRestrictions struct {
+ Teams []string
+ Channels []string
+}
+
+func (r *ViewUsersRestrictions) Hash() string {
+ if r == nil {
+ return ""
+ }
+ ids := append(r.Teams, r.Channels...)
+ sort.Strings(ids)
+ hash := sha256.New()
+ hash.Write([]byte(strings.Join(ids, "")))
+ return fmt.Sprintf("%x", hash.Sum(nil))
+}
+
+//msgp:ignore UserSlice
+type UserSlice []*User
+
+func (u UserSlice) Usernames() []string {
+ usernames := []string{}
+ for _, user := range u {
+ usernames = append(usernames, user.Username)
+ }
+ sort.Strings(usernames)
+ return usernames
+}
+
+func (u UserSlice) IDs() []string {
+ ids := []string{}
+ for _, user := range u {
+ ids = append(ids, user.Id)
+ }
+ return ids
+}
+
+func (u UserSlice) FilterWithoutBots() UserSlice {
+ var matches []*User
+
+ for _, user := range u {
+ if !user.IsBot {
+ matches = append(matches, user)
+ }
+ }
+ return UserSlice(matches)
+}
+
+func (u UserSlice) FilterByActive(active bool) UserSlice {
+ var matches []*User
+
+ for _, user := range u {
+ if user.DeleteAt == 0 && active {
+ matches = append(matches, user)
+ } else if user.DeleteAt != 0 && !active {
+ matches = append(matches, user)
+ }
+ }
+ return UserSlice(matches)
+}
+
+func (u UserSlice) FilterByID(ids []string) UserSlice {
+ var matches []*User
+ for _, user := range u {
+ for _, id := range ids {
+ if id == user.Id {
+ matches = append(matches, user)
+ }
+ }
+ }
+ return UserSlice(matches)
+}
+
+func (u UserSlice) FilterWithoutID(ids []string) UserSlice {
+ var keep []*User
+ for _, user := range u {
+ present := false
+ for _, id := range ids {
+ if id == user.Id {
+ present = true
+ }
+ }
+ if !present {
+ keep = append(keep, user)
+ }
+ }
+ return UserSlice(keep)
+}
+
+func (u *User) DeepCopy() *User {
+ copyUser := *u
+ if u.AuthData != nil {
+ copyUser.AuthData = NewString(*u.AuthData)
+ }
+ if u.Props != nil {
+ copyUser.Props = CopyStringMap(u.Props)
+ }
+ if u.NotifyProps != nil {
+ copyUser.NotifyProps = CopyStringMap(u.NotifyProps)
+ }
+ if u.Timezone != nil {
+ copyUser.Timezone = CopyStringMap(u.Timezone)
+ }
+ return &copyUser
+}
+
+// IsValid validates the user and returns an error if it isn't configured
+// correctly.
+func (u *User) IsValid() *AppError {
+
+ if !IsValidId(u.Id) {
+ return InvalidUserError("id", "")
+ }
+
+ if u.CreateAt == 0 {
+ return InvalidUserError("create_at", u.Id)
+ }
+
+ if u.UpdateAt == 0 {
+ return InvalidUserError("update_at", u.Id)
+ }
+
+ if u.IsRemote() {
+ if !IsValidUsernameAllowRemote(u.Username) {
+ return InvalidUserError("username", u.Id)
+ }
+ } else {
+ if !IsValidUsername(u.Username) {
+ return InvalidUserError("username", u.Id)
+ }
+ }
+
+ if len(u.Email) > UserEmailMaxLength || u.Email == "" || !IsValidEmail(u.Email) {
+ return InvalidUserError("email", u.Id)
+ }
+
+ if utf8.RuneCountInString(u.Nickname) > UserNicknameMaxRunes {
+ return InvalidUserError("nickname", u.Id)
+ }
+
+ if utf8.RuneCountInString(u.Position) > UserPositionMaxRunes {
+ return InvalidUserError("position", u.Id)
+ }
+
+ if utf8.RuneCountInString(u.FirstName) > UserFirstNameMaxRunes {
+ return InvalidUserError("first_name", u.Id)
+ }
+
+ if utf8.RuneCountInString(u.LastName) > UserLastNameMaxRunes {
+ return InvalidUserError("last_name", u.Id)
+ }
+
+ if u.AuthData != nil && len(*u.AuthData) > UserAuthDataMaxLength {
+ return InvalidUserError("auth_data", u.Id)
+ }
+
+ if u.AuthData != nil && *u.AuthData != "" && u.AuthService == "" {
+ return InvalidUserError("auth_data_type", u.Id)
+ }
+
+ if u.Password != "" && u.AuthData != nil && *u.AuthData != "" {
+ return InvalidUserError("auth_data_pwd", u.Id)
+ }
+
+ if len(u.Password) > UserPasswordMaxLength {
+ return InvalidUserError("password_limit", u.Id)
+ }
+
+ if !IsValidLocale(u.Locale) {
+ return InvalidUserError("locale", u.Id)
+ }
+
+ if len(u.Timezone) > 0 {
+ if tzJSON, err := json.Marshal(u.Timezone); err != nil {
+ return NewAppError("User.IsValid", "model.user.is_valid.marshal.app_error", nil, err.Error(), http.StatusInternalServerError)
+ } else if utf8.RuneCount(tzJSON) > UserTimezoneMaxRunes {
+ return InvalidUserError("timezone_limit", u.Id)
+ }
+ }
+
+ return nil
+}
+
+func InvalidUserError(fieldName string, userId string) *AppError {
+ id := fmt.Sprintf("model.user.is_valid.%s.app_error", fieldName)
+ details := ""
+ if userId != "" {
+ details = "user_id=" + userId
+ }
+ return NewAppError("User.IsValid", id, nil, details, http.StatusBadRequest)
+}
+
+func NormalizeUsername(username string) string {
+ return strings.ToLower(username)
+}
+
+func NormalizeEmail(email string) string {
+ return strings.ToLower(email)
+}
+
+// PreSave will set the Id and Username if missing. It will also fill
+// in the CreateAt, UpdateAt times. It will also hash the password. It should
+// be run before saving the user to the db.
+func (u *User) PreSave() {
+ if u.Id == "" {
+ u.Id = NewId()
+ }
+
+ if u.Username == "" {
+ u.Username = NewId()
+ }
+
+ if u.AuthData != nil && *u.AuthData == "" {
+ u.AuthData = nil
+ }
+
+ u.Username = SanitizeUnicode(u.Username)
+ u.FirstName = SanitizeUnicode(u.FirstName)
+ u.LastName = SanitizeUnicode(u.LastName)
+ u.Nickname = SanitizeUnicode(u.Nickname)
+
+ u.Username = NormalizeUsername(u.Username)
+ u.Email = NormalizeEmail(u.Email)
+
+ u.CreateAt = GetMillis()
+ u.UpdateAt = u.CreateAt
+
+ u.LastPasswordUpdate = u.CreateAt
+
+ u.MfaActive = false
+
+ if u.Locale == "" {
+ u.Locale = DefaultLocale
+ }
+
+ if u.Props == nil {
+ u.Props = make(map[string]string)
+ }
+
+ if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
+ u.SetDefaultNotifications()
+ }
+
+ if u.Timezone == nil {
+ u.Timezone = timezones.DefaultUserTimezone()
+ }
+
+ if u.Password != "" {
+ u.Password = HashPassword(u.Password)
+ }
+}
+
+// PreUpdate should be run before updating the user in the db.
+func (u *User) PreUpdate() {
+ u.Username = SanitizeUnicode(u.Username)
+ u.FirstName = SanitizeUnicode(u.FirstName)
+ u.LastName = SanitizeUnicode(u.LastName)
+ u.Nickname = SanitizeUnicode(u.Nickname)
+ u.BotDescription = SanitizeUnicode(u.BotDescription)
+
+ u.Username = NormalizeUsername(u.Username)
+ u.Email = NormalizeEmail(u.Email)
+ u.UpdateAt = GetMillis()
+
+ u.FirstName = SanitizeUnicode(u.FirstName)
+ u.LastName = SanitizeUnicode(u.LastName)
+ u.Nickname = SanitizeUnicode(u.Nickname)
+ u.BotDescription = SanitizeUnicode(u.BotDescription)
+
+ if u.AuthData != nil && *u.AuthData == "" {
+ u.AuthData = nil
+ }
+
+ if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
+ u.SetDefaultNotifications()
+ } else if _, ok := u.NotifyProps[MentionKeysNotifyProp]; ok {
+ // Remove any blank mention keys
+ splitKeys := strings.Split(u.NotifyProps[MentionKeysNotifyProp], ",")
+ goodKeys := []string{}
+ for _, key := range splitKeys {
+ if key != "" {
+ goodKeys = append(goodKeys, strings.ToLower(key))
+ }
+ }
+ u.NotifyProps[MentionKeysNotifyProp] = strings.Join(goodKeys, ",")
+ }
+}
+
+func (u *User) SetDefaultNotifications() {
+ u.NotifyProps = make(map[string]string)
+ u.NotifyProps[EmailNotifyProp] = "true"
+ u.NotifyProps[PushNotifyProp] = UserNotifyMention
+ u.NotifyProps[DesktopNotifyProp] = UserNotifyMention
+ u.NotifyProps[DesktopSoundNotifyProp] = "true"
+ u.NotifyProps[MentionKeysNotifyProp] = ""
+ u.NotifyProps[ChannelMentionsNotifyProp] = "true"
+ u.NotifyProps[PushStatusNotifyProp] = StatusAway
+ u.NotifyProps[CommentsNotifyProp] = CommentsNotifyNever
+ u.NotifyProps[FirstNameNotifyProp] = "false"
+ u.NotifyProps[DesktopThreadsNotifyProp] = UserNotifyAll
+ u.NotifyProps[EmailThreadsNotifyProp] = UserNotifyAll
+ u.NotifyProps[PushThreadsNotifyProp] = UserNotifyAll
+}
+
+func (u *User) UpdateMentionKeysFromUsername(oldUsername string) {
+ nonUsernameKeys := []string{}
+ for _, key := range u.GetMentionKeys() {
+ if key != oldUsername && key != "@"+oldUsername {
+ nonUsernameKeys = append(nonUsernameKeys, key)
+ }
+ }
+
+ u.NotifyProps[MentionKeysNotifyProp] = ""
+ if len(nonUsernameKeys) > 0 {
+ u.NotifyProps[MentionKeysNotifyProp] += "," + strings.Join(nonUsernameKeys, ",")
+ }
+}
+
+func (u *User) GetMentionKeys() []string {
+ var keys []string
+
+ for _, key := range strings.Split(u.NotifyProps[MentionKeysNotifyProp], ",") {
+ trimmedKey := strings.TrimSpace(key)
+
+ if trimmedKey == "" {
+ continue
+ }
+
+ keys = append(keys, trimmedKey)
+ }
+
+ return keys
+}
+
+func (u *User) Patch(patch *UserPatch) {
+ if patch.Username != nil {
+ u.Username = *patch.Username
+ }
+
+ if patch.Nickname != nil {
+ u.Nickname = *patch.Nickname
+ }
+
+ if patch.FirstName != nil {
+ u.FirstName = *patch.FirstName
+ }
+
+ if patch.LastName != nil {
+ u.LastName = *patch.LastName
+ }
+
+ if patch.Position != nil {
+ u.Position = *patch.Position
+ }
+
+ if patch.Email != nil {
+ u.Email = *patch.Email
+ }
+
+ if patch.Props != nil {
+ u.Props = patch.Props
+ }
+
+ if patch.NotifyProps != nil {
+ u.NotifyProps = patch.NotifyProps
+ }
+
+ if patch.Locale != nil {
+ u.Locale = *patch.Locale
+ }
+
+ if patch.Timezone != nil {
+ u.Timezone = patch.Timezone
+ }
+
+ if patch.RemoteId != nil {
+ u.RemoteId = patch.RemoteId
+ }
+}
+
+// Generate a valid strong etag so the browser can cache the results
+func (u *User) Etag(showFullName, showEmail bool) string {
+ return Etag(u.Id, u.UpdateAt, u.TermsOfServiceId, u.TermsOfServiceCreateAt, showFullName, showEmail, u.BotLastIconUpdate)
+}
+
+// Remove any private data from the user object
+func (u *User) Sanitize(options map[string]bool) {
+ u.Password = ""
+ u.AuthData = NewString("")
+ u.MfaSecret = ""
+
+ if len(options) != 0 && !options["email"] {
+ u.Email = ""
+ }
+ if len(options) != 0 && !options["fullname"] {
+ u.FirstName = ""
+ u.LastName = ""
+ }
+ if len(options) != 0 && !options["passwordupdate"] {
+ u.LastPasswordUpdate = 0
+ }
+ if len(options) != 0 && !options["authservice"] {
+ u.AuthService = ""
+ }
+}
+
+// Remove any input data from the user object that is not user controlled
+func (u *User) SanitizeInput(isAdmin bool) {
+ if !isAdmin {
+ u.AuthData = NewString("")
+ u.AuthService = ""
+ u.EmailVerified = false
+ }
+ u.LastPasswordUpdate = 0
+ u.LastPictureUpdate = 0
+ u.FailedAttempts = 0
+ u.MfaActive = false
+ u.MfaSecret = ""
+ u.Email = strings.TrimSpace(u.Email)
+}
+
+func (u *User) ClearNonProfileFields() {
+ u.Password = ""
+ u.AuthData = NewString("")
+ u.MfaSecret = ""
+ u.EmailVerified = false
+ u.AllowMarketing = false
+ u.NotifyProps = StringMap{}
+ u.LastPasswordUpdate = 0
+ u.FailedAttempts = 0
+}
+
+func (u *User) SanitizeProfile(options map[string]bool) {
+ u.ClearNonProfileFields()
+
+ u.Sanitize(options)
+}
+
+func (u *User) MakeNonNil() {
+ if u.Props == nil {
+ u.Props = make(map[string]string)
+ }
+
+ if u.NotifyProps == nil {
+ u.NotifyProps = make(map[string]string)
+ }
+}
+
+func (u *User) AddNotifyProp(key string, value string) {
+ u.MakeNonNil()
+
+ u.NotifyProps[key] = value
+}
+
+func (u *User) SetCustomStatus(cs *CustomStatus) error {
+ u.MakeNonNil()
+ statusJSON, jsonErr := json.Marshal(cs)
+ if jsonErr != nil {
+ return jsonErr
+ }
+ u.Props[UserPropsKeyCustomStatus] = string(statusJSON)
+ return nil
+}
+
+func (u *User) ClearCustomStatus() {
+ u.MakeNonNil()
+ u.Props[UserPropsKeyCustomStatus] = ""
+}
+
+func (u *User) GetFullName() string {
+ if u.FirstName != "" && u.LastName != "" {
+ return u.FirstName + " " + u.LastName
+ } else if u.FirstName != "" {
+ return u.FirstName
+ } else if u.LastName != "" {
+ return u.LastName
+ } else {
+ return ""
+ }
+}
+
+func (u *User) getDisplayName(baseName, nameFormat string) string {
+ displayName := baseName
+
+ if nameFormat == ShowNicknameFullName {
+ if u.Nickname != "" {
+ displayName = u.Nickname
+ } else if fullName := u.GetFullName(); fullName != "" {
+ displayName = fullName
+ }
+ } else if nameFormat == ShowFullName {
+ if fullName := u.GetFullName(); fullName != "" {
+ displayName = fullName
+ }
+ }
+
+ return displayName
+}
+
+func (u *User) GetDisplayName(nameFormat string) string {
+ displayName := u.Username
+
+ return u.getDisplayName(displayName, nameFormat)
+}
+
+func (u *User) GetDisplayNameWithPrefix(nameFormat, prefix string) string {
+ displayName := prefix + u.Username
+
+ return u.getDisplayName(displayName, nameFormat)
+}
+
+func (u *User) GetRoles() []string {
+ return strings.Fields(u.Roles)
+}
+
+func (u *User) GetRawRoles() string {
+ return u.Roles
+}
+
+func IsValidUserRoles(userRoles string) bool {
+
+ roles := strings.Fields(userRoles)
+
+ for _, r := range roles {
+ if !IsValidRoleName(r) {
+ return false
+ }
+ }
+
+ // Exclude just the system_admin role explicitly to prevent mistakes
+ if len(roles) == 1 && roles[0] == "system_admin" {
+ return false
+ }
+
+ return true
+}
+
+// Make sure you acually want to use this function. In context.go there are functions to check permissions
+// This function should not be used to check permissions.
+func (u *User) IsGuest() bool {
+ return IsInRole(u.Roles, SystemGuestRoleId)
+}
+
+func (u *User) IsSystemAdmin() bool {
+ return IsInRole(u.Roles, SystemAdminRoleId)
+}
+
+// Make sure you acually want to use this function. In context.go there are functions to check permissions
+// This function should not be used to check permissions.
+func (u *User) IsInRole(inRole string) bool {
+ return IsInRole(u.Roles, inRole)
+}
+
+// Make sure you acually want to use this function. In context.go there are functions to check permissions
+// This function should not be used to check permissions.
+func IsInRole(userRoles string, inRole string) bool {
+ roles := strings.Split(userRoles, " ")
+
+ for _, r := range roles {
+ if r == inRole {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (u *User) IsSSOUser() bool {
+ return u.AuthService != "" && u.AuthService != UserAuthServiceEmail
+}
+
+func (u *User) IsOAuthUser() bool {
+ return u.AuthService == ServiceGitlab ||
+ u.AuthService == ServiceGoogle ||
+ u.AuthService == ServiceOffice365 ||
+ u.AuthService == ServiceOpenid
+}
+
+func (u *User) IsLDAPUser() bool {
+ return u.AuthService == UserAuthServiceLdap
+}
+
+func (u *User) IsSAMLUser() bool {
+ return u.AuthService == UserAuthServiceSaml
+}
+
+func (u *User) GetPreferredTimezone() string {
+ return GetPreferredTimezone(u.Timezone)
+}
+
+// IsRemote returns true if the user belongs to a remote cluster (has RemoteId).
+func (u *User) IsRemote() bool {
+ return u.RemoteId != nil && *u.RemoteId != ""
+}
+
+// GetRemoteID returns the remote id for this user or "" if not a remote user.
+func (u *User) GetRemoteID() string {
+ if u.RemoteId != nil {
+ return *u.RemoteId
+ }
+ return ""
+}
+
+// GetProp fetches a prop value by name.
+func (u *User) GetProp(name string) (string, bool) {
+ val, ok := u.Props[name]
+ return val, ok
+}
+
+// SetProp sets a prop value by name, creating the map if nil.
+// Not thread safe.
+func (u *User) SetProp(name string, value string) {
+ if u.Props == nil {
+ u.Props = make(map[string]string)
+ }
+ u.Props[name] = value
+}
+
+func (u *User) ToPatch() *UserPatch {
+ return &UserPatch{
+ Username: &u.Username, Password: &u.Password,
+ Nickname: &u.Nickname, FirstName: &u.FirstName, LastName: &u.LastName,
+ Position: &u.Position, Email: &u.Email,
+ Props: u.Props, NotifyProps: u.NotifyProps,
+ Locale: &u.Locale, Timezone: u.Timezone,
+ }
+}
+
+func (u *UserPatch) SetField(fieldName string, fieldValue string) {
+ switch fieldName {
+ case "FirstName":
+ u.FirstName = &fieldValue
+ case "LastName":
+ u.LastName = &fieldValue
+ case "Nickname":
+ u.Nickname = &fieldValue
+ case "Email":
+ u.Email = &fieldValue
+ case "Position":
+ u.Position = &fieldValue
+ case "Username":
+ u.Username = &fieldValue
+ }
+}
+
+// HashPassword generates a hash using the bcrypt.GenerateFromPassword
+func HashPassword(password string) string {
+ hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
+ if err != nil {
+ panic(err)
+ }
+
+ return string(hash)
+}
+
+var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
+var validUsernameCharsForRemote = regexp.MustCompile(`^[a-z0-9\.\-_:]+$`)
+
+var restrictedUsernames = map[string]struct{}{
+ "all": {},
+ "channel": {},
+ "matterbot": {},
+ "system": {},
+}
+
+func IsValidUsername(s string) bool {
+ if len(s) < UserNameMinLength || len(s) > UserNameMaxLength {
+ return false
+ }
+
+ if !validUsernameChars.MatchString(s) {
+ return false
+ }
+
+ _, found := restrictedUsernames[s]
+ return !found
+}
+
+func IsValidUsernameAllowRemote(s string) bool {
+ if len(s) < UserNameMinLength || len(s) > UserNameMaxLength {
+ return false
+ }
+
+ if !validUsernameCharsForRemote.MatchString(s) {
+ return false
+ }
+
+ _, found := restrictedUsernames[s]
+ return !found
+}
+
+func CleanUsername(username string) string {
+ s := NormalizeUsername(strings.Replace(username, " ", "-", -1))
+
+ for _, value := range reservedName {
+ if s == value {
+ s = strings.Replace(s, value, "", -1)
+ }
+ }
+
+ s = strings.TrimSpace(s)
+
+ for _, c := range s {
+ char := fmt.Sprintf("%c", c)
+ if !validUsernameChars.MatchString(char) {
+ s = strings.Replace(s, char, "-", -1)
+ }
+ }
+
+ s = strings.Trim(s, "-")
+
+ if !IsValidUsername(s) {
+ s = "a" + NewId()
+ mlog.Warn("Generating new username since provided username was invalid",
+ mlog.String("provided_username", username), mlog.String("new_username", s))
+ }
+
+ return s
+}
+
+func IsValidLocale(locale string) bool {
+ if locale != "" {
+ if len(locale) > UserLocaleMaxLength {
+ return false
+ } else if _, err := language.Parse(locale); err != nil {
+ return false
+ }
+ }
+
+ return true
+}
+
+//msgp:ignore UserWithGroups
+type UserWithGroups struct {
+ User
+ GroupIDs *string `json:"-"`
+ Groups []*Group `json:"groups"`
+ SchemeGuest bool `json:"scheme_guest"`
+ SchemeUser bool `json:"scheme_user"`
+ SchemeAdmin bool `json:"scheme_admin"`
+}
+
+func (u *UserWithGroups) GetGroupIDs() []string {
+ if u.GroupIDs == nil {
+ return nil
+ }
+ trimmed := strings.TrimSpace(*u.GroupIDs)
+ if trimmed == "" {
+ return nil
+ }
+ return strings.Split(trimmed, ",")
+}
+
+//msgp:ignore UsersWithGroupsAndCount
+type UsersWithGroupsAndCount struct {
+ Users []*UserWithGroups `json:"users"`
+ Count int64 `json:"total_count"`
+}