Files
mostlymatter/app/user.go
jfrerich baedcc856f [MM-17741] when converting bot to a user set the correct email. (#11879)
* Send notifications when bot converted to user

* Revert notification sending from CLI.
If user is a bot, set email to CLI email, not the previous email because
previous email <botname>@localhost

* Add comment describing the additional if statement

* Rewording

* remove duplicate word

* Update Comment

* Add tests for bots.  In both cases of RequireEmailVerification settings,
the Email should be set to the value passed to the UpdateUser function
and the previous faked localhost email ignored
2019-08-16 07:37:22 -07:00

2341 строка
66 KiB
Go

// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"bytes"
b64 "encoding/base64"
"encoding/json"
"fmt"
"hash/fnv"
"image"
"image/color"
"image/draw"
_ "image/gif"
_ "image/jpeg"
"image/png"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"path/filepath"
"strconv"
"strings"
"github.com/disintegration/imaging"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/mattermost/mattermost-server/einterfaces"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/plugin"
"github.com/mattermost/mattermost-server/services/mfa"
"github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/utils"
"github.com/mattermost/mattermost-server/utils/fileutils"
)
const (
TOKEN_TYPE_PASSWORD_RECOVERY = "password_recovery"
TOKEN_TYPE_VERIFY_EMAIL = "verify_email"
TOKEN_TYPE_TEAM_INVITATION = "team_invitation"
TOKEN_TYPE_GUEST_INVITATION = "guest_invitation"
PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
INVITATION_EXPIRY_TIME = 1000 * 60 * 60 * 48 // 48 hours
IMAGE_PROFILE_PIXEL_DIMENSION = 128
)
func (a *App) CreateUserWithToken(user *model.User, token *model.Token) (*model.User, *model.AppError) {
if err := a.IsUserSignUpAllowed(); err != nil {
return nil, err
}
if token.Type != TOKEN_TYPE_TEAM_INVITATION && token.Type != TOKEN_TYPE_GUEST_INVITATION {
return nil, model.NewAppError("CreateUserWithToken", "api.user.create_user.signup_link_invalid.app_error", nil, "", http.StatusBadRequest)
}
if model.GetMillis()-token.CreateAt >= INVITATION_EXPIRY_TIME {
a.DeleteToken(token)
return nil, model.NewAppError("CreateUserWithToken", "api.user.create_user.signup_link_expired.app_error", nil, "", http.StatusBadRequest)
}
tokenData := model.MapFromJson(strings.NewReader(token.Extra))
team, err := a.Srv.Store.Team().Get(tokenData["teamId"])
if err != nil {
return nil, err
}
channels, err := a.Srv.Store.Channel().GetChannelsByIds(strings.Split(tokenData["channels"], " "))
if err != nil {
return nil, err
}
user.Email = tokenData["email"]
user.EmailVerified = true
var ruser *model.User
if token.Type == TOKEN_TYPE_TEAM_INVITATION {
ruser, err = a.CreateUser(user)
} else {
ruser, err = a.CreateGuest(user)
}
if err != nil {
return nil, err
}
if err := a.JoinUserToTeam(team, ruser, ""); err != nil {
return nil, err
}
a.AddDirectChannels(team.Id, ruser)
if token.Type == TOKEN_TYPE_GUEST_INVITATION {
for _, channel := range channels {
_, err := a.AddChannelMember(ruser.Id, channel, "", "")
if err != nil {
mlog.Error(err.Error())
}
}
}
if err := a.DeleteToken(token); err != nil {
return nil, err
}
return ruser, nil
}
func (a *App) CreateUserWithInviteId(user *model.User, inviteId string) (*model.User, *model.AppError) {
if err := a.IsUserSignUpAllowed(); err != nil {
return nil, err
}
team, err := a.Srv.Store.Team().GetByInviteId(inviteId)
if err != nil {
return nil, err
}
if team.IsGroupConstrained() {
return nil, model.NewAppError("CreateUserWithInviteId", "app.team.invite_id.group_constrained.error", nil, "", http.StatusForbidden)
}
user.EmailVerified = false
ruser, err := a.CreateUser(user)
if err != nil {
return nil, err
}
if err := a.JoinUserToTeam(team, ruser, ""); err != nil {
return nil, err
}
a.AddDirectChannels(team.Id, ruser)
if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
return ruser, nil
}
func (a *App) CreateUserAsAdmin(user *model.User) (*model.User, *model.AppError) {
ruser, err := a.CreateUser(user)
if err != nil {
return nil, err
}
if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
return ruser, nil
}
func (a *App) CreateUserFromSignup(user *model.User) (*model.User, *model.AppError) {
if err := a.IsUserSignUpAllowed(); err != nil {
return nil, err
}
if !a.IsFirstUserAccount() && !*a.Config().TeamSettings.EnableOpenServer {
err := model.NewAppError("CreateUserFromSignup", "api.user.create_user.no_open_server", nil, "email="+user.Email, http.StatusForbidden)
return nil, err
}
user.EmailVerified = false
ruser, err := a.CreateUser(user)
if err != nil {
return nil, err
}
if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
return ruser, nil
}
func (a *App) IsUserSignUpAllowed() *model.AppError {
if !*a.Config().EmailSettings.EnableSignUpWithEmail || !*a.Config().TeamSettings.EnableUserCreation {
err := model.NewAppError("IsUserSignUpAllowed", "api.user.create_user.signup_email_disabled.app_error", nil, "", http.StatusNotImplemented)
return err
}
return nil
}
func (a *App) IsFirstUserAccount() bool {
if a.SessionCacheLength() == 0 {
count, err := a.Srv.Store.User().Count(model.UserCountOptions{IncludeDeleted: true})
if err != nil {
mlog.Error(fmt.Sprint(err))
return false
}
if count <= 0 {
return true
}
}
return false
}
// indexUser fetches the required information to index a user from the database and
// calls the elasticsearch interface method
func (a *App) indexUser(user *model.User) *model.AppError {
userTeams, err := a.Srv.Store.Team().GetTeamsByUserId(user.Id)
if err != nil {
return err
}
userTeamsIds := []string{}
for _, team := range userTeams {
userTeamsIds = append(userTeamsIds, team.Id)
}
userChannelMembers, err := a.Srv.Store.Channel().GetAllChannelMembersForUser(user.Id, false, true)
if err != nil {
return err
}
userChannelsIds := []string{}
for channelId := range userChannelMembers {
userChannelsIds = append(userChannelsIds, channelId)
}
return a.Elasticsearch.IndexUser(user, userTeamsIds, userChannelsIds)
}
func (a *App) indexUserFromId(userId string) *model.AppError {
user, err := a.GetUser(userId)
if err != nil {
return err
}
return a.indexUser(user)
}
// CreateUser creates a user and sets several fields of the returned User struct to
// their zero values.
func (a *App) CreateUser(user *model.User) (*model.User, *model.AppError) {
return a.createUserOrGuest(user, false)
}
// CreateGuest creates a guest and sets several fields of the returned User struct to
// their zero values.
func (a *App) CreateGuest(user *model.User) (*model.User, *model.AppError) {
return a.createUserOrGuest(user, true)
}
func (a *App) createUserOrGuest(user *model.User, guest bool) (*model.User, *model.AppError) {
user.Roles = model.SYSTEM_USER_ROLE_ID
if guest {
user.Roles = model.SYSTEM_GUEST_ROLE_ID
}
if !user.IsLDAPUser() && !user.IsSAMLUser() && !user.IsGuest() && !CheckUserDomain(user, *a.Config().TeamSettings.RestrictCreationToDomains) {
return nil, model.NewAppError("CreateUser", "api.user.create_user.accepted_domain.app_error", nil, "", http.StatusBadRequest)
}
if !user.IsLDAPUser() && !user.IsSAMLUser() && user.IsGuest() && !CheckUserDomain(user, *a.Config().GuestAccountsSettings.RestrictCreationToDomains) {
return nil, model.NewAppError("CreateUser", "api.user.create_user.accepted_domain.app_error", nil, "", http.StatusBadRequest)
}
// Below is a special case where the first user in the entire
// system is granted the system_admin role
count, err := a.Srv.Store.User().Count(model.UserCountOptions{IncludeDeleted: true})
if err != nil {
return nil, err
}
if count <= 0 {
user.Roles = model.SYSTEM_ADMIN_ROLE_ID + " " + model.SYSTEM_USER_ROLE_ID
}
if _, ok := utils.GetSupportedLocales()[user.Locale]; !ok {
user.Locale = *a.Config().LocalizationSettings.DefaultClientLocale
}
ruser, err := a.createUser(user)
if err != nil {
return nil, err
}
// This message goes to everyone, so the teamId, channelId and userId are irrelevant
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil)
message.Add("user_id", ruser.Id)
a.Publish(message)
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
a.Srv.Go(func() {
pluginContext := a.PluginContext()
pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.UserHasBeenCreated(pluginContext, user)
return true
}, plugin.UserHasBeenCreatedId)
})
}
if a.IsESIndexingEnabled() {
a.Srv.Go(func() {
if err := a.indexUser(user); err != nil {
mlog.Error("Encountered error indexing user", mlog.String("user_id", user.Id), mlog.Err(err))
}
})
}
return ruser, nil
}
func (a *App) createUser(user *model.User) (*model.User, *model.AppError) {
user.MakeNonNil()
if err := a.IsPasswordValid(user.Password); user.AuthService == "" && err != nil {
return nil, err
}
ruser, err := a.Srv.Store.User().Save(user)
if err != nil {
mlog.Error(fmt.Sprintf("Couldn't save the user err=%v", err))
return nil, err
}
if user.EmailVerified {
if err := a.VerifyUserEmail(ruser.Id, user.Email); err != nil {
mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", err))
}
}
pref := model.Preference{UserId: ruser.Id, Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, Name: ruser.Id, Value: "0"}
if err := a.Srv.Store.Preference().Save(&model.Preferences{pref}); err != nil {
mlog.Error(fmt.Sprintf("Encountered error saving tutorial preference, err=%v", err.Message))
}
ruser.Sanitize(map[string]bool{})
return ruser, nil
}
func (a *App) CreateOAuthUser(service string, userData io.Reader, teamId string) (*model.User, *model.AppError) {
if !*a.Config().TeamSettings.EnableUserCreation {
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_user.disabled.app_error", nil, "", http.StatusNotImplemented)
}
provider := einterfaces.GetOauthProvider(service)
if provider == nil {
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_oauth_user.not_available.app_error", map[string]interface{}{"Service": strings.Title(service)}, "", http.StatusNotImplemented)
}
user := provider.GetUserFromJson(userData)
if user == nil {
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_oauth_user.create.app_error", map[string]interface{}{"Service": service}, "", http.StatusInternalServerError)
}
suchan := make(chan store.StoreResult, 1)
euchan := make(chan store.StoreResult, 1)
go func() {
userByAuth, err := a.Srv.Store.User().GetByAuth(user.AuthData, service)
suchan <- store.StoreResult{Data: userByAuth, Err: err}
close(suchan)
}()
go func() {
userByEmail, err := a.Srv.Store.User().GetByEmail(user.Email)
euchan <- store.StoreResult{Data: userByEmail, Err: err}
close(euchan)
}()
found := true
count := 0
for found {
if found = a.IsUsernameTaken(user.Username); found {
user.Username = user.Username + strconv.Itoa(count)
count++
}
}
if result := <-suchan; result.Err == nil {
return result.Data.(*model.User), nil
}
if result := <-euchan; result.Err == nil {
authService := result.Data.(*model.User).AuthService
if authService == "" {
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error", map[string]interface{}{"Service": service, "Auth": model.USER_AUTH_SERVICE_EMAIL}, "email="+user.Email, http.StatusBadRequest)
}
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error", map[string]interface{}{"Service": service, "Auth": authService}, "email="+user.Email, http.StatusBadRequest)
}
user.EmailVerified = true
ruser, err := a.CreateUser(user)
if err != nil {
return nil, err
}
if len(teamId) > 0 {
err = a.AddUserToTeamByTeamId(teamId, user)
if err != nil {
return nil, err
}
err = a.AddDirectChannels(teamId, user)
if err != nil {
mlog.Error(err.Error())
}
}
return ruser, nil
}
// CheckEmailDomain checks that an email domain matches a list of space-delimited domains as a string.
func CheckEmailDomain(email string, domains string) bool {
if len(domains) == 0 {
return true
}
domainArray := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(strings.Replace(domains, "@", " ", -1), ",", " ", -1))))
for _, d := range domainArray {
if strings.HasSuffix(strings.ToLower(email), "@"+d) {
return true
}
}
return false
}
// CheckUserDomain checks that a user's email domain matches a list of space-delimited domains as a string.
func CheckUserDomain(user *model.User, domains string) bool {
return CheckEmailDomain(user.Email, domains)
}
// IsUsernameTaken checks if the username is already used by another user. Return false if the username is invalid.
func (a *App) IsUsernameTaken(name string) bool {
if !model.IsValidUsername(name) {
return false
}
if _, err := a.Srv.Store.User().GetByUsername(name); err != nil {
return false
}
return true
}
func (a *App) GetUser(userId string) (*model.User, *model.AppError) {
return a.Srv.Store.User().Get(userId)
}
func (a *App) GetUserByUsername(username string) (*model.User, *model.AppError) {
result, err := a.Srv.Store.User().GetByUsername(username)
if err != nil && err.Id == "store.sql_user.get_by_username.app_error" {
err.StatusCode = http.StatusNotFound
return nil, err
}
return result, nil
}
func (a *App) GetUserByEmail(email string) (*model.User, *model.AppError) {
user, err := a.Srv.Store.User().GetByEmail(email)
if err != nil {
if err.Id == "store.sql_user.missing_account.const" {
err.StatusCode = http.StatusNotFound
return nil, err
}
err.StatusCode = http.StatusBadRequest
return nil, err
}
return user, nil
}
func (a *App) GetUserByAuth(authData *string, authService string) (*model.User, *model.AppError) {
return a.Srv.Store.User().GetByAuth(authData, authService)
}
func (a *App) GetUsers(options *model.UserGetOptions) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetAllProfiles(options)
}
func (a *App) GetUsersPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError) {
users, err := a.GetUsers(options)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, asAdmin), nil
}
func (a *App) GetUsersEtag(restrictionsHash string) string {
return fmt.Sprintf("%v.%v.%v.%v", a.Srv.Store.User().GetEtagForAllProfiles(), a.Config().PrivacySettings.ShowFullName, a.Config().PrivacySettings.ShowEmailAddress, restrictionsHash)
}
func (a *App) GetUsersInTeam(options *model.UserGetOptions) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetProfiles(options)
}
func (a *App) GetUsersNotInTeam(teamId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetProfilesNotInTeam(teamId, groupConstrained, offset, limit, viewRestrictions)
}
func (a *App) GetUsersInTeamPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError) {
users, err := a.GetUsersInTeam(options)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, asAdmin), nil
}
func (a *App) GetUsersNotInTeamPage(teamId string, groupConstrained bool, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
users, err := a.GetUsersNotInTeam(teamId, groupConstrained, page*perPage, perPage, viewRestrictions)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, asAdmin), nil
}
func (a *App) GetUsersInTeamEtag(teamId string, restrictionsHash string) string {
return fmt.Sprintf("%v.%v.%v.%v", a.Srv.Store.User().GetEtagForProfiles(teamId), a.Config().PrivacySettings.ShowFullName, a.Config().PrivacySettings.ShowEmailAddress, restrictionsHash)
}
func (a *App) GetUsersNotInTeamEtag(teamId string, restrictionsHash string) string {
return fmt.Sprintf("%v.%v.%v.%v", a.Srv.Store.User().GetEtagForProfilesNotInTeam(teamId), a.Config().PrivacySettings.ShowFullName, a.Config().PrivacySettings.ShowEmailAddress, restrictionsHash)
}
func (a *App) GetUsersInChannel(channelId string, offset int, limit int) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetProfilesInChannel(channelId, offset, limit)
}
func (a *App) GetUsersInChannelByStatus(channelId string, offset int, limit int) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetProfilesInChannelByStatus(channelId, offset, limit)
}
func (a *App) GetUsersInChannelMap(channelId string, offset int, limit int, asAdmin bool) (map[string]*model.User, *model.AppError) {
users, err := a.GetUsersInChannel(channelId, offset, limit)
if err != nil {
return nil, err
}
userMap := make(map[string]*model.User, len(users))
for _, user := range users {
a.SanitizeProfile(user, asAdmin)
userMap[user.Id] = user
}
return userMap, nil
}
func (a *App) GetUsersInChannelPage(channelId string, page int, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
users, err := a.GetUsersInChannel(channelId, page*perPage, perPage)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, asAdmin), nil
}
func (a *App) GetUsersInChannelPageByStatus(channelId string, page int, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
users, err := a.GetUsersInChannelByStatus(channelId, page*perPage, perPage)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, asAdmin), nil
}
func (a *App) GetUsersNotInChannel(teamId string, channelId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, groupConstrained, offset, limit, viewRestrictions)
}
func (a *App) GetUsersNotInChannelMap(teamId string, channelId string, groupConstrained bool, offset int, limit int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) (map[string]*model.User, *model.AppError) {
users, err := a.GetUsersNotInChannel(teamId, channelId, groupConstrained, offset, limit, viewRestrictions)
if err != nil {
return nil, err
}
userMap := make(map[string]*model.User, len(users))
for _, user := range users {
a.SanitizeProfile(user, asAdmin)
userMap[user.Id] = user
}
return userMap, nil
}
func (a *App) GetUsersNotInChannelPage(teamId string, channelId string, groupConstrained bool, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
users, err := a.GetUsersNotInChannel(teamId, channelId, groupConstrained, page*perPage, perPage, viewRestrictions)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, asAdmin), nil
}
func (a *App) GetUsersWithoutTeamPage(page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
users, err := a.GetUsersWithoutTeam(page*perPage, perPage, viewRestrictions)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, asAdmin), nil
}
func (a *App) GetUsersWithoutTeam(offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetProfilesWithoutTeam(offset, limit, viewRestrictions)
}
// GetTeamGroupUsers returns the users who are associated to the team via GroupTeams and GroupMembers.
func (a *App) GetTeamGroupUsers(teamID string) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetTeamGroupUsers(teamID)
}
// GetChannelGroupUsers returns the users who are associated to the channel via GroupChannels and GroupMembers.
func (a *App) GetChannelGroupUsers(channelID string) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetChannelGroupUsers(channelID)
}
func (a *App) GetUsersByIds(userIds []string, options *store.UserGetByIdsOpts) ([]*model.User, *model.AppError) {
allowFromCache := options.ViewRestrictions == nil
users, err := a.Srv.Store.User().GetProfileByIds(userIds, options, allowFromCache)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, options.IsAdmin), nil
}
func (a *App) GetUsersByGroupChannelIds(channelIds []string, asAdmin bool) (map[string][]*model.User, *model.AppError) {
usersByChannelId, err := a.Srv.Store.User().GetProfileByGroupChannelIdsForUser(a.Session.UserId, channelIds)
if err != nil {
return nil, err
}
for channelId, userList := range usersByChannelId {
usersByChannelId[channelId] = a.sanitizeProfiles(userList, asAdmin)
}
return usersByChannelId, nil
}
func (a *App) GetUsersByUsernames(usernames []string, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
users, err := a.Srv.Store.User().GetProfilesByUsernames(usernames, viewRestrictions)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, asAdmin), nil
}
func (a *App) sanitizeProfiles(users []*model.User, asAdmin bool) []*model.User {
for _, u := range users {
a.SanitizeProfile(u, asAdmin)
}
return users
}
func (a *App) GenerateMfaSecret(userId string) (*model.MfaSecret, *model.AppError) {
user, err := a.GetUser(userId)
if err != nil {
return nil, err
}
mfaService := mfa.New(a, a.Srv.Store)
secret, img, err := mfaService.GenerateSecret(user)
if err != nil {
return nil, err
}
mfaSecret := &model.MfaSecret{Secret: secret, QRCode: b64.StdEncoding.EncodeToString(img)}
return mfaSecret, nil
}
func (a *App) ActivateMfa(userId, token string) *model.AppError {
user, err := a.Srv.Store.User().Get(userId)
if err != nil {
return err
}
if len(user.AuthService) > 0 && user.AuthService != model.USER_AUTH_SERVICE_LDAP {
return model.NewAppError("ActivateMfa", "api.user.activate_mfa.email_and_ldap_only.app_error", nil, "", http.StatusBadRequest)
}
mfaService := mfa.New(a, a.Srv.Store)
if err := mfaService.Activate(user, token); err != nil {
return err
}
return nil
}
func (a *App) DeactivateMfa(userId string) *model.AppError {
mfaService := mfa.New(a, a.Srv.Store)
if err := mfaService.Deactivate(userId); err != nil {
return err
}
return nil
}
func CreateProfileImage(username string, userId string, initialFont string) ([]byte, *model.AppError) {
colors := []color.NRGBA{
{197, 8, 126, 255},
{227, 207, 18, 255},
{28, 181, 105, 255},
{35, 188, 224, 255},
{116, 49, 196, 255},
{197, 8, 126, 255},
{197, 19, 19, 255},
{250, 134, 6, 255},
{227, 207, 18, 255},
{123, 201, 71, 255},
{28, 181, 105, 255},
{35, 188, 224, 255},
{116, 49, 196, 255},
{197, 8, 126, 255},
{197, 19, 19, 255},
{250, 134, 6, 255},
{227, 207, 18, 255},
{123, 201, 71, 255},
{28, 181, 105, 255},
{35, 188, 224, 255},
{116, 49, 196, 255},
{197, 8, 126, 255},
{197, 19, 19, 255},
{250, 134, 6, 255},
{227, 207, 18, 255},
{123, 201, 71, 255},
}
h := fnv.New32a()
h.Write([]byte(userId))
seed := h.Sum32()
initial := string(strings.ToUpper(username)[0])
font, err := getFont(initialFont)
if err != nil {
return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error(), http.StatusInternalServerError)
}
color := colors[int64(seed)%int64(len(colors))]
dstImg := image.NewRGBA(image.Rect(0, 0, IMAGE_PROFILE_PIXEL_DIMENSION, IMAGE_PROFILE_PIXEL_DIMENSION))
srcImg := image.White
draw.Draw(dstImg, dstImg.Bounds(), &image.Uniform{color}, image.ZP, draw.Src)
size := float64(IMAGE_PROFILE_PIXEL_DIMENSION / 2)
c := freetype.NewContext()
c.SetFont(font)
c.SetFontSize(size)
c.SetClip(dstImg.Bounds())
c.SetDst(dstImg)
c.SetSrc(srcImg)
pt := freetype.Pt(IMAGE_PROFILE_PIXEL_DIMENSION/5, IMAGE_PROFILE_PIXEL_DIMENSION*2/3)
_, err = c.DrawString(initial, pt)
if err != nil {
return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.initial.app_error", nil, err.Error(), http.StatusInternalServerError)
}
buf := new(bytes.Buffer)
if imgErr := png.Encode(buf, dstImg); imgErr != nil {
return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.encode.app_error", nil, imgErr.Error(), http.StatusInternalServerError)
}
return buf.Bytes(), nil
}
func getFont(initialFont string) (*truetype.Font, error) {
// Some people have the old default font still set, so just treat that as if they're using the new default
if initialFont == "luximbi.ttf" {
initialFont = "nunito-bold.ttf"
}
fontDir, _ := fileutils.FindDir("fonts")
fontBytes, err := ioutil.ReadFile(filepath.Join(fontDir, initialFont))
if err != nil {
return nil, err
}
return freetype.ParseFont(fontBytes)
}
func (a *App) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
if len(*a.Config().FileSettings.DriverName) == 0 {
img, appErr := a.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
return img, false, nil
}
path := "users/" + user.Id + "/profile.png"
data, err := a.ReadFile(path)
if err != nil {
img, appErr := a.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
if user.LastPictureUpdate == 0 {
if _, err := a.WriteFile(bytes.NewReader(img), path); err != nil {
return nil, false, err
}
}
return img, true, nil
}
return data, false, nil
}
func (a *App) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) {
var img []byte
var appErr *model.AppError
if user.IsBot {
img = model.BotDefaultImage
appErr = nil
} else {
img, appErr = CreateProfileImage(user.Username, user.Id, *a.Config().FileSettings.InitialFont)
}
if appErr != nil {
return nil, appErr
}
return img, nil
}
func (a *App) SetDefaultProfileImage(user *model.User) *model.AppError {
img, appErr := a.GetDefaultProfileImage(user)
if appErr != nil {
return appErr
}
path := "users/" + user.Id + "/profile.png"
if _, err := a.WriteFile(bytes.NewReader(img), path); err != nil {
return err
}
if err := a.Srv.Store.User().ResetLastPictureUpdate(user.Id); err != nil {
mlog.Error(err.Error())
}
a.InvalidateCacheForUser(user.Id)
updatedUser, appErr := a.GetUser(user.Id)
if appErr != nil {
mlog.Error(fmt.Sprintf("Error in getting users profile for id=%v forcing logout", user.Id), mlog.String("user_id", user.Id))
return nil
}
options := a.Config().GetSanitizeOptions()
updatedUser.SanitizeProfile(options)
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", updatedUser)
a.Publish(message)
return nil
}
func (a *App) SetProfileImage(userId string, imageData *multipart.FileHeader) *model.AppError {
file, err := imageData.Open()
if err != nil {
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.open.app_error", nil, err.Error(), http.StatusBadRequest)
}
defer file.Close()
return a.SetProfileImageFromMultiPartFile(userId, file)
}
func (a *App) SetProfileImageFromMultiPartFile(userId string, file multipart.File) *model.AppError {
// Decode image config first to check dimensions before loading the whole thing into memory later on
config, _, err := image.DecodeConfig(file)
if err != nil {
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.decode_config.app_error", nil, err.Error(), http.StatusBadRequest)
}
if config.Width*config.Height > model.MaxImageSize {
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.too_large.app_error", nil, "", http.StatusBadRequest)
}
file.Seek(0, 0)
return a.SetProfileImageFromFile(userId, file)
}
func (a *App) SetProfileImageFromFile(userId string, file io.Reader) *model.AppError {
// Decode image into Image object
img, _, err := image.Decode(file)
if err != nil {
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.decode.app_error", nil, err.Error(), http.StatusBadRequest)
}
orientation, _ := getImageOrientation(file)
img = makeImageUpright(img, orientation)
// Scale profile image
profileWidthAndHeight := 128
img = imaging.Fill(img, profileWidthAndHeight, profileWidthAndHeight, imaging.Center, imaging.Lanczos)
buf := new(bytes.Buffer)
err = png.Encode(buf, img)
if err != nil {
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.encode.app_error", nil, err.Error(), http.StatusInternalServerError)
}
path := "users/" + userId + "/profile.png"
if _, err := a.WriteFile(buf, path); err != nil {
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "", http.StatusInternalServerError)
}
if err := a.Srv.Store.User().UpdateLastPictureUpdate(userId); err != nil {
mlog.Error(err.Error())
}
a.invalidateUserCacheAndPublish(userId)
return nil
}
func (a *App) UpdatePasswordAsUser(userId, currentPassword, newPassword string) *model.AppError {
user, err := a.GetUser(userId)
if err != nil {
return err
}
if user == nil {
err = model.NewAppError("updatePassword", "api.user.update_password.valid_account.app_error", nil, "", http.StatusBadRequest)
return err
}
if user.AuthData != nil && *user.AuthData != "" {
err = model.NewAppError("updatePassword", "api.user.update_password.oauth.app_error", nil, "auth_service="+user.AuthService, http.StatusBadRequest)
return err
}
if err := a.DoubleCheckPassword(user, currentPassword); err != nil {
if err.Id == "api.user.check_user_password.invalid.app_error" {
err = model.NewAppError("updatePassword", "api.user.update_password.incorrect.app_error", nil, "", http.StatusBadRequest)
}
return err
}
T := utils.GetUserTranslations(user.Locale)
return a.UpdatePasswordSendEmail(user, newPassword, T("api.user.update_password.menu"))
}
func (a *App) userDeactivated(user *model.User) *model.AppError {
if err := a.RevokeAllSessions(user.Id); err != nil {
return err
}
a.SetStatusOffline(user.Id, false)
if *a.Config().ServiceSettings.DisableBotsWhenOwnerIsDeactivated {
a.disableUserBots(user.Id)
}
return nil
}
func (a *App) invalidateUserChannelMembersCaches(user *model.User) *model.AppError {
teamsForUser, err := a.GetTeamsForUser(user.Id)
if err != nil {
return err
}
for _, team := range teamsForUser {
channelsForUser, err := a.GetChannelsForUser(team.Id, user.Id, false)
if err != nil {
return err
}
for _, channel := range *channelsForUser {
a.InvalidateCacheForChannelMembers(channel.Id)
}
}
return nil
}
func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.AppError) {
if active {
user.DeleteAt = 0
} else {
user.DeleteAt = model.GetMillis()
}
userUpdate, err := a.Srv.Store.User().Update(user, true)
if err != nil {
return nil, err
}
ruser := userUpdate.New
if !active {
if err := a.userDeactivated(ruser); err != nil {
return nil, err
}
}
a.invalidateUserChannelMembersCaches(user)
a.sendUpdatedUserEvent(*ruser)
return ruser, nil
}
func (a *App) GetSanitizeOptions(asAdmin bool) map[string]bool {
options := a.Config().GetSanitizeOptions()
if asAdmin {
options["email"] = true
options["fullname"] = true
options["authservice"] = true
}
return options
}
func (a *App) SanitizeProfile(user *model.User, asAdmin bool) {
options := a.GetSanitizeOptions(asAdmin)
user.SanitizeProfile(options)
}
func (a *App) UpdateUserAsUser(user *model.User, asAdmin bool) (*model.User, *model.AppError) {
updatedUser, err := a.UpdateUser(user, true)
if err != nil {
return nil, err
}
a.sendUpdatedUserEvent(*updatedUser)
return updatedUser, nil
}
func (a *App) PatchUser(userId string, patch *model.UserPatch, asAdmin bool) (*model.User, *model.AppError) {
user, err := a.GetUser(userId)
if err != nil {
return nil, err
}
user.Patch(patch)
updatedUser, err := a.UpdateUser(user, true)
if err != nil {
return nil, err
}
a.sendUpdatedUserEvent(*updatedUser)
return updatedUser, nil
}
func (a *App) UpdateUserAuth(userId string, userAuth *model.UserAuth) (*model.UserAuth, *model.AppError) {
if userAuth.AuthData == nil || *userAuth.AuthData == "" || userAuth.AuthService == "" {
userAuth.AuthData = nil
userAuth.AuthService = ""
if err := a.IsPasswordValid(userAuth.Password); err != nil {
return nil, err
}
password := model.HashPassword(userAuth.Password)
if err := a.Srv.Store.User().UpdatePassword(userId, password); err != nil {
return nil, err
}
} else {
userAuth.Password = ""
if _, err := a.Srv.Store.User().UpdateAuthData(userId, userAuth.AuthService, userAuth.AuthData, "", false); err != nil {
return nil, err
}
}
return userAuth, nil
}
func (a *App) sendUpdatedUserEvent(user model.User) {
adminCopyOfUser := user.DeepCopy()
a.SanitizeProfile(adminCopyOfUser, true)
adminMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
adminMessage.Add("user", &adminCopyOfUser)
adminMessage.Broadcast.ContainsSensitiveData = true
a.Publish(adminMessage)
a.SanitizeProfile(&user, false)
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", &user)
message.Broadcast.ContainsSanitizedData = true
a.Publish(message)
}
func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) {
prev, err := a.Srv.Store.User().Get(user.Id)
if err != nil {
return nil, err
}
if !CheckUserDomain(user, *a.Config().TeamSettings.RestrictCreationToDomains) {
if !prev.IsGuest() && !prev.IsLDAPUser() && !prev.IsSAMLUser() && user.Email != prev.Email {
return nil, model.NewAppError("UpdateUser", "api.user.update_user.accepted_domain.app_error", nil, "", http.StatusBadRequest)
}
}
if !CheckUserDomain(user, *a.Config().GuestAccountsSettings.RestrictCreationToDomains) {
if prev.IsGuest() && !prev.IsLDAPUser() && !prev.IsSAMLUser() && user.Email != prev.Email {
return nil, model.NewAppError("UpdateUser", "api.user.update_user.accepted_guest_domain.app_error", nil, "", http.StatusBadRequest)
}
}
// Don't set new eMail on user account if email verification is required, this will be done as a post-verification action
// to avoid users being able to set non-controlled eMails as their account email
newEmail := ""
if *a.Config().EmailSettings.RequireEmailVerification && prev.Email != user.Email {
newEmail = user.Email
_, err = a.GetUserByEmail(newEmail)
if err == nil {
return nil, model.NewAppError("UpdateUser", "store.sql_user.update.email_taken.app_error", nil, "user_id="+user.Id, http.StatusBadRequest)
}
// When a bot is created, prev.Email will be an autogenerated faked email,
// which will not match a CLI email input during bot to user conversions.
// To update a bot users email, do not set the email to the faked email
// stored in prev.Email. Allow using the email defined in the CLI
if !user.IsBot {
user.Email = prev.Email
}
}
userUpdate, err := a.Srv.Store.User().Update(user, false)
if err != nil {
return nil, err
}
if sendNotifications {
if userUpdate.New.Email != userUpdate.Old.Email || newEmail != "" {
if *a.Config().EmailSettings.RequireEmailVerification {
a.Srv.Go(func() {
if err := a.SendEmailVerification(userUpdate.New, newEmail); err != nil {
mlog.Error(err.Error())
}
})
} else {
a.Srv.Go(func() {
if err := a.SendEmailChangeEmail(userUpdate.Old.Email, userUpdate.New.Email, userUpdate.New.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
})
}
}
if userUpdate.New.Username != userUpdate.Old.Username {
a.Srv.Go(func() {
if err := a.SendChangeUsernameEmail(userUpdate.Old.Username, userUpdate.New.Username, userUpdate.New.Email, userUpdate.New.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
})
}
}
a.InvalidateCacheForUser(user.Id)
if a.IsESIndexingEnabled() {
a.Srv.Go(func() {
if err := a.indexUser(user); err != nil {
mlog.Error("Encountered error indexing user", mlog.String("user_id", user.Id), mlog.Err(err))
}
})
}
return userUpdate.New, nil
}
func (a *App) UpdateUserActive(userId string, active bool) *model.AppError {
user, err := a.GetUser(userId)
if err != nil {
return err
}
if _, err = a.UpdateActive(user, active); err != nil {
return err
}
return nil
}
func (a *App) UpdateUserNotifyProps(userId string, props map[string]string) (*model.User, *model.AppError) {
user, err := a.GetUser(userId)
if err != nil {
return nil, err
}
user.NotifyProps = props
ruser, err := a.UpdateUser(user, true)
if err != nil {
return nil, err
}
return ruser, nil
}
func (a *App) UpdateMfa(activate bool, userId, token string) *model.AppError {
if activate {
if err := a.ActivateMfa(userId, token); err != nil {
return err
}
} else {
if err := a.DeactivateMfa(userId); err != nil {
return err
}
}
a.Srv.Go(func() {
user, err := a.GetUser(userId)
if err != nil {
mlog.Error(err.Error())
return
}
if err := a.SendMfaChangeEmail(user.Email, activate, user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
})
return nil
}
func (a *App) UpdatePasswordByUserIdSendEmail(userId, newPassword, method string) *model.AppError {
user, err := a.GetUser(userId)
if err != nil {
return err
}
return a.UpdatePasswordSendEmail(user, newPassword, method)
}
func (a *App) UpdatePassword(user *model.User, newPassword string) *model.AppError {
if err := a.IsPasswordValid(newPassword); err != nil {
return err
}
hashedPassword := model.HashPassword(newPassword)
if err := a.Srv.Store.User().UpdatePassword(user.Id, hashedPassword); err != nil {
return model.NewAppError("UpdatePassword", "api.user.update_password.failed.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return nil
}
func (a *App) UpdatePasswordSendEmail(user *model.User, newPassword, method string) *model.AppError {
if err := a.UpdatePassword(user, newPassword); err != nil {
return err
}
a.Srv.Go(func() {
if err := a.SendPasswordChangeEmail(user.Email, method, user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
})
return nil
}
func (a *App) ResetPasswordFromToken(userSuppliedTokenString, newPassword string) *model.AppError {
token, err := a.GetPasswordRecoveryToken(userSuppliedTokenString)
if err != nil {
return err
}
if model.GetMillis()-token.CreateAt >= PASSWORD_RECOVER_EXPIRY_TIME {
return model.NewAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "", http.StatusBadRequest)
}
tokenData := struct {
UserId string
Email string
}{}
err2 := json.Unmarshal([]byte(token.Extra), &tokenData)
if err2 != nil {
return model.NewAppError("resetPassword", "api.user.reset_password.token_parse.error", nil, "", http.StatusInternalServerError)
}
user, err := a.GetUser(tokenData.UserId)
if err != nil {
return err
}
if user.Email != tokenData.Email {
return model.NewAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "", http.StatusBadRequest)
}
if user.IsSSOUser() {
return model.NewAppError("ResetPasswordFromCode", "api.user.reset_password.sso.app_error", nil, "userId="+user.Id, http.StatusBadRequest)
}
T := utils.GetUserTranslations(user.Locale)
if err := a.UpdatePasswordSendEmail(user, newPassword, T("api.user.reset_password.method")); err != nil {
return err
}
if err := a.DeleteToken(token); err != nil {
mlog.Error(err.Error())
}
return nil
}
func (a *App) SendPasswordReset(email string, siteURL string) (bool, *model.AppError) {
user, err := a.GetUserByEmail(email)
if err != nil {
return false, nil
}
if user.AuthData != nil && len(*user.AuthData) != 0 {
return false, model.NewAppError("SendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id, http.StatusBadRequest)
}
token, err := a.CreatePasswordRecoveryToken(user.Id, user.Email)
if err != nil {
return false, err
}
return a.SendPasswordResetEmail(user.Email, token, user.Locale, siteURL)
}
func (a *App) CreatePasswordRecoveryToken(userId, email string) (*model.Token, *model.AppError) {
tokenExtra := struct {
UserId string
Email string
}{
userId,
email,
}
jsonData, err := json.Marshal(tokenExtra)
if err != nil {
return nil, model.NewAppError("CreatePasswordRecoveryToken", "api.user.create_password_token.error", nil, "", http.StatusInternalServerError)
}
token := model.NewToken(TOKEN_TYPE_PASSWORD_RECOVERY, string(jsonData))
if err := a.Srv.Store.Token().Save(token); err != nil {
return nil, err
}
return token, nil
}
func (a *App) GetPasswordRecoveryToken(token string) (*model.Token, *model.AppError) {
rtoken, err := a.Srv.Store.Token().GetByToken(token)
if err != nil {
return nil, model.NewAppError("GetPasswordRecoveryToken", "api.user.reset_password.invalid_link.app_error", nil, err.Error(), http.StatusBadRequest)
}
if rtoken.Type != TOKEN_TYPE_PASSWORD_RECOVERY {
return nil, model.NewAppError("GetPasswordRecoveryToken", "api.user.reset_password.broken_token.app_error", nil, "", http.StatusBadRequest)
}
return rtoken, nil
}
func (a *App) DeleteToken(token *model.Token) *model.AppError {
return a.Srv.Store.Token().Delete(token.Token)
}
func (a *App) UpdateUserRoles(userId string, newRoles string, sendWebSocketEvent bool) (*model.User, *model.AppError) {
user, err := a.GetUser(userId)
if err != nil {
err.StatusCode = http.StatusBadRequest
return nil, err
}
if err := a.CheckRolesExist(strings.Fields(newRoles)); err != nil {
return nil, err
}
user.Roles = newRoles
uchan := make(chan store.StoreResult, 1)
go func() {
userUpdate, err := a.Srv.Store.User().Update(user, true)
uchan <- store.StoreResult{Data: userUpdate, Err: err}
close(uchan)
}()
schan := make(chan store.StoreResult, 1)
go func() {
userId, err := a.Srv.Store.Session().UpdateRoles(user.Id, newRoles)
schan <- store.StoreResult{Data: userId, Err: err}
close(schan)
}()
result := <-uchan
if result.Err != nil {
return nil, result.Err
}
ruser := result.Data.(*model.UserUpdate).New
if result := <-schan; result.Err != nil {
// soft error since the user roles were still updated
mlog.Error(fmt.Sprint(result.Err))
}
a.ClearSessionCacheForUser(user.Id)
if sendWebSocketEvent {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_ROLE_UPDATED, "", "", user.Id, nil)
message.Add("user_id", user.Id)
message.Add("roles", newRoles)
a.Publish(message)
}
return ruser, nil
}
func (a *App) PermanentDeleteUser(user *model.User) *model.AppError {
mlog.Warn(fmt.Sprintf("Attempting to permanently delete account %v id=%v", user.Email, user.Id), mlog.String("user_id", user.Id))
if user.IsInRole(model.SYSTEM_ADMIN_ROLE_ID) {
mlog.Warn(fmt.Sprintf("You are deleting %v that is a system administrator. You may need to set another account as the system administrator using the command line tools.", user.Email))
}
if _, err := a.UpdateActive(user, false); err != nil {
return err
}
if err := a.Srv.Store.Session().PermanentDeleteSessionsByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.UserAccessToken().DeleteAllForUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.OAuth().PermanentDeleteAuthDataByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.Webhook().PermanentDeleteIncomingByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.Webhook().PermanentDeleteOutgoingByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.Command().PermanentDeleteByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.Preference().PermanentDeleteByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.Channel().PermanentDeleteMembersByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.Post().PermanentDeleteByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.Bot().PermanentDelete(user.Id); err != nil {
return err
}
infos, err := a.Srv.Store.FileInfo().GetForUser(user.Id)
if err != nil {
mlog.Warn("Error getting file list for user from FileInfoStore")
}
for _, info := range infos {
res, err := a.FileExists(info.Path)
if err != nil {
mlog.Warn(
"Error checking existence of file",
mlog.String("path", info.Path),
mlog.Err(err),
)
continue
}
if !res {
mlog.Warn("File not found", mlog.String("path", info.Path))
continue
}
err = a.RemoveFile(info.Path)
if err != nil {
mlog.Warn(
"Unable to remove file",
mlog.String("path", info.Path),
mlog.Err(err),
)
}
}
if _, err := a.Srv.Store.FileInfo().PermanentDeleteByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.User().PermanentDelete(user.Id); err != nil {
return err
}
if err := a.Srv.Store.Audit().PermanentDeleteByUser(user.Id); err != nil {
return err
}
if err := a.Srv.Store.Team().RemoveAllMembersByUser(user.Id); err != nil {
return err
}
mlog.Warn(fmt.Sprintf("Permanently deleted account %v id=%v", user.Email, user.Id), mlog.String("user_id", user.Id))
if a.IsESIndexingEnabled() {
a.Srv.Go(func() {
if err := a.Elasticsearch.DeleteUser(user); err != nil {
mlog.Error("Encountered error deleting user", mlog.String("user_id", user.Id), mlog.Err(err))
}
})
}
return nil
}
func (a *App) PermanentDeleteAllUsers() *model.AppError {
users, err := a.Srv.Store.User().GetAll()
if err != nil {
return err
}
for _, user := range users {
a.PermanentDeleteUser(user)
}
return nil
}
func (a *App) SendEmailVerification(user *model.User, newEmail string) *model.AppError {
token, err := a.CreateVerifyEmailToken(user.Id, newEmail)
if err != nil {
return err
}
if _, err := a.GetStatus(user.Id); err != nil {
return a.SendVerifyEmail(newEmail, user.Locale, a.GetSiteURL(), token.Token)
}
return a.SendEmailChangeVerifyEmail(newEmail, user.Locale, a.GetSiteURL(), token.Token)
}
func (a *App) VerifyEmailFromToken(userSuppliedTokenString string) *model.AppError {
token, err := a.GetVerifyEmailToken(userSuppliedTokenString)
if err != nil {
return err
}
if model.GetMillis()-token.CreateAt >= PASSWORD_RECOVER_EXPIRY_TIME {
return model.NewAppError("VerifyEmailFromToken", "api.user.verify_email.link_expired.app_error", nil, "", http.StatusBadRequest)
}
tokenData := struct {
UserId string
Email string
}{}
err2 := json.Unmarshal([]byte(token.Extra), &tokenData)
if err2 != nil {
return model.NewAppError("VerifyEmailFromToken", "api.user.verify_email.token_parse.error", nil, "", http.StatusInternalServerError)
}
user, err := a.GetUser(tokenData.UserId)
if err != nil {
return err
}
if err := a.VerifyUserEmail(tokenData.UserId, tokenData.Email); err != nil {
return err
}
if user.Email != tokenData.Email {
a.Srv.Go(func() {
if err := a.SendEmailChangeEmail(user.Email, tokenData.Email, user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
})
}
if err := a.DeleteToken(token); err != nil {
mlog.Error(err.Error())
}
return nil
}
func (a *App) CreateVerifyEmailToken(userId string, newEmail string) (*model.Token, *model.AppError) {
tokenExtra := struct {
UserId string
Email string
}{
userId,
newEmail,
}
jsonData, err := json.Marshal(tokenExtra)
if err != nil {
return nil, model.NewAppError("CreateVerifyEmailToken", "api.user.create_email_token.error", nil, "", http.StatusInternalServerError)
}
token := model.NewToken(TOKEN_TYPE_VERIFY_EMAIL, string(jsonData))
if err := a.Srv.Store.Token().Save(token); err != nil {
return nil, err
}
return token, nil
}
func (a *App) GetVerifyEmailToken(token string) (*model.Token, *model.AppError) {
rtoken, err := a.Srv.Store.Token().GetByToken(token)
if err != nil {
return nil, model.NewAppError("GetVerifyEmailToken", "api.user.verify_email.bad_link.app_error", nil, err.Error(), http.StatusBadRequest)
}
if rtoken.Type != TOKEN_TYPE_VERIFY_EMAIL {
return nil, model.NewAppError("GetVerifyEmailToken", "api.user.verify_email.broken_token.app_error", nil, "", http.StatusBadRequest)
}
return rtoken, nil
}
// GetTotalUsersStats is used for the DM list total
func (a *App) GetTotalUsersStats(viewRestrictions *model.ViewUsersRestrictions) (*model.UsersStats, *model.AppError) {
count, err := a.Srv.Store.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
ViewRestrictions: viewRestrictions,
})
if err != nil {
return nil, err
}
stats := &model.UsersStats{
TotalUsersCount: count,
}
return stats, nil
}
func (a *App) VerifyUserEmail(userId, email string) *model.AppError {
_, err := a.Srv.Store.User().VerifyEmail(userId, email)
if err != nil {
return err
}
user, err := a.GetUser(userId)
if err != nil {
return err
}
a.sendUpdatedUserEvent(*user)
return nil
}
func (a *App) SearchUsers(props *model.UserSearch, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
if props.WithoutTeam {
return a.SearchUsersWithoutTeam(props.Term, options)
}
if props.InChannelId != "" {
return a.SearchUsersInChannel(props.InChannelId, props.Term, options)
}
if props.NotInChannelId != "" {
return a.SearchUsersNotInChannel(props.TeamId, props.NotInChannelId, props.Term, options)
}
if props.NotInTeamId != "" {
return a.SearchUsersNotInTeam(props.NotInTeamId, props.Term, options)
}
return a.SearchUsersInTeam(props.TeamId, props.Term, options)
}
func (a *App) SearchUsersInChannel(channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
term = strings.TrimSpace(term)
users, err := a.Srv.Store.User().SearchInChannel(channelId, term, options)
if err != nil {
return nil, err
}
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
func (a *App) SearchUsersNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
term = strings.TrimSpace(term)
users, err := a.Srv.Store.User().SearchNotInChannel(teamId, channelId, term, options)
if err != nil {
return nil, err
}
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
func (a *App) esSearchUsersInTeam(teamId, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
listOfAllowedChannels, err := a.GetViewUsersRestrictionsForTeam(a.Session.UserId, teamId)
if err != nil {
return nil, err
}
if listOfAllowedChannels != nil && len(listOfAllowedChannels) == 0 {
return []*model.User{}, nil
}
usersIds, err := a.Elasticsearch.SearchUsersInTeam(teamId, listOfAllowedChannels, term, options)
if err != nil {
return nil, err
}
users, err := a.Srv.Store.User().GetProfileByIds(usersIds, nil, false)
if err != nil {
return nil, err
}
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
func (a *App) SearchUsersInTeam(teamId, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
var users []*model.User
var err *model.AppError
term = strings.TrimSpace(term)
if a.IsESAutocompletionEnabled() {
users, err = a.esSearchUsersInTeam(teamId, term, options)
if err != nil {
mlog.Error("Encountered error on SearchUsersInTeam through Elasticsearch. Falling back to default search.", mlog.Err(err))
}
}
if !a.IsESAutocompletionEnabled() || err != nil {
users, err = a.Srv.Store.User().Search(teamId, term, options)
if err != nil {
return nil, err
}
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
}
return users, nil
}
func (a *App) SearchUsersNotInTeam(notInTeamId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
term = strings.TrimSpace(term)
users, err := a.Srv.Store.User().SearchNotInTeam(notInTeamId, term, options)
if err != nil {
return nil, err
}
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
func (a *App) SearchUsersWithoutTeam(term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
term = strings.TrimSpace(term)
users, err := a.Srv.Store.User().SearchWithoutTeam(term, options)
if err != nil {
return nil, err
}
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
func (a *App) esAutocompleteUsersInChannel(teamId, channelId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, *model.AppError) {
listOfAllowedChannels, err := a.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions)
if err != nil {
return nil, err
}
if len(listOfAllowedChannels) == 0 {
return &model.UserAutocompleteInChannel{}, nil
}
uchanIds := []string{}
nuchanIds := []string{}
if !strings.Contains(strings.Join(listOfAllowedChannels, "."), channelId) {
nuchanIds, err = a.Elasticsearch.SearchUsersInTeam(teamId, listOfAllowedChannels, term, options)
} else {
uchanIds, nuchanIds, err = a.Elasticsearch.SearchUsersInChannel(teamId, channelId, listOfAllowedChannels, term, options)
}
if err != nil {
return nil, err
}
uchan := make(chan store.StoreResult, 1)
go func() {
users, err := a.Srv.Store.User().GetProfileByIds(uchanIds, nil, false)
uchan <- store.StoreResult{Data: users, Err: err}
close(uchan)
}()
nuchan := make(chan store.StoreResult, 1)
go func() {
users, err := a.Srv.Store.User().GetProfileByIds(nuchanIds, nil, false)
nuchan <- store.StoreResult{Data: users, Err: err}
close(nuchan)
}()
autocomplete := &model.UserAutocompleteInChannel{}
result := <-uchan
if result.Err != nil {
return nil, result.Err
}
users := result.Data.([]*model.User)
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
autocomplete.InChannel = users
result = <-nuchan
if result.Err != nil {
return nil, result.Err
}
users = result.Data.([]*model.User)
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
autocomplete.OutOfChannel = users
return autocomplete, nil
}
func (a *App) AutocompleteUsersInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, *model.AppError) {
var autocomplete *model.UserAutocompleteInChannel
var err *model.AppError
term = strings.TrimSpace(term)
if a.IsESAutocompletionEnabled() {
autocomplete, err = a.esAutocompleteUsersInChannel(teamId, channelId, term, options)
if err != nil {
mlog.Error("Encountered error on AutocompleteUsersInChannel through Elasticsearch. Falling back to default autocompletion.", mlog.Err(err))
}
}
if !a.IsESAutocompletionEnabled() || err != nil {
autocomplete = &model.UserAutocompleteInChannel{}
uchan := make(chan store.StoreResult, 1)
go func() {
users, err := a.Srv.Store.User().SearchInChannel(channelId, term, options)
uchan <- store.StoreResult{Data: users, Err: err}
close(uchan)
}()
nuchan := make(chan store.StoreResult, 1)
go func() {
users, err := a.Srv.Store.User().SearchNotInChannel(teamId, channelId, term, options)
nuchan <- store.StoreResult{Data: users, Err: err}
close(nuchan)
}()
result := <-uchan
if result.Err != nil {
return nil, result.Err
}
users := result.Data.([]*model.User)
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
autocomplete.InChannel = users
result = <-nuchan
if result.Err != nil {
return nil, result.Err
}
users = result.Data.([]*model.User)
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
autocomplete.OutOfChannel = users
}
return autocomplete, nil
}
func (a *App) esAutocompleteUsersInTeam(teamId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInTeam, *model.AppError) {
listOfAllowedChannels, err := a.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions)
if err != nil {
return nil, err
}
if len(listOfAllowedChannels) == 0 {
return &model.UserAutocompleteInTeam{}, nil
}
usersIds, err := a.Elasticsearch.SearchUsersInTeam(teamId, listOfAllowedChannels, term, options)
if err != nil {
return nil, err
}
users, err := a.Srv.Store.User().GetProfileByIds(usersIds, nil, false)
if err != nil {
return nil, err
}
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
autocomplete := &model.UserAutocompleteInTeam{}
autocomplete.InTeam = users
return autocomplete, nil
}
func (a *App) AutocompleteUsersInTeam(teamId string, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInTeam, *model.AppError) {
var autocomplete *model.UserAutocompleteInTeam
var err *model.AppError
term = strings.TrimSpace(term)
if a.IsESAutocompletionEnabled() {
autocomplete, err = a.esAutocompleteUsersInTeam(teamId, term, options)
if err != nil {
mlog.Error("Encountered error on AutocompleteUsersInTeam through Elasticsearch. Falling back to default autocompletion.", mlog.Err(err))
}
}
if !a.IsESAutocompletionEnabled() || err != nil {
autocomplete = &model.UserAutocompleteInTeam{}
users, err := a.Srv.Store.User().Search(teamId, term, options)
if err != nil {
return nil, err
}
for _, user := range users {
a.SanitizeProfile(user, options.IsAdmin)
}
autocomplete.InTeam = users
}
return autocomplete, nil
}
func (a *App) UpdateOAuthUserAttrs(userData io.Reader, user *model.User, provider einterfaces.OauthProvider, service string) *model.AppError {
oauthUser := provider.GetUserFromJson(userData)
if oauthUser == nil {
return model.NewAppError("UpdateOAuthUserAttrs", "api.user.update_oauth_user_attrs.get_user.app_error", map[string]interface{}{"Service": service}, "", http.StatusBadRequest)
}
userAttrsChanged := false
if oauthUser.Username != user.Username {
if existingUser, _ := a.GetUserByUsername(oauthUser.Username); existingUser == nil {
user.Username = oauthUser.Username
userAttrsChanged = true
}
}
if oauthUser.GetFullName() != user.GetFullName() {
user.FirstName = oauthUser.FirstName
user.LastName = oauthUser.LastName
userAttrsChanged = true
}
if oauthUser.Email != user.Email {
if existingUser, _ := a.GetUserByEmail(oauthUser.Email); existingUser == nil {
user.Email = oauthUser.Email
userAttrsChanged = true
}
}
if user.DeleteAt > 0 {
// Make sure they are not disabled
user.DeleteAt = 0
userAttrsChanged = true
}
if userAttrsChanged {
users, err := a.Srv.Store.User().Update(user, true)
if err != nil {
return err
}
user = users.New
a.InvalidateCacheForUser(user.Id)
if a.IsESIndexingEnabled() {
a.Srv.Go(func() {
if err := a.indexUser(user); err != nil {
mlog.Error("Encountered error indexing user", mlog.String("user_id", user.Id), mlog.Err(err))
}
})
}
}
return nil
}
func (a *App) RestrictUsersGetByPermissions(userId string, options *model.UserGetOptions) (*model.UserGetOptions, *model.AppError) {
restrictions, err := a.GetViewUsersRestrictions(userId)
if err != nil {
return nil, err
}
options.ViewRestrictions = restrictions
return options, nil
}
// FilterNonGroupTeamMembers returns the subset of the given user IDs of the users who are not members of groups
// associated to the team.
func (a *App) FilterNonGroupTeamMembers(userIDs []string, team *model.Team) ([]string, error) {
teamGroupUsers, err := a.GetTeamGroupUsers(team.Id)
if err != nil {
return nil, err
}
// possible if no groups associated or no group members in any of the associated groups
if len(teamGroupUsers) == 0 {
return userIDs, nil
}
nonMemberIDs := []string{}
for _, userID := range userIDs {
userIsMember := false
for _, pu := range teamGroupUsers {
if pu.Id == userID {
userIsMember = true
break
}
}
if !userIsMember {
nonMemberIDs = append(nonMemberIDs, userID)
}
}
return nonMemberIDs, nil
}
// FilterNonGroupChannelMembers returns the subset of the given user IDs of the users who are not members of groups
// associated to the channel.
func (a *App) FilterNonGroupChannelMembers(userIDs []string, channel *model.Channel) ([]string, error) {
channelGroupUsers, err := a.GetChannelGroupUsers(channel.Id)
if err != nil {
return nil, err
}
// possible if no groups associated or no group members in any of the associated groups
if len(channelGroupUsers) == 0 {
return userIDs, nil
}
nonMemberIDs := []string{}
for _, userID := range userIDs {
userIsMember := false
for _, pu := range channelGroupUsers {
if pu.Id == userID {
userIsMember = true
break
}
}
if !userIsMember {
nonMemberIDs = append(nonMemberIDs, userID)
}
}
return nonMemberIDs, nil
}
func (a *App) RestrictUsersSearchByPermissions(userId string, options *model.UserSearchOptions) (*model.UserSearchOptions, *model.AppError) {
restrictions, err := a.GetViewUsersRestrictions(userId)
if err != nil {
return nil, err
}
options.ViewRestrictions = restrictions
return options, nil
}
func (a *App) UserCanSeeOtherUser(userId string, otherUserId string) (bool, *model.AppError) {
if userId == otherUserId {
return true, nil
}
restrictions, err := a.GetViewUsersRestrictions(userId)
if err != nil {
return false, err
}
if restrictions == nil {
return true, nil
}
if len(restrictions.Teams) > 0 {
result, err := a.Srv.Store.Team().UserBelongsToTeams(otherUserId, restrictions.Teams)
if err != nil {
return false, err
}
if result {
return true, nil
}
}
if len(restrictions.Channels) > 0 {
result, err := a.userBelongsToChannels(otherUserId, restrictions.Channels)
if err != nil {
return false, err
}
if result {
return true, nil
}
}
return false, nil
}
func (a *App) userBelongsToChannels(userId string, channelIds []string) (bool, *model.AppError) {
return a.Srv.Store.Channel().UserBelongsToChannels(userId, channelIds)
}
func (a *App) GetViewUsersRestrictions(userId string) (*model.ViewUsersRestrictions, *model.AppError) {
if a.HasPermissionTo(userId, model.PERMISSION_VIEW_MEMBERS) {
return nil, nil
}
teamIds, getTeamErr := a.Srv.Store.Team().GetUserTeamIds(userId, true)
if getTeamErr != nil {
return nil, getTeamErr
}
teamIdsWithPermission := []string{}
teamIdsWithoutPermission := []string{}
for _, teamId := range teamIds {
if a.HasPermissionToTeam(userId, teamId, model.PERMISSION_VIEW_MEMBERS) {
teamIdsWithPermission = append(teamIdsWithPermission, teamId)
} else {
teamIdsWithoutPermission = append(teamIdsWithoutPermission, teamId)
}
}
if len(teamIdsWithoutPermission) == 0 {
return &model.ViewUsersRestrictions{Teams: teamIdsWithPermission}, nil
}
userChannelMembers, err := a.Srv.Store.Channel().GetAllChannelMembersForUser(userId, true, true)
if err != nil {
return nil, err
}
channelIds := []string{}
for channelId := range userChannelMembers {
channelIds = append(channelIds, channelId)
}
return &model.ViewUsersRestrictions{Teams: teamIdsWithPermission, Channels: channelIds}, nil
}
/**
* Returns a list with the channel ids that the user has permissions to view on a
* team. If the result is an empty list, the user can't view any channel; if it's
* nil, there are no restrictions for the user in the specified team.
*/
func (a *App) GetViewUsersRestrictionsForTeam(userId string, teamId string) ([]string, *model.AppError) {
if a.HasPermissionTo(userId, model.PERMISSION_VIEW_MEMBERS) {
return nil, nil
}
if a.HasPermissionToTeam(userId, teamId, model.PERMISSION_VIEW_MEMBERS) {
return nil, nil
}
members, err := a.Srv.Store.Channel().GetMembersForUser(teamId, userId)
if err != nil {
return nil, err
}
channelIds := []string{}
for _, membership := range *members {
channelIds = append(channelIds, membership.ChannelId)
}
return channelIds, nil
}
func (a *App) getListOfAllowedChannelsForTeam(teamId string, viewRestrictions *model.ViewUsersRestrictions) ([]string, *model.AppError) {
var listOfAllowedChannels []string
if viewRestrictions == nil || strings.Contains(strings.Join(viewRestrictions.Teams, "."), teamId) {
channels, err := a.Srv.Store.Channel().GetTeamChannels(teamId)
if err != nil {
return nil, err
}
channelIds := []string{}
for _, channel := range *channels {
channelIds = append(channelIds, channel.Id)
}
return channelIds, nil
}
channels, err := a.Srv.Store.Channel().GetChannelsByIds(viewRestrictions.Channels)
if err != nil {
return nil, err
}
for _, c := range channels {
if c.TeamId == teamId {
listOfAllowedChannels = append(listOfAllowedChannels, c.Id)
}
}
return listOfAllowedChannels, nil
}
// PromoteGuestToUser Convert user's roles and all his mermbership's roles from
// guest roles to regular user roles.
func (a *App) PromoteGuestToUser(user *model.User, requestorId string) *model.AppError {
err := a.Srv.Store.User().PromoteGuestToUser(user.Id)
if err != nil {
return err
}
userTeams, err := a.Srv.Store.Team().GetTeamsByUserId(user.Id)
if err != nil {
return err
}
for _, team := range userTeams {
// Soft error if there is an issue joining the default channels
if err = a.JoinDefaultChannels(team.Id, user, false, requestorId); err != nil {
mlog.Error(fmt.Sprintf("Encountered an issue joining default channels err=%v", err), mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.String("requestor_id", requestorId))
}
}
promotedUser, err := a.GetUser(user.Id)
if err != nil {
mlog.Error(err.Error())
} else {
a.sendUpdatedUserEvent(*promotedUser)
a.UpdateSessionsIsGuest(promotedUser.Id, promotedUser.IsGuest())
}
teamMembers, err := a.GetTeamMembersForUser(user.Id)
if err != nil {
mlog.Error(err.Error())
}
for _, member := range teamMembers {
a.sendUpdatedMemberRoleEvent(user.Id, member)
channelMembers, err := a.GetChannelMembersForUser(member.TeamId, user.Id)
if err != nil {
mlog.Error(err.Error())
}
for _, member := range *channelMembers {
evt := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED, "", "", user.Id, nil)
evt.Add("channelMember", member.ToJson())
a.Publish(evt)
}
}
return nil
}
// DemoteUserToGuest Convert user's roles and all his mermbership's roles from
// regular user roles to guest roles.
func (a *App) DemoteUserToGuest(user *model.User) *model.AppError {
err := a.Srv.Store.User().DemoteUserToGuest(user.Id)
if err != nil {
return err
}
demotedUser, err := a.GetUser(user.Id)
if err != nil {
mlog.Error(err.Error())
} else {
a.sendUpdatedUserEvent(*demotedUser)
a.UpdateSessionsIsGuest(demotedUser.Id, demotedUser.IsGuest())
}
teamMembers, err := a.GetTeamMembersForUser(user.Id)
if err != nil {
mlog.Error(err.Error())
}
for _, member := range teamMembers {
a.sendUpdatedMemberRoleEvent(user.Id, member)
channelMembers, err := a.GetChannelMembersForUser(member.TeamId, user.Id)
if err != nil {
mlog.Error(err.Error())
}
for _, member := range *channelMembers {
evt := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED, "", "", user.Id, nil)
evt.Add("channelMember", member.ToJson())
a.Publish(evt)
}
}
return nil
}
// invalidateUserCacheAndPublish Invalidates cache for a user and publishes user updated event
func (a *App) invalidateUserCacheAndPublish(userId string) {
a.InvalidateCacheForUser(userId)
user, userErr := a.GetUser(userId)
if userErr != nil {
mlog.Error(fmt.Sprintf("Error in getting users profile for id=%v, err=%v", userId, userErr.Error()), mlog.String("user_id", userId))
return
}
options := a.Config().GetSanitizeOptions()
user.SanitizeProfile(options)
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", user)
a.Publish(message)
}