Initial team service implementation (#18279)
* initial service implementation * reflect review comments
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
d074c3d20d
Коммит
be203d9bf3
@@ -83,9 +83,6 @@ type AppIface interface {
|
||||
CreateBot(c *request.Context, bot *model.Bot) (*model.Bot, *model.AppError)
|
||||
// CreateChannelScheme creates a new Scheme of scope channel and assigns it to the channel.
|
||||
CreateChannelScheme(channel *model.Channel) (*model.Scheme, *model.AppError)
|
||||
// CreateDefaultChannels creates channels in the given team for each channel returned by (*App).DefaultChannelNames.
|
||||
//
|
||||
CreateDefaultChannels(c *request.Context, teamID string) ([]*model.Channel, *model.AppError)
|
||||
// CreateDefaultMemberships adds users to teams and channels based on their group memberships and how those groups
|
||||
// are configured to sync with teams and channels for group members on or after the given timestamp.
|
||||
// If includeRemovedMembers is true, then members who left or were removed from a team/channel will
|
||||
@@ -435,7 +432,6 @@ type AppIface interface {
|
||||
CheckUserMfa(user *model.User, token string) *model.AppError
|
||||
CheckUserPostflightAuthenticationCriteria(user *model.User) *model.AppError
|
||||
CheckUserPreflightAuthenticationCriteria(user *model.User, mfaToken string) *model.AppError
|
||||
CheckValidDomains(team *model.Team) *model.AppError
|
||||
CheckWebConn(userID, connectionID string) *CheckConnResult
|
||||
ClearChannelMembersCache(channelID string)
|
||||
ClearSessionCacheForAllUsers()
|
||||
@@ -912,7 +908,6 @@ type AppIface interface {
|
||||
RemoveSamlPrivateCertificate() *model.AppError
|
||||
RemoveSamlPublicCertificate() *model.AppError
|
||||
RemoveTeamIcon(teamID string) *model.AppError
|
||||
RemoveTeamMemberFromTeam(c *request.Context, teamMember *model.TeamMember, requestorId string) *model.AppError
|
||||
RemoveTeamsFromRetentionPolicy(policyID string, teamIDs []string) *model.AppError
|
||||
RemoveUserFromChannel(c *request.Context, userIDToRemove string, removerUserId string, channel *model.Channel) *model.AppError
|
||||
RemoveUserFromTeam(c *request.Context, teamID string, userID string, requestorId string) *model.AppError
|
||||
|
||||
@@ -21,26 +21,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
// CreateDefaultChannels creates channels in the given team for each channel returned by (*App).DefaultChannelNames.
|
||||
//
|
||||
func (a *App) CreateDefaultChannels(c *request.Context, teamID string) ([]*model.Channel, *model.AppError) {
|
||||
displayNames := map[string]string{
|
||||
"town-square": i18n.T("api.channel.create_default_channels.town_square"),
|
||||
"off-topic": i18n.T("api.channel.create_default_channels.off_topic"),
|
||||
}
|
||||
channels := []*model.Channel{}
|
||||
defaultChannelNames := a.DefaultChannelNames()
|
||||
for _, name := range defaultChannelNames {
|
||||
displayName := i18n.TDefault(displayNames[name], name)
|
||||
channel := &model.Channel{DisplayName: displayName, Name: name, Type: model.ChannelTypeOpen, TeamId: teamID}
|
||||
if _, err := a.CreateChannel(c, channel, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
channels = append(channels, channel)
|
||||
}
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
// DefaultChannelNames returns the list of system-wide default channel names.
|
||||
//
|
||||
// By default the list will be (not necessarily in this order):
|
||||
@@ -2350,7 +2330,11 @@ func (a *App) removeUserFromChannel(c *request.Context, userIDToRemove string, r
|
||||
return model.NewAppError("removeUserFromChannel", "api.team.remove_user_from_team.missing.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err = a.RemoveTeamMemberFromTeam(c, teamMember, removerUserId); err != nil {
|
||||
if err := a.ch.srv.teamService.RemoveTeamMember(teamMember); err != nil {
|
||||
return model.NewAppError("removeUserFromChannel", "api.team.remove_user_from_team.missing.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err = a.postProcessTeamMemberLeave(c, teamMember, removerUserId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/app/teams"
|
||||
"github.com/mattermost/mattermost-server/v6/app/users"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
@@ -209,8 +210,17 @@ func (a *App) importTeam(c *request.Context, data *TeamImportData, dryRun bool)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := a.updateTeamUnsanitized(team); err != nil {
|
||||
return err
|
||||
if _, err := a.ch.srv.teamService.UpdateTeam(team, teams.UpdateOptions{Imported: true}); err != nil {
|
||||
var invErr *store.ErrInvalidInput
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
case errors.As(err, &nfErr):
|
||||
return model.NewAppError("BulkImport", "app.team.get.find.app_error", nil, nfErr.Error(), http.StatusNotFound)
|
||||
case errors.As(err, &invErr):
|
||||
return model.NewAppError("BulkImport", "app.team.update.find.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
||||
default:
|
||||
return model.NewAppError("BulkImport", "app.team.update.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1859,7 +1859,8 @@ func TestImportUserChannels(t *testing.T) {
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
user := th.CreateUser()
|
||||
th.App.joinUserToTeam(th.BasicTeam, user)
|
||||
_, _, err := th.App.ch.srv.teamService.JoinUserToTeam(th.BasicTeam, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Two times import must end with the same results
|
||||
for x := 0; x < 2; x++ {
|
||||
|
||||
@@ -1362,28 +1362,6 @@ func (a *OpenTracingAppLayer) CheckUserPreflightAuthenticationCriteria(user *mod
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CheckValidDomains(team *model.Team) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CheckValidDomains")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.CheckValidDomains(team)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CheckWebConn(userID string, connectionID string) *app.CheckConnResult {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CheckWebConn")
|
||||
@@ -1888,28 +1866,6 @@ func (a *OpenTracingAppLayer) CreateCommandWebhook(commandID string, args *model
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CreateDefaultChannels(c *request.Context, teamID string) ([]*model.Channel, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateDefaultChannels")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.CreateDefaultChannels(c, teamID)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CreateDefaultMemberships(c *request.Context, since int64, includeRemovedMembers bool) error {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateDefaultMemberships")
|
||||
@@ -12898,28 +12854,6 @@ func (a *OpenTracingAppLayer) RemoveTeamIcon(teamID string) *model.AppError {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RemoveTeamMemberFromTeam(c *request.Context, teamMember *model.TeamMember, requestorId string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveTeamMemberFromTeam")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.RemoveTeamMemberFromTeam(c, teamMember, requestorId)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RemoveTeamsFromRetentionPolicy(policyID string, teamIDs []string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveTeamsFromRetentionPolicy")
|
||||
|
||||
@@ -419,20 +419,20 @@ func TestPluginAPIGetUsersInTeam(t *testing.T) {
|
||||
defer th.App.PermanentDeleteUser(th.Context, user4)
|
||||
|
||||
// Add all users to team 1
|
||||
_, _, err = th.App.joinUserToTeam(team1, user1)
|
||||
require.Nil(t, err)
|
||||
_, _, err = th.App.joinUserToTeam(team1, user2)
|
||||
require.Nil(t, err)
|
||||
_, _, err = th.App.joinUserToTeam(team1, user3)
|
||||
require.Nil(t, err)
|
||||
_, _, err = th.App.joinUserToTeam(team1, user4)
|
||||
require.Nil(t, err)
|
||||
_, appErr := th.App.JoinUserToTeam(th.Context, team1, user1, "")
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.JoinUserToTeam(th.Context, team1, user2, "")
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.JoinUserToTeam(th.Context, team1, user3, "")
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.JoinUserToTeam(th.Context, team1, user4, "")
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Add only user3 and user4 to team 2
|
||||
_, _, err = th.App.joinUserToTeam(team2, user3)
|
||||
require.Nil(t, err)
|
||||
_, _, err = th.App.joinUserToTeam(team2, user4)
|
||||
require.Nil(t, err)
|
||||
_, appErr = th.App.JoinUserToTeam(th.Context, team2, user3, "")
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.JoinUserToTeam(th.Context, team2, user4, "")
|
||||
require.Nil(t, appErr)
|
||||
|
||||
testCases := []struct {
|
||||
Description string
|
||||
@@ -482,7 +482,14 @@ func TestPluginAPIGetUsersInTeam(t *testing.T) {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
users, err := api.GetUsersInTeam(testCase.TeamId, testCase.Page, testCase.PerPage)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, testCase.ExpectedUsers, users)
|
||||
usersMap := make(map[string]bool)
|
||||
for _, user := range testCase.ExpectedUsers {
|
||||
usersMap[user.Id] = true
|
||||
}
|
||||
for _, user := range users {
|
||||
delete(usersMap, user.Id)
|
||||
}
|
||||
assert.Empty(t, usersMap)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/app/featureflag"
|
||||
"github.com/mattermost/mattermost-server/v6/app/imaging"
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/app/teams"
|
||||
"github.com/mattermost/mattermost-server/v6/app/users"
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
@@ -155,6 +156,7 @@ type Server struct {
|
||||
|
||||
telemetryService *telemetry.TelemetryService
|
||||
userService *users.UserService
|
||||
teamService *teams.TeamService
|
||||
|
||||
serviceMux sync.RWMutex
|
||||
remoteClusterService remotecluster.RemoteClusterServiceIFace
|
||||
@@ -428,6 +430,19 @@ func NewServer(options ...Option) (*Server, error) {
|
||||
return nil, errors.Wrapf(err, "unable to create users service")
|
||||
}
|
||||
|
||||
s.teamService, err = teams.New(teams.ServiceConfig{
|
||||
TeamStore: s.Store.Team(),
|
||||
ChannelStore: s.Store.Channel(),
|
||||
GroupStore: s.Store.Group(),
|
||||
Users: s.userService,
|
||||
WebHub: s,
|
||||
ConfigFn: s.Config,
|
||||
LicenseFn: s.License,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to create teams service")
|
||||
}
|
||||
|
||||
s.configListenerId = s.AddConfigListener(func(_, _ *model.Config) {
|
||||
s.configOrLicenseListener()
|
||||
|
||||
|
||||
382
app/team.go
382
app/team.go
@@ -19,6 +19,7 @@ import (
|
||||
"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/teams"
|
||||
"github.com/mattermost/mattermost-server/v6/app/users"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||
@@ -29,14 +30,29 @@ import (
|
||||
)
|
||||
|
||||
func (a *App) CreateTeam(c *request.Context, team *model.Team) (*model.Team, *model.AppError) {
|
||||
team.InviteId = ""
|
||||
rteam, err := a.Srv().Store.Team().Save(team)
|
||||
rteam, err := a.ch.srv.teamService.CreateTeam(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):
|
||||
return nil, model.NewAppError("CreateTeam", "app.team.save.existing.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
||||
switch {
|
||||
case invErr.Entity == "Channel" && invErr.Field == "DeleteAt":
|
||||
return nil, model.NewAppError("CreateTeam", "store.sql_channel.save.archived_channel.app_error", nil, "", http.StatusBadRequest)
|
||||
case invErr.Entity == "Channel" && invErr.Field == "Type":
|
||||
return nil, model.NewAppError("CreateTeam", "store.sql_channel.save.direct_channel.app_error", nil, "", http.StatusBadRequest)
|
||||
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)
|
||||
default:
|
||||
return nil, model.NewAppError("CreateTeam", "app.team.save.existing.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
||||
}
|
||||
case errors.As(err, &cErr):
|
||||
return nil, model.NewAppError("CreateTeam", store.ChannelExistsError, nil, cErr.Error(), http.StatusBadRequest)
|
||||
case errors.As(err, <Err):
|
||||
return nil, model.NewAppError("CreateTeam", "store.sql_channel.save_channel.limit.app_error", nil, ltErr.Error(), http.StatusBadRequest)
|
||||
case errors.As(err, &appErr):
|
||||
return nil, appErr
|
||||
default:
|
||||
@@ -44,10 +60,6 @@ func (a *App) CreateTeam(c *request.Context, team *model.Team) (*model.Team, *mo
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := a.CreateDefaultChannels(c, rteam.Id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rteam, nil
|
||||
}
|
||||
|
||||
@@ -58,8 +70,8 @@ func (a *App) CreateTeamWithUser(c *request.Context, team *model.Team, userID st
|
||||
}
|
||||
team.Email = user.Email
|
||||
|
||||
if !a.isTeamEmailAllowed(user, team) {
|
||||
return nil, model.NewAppError("isTeamEmailAllowed", "api.team.is_team_creation_allowed.domain.app_error", nil, "", http.StatusBadRequest)
|
||||
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(c, team)
|
||||
@@ -80,86 +92,25 @@ func (a *App) normalizeDomains(domains string) []string {
|
||||
return strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(strings.Replace(domains, "@", " ", -1), ",", " ", -1))))
|
||||
}
|
||||
|
||||
func (a *App) isEmailAddressAllowed(email string, allowedDomains []string) bool {
|
||||
for _, restriction := range allowedDomains {
|
||||
domains := a.normalizeDomains(restriction)
|
||||
if len(domains) <= 0 {
|
||||
continue
|
||||
}
|
||||
matched := false
|
||||
for _, d := range domains {
|
||||
if strings.HasSuffix(email, "@"+d) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *App) isTeamEmailAllowed(user *model.User, team *model.Team) bool {
|
||||
if user.IsBot {
|
||||
return true
|
||||
}
|
||||
email := strings.ToLower(user.Email)
|
||||
allowedDomains := a.getAllowedDomains(user, team)
|
||||
return a.isEmailAddressAllowed(email, allowedDomains)
|
||||
}
|
||||
|
||||
func (a *App) getAllowedDomains(user *model.User, team *model.Team) []string {
|
||||
if user.IsGuest() {
|
||||
return []string{*a.Config().GuestAccountsSettings.RestrictCreationToDomains}
|
||||
}
|
||||
// First check per team allowedDomains, then app wide restrictions
|
||||
return []string{team.AllowedDomains, *a.Config().TeamSettings.RestrictCreationToDomains}
|
||||
}
|
||||
|
||||
func (a *App) CheckValidDomains(team *model.Team) *model.AppError {
|
||||
validDomains := a.normalizeDomains(*a.Config().TeamSettings.RestrictCreationToDomains)
|
||||
if len(validDomains) > 0 {
|
||||
for _, domain := range a.normalizeDomains(team.AllowedDomains) {
|
||||
matched := false
|
||||
for _, d := range validDomains {
|
||||
if domain == d {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
err := model.NewAppError("UpdateTeam", "api.team.update_restricted_domains.mismatch.app_error", map[string]interface{}{"Domain": domain}, "", http.StatusBadRequest)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
|
||||
oldTeam, err := a.GetTeam(team.Id)
|
||||
oldTeam, err := a.ch.srv.teamService.UpdateTeam(team, teams.UpdateOptions{Sanitized: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = a.CheckValidDomains(team); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldTeam.DisplayName = team.DisplayName
|
||||
oldTeam.Description = team.Description
|
||||
oldTeam.AllowOpenInvite = team.AllowOpenInvite
|
||||
oldTeam.CompanyName = team.CompanyName
|
||||
oldTeam.AllowedDomains = team.AllowedDomains
|
||||
oldTeam.LastTeamIconUpdate = team.LastTeamIconUpdate
|
||||
oldTeam.GroupConstrained = team.GroupConstrained
|
||||
|
||||
oldTeam, err = a.updateTeamUnsanitized(oldTeam)
|
||||
if err != nil {
|
||||
return team, err
|
||||
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, nfErr.Error(), http.StatusNotFound)
|
||||
case errors.As(err, &invErr):
|
||||
return nil, model.NewAppError("UpdateTeam", "app.team.update.find.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
||||
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]interface{}{"Domain": domErr.Domain}, "", http.StatusBadRequest)
|
||||
default:
|
||||
return nil, model.NewAppError("UpdateTeam", "app.team.update.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeam)
|
||||
@@ -167,24 +118,6 @@ func (a *App) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
|
||||
return oldTeam, nil
|
||||
}
|
||||
|
||||
func (a *App) updateTeamUnsanitized(team *model.Team) (*model.Team, *model.AppError) {
|
||||
team, err := a.Srv().Store.Team().Update(team)
|
||||
if err != nil {
|
||||
var invErr *store.ErrInvalidInput
|
||||
var appErr *model.AppError
|
||||
switch {
|
||||
case errors.As(err, &invErr):
|
||||
return nil, model.NewAppError("updateTeamUnsanitized", "app.team.update.find.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
||||
case errors.As(err, &appErr):
|
||||
return nil, appErr
|
||||
default:
|
||||
return nil, model.NewAppError("updateTeamUnsanitized", "app.team.update.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return team, 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) {
|
||||
|
||||
@@ -205,9 +138,24 @@ func (a *App) RenameTeam(team *model.Team, newTeamName string, newDisplayName st
|
||||
team.DisplayName = newDisplayName
|
||||
}
|
||||
|
||||
newTeam, err := a.updateTeamUnsanitized(team)
|
||||
newTeam, err := a.ch.srv.teamService.UpdateTeam(team, teams.UpdateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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, nfErr.Error(), http.StatusNotFound)
|
||||
case errors.As(err, &invErr):
|
||||
return nil, model.NewAppError("RenameTeam", "app.team.update.find.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
||||
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]interface{}{"Domain": domErr.Domain}, "", http.StatusBadRequest)
|
||||
default:
|
||||
return nil, model.NewAppError("RenameTeam", "app.team.update.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return newTeam, nil
|
||||
@@ -276,23 +224,24 @@ func (a *App) UpdateTeamPrivacy(teamID string, teamType string, allowOpenInvite
|
||||
}
|
||||
|
||||
func (a *App) PatchTeam(teamID string, patch *model.TeamPatch) (*model.Team, *model.AppError) {
|
||||
team, err := a.GetTeam(teamID)
|
||||
team, err := a.ch.srv.teamService.PatchTeam(teamID, patch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
team.Patch(patch)
|
||||
if patch.AllowOpenInvite != nil && !*patch.AllowOpenInvite {
|
||||
team.InviteId = model.NewId()
|
||||
}
|
||||
|
||||
if err = a.CheckValidDomains(team); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
team, err = a.updateTeamUnsanitized(team)
|
||||
if err != nil {
|
||||
return team, err
|
||||
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, nfErr.Error(), http.StatusNotFound)
|
||||
case errors.As(err, &invErr):
|
||||
return nil, model.NewAppError("PatchTeam", "app.team.update.find.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
||||
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]interface{}{"Domain": domErr.Domain}, "", http.StatusBadRequest)
|
||||
default:
|
||||
return nil, model.NewAppError("PatchTeam", "app.team.update.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
a.sendTeamEvent(team, model.WebsocketEventUpdateTeam)
|
||||
@@ -696,88 +645,28 @@ func (a *App) AddUserToTeamByInviteId(c *request.Context, inviteId string, userI
|
||||
return team, teamMember, nil
|
||||
}
|
||||
|
||||
// Returns three values:
|
||||
// 1. a pointer to the team member, if successful
|
||||
// 2. a boolean: true if the user has a non-deleted team member for that team already, otherwise false.
|
||||
// 3. a pointer to an AppError if something went wrong.
|
||||
func (a *App) joinUserToTeam(team *model.Team, user *model.User) (*model.TeamMember, bool, *model.AppError) {
|
||||
tm := &model.TeamMember{
|
||||
TeamId: team.Id,
|
||||
UserId: user.Id,
|
||||
SchemeGuest: user.IsGuest(),
|
||||
SchemeUser: !user.IsGuest(),
|
||||
}
|
||||
|
||||
if !user.IsGuest() {
|
||||
userShouldBeAdmin, err := a.UserIsInAdminRoleGroup(user.Id, team.Id, model.GroupSyncableTypeTeam)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
tm.SchemeAdmin = userShouldBeAdmin
|
||||
}
|
||||
|
||||
if team.Email == user.Email {
|
||||
tm.SchemeAdmin = true
|
||||
}
|
||||
|
||||
rtm, err := a.Srv().Store.Team().GetMember(context.Background(), team.Id, user.Id)
|
||||
if err != nil {
|
||||
// Membership appears to be missing. Lets try to add.
|
||||
tmr, nErr := a.Srv().Store.Team().SaveMember(tm, *a.Config().TeamSettings.MaxUsersPerTeam)
|
||||
if nErr != nil {
|
||||
var appErr *model.AppError
|
||||
var conflictErr *store.ErrConflict
|
||||
var limitExeededErr *store.ErrLimitExceeded
|
||||
switch {
|
||||
case errors.As(nErr, &appErr): // in case we haven't converted to plain error.
|
||||
return nil, false, appErr
|
||||
case errors.As(nErr, &conflictErr):
|
||||
return nil, false, model.NewAppError("joinUserToTeam", "app.team.join_user_to_team.save_member.conflict.app_error", nil, nErr.Error(), http.StatusBadRequest)
|
||||
case errors.As(nErr, &limitExeededErr):
|
||||
return nil, false, model.NewAppError("joinUserToTeam", "app.team.join_user_to_team.save_member.max_accounts.app_error", nil, nErr.Error(), http.StatusBadRequest)
|
||||
default: // last fallback in case it doesn't map to an existing app error.
|
||||
return nil, false, model.NewAppError("joinUserToTeam", "app.team.join_user_to_team.save_member.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
return tmr, false, nil
|
||||
}
|
||||
|
||||
// Membership already exists. Check if deleted and update, otherwise do nothing
|
||||
// Do nothing if already added
|
||||
if rtm.DeleteAt == 0 {
|
||||
return rtm, true, nil
|
||||
}
|
||||
|
||||
membersCount, err := a.Srv().Store.Team().GetActiveMemberCount(tm.TeamId, nil)
|
||||
if err != nil {
|
||||
return nil, false, model.NewAppError("joinUserToTeam", "app.team.get_active_member_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if membersCount >= int64(*a.Config().TeamSettings.MaxUsersPerTeam) {
|
||||
return nil, false, model.NewAppError("joinUserToTeam", "app.team.join_user_to_team.max_accounts.app_error", nil, "teamId="+tm.TeamId, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
member, nErr := a.Srv().Store.Team().UpdateMember(tm)
|
||||
if nErr != nil {
|
||||
var appErr *model.AppError
|
||||
switch {
|
||||
case errors.As(nErr, &appErr):
|
||||
return nil, false, appErr
|
||||
default:
|
||||
return nil, false, model.NewAppError("joinUserToTeam", "app.team.save_member.save.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return member, false, nil
|
||||
}
|
||||
|
||||
func (a *App) JoinUserToTeam(c *request.Context, team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
|
||||
if !a.isTeamEmailAllowed(user, team) {
|
||||
return nil, model.NewAppError("JoinUserToTeam", "api.team.join_user_to_team.allowed_domains.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
teamMember, alreadyAdded, err := a.joinUserToTeam(team, user)
|
||||
teamMember, alreadyAdded, err := a.ch.srv.teamService.JoinUserToTeam(team, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var appErr *model.AppError
|
||||
var conflictErr *store.ErrConflict
|
||||
var limitExeededErr *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)
|
||||
case errors.Is(err, teams.MemberCountError):
|
||||
return nil, model.NewAppError("JoinUserToTeam", "app.team.get_active_member_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
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)
|
||||
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, err.Error(), http.StatusBadRequest)
|
||||
case errors.As(err, &limitExeededErr):
|
||||
return nil, model.NewAppError("JoinUserToTeam", "app.team.join_user_to_team.save_member.max_accounts.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
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, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
if alreadyAdded {
|
||||
return teamMember, nil
|
||||
@@ -838,7 +727,7 @@ func (a *App) JoinUserToTeam(c *request.Context, team *model.Team, user *model.U
|
||||
}
|
||||
|
||||
func (a *App) GetTeam(teamID string) (*model.Team, *model.AppError) {
|
||||
team, err := a.Srv().Store.Team().Get(teamID)
|
||||
team, err := a.ch.srv.teamService.GetTeam(teamID)
|
||||
if err != nil {
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
@@ -1172,37 +1061,7 @@ func (a *App) RemoveUserFromTeam(c *request.Context, teamID string, userID strin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) RemoveTeamMemberFromTeam(c *request.Context, teamMember *model.TeamMember, requestorId string) *model.AppError {
|
||||
// Send the websocket message before we actually do the remove so the user being removed gets it.
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventLeaveTeam, teamMember.TeamId, "", "", nil)
|
||||
message.Add("user_id", teamMember.UserId)
|
||||
message.Add("team_id", teamMember.TeamId)
|
||||
a.Publish(message)
|
||||
|
||||
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("RemoveTeamMemberFromTeam", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound)
|
||||
default:
|
||||
return model.NewAppError("RemoveTeamMemberFromTeam", "app.user.get.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
teamMember.Roles = ""
|
||||
teamMember.DeleteAt = model.GetMillis()
|
||||
|
||||
if _, nErr := a.Srv().Store.Team().UpdateMember(teamMember); nErr != nil {
|
||||
var appErr *model.AppError
|
||||
switch {
|
||||
case errors.As(nErr, &appErr):
|
||||
return appErr
|
||||
default:
|
||||
return model.NewAppError("RemoveTeamMemberFromTeam", "app.team.save_member.save.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) postProcessTeamMemberLeave(c *request.Context, teamMember *model.TeamMember, requestorId string) *model.AppError {
|
||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||
var actor *model.User
|
||||
if requestorId != "" {
|
||||
@@ -1218,17 +1077,28 @@ func (a *App) RemoveTeamMemberFromTeam(c *request.Context, teamMember *model.Tea
|
||||
})
|
||||
}
|
||||
|
||||
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, nfErr.Error(), http.StatusNotFound)
|
||||
default:
|
||||
return model.NewAppError("postProcessTeamMemberLeave", "app.user.get.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := a.Srv().Store.User().UpdateUpdateAt(user.Id); err != nil {
|
||||
return model.NewAppError("RemoveTeamMemberFromTeam", "app.user.update_update.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return model.NewAppError("postProcessTeamMemberLeave", "app.user.update_update.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if err := a.Srv().Store.Channel().ClearSidebarOnTeamLeave(user.Id, teamMember.TeamId); err != nil {
|
||||
return model.NewAppError("RemoveTeamMemberFromTeam", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return model.NewAppError("postProcessTeamMemberLeave", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// 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("RemoveTeamMemberFromTeam", "app.preference.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return model.NewAppError("postProcessTeamMemberLeave", "app.preference.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
a.ClearSessionCacheForUser(user.Id)
|
||||
@@ -1264,18 +1134,18 @@ func (a *App) LeaveTeam(c *request.Context, team *model.Team, user *model.User,
|
||||
}
|
||||
}
|
||||
|
||||
channel, nErr := a.Srv().Store.Channel().GetByName(team.Id, model.DefaultChannelName, false)
|
||||
if nErr != nil {
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
case errors.As(nErr, &nfErr):
|
||||
return model.NewAppError("LeaveTeam", "app.channel.get_by_name.missing.app_error", nil, nfErr.Error(), http.StatusNotFound)
|
||||
default:
|
||||
return model.NewAppError("LeaveTeam", "app.channel.get_by_name.existing.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
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, nfErr.Error(), http.StatusNotFound)
|
||||
default:
|
||||
return model.NewAppError("LeaveTeam", "app.channel.get_by_name.existing.app_error", nil, cErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
if requestorId == user.Id {
|
||||
if err = a.postLeaveTeamMessage(c, user, channel); err != nil {
|
||||
mlog.Warn("Failed to post join/leave message", mlog.Err(err))
|
||||
@@ -1287,7 +1157,11 @@ func (a *App) LeaveTeam(c *request.Context, team *model.Team, user *model.User,
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.RemoveTeamMemberFromTeam(c, teamMember, requestorId); err != nil {
|
||||
if err := a.ch.srv.teamService.RemoveTeamMember(teamMember); err != nil {
|
||||
return model.NewAppError("RemoveTeamMemberFromTeam", "app.team.save_member.save.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if err := a.postProcessTeamMemberLeave(c, teamMember, requestorId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1425,7 +1299,7 @@ func (a *App) InviteNewUsersToTeamGracefully(emailList []string, teamID, senderI
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowedDomains := a.getAllowedDomains(user, team)
|
||||
allowedDomains := a.ch.srv.teamService.GetAllowedDomains(user, team)
|
||||
var inviteListWithErrors []*model.EmailInviteWithError
|
||||
var goodEmails []string
|
||||
for _, email := range emailList {
|
||||
@@ -1433,7 +1307,7 @@ func (a *App) InviteNewUsersToTeamGracefully(emailList []string, teamID, senderI
|
||||
Email: email,
|
||||
Error: nil,
|
||||
}
|
||||
if !a.isEmailAddressAllowed(email, allowedDomains) {
|
||||
if !teams.IsEmailAddressAllowed(email, allowedDomains) {
|
||||
invite.Error = model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.invalid_email.app_error", map[string]interface{}{"Addresses": email}, "", http.StatusBadRequest)
|
||||
} else {
|
||||
goodEmails = append(goodEmails, email)
|
||||
@@ -1583,11 +1457,11 @@ func (a *App) InviteNewUsersToTeam(emailList []string, teamID, senderId string)
|
||||
return err
|
||||
}
|
||||
|
||||
allowedDomains := a.getAllowedDomains(user, team)
|
||||
allowedDomains := a.ch.srv.teamService.GetAllowedDomains(user, team)
|
||||
var invalidEmailList []string
|
||||
|
||||
for _, email := range emailList {
|
||||
if !a.isEmailAddressAllowed(email, allowedDomains) {
|
||||
if !teams.IsEmailAddressAllowed(email, allowedDomains) {
|
||||
invalidEmailList = append(invalidEmailList, email)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,37 +740,8 @@ func TestJoinUserToTeam(t *testing.T) {
|
||||
ruser, _ := th.App.CreateUser(th.Context, &user)
|
||||
defer th.App.PermanentDeleteUser(th.Context, &user)
|
||||
|
||||
var alreadyAdded bool
|
||||
_, alreadyAdded, err = th.App.joinUserToTeam(team, ruser)
|
||||
require.False(t, alreadyAdded, "Should return already added equal to false")
|
||||
require.Nil(t, err, "Should return no error")
|
||||
})
|
||||
|
||||
t.Run("join when you are a member", func(t *testing.T) {
|
||||
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
ruser, _ := th.App.CreateUser(th.Context, &user)
|
||||
defer th.App.PermanentDeleteUser(th.Context, &user)
|
||||
|
||||
th.App.joinUserToTeam(team, ruser)
|
||||
|
||||
var alreadyAdded bool
|
||||
_, alreadyAdded, err = th.App.joinUserToTeam(team, ruser)
|
||||
require.True(t, alreadyAdded, "Should return already added")
|
||||
require.Nil(t, err, "Should return no error")
|
||||
})
|
||||
|
||||
t.Run("re-join after leaving", func(t *testing.T) {
|
||||
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
ruser, _ := th.App.CreateUser(th.Context, &user)
|
||||
defer th.App.PermanentDeleteUser(th.Context, &user)
|
||||
|
||||
th.App.joinUserToTeam(team, ruser)
|
||||
th.App.LeaveTeam(th.Context, team, ruser, ruser.Id)
|
||||
|
||||
var alreadyAdded bool
|
||||
_, alreadyAdded, err = th.App.joinUserToTeam(team, ruser)
|
||||
require.False(t, alreadyAdded, "Should return already added equal to false")
|
||||
require.Nil(t, err, "Should return no error")
|
||||
_, appErr := th.App.JoinUserToTeam(th.Context, team, ruser, "")
|
||||
require.Nil(t, appErr, "Should return no error")
|
||||
})
|
||||
|
||||
t.Run("new join with limit problem", func(t *testing.T) {
|
||||
@@ -781,10 +752,12 @@ func TestJoinUserToTeam(t *testing.T) {
|
||||
|
||||
defer th.App.PermanentDeleteUser(th.Context, &user1)
|
||||
defer th.App.PermanentDeleteUser(th.Context, &user2)
|
||||
th.App.joinUserToTeam(team, ruser1)
|
||||
|
||||
_, _, err = th.App.joinUserToTeam(team, ruser2)
|
||||
require.NotNil(t, err, "Should fail")
|
||||
_, appErr := th.App.JoinUserToTeam(th.Context, team, ruser1, ruser2.Id)
|
||||
require.Nil(t, appErr, "Should return no error")
|
||||
|
||||
_, appErr = th.App.JoinUserToTeam(th.Context, team, ruser2, ruser1.Id)
|
||||
require.NotNil(t, appErr, "Should fail")
|
||||
})
|
||||
|
||||
t.Run("re-join alfter leaving with limit problem", func(t *testing.T) {
|
||||
@@ -797,12 +770,15 @@ func TestJoinUserToTeam(t *testing.T) {
|
||||
defer th.App.PermanentDeleteUser(th.Context, &user1)
|
||||
defer th.App.PermanentDeleteUser(th.Context, &user2)
|
||||
|
||||
th.App.joinUserToTeam(team, ruser1)
|
||||
th.App.LeaveTeam(th.Context, team, ruser1, ruser1.Id)
|
||||
th.App.joinUserToTeam(team, ruser2)
|
||||
_, appErr := th.App.JoinUserToTeam(th.Context, team, ruser1, ruser2.Id)
|
||||
require.Nil(t, appErr, "Should return no error")
|
||||
appErr = th.App.LeaveTeam(th.Context, team, ruser1, ruser1.Id)
|
||||
require.Nil(t, appErr, "Should return no error")
|
||||
_, appErr = th.App.JoinUserToTeam(th.Context, team, ruser2, ruser2.Id)
|
||||
require.Nil(t, appErr, "Should return no error")
|
||||
|
||||
_, _, err = th.App.joinUserToTeam(team, ruser1)
|
||||
require.NotNil(t, err, "Should fail")
|
||||
_, appErr = th.App.JoinUserToTeam(th.Context, team, ruser1, ruser2.Id)
|
||||
require.NotNil(t, appErr, "Should fail")
|
||||
})
|
||||
|
||||
t.Run("new join with correct scheme_admin value from group syncable", func(t *testing.T) {
|
||||
@@ -826,8 +802,8 @@ func TestJoinUserToTeam(t *testing.T) {
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = model.NewInt(999) })
|
||||
|
||||
tm1, _, err := th.App.joinUserToTeam(team, ruser1)
|
||||
require.Nil(t, err)
|
||||
tm1, appErr := th.App.JoinUserToTeam(th.Context, team, ruser1, "")
|
||||
require.Nil(t, appErr)
|
||||
require.False(t, tm1.SchemeAdmin)
|
||||
|
||||
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
@@ -841,8 +817,8 @@ func TestJoinUserToTeam(t *testing.T) {
|
||||
_, err = th.App.UpdateGroupSyncable(gs)
|
||||
require.Nil(t, err)
|
||||
|
||||
tm2, _, err := th.App.joinUserToTeam(team, ruser2)
|
||||
require.Nil(t, err)
|
||||
tm2, appErr := th.App.JoinUserToTeam(th.Context, team, ruser2, "")
|
||||
require.Nil(t, appErr)
|
||||
require.True(t, tm2.SchemeAdmin)
|
||||
})
|
||||
}
|
||||
|
||||
20
app/teams/errors.go
Обычный файл
20
app/teams/errors.go
Обычный файл
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package teams
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
AcceptedDomainError = errors.New("the user cannot be added as the domain associated with the account is not permitted")
|
||||
MemberCountError = errors.New("unable to count the team members")
|
||||
MaxMemberCountError = errors.New("reached to the maximum number of allowed accounts")
|
||||
)
|
||||
|
||||
type DomainError struct {
|
||||
Domain string
|
||||
}
|
||||
|
||||
func (DomainError) Error() string {
|
||||
return "restricting team to the domain, it is not allowed by the system config"
|
||||
}
|
||||
143
app/teams/helper_test.go
Обычный файл
143
app/teams/helper_test.go
Обычный файл
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package teams
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
)
|
||||
|
||||
type TestHelper struct {
|
||||
service *TeamService
|
||||
configStore *config.Store
|
||||
dbStore store.Store
|
||||
workspace string
|
||||
|
||||
Context *request.Context
|
||||
LogBuffer *bytes.Buffer
|
||||
}
|
||||
|
||||
type mockWebHub struct{}
|
||||
|
||||
func (mockWebHub) Publish(*model.WebSocketEvent) {}
|
||||
|
||||
func Setup(tb testing.TB) *TestHelper {
|
||||
if testing.Short() {
|
||||
tb.SkipNow()
|
||||
}
|
||||
dbStore := mainHelper.GetStore()
|
||||
dbStore.DropAllTables()
|
||||
dbStore.MarkSystemRanUnitTests()
|
||||
mainHelper.PreloadMigrations()
|
||||
|
||||
return setupTestHelper(dbStore, false, tb)
|
||||
}
|
||||
|
||||
func setupTestHelper(s store.Store, includeCacheLayer bool, tb testing.TB) *TestHelper {
|
||||
tempWorkspace, err := ioutil.TempDir("", "teamservicetest")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configStore := config.NewTestMemoryStore()
|
||||
|
||||
config := configStore.Get()
|
||||
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
||||
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
||||
*config.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||
*config.LogSettings.EnableSentry = false // disable error reporting during tests
|
||||
*config.AnnouncementSettings.AdminNoticesEnabled = false
|
||||
*config.AnnouncementSettings.UserNoticesEnabled = false
|
||||
*config.TeamSettings.MaxUsersPerTeam = 50
|
||||
*config.RateLimitSettings.Enable = false
|
||||
*config.TeamSettings.EnableOpenServer = true
|
||||
// Disable strict password requirements for test
|
||||
*config.PasswordSettings.MinimumLength = 5
|
||||
*config.PasswordSettings.Lowercase = false
|
||||
*config.PasswordSettings.Uppercase = false
|
||||
*config.PasswordSettings.Symbol = false
|
||||
*config.PasswordSettings.Number = false
|
||||
configStore.Set(config)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
|
||||
return &TestHelper{
|
||||
service: &TeamService{
|
||||
store: s.Team(),
|
||||
channelStore: s.Channel(),
|
||||
groupStore: s.Group(),
|
||||
config: configStore.Get,
|
||||
license: func() *model.License {
|
||||
return model.NewTestLicense()
|
||||
},
|
||||
wh: &mockWebHub{},
|
||||
},
|
||||
Context: &request.Context{},
|
||||
configStore: configStore,
|
||||
dbStore: s,
|
||||
LogBuffer: buffer,
|
||||
workspace: tempWorkspace,
|
||||
}
|
||||
}
|
||||
|
||||
func (th *TestHelper) TearDown() {
|
||||
th.configStore.Close()
|
||||
|
||||
th.dbStore.Close()
|
||||
|
||||
if th.workspace != "" {
|
||||
os.RemoveAll(th.workspace)
|
||||
}
|
||||
}
|
||||
|
||||
func (th *TestHelper) UpdateConfig(f func(*model.Config)) {
|
||||
if th.configStore.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
old := th.configStore.Get()
|
||||
updated := old.Clone()
|
||||
f(updated)
|
||||
if _, _, err := th.configStore.Set(updated); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateUser(u *model.User) *model.User {
|
||||
u.EmailVerified = true
|
||||
user, err := th.dbStore.User().Save(u)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func (th *TestHelper) DeleteUser(u *model.User) {
|
||||
err := th.dbStore.User().PermanentDelete(u.Id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (th *TestHelper) DeleteTeam(t *model.Team) {
|
||||
if err := th.dbStore.Channel().PermanentDeleteByTeam(t.Id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := th.dbStore.Team().RemoveAllMembersByTeam(t.Id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := th.dbStore.Team().PermanentDelete(t.Id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
32
app/teams/main_test.go
Обычный файл
32
app/teams/main_test.go
Обычный файл
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package teams
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
var mainHelper *testlib.MainHelper
|
||||
var replicaFlag bool
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if f := flag.Lookup("mysql-replica"); f == nil {
|
||||
flag.BoolVar(&replicaFlag, "mysql-replica", false, "")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
var options = testlib.HelperOptions{
|
||||
EnableStore: true,
|
||||
EnableResources: true,
|
||||
WithReadReplica: replicaFlag,
|
||||
}
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
|
||||
mainHelper.Main(m)
|
||||
}
|
||||
68
app/teams/service.go
Обычный файл
68
app/teams/service.go
Обычный файл
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package teams
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
)
|
||||
|
||||
type TeamService struct {
|
||||
store store.TeamStore
|
||||
groupStore store.GroupStore
|
||||
channelStore store.ChannelStore // TODO: replace this with ChannelService in the future
|
||||
users Users
|
||||
wh WebHub
|
||||
config func() *model.Config
|
||||
license func() *model.License
|
||||
}
|
||||
|
||||
// ServiceConfig is used to initialize the TeamService.
|
||||
type ServiceConfig struct {
|
||||
// Mandatory fields
|
||||
TeamStore store.TeamStore
|
||||
GroupStore store.GroupStore
|
||||
ChannelStore store.ChannelStore
|
||||
Users Users
|
||||
WebHub WebHub
|
||||
ConfigFn func() *model.Config
|
||||
LicenseFn func() *model.License
|
||||
}
|
||||
|
||||
// Users is a subset of UserService interface
|
||||
type Users interface {
|
||||
GetUser(userID string) (*model.User, error)
|
||||
}
|
||||
|
||||
// WebHub is used to publish events, the name should be given appropriately
|
||||
// while developing the websocket or clustering service
|
||||
type WebHub interface {
|
||||
Publish(message *model.WebSocketEvent)
|
||||
}
|
||||
|
||||
func New(c ServiceConfig) (*TeamService, error) {
|
||||
if err := c.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TeamService{
|
||||
store: c.TeamStore,
|
||||
groupStore: c.GroupStore,
|
||||
channelStore: c.ChannelStore,
|
||||
users: c.Users,
|
||||
config: c.ConfigFn,
|
||||
license: c.LicenseFn,
|
||||
wh: c.WebHub,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ServiceConfig) validate() error {
|
||||
if c.ConfigFn == nil || c.TeamStore == nil || c.LicenseFn == nil || c.Users == nil || c.ChannelStore == nil || c.GroupStore == nil || c.WebHub == nil {
|
||||
return errors.New("required parameters are not provided")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
198
app/teams/teams.go
Обычный файл
198
app/teams/teams.go
Обычный файл
@@ -0,0 +1,198 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package teams
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
||||
)
|
||||
|
||||
func (ts *TeamService) CreateTeam(team *model.Team) (*model.Team, error) {
|
||||
team.InviteId = ""
|
||||
rteam, err := ts.store.Save(team)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := ts.createDefaultChannels(rteam.Id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rteam, nil
|
||||
}
|
||||
|
||||
func (ts *TeamService) GetTeam(teamID string) (*model.Team, error) {
|
||||
team, err := ts.store.Get(teamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return team, nil
|
||||
}
|
||||
|
||||
// CreateDefaultChannels creates channels in the given team for each channel returned by (*App).DefaultChannelNames.
|
||||
//
|
||||
func (ts *TeamService) createDefaultChannels(teamID string) ([]*model.Channel, error) {
|
||||
displayNames := map[string]string{
|
||||
"town-square": i18n.T("api.channel.create_default_channels.town_square"),
|
||||
"off-topic": i18n.T("api.channel.create_default_channels.off_topic"),
|
||||
}
|
||||
channels := []*model.Channel{}
|
||||
defaultChannelNames := ts.DefaultChannelNames()
|
||||
for _, name := range defaultChannelNames {
|
||||
displayName := i18n.TDefault(displayNames[name], name)
|
||||
channel := &model.Channel{DisplayName: displayName, Name: name, Type: model.ChannelTypeOpen, TeamId: teamID}
|
||||
// We should use the channel service here (coming soon). Ideally, we should just emit an event
|
||||
// and let the subscribers do the job, in this case it would be the channels service.
|
||||
// Currently we are adding services to the server and because of that we are using
|
||||
// the channel store here. This should be replaced in the future.
|
||||
if _, err := ts.channelStore.Save(channel, *ts.config().TeamSettings.MaxChannelsPerTeam); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
channels = append(channels, channel)
|
||||
}
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
type UpdateOptions struct {
|
||||
Sanitized bool
|
||||
Imported bool
|
||||
}
|
||||
|
||||
func (ts *TeamService) UpdateTeam(team *model.Team, opts UpdateOptions) (*model.Team, error) {
|
||||
oldTeam := team
|
||||
var err error
|
||||
|
||||
if !opts.Imported {
|
||||
oldTeam, err = ts.store.Get(team.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = ts.checkValidDomains(team); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Sanitized {
|
||||
oldTeam.DisplayName = team.DisplayName
|
||||
oldTeam.Description = team.Description
|
||||
oldTeam.AllowOpenInvite = team.AllowOpenInvite
|
||||
oldTeam.CompanyName = team.CompanyName
|
||||
oldTeam.AllowedDomains = team.AllowedDomains
|
||||
oldTeam.LastTeamIconUpdate = team.LastTeamIconUpdate
|
||||
oldTeam.GroupConstrained = team.GroupConstrained
|
||||
}
|
||||
|
||||
oldTeam, err = ts.store.Update(oldTeam)
|
||||
if err != nil {
|
||||
return team, err
|
||||
}
|
||||
|
||||
return oldTeam, nil
|
||||
}
|
||||
|
||||
func (ts *TeamService) PatchTeam(teamID string, patch *model.TeamPatch) (*model.Team, error) {
|
||||
team, err := ts.store.Get(teamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
team.Patch(patch)
|
||||
if patch.AllowOpenInvite != nil && !*patch.AllowOpenInvite {
|
||||
team.InviteId = model.NewId()
|
||||
}
|
||||
|
||||
if err = ts.checkValidDomains(team); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
team, err = ts.store.Update(team)
|
||||
if err != nil {
|
||||
return team, err
|
||||
}
|
||||
|
||||
return team, nil
|
||||
}
|
||||
|
||||
// JoinUserToTeam adds a user to the team and it returns three values:
|
||||
// 1. a pointer to the team member, if successful
|
||||
// 2. a boolean: true if the user has a non-deleted team member for that team already, otherwise false.
|
||||
// 3. a pointer to an AppError if something went wrong.
|
||||
func (ts *TeamService) JoinUserToTeam(team *model.Team, user *model.User) (*model.TeamMember, bool, error) {
|
||||
if !ts.IsTeamEmailAllowed(user, team) {
|
||||
return nil, false, AcceptedDomainError
|
||||
}
|
||||
|
||||
tm := &model.TeamMember{
|
||||
TeamId: team.Id,
|
||||
UserId: user.Id,
|
||||
SchemeGuest: user.IsGuest(),
|
||||
SchemeUser: !user.IsGuest(),
|
||||
}
|
||||
|
||||
if !user.IsGuest() {
|
||||
userShouldBeAdmin, err := ts.userIsInAdminRoleGroup(user.Id, team.Id, model.GroupSyncableTypeTeam)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
tm.SchemeAdmin = userShouldBeAdmin
|
||||
}
|
||||
|
||||
if team.Email == user.Email {
|
||||
tm.SchemeAdmin = true
|
||||
}
|
||||
|
||||
rtm, err := ts.store.GetMember(context.Background(), team.Id, user.Id)
|
||||
if err != nil {
|
||||
// Membership appears to be missing. Lets try to add.
|
||||
tmr, nErr := ts.store.SaveMember(tm, *ts.config().TeamSettings.MaxUsersPerTeam)
|
||||
if nErr != nil {
|
||||
return nil, false, nErr
|
||||
}
|
||||
return tmr, false, nil
|
||||
}
|
||||
|
||||
// Membership already exists. Check if deleted and update, otherwise do nothing
|
||||
// Do nothing if already added
|
||||
if rtm.DeleteAt == 0 {
|
||||
return rtm, true, nil
|
||||
}
|
||||
|
||||
membersCount, err := ts.store.GetActiveMemberCount(tm.TeamId, nil)
|
||||
if err != nil {
|
||||
return nil, false, MemberCountError
|
||||
}
|
||||
|
||||
if membersCount >= int64(*ts.config().TeamSettings.MaxUsersPerTeam) {
|
||||
return nil, false, MaxMemberCountError
|
||||
}
|
||||
|
||||
member, nErr := ts.store.UpdateMember(tm)
|
||||
if nErr != nil {
|
||||
return nil, false, nErr
|
||||
}
|
||||
|
||||
return member, false, nil
|
||||
}
|
||||
|
||||
// RemoveTeamMember removes the team member from the team. This method sends
|
||||
// the websocket message before actually removing so the user being removed gets it.
|
||||
func (ts *TeamService) RemoveTeamMember(teamMember *model.TeamMember) error {
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventLeaveTeam, teamMember.TeamId, "", "", nil)
|
||||
message.Add("user_id", teamMember.UserId)
|
||||
message.Add("team_id", teamMember.TeamId)
|
||||
ts.wh.Publish(message)
|
||||
|
||||
teamMember.Roles = ""
|
||||
teamMember.DeleteAt = model.GetMillis()
|
||||
|
||||
if _, nErr := ts.store.UpdateMember(teamMember); nErr != nil {
|
||||
return nErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
130
app/teams/teams_test.go
Обычный файл
130
app/teams/teams_test.go
Обычный файл
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package teams
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateTeam(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
team := &model.Team{
|
||||
DisplayName: "dn_" + id,
|
||||
Name: "name" + id,
|
||||
Email: "success+" + id + "@simulator.amazonses.com",
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
|
||||
_, err := th.service.CreateTeam(team)
|
||||
require.NoError(t, err, "Should create a new team")
|
||||
|
||||
_, err = th.service.CreateTeam(team)
|
||||
require.Error(t, err, "Should not create a new team - team already exist")
|
||||
}
|
||||
|
||||
func TestJoinUserToTeam(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
team := &model.Team{
|
||||
DisplayName: "dn_" + id,
|
||||
Name: "name" + id,
|
||||
Email: "success+" + id + "@simulator.amazonses.com",
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
|
||||
_, err := th.service.CreateTeam(team)
|
||||
require.NoError(t, err, "Should create a new team")
|
||||
|
||||
maxUsersPerTeam := th.service.config().TeamSettings.MaxUsersPerTeam
|
||||
defer func() {
|
||||
th.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = maxUsersPerTeam })
|
||||
th.DeleteTeam(team)
|
||||
}()
|
||||
one := 1
|
||||
th.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = &one })
|
||||
|
||||
t.Run("new join", func(t *testing.T) {
|
||||
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
ruser := th.CreateUser(&user)
|
||||
defer th.DeleteUser(&user)
|
||||
|
||||
_, alreadyAdded, err := th.service.JoinUserToTeam(team, ruser)
|
||||
require.False(t, alreadyAdded, "Should return already added equal to false")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("join when you are a member", func(t *testing.T) {
|
||||
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
ruser := th.CreateUser(&user)
|
||||
defer th.DeleteUser(&user)
|
||||
|
||||
_, _, err := th.service.JoinUserToTeam(team, ruser)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, alreadyAdded, err := th.service.JoinUserToTeam(team, ruser)
|
||||
require.True(t, alreadyAdded, "Should return already added")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("re-join after leaving", func(t *testing.T) {
|
||||
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
ruser := th.CreateUser(&user)
|
||||
defer th.DeleteUser(&user)
|
||||
|
||||
member, _, err := th.service.JoinUserToTeam(team, ruser)
|
||||
require.NoError(t, err)
|
||||
err = th.service.RemoveTeamMember(member)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, alreadyAdded, err := th.service.JoinUserToTeam(team, ruser)
|
||||
require.False(t, alreadyAdded, "Should return already added equal to false")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("new join with limit problem", func(t *testing.T) {
|
||||
user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
ruser1 := th.CreateUser(&user1)
|
||||
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
ruser2 := th.CreateUser(&user2)
|
||||
|
||||
defer th.DeleteUser(&user1)
|
||||
defer th.DeleteUser(&user2)
|
||||
|
||||
_, _, err := th.service.JoinUserToTeam(team, ruser1)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = th.service.JoinUserToTeam(team, ruser2)
|
||||
require.Error(t, err, "Should fail")
|
||||
})
|
||||
|
||||
t.Run("re-join alfter leaving with limit problem", func(t *testing.T) {
|
||||
user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
ruser1 := th.CreateUser(&user1)
|
||||
|
||||
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
||||
ruser2 := th.CreateUser(&user2)
|
||||
|
||||
defer th.DeleteUser(&user1)
|
||||
defer th.DeleteUser(&user2)
|
||||
|
||||
member, _, err := th.service.JoinUserToTeam(team, ruser1)
|
||||
require.NoError(t, err)
|
||||
err = th.service.RemoveTeamMember(member)
|
||||
require.NoError(t, err)
|
||||
_, _, err = th.service.JoinUserToTeam(team, ruser2)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = th.service.JoinUserToTeam(team, ruser1)
|
||||
require.Error(t, err, "Should fail")
|
||||
})
|
||||
}
|
||||
113
app/teams/utils.go
Обычный файл
113
app/teams/utils.go
Обычный файл
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package teams
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
// By default the list will be (not necessarily in this order):
|
||||
// ['town-square', 'off-topic']
|
||||
// However, if TeamSettings.ExperimentalDefaultChannels contains a list of channels then that list will replace
|
||||
// 'off-topic' and be included in the return results in addition to 'town-square'. For example:
|
||||
// ['town-square', 'game-of-thrones', 'wow']
|
||||
//
|
||||
func (ts *TeamService) DefaultChannelNames() []string {
|
||||
names := []string{"town-square"}
|
||||
|
||||
if len(ts.config().TeamSettings.ExperimentalDefaultChannels) == 0 {
|
||||
names = append(names, "off-topic")
|
||||
} else {
|
||||
seenChannels := map[string]bool{"town-square": true}
|
||||
for _, channelName := range ts.config().TeamSettings.ExperimentalDefaultChannels {
|
||||
if !seenChannels[channelName] {
|
||||
names = append(names, channelName)
|
||||
seenChannels[channelName] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func IsEmailAddressAllowed(email string, allowedDomains []string) bool {
|
||||
for _, restriction := range allowedDomains {
|
||||
domains := normalizeDomains(restriction)
|
||||
if len(domains) <= 0 {
|
||||
continue
|
||||
}
|
||||
matched := false
|
||||
for _, d := range domains {
|
||||
if strings.HasSuffix(email, "@"+d) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ts *TeamService) IsTeamEmailAllowed(user *model.User, team *model.Team) bool {
|
||||
if user.IsBot {
|
||||
return true
|
||||
}
|
||||
email := strings.ToLower(user.Email)
|
||||
allowedDomains := ts.GetAllowedDomains(user, team)
|
||||
return IsEmailAddressAllowed(email, allowedDomains)
|
||||
}
|
||||
|
||||
func (ts *TeamService) GetAllowedDomains(user *model.User, team *model.Team) []string {
|
||||
if user.IsGuest() {
|
||||
return []string{*ts.config().GuestAccountsSettings.RestrictCreationToDomains}
|
||||
}
|
||||
// First check per team allowedDomains, then app wide restrictions
|
||||
return []string{team.AllowedDomains, *ts.config().TeamSettings.RestrictCreationToDomains}
|
||||
}
|
||||
|
||||
func (ts *TeamService) checkValidDomains(team *model.Team) error {
|
||||
validDomains := normalizeDomains(*ts.config().TeamSettings.RestrictCreationToDomains)
|
||||
if len(validDomains) > 0 {
|
||||
for _, domain := range normalizeDomains(team.AllowedDomains) {
|
||||
matched := false
|
||||
for _, d := range validDomains {
|
||||
if domain == d {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return &DomainError{Domain: domain}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeDomains(domains string) []string {
|
||||
// commas and @ signs are optional
|
||||
// can be in the form of "@corp.mattermost.com, mattermost.com mattermost.org" -> corp.mattermost.com mattermost.com mattermost.org
|
||||
return strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(strings.Replace(domains, "@", " ", -1), ",", " ", -1))))
|
||||
}
|
||||
|
||||
// UserIsInAdminRoleGroup returns true at least one of the user's groups are configured to set the members as
|
||||
// admins in the given syncable.
|
||||
func (ts *TeamService) userIsInAdminRoleGroup(userID, syncableID string, syncableType model.GroupSyncableType) (bool, error) {
|
||||
groupIDs, err := ts.groupStore.AdminRoleGroupsForSyncableMember(userID, syncableID, syncableType)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(groupIDs) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -89,6 +89,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
||||
|
||||
sessionStore := mocks.SessionStore{}
|
||||
oAuthStore := mocks.OAuthStore{}
|
||||
groupStore := mocks.GroupStore{}
|
||||
|
||||
mockStore.On("System").Return(&systemStore)
|
||||
mockStore.On("User").Return(&userStore)
|
||||
@@ -103,5 +104,6 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
||||
mockStore.On("MarkSystemRanUnitTests").Return(nil)
|
||||
mockStore.On("Session").Return(&sessionStore)
|
||||
mockStore.On("OAuth").Return(&oAuthStore)
|
||||
mockStore.On("Group").Return(&groupStore)
|
||||
return &mockStore
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user