We move these fields to Channels: ``` uploadLockMapMut sync.Mutex uploadLockMap map[string]bool imgDecoder *imaging.Decoder imgEncoder *imaging.Encoder dndTaskMut sync.Mutex dndTask *model.ScheduledTask ``` I think this PR should conclue the initial phase of migrating stuff from Server to Channels. The remaining task would be to focus on continue to create the remaining services from the common things like users, teams, push notifications, clustering for other products to consume. https://community-daily.mattermost.com/boards/workspace/zyoahc9uapdn3xdptac6jb69ic/285b80a3-257d-41f6-8cf4-ed80ca9d92e5/495cdb4d-c13a-4992-8eb9-80cfee2819a4/87df1e15-588e-49ff-8bd1-ffa9651b8c82 ```release-note NONE ```
2360 строки
82 KiB
Go
2360 строки
82 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/mattermost/mattermost-server/v6/app/email"
|
|
"github.com/mattermost/mattermost-server/v6/app/imaging"
|
|
"github.com/mattermost/mattermost-server/v6/app/request"
|
|
"github.com/mattermost/mattermost-server/v6/app/users"
|
|
"github.com/mattermost/mattermost-server/v6/einterfaces"
|
|
"github.com/mattermost/mattermost-server/v6/model"
|
|
"github.com/mattermost/mattermost-server/v6/plugin"
|
|
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
|
"github.com/mattermost/mattermost-server/v6/shared/mfa"
|
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
|
"github.com/mattermost/mattermost-server/v6/store"
|
|
)
|
|
|
|
const (
|
|
TokenTypePasswordRecovery = "password_recovery"
|
|
TokenTypeVerifyEmail = "verify_email"
|
|
TokenTypeTeamInvitation = "team_invitation"
|
|
TokenTypeGuestInvitation = "guest_invitation"
|
|
TokenTypeCWSAccess = "cws_access_token"
|
|
PasswordRecoverExpiryTime = 1000 * 60 * 60 // 1 hour
|
|
InvitationExpiryTime = 1000 * 60 * 60 * 48 // 48 hours
|
|
ImageProfilePixelDimension = 128
|
|
)
|
|
|
|
func (a *App) CreateUserWithToken(c *request.Context, user *model.User, token *model.Token) (*model.User, *model.AppError) {
|
|
if err := a.IsUserSignUpAllowed(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if token.Type != TokenTypeTeamInvitation && token.Type != TokenTypeGuestInvitation {
|
|
return nil, model.NewAppError("CreateUserWithToken", "api.user.create_user.signup_link_invalid.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
if model.GetMillis()-token.CreateAt >= InvitationExpiryTime {
|
|
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, nErr := a.Srv().Store.Team().Get(tokenData["teamId"])
|
|
if nErr != nil {
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(nErr, &nfErr):
|
|
return nil, model.NewAppError("CreateUserWithToken", "app.team.get.find.app_error", nil, nfErr.Error(), http.StatusNotFound)
|
|
default:
|
|
return nil, model.NewAppError("CreateUserWithToken", "app.team.get.finding.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
channels, nErr := a.Srv().Store.Channel().GetChannelsByIds(strings.Split(tokenData["channels"], " "), false)
|
|
if nErr != nil {
|
|
return nil, model.NewAppError("CreateUserWithToken", "app.channel.get_channels_by_ids.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
emailFromToken := tokenData["email"]
|
|
if emailFromToken != user.Email {
|
|
return nil, model.NewAppError("CreateUserWithToken", "api.user.create_user.bad_token_email_data.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
user.Email = tokenData["email"]
|
|
user.EmailVerified = true
|
|
|
|
var ruser *model.User
|
|
var err *model.AppError
|
|
if token.Type == TokenTypeTeamInvitation {
|
|
ruser, err = a.CreateUser(c, user)
|
|
} else {
|
|
ruser, err = a.CreateGuest(c, user)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := a.JoinUserToTeam(c, team, ruser, ""); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
a.AddDirectChannels(team.Id, ruser)
|
|
|
|
if token.Type == TokenTypeGuestInvitation {
|
|
for _, channel := range channels {
|
|
_, err := a.AddChannelMember(c, ruser.Id, channel, ChannelMemberOpts{})
|
|
if err != nil {
|
|
mlog.Warn("Failed to add channel member", mlog.Err(err))
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := a.DeleteToken(token); err != nil {
|
|
mlog.Warn("Error while deleting token", mlog.Err(err))
|
|
}
|
|
|
|
return ruser, nil
|
|
}
|
|
|
|
func (a *App) CreateUserWithInviteId(c *request.Context, user *model.User, inviteId, redirect string) (*model.User, *model.AppError) {
|
|
if err := a.IsUserSignUpAllowed(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
team, nErr := a.Srv().Store.Team().GetByInviteId(inviteId)
|
|
if nErr != nil {
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(nErr, &nfErr):
|
|
return nil, model.NewAppError("CreateUserWithInviteId", "app.team.get_by_invite_id.finding.app_error", nil, nfErr.Error(), http.StatusNotFound)
|
|
default:
|
|
return nil, model.NewAppError("CreateUserWithInviteId", "app.team.get_by_invite_id.finding.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
if team.IsGroupConstrained() {
|
|
return nil, model.NewAppError("CreateUserWithInviteId", "app.team.invite_id.group_constrained.error", nil, "", http.StatusForbidden)
|
|
}
|
|
|
|
if !users.CheckUserDomain(user, team.AllowedDomains) {
|
|
return nil, model.NewAppError("CreateUserWithInviteId", "api.team.invite_members.invalid_email.app_error", map[string]interface{}{"Addresses": team.AllowedDomains}, "", http.StatusForbidden)
|
|
}
|
|
|
|
user.EmailVerified = false
|
|
|
|
ruser, err := a.CreateUser(c, user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := a.JoinUserToTeam(c, team, ruser, ""); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
a.AddDirectChannels(team.Id, ruser)
|
|
|
|
if err := a.Srv().EmailService.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.DisableWelcomeEmail, ruser.Locale, a.GetSiteURL(), redirect); err != nil {
|
|
mlog.Warn("Failed to send welcome email on create user with inviteId", mlog.Err(err))
|
|
}
|
|
|
|
return ruser, nil
|
|
}
|
|
|
|
func (a *App) CreateUserAsAdmin(c *request.Context, user *model.User, redirect string) (*model.User, *model.AppError) {
|
|
ruser, err := a.CreateUser(c, user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := a.Srv().EmailService.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.DisableWelcomeEmail, ruser.Locale, a.GetSiteURL(), redirect); err != nil {
|
|
mlog.Warn("Failed to send welcome email to the new user, created by system admin", mlog.Err(err))
|
|
}
|
|
|
|
return ruser, nil
|
|
}
|
|
|
|
func (a *App) CreateUserFromSignup(c *request.Context, user *model.User, redirect string) (*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(c, user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := a.Srv().EmailService.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.DisableWelcomeEmail, ruser.Locale, a.GetSiteURL(), redirect); err != nil {
|
|
mlog.Warn("Failed to send welcome email on create user from signup", mlog.Err(err))
|
|
}
|
|
|
|
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 {
|
|
return a.ch.srv.userService.IsFirstUserAccount()
|
|
}
|
|
|
|
// CreateUser creates a user and sets several fields of the returned User struct to
|
|
// their zero values.
|
|
func (a *App) CreateUser(c *request.Context, user *model.User) (*model.User, *model.AppError) {
|
|
return a.createUserOrGuest(c, user, false)
|
|
}
|
|
|
|
// CreateGuest creates a guest and sets several fields of the returned User struct to
|
|
// their zero values.
|
|
func (a *App) CreateGuest(c *request.Context, user *model.User) (*model.User, *model.AppError) {
|
|
return a.createUserOrGuest(c, user, true)
|
|
}
|
|
|
|
func (a *App) createUserOrGuest(c *request.Context, user *model.User, guest bool) (*model.User, *model.AppError) {
|
|
ruser, nErr := a.ch.srv.userService.CreateUser(user, users.UserCreateOptions{Guest: guest})
|
|
if nErr != nil {
|
|
var appErr *model.AppError
|
|
var invErr *store.ErrInvalidInput
|
|
var nfErr *users.ErrInvalidPassword
|
|
switch {
|
|
case errors.As(nErr, &appErr):
|
|
return nil, appErr
|
|
case errors.Is(nErr, users.AcceptedDomainError):
|
|
return nil, model.NewAppError("createUserOrGuest", "api.user.create_user.accepted_domain.app_error", nil, "", http.StatusBadRequest)
|
|
case errors.As(nErr, &nfErr):
|
|
return nil, model.NewAppError("createUserOrGuest", "api.user.check_user_password.invalid.app_error", nil, "", http.StatusBadRequest)
|
|
case errors.Is(nErr, users.UserStoreIsEmptyError):
|
|
return nil, model.NewAppError("createUserOrGuest", "app.user.store_is_empty.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
case errors.As(nErr, &invErr):
|
|
switch invErr.Field {
|
|
case "email":
|
|
return nil, model.NewAppError("createUserOrGuest", "app.user.save.email_exists.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
case "username":
|
|
return nil, model.NewAppError("createUserOrGuest", "app.user.save.username_exists.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
default:
|
|
return nil, model.NewAppError("createUserOrGuest", "app.user.save.existing.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
}
|
|
default:
|
|
return nil, model.NewAppError("createUserOrGuest", "app.user.save.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
if user.EmailVerified {
|
|
a.InvalidateCacheForUser(ruser.Id)
|
|
|
|
nUser, err := a.ch.srv.userService.GetUser(ruser.Id)
|
|
if err != nil {
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(err, &nfErr):
|
|
return nil, model.NewAppError("createUserOrGuest", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound)
|
|
default:
|
|
return nil, model.NewAppError("createUserOrGuest", "app.user.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
a.sendUpdatedUserEvent(*nUser)
|
|
}
|
|
|
|
recommendedNextStepsPref := model.Preference{UserId: ruser.Id, Category: model.PreferenceRecommendedNextSteps, Name: "hide", Value: "false"}
|
|
tutorialStepPref := model.Preference{UserId: ruser.Id, Category: model.PreferenceCategoryTutorialSteps, Name: ruser.Id, Value: "0"}
|
|
if err := a.Srv().Store.Preference().Save(model.Preferences{recommendedNextStepsPref, tutorialStepPref}); err != nil {
|
|
mlog.Warn("Encountered error saving user preferences", mlog.Err(err))
|
|
}
|
|
|
|
go a.UpdateViewedProductNoticesForNewUser(ruser.Id)
|
|
|
|
// This message goes to everyone, so the teamID, channelID and userID are irrelevant
|
|
message := model.NewWebSocketEvent(model.WebsocketEventNewUser, "", "", "", nil)
|
|
message.Add("user_id", ruser.Id)
|
|
a.Publish(message)
|
|
|
|
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
|
a.Srv().Go(func() {
|
|
pluginContext := pluginContext(c)
|
|
pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
|
|
hooks.UserHasBeenCreated(pluginContext, user)
|
|
return true
|
|
}, plugin.UserHasBeenCreatedID)
|
|
})
|
|
}
|
|
|
|
return ruser, nil
|
|
}
|
|
|
|
func (a *App) CreateOAuthUser(c *request.Context, service string, userData io.Reader, teamID string, tokenUser *model.User) (*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, e := a.getSSOProvider(service)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
user, err1 := provider.GetUserFromJSON(userData, tokenUser)
|
|
if err1 != nil {
|
|
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_oauth_user.create.app_error", map[string]interface{}{"Service": service}, err1.Error(), http.StatusInternalServerError)
|
|
}
|
|
if user.AuthService == "" {
|
|
user.AuthService = service
|
|
}
|
|
|
|
found := true
|
|
count := 0
|
|
for found {
|
|
if found = a.ch.srv.userService.IsUsernameTaken(user.Username); found {
|
|
user.Username = user.Username + strconv.Itoa(count)
|
|
count++
|
|
}
|
|
}
|
|
|
|
userByAuth, _ := a.ch.srv.userService.GetUserByAuth(user.AuthData, service)
|
|
if userByAuth != nil {
|
|
return userByAuth, nil
|
|
}
|
|
|
|
userByEmail, _ := a.ch.srv.userService.GetUserByEmail(user.Email)
|
|
if userByEmail != nil {
|
|
if userByEmail.AuthService == "" {
|
|
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error", map[string]interface{}{"Service": service, "Auth": model.UserAuthServiceEmail}, "email="+user.Email, http.StatusBadRequest)
|
|
}
|
|
if provider.IsSameUser(userByEmail, user) {
|
|
if _, err := a.Srv().Store.User().UpdateAuthData(userByEmail.Id, user.AuthService, user.AuthData, "", false); err != nil {
|
|
// if the user is not updated, write a warning to the log, but don't prevent user login
|
|
mlog.Warn("Error attempting to update user AuthData", mlog.Err(err))
|
|
}
|
|
return userByEmail, nil
|
|
}
|
|
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error", map[string]interface{}{"Service": service, "Auth": userByEmail.AuthService}, "email="+user.Email+" authData="+*user.AuthData, http.StatusBadRequest)
|
|
}
|
|
|
|
user.EmailVerified = true
|
|
|
|
ruser, err := a.CreateUser(c, user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if teamID != "" {
|
|
err = a.AddUserToTeamByTeamId(c, teamID, user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = a.AddDirectChannels(teamID, user)
|
|
if err != nil {
|
|
mlog.Warn("Failed to add direct channels", mlog.Err(err))
|
|
}
|
|
}
|
|
|
|
return ruser, nil
|
|
}
|
|
|
|
func (a *App) GetUser(userID string) (*model.User, *model.AppError) {
|
|
user, err := a.ch.srv.userService.GetUser(userID)
|
|
if err != nil {
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(err, &nfErr):
|
|
return nil, model.NewAppError("GetUser", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound)
|
|
default:
|
|
return nil, model.NewAppError("GetUser", "app.user.get_by_username.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (a *App) GetUserByUsername(username string) (*model.User, *model.AppError) {
|
|
result, err := a.ch.srv.userService.GetUserByUsername(username)
|
|
if err != nil {
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(err, &nfErr):
|
|
return nil, model.NewAppError("GetUserByUsername", "app.user.get_by_username.app_error", nil, nfErr.Error(), http.StatusNotFound)
|
|
default:
|
|
return nil, model.NewAppError("GetUserByUsername", "app.user.get_by_username.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (a *App) GetUserByEmail(email string) (*model.User, *model.AppError) {
|
|
user, err := a.ch.srv.userService.GetUserByEmail(email)
|
|
if err != nil {
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(err, &nfErr):
|
|
return nil, model.NewAppError("GetUserByEmail", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound)
|
|
default:
|
|
return nil, model.NewAppError("GetUserByEmail", MissingAccountError, nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (a *App) GetUserByAuth(authData *string, authService string) (*model.User, *model.AppError) {
|
|
user, err := a.ch.srv.userService.GetUserByAuth(authData, authService)
|
|
if err != nil {
|
|
var invErr *store.ErrInvalidInput
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(err, &invErr):
|
|
return nil, model.NewAppError("GetUserByAuth", MissingAuthAccountError, nil, invErr.Error(), http.StatusBadRequest)
|
|
case errors.As(err, &nfErr):
|
|
return nil, model.NewAppError("GetUserByAuth", MissingAuthAccountError, nil, nfErr.Error(), http.StatusInternalServerError)
|
|
default:
|
|
return nil, model.NewAppError("GetUserByAuth", "app.user.get_by_auth.other.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (a *App) GetUsers(options *model.UserGetOptions) ([]*model.User, *model.AppError) {
|
|
users, err := a.ch.srv.userService.GetUsers(options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsers", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) GetUsersPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError) {
|
|
users, err := a.ch.srv.userService.GetUsersPage(options, asAdmin)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersPage", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) GetUsersEtag(restrictionsHash string) string {
|
|
return a.ch.srv.userService.GetUsersEtag(restrictionsHash)
|
|
}
|
|
|
|
func (a *App) GetUsersInTeam(options *model.UserGetOptions) ([]*model.User, *model.AppError) {
|
|
users, err := a.ch.srv.userService.GetUsersInTeam(options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersInTeam", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) GetUsersNotInTeam(teamID string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
|
users, err := a.ch.srv.userService.GetUsersNotInTeam(teamID, groupConstrained, offset, limit, viewRestrictions)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersNotInTeam", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) GetUsersInTeamPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError) {
|
|
users, err := a.ch.srv.userService.GetUsersInTeamPage(options, asAdmin)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersInTeamPage", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
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.ch.srv.userService.GetUsersNotInTeamPage(teamID, groupConstrained, page*perPage, perPage, asAdmin, viewRestrictions)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersNotInTeamPage", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return a.sanitizeProfiles(users, asAdmin), nil
|
|
}
|
|
|
|
func (a *App) GetUsersInTeamEtag(teamID string, restrictionsHash string) string {
|
|
return a.ch.srv.userService.GetUsersInTeamEtag(teamID, restrictionsHash)
|
|
}
|
|
|
|
func (a *App) GetUsersNotInTeamEtag(teamID string, restrictionsHash string) string {
|
|
return a.ch.srv.userService.GetUsersNotInTeamEtag(teamID, restrictionsHash)
|
|
}
|
|
|
|
func (a *App) GetUsersInChannel(options *model.UserGetOptions) ([]*model.User, *model.AppError) {
|
|
users, err := a.Srv().Store.User().GetProfilesInChannel(options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersInChannel", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) GetUsersInChannelByStatus(options *model.UserGetOptions) ([]*model.User, *model.AppError) {
|
|
users, err := a.Srv().Store.User().GetProfilesInChannelByStatus(options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersInChannelByStatus", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) GetUsersInChannelMap(options *model.UserGetOptions, asAdmin bool) (map[string]*model.User, *model.AppError) {
|
|
users, err := a.GetUsersInChannel(options)
|
|
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(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError) {
|
|
users, err := a.GetUsersInChannel(options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return a.sanitizeProfiles(users, asAdmin), nil
|
|
}
|
|
|
|
func (a *App) GetUsersInChannelPageByStatus(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError) {
|
|
users, err := a.GetUsersInChannelByStatus(options)
|
|
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) {
|
|
users, err := a.Srv().Store.User().GetProfilesNotInChannel(teamID, channelID, groupConstrained, offset, limit, viewRestrictions)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersNotInChannel", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
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(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError) {
|
|
users, err := a.ch.srv.userService.GetUsersWithoutTeamPage(options, asAdmin)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersWithoutTeamPage", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return a.sanitizeProfiles(users, asAdmin), nil
|
|
}
|
|
|
|
func (a *App) GetUsersWithoutTeam(options *model.UserGetOptions) ([]*model.User, *model.AppError) {
|
|
users, err := a.ch.srv.userService.GetUsersWithoutTeam(options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersWithoutTeam", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
// GetTeamGroupUsers returns the users who are associated to the team via GroupTeams and GroupMembers.
|
|
func (a *App) GetTeamGroupUsers(teamID string) ([]*model.User, *model.AppError) {
|
|
users, err := a.Srv().Store.User().GetTeamGroupUsers(teamID)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetTeamGroupUsers", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
// GetChannelGroupUsers returns the users who are associated to the channel via GroupChannels and GroupMembers.
|
|
func (a *App) GetChannelGroupUsers(channelID string) ([]*model.User, *model.AppError) {
|
|
users, err := a.Srv().Store.User().GetChannelGroupUsers(channelID)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetChannelGroupUsers", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) GetUsersByIds(userIDs []string, options *store.UserGetByIdsOpts) ([]*model.User, *model.AppError) {
|
|
users, err := a.ch.srv.userService.GetUsersByIds(userIDs, options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersByIds", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) GetUsersByGroupChannelIds(c *request.Context, channelIDs []string, asAdmin bool) (map[string][]*model.User, *model.AppError) {
|
|
usersByChannelId, err := a.Srv().Store.User().GetProfileByGroupChannelIdsForUser(c.Session().UserId, channelIDs)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersByGroupChannelIds", "app.user.get_profile_by_group_channel_ids_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
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.ch.srv.userService.GetUsersByUsernames(usernames, &model.UserGetOptions{ViewRestrictions: viewRestrictions})
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetUsersByUsernames", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
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, appErr := a.GetUser(userID)
|
|
if appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
|
|
if !*a.Config().ServiceSettings.EnableMultifactorAuthentication {
|
|
return nil, model.NewAppError("GenerateMfaSecret", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented)
|
|
}
|
|
|
|
mfaSecret, err := a.ch.srv.userService.GenerateMfaSecret(user)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GenerateMfaSecret", "mfa.generate_qr_code.create_code.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return mfaSecret, nil
|
|
}
|
|
|
|
func (a *App) ActivateMfa(userID, token string) *model.AppError {
|
|
user, appErr := a.GetUser(userID)
|
|
if appErr != nil {
|
|
return appErr
|
|
}
|
|
|
|
if user.AuthService != "" && user.AuthService != model.UserAuthServiceLdap {
|
|
return model.NewAppError("ActivateMfa", "api.user.activate_mfa.email_and_ldap_only.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
if !*a.Config().ServiceSettings.EnableMultifactorAuthentication {
|
|
return model.NewAppError("ActivateMfa", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented)
|
|
}
|
|
|
|
if err := a.ch.srv.userService.ActivateMfa(user, token); err != nil {
|
|
switch {
|
|
case errors.Is(err, mfa.InvalidToken):
|
|
return model.NewAppError("ActivateMfa", "mfa.activate.bad_token.app_error", nil, "", http.StatusUnauthorized)
|
|
default:
|
|
return model.NewAppError("ActivateMfa", "mfa.activate.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
// Make sure old MFA status is not cached locally or in cluster nodes.
|
|
a.InvalidateCacheForUser(userID)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) DeactivateMfa(userID string) *model.AppError {
|
|
user, appErr := a.GetUser(userID)
|
|
if appErr != nil {
|
|
return appErr
|
|
}
|
|
|
|
if err := a.ch.srv.userService.DeactivateMfa(user); err != nil {
|
|
return model.NewAppError("DeactivateMfa", "mfa.deactivate.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
// Make sure old MFA status is not cached locally or in cluster nodes.
|
|
a.InvalidateCacheForUser(userID)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
|
|
return a.ch.srv.GetProfileImage(user)
|
|
}
|
|
|
|
func (a *App) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) {
|
|
return a.ch.srv.GetDefaultProfileImage(user)
|
|
}
|
|
|
|
func (a *App) SetDefaultProfileImage(user *model.User) *model.AppError {
|
|
img, appErr := a.GetDefaultProfileImage(user)
|
|
if appErr != nil {
|
|
return appErr
|
|
}
|
|
|
|
path := getProfileImagePath(user.Id)
|
|
if _, err := a.WriteFile(bytes.NewReader(img), path); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := a.Srv().Store.User().ResetLastPictureUpdate(user.Id); err != nil {
|
|
mlog.Warn("Failed to reset last picture update", mlog.Err(err))
|
|
}
|
|
|
|
a.InvalidateCacheForUser(user.Id)
|
|
|
|
updatedUser, appErr := a.GetUser(user.Id)
|
|
if appErr != nil {
|
|
mlog.Warn("Error in getting users profile forcing logout", mlog.String("user_id", user.Id), mlog.Err(appErr))
|
|
return nil
|
|
}
|
|
|
|
options := a.Config().GetSanitizeOptions()
|
|
updatedUser.SanitizeProfile(options)
|
|
|
|
message := model.NewWebSocketEvent(model.WebsocketEventUserUpdated, "", "", "", 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 {
|
|
if limitErr := checkImageLimits(file, *a.Config().FileSettings.MaxImageResolution); limitErr != nil {
|
|
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.check_image_limits.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
return a.SetProfileImageFromFile(userID, file)
|
|
}
|
|
|
|
func (a *App) AdjustImage(file io.Reader) (*bytes.Buffer, *model.AppError) {
|
|
// Decode image into Image object
|
|
img, _, err := a.ch.imgDecoder.Decode(file)
|
|
if err != nil {
|
|
return nil, model.NewAppError("SetProfileImage", "api.user.upload_profile_user.decode.app_error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
|
|
orientation, _ := imaging.GetImageOrientation(file)
|
|
img = imaging.MakeImageUpright(img, orientation)
|
|
|
|
// Scale profile image
|
|
profileWidthAndHeight := 128
|
|
img = imaging.FillCenter(img, profileWidthAndHeight, profileWidthAndHeight)
|
|
|
|
buf := new(bytes.Buffer)
|
|
err = a.ch.imgEncoder.EncodePNG(buf, img)
|
|
if err != nil {
|
|
return nil, model.NewAppError("SetProfileImage", "api.user.upload_profile_user.encode.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
func (a *App) SetProfileImageFromFile(userID string, file io.Reader) *model.AppError {
|
|
buf, err := a.AdjustImage(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
path := getProfileImagePath(userID)
|
|
if storedData, err := a.ReadFile(path); err == nil && bytes.Equal(storedData, buf.Bytes()) {
|
|
return nil
|
|
}
|
|
|
|
if _, err := a.WriteFile(buf, path); err != nil {
|
|
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.User().UpdateLastPictureUpdate(userID); err != nil {
|
|
mlog.Warn("Error with updating last picture update", mlog.Err(err))
|
|
}
|
|
a.invalidateUserCacheAndPublish(userID)
|
|
a.onUserProfileChange(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 := i18n.GetUserTranslations(user.Locale)
|
|
|
|
return a.UpdatePasswordSendEmail(user, newPassword, T("api.user.update_password.menu"))
|
|
}
|
|
|
|
func (a *App) userDeactivated(c *request.Context, userID string) *model.AppError {
|
|
a.SetStatusOffline(userID, false)
|
|
|
|
user, err := a.GetUser(userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// when disable a user, userDeactivated is called for the user and the
|
|
// bots the user owns. Only notify once, when the user is the owner, not the
|
|
// owners bots
|
|
if !user.IsBot {
|
|
a.notifySysadminsBotOwnerDeactivated(c, userID)
|
|
}
|
|
|
|
if *a.Config().ServiceSettings.DisableBotsWhenOwnerIsDeactivated {
|
|
a.disableUserBots(c, userID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) invalidateUserChannelMembersCaches(userID string) *model.AppError {
|
|
teamsForUser, err := a.GetTeamsForUser(userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, team := range teamsForUser {
|
|
channelsForUser, err := a.GetChannelsForTeamForUser(team.Id, userID, false, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, channel := range channelsForUser {
|
|
a.invalidateCacheForChannelMembers(channel.Id)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) UpdateActive(c *request.Context, user *model.User, active bool) (*model.User, *model.AppError) {
|
|
user.UpdateAt = model.GetMillis()
|
|
if active {
|
|
user.DeleteAt = 0
|
|
} else {
|
|
user.DeleteAt = user.UpdateAt
|
|
}
|
|
|
|
userUpdate, err := a.ch.srv.userService.UpdateUser(user, true)
|
|
if err != nil {
|
|
var appErr *model.AppError
|
|
var invErr *store.ErrInvalidInput
|
|
switch {
|
|
case errors.As(err, &appErr):
|
|
return nil, appErr
|
|
case errors.As(err, &invErr):
|
|
return nil, model.NewAppError("UpdateActive", "app.user.update.find.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
default:
|
|
return nil, model.NewAppError("UpdateActive", "app.user.update.finding.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
ruser := userUpdate.New
|
|
|
|
if !active {
|
|
if err := a.RevokeAllSessions(ruser.Id); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := a.userDeactivated(c, ruser.Id); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
a.invalidateUserChannelMembersCaches(user.Id)
|
|
a.InvalidateCacheForUser(user.Id)
|
|
|
|
a.sendUpdatedUserEvent(*ruser)
|
|
|
|
return ruser, nil
|
|
}
|
|
|
|
func (a *App) DeactivateGuests(c *request.Context) *model.AppError {
|
|
userIDs, err := a.ch.srv.userService.DeactivateAllGuests()
|
|
if err != nil {
|
|
return model.NewAppError("DeactivateGuests", "app.user.update_active_for_multiple_users.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for _, userID := range userIDs {
|
|
if err := a.userDeactivated(c, userID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
a.Srv().Store.Channel().ClearCaches()
|
|
a.Srv().Store.User().ClearCaches()
|
|
|
|
message := model.NewWebSocketEvent(model.WebsocketEventGuestsDeactivated, "", "", "", nil)
|
|
a.Publish(message)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) GetSanitizeOptions(asAdmin bool) map[string]bool {
|
|
return a.ch.srv.userService.GetSanitizeOptions(asAdmin)
|
|
}
|
|
|
|
func (a *App) SanitizeProfile(user *model.User, asAdmin bool) {
|
|
options := a.ch.srv.userService.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
|
|
}
|
|
|
|
return updatedUser, nil
|
|
}
|
|
|
|
// CheckProviderAttributes returns the empty string if the patch can be applied without
|
|
// overriding attributes set by the user's login provider; otherwise, the name of the offending
|
|
// field is returned.
|
|
func (a *App) CheckProviderAttributes(user *model.User, patch *model.UserPatch) string {
|
|
tryingToChange := func(userValue *string, patchValue *string) bool {
|
|
return patchValue != nil && *patchValue != *userValue
|
|
}
|
|
|
|
// If any login provider is used, then the username may not be changed
|
|
if user.AuthService != "" && tryingToChange(&user.Username, patch.Username) {
|
|
return "username"
|
|
}
|
|
|
|
LdapSettings := &a.Config().LdapSettings
|
|
SamlSettings := &a.Config().SamlSettings
|
|
|
|
conflictField := ""
|
|
if a.Ldap() != nil &&
|
|
(user.IsLDAPUser() || (user.IsSAMLUser() && *SamlSettings.EnableSyncWithLdap)) {
|
|
conflictField = a.Ldap().CheckProviderAttributes(LdapSettings, user, patch)
|
|
} else if a.Saml() != nil && user.IsSAMLUser() {
|
|
conflictField = a.Saml().CheckProviderAttributes(SamlSettings, user, patch)
|
|
} else if user.IsOAuthUser() {
|
|
if tryingToChange(&user.FirstName, patch.FirstName) || tryingToChange(&user.LastName, patch.LastName) {
|
|
conflictField = "full name"
|
|
}
|
|
}
|
|
|
|
return conflictField
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return updatedUser, nil
|
|
}
|
|
|
|
func (a *App) UpdateUserAuth(userID string, userAuth *model.UserAuth) (*model.UserAuth, *model.AppError) {
|
|
userAuth.Password = ""
|
|
if _, err := a.Srv().Store.User().UpdateAuthData(userID, userAuth.AuthService, userAuth.AuthData, "", false); err != nil {
|
|
var invErr *store.ErrInvalidInput
|
|
switch {
|
|
case errors.As(err, &invErr):
|
|
return nil, model.NewAppError("UpdateUserAuth", "app.user.update_auth_data.email_exists.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
default:
|
|
return nil, model.NewAppError("UpdateUserAuth", "app.user.update_auth_data.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
return userAuth, nil
|
|
}
|
|
|
|
func (a *App) sendUpdatedUserEvent(user model.User) {
|
|
adminCopyOfUser := user.DeepCopy()
|
|
a.SanitizeProfile(adminCopyOfUser, true)
|
|
adminMessage := model.NewWebSocketEvent(model.WebsocketEventUserUpdated, "", "", "", nil)
|
|
adminMessage.Add("user", adminCopyOfUser)
|
|
adminMessage.GetBroadcast().ContainsSensitiveData = true
|
|
a.Publish(adminMessage)
|
|
|
|
a.SanitizeProfile(&user, false)
|
|
message := model.NewWebSocketEvent(model.WebsocketEventUserUpdated, "", "", "", nil)
|
|
message.Add("user", &user)
|
|
message.GetBroadcast().ContainsSanitizedData = true
|
|
a.Publish(message)
|
|
}
|
|
|
|
func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) {
|
|
prev, err := a.ch.srv.userService.GetUser(user.Id)
|
|
if err != nil {
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(err, &nfErr):
|
|
return nil, model.NewAppError("UpdateUser", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound)
|
|
default:
|
|
return nil, model.NewAppError("UpdateUser", "app.user.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
var newEmail string
|
|
if user.Email != prev.Email {
|
|
if !users.CheckUserDomain(user, *a.Config().TeamSettings.RestrictCreationToDomains) {
|
|
if !prev.IsGuest() && !prev.IsLDAPUser() && !prev.IsSAMLUser() {
|
|
return nil, model.NewAppError("UpdateUser", "api.user.update_user.accepted_domain.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
if !users.CheckUserDomain(user, *a.Config().GuestAccountsSettings.RestrictCreationToDomains) {
|
|
if prev.IsGuest() && !prev.IsLDAPUser() && !prev.IsSAMLUser() {
|
|
return nil, model.NewAppError("UpdateUser", "api.user.update_user.accepted_guest_domain.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
if *a.Config().EmailSettings.RequireEmailVerification {
|
|
newEmail = user.Email
|
|
// 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
|
|
if _, appErr := a.GetUserByEmail(newEmail); appErr == nil {
|
|
return nil, model.NewAppError("UpdateUser", "app.user.save.email_exists.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.ch.srv.userService.UpdateUser(user, false)
|
|
if err != nil {
|
|
var appErr *model.AppError
|
|
var invErr *store.ErrInvalidInput
|
|
var conErr *store.ErrConflict
|
|
switch {
|
|
case errors.As(err, &appErr):
|
|
return nil, appErr
|
|
case errors.As(err, &invErr):
|
|
return nil, model.NewAppError("UpdateUser", "app.user.update.find.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
case errors.As(err, &conErr):
|
|
if cErr, ok := err.(*store.ErrConflict); ok && cErr.Resource == "Username" {
|
|
return nil, model.NewAppError("UpdateUser", "app.user.save.username_exists.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
return nil, model.NewAppError("UpdateUser", "app.user.save.email_exists.app_error", nil, "", http.StatusBadRequest)
|
|
default:
|
|
return nil, model.NewAppError("UpdateUser", "app.user.update.finding.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
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("Failed to send email verification", mlog.Err(err))
|
|
}
|
|
})
|
|
} else {
|
|
a.Srv().Go(func() {
|
|
if err := a.Srv().EmailService.SendEmailChangeEmail(userUpdate.Old.Email, userUpdate.New.Email, userUpdate.New.Locale, a.GetSiteURL()); err != nil {
|
|
mlog.Error("Failed to send email change email", mlog.Err(err))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
if userUpdate.New.Username != userUpdate.Old.Username {
|
|
a.Srv().Go(func() {
|
|
if err := a.Srv().EmailService.SendChangeUsernameEmail(userUpdate.New.Username, userUpdate.New.Email, userUpdate.New.Locale, a.GetSiteURL()); err != nil {
|
|
mlog.Error("Failed to send change username email", mlog.Err(err))
|
|
}
|
|
})
|
|
}
|
|
a.sendUpdatedUserEvent(*userUpdate.New)
|
|
}
|
|
|
|
a.InvalidateCacheForUser(user.Id)
|
|
a.onUserProfileChange(user.Id)
|
|
|
|
return userUpdate.New, nil
|
|
}
|
|
|
|
func (a *App) UpdateUserActive(c *request.Context, userID string, active bool) *model.AppError {
|
|
user, err := a.GetUser(userID)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err = a.UpdateActive(c, user, active); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) updateUserNotifyProps(userID string, props map[string]string) *model.AppError {
|
|
err := a.ch.srv.userService.UpdateUserNotifyProps(userID, props)
|
|
if err != nil {
|
|
var appErr *model.AppError
|
|
switch {
|
|
case errors.As(err, &appErr):
|
|
return appErr
|
|
default:
|
|
return model.NewAppError("UpdateUser", "app.user.update.finding.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
a.InvalidateCacheForUser(userID)
|
|
a.onUserProfileChange(userID)
|
|
|
|
return 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("Failed to get user", mlog.Err(err))
|
|
return
|
|
}
|
|
|
|
if err := a.Srv().EmailService.SendMfaChangeEmail(user.Email, activate, user.Locale, a.GetSiteURL()); err != nil {
|
|
mlog.Error("Failed to send mfa change email", mlog.Err(err))
|
|
}
|
|
})
|
|
|
|
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)
|
|
}
|
|
|
|
a.InvalidateCacheForUser(user.Id)
|
|
|
|
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.Srv().EmailService.SendPasswordChangeEmail(user.Email, method, user.Locale, a.GetSiteURL()); err != nil {
|
|
mlog.Error("Failed to send password change email", mlog.Err(err))
|
|
}
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) UpdateHashedPasswordByUserId(userID, newHashedPassword string) *model.AppError {
|
|
user, err := a.GetUser(userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return a.UpdateHashedPassword(user, newHashedPassword)
|
|
}
|
|
|
|
func (a *App) UpdateHashedPassword(user *model.User, newHashedPassword string) *model.AppError {
|
|
if err := a.Srv().Store.User().UpdatePassword(user.Id, newHashedPassword); err != nil {
|
|
return model.NewAppError("UpdatePassword", "api.user.update_password.failed.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
a.InvalidateCacheForUser(user.Id)
|
|
|
|
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 >= PasswordRecoverExpiryTime {
|
|
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 := i18n.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.Warn("Failed to delete token", mlog.Err(err))
|
|
}
|
|
|
|
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 && *user.AuthData != "" {
|
|
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
|
|
}
|
|
|
|
result, eErr := a.Srv().EmailService.SendPasswordResetEmail(user.Email, token, user.Locale, siteURL)
|
|
if eErr != nil {
|
|
return result, model.NewAppError("SendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+eErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
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(TokenTypePasswordRecovery, string(jsonData))
|
|
|
|
if err := a.Srv().Store.Token().Save(token); err != nil {
|
|
var appErr *model.AppError
|
|
switch {
|
|
case errors.As(err, &appErr):
|
|
return nil, appErr
|
|
default:
|
|
return nil, model.NewAppError("CreatePasswordRecoveryToken", "app.recover.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
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 != TokenTypePasswordRecovery {
|
|
return nil, model.NewAppError("GetPasswordRecoveryToken", "api.user.reset_password.broken_token.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
return rtoken, nil
|
|
}
|
|
|
|
func (a *App) GetTokenById(token string) (*model.Token, *model.AppError) {
|
|
rtoken, err := a.Srv().Store.Token().GetByToken(token)
|
|
|
|
if err != nil {
|
|
var status int
|
|
|
|
switch err.(type) {
|
|
case *store.ErrNotFound:
|
|
status = http.StatusNotFound
|
|
default:
|
|
status = http.StatusInternalServerError
|
|
}
|
|
|
|
return nil, model.NewAppError("GetTokenById", "api.user.create_user.signup_link_invalid.app_error", nil, err.Error(), status)
|
|
}
|
|
|
|
return rtoken, nil
|
|
}
|
|
|
|
func (a *App) DeleteToken(token *model.Token) *model.AppError {
|
|
err := a.Srv().Store.Token().Delete(token.Token)
|
|
if err != nil {
|
|
return model.NewAppError("DeleteToken", "app.recover.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return a.UpdateUserRolesWithUser(user, newRoles, sendWebSocketEvent)
|
|
}
|
|
|
|
func (a *App) UpdateUserRolesWithUser(user *model.User, newRoles string, sendWebSocketEvent bool) (*model.User, *model.AppError) {
|
|
|
|
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, NErr: err}
|
|
close(uchan)
|
|
}()
|
|
|
|
schan := make(chan store.StoreResult, 1)
|
|
go func() {
|
|
id, err := a.Srv().Store.Session().UpdateRoles(user.Id, newRoles)
|
|
schan <- store.StoreResult{Data: id, NErr: err}
|
|
close(schan)
|
|
}()
|
|
|
|
result := <-uchan
|
|
if result.NErr != nil {
|
|
var appErr *model.AppError
|
|
var invErr *store.ErrInvalidInput
|
|
switch {
|
|
case errors.As(result.NErr, &appErr):
|
|
return nil, appErr
|
|
case errors.As(result.NErr, &invErr):
|
|
return nil, model.NewAppError("UpdateUserRoles", "app.user.update.find.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
default:
|
|
return nil, model.NewAppError("UpdateUserRoles", "app.user.update.finding.app_error", nil, result.NErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
ruser := result.Data.(*model.UserUpdate).New
|
|
|
|
if result := <-schan; result.NErr != nil {
|
|
// soft error since the user roles were still updated
|
|
mlog.Warn("Failed during updating user roles", mlog.Err(result.NErr))
|
|
}
|
|
|
|
a.InvalidateCacheForUser(user.Id)
|
|
a.ClearSessionCacheForUser(user.Id)
|
|
|
|
if sendWebSocketEvent {
|
|
message := model.NewWebSocketEvent(model.WebsocketEventUserRoleUpdated, "", "", user.Id, nil)
|
|
message.Add("user_id", user.Id)
|
|
message.Add("roles", newRoles)
|
|
a.Publish(message)
|
|
}
|
|
|
|
return ruser, nil
|
|
}
|
|
|
|
func (a *App) PermanentDeleteUser(c *request.Context, user *model.User) *model.AppError {
|
|
mlog.Warn("Attempting to permanently delete account", mlog.String("user_id", user.Id), mlog.String("user_email", user.Email))
|
|
if user.IsInRole(model.SystemAdminRoleId) {
|
|
mlog.Warn("You are deleting a user that is a system administrator. You may need to set another account as the system administrator using the command line tools.", mlog.String("user_email", user.Email))
|
|
}
|
|
|
|
if _, err := a.UpdateActive(c, user, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := a.Srv().Store.Session().PermanentDeleteSessionsByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.session.permanent_delete_sessions_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.UserAccessToken().DeleteAllForUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.user_access_token.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.OAuth().PermanentDeleteAuthDataByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.oauth.permanent_delete_auth_data_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Webhook().PermanentDeleteIncomingByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.webhooks.permanent_delete_incoming_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Webhook().PermanentDeleteOutgoingByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.webhooks.permanent_delete_outgoing_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Command().PermanentDeleteByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.user.permanentdeleteuser.internal_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Preference().PermanentDeleteByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.preference.permanent_delete_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Channel().PermanentDeleteMembersByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.channel.permanent_delete_members_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Group().PermanentDeleteMembersByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.group.permanent_delete_members_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Post().PermanentDeleteByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.post.permanent_delete_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Bot().PermanentDelete(user.Id); err != nil {
|
|
var invErr *store.ErrInvalidInput
|
|
switch {
|
|
case errors.As(err, &invErr):
|
|
return model.NewAppError("PermanentDeleteUser", "app.bot.permenent_delete.bad_id", map[string]interface{}{"user_id": invErr.Value}, invErr.Error(), http.StatusBadRequest)
|
|
default: // last fallback in case it doesn't map to an existing app error.
|
|
return model.NewAppError("PermanentDeleteUser", "app.bot.permanent_delete.internal_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
infos, err := a.Srv().Store.FileInfo().GetForUser(user.Id)
|
|
if err != nil {
|
|
mlog.Warn("Error getting file list for user from FileInfoStore", mlog.Err(err))
|
|
}
|
|
|
|
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 model.NewAppError("PermanentDeleteUser", "app.file_info.permanent_delete_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.User().PermanentDelete(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.user.permanent_delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Audit().PermanentDeleteByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.audit.permanent_delete_by_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if err := a.Srv().Store.Team().RemoveAllMembersByUser(user.Id); err != nil {
|
|
return model.NewAppError("PermanentDeleteUser", "app.team.remove_member.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
mlog.Warn("Permanently deleted account", mlog.String("user_email", user.Email), mlog.String("user_id", user.Id))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) PermanentDeleteAllUsers(c *request.Context) *model.AppError {
|
|
users, err := a.Srv().Store.User().GetAll()
|
|
if err != nil {
|
|
return model.NewAppError("PermanentDeleteAllUsers", "app.user.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
for _, user := range users {
|
|
a.PermanentDeleteUser(c, user)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) SendEmailVerification(user *model.User, newEmail, redirect string) *model.AppError {
|
|
token, err := a.Srv().EmailService.CreateVerifyEmailToken(user.Id, newEmail)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, email.CreateEmailTokenError):
|
|
return model.NewAppError("CreateVerifyEmailToken", "api.user.create_email_token.error", nil, "", http.StatusInternalServerError)
|
|
default:
|
|
return model.NewAppError("CreateVerifyEmailToken", "app.recover.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
if _, err := a.GetStatus(user.Id); err != nil {
|
|
eErr := a.Srv().EmailService.SendVerifyEmail(newEmail, user.Locale, a.GetSiteURL(), token.Token, redirect)
|
|
if eErr != nil {
|
|
return model.NewAppError("SendVerifyEmail", "api.user.send_verify_email_and_forget.failed.error", nil, eErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if err := a.Srv().EmailService.SendEmailChangeVerifyEmail(newEmail, user.Locale, a.GetSiteURL(), token.Token); err != nil {
|
|
return model.NewAppError("sendEmailChangeVerifyEmail", "api.user.send_email_change_verify_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) VerifyEmailFromToken(userSuppliedTokenString string) *model.AppError {
|
|
token, err := a.GetVerifyEmailToken(userSuppliedTokenString)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if model.GetMillis()-token.CreateAt >= PasswordRecoverExpiryTime {
|
|
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
|
|
}
|
|
|
|
tokenData.Email = strings.ToLower(tokenData.Email)
|
|
if err := a.VerifyUserEmail(tokenData.UserId, tokenData.Email); err != nil {
|
|
return err
|
|
}
|
|
|
|
if user.Email != tokenData.Email {
|
|
a.Srv().Go(func() {
|
|
if err := a.Srv().EmailService.SendEmailChangeEmail(user.Email, tokenData.Email, user.Locale, a.GetSiteURL()); err != nil {
|
|
mlog.Error("Failed to send email change email", mlog.Err(err))
|
|
}
|
|
})
|
|
}
|
|
|
|
if err := a.DeleteToken(token); err != nil {
|
|
mlog.Warn("Failed to delete token", mlog.Err(err))
|
|
}
|
|
|
|
return 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 != TokenTypeVerifyEmail {
|
|
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, model.NewAppError("GetTotalUsersStats", "app.user.get_total_users_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
stats := &model.UsersStats{
|
|
TotalUsersCount: count,
|
|
}
|
|
return stats, nil
|
|
}
|
|
|
|
// GetFilteredUsersStats is used to get a count of users based on the set of filters supported by UserCountOptions.
|
|
func (a *App) GetFilteredUsersStats(options *model.UserCountOptions) (*model.UsersStats, *model.AppError) {
|
|
count, err := a.Srv().Store.User().Count(*options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetFilteredUsersStats", "app.user.get_total_users_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
stats := &model.UsersStats{
|
|
TotalUsersCount: count,
|
|
}
|
|
return stats, nil
|
|
}
|
|
|
|
func (a *App) VerifyUserEmail(userID, email string) *model.AppError {
|
|
if _, err := a.Srv().Store.User().VerifyEmail(userID, email); err != nil {
|
|
return model.NewAppError("VerifyUserEmail", "app.user.verify_email.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
a.InvalidateCacheForUser(userID)
|
|
|
|
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)
|
|
}
|
|
if props.InGroupId != "" {
|
|
return a.SearchUsersInGroup(props.InGroupId, 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, model.NewAppError("SearchUsersInChannel", "app.user.search.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
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, model.NewAppError("SearchUsersNotInChannel", "app.user.search.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
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) {
|
|
term = strings.TrimSpace(term)
|
|
|
|
users, err := a.Srv().Store.User().Search(teamID, term, options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("SearchUsersInTeam", "app.user.search.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
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, model.NewAppError("SearchUsersNotInTeam", "app.user.search.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
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, model.NewAppError("SearchUsersWithoutTeam", "app.user.search.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for _, user := range users {
|
|
a.SanitizeProfile(user, options.IsAdmin)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) SearchUsersInGroup(groupID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
|
term = strings.TrimSpace(term)
|
|
users, err := a.Srv().Store.User().SearchInGroup(groupID, term, options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("SearchUsersInGroup", "app.user.search.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for _, user := range users {
|
|
a.SanitizeProfile(user, options.IsAdmin)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) AutocompleteUsersInChannel(teamID string, channelID string, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, *model.AppError) {
|
|
term = strings.TrimSpace(term)
|
|
|
|
autocomplete, err := a.Srv().Store.User().AutocompleteUsersInChannel(teamID, channelID, term, options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("AutocompleteUsersInChannel", "app.user.search.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for _, user := range autocomplete.InChannel {
|
|
a.SanitizeProfile(user, options.IsAdmin)
|
|
}
|
|
|
|
for _, user := range autocomplete.OutOfChannel {
|
|
a.SanitizeProfile(user, options.IsAdmin)
|
|
}
|
|
|
|
return autocomplete, nil
|
|
}
|
|
|
|
func (a *App) AutocompleteUsersInTeam(teamID string, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInTeam, *model.AppError) {
|
|
term = strings.TrimSpace(term)
|
|
|
|
users, err := a.Srv().Store.User().Search(teamID, term, options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("AutocompleteUsersInTeam", "app.user.search.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for _, user := range users {
|
|
a.SanitizeProfile(user, options.IsAdmin)
|
|
}
|
|
|
|
autocomplete := &model.UserAutocompleteInTeam{}
|
|
autocomplete.InTeam = users
|
|
return autocomplete, nil
|
|
}
|
|
|
|
func (a *App) UpdateOAuthUserAttrs(userData io.Reader, user *model.User, provider einterfaces.OAuthProvider, service string, tokenUser *model.User) *model.AppError {
|
|
oauthUser, err1 := provider.GetUserFromJSON(userData, tokenUser)
|
|
if err1 != nil {
|
|
return model.NewAppError("UpdateOAuthUserAttrs", "api.user.update_oauth_user_attrs.get_user.app_error", map[string]interface{}{"Service": service}, err1.Error(), 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 {
|
|
var appErr *model.AppError
|
|
var invErr *store.ErrInvalidInput
|
|
switch {
|
|
case errors.As(err, &appErr):
|
|
return appErr
|
|
case errors.As(err, &invErr):
|
|
return model.NewAppError("UpdateOAuthUserAttrs", "app.user.update.find.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
default:
|
|
return model.NewAppError("UpdateOAuthUserAttrs", "app.user.update.finding.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
user = users.New
|
|
a.InvalidateCacheForUser(user.Id)
|
|
}
|
|
|
|
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 excluding bots.
|
|
func (a *App) FilterNonGroupTeamMembers(userIDs []string, team *model.Team) ([]string, error) {
|
|
teamGroupUsers, err := a.GetTeamGroupUsers(team.Id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return a.filterNonGroupUsers(userIDs, teamGroupUsers)
|
|
}
|
|
|
|
// FilterNonGroupChannelMembers returns the subset of the given user IDs of the users who are not members of groups
|
|
// associated to the channel excluding bots
|
|
func (a *App) FilterNonGroupChannelMembers(userIDs []string, channel *model.Channel) ([]string, error) {
|
|
channelGroupUsers, err := a.GetChannelGroupUsers(channel.Id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return a.filterNonGroupUsers(userIDs, channelGroupUsers)
|
|
}
|
|
|
|
// filterNonGroupUsers is a helper function that takes a list of user ids and a list of users
|
|
// and returns the list of normal users present in userIDs but not in groupUsers.
|
|
func (a *App) filterNonGroupUsers(userIDs []string, groupUsers []*model.User) ([]string, error) {
|
|
nonMemberIds := []string{}
|
|
users, err := a.Srv().Store.User().GetProfileByIds(context.Background(), userIDs, nil, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, user := range users {
|
|
userIsMember := user.IsBot
|
|
|
|
for _, pu := range groupUsers {
|
|
if pu.Id == user.Id {
|
|
userIsMember = true
|
|
break
|
|
}
|
|
}
|
|
if !userIsMember {
|
|
nonMemberIds = append(nonMemberIds, user.Id)
|
|
}
|
|
}
|
|
|
|
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, model.NewAppError("UserCanSeeOtherUser", "app.team.user_belongs_to_teams.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
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) {
|
|
belongs, err := a.Srv().Store.Channel().UserBelongsToChannels(userID, channelIDs)
|
|
if err != nil {
|
|
return false, model.NewAppError("userBelongsToChannels", "app.channel.user_belongs_to_channels.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return belongs, nil
|
|
}
|
|
|
|
func (a *App) GetViewUsersRestrictions(userID string) (*model.ViewUsersRestrictions, *model.AppError) {
|
|
if a.HasPermissionTo(userID, model.PermissionViewMembers) {
|
|
return nil, nil
|
|
}
|
|
|
|
teamIDs, nErr := a.Srv().Store.Team().GetUserTeamIds(userID, true)
|
|
if nErr != nil {
|
|
return nil, model.NewAppError("GetViewUsersRestrictions", "app.team.get_user_team_ids.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
teamIDsWithPermission := []string{}
|
|
for _, teamID := range teamIDs {
|
|
if a.HasPermissionToTeam(userID, teamID, model.PermissionViewMembers) {
|
|
teamIDsWithPermission = append(teamIDsWithPermission, teamID)
|
|
}
|
|
}
|
|
|
|
userChannelMembers, err := a.Srv().Store.Channel().GetAllChannelMembersForUser(userID, true, true)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetViewUsersRestrictions", "app.channel.get_channels.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
channelIDs := []string{}
|
|
for channelID := range userChannelMembers {
|
|
channelIDs = append(channelIDs, channelID)
|
|
}
|
|
|
|
return &model.ViewUsersRestrictions{Teams: teamIDsWithPermission, Channels: channelIDs}, nil
|
|
}
|
|
|
|
// PromoteGuestToUser Convert user's roles and all his mermbership's roles from
|
|
// guest roles to regular user roles.
|
|
func (a *App) PromoteGuestToUser(c *request.Context, user *model.User, requestorId string) *model.AppError {
|
|
nErr := a.ch.srv.userService.PromoteGuestToUser(user)
|
|
a.InvalidateCacheForUser(user.Id)
|
|
if nErr != nil {
|
|
return model.NewAppError("PromoteGuestToUser", "app.user.promote_guest.user_update.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
userTeams, nErr := a.Srv().Store.Team().GetTeamsByUserId(user.Id)
|
|
if nErr != nil {
|
|
return model.NewAppError("PromoteGuestToUser", "app.team.get_all.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for _, team := range userTeams {
|
|
// Soft error if there is an issue joining the default channels
|
|
if err := a.JoinDefaultChannels(c, team.Id, user, false, requestorId); err != nil {
|
|
mlog.Warn("Failed to join default channels", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.String("requestor_id", requestorId), mlog.Err(err))
|
|
}
|
|
}
|
|
|
|
promotedUser, err := a.GetUser(user.Id)
|
|
if err != nil {
|
|
mlog.Warn("Failed to get user on promote guest to user", mlog.Err(err))
|
|
} else {
|
|
a.sendUpdatedUserEvent(*promotedUser)
|
|
if uErr := a.ch.srv.userService.UpdateSessionsIsGuest(promotedUser.Id, promotedUser.IsGuest()); uErr != nil {
|
|
mlog.Warn("Unable to update user sessions", mlog.String("user_id", promotedUser.Id), mlog.Err(uErr))
|
|
}
|
|
}
|
|
|
|
teamMembers, err := a.GetTeamMembersForUser(user.Id)
|
|
if err != nil {
|
|
mlog.Warn("Failed to get team members for user on promote guest to user", mlog.Err(err))
|
|
}
|
|
|
|
for _, member := range teamMembers {
|
|
a.sendUpdatedMemberRoleEvent(user.Id, member)
|
|
|
|
channelMembers, err := a.GetChannelMembersForUser(member.TeamId, user.Id)
|
|
if err != nil {
|
|
mlog.Warn("Failed to get channel members for user on promote guest to user", mlog.Err(err))
|
|
}
|
|
|
|
for _, member := range channelMembers {
|
|
a.invalidateCacheForChannelMembers(member.ChannelId)
|
|
|
|
evt := model.NewWebSocketEvent(model.WebsocketEventChannelMemberUpdated, "", "", user.Id, nil)
|
|
memberJSON, jsonErr := json.Marshal(member)
|
|
if jsonErr != nil {
|
|
mlog.Warn("Failed to encode channel member to JSON", mlog.Err(jsonErr))
|
|
}
|
|
evt.Add("channelMember", string(memberJSON))
|
|
a.Publish(evt)
|
|
}
|
|
}
|
|
|
|
a.ClearSessionCacheForUser(user.Id)
|
|
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 {
|
|
demotedUser, nErr := a.ch.srv.userService.DemoteUserToGuest(user)
|
|
a.InvalidateCacheForUser(user.Id)
|
|
if nErr != nil {
|
|
return model.NewAppError("DemoteUserToGuest", "app.user.demote_user_to_guest.user_update.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
a.sendUpdatedUserEvent(*demotedUser)
|
|
if uErr := a.ch.srv.userService.UpdateSessionsIsGuest(demotedUser.Id, demotedUser.IsGuest()); uErr != nil {
|
|
mlog.Warn("Unable to update user sessions", mlog.String("user_id", demotedUser.Id), mlog.Err(uErr))
|
|
}
|
|
|
|
teamMembers, err := a.GetTeamMembersForUser(user.Id)
|
|
if err != nil {
|
|
mlog.Warn("Failed to get team members for users on demote user to guest", mlog.Err(err))
|
|
}
|
|
|
|
for _, member := range teamMembers {
|
|
a.sendUpdatedMemberRoleEvent(user.Id, member)
|
|
|
|
channelMembers, err := a.GetChannelMembersForUser(member.TeamId, user.Id)
|
|
if err != nil {
|
|
mlog.Warn("Failed to get channel members for users on demote user to guest", mlog.Err(err))
|
|
continue
|
|
}
|
|
|
|
for _, member := range channelMembers {
|
|
a.invalidateCacheForChannelMembers(member.ChannelId)
|
|
|
|
evt := model.NewWebSocketEvent(model.WebsocketEventChannelMemberUpdated, "", "", user.Id, nil)
|
|
memberJSON, jsonErr := json.Marshal(member)
|
|
if jsonErr != nil {
|
|
mlog.Warn("Failed to encode channel member to JSON", mlog.Err(jsonErr))
|
|
}
|
|
evt.Add("channelMember", string(memberJSON))
|
|
a.Publish(evt)
|
|
}
|
|
}
|
|
|
|
a.ClearSessionCacheForUser(user.Id)
|
|
return nil
|
|
}
|
|
|
|
func (a *App) PublishUserTyping(userID, channelID, parentId string) *model.AppError {
|
|
omitUsers := make(map[string]bool, 1)
|
|
omitUsers[userID] = true
|
|
|
|
event := model.NewWebSocketEvent(model.WebsocketEventTyping, "", channelID, "", omitUsers)
|
|
event.Add("parent_id", parentId)
|
|
event.Add("user_id", userID)
|
|
a.Publish(event)
|
|
|
|
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("Error in getting users profile", mlog.String("user_id", userID), mlog.Err(userErr))
|
|
return
|
|
}
|
|
|
|
options := a.Config().GetSanitizeOptions()
|
|
user.SanitizeProfile(options)
|
|
|
|
message := model.NewWebSocketEvent(model.WebsocketEventUserUpdated, "", "", "", nil)
|
|
message.Add("user", user)
|
|
a.Publish(message)
|
|
}
|
|
|
|
// GetKnownUsers returns the list of user ids of users with any direct
|
|
// relationship with a user. That means any user sharing any channel, including
|
|
// direct and group channels.
|
|
func (a *App) GetKnownUsers(userID string) ([]string, *model.AppError) {
|
|
users, err := a.Srv().Store.User().GetKnownUsers(userID)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetKnownUsers", "app.user.get_known_users.get_users.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
// ConvertBotToUser converts a bot to user.
|
|
func (a *App) ConvertBotToUser(bot *model.Bot, userPatch *model.UserPatch, sysadmin bool) (*model.User, *model.AppError) {
|
|
user, nErr := a.Srv().Store.User().Get(context.Background(), bot.UserId)
|
|
if nErr != nil {
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(nErr, &nfErr):
|
|
return nil, model.NewAppError("ConvertBotToUser", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound)
|
|
default:
|
|
return nil, model.NewAppError("ConvertBotToUser", "app.user.get.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
if sysadmin && !user.IsInRole(model.SystemAdminRoleId) {
|
|
_, appErr := a.UpdateUserRoles(
|
|
user.Id,
|
|
fmt.Sprintf("%s %s", user.Roles, model.SystemAdminRoleId),
|
|
false)
|
|
if appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
}
|
|
|
|
user.Patch(userPatch)
|
|
|
|
user, err := a.UpdateUser(user, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = a.UpdatePassword(user, *userPatch.Password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
appErr := a.Srv().Store.Bot().PermanentDelete(bot.UserId)
|
|
if appErr != nil {
|
|
return nil, model.NewAppError("ConvertBotToUser", "app.user.convert_bot_to_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (a *App) GetThreadsForUser(userID, teamID string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) {
|
|
threads, err := a.Srv().Store.Thread().GetThreadsForUser(userID, teamID, options)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetThreadsForUser", "app.user.get_threads_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
for _, thread := range threads.Threads {
|
|
a.sanitizeProfiles(thread.Participants, false)
|
|
thread.Post.SanitizeProps()
|
|
}
|
|
return threads, nil
|
|
}
|
|
|
|
func (a *App) GetThreadMembershipForUser(userId, threadId string) (*model.ThreadMembership, *model.AppError) {
|
|
threadMembership, err := a.Srv().Store.Thread().GetMembershipForUser(userId, threadId)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetThreadMembershipForUser", "app.user.get_thread_membership_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
if threadMembership == nil {
|
|
return nil, model.NewAppError("GetThreadMembershipForUser", "app.user.get_thread_membership_for_user.not_found", nil, "thread membership not found/followed", http.StatusNotFound)
|
|
}
|
|
return threadMembership, nil
|
|
}
|
|
|
|
func (a *App) GetThreadForUser(teamID string, threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, *model.AppError) {
|
|
thread, err := a.Srv().Store.Thread().GetThreadForUser(teamID, threadMembership, extended)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetThreadForUser", "app.user.get_threads_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
if thread == nil {
|
|
return nil, model.NewAppError("GetThreadForUser", "app.user.get_threads_for_user.not_found", nil, "thread not found/followed", http.StatusNotFound)
|
|
}
|
|
a.sanitizeProfiles(thread.Participants, false)
|
|
thread.Post.SanitizeProps()
|
|
return thread, nil
|
|
}
|
|
|
|
func (a *App) UpdateThreadsReadForUser(userID, teamID string) *model.AppError {
|
|
nErr := a.Srv().Store.Thread().MarkAllAsRead(userID, teamID)
|
|
if nErr != nil {
|
|
return model.NewAppError("UpdateThreadsReadForUser", "app.user.update_threads_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
message := model.NewWebSocketEvent(model.WebsocketEventThreadReadChanged, teamID, "", userID, nil)
|
|
a.Publish(message)
|
|
return nil
|
|
}
|
|
|
|
func (a *App) UpdateThreadFollowForUser(userID, teamID, threadID string, state bool) *model.AppError {
|
|
opts := store.ThreadMembershipOpts{
|
|
Following: state,
|
|
IncrementMentions: false,
|
|
UpdateFollowing: true,
|
|
UpdateViewedTimestamp: state,
|
|
UpdateParticipants: false,
|
|
}
|
|
_, err := a.Srv().Store.Thread().MaintainMembership(userID, threadID, opts)
|
|
if err != nil {
|
|
return model.NewAppError("UpdateThreadFollowForUser", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
thread, err := a.Srv().Store.Thread().Get(threadID)
|
|
if err != nil {
|
|
return model.NewAppError("UpdateThreadFollowForUser", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
replyCount := int64(0)
|
|
if thread != nil {
|
|
replyCount = thread.ReplyCount
|
|
}
|
|
message := model.NewWebSocketEvent(model.WebsocketEventThreadFollowChanged, teamID, "", userID, nil)
|
|
message.Add("thread_id", threadID)
|
|
message.Add("state", state)
|
|
message.Add("reply_count", replyCount)
|
|
a.Publish(message)
|
|
return nil
|
|
}
|
|
|
|
func (a *App) UpdateThreadReadForUser(currentSessionId, userID, teamID, threadID string, timestamp int64) (*model.ThreadResponse, *model.AppError) {
|
|
user, err := a.GetUser(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
opts := store.ThreadMembershipOpts{
|
|
Following: true,
|
|
UpdateFollowing: true,
|
|
}
|
|
membership, storeErr := a.Srv().Store.Thread().MaintainMembership(userID, threadID, opts)
|
|
if storeErr != nil {
|
|
return nil, model.NewAppError("UpdateThreadReadForUser", "app.user.update_thread_read_for_user.app_error", nil, storeErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
post, err := a.GetSinglePost(threadID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
membership.UnreadMentions, err = a.countThreadMentions(user, post, teamID, timestamp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, nErr := a.Srv().Store.Thread().UpdateMembership(membership)
|
|
if nErr != nil {
|
|
return nil, model.NewAppError("UpdateThreadReadForUser", "app.user.update_thread_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
membership.LastViewed = timestamp
|
|
nErr = a.Srv().Store.Thread().MarkAsRead(userID, threadID, timestamp)
|
|
if nErr != nil {
|
|
return nil, model.NewAppError("UpdateThreadReadForUser", "app.user.update_thread_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
thread, err := a.GetThreadForUser(teamID, membership, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Clear if user has read the messages
|
|
if thread.UnreadReplies == 0 && a.IsCRTEnabledForUser(userID) {
|
|
a.clearPushNotification(currentSessionId, userID, post.ChannelId, threadID)
|
|
}
|
|
|
|
message := model.NewWebSocketEvent(model.WebsocketEventThreadReadChanged, teamID, "", userID, nil)
|
|
message.Add("thread_id", threadID)
|
|
message.Add("timestamp", timestamp)
|
|
message.Add("unread_mentions", membership.UnreadMentions)
|
|
message.Add("unread_replies", thread.UnreadReplies)
|
|
message.Add("channel_id", post.ChannelId)
|
|
a.Publish(message)
|
|
return thread, nil
|
|
}
|
|
|
|
func getProfileImagePath(userID string) string {
|
|
return filepath.Join("users", userID, "profile.png")
|
|
}
|