// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package app import ( "bytes" "context" "encoding/json" "errors" "fmt" "image" "io" "mime/multipart" "net/http" "net/url" "sort" "strings" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/shared/i18n" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" "github.com/mattermost/mattermost/server/v8/channels/app/email" "github.com/mattermost/mattermost/server/v8/channels/app/imaging" "github.com/mattermost/mattermost/server/v8/channels/app/teams" "github.com/mattermost/mattermost/server/v8/channels/app/users" "github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/channels/store/sqlstore" ) func (a *App) AdjustTeamsFromProductLimits(teamLimits *model.TeamsLimits) *model.AppError { maxActiveTeams := *teamLimits.Active teams, appErr := a.GetAllTeams() if appErr != nil { return appErr } if teams == nil { return nil } // Sort the list of teams based on their creation date sort.Slice(teams, func(i, j int) bool { return teams[i].CreateAt < teams[j].CreateAt }) var activeTeams []*model.Team var cloudArchivedTeams []*model.Team for _, team := range teams { if team.DeleteAt == 0 { activeTeams = append(activeTeams, team) } if team.DeleteAt > 0 && team.CloudLimitsArchived { cloudArchivedTeams = append(cloudArchivedTeams, team) } } if len(activeTeams) > maxActiveTeams { // If there are more active teams than allowed, we must archive them // Remove the first n elements (where n is the allowed number of teams) so they aren't archived teamsToArchive := activeTeams[maxActiveTeams:] for _, team := range teamsToArchive { cloudLimitsArchived := true // Archive the remainder patch := model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived} _, err := a.PatchTeam(team.Id, &patch) if err != nil { return err } err = a.SoftDeleteTeam(team.Id) if err != nil { return err } } } else if len(activeTeams) < maxActiveTeams && len(cloudArchivedTeams) > 0 { // If the number of activeTeams is less than the allowed limit, and there are some cloudArchivedTeams, we can restore these cloudArchivedTeams activeTeamsBeforeLimit := maxActiveTeams - len(activeTeams) teamsToRestore := cloudArchivedTeams // If the number of active teams remaining before the limit is hit is fewer than the number of cloudArchivedTeams, trim the list (still according to CreateAt) // Otherwise, we can restore all of the cloudArchivedTeams without hitting the limit, so don't filter the list if activeTeamsBeforeLimit < len(cloudArchivedTeams) { teamsToRestore = cloudArchivedTeams[:(activeTeamsBeforeLimit)] } cloudLimitsArchived := false patch := &model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived} for _, team := range teamsToRestore { err := a.RestoreTeam(team.Id) if err != nil { return err } _, err = a.PatchTeam(team.Id, patch) if err != nil { return err } } } return nil } func (a *App) CreateTeam(rctx request.CTX, team *model.Team) (*model.Team, *model.AppError) { rteam, err := a.ch.srv.teamService.CreateTeam(rctx, team) if err != nil { var invErr *store.ErrInvalidInput var cErr *store.ErrConflict var ltErr *store.ErrLimitExceeded var appErr *model.AppError switch { case errors.As(err, &invErr): switch { case invErr.Entity == "Channel" && invErr.Field == "DeleteAt": return nil, model.NewAppError("CreateTeam", "store.sql_channel.save.archived_channel.app_error", nil, "", http.StatusBadRequest).Wrap(err) case invErr.Entity == "Channel" && invErr.Field == "Type": return nil, model.NewAppError("CreateTeam", "store.sql_channel.save.direct_channel.app_error", nil, "", http.StatusBadRequest).Wrap(err) case invErr.Entity == "Channel" && invErr.Field == "Id": return nil, model.NewAppError("CreateTeam", "store.sql_channel.save_channel.existing.app_error", nil, "id="+invErr.Value.(string), http.StatusBadRequest).Wrap(err) case invErr.Entity == "Team" && invErr.Field == "id": return nil, model.NewAppError("CreateTeam", "store.sql_team.save_team.existing.app_error", nil, "id="+invErr.Value.(string), http.StatusBadRequest).Wrap(err) default: return nil, model.NewAppError("CreateTeam", "app.team.save.existing.app_error", nil, "", http.StatusBadRequest).Wrap(err) } case errors.As(err, &cErr): return nil, model.NewAppError("CreateTeam", store.ChannelExistsError, nil, "", http.StatusBadRequest).Wrap(err) case errors.As(err, <Err): return nil, model.NewAppError("CreateTeam", "store.sql_channel.save_channel.limit.app_error", nil, "", http.StatusBadRequest).Wrap(err) case errors.As(err, &appErr): return nil, appErr default: return nil, model.NewAppError("CreateTeam", "app.team.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } return rteam, nil } func (a *App) CreateTeamWithUser(rctx request.CTX, team *model.Team, userID string) (*model.Team, *model.AppError) { user, err := a.GetUser(userID) if err != nil { return nil, err } team.Email = user.Email if !a.ch.srv.teamService.IsTeamEmailAllowed(user, team) { return nil, model.NewAppError("CreateTeamWithUser", "api.team.is_team_creation_allowed.domain.app_error", nil, "", http.StatusBadRequest) } rteam, err := a.CreateTeam(rctx, team) if err != nil { return nil, err } if _, err := a.JoinUserToTeam(rctx, rteam, user, ""); err != nil { return nil, err } return rteam, nil } func (a *App) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) { oldTeam, err := a.ch.srv.teamService.UpdateTeam(team, teams.UpdateOptions{Sanitized: true}) if err != nil { var invErr *store.ErrInvalidInput var appErr *model.AppError var domErr *teams.DomainError var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): return nil, model.NewAppError("UpdateTeam", "app.team.get.find.app_error", nil, "", http.StatusNotFound).Wrap(err) case errors.As(err, &invErr): return nil, model.NewAppError("UpdateTeam", "app.team.update.find.app_error", nil, "", http.StatusBadRequest).Wrap(err) case errors.As(err, &appErr): return nil, appErr case errors.As(err, &domErr): return nil, model.NewAppError("UpdateTeam", "api.team.update_restricted_domains.mismatch.app_error", map[string]any{"Domain": domErr.Domain}, "", http.StatusBadRequest).Wrap(err) default: return nil, model.NewAppError("UpdateTeam", "app.team.update.updating.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } if appErr := a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeam); appErr != nil { return nil, appErr } return oldTeam, nil } // RenameTeam is used to rename the team Name and the DisplayName fields func (a *App) RenameTeam(team *model.Team, newTeamName string, newDisplayName string) (*model.Team, *model.AppError) { // check if name is occupied _, errnf := a.GetTeamByName(newTeamName) // "-" can be used as a newTeamName if only DisplayName change is wanted if errnf == nil && newTeamName != "-" { errbody := fmt.Sprintf("team with name %s already exists", newTeamName) return nil, model.NewAppError("RenameTeam", "app.team.rename_team.name_occupied", nil, errbody, http.StatusBadRequest) } if newTeamName != "-" { team.Name = newTeamName } if newDisplayName != "" { team.DisplayName = newDisplayName } newTeam, err := a.ch.srv.teamService.UpdateTeam(team, teams.UpdateOptions{}) if err != nil { var invErr *store.ErrInvalidInput var appErr *model.AppError var domErr *teams.DomainError var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): return nil, model.NewAppError("RenameTeam", "app.team.get.find.app_error", nil, "", http.StatusNotFound).Wrap(err) case errors.As(err, &invErr): return nil, model.NewAppError("RenameTeam", "app.team.update.find.app_error", nil, "", http.StatusBadRequest).Wrap(err) case errors.As(err, &appErr): return nil, appErr case errors.As(err, &domErr): return nil, model.NewAppError("RenameTeam", "api.team.update_restricted_domains.mismatch.app_error", map[string]any{"Domain": domErr.Domain}, "", http.StatusBadRequest).Wrap(err) default: return nil, model.NewAppError("RenameTeam", "app.team.update.updating.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } return newTeam, nil } func (a *App) UpdateTeamScheme(team *model.Team) (*model.Team, *model.AppError) { oldTeam, err := a.GetTeam(team.Id) if err != nil { return nil, err } oldTeam.SchemeId = team.SchemeId oldTeam, nErr := a.Srv().Store().Team().Update(oldTeam) if nErr != nil { var invErr *store.ErrInvalidInput var appErr *model.AppError switch { case errors.As(nErr, &invErr): return nil, model.NewAppError("UpdateTeamScheme", "app.team.update.find.app_error", nil, "", http.StatusBadRequest).Wrap(nErr) case errors.As(nErr, &appErr): return nil, appErr default: return nil, model.NewAppError("UpdateTeamScheme", "app.team.update.updating.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } nErr = a.ClearTeamMembersCache(team.Id) if nErr != nil { return nil, model.NewAppError("UpdateTeamScheme", "app.team.clear_cache.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } a.Srv().Store().Channel().ClearMembersForUserCache() if appErr := a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeamScheme); appErr != nil { return nil, appErr } return oldTeam, nil } func (a *App) UpdateTeamPrivacy(teamID string, teamType string, allowOpenInvite bool) *model.AppError { oldTeam, err := a.GetTeam(teamID) if err != nil { return err } // Force a regeneration of the invite token if changing a team to restricted. if (allowOpenInvite != oldTeam.AllowOpenInvite || teamType != oldTeam.Type) && (!allowOpenInvite || teamType == model.TeamInvite) { oldTeam.InviteId = model.NewId() } oldTeam.Type = teamType oldTeam.AllowOpenInvite = allowOpenInvite oldTeam, nErr := a.Srv().Store().Team().Update(oldTeam) if nErr != nil { var invErr *store.ErrInvalidInput var appErr *model.AppError switch { case errors.As(nErr, &invErr): return model.NewAppError("UpdateTeamPrivacy", "app.team.update.find.app_error", nil, "", http.StatusBadRequest).Wrap(nErr) case errors.As(nErr, &appErr): return appErr default: return model.NewAppError("UpdateTeamPrivacy", "app.team.update.updating.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } if appErr := a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeam); appErr != nil { return appErr } return nil } func (a *App) PatchTeam(teamID string, patch *model.TeamPatch) (*model.Team, *model.AppError) { team, err := a.ch.srv.teamService.PatchTeam(teamID, patch) if err != nil { var invErr *store.ErrInvalidInput var appErr *model.AppError var domErr *teams.DomainError var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): return nil, model.NewAppError("PatchTeam", "app.team.get.find.app_error", nil, "", http.StatusNotFound).Wrap(err) case errors.As(err, &invErr): return nil, model.NewAppError("PatchTeam", "app.team.update.find.app_error", nil, "", http.StatusBadRequest).Wrap(err) case errors.As(err, &appErr): return nil, appErr case errors.As(err, &domErr): return nil, model.NewAppError("PatchTeam", "api.team.update_restricted_domains.mismatch.app_error", map[string]any{"Domain": domErr.Domain}, "", http.StatusBadRequest).Wrap(err) default: return nil, model.NewAppError("PatchTeam", "app.team.update.updating.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } if appErr := a.sendTeamEvent(team, model.WebsocketEventUpdateTeam); appErr != nil { return nil, appErr } return team, nil } func (a *App) RegenerateTeamInviteId(teamID string) (*model.Team, *model.AppError) { team, err := a.GetTeam(teamID) if err != nil { return nil, err } team.InviteId = model.NewId() updatedTeam, nErr := a.Srv().Store().Team().Update(team) if nErr != nil { var invErr *store.ErrInvalidInput var appErr *model.AppError switch { case errors.As(nErr, &invErr): return nil, model.NewAppError("RegenerateTeamInviteId", "app.team.update.find.app_error", nil, "", http.StatusBadRequest).Wrap(nErr) case errors.As(nErr, &appErr): return nil, appErr default: return nil, model.NewAppError("RegenerateTeamInviteId", "app.team.update.updating.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } if appErr := a.sendTeamEvent(updatedTeam, model.WebsocketEventUpdateTeam); appErr != nil { return nil, appErr } return updatedTeam, nil } func (a *App) sendTeamEvent(team *model.Team, event model.WebsocketEventType) *model.AppError { sanitizedTeam := &model.Team{} *sanitizedTeam = *team sanitizedTeam.Sanitize() message := model.NewWebSocketEvent(event, sanitizedTeam.Id, "", "", nil, "") teamJSON, jsonErr := json.Marshal(sanitizedTeam) if jsonErr != nil { return model.NewAppError("sendTeamEvent", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("team", string(teamJSON)) a.Publish(message) return nil } func (a *App) GetSchemeRolesForTeam(teamID string) (string, string, string, *model.AppError) { team, err := a.GetTeam(teamID) if err != nil { return "", "", "", err } if team.SchemeId != nil && *team.SchemeId != "" { scheme, err := a.GetScheme(*team.SchemeId) if err != nil { return "", "", "", err } return scheme.DefaultTeamGuestRole, scheme.DefaultTeamUserRole, scheme.DefaultTeamAdminRole, nil } return model.TeamGuestRoleId, model.TeamUserRoleId, model.TeamAdminRoleId, nil } func (a *App) UpdateTeamMemberRoles(rctx request.CTX, teamID string, userID string, newRoles string) (*model.TeamMember, *model.AppError) { member, nErr := a.Srv().Store().Team().GetMember(rctx, teamID, userID) if nErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(nErr, &nfErr): return nil, model.NewAppError("UpdateTeamMemberRoles", "app.team.get_member.missing.app_error", nil, "", http.StatusNotFound).Wrap(nErr) default: return nil, model.NewAppError("UpdateTeamMemberRoles", "app.team.get_member.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } if member == nil { return nil, model.NewAppError("UpdateTeamMemberRoles", "api.team.update_member_roles.not_a_member", nil, "userId="+userID+" teamId="+teamID, http.StatusBadRequest) } schemeGuestRole, schemeUserRole, schemeAdminRole, err := a.GetSchemeRolesForTeam(teamID) if err != nil { return nil, err } prevSchemeGuestValue := member.SchemeGuest var newExplicitRoles []string member.SchemeGuest = false member.SchemeUser = false member.SchemeAdmin = false for roleName := range strings.FieldsSeq(newRoles) { var role *model.Role role, err = a.GetRoleByName(rctx, roleName) if err != nil { err.StatusCode = http.StatusBadRequest return nil, err } if !role.SchemeManaged { // The role is not scheme-managed, so it's OK to apply it to the explicit roles field. newExplicitRoles = append(newExplicitRoles, roleName) } else { // The role is scheme-managed, so need to check if it is part of the scheme for this channel or not. switch roleName { case schemeAdminRole: member.SchemeAdmin = true case schemeUserRole: member.SchemeUser = true case schemeGuestRole: member.SchemeGuest = true default: // If not part of the scheme for this team, then it is not allowed to apply it as an explicit role. return nil, model.NewAppError("UpdateTeamMemberRoles", "api.channel.update_team_member_roles.scheme_role.app_error", nil, "role_name="+roleName, http.StatusBadRequest) } } } if member.SchemeGuest && member.SchemeUser { return nil, model.NewAppError("UpdateTeamMemberRoles", "api.team.update_team_member_roles.guest_and_user.app_error", nil, "", http.StatusBadRequest) } if prevSchemeGuestValue != member.SchemeGuest { return nil, model.NewAppError("UpdateTeamMemberRoles", "api.channel.update_team_member_roles.changing_guest_role.app_error", nil, "", http.StatusBadRequest) } member.ExplicitRoles = strings.Join(newExplicitRoles, " ") member, nErr = a.Srv().Store().Team().UpdateMember(rctx, member) if nErr != nil { var appErr *model.AppError switch { case errors.As(nErr, &appErr): return nil, appErr default: return nil, model.NewAppError("UpdateTeamMemberRoles", "app.team.save_member.save.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } a.ClearSessionCacheForUser(userID) if appErr := a.sendUpdatedTeamMemberEvent(member); appErr != nil { return nil, appErr } return member, nil } func (a *App) UpdateTeamMemberSchemeRoles(rctx request.CTX, teamID string, userID string, isSchemeGuest bool, isSchemeUser bool, isSchemeAdmin bool) (*model.TeamMember, *model.AppError) { member, err := a.GetTeamMember(rctx, teamID, userID) if err != nil { return nil, err } if member.SchemeGuest { return nil, model.NewAppError("UpdateTeamMemberSchemeRoles", "api.team.update_team_member_roles.guest.app_error", nil, "", http.StatusBadRequest) } if isSchemeGuest { return nil, model.NewAppError("UpdateTeamMemberSchemeRoles", "api.team.update_team_member_roles.user_and_guest.app_error", nil, "", http.StatusBadRequest) } if !isSchemeUser { return nil, model.NewAppError("UpdateTeamMemberSchemeRoles", "api.team.update_team_member_roles.unset_user_scheme.app_error", nil, "", http.StatusBadRequest) } member.SchemeAdmin = isSchemeAdmin member.SchemeUser = isSchemeUser member.SchemeGuest = isSchemeGuest // If the migration is not completed, we also need to check the default team_admin/team_user roles are not present in the roles field. if err = a.IsPhase2MigrationCompleted(); err != nil { member.ExplicitRoles = removeRoles([]string{model.TeamGuestRoleId, model.TeamUserRoleId, model.TeamAdminRoleId}, member.ExplicitRoles) } member, nErr := a.Srv().Store().Team().UpdateMember(rctx, member) if nErr != nil { var appErr *model.AppError switch { case errors.As(nErr, &appErr): return nil, appErr default: return nil, model.NewAppError("UpdateTeamMemberSchemeRoles", "app.team.save_member.save.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } a.ClearSessionCacheForUser(userID) if appErr := a.sendUpdatedTeamMemberEvent(member); appErr != nil { return nil, appErr } return member, nil } func (a *App) sendUpdatedTeamMemberEvent(member *model.TeamMember) *model.AppError { message := model.NewWebSocketEvent(model.WebsocketEventMemberroleUpdated, "", "", member.UserId, nil, "") tmJSON, jsonErr := json.Marshal(member) if jsonErr != nil { return model.NewAppError("sendUpdatedMemberRoleEvent", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("member", string(tmJSON)) a.Publish(message) return nil } func (a *App) AddUserToTeam(rctx request.CTX, teamID string, userID string, userRequestorId string) (*model.Team, *model.TeamMember, *model.AppError) { tchan := make(chan store.StoreResult[*model.Team], 1) go func() { team, err := a.Srv().Store().Team().Get(teamID) tchan <- store.StoreResult[*model.Team]{Data: team, NErr: err} close(tchan) }() uchan := make(chan store.StoreResult[*model.User], 1) go func() { user, err := a.Srv().Store().User().Get(context.Background(), userID) uchan <- store.StoreResult[*model.User]{Data: user, NErr: err} close(uchan) }() teamChanResult := <-tchan if teamChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(teamChanResult.NErr, &nfErr): return nil, nil, model.NewAppError("AddUserToTeam", "app.team.get.find.app_error", nil, "", http.StatusNotFound).Wrap(teamChanResult.NErr) default: return nil, nil, model.NewAppError("AddUserToTeam", "app.team.get.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(teamChanResult.NErr) } } team := teamChanResult.Data userChanResult := <-uchan if userChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(userChanResult.NErr, &nfErr): return nil, nil, model.NewAppError("AddUserToTeam", MissingAccountError, nil, "", http.StatusNotFound).Wrap(userChanResult.NErr) default: return nil, nil, model.NewAppError("AddUserToTeam", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(userChanResult.NErr) } } user := userChanResult.Data teamMember, err := a.JoinUserToTeam(rctx, team, user, userRequestorId) if err != nil { return nil, nil, err } return team, teamMember, nil } func (a *App) AddUserToTeamByTeamId(rctx request.CTX, teamID string, user *model.User) *model.AppError { team, err := a.Srv().Store().Team().Get(teamID) if err != nil { var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): return model.NewAppError("AddUserToTeamByTeamId", "app.team.get.find.app_error", nil, "", http.StatusNotFound).Wrap(err) default: return model.NewAppError("AddUserToTeamByTeamId", "app.team.get.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } if _, err := a.JoinUserToTeam(rctx, team, user, ""); err != nil { return err } return nil } func (a *App) AddUserToTeamByToken(rctx request.CTX, userID string, tokenID string) (*model.Team, *model.TeamMember, *model.AppError) { token, err := a.Srv().Store().Token().GetByToken(tokenID) if err != nil { return nil, nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.signup_link_invalid.app_error", nil, "", http.StatusBadRequest).Wrap(err) } if token.Type != TokenTypeTeamInvitation && token.Type != TokenTypeGuestInvitation { return nil, nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.signup_link_invalid.app_error", nil, "", http.StatusBadRequest) } if model.GetMillis()-token.CreateAt >= InvitationExpiryTime { if err := a.DeleteToken(token); err != nil { rctx.Logger().Warn("Error deleting expired team invitation token during team join", mlog.String("token_id", token.Token), mlog.String("token_type", token.Type), mlog.Err(err)) } return nil, nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.signup_link_expired.app_error", nil, "", http.StatusBadRequest) } tokenData := model.MapFromJSON(strings.NewReader(token.Extra)) tchan := make(chan store.StoreResult[*model.Team], 1) go func() { team, err := a.Srv().Store().Team().Get(tokenData["teamId"]) tchan <- store.StoreResult[*model.Team]{Data: team, NErr: err} close(tchan) }() uchan := make(chan store.StoreResult[*model.User], 1) go func() { user, err := a.Srv().Store().User().Get(context.Background(), userID) uchan <- store.StoreResult[*model.User]{Data: user, NErr: err} close(uchan) }() teamChanResult := <-tchan if teamChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(teamChanResult.NErr, &nfErr): return nil, nil, model.NewAppError("AddUserToTeamByToken", "app.team.get.find.app_error", nil, "", http.StatusNotFound).Wrap(teamChanResult.NErr) default: return nil, nil, model.NewAppError("AddUserToTeamByToken", "app.team.get.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(teamChanResult.NErr) } } team := teamChanResult.Data if team.IsGroupConstrained() { return nil, nil, model.NewAppError("AddUserToTeamByToken", "app.team.invite_token.group_constrained.error", nil, "", http.StatusForbidden) } userChanResult := <-uchan if userChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(userChanResult.NErr, &nfErr): return nil, nil, model.NewAppError("AddUserToTeamByToken", MissingAccountError, nil, "", http.StatusNotFound).Wrap(userChanResult.NErr) default: return nil, nil, model.NewAppError("AddUserToTeamByToken", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(userChanResult.NErr) } } user := userChanResult.Data if user.IsGuest() && token.Type == TokenTypeTeamInvitation { return nil, nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.invalid_invitation_type.app_error", nil, "", http.StatusBadRequest) } if !user.IsGuest() && token.Type == TokenTypeGuestInvitation { return nil, nil, model.NewAppError("AddUserToTeamByToken", "api.user.create_user.invalid_invitation_type.app_error", nil, "", http.StatusBadRequest) } teamMember, appErr := a.JoinUserToTeam(rctx, team, user, "") if appErr != nil { return nil, nil, appErr } if token.Type == TokenTypeGuestInvitation { channels, err := a.Srv().Store().Channel().GetChannelsByIds(strings.Split(tokenData["channels"], " "), false) if err != nil { return nil, nil, model.NewAppError("AddUserToTeamByToken", "app.channel.get_channels_by_ids.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } for _, channel := range channels { _, err := a.AddUserToChannel(rctx, user, channel, false) if err != nil { rctx.Logger().Warn("Error adding user to channel", mlog.Err(err)) } } } if err := a.DeleteToken(token); err != nil { rctx.Logger().Warn("Error while deleting token", mlog.Err(err)) } return team, teamMember, nil } func (a *App) AddUserToTeamByInviteId(rctx request.CTX, inviteId string, userID string) (*model.Team, *model.TeamMember, *model.AppError) { tchan := make(chan store.StoreResult[*model.Team], 1) go func() { team, err := a.Srv().Store().Team().GetByInviteId(inviteId) tchan <- store.StoreResult[*model.Team]{Data: team, NErr: err} close(tchan) }() uchan := make(chan store.StoreResult[*model.User], 1) go func() { user, err := a.Srv().Store().User().Get(context.Background(), userID) uchan <- store.StoreResult[*model.User]{Data: user, NErr: err} close(uchan) }() teamChanResult := <-tchan if teamChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(teamChanResult.NErr, &nfErr): return nil, nil, model.NewAppError("AddUserToTeamByInviteId", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusNotFound).Wrap(teamChanResult.NErr) default: return nil, nil, model.NewAppError("AddUserToTeamByInviteId", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(teamChanResult.NErr) } } team := teamChanResult.Data if team.IsGroupConstrained() { return nil, nil, model.NewAppError("AddUserToTeamByInviteId", "app.team.invite_id.group_constrained.error", nil, "", http.StatusForbidden) } userChanResult := <-uchan if userChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(userChanResult.NErr, &nfErr): return nil, nil, model.NewAppError("AddUserToTeamByInviteId", MissingAccountError, nil, "", http.StatusNotFound).Wrap(userChanResult.NErr) default: return nil, nil, model.NewAppError("AddUserToTeamByInviteId", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(userChanResult.NErr) } } user := userChanResult.Data teamMember, err := a.JoinUserToTeam(rctx, team, user, "") if err != nil { return nil, nil, err } return team, teamMember, nil } func (a *App) JoinUserToTeam(rctx request.CTX, team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) { teamMember, alreadyAdded, err := a.ch.srv.teamService.JoinUserToTeam(rctx, team, user) if err != nil { var appErr *model.AppError var conflictErr *store.ErrConflict var limitExceededErr *store.ErrLimitExceeded switch { case errors.Is(err, teams.AcceptedDomainError): return nil, model.NewAppError("JoinUserToTeam", "api.team.join_user_to_team.allowed_domains.app_error", nil, "", http.StatusBadRequest).Wrap(err) case errors.Is(err, teams.MemberCountError): return nil, model.NewAppError("JoinUserToTeam", "app.team.get_active_member_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) case errors.Is(err, teams.MaxMemberCountError): return nil, model.NewAppError("JoinUserToTeam", "app.team.join_user_to_team.max_accounts.app_error", nil, "teamId="+team.Id, http.StatusBadRequest).Wrap(err) case errors.As(err, &appErr): // in case we haven't converted to plain error. return nil, appErr case errors.As(err, &conflictErr): return nil, model.NewAppError("JoinUserToTeam", "app.team.join_user_to_team.save_member.conflict.app_error", nil, "", http.StatusBadRequest).Wrap(err) case errors.As(err, &limitExceededErr): return nil, model.NewAppError("JoinUserToTeam", "app.team.join_user_to_team.save_member.max_accounts.app_error", nil, "", http.StatusBadRequest).Wrap(err) default: // last fallback in case it doesn't map to an existing app error. return nil, model.NewAppError("JoinUserToTeam", "app.team.join_user_to_team.save_member.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } if alreadyAdded { return teamMember, nil } if _, err := a.Srv().Store().User().UpdateUpdateAt(user.Id); err != nil { return nil, model.NewAppError("JoinUserToTeam", "app.user.update_update.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if _, err := a.createInitialSidebarCategories(rctx, user.Id, team.Id); err != nil { rctx.Logger().Warn( "Encountered an issue creating default sidebar categories.", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.Err(err), ) } shouldBeAdmin := team.Email == user.Email if !user.IsGuest() { // Soft error if there is an issue joining the default channels if err := a.JoinDefaultChannels(rctx, team.Id, user, shouldBeAdmin, userRequestorId); err != nil { rctx.Logger().Warn( "Encountered an issue joining default channels.", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.Err(err), ) } } a.ClearSessionCacheForUser(user.Id) a.InvalidateCacheForUser(user.Id) a.invalidateCacheForUserTeams(user.Id) var actor *model.User if userRequestorId != "" { actor, _ = a.GetUser(userRequestorId) } a.Srv().Go(func() { pluginContext := pluginContext(rctx) a.ch.RunMultiHook(func(hooks plugin.Hooks, _ *model.Manifest) bool { hooks.UserHasJoinedTeam(pluginContext, teamMember, actor) return true }, plugin.UserHasJoinedTeamID) }) message := model.NewWebSocketEvent(model.WebsocketEventAddedToTeam, "", "", user.Id, nil, "") message.Add("team_id", team.Id) message.Add("user_id", user.Id) a.Publish(message) return teamMember, nil } func (a *App) GetTeam(teamID string) (*model.Team, *model.AppError) { team, err := a.ch.srv.teamService.GetTeam(teamID) if err != nil { var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): return nil, model.NewAppError("GetTeam", "app.team.get.find.app_error", nil, "", http.StatusNotFound).Wrap(err) default: return nil, model.NewAppError("GetTeam", "app.team.get.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } return team, nil } func (a *App) GetTeams(teamIDs []string) ([]*model.Team, *model.AppError) { teams, err := a.ch.srv.teamService.GetTeams(teamIDs) if err != nil { var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): return nil, model.NewAppError("GetTeam", "app.team.get.find.app_error", nil, "", http.StatusNotFound).Wrap(err) default: return nil, model.NewAppError("GetTeam", "app.team.get.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } return teams, nil } func (a *App) GetTeamByName(name string) (*model.Team, *model.AppError) { team, err := a.Srv().Store().Team().GetByName(name) if err != nil { var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): return nil, model.NewAppError("GetTeamByName", "app.team.get_by_name.missing.app_error", nil, "", http.StatusNotFound).Wrap(err) default: return nil, model.NewAppError("GetTeamByName", "app.team.get_by_name.app_error", nil, "", http.StatusNotFound).Wrap(err) } } return team, nil } func (a *App) GetTeamByInviteId(inviteId string) (*model.Team, *model.AppError) { team, err := a.Srv().Store().Team().GetByInviteId(inviteId) if err != nil { var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): return nil, model.NewAppError("GetTeamByInviteId", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusNotFound).Wrap(err) default: return nil, model.NewAppError("GetTeamByInviteId", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } return team, nil } func (a *App) GetAllTeams() ([]*model.Team, *model.AppError) { teams, err := a.Srv().Store().Team().GetAll() if err != nil { return nil, model.NewAppError("GetAllTeams", "app.team.get_all.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teams, nil } func (a *App) GetAllTeamsPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, *model.AppError) { teams, err := a.Srv().Store().Team().GetAllPage(offset, limit, opts) if err != nil { return nil, model.NewAppError("GetAllTeamsPage", "app.team.get_all.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teams, nil } func (a *App) GetAllTeamsPageWithCount(offset int, limit int, opts *model.TeamSearch) (*model.TeamsWithCount, *model.AppError) { totalCount, err := a.Srv().Store().Team().AnalyticsTeamCount(opts) if err != nil { return nil, model.NewAppError("GetAllTeamsPageWithCount", "app.team.analytics_team_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } teams, err := a.Srv().Store().Team().GetAllPage(offset, limit, opts) if err != nil { return nil, model.NewAppError("GetAllTeamsPageWithCount", "app.team.get_all.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return &model.TeamsWithCount{Teams: teams, TotalCount: totalCount}, nil } func (a *App) GetAllPrivateTeams() ([]*model.Team, *model.AppError) { teams, err := a.Srv().Store().Team().GetAllPrivateTeamListing() if err != nil { return nil, model.NewAppError("GetAllPrivateTeams", "app.team.get_all_private_team_listing.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teams, nil } func (a *App) GetAllPublicTeams() ([]*model.Team, *model.AppError) { teams, err := a.Srv().Store().Team().GetAllTeamListing() if err != nil { return nil, model.NewAppError("GetAllPublicTeams", "app.team.get_all_team_listing.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teams, nil } // SearchAllTeams returns a team list and the total count of the results func (a *App) SearchAllTeams(searchOpts *model.TeamSearch) ([]*model.Team, int64, *model.AppError) { if searchOpts.IsPaginated() { teams, count, err := a.Srv().Store().Team().SearchAllPaged(searchOpts) if err != nil { return nil, 0, model.NewAppError("SearchAllTeams", "app.team.search_all_team.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teams, count, nil } results, err := a.Srv().Store().Team().SearchAll(searchOpts) if err != nil { return nil, 0, model.NewAppError("SearchAllTeams", "app.team.search_all_team.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return results, int64(len(results)), nil } func (a *App) SearchPublicTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError) { teams, err := a.Srv().Store().Team().SearchOpen(searchOpts) if err != nil { return nil, model.NewAppError("SearchPublicTeams", "app.team.search_open_team.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teams, nil } func (a *App) SearchPrivateTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError) { teams, err := a.Srv().Store().Team().SearchPrivate(searchOpts) if err != nil { return nil, model.NewAppError("SearchPrivateTeams", "app.team.search_private_team.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teams, nil } func (a *App) GetTeamsForUser(userID string) ([]*model.Team, *model.AppError) { teams, err := a.Srv().Store().Team().GetTeamsByUserId(userID) if err != nil { return nil, model.NewAppError("GetTeamsForUser", "app.team.get_all.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teams, nil } func (a *App) GetTeamMember(rctx request.CTX, teamID, userID string) (*model.TeamMember, *model.AppError) { teamMember, err := a.Srv().Store().Team().GetMember(sqlstore.RequestContextWithMaster(rctx), teamID, userID) if err != nil { var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): return nil, model.NewAppError("GetTeamMember", "app.team.get_member.missing.app_error", nil, "", http.StatusNotFound).Wrap(err) default: return nil, model.NewAppError("GetTeamMember", "app.team.get_member.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } return teamMember, nil } func (a *App) GetTeamMembersForUser(rctx request.CTX, userID string, excludeTeamID string, includeDeleted bool) ([]*model.TeamMember, *model.AppError) { teamMembers, err := a.Srv().Store().Team().GetTeamsForUser(rctx, userID, excludeTeamID, includeDeleted) if err != nil { return nil, model.NewAppError("GetTeamMembersForUser", "app.team.get_members.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teamMembers, nil } func (a *App) GetTeamMembersForUserWithPagination(userID string, page, perPage int) ([]*model.TeamMember, *model.AppError) { teamMembers, err := a.Srv().Store().Team().GetTeamsForUserWithPagination(userID, page, perPage) if err != nil { return nil, model.NewAppError("GetTeamMembersForUserWithPagination", "app.team.get_members.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teamMembers, nil } func (a *App) GetTeamMembers(teamID string, offset int, limit int, teamMembersGetOptions *model.TeamMembersGetOptions) ([]*model.TeamMember, *model.AppError) { teamMembers, err := a.Srv().Store().Team().GetMembers(teamID, offset, limit, teamMembersGetOptions) if err != nil { return nil, model.NewAppError("GetTeamMembers", "app.team.get_members.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teamMembers, nil } func (a *App) GetTeamMembersByIds(teamID string, userIDs []string, restrictions *model.ViewUsersRestrictions) ([]*model.TeamMember, *model.AppError) { teamMembers, err := a.Srv().Store().Team().GetMembersByIds(teamID, userIDs, restrictions) if err != nil { return nil, model.NewAppError("GetTeamMembersByIds", "app.team.get_members_by_ids.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teamMembers, nil } func (a *App) GetCommonTeamIDsForTwoUsers(userID, otherUserID string) ([]string, *model.AppError) { teamIDs, err := a.Srv().Store().Team().GetCommonTeamIDsForTwoUsers(userID, otherUserID) if err != nil { return nil, model.NewAppError("GetCommonTeamIDsForUsers", "app.team.get_common_team_ids_for_users.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return teamIDs, nil } func (a *App) AddTeamMember(rctx request.CTX, teamID, userID string) (*model.TeamMember, *model.AppError) { _, teamMember, err := a.AddUserToTeam(rctx, teamID, userID, "") if err != nil { return nil, err } message := model.NewWebSocketEvent(model.WebsocketEventAddedToTeam, "", "", userID, nil, "") message.Add("team_id", teamID) message.Add("user_id", userID) a.Publish(message) return teamMember, nil } func (a *App) AddTeamMembers(rctx request.CTX, teamID string, userIDs []string, userRequestorId string, graceful bool) ([]*model.TeamMemberWithError, *model.AppError) { var membersWithErrors []*model.TeamMemberWithError for _, userID := range userIDs { _, teamMember, err := a.AddUserToTeam(rctx, teamID, userID, userRequestorId) if err != nil { if graceful { membersWithErrors = append(membersWithErrors, &model.TeamMemberWithError{ UserId: userID, Error: err, }) continue } return nil, err } membersWithErrors = append(membersWithErrors, &model.TeamMemberWithError{ UserId: userID, Member: teamMember, }) message := model.NewWebSocketEvent(model.WebsocketEventAddedToTeam, "", "", userID, nil, "") message.Add("team_id", teamID) message.Add("user_id", userID) a.Publish(message) } return membersWithErrors, nil } func (a *App) AddTeamMemberByToken(rctx request.CTX, userID, tokenID string) (*model.TeamMember, *model.AppError) { _, teamMember, err := a.AddUserToTeamByToken(rctx, userID, tokenID) if err != nil { return nil, err } return teamMember, nil } func (a *App) AddTeamMemberByInviteId(rctx request.CTX, inviteId, userID string) (*model.TeamMember, *model.AppError) { _, teamMember, err := a.AddUserToTeamByInviteId(rctx, inviteId, userID) if err != nil { return nil, err } return teamMember, nil } func (a *App) GetTeamUnread(teamID, userID string) (*model.TeamUnread, *model.AppError) { channelUnreads, err := a.Srv().Store().Team().GetChannelUnreadsForTeam(teamID, userID) if err != nil { return nil, model.NewAppError("GetTeamUnread", "app.team.get_unread.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } teamUnread := &model.TeamUnread{ MsgCount: 0, MentionCount: 0, MentionCountRoot: 0, MsgCountRoot: 0, TeamId: teamID, } for _, cu := range channelUnreads { teamUnread.MentionCount += cu.MentionCount teamUnread.MentionCountRoot += cu.MentionCountRoot if cu.NotifyProps[model.MarkUnreadNotifyProp] != model.ChannelMarkUnreadMention { teamUnread.MsgCount += cu.MsgCount teamUnread.MsgCountRoot += cu.MsgCountRoot } } return teamUnread, nil } func (a *App) RemoveUserFromTeam(rctx request.CTX, teamID string, userID string, requestorId string) *model.AppError { tchan := make(chan store.StoreResult[*model.Team], 1) go func() { team, err := a.Srv().Store().Team().Get(teamID) tchan <- store.StoreResult[*model.Team]{Data: team, NErr: err} close(tchan) }() uchan := make(chan store.StoreResult[*model.User], 1) go func() { user, err := a.Srv().Store().User().Get(context.Background(), userID) uchan <- store.StoreResult[*model.User]{Data: user, NErr: err} close(uchan) }() teamChanResult := <-tchan if teamChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(teamChanResult.NErr, &nfErr): return model.NewAppError("RemoveUserFromTeam", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusNotFound).Wrap(teamChanResult.NErr) default: return model.NewAppError("RemoveUserFromTeam", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(teamChanResult.NErr) } } team := teamChanResult.Data userChanResult := <-uchan if userChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(userChanResult.NErr, &nfErr): return model.NewAppError("RemoveUserFromTeam", MissingAccountError, nil, "", http.StatusNotFound).Wrap(userChanResult.NErr) default: return model.NewAppError("RemoveUserFromTeam", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(userChanResult.NErr) } } user := userChanResult.Data if err := a.LeaveTeam(rctx, team, user, requestorId); err != nil { return err } return nil } func (a *App) postProcessTeamMemberLeave(rctx request.CTX, teamMember *model.TeamMember, requestorId string) *model.AppError { var actor *model.User if requestorId != "" { actor, _ = a.GetUser(requestorId) } a.Srv().Go(func() { pluginContext := pluginContext(rctx) a.ch.RunMultiHook(func(hooks plugin.Hooks, _ *model.Manifest) bool { hooks.UserHasLeftTeam(pluginContext, teamMember, actor) return true }, plugin.UserHasLeftTeamID) }) user, nErr := a.Srv().Store().User().Get(context.Background(), teamMember.UserId) if nErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(nErr, &nfErr): return model.NewAppError("postProcessTeamMemberLeave", MissingAccountError, nil, "", http.StatusNotFound).Wrap(nErr) default: return model.NewAppError("postProcessTeamMemberLeave", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } if _, err := a.Srv().Store().User().UpdateUpdateAt(user.Id); err != nil { return model.NewAppError("postProcessTeamMemberLeave", "app.user.update_update.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if err := a.Srv().Store().Channel().ClearSidebarOnTeamLeave(user.Id, teamMember.TeamId); err != nil { return model.NewAppError("postProcessTeamMemberLeave", "app.channel.sidebar_categories.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } // delete the preferences that set the last channel used in the team and other team specific preferences if err := a.Srv().Store().Preference().DeleteCategory(user.Id, teamMember.TeamId); err != nil { return model.NewAppError("postProcessTeamMemberLeave", "app.preference.delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } a.ClearSessionCacheForUser(user.Id) a.InvalidateCacheForUser(user.Id) a.invalidateCacheForUserTeams(user.Id) return nil } func (a *App) LeaveTeam(rctx request.CTX, team *model.Team, user *model.User, requestorId string) *model.AppError { teamMember, err := a.GetTeamMember(rctx, team.Id, user.Id) if err != nil { return model.NewAppError("LeaveTeam", "api.team.remove_user_from_team.missing.app_error", nil, "", http.StatusBadRequest).Wrap(err) } var channelList model.ChannelList var nErr error if channelList, nErr = a.Srv().Store().Channel().GetChannels(team.Id, user.Id, &model.ChannelSearchOpts{ IncludeDeleted: true, LastDeleteAt: 0, }); nErr != nil { var nfErr *store.ErrNotFound if errors.As(nErr, &nfErr) { channelList = model.ChannelList{} } else { return model.NewAppError("LeaveTeam", "app.channel.get_channels.get.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } for _, channel := range channelList { if !channel.IsGroupOrDirect() { a.invalidateCacheForChannelMembers(channel.Id) if nErr = a.Srv().Store().Channel().RemoveMember(rctx, channel.Id, user.Id); nErr != nil { return model.NewAppError("LeaveTeam", "app.channel.remove_member.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } } if *a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages { channel, cErr := a.Srv().Store().Channel().GetByName(team.Id, model.DefaultChannelName, false) if cErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(cErr, &nfErr): return model.NewAppError("LeaveTeam", "app.channel.get_by_name.missing.app_error", nil, "", http.StatusNotFound).Wrap(cErr) default: return model.NewAppError("LeaveTeam", "app.channel.get_by_name.existing.app_error", nil, "", http.StatusInternalServerError).Wrap(cErr) } } if requestorId == user.Id { if err = a.postLeaveTeamMessage(rctx, user, channel); err != nil { rctx.Logger().Warn("Failed to post join/leave message", mlog.Err(err)) } } else { if err = a.postRemoveFromTeamMessage(rctx, user, channel); err != nil { rctx.Logger().Warn("Failed to post join/leave message", mlog.Err(err)) } } } if err := a.ch.srv.teamService.RemoveTeamMember(rctx, teamMember); err != nil { return model.NewAppError("RemoveTeamMemberFromTeam", "app.team.save_member.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if err := a.postProcessTeamMemberLeave(rctx, teamMember, requestorId); err != nil { return err } return nil } func (a *App) postLeaveTeamMessage(rctx request.CTX, user *model.User, channel *model.Channel) *model.AppError { post := &model.Post{ ChannelId: channel.Id, Message: fmt.Sprintf(i18n.T("api.team.leave.left"), user.Username), Type: model.PostTypeLeaveTeam, UserId: user.Id, Props: model.StringInterface{ "username": user.Username, }, } if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postRemoveFromChannelMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } return nil } func (a *App) postRemoveFromTeamMessage(rctx request.CTX, user *model.User, channel *model.Channel) *model.AppError { post := &model.Post{ ChannelId: channel.Id, Message: fmt.Sprintf(i18n.T("api.team.remove_user_from_team.removed"), user.Username), Type: model.PostTypeRemoveFromTeam, UserId: user.Id, Props: model.StringInterface{ "username": user.Username, }, } if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postRemoveFromTeamMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } return nil } func (a *App) prepareInviteNewUsersToTeam(teamID, senderId string, channelIds []string) (*model.User, *model.Team, []*model.Channel, *model.AppError) { tchan := make(chan store.StoreResult[*model.Team], 1) go func() { team, err := a.Srv().Store().Team().Get(teamID) tchan <- store.StoreResult[*model.Team]{Data: team, NErr: err} close(tchan) }() uchan := make(chan store.StoreResult[*model.User], 1) go func() { user, err := a.Srv().Store().User().Get(context.Background(), senderId) uchan <- store.StoreResult[*model.User]{Data: user, NErr: err} close(uchan) }() var channels []*model.Channel var err error if len(channelIds) > 0 { channels, err = a.Srv().Store().Channel().GetChannelsByIds(channelIds, false) if err != nil { return nil, nil, nil, model.NewAppError("prepareInviteNewUsersToTeam", "app.channel.get_channels_by_ids.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } teamChanResult := <-tchan if teamChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(teamChanResult.NErr, &nfErr): return nil, nil, nil, model.NewAppError("prepareInviteNewUsersToTeam", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusNotFound).Wrap(teamChanResult.NErr) default: return nil, nil, nil, model.NewAppError("prepareInviteNewUsersToTeam", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(teamChanResult.NErr) } } team := teamChanResult.Data userChanResult := <-uchan if userChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(userChanResult.NErr, &nfErr): return nil, nil, nil, model.NewAppError("prepareInviteNewUsersToTeam", MissingAccountError, nil, "", http.StatusNotFound).Wrap(userChanResult.NErr) default: return nil, nil, nil, model.NewAppError("prepareInviteNewUsersToTeam", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(userChanResult.NErr) } } user := userChanResult.Data for _, channel := range channels { if channel.TeamId != teamID { return nil, nil, nil, model.NewAppError("prepareInviteGuestsToChannels", "api.team.invite_guests.channel_in_invalid_team.app_error", nil, "", http.StatusBadRequest) } } return user, team, channels, nil } func (a *App) InviteNewUsersToTeamGracefully(rctx request.CTX, memberInvite *model.MemberInvite, teamID, senderId string, reminderInterval string) ([]*model.EmailInviteWithError, *model.AppError) { if !*a.Config().ServiceSettings.EnableEmailInvitations { return nil, model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.disabled.app_error", nil, "", http.StatusNotImplemented) } emailList := memberInvite.Emails if len(emailList) == 0 { err := model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.no_one.app_error", nil, "", http.StatusBadRequest) return nil, err } user, team, channels, err := a.prepareInviteNewUsersToTeam(teamID, senderId, memberInvite.ChannelIds) if err != nil { return nil, err } allowedDomains := a.ch.srv.teamService.GetAllowedDomains(user, team) var inviteListWithErrors []*model.EmailInviteWithError var goodEmails []string for _, email := range emailList { invite := &model.EmailInviteWithError{ Email: email, Error: nil, } if !teams.IsEmailAddressAllowed(email, allowedDomains) { invite.Error = model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.invalid_email.app_error", map[string]any{"Addresses": email}, "", http.StatusBadRequest) } else { goodEmails = append(goodEmails, email) } inviteListWithErrors = append(inviteListWithErrors, invite) } var reminderData *model.TeamInviteReminderData if reminderInterval != "" { reminderData = &model.TeamInviteReminderData{Interval: reminderInterval} } if len(goodEmails) > 0 { nameFormat := *a.Config().TeamSettings.TeammateNameDisplay senderProfileImage, _, err := a.GetProfileImage(user) if err != nil { rctx.Logger().Warn("Unable to get the sender user profile image.", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.Err(err)) } userIsFirstAdmin := a.UserIsFirstAdmin(rctx, user) var eErr error var invitesWithErrors2 []*model.EmailInviteWithError if len(channels) > 0 { invitesWithErrors2, eErr = a.Srv().EmailService.SendInviteEmailsToTeamAndChannels(team, channels, user.GetDisplayName(nameFormat), user.Id, senderProfileImage, goodEmails, a.GetSiteURL(), reminderData, memberInvite.Message, true, user.IsSystemAdmin(), userIsFirstAdmin) inviteListWithErrors = append(inviteListWithErrors, invitesWithErrors2...) } else { eErr = a.Srv().EmailService.SendInviteEmails(team, user.GetDisplayName(nameFormat), user.Id, goodEmails, a.GetSiteURL(), reminderData, true, user.IsSystemAdmin(), userIsFirstAdmin) } if eErr != nil { switch { case errors.Is(eErr, email.SendMailError): for i := range inviteListWithErrors { if inviteListWithErrors[i].Error == nil { if *a.Config().EmailSettings.SMTPServer == model.EmailSMTPDefaultServer && *a.Config().EmailSettings.SMTPPort == model.EmailSMTPDefaultPort { inviteListWithErrors[i].Error = model.NewAppError("InviteNewUsersToTeamGracefully", "api.team.invite_members.unable_to_send_email_with_defaults.app_error", nil, "", http.StatusInternalServerError) } else { inviteListWithErrors[i].Error = model.NewAppError("InviteNewUsersToTeamGracefully", "api.team.invite_members.unable_to_send_email.app_error", nil, "", http.StatusInternalServerError) } } } case errors.Is(eErr, email.NoRateLimiterError): return nil, model.NewAppError("InviteNewUsersToTeamGracefully", "app.email.no_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s", user.Id, team.Id), http.StatusInternalServerError) case errors.Is(eErr, email.SetupRateLimiterError): return nil, model.NewAppError("InviteNewUsersToTeamGracefully", "app.email.setup_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", user.Id, team.Id, eErr), http.StatusInternalServerError) default: return nil, model.NewAppError("InviteNewUsersToTeamGracefully", "app.email.rate_limit_exceeded.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", user.Id, team.Id, eErr), http.StatusRequestEntityTooLarge) } } } return inviteListWithErrors, nil } func (a *App) prepareInviteGuestsToChannels(teamID string, guestsInvite *model.GuestsInvite, senderId string) (*model.User, *model.Team, []*model.Channel, *model.AppError) { if err := guestsInvite.IsValid(); err != nil { return nil, nil, nil, err } tchan := make(chan store.StoreResult[*model.Team], 1) go func() { team, err := a.Srv().Store().Team().Get(teamID) tchan <- store.StoreResult[*model.Team]{Data: team, NErr: err} close(tchan) }() cchan := make(chan store.StoreResult[[]*model.Channel], 1) go func() { channels, err := a.Srv().Store().Channel().GetChannelsByIds(guestsInvite.Channels, false) cchan <- store.StoreResult[[]*model.Channel]{Data: channels, NErr: err} close(cchan) }() uchan := make(chan store.StoreResult[*model.User], 1) go func() { user, err := a.Srv().Store().User().Get(context.Background(), senderId) uchan <- store.StoreResult[*model.User]{Data: user, NErr: err} close(uchan) }() channelChanResult := <-cchan if channelChanResult.NErr != nil { return nil, nil, nil, model.NewAppError("prepareInviteGuestsToChannels", "app.channel.get_channels_by_ids.app_error", nil, "", http.StatusInternalServerError).Wrap(channelChanResult.NErr) } channels := channelChanResult.Data userChanResult := <-uchan if userChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(userChanResult.NErr, &nfErr): return nil, nil, nil, model.NewAppError("prepareInviteGuestsToChannels", MissingAccountError, nil, "", http.StatusNotFound).Wrap(userChanResult.NErr) default: return nil, nil, nil, model.NewAppError("prepareInviteGuestsToChannels", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(userChanResult.NErr) } } user := userChanResult.Data teamChanResult := <-tchan if teamChanResult.NErr != nil { var nfErr *store.ErrNotFound switch { case errors.As(teamChanResult.NErr, &nfErr): return nil, nil, nil, model.NewAppError("prepareInviteGuestsToChannels", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusNotFound).Wrap(teamChanResult.NErr) default: return nil, nil, nil, model.NewAppError("prepareInviteGuestsToChannels", "app.team.get_by_invite_id.finding.app_error", nil, "", http.StatusInternalServerError).Wrap(teamChanResult.NErr) } } team := teamChanResult.Data for _, channel := range channels { if channel.TeamId != teamID { return nil, nil, nil, model.NewAppError("prepareInviteGuestsToChannels", "api.team.invite_guests.channel_in_invalid_team.app_error", nil, "", http.StatusBadRequest) } // Check if the channel has access control policy enforcement if channel.PolicyEnforced { return nil, nil, nil, model.NewAppError("prepareInviteGuestsToChannels", "api.team.invite_guests.policy_enforced_channel.app_error", nil, "", http.StatusBadRequest) } } return user, team, channels, nil } func (a *App) InviteGuestsToChannelsGracefully(rctx request.CTX, teamID string, guestsInvite *model.GuestsInvite, senderId string) ([]*model.EmailInviteWithError, *model.AppError) { if !*a.Config().ServiceSettings.EnableEmailInvitations { return nil, model.NewAppError("InviteGuestsToChannelsGracefully", "api.team.invite_members.disabled.app_error", nil, "", http.StatusNotImplemented) } user, team, channels, err := a.prepareInviteGuestsToChannels(teamID, guestsInvite, senderId) if err != nil { return nil, err } var inviteListWithErrors []*model.EmailInviteWithError var goodEmails []string for _, email := range guestsInvite.Emails { invite := &model.EmailInviteWithError{ Email: email, Error: nil, } if !users.CheckEmailDomain(email, *a.Config().GuestAccountsSettings.RestrictCreationToDomains) { invite.Error = model.NewAppError("InviteGuestsToChannelsGracefully", "api.team.invite_members.invalid_email.app_error", map[string]any{"Addresses": email}, "", http.StatusBadRequest) } else { goodEmails = append(goodEmails, email) } inviteListWithErrors = append(inviteListWithErrors, invite) } if len(goodEmails) > 0 { nameFormat := *a.Config().TeamSettings.TeammateNameDisplay senderProfileImage, _, err := a.GetProfileImage(user) if err != nil { rctx.Logger().Warn("Unable to get the sender user profile image.", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.Err(err)) } eErr := a.Srv().EmailService.SendGuestInviteEmails(team, channels, user.GetDisplayName(nameFormat), user.Id, senderProfileImage, goodEmails, a.GetSiteURL(), guestsInvite.Message, true, user.IsSystemAdmin(), a.UserIsFirstAdmin(rctx, user)) if eErr != nil { switch { case errors.Is(eErr, email.SendMailError): for i := range inviteListWithErrors { if inviteListWithErrors[i].Error == nil { if *a.Config().EmailSettings.SMTPServer == model.EmailSMTPDefaultServer && *a.Config().EmailSettings.SMTPPort == model.EmailSMTPDefaultPort { inviteListWithErrors[i].Error = model.NewAppError("InviteGuestsToChannelsGracefully", "api.team.invite_members.unable_to_send_email_with_defaults.app_error", nil, "", http.StatusInternalServerError) } else { inviteListWithErrors[i].Error = model.NewAppError("InviteGuestsToChannelsGracefully", "api.team.invite_members.unable_to_send_email.app_error", nil, "", http.StatusInternalServerError) } } } case errors.Is(eErr, email.NoRateLimiterError): return nil, model.NewAppError("SendInviteEmails", "app.email.no_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s", user.Id, team.Id), http.StatusInternalServerError) case errors.Is(eErr, email.SetupRateLimiterError): return nil, model.NewAppError("SendInviteEmails", "app.email.setup_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", user.Id, team.Id, eErr), http.StatusInternalServerError) default: return nil, model.NewAppError("SendInviteEmails", "app.email.rate_limit_exceeded.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", user.Id, team.Id, eErr), http.StatusRequestEntityTooLarge) } } } return inviteListWithErrors, nil } func (a *App) InviteNewUsersToTeam(rctx request.CTX, emailList []string, teamID, senderId string) *model.AppError { if !*a.Config().ServiceSettings.EnableEmailInvitations { return model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.disabled.app_error", nil, "", http.StatusNotImplemented) } if len(emailList) == 0 { err := model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.no_one.app_error", nil, "", http.StatusBadRequest) return err } user, team, _, err := a.prepareInviteNewUsersToTeam(teamID, senderId, []string{}) if err != nil { return err } allowedDomains := a.ch.srv.teamService.GetAllowedDomains(user, team) var invalidEmailList []string for _, email := range emailList { if !teams.IsEmailAddressAllowed(email, allowedDomains) { invalidEmailList = append(invalidEmailList, email) } } if len(invalidEmailList) > 0 { s := strings.Join(invalidEmailList, ", ") return model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.invalid_email.app_error", map[string]any{"Addresses": s}, "", http.StatusBadRequest) } nameFormat := *a.Config().TeamSettings.TeammateNameDisplay eErr := a.Srv().EmailService.SendInviteEmails(team, user.GetDisplayName(nameFormat), user.Id, emailList, a.GetSiteURL(), nil, false, user.IsSystemAdmin(), a.UserIsFirstAdmin(rctx, user)) if eErr != nil { switch { case errors.Is(eErr, email.NoRateLimiterError): return model.NewAppError("SendInviteEmails", "app.email.no_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s", user.Id, team.Id), http.StatusInternalServerError) case errors.Is(eErr, email.SetupRateLimiterError): return model.NewAppError("SendInviteEmails", "app.email.setup_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", user.Id, team.Id, eErr), http.StatusInternalServerError) default: return model.NewAppError("SendInviteEmails", "app.email.rate_limit_exceeded.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", user.Id, team.Id, eErr), http.StatusRequestEntityTooLarge) } } return nil } func (a *App) InviteGuestsToChannels(rctx request.CTX, teamID string, guestsInvite *model.GuestsInvite, senderId string) *model.AppError { if !*a.Config().ServiceSettings.EnableEmailInvitations { return model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.disabled.app_error", nil, "", http.StatusNotImplemented) } user, team, channels, err := a.prepareInviteGuestsToChannels(teamID, guestsInvite, senderId) if err != nil { return err } var invalidEmailList []string for _, email := range guestsInvite.Emails { if !users.CheckEmailDomain(email, *a.Config().GuestAccountsSettings.RestrictCreationToDomains) { invalidEmailList = append(invalidEmailList, email) } } if len(invalidEmailList) > 0 { s := strings.Join(invalidEmailList, ", ") return model.NewAppError("InviteGuestsToChannels", "api.team.invite_members.invalid_email.app_error", map[string]any{"Addresses": s}, "", http.StatusBadRequest) } nameFormat := *a.Config().TeamSettings.TeammateNameDisplay senderProfileImage, _, err := a.GetProfileImage(user) if err != nil { rctx.Logger().Warn("Unable to get the sender user profile image.", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.Err(err)) } eErr := a.Srv().EmailService.SendGuestInviteEmails(team, channels, user.GetDisplayName(nameFormat), user.Id, senderProfileImage, guestsInvite.Emails, a.GetSiteURL(), guestsInvite.Message, false, user.IsSystemAdmin(), a.UserIsFirstAdmin(rctx, user)) if eErr != nil { switch { case errors.Is(eErr, email.NoRateLimiterError): return model.NewAppError("SendInviteEmails", "app.email.no_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s", user.Id, team.Id), http.StatusInternalServerError) case errors.Is(eErr, email.SetupRateLimiterError): return model.NewAppError("SendInviteEmails", "app.email.setup_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", user.Id, team.Id, err), http.StatusInternalServerError) default: return model.NewAppError("SendInviteEmails", "app.email.rate_limit_exceeded.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", user.Id, team.Id, err), http.StatusRequestEntityTooLarge) } } return nil } func (a *App) FindTeamByName(name string) bool { if _, err := a.Srv().Store().Team().GetByName(name); err != nil { return false } return true } func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string, includeCollapsedThreads bool) ([]*model.TeamUnread, *model.AppError) { data, err := a.Srv().Store().Team().GetChannelUnreadsForAllTeams(excludeTeamId, userID) if err != nil { return nil, model.NewAppError("GetTeamsUnreadForUser", "app.team.get_unread.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } members := []*model.TeamUnread{} membersMap := make(map[string]*model.TeamUnread) unreads := func(cu *model.ChannelUnread, tu *model.TeamUnread) *model.TeamUnread { tu.MentionCount += cu.MentionCount tu.MentionCountRoot += cu.MentionCountRoot if cu.NotifyProps[model.MarkUnreadNotifyProp] != model.ChannelMarkUnreadMention { tu.MsgCount += cu.MsgCount tu.MsgCountRoot += cu.MsgCountRoot } return tu } teamIDs := make([]string, 0, len(data)) for i := range data { id := data[i].TeamId if mu, ok := membersMap[id]; ok { membersMap[id] = unreads(data[i], mu) } else { teamIDs = append(teamIDs, id) membersMap[id] = unreads(data[i], &model.TeamUnread{ MsgCount: 0, MentionCount: 0, MentionCountRoot: 0, MsgCountRoot: 0, ThreadCount: 0, ThreadMentionCount: 0, ThreadUrgentMentionCount: 0, TeamId: id, }) } } includeCollapsedThreads = includeCollapsedThreads && *a.Config().ServiceSettings.CollapsedThreads != model.CollapsedThreadsDisabled if includeCollapsedThreads { teamUnreads, err := a.Srv().Store().Thread().GetTeamsUnreadForUser(userID, teamIDs, a.IsPostPriorityEnabled()) if err != nil { return nil, model.NewAppError("GetTeamsUnreadForUser", "app.team.get_unread.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } for teamID, member := range membersMap { if _, ok := teamUnreads[teamID]; ok { member.ThreadCount = teamUnreads[teamID].ThreadCount member.ThreadMentionCount = teamUnreads[teamID].ThreadMentionCount member.ThreadUrgentMentionCount = teamUnreads[teamID].ThreadUrgentMentionCount } } } for _, member := range membersMap { members = append(members, member) } return members, nil } func (a *App) PermanentDeleteTeamId(rctx request.CTX, teamID string) *model.AppError { team, err := a.GetTeam(teamID) if err != nil { return err } return a.PermanentDeleteTeam(rctx, team) } func (a *App) PermanentDeleteTeam(rctx request.CTX, team *model.Team) *model.AppError { team.DeleteAt = model.GetMillis() if _, err := a.Srv().Store().Team().Update(team); err != nil { var invErr *store.ErrInvalidInput var appErr *model.AppError switch { case errors.As(err, &invErr): return model.NewAppError("PermanentDeleteTeam", "app.team.update.find.app_error", nil, "", http.StatusBadRequest).Wrap(err) case errors.As(err, &appErr): return appErr default: return model.NewAppError("PermanentDeleteTeam", "app.team.update.updating.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } if channels, err := a.Srv().Store().Channel().GetTeamChannels(team.Id); err != nil { var nfErr *store.ErrNotFound if !errors.As(err, &nfErr) { return model.NewAppError("PermanentDeleteTeam", "app.channel.get_channels.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } else { for _, ch := range channels { if err := a.PermanentDeleteChannel(rctx, ch); err != nil { rctx.Logger().Warn("Error permanently deleting channel during team deletion", mlog.String("channel_id", ch.Id), mlog.String("team_id", team.Id), mlog.Err(err)) } } } if err := a.Srv().Store().Team().RemoveAllMembersByTeam(team.Id); err != nil { return model.NewAppError("PermanentDeleteTeam", "app.team.remove_member.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if err := a.Srv().Store().Command().PermanentDeleteByTeam(team.Id); err != nil { return model.NewAppError("PermanentDeleteTeam", "app.team.permanentdeleteteam.internal_error", nil, "", http.StatusInternalServerError).Wrap(err) } if err := a.Srv().Store().Team().PermanentDelete(team.Id); err != nil { return model.NewAppError("PermanentDeleteTeam", "app.team.permanent_delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if appErr := a.sendTeamEvent(team, model.WebsocketEventDeleteTeam); appErr != nil { return appErr } return nil } func (a *App) SoftDeleteTeam(teamID string) *model.AppError { team, err := a.GetTeam(teamID) if err != nil { return err } if err := a.Srv().Store().PostPersistentNotification().DeleteByTeam([]string{team.Id}); err != nil { return model.NewAppError("SoftDeleteTeam", "app.post_persistent_notification.delete_by_team.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } team.DeleteAt = model.GetMillis() team, nErr := a.Srv().Store().Team().Update(team) if nErr != nil { var invErr *store.ErrInvalidInput var appErr *model.AppError switch { case errors.As(nErr, &invErr): return model.NewAppError("SoftDeleteTeam", "app.team.update.find.app_error", nil, "", http.StatusBadRequest).Wrap(nErr) case errors.As(nErr, &appErr): return appErr default: return model.NewAppError("SoftDeleteTeam", "app.team.update.updating.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } if appErr := a.sendTeamEvent(team, model.WebsocketEventDeleteTeam); appErr != nil { return appErr } return nil } func (a *App) RestoreTeam(teamID string) *model.AppError { team, err := a.GetTeam(teamID) if err != nil { return err } team.DeleteAt = 0 team, nErr := a.Srv().Store().Team().Update(team) if nErr != nil { var invErr *store.ErrInvalidInput var appErr *model.AppError switch { case errors.As(nErr, &invErr): return model.NewAppError("RestoreTeam", "app.team.update.find.app_error", nil, "", http.StatusBadRequest).Wrap(nErr) case errors.As(nErr, &appErr): return appErr default: return model.NewAppError("RestoreTeam", "app.team.update.updating.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } if appErr := a.sendTeamEvent(team, model.WebsocketEventRestoreTeam); appErr != nil { return appErr } return nil } func (a *App) GetTeamStats(teamID string, restrictions *model.ViewUsersRestrictions) (*model.TeamStats, *model.AppError) { tchan := make(chan store.StoreResult[int64], 1) go func() { totalMemberCount, err := a.Srv().Store().Team().GetTotalMemberCount(teamID, restrictions) tchan <- store.StoreResult[int64]{Data: totalMemberCount, NErr: err} close(tchan) }() achan := make(chan store.StoreResult[int64], 1) go func() { memberCount, err := a.Srv().Store().Team().GetActiveMemberCount(teamID, restrictions) achan <- store.StoreResult[int64]{Data: memberCount, NErr: err} close(achan) }() stats := &model.TeamStats{} stats.TeamId = teamID totalMemberCountChanResult := <-tchan if totalMemberCountChanResult.NErr != nil { return nil, model.NewAppError("GetTeamStats", "app.team.get_member_count.app_error", nil, "", http.StatusInternalServerError).Wrap(totalMemberCountChanResult.NErr) } stats.TotalMemberCount = totalMemberCountChanResult.Data activeMemberCountChanResult := <-achan if activeMemberCountChanResult.NErr != nil { return nil, model.NewAppError("GetTeamStats", "app.team.get_active_member_count.app_error", nil, "", http.StatusInternalServerError).Wrap(activeMemberCountChanResult.NErr) } stats.ActiveMemberCount = activeMemberCountChanResult.Data return stats, nil } func (a *App) GetTeamIdFromQuery(rctx request.CTX, query url.Values) (string, *model.AppError) { tokenID := query.Get("t") inviteId := query.Get("id") if tokenID != "" { token, err := a.Srv().Store().Token().GetByToken(tokenID) if err != nil { return "", model.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.invalid_link.app_error", nil, "", http.StatusBadRequest).Wrap(err) } if token.Type != TokenTypeTeamInvitation && token.Type != TokenTypeGuestInvitation { return "", model.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.invalid_link.app_error", nil, "", http.StatusBadRequest) } if model.GetMillis()-token.CreateAt >= InvitationExpiryTime { if err := a.DeleteToken(token); err != nil { rctx.Logger().Warn("Error deleting expired invitation token during team ID lookup", mlog.String("token_id", token.Token), mlog.String("token_type", token.Type), mlog.Err(err)) } return "", model.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.expired_link.app_error", nil, "", http.StatusBadRequest) } tokenData := model.MapFromJSON(strings.NewReader(token.Extra)) return tokenData["teamId"], nil } if inviteId != "" { team, err := a.Srv().Store().Team().GetByInviteId(inviteId) if err == nil { return team.Id, nil } // soft fail, so we still create user but don't auto-join team rctx.Logger().Warn("Error getting team by inviteId.", mlog.String("invite_id", inviteId), mlog.Err(err)) } return "", nil } func (a *App) SanitizeTeam(session model.Session, team *model.Team) *model.Team { manageTeamPermission := a.SessionHasPermissionToTeam(session, team.Id, model.PermissionManageTeam) inviteUserPermission := a.SessionHasPermissionToTeam(session, team.Id, model.PermissionInviteUser) if manageTeamPermission && inviteUserPermission { return team } email := team.Email inviteId := team.InviteId team.Sanitize() if manageTeamPermission { team.Email = email } if inviteUserPermission { team.InviteId = inviteId } return team } func (a *App) SanitizeTeams(session model.Session, teams []*model.Team) []*model.Team { for _, team := range teams { a.SanitizeTeam(session, team) } return teams } func (a *App) GetTeamIcon(team *model.Team) ([]byte, *model.AppError) { if *a.Config().FileSettings.DriverName == "" { return nil, model.NewAppError("GetTeamIcon", "api.team.get_team_icon.filesettings_no_driver.app_error", nil, "", http.StatusNotImplemented) } path := "teams/" + team.Id + "/teamIcon.png" data, err := a.ReadFile(path) if err != nil { return nil, model.NewAppError("GetTeamIcon", "api.team.get_team_icon.read_file.app_error", nil, "", http.StatusNotFound).Wrap(err) } return data, nil } func (a *App) SetTeamIcon(rctx request.CTX, teamID string, imageData *multipart.FileHeader) *model.AppError { file, err := imageData.Open() if err != nil { return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.open.app_error", nil, "", http.StatusBadRequest).Wrap(err) } defer file.Close() return a.SetTeamIconFromMultiPartFile(rctx, teamID, file) } func (a *App) SetTeamIconFromMultiPartFile(rctx request.CTX, teamID string, file multipart.File) *model.AppError { team, getTeamErr := a.GetTeam(teamID) if getTeamErr != nil { return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.get_team.app_error", nil, "", http.StatusBadRequest).Wrap(getTeamErr) } if *a.Config().FileSettings.DriverName == "" { return model.NewAppError("setTeamIcon", "api.team.set_team_icon.storage.app_error", nil, "", http.StatusNotImplemented) } if limitErr := checkImageLimits(file, *a.Config().FileSettings.MaxImageResolution); limitErr != nil { return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.check_image_limits.app_error", nil, "", http.StatusBadRequest).Wrap(limitErr) } return a.SetTeamIconFromFile(rctx, team, file) } func (a *App) SetTeamIconFromFile(rctx request.CTX, team *model.Team, file io.ReadSeeker) *model.AppError { // Decode image into Image object img, format, err := image.Decode(file) if err != nil { return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.decode.app_error", nil, "", http.StatusBadRequest).Wrap(err) } orientation, err := imaging.GetImageOrientation(file, format) if err != nil { rctx.Logger().Warn("Failed to get image orientation", mlog.Err(err)) } img = imaging.MakeImageUpright(img, orientation) // Scale team icon teamIconWidthAndHeight := 128 img = imaging.FillCenter(img, teamIconWidthAndHeight, teamIconWidthAndHeight) buf := new(bytes.Buffer) err = a.ch.imgEncoder.EncodePNG(buf, img) if err != nil { return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.encode.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } path := "teams/" + team.Id + "/teamIcon.png" if _, err := a.WriteFile(buf, path); err != nil { return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.write_file.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } curTime := model.GetMillis() if err := a.Srv().Store().Team().UpdateLastTeamIconUpdate(team.Id, curTime); err != nil { return model.NewAppError("SetTeamIcon", "api.team.team_icon.update.app_error", nil, "", http.StatusBadRequest).Wrap(err) } // manually set time to avoid possible cluster inconsistencies team.LastTeamIconUpdate = curTime if appErr := a.sendTeamEvent(team, model.WebsocketEventUpdateTeam); appErr != nil { return appErr } return nil } func (a *App) RemoveTeamIcon(teamID string) *model.AppError { team, err := a.GetTeam(teamID) if err != nil { return model.NewAppError("RemoveTeamIcon", "api.team.remove_team_icon.get_team.app_error", nil, "", http.StatusBadRequest).Wrap(err) } if err := a.Srv().Store().Team().UpdateLastTeamIconUpdate(teamID, 0); err != nil { return model.NewAppError("RemoveTeamIcon", "api.team.team_icon.update.app_error", nil, "", http.StatusBadRequest).Wrap(err) } team.LastTeamIconUpdate = 0 if appErr := a.sendTeamEvent(team, model.WebsocketEventUpdateTeam); appErr != nil { return appErr } return nil } func (a *App) InvalidateAllEmailInvites(rctx request.CTX) *model.AppError { if err := a.Srv().Store().Token().RemoveAllTokensByType(TokenTypeTeamInvitation); err != nil { return model.NewAppError("InvalidateAllEmailInvites", "api.team.invalidate_all_email_invites.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if err := a.Srv().Store().Token().RemoveAllTokensByType(TokenTypeGuestInvitation); err != nil { return model.NewAppError("InvalidateAllEmailInvites", "api.team.invalidate_all_email_invites.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if err := a.InvalidateAllResendInviteEmailJobs(rctx); err != nil { return model.NewAppError("InvalidateAllEmailInvites", "api.team.invalidate_all_email_invites.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return nil } func (a *App) InvalidateAllResendInviteEmailJobs(rctx request.CTX) *model.AppError { jobs, appErr := a.Srv().Jobs.GetJobsByTypeAndStatus(rctx, model.JobTypeResendInvitationEmail, model.JobStatusPending) if appErr != nil { return appErr } for _, j := range jobs { if err := a.Srv().Jobs.SetJobCanceled(j); err != nil { rctx.Logger().Warn("Error canceling resend invitation email job during team invitation invalidation", mlog.String("job_id", j.Id), mlog.Err(err)) } // clean up any system values this job was using _, err := a.Srv().Store().System().PermanentDeleteByName(j.Id) if err != nil { rctx.Logger().Warn("Error deleting system values for resend invitation email job during team invitation invalidation", mlog.String("job_id", j.Id), mlog.Err(err)) } } return nil } func (a *App) ClearTeamMembersCache(teamID string) error { perPage := 100 page := 0 for { teamMembers, err := a.Srv().Store().Team().GetMembers(teamID, page*perPage, perPage, nil) if err != nil { return fmt.Errorf("failed to get team members: %v", err) } for _, teamMember := range teamMembers { a.ClearSessionCacheForUser(teamMember.UserId) message := model.NewWebSocketEvent(model.WebsocketEventMemberroleUpdated, "", "", teamMember.UserId, nil, "") tmJSON, jsonErr := json.Marshal(teamMember) if jsonErr != nil { return jsonErr } message.Add("member", string(tmJSON)) a.Publish(message) } length := len(teamMembers) if length < perPage { break } page++ } return nil }