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