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