diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/app.go | 8 | ||||
-rw-r--r-- | app/command_invite_people.go | 6 | ||||
-rw-r--r-- | app/email.go | 48 | ||||
-rw-r--r-- | app/team.go | 6 |
4 files changed, 64 insertions, 4 deletions
diff --git a/app/app.go b/app/app.go index 96b9b6d13..6f98d4234 100644 --- a/app/app.go +++ b/app/app.go @@ -17,6 +17,7 @@ import ( "github.com/gorilla/mux" "github.com/pkg/errors" + "github.com/throttled/throttled" "github.com/mattermost/mattermost-server/einterfaces" ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs" @@ -46,7 +47,8 @@ type App struct { IsPluginSandboxSupported bool pluginStatuses map[string]*model.PluginStatus - EmailBatching *EmailBatchingJob + EmailBatching *EmailBatchingJob + EmailRateLimiter *throttled.GCRARateLimiter Hubs []*Hub HubsStopCheckingForDeadlock chan bool @@ -185,6 +187,10 @@ func New(options ...Option) (outApp *App, outErr error) { }) + if err := app.SetupInviteEmailRateLimiting(); err != nil { + return nil, err + } + mlog.Info("Server is initializing...") app.initEnterprise() diff --git a/app/command_invite_people.go b/app/command_invite_people.go index c3dc4f469..9ced3c5a1 100644 --- a/app/command_invite_people.go +++ b/app/command_invite_people.go @@ -28,7 +28,7 @@ func (me *InvitePeopleProvider) GetTrigger() string { func (me *InvitePeopleProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Command { autoComplete := true - if !a.Config().EmailSettings.SendEmailNotifications || !*a.Config().TeamSettings.EnableUserCreation { + if !a.Config().EmailSettings.SendEmailNotifications || !*a.Config().TeamSettings.EnableUserCreation || !*a.Config().ServiceSettings.EnableEmailInvitations { autoComplete = false } return &model.Command{ @@ -49,6 +49,10 @@ func (me *InvitePeopleProvider) DoCommand(a *App, args *model.CommandArgs, messa return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.invite_off")} } + if !*a.Config().ServiceSettings.EnableEmailInvitations { + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.email_invitations_off")} + } + emailList := strings.Fields(message) for i := len(emailList) - 1; i >= 0; i-- { diff --git a/app/email.go b/app/email.go index b4e0a8983..569e6f454 100644 --- a/app/email.go +++ b/app/email.go @@ -10,12 +10,41 @@ import ( "net/http" "github.com/nicksnyder/go-i18n/i18n" + "github.com/pkg/errors" + "github.com/throttled/throttled" + "github.com/throttled/throttled/store/memstore" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) +const ( + emailRateLimitingMemstoreSize = 65536 + emailRateLimitingPerHour = 20 + emailRateLimitingMaxBurst = 20 +) + +func (a *App) SetupInviteEmailRateLimiting() error { + store, err := memstore.New(emailRateLimitingMemstoreSize) + if err != nil { + return errors.Wrap(err, "Unable to setup email rate limiting memstore.") + } + + quota := throttled.RateQuota{ + MaxRate: throttled.PerHour(emailRateLimitingPerHour), + MaxBurst: emailRateLimitingMaxBurst, + } + + rateLimiter, err := throttled.NewGCRARateLimiter(store, quota) + if err != nil || rateLimiter == nil { + return errors.Wrap(err, "Unable to setup email rate limiting GCRA rate limiter.") + } + + a.EmailRateLimiter = rateLimiter + return nil +} + func (a *App) SendChangeUsernameEmail(oldUsername, newUsername, email, locale, siteURL string) *model.AppError { T := utils.GetUserTranslations(locale) @@ -247,7 +276,24 @@ func (a *App) SendMfaChangeEmail(email string, activated bool, locale, siteURL s return nil } -func (a *App) SendInviteEmails(team *model.Team, senderName string, invites []string, siteURL string) { +func (a *App) SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string) { + if a.EmailRateLimiter == nil { + a.Log.Error("Email invite not sent, rate limiting could not be setup.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id)) + return + } + rateLimited, result, err := a.EmailRateLimiter.RateLimit(senderUserId, len(invites)) + if rateLimited { + a.Log.Error("Invite emails rate limited.", + mlog.String("user_id", senderUserId), + mlog.String("team_id", team.Id), + mlog.String("retry_after", result.RetryAfter.String()), + mlog.Err(err)) + return + } else if err != nil { + a.Log.Error("Error rate limiting invite email.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id), mlog.Err(err)) + return + } + for _, invite := range invites { if len(invite) > 0 { senderRole := utils.T("api.team.invite_members.member") diff --git a/app/team.go b/app/team.go index beb4b1449..d9f19fab8 100644 --- a/app/team.go +++ b/app/team.go @@ -805,6 +805,10 @@ func (a *App) postRemoveFromTeamMessage(user *model.User, channel *model.Channel } func (a *App) InviteNewUsersToTeam(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 @@ -842,7 +846,7 @@ func (a *App) InviteNewUsersToTeam(emailList []string, teamId, senderId string) } nameFormat := *a.Config().TeamSettings.TeammateNameDisplay - a.SendInviteEmails(team, user.GetDisplayName(nameFormat), emailList, a.GetSiteURL()) + a.SendInviteEmails(team, user.GetDisplayName(nameFormat), user.Id, emailList, a.GetSiteURL()) return nil } |