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