github.com/qichengzx/mattermost-server@v4.5.1-0.20180604164826-2c75247c97d0+incompatible/model/user.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package model
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"regexp"
    12  	"strings"
    13  	"unicode/utf8"
    14  
    15  	"golang.org/x/crypto/bcrypt"
    16  )
    17  
    18  const (
    19  	ME                           = "me"
    20  	USER_NOTIFY_ALL              = "all"
    21  	USER_NOTIFY_MENTION          = "mention"
    22  	USER_NOTIFY_NONE             = "none"
    23  	DESKTOP_NOTIFY_PROP          = "desktop"
    24  	DESKTOP_SOUND_NOTIFY_PROP    = "desktop_sound"
    25  	MARK_UNREAD_NOTIFY_PROP      = "mark_unread"
    26  	PUSH_NOTIFY_PROP             = "push"
    27  	PUSH_STATUS_NOTIFY_PROP      = "push_status"
    28  	EMAIL_NOTIFY_PROP            = "email"
    29  	CHANNEL_MENTIONS_NOTIFY_PROP = "channel"
    30  	COMMENTS_NOTIFY_PROP         = "comments"
    31  	MENTION_KEYS_NOTIFY_PROP     = "mention_keys"
    32  	COMMENTS_NOTIFY_NEVER        = "never"
    33  	COMMENTS_NOTIFY_ROOT         = "root"
    34  	COMMENTS_NOTIFY_ANY          = "any"
    35  
    36  	DEFAULT_LOCALE          = "en"
    37  	USER_AUTH_SERVICE_EMAIL = "email"
    38  
    39  	USER_EMAIL_MAX_LENGTH     = 128
    40  	USER_NICKNAME_MAX_RUNES   = 64
    41  	USER_POSITION_MAX_RUNES   = 128
    42  	USER_FIRST_NAME_MAX_RUNES = 64
    43  	USER_LAST_NAME_MAX_RUNES  = 64
    44  	USER_AUTH_DATA_MAX_LENGTH = 128
    45  	USER_NAME_MAX_LENGTH      = 64
    46  	USER_NAME_MIN_LENGTH      = 1
    47  	USER_PASSWORD_MAX_LENGTH  = 72
    48  )
    49  
    50  type User struct {
    51  	Id                 string    `json:"id"`
    52  	CreateAt           int64     `json:"create_at,omitempty"`
    53  	UpdateAt           int64     `json:"update_at,omitempty"`
    54  	DeleteAt           int64     `json:"delete_at"`
    55  	Username           string    `json:"username"`
    56  	Password           string    `json:"password,omitempty"`
    57  	AuthData           *string   `json:"auth_data,omitempty"`
    58  	AuthService        string    `json:"auth_service"`
    59  	Email              string    `json:"email"`
    60  	EmailVerified      bool      `json:"email_verified,omitempty"`
    61  	Nickname           string    `json:"nickname"`
    62  	FirstName          string    `json:"first_name"`
    63  	LastName           string    `json:"last_name"`
    64  	Position           string    `json:"position"`
    65  	Roles              string    `json:"roles"`
    66  	AllowMarketing     bool      `json:"allow_marketing,omitempty"`
    67  	Props              StringMap `json:"props,omitempty"`
    68  	NotifyProps        StringMap `json:"notify_props,omitempty"`
    69  	LastPasswordUpdate int64     `json:"last_password_update,omitempty"`
    70  	LastPictureUpdate  int64     `json:"last_picture_update,omitempty"`
    71  	FailedAttempts     int       `json:"failed_attempts,omitempty"`
    72  	Locale             string    `json:"locale"`
    73  	Timezone           StringMap `json:"timezone"`
    74  	MfaActive          bool      `json:"mfa_active,omitempty"`
    75  	MfaSecret          string    `json:"mfa_secret,omitempty"`
    76  	LastActivityAt     int64     `db:"-" json:"last_activity_at,omitempty"`
    77  }
    78  
    79  type UserPatch struct {
    80  	Username    *string   `json:"username"`
    81  	Nickname    *string   `json:"nickname"`
    82  	FirstName   *string   `json:"first_name"`
    83  	LastName    *string   `json:"last_name"`
    84  	Position    *string   `json:"position"`
    85  	Email       *string   `json:"email"`
    86  	Props       StringMap `json:"props,omitempty"`
    87  	NotifyProps StringMap `json:"notify_props,omitempty"`
    88  	Locale      *string   `json:"locale"`
    89  	Timezone    StringMap `json:"timezone"`
    90  }
    91  
    92  type UserAuth struct {
    93  	Password    string  `json:"password,omitempty"`
    94  	AuthData    *string `json:"auth_data,omitempty"`
    95  	AuthService string  `json:"auth_service,omitempty"`
    96  }
    97  
    98  func (u *User) DeepCopy() *User {
    99  	copyUser := *u
   100  	if u.AuthData != nil {
   101  		copyUser.AuthData = NewString(*u.AuthData)
   102  	}
   103  	if u.Props != nil {
   104  		copyUser.Props = CopyStringMap(u.Props)
   105  	}
   106  	if u.NotifyProps != nil {
   107  		copyUser.NotifyProps = CopyStringMap(u.NotifyProps)
   108  	}
   109  	if u.Timezone != nil {
   110  		copyUser.Timezone = CopyStringMap(u.Timezone)
   111  	}
   112  	return &copyUser
   113  }
   114  
   115  // IsValid validates the user and returns an error if it isn't configured
   116  // correctly.
   117  func (u *User) IsValid() *AppError {
   118  
   119  	if len(u.Id) != 26 {
   120  		return InvalidUserError("id", "")
   121  	}
   122  
   123  	if u.CreateAt == 0 {
   124  		return InvalidUserError("create_at", u.Id)
   125  	}
   126  
   127  	if u.UpdateAt == 0 {
   128  		return InvalidUserError("update_at", u.Id)
   129  	}
   130  
   131  	if !IsValidUsername(u.Username) {
   132  		return InvalidUserError("username", u.Id)
   133  	}
   134  
   135  	if len(u.Email) > USER_EMAIL_MAX_LENGTH || len(u.Email) == 0 {
   136  		return InvalidUserError("email", u.Id)
   137  	}
   138  
   139  	if utf8.RuneCountInString(u.Nickname) > USER_NICKNAME_MAX_RUNES {
   140  		return InvalidUserError("nickname", u.Id)
   141  	}
   142  
   143  	if utf8.RuneCountInString(u.Position) > USER_POSITION_MAX_RUNES {
   144  		return InvalidUserError("position", u.Id)
   145  	}
   146  
   147  	if utf8.RuneCountInString(u.FirstName) > USER_FIRST_NAME_MAX_RUNES {
   148  		return InvalidUserError("first_name", u.Id)
   149  	}
   150  
   151  	if utf8.RuneCountInString(u.LastName) > USER_LAST_NAME_MAX_RUNES {
   152  		return InvalidUserError("last_name", u.Id)
   153  	}
   154  
   155  	if u.AuthData != nil && len(*u.AuthData) > USER_AUTH_DATA_MAX_LENGTH {
   156  		return InvalidUserError("auth_data", u.Id)
   157  	}
   158  
   159  	if u.AuthData != nil && len(*u.AuthData) > 0 && len(u.AuthService) == 0 {
   160  		return InvalidUserError("auth_data_type", u.Id)
   161  	}
   162  
   163  	if len(u.Password) > 0 && u.AuthData != nil && len(*u.AuthData) > 0 {
   164  		return InvalidUserError("auth_data_pwd", u.Id)
   165  	}
   166  
   167  	if len(u.Password) > USER_PASSWORD_MAX_LENGTH {
   168  		return InvalidUserError("password_limit", u.Id)
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  func InvalidUserError(fieldName string, userId string) *AppError {
   175  	id := fmt.Sprintf("model.user.is_valid.%s.app_error", fieldName)
   176  	details := ""
   177  	if userId != "" {
   178  		details = "user_id=" + userId
   179  	}
   180  	return NewAppError("User.IsValid", id, nil, details, http.StatusBadRequest)
   181  }
   182  
   183  func NormalizeUsername(username string) string {
   184  	return strings.ToLower(username)
   185  }
   186  
   187  func NormalizeEmail(email string) string {
   188  	return strings.ToLower(email)
   189  }
   190  
   191  // PreSave will set the Id and Username if missing.  It will also fill
   192  // in the CreateAt, UpdateAt times.  It will also hash the password.  It should
   193  // be run before saving the user to the db.
   194  func (u *User) PreSave() {
   195  	if u.Id == "" {
   196  		u.Id = NewId()
   197  	}
   198  
   199  	if u.Username == "" {
   200  		u.Username = NewId()
   201  	}
   202  
   203  	if u.AuthData != nil && *u.AuthData == "" {
   204  		u.AuthData = nil
   205  	}
   206  
   207  	u.Username = NormalizeUsername(u.Username)
   208  	u.Email = NormalizeEmail(u.Email)
   209  
   210  	u.CreateAt = GetMillis()
   211  	u.UpdateAt = u.CreateAt
   212  
   213  	u.LastPasswordUpdate = u.CreateAt
   214  
   215  	u.MfaActive = false
   216  
   217  	if u.Locale == "" {
   218  		u.Locale = DEFAULT_LOCALE
   219  	}
   220  
   221  	if u.Props == nil {
   222  		u.Props = make(map[string]string)
   223  	}
   224  
   225  	if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
   226  		u.SetDefaultNotifications()
   227  	}
   228  
   229  	if u.Timezone == nil {
   230  		u.Timezone = DefaultUserTimezone()
   231  	}
   232  
   233  	if len(u.Password) > 0 {
   234  		u.Password = HashPassword(u.Password)
   235  	}
   236  }
   237  
   238  // PreUpdate should be run before updating the user in the db.
   239  func (u *User) PreUpdate() {
   240  	u.Username = NormalizeUsername(u.Username)
   241  	u.Email = NormalizeEmail(u.Email)
   242  	u.UpdateAt = GetMillis()
   243  
   244  	if u.AuthData != nil && *u.AuthData == "" {
   245  		u.AuthData = nil
   246  	}
   247  
   248  	if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
   249  		u.SetDefaultNotifications()
   250  	} else if _, ok := u.NotifyProps["mention_keys"]; ok {
   251  		// Remove any blank mention keys
   252  		splitKeys := strings.Split(u.NotifyProps["mention_keys"], ",")
   253  		goodKeys := []string{}
   254  		for _, key := range splitKeys {
   255  			if len(key) > 0 {
   256  				goodKeys = append(goodKeys, strings.ToLower(key))
   257  			}
   258  		}
   259  		u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",")
   260  	}
   261  }
   262  
   263  func (u *User) SetDefaultNotifications() {
   264  	u.NotifyProps = make(map[string]string)
   265  	u.NotifyProps["email"] = "true"
   266  	u.NotifyProps["push"] = USER_NOTIFY_MENTION
   267  	u.NotifyProps["desktop"] = USER_NOTIFY_MENTION
   268  	u.NotifyProps["desktop_sound"] = "true"
   269  	u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username
   270  	u.NotifyProps["channel"] = "true"
   271  	u.NotifyProps["push_status"] = STATUS_AWAY
   272  	u.NotifyProps["comments"] = "never"
   273  	u.NotifyProps["first_name"] = "false"
   274  }
   275  
   276  func (user *User) UpdateMentionKeysFromUsername(oldUsername string) {
   277  	nonUsernameKeys := []string{}
   278  	splitKeys := strings.Split(user.NotifyProps["mention_keys"], ",")
   279  	for _, key := range splitKeys {
   280  		if key != oldUsername && key != "@"+oldUsername {
   281  			nonUsernameKeys = append(nonUsernameKeys, key)
   282  		}
   283  	}
   284  
   285  	user.NotifyProps["mention_keys"] = user.Username + ",@" + user.Username
   286  	if len(nonUsernameKeys) > 0 {
   287  		user.NotifyProps["mention_keys"] += "," + strings.Join(nonUsernameKeys, ",")
   288  	}
   289  }
   290  
   291  func (u *User) Patch(patch *UserPatch) {
   292  	if patch.Username != nil {
   293  		u.Username = *patch.Username
   294  	}
   295  
   296  	if patch.Nickname != nil {
   297  		u.Nickname = *patch.Nickname
   298  	}
   299  
   300  	if patch.FirstName != nil {
   301  		u.FirstName = *patch.FirstName
   302  	}
   303  
   304  	if patch.LastName != nil {
   305  		u.LastName = *patch.LastName
   306  	}
   307  
   308  	if patch.Position != nil {
   309  		u.Position = *patch.Position
   310  	}
   311  
   312  	if patch.Email != nil {
   313  		u.Email = *patch.Email
   314  	}
   315  
   316  	if patch.Props != nil {
   317  		u.Props = patch.Props
   318  	}
   319  
   320  	if patch.NotifyProps != nil {
   321  		u.NotifyProps = patch.NotifyProps
   322  	}
   323  
   324  	if patch.Locale != nil {
   325  		u.Locale = *patch.Locale
   326  	}
   327  
   328  	if patch.Timezone != nil {
   329  		u.Timezone = patch.Timezone
   330  	}
   331  }
   332  
   333  // ToJson convert a User to a json string
   334  func (u *User) ToJson() string {
   335  	b, _ := json.Marshal(u)
   336  	return string(b)
   337  }
   338  
   339  func (u *UserPatch) ToJson() string {
   340  	b, _ := json.Marshal(u)
   341  	return string(b)
   342  }
   343  
   344  func (u *UserAuth) ToJson() string {
   345  	b, _ := json.Marshal(u)
   346  	return string(b)
   347  }
   348  
   349  // Generate a valid strong etag so the browser can cache the results
   350  func (u *User) Etag(showFullName, showEmail bool) string {
   351  	return Etag(u.Id, u.UpdateAt, showFullName, showEmail)
   352  }
   353  
   354  // Remove any private data from the user object
   355  func (u *User) Sanitize(options map[string]bool) {
   356  	u.Password = ""
   357  	u.AuthData = NewString("")
   358  	u.MfaSecret = ""
   359  
   360  	if len(options) != 0 && !options["email"] {
   361  		u.Email = ""
   362  	}
   363  	if len(options) != 0 && !options["fullname"] {
   364  		u.FirstName = ""
   365  		u.LastName = ""
   366  	}
   367  	if len(options) != 0 && !options["passwordupdate"] {
   368  		u.LastPasswordUpdate = 0
   369  	}
   370  	if len(options) != 0 && !options["authservice"] {
   371  		u.AuthService = ""
   372  	}
   373  }
   374  
   375  func (u *User) ClearNonProfileFields() {
   376  	u.Password = ""
   377  	u.AuthData = NewString("")
   378  	u.MfaSecret = ""
   379  	u.EmailVerified = false
   380  	u.AllowMarketing = false
   381  	u.NotifyProps = StringMap{}
   382  	u.LastPasswordUpdate = 0
   383  	u.FailedAttempts = 0
   384  }
   385  
   386  func (u *User) SanitizeProfile(options map[string]bool) {
   387  	u.ClearNonProfileFields()
   388  
   389  	u.Sanitize(options)
   390  }
   391  
   392  func (u *User) MakeNonNil() {
   393  	if u.Props == nil {
   394  		u.Props = make(map[string]string)
   395  	}
   396  
   397  	if u.NotifyProps == nil {
   398  		u.NotifyProps = make(map[string]string)
   399  	}
   400  }
   401  
   402  func (u *User) AddNotifyProp(key string, value string) {
   403  	u.MakeNonNil()
   404  
   405  	u.NotifyProps[key] = value
   406  }
   407  
   408  func (u *User) GetFullName() string {
   409  	if u.FirstName != "" && u.LastName != "" {
   410  		return u.FirstName + " " + u.LastName
   411  	} else if u.FirstName != "" {
   412  		return u.FirstName
   413  	} else if u.LastName != "" {
   414  		return u.LastName
   415  	} else {
   416  		return ""
   417  	}
   418  }
   419  
   420  func (u *User) GetDisplayName(nameFormat string) string {
   421  	displayName := u.Username
   422  
   423  	if nameFormat == SHOW_NICKNAME_FULLNAME {
   424  		if u.Nickname != "" {
   425  			displayName = u.Nickname
   426  		} else if fullName := u.GetFullName(); fullName != "" {
   427  			displayName = fullName
   428  		}
   429  	} else if nameFormat == SHOW_FULLNAME {
   430  		if fullName := u.GetFullName(); fullName != "" {
   431  			displayName = fullName
   432  		}
   433  	}
   434  
   435  	return displayName
   436  }
   437  
   438  func (u *User) GetRoles() []string {
   439  	return strings.Fields(u.Roles)
   440  }
   441  
   442  func (u *User) GetRawRoles() string {
   443  	return u.Roles
   444  }
   445  
   446  func IsValidUserRoles(userRoles string) bool {
   447  
   448  	roles := strings.Fields(userRoles)
   449  
   450  	for _, r := range roles {
   451  		if !IsValidRoleName(r) {
   452  			return false
   453  		}
   454  	}
   455  
   456  	// Exclude just the system_admin role explicitly to prevent mistakes
   457  	if len(roles) == 1 && roles[0] == "system_admin" {
   458  		return false
   459  	}
   460  
   461  	return true
   462  }
   463  
   464  // Make sure you acually want to use this function. In context.go there are functions to check permissions
   465  // This function should not be used to check permissions.
   466  func (u *User) IsInRole(inRole string) bool {
   467  	return IsInRole(u.Roles, inRole)
   468  }
   469  
   470  // Make sure you acually want to use this function. In context.go there are functions to check permissions
   471  // This function should not be used to check permissions.
   472  func IsInRole(userRoles string, inRole string) bool {
   473  	roles := strings.Split(userRoles, " ")
   474  
   475  	for _, r := range roles {
   476  		if r == inRole {
   477  			return true
   478  		}
   479  	}
   480  
   481  	return false
   482  }
   483  
   484  func (u *User) IsSSOUser() bool {
   485  	return u.AuthService != "" && u.AuthService != USER_AUTH_SERVICE_EMAIL
   486  }
   487  
   488  func (u *User) IsOAuthUser() bool {
   489  	return u.AuthService == USER_AUTH_SERVICE_GITLAB
   490  }
   491  
   492  func (u *User) IsLDAPUser() bool {
   493  	return u.AuthService == USER_AUTH_SERVICE_LDAP
   494  }
   495  
   496  func (u *User) IsSAMLUser() bool {
   497  	return u.AuthService == USER_AUTH_SERVICE_SAML
   498  }
   499  
   500  // UserFromJson will decode the input and return a User
   501  func UserFromJson(data io.Reader) *User {
   502  	var user *User
   503  	json.NewDecoder(data).Decode(&user)
   504  	return user
   505  }
   506  
   507  func UserPatchFromJson(data io.Reader) *UserPatch {
   508  	var user *UserPatch
   509  	json.NewDecoder(data).Decode(&user)
   510  	return user
   511  }
   512  
   513  func UserAuthFromJson(data io.Reader) *UserAuth {
   514  	var user *UserAuth
   515  	json.NewDecoder(data).Decode(&user)
   516  	return user
   517  }
   518  
   519  func UserMapToJson(u map[string]*User) string {
   520  	b, _ := json.Marshal(u)
   521  	return string(b)
   522  }
   523  
   524  func UserMapFromJson(data io.Reader) map[string]*User {
   525  	var users map[string]*User
   526  	json.NewDecoder(data).Decode(&users)
   527  	return users
   528  }
   529  
   530  func UserListToJson(u []*User) string {
   531  	b, _ := json.Marshal(u)
   532  	return string(b)
   533  }
   534  
   535  func UserListFromJson(data io.Reader) []*User {
   536  	var users []*User
   537  	json.NewDecoder(data).Decode(&users)
   538  	return users
   539  }
   540  
   541  // HashPassword generates a hash using the bcrypt.GenerateFromPassword
   542  func HashPassword(password string) string {
   543  	hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
   544  	if err != nil {
   545  		panic(err)
   546  	}
   547  
   548  	return string(hash)
   549  }
   550  
   551  // ComparePassword compares the hash
   552  func ComparePassword(hash string, password string) bool {
   553  
   554  	if len(password) == 0 || len(hash) == 0 {
   555  		return false
   556  	}
   557  
   558  	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
   559  	return err == nil
   560  }
   561  
   562  var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
   563  
   564  var restrictedUsernames = []string{
   565  	"all",
   566  	"channel",
   567  	"matterbot",
   568  }
   569  
   570  func IsValidUsername(s string) bool {
   571  	if len(s) < USER_NAME_MIN_LENGTH || len(s) > USER_NAME_MAX_LENGTH {
   572  		return false
   573  	}
   574  
   575  	if !validUsernameChars.MatchString(s) {
   576  		return false
   577  	}
   578  
   579  	for _, restrictedUsername := range restrictedUsernames {
   580  		if s == restrictedUsername {
   581  			return false
   582  		}
   583  	}
   584  
   585  	return true
   586  }
   587  
   588  func CleanUsername(s string) string {
   589  	s = NormalizeUsername(strings.Replace(s, " ", "-", -1))
   590  
   591  	for _, value := range reservedName {
   592  		if s == value {
   593  			s = strings.Replace(s, value, "", -1)
   594  		}
   595  	}
   596  
   597  	s = strings.TrimSpace(s)
   598  
   599  	for _, c := range s {
   600  		char := fmt.Sprintf("%c", c)
   601  		if !validUsernameChars.MatchString(char) {
   602  			s = strings.Replace(s, char, "-", -1)
   603  		}
   604  	}
   605  
   606  	s = strings.Trim(s, "-")
   607  
   608  	if !IsValidUsername(s) {
   609  		s = "a" + NewId()
   610  	}
   611  
   612  	return s
   613  }
   614  
   615  func IsValidUserNotifyLevel(notifyLevel string) bool {
   616  	return notifyLevel == CHANNEL_NOTIFY_ALL ||
   617  		notifyLevel == CHANNEL_NOTIFY_MENTION ||
   618  		notifyLevel == CHANNEL_NOTIFY_NONE
   619  }
   620  
   621  func IsValidPushStatusNotifyLevel(notifyLevel string) bool {
   622  	return notifyLevel == STATUS_ONLINE ||
   623  		notifyLevel == STATUS_AWAY ||
   624  		notifyLevel == STATUS_OFFLINE
   625  }
   626  
   627  func IsValidCommentsNotifyLevel(notifyLevel string) bool {
   628  	return notifyLevel == COMMENTS_NOTIFY_ANY ||
   629  		notifyLevel == COMMENTS_NOTIFY_ROOT ||
   630  		notifyLevel == COMMENTS_NOTIFY_NEVER
   631  }