MM-11120 Adding setting to disable email invitations and rate limiting. (#9063)
* Adding setting to disable email invitations. * Adding a setting and rate limiting for email invite sending. * Modifying email rate limit to 20/user/hour * Adding EnableEmailInvitations to client side config and command.
Этот коммит содержится в:
коммит произвёл
Carlos Tadeu Panato Junior
родитель
951e4ad984
Коммит
74e5d8ae66
@@ -1935,6 +1935,15 @@ func TestInviteUsersToTeam(t *testing.T) {
|
|||||||
utils.DeleteMailBox(user1)
|
utils.DeleteMailBox(user1)
|
||||||
utils.DeleteMailBox(user2)
|
utils.DeleteMailBox(user2)
|
||||||
|
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableEmailInvitations = false })
|
||||||
|
|
||||||
|
_, resp := th.SystemAdminClient.InviteUsersToTeam(th.BasicTeam.Id, emailList)
|
||||||
|
if resp.Error == nil {
|
||||||
|
t.Fatal("Should be disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableEmailInvitations = true })
|
||||||
|
|
||||||
okMsg, resp := th.SystemAdminClient.InviteUsersToTeam(th.BasicTeam.Id, emailList)
|
okMsg, resp := th.SystemAdminClient.InviteUsersToTeam(th.BasicTeam.Id, emailList)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if !okMsg {
|
if !okMsg {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/throttled/throttled"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/einterfaces"
|
"github.com/mattermost/mattermost-server/einterfaces"
|
||||||
ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs"
|
ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs"
|
||||||
@@ -47,6 +48,7 @@ type App struct {
|
|||||||
pluginStatuses map[string]*model.PluginStatus
|
pluginStatuses map[string]*model.PluginStatus
|
||||||
|
|
||||||
EmailBatching *EmailBatchingJob
|
EmailBatching *EmailBatchingJob
|
||||||
|
EmailRateLimiter *throttled.GCRARateLimiter
|
||||||
|
|
||||||
Hubs []*Hub
|
Hubs []*Hub
|
||||||
HubsStopCheckingForDeadlock chan bool
|
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...")
|
mlog.Info("Server is initializing...")
|
||||||
|
|
||||||
app.initEnterprise()
|
app.initEnterprise()
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (me *InvitePeopleProvider) GetTrigger() string {
|
|||||||
|
|
||||||
func (me *InvitePeopleProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Command {
|
func (me *InvitePeopleProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Command {
|
||||||
autoComplete := true
|
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
|
autoComplete = false
|
||||||
}
|
}
|
||||||
return &model.Command{
|
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")}
|
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)
|
emailList := strings.Fields(message)
|
||||||
|
|
||||||
for i := len(emailList) - 1; i >= 0; i-- {
|
for i := len(emailList) - 1; i >= 0; i-- {
|
||||||
|
|||||||
48
app/email.go
48
app/email.go
@@ -10,12 +10,41 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/nicksnyder/go-i18n/i18n"
|
"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/mlog"
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
"github.com/mattermost/mattermost-server/utils"
|
"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 {
|
func (a *App) SendChangeUsernameEmail(oldUsername, newUsername, email, locale, siteURL string) *model.AppError {
|
||||||
T := utils.GetUserTranslations(locale)
|
T := utils.GetUserTranslations(locale)
|
||||||
|
|
||||||
@@ -247,7 +276,24 @@ func (a *App) SendMfaChangeEmail(email string, activated bool, locale, siteURL s
|
|||||||
return nil
|
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 {
|
for _, invite := range invites {
|
||||||
if len(invite) > 0 {
|
if len(invite) > 0 {
|
||||||
senderRole := utils.T("api.team.invite_members.member")
|
senderRole := utils.T("api.team.invite_members.member")
|
||||||
|
|||||||
@@ -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 {
|
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 {
|
if len(emailList) == 0 {
|
||||||
err := model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.no_one.app_error", nil, "", http.StatusBadRequest)
|
err := model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.no_one.app_error", nil, "", http.StatusBadRequest)
|
||||||
return err
|
return err
|
||||||
@@ -842,7 +846,7 @@ func (a *App) InviteNewUsersToTeam(emailList []string, teamId, senderId string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
nameFormat := *a.Config().TeamSettings.TeammateNameDisplay
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,7 +384,11 @@ func inviteUser(a *app.App, email string, team *model.Team, teamArg string) erro
|
|||||||
return fmt.Errorf("Can't find team '%v'", teamArg)
|
return fmt.Errorf("Can't find team '%v'", teamArg)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.SendInviteEmails(team, "Administrator", invites, *a.Config().ServiceSettings.SiteURL)
|
if !*a.Config().ServiceSettings.EnableEmailInvitations {
|
||||||
|
return fmt.Errorf("Email invites are disabled.")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SendInviteEmails(team, "Administrator", "Mattermost CLI "+model.NewId(), invites, *a.Config().ServiceSettings.SiteURL)
|
||||||
CommandPrettyPrintln("Invites may or may not have been sent.")
|
CommandPrettyPrintln("Invites may or may not have been sent.")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -68,7 +68,8 @@
|
|||||||
"ImageProxyURL": "",
|
"ImageProxyURL": "",
|
||||||
"EnableAPITeamDeletion": false,
|
"EnableAPITeamDeletion": false,
|
||||||
"ExperimentalEnableHardenedMode": false,
|
"ExperimentalEnableHardenedMode": false,
|
||||||
"ExperimentalLimitClientConfig": false
|
"ExperimentalLimitClientConfig": false,
|
||||||
|
"EnableEmailInvitations": false
|
||||||
},
|
},
|
||||||
"TeamSettings": {
|
"TeamSettings": {
|
||||||
"SiteName": "Mattermost",
|
"SiteName": "Mattermost",
|
||||||
|
|||||||
@@ -311,6 +311,10 @@
|
|||||||
"id": "api.command.invite_people.email_off",
|
"id": "api.command.invite_people.email_off",
|
||||||
"translation": "Email has not been configured, no invite(s) sent"
|
"translation": "Email has not been configured, no invite(s) sent"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.command.invite_people.email_invitations_off",
|
||||||
|
"translation": "Email invitations are disabled, no invite(s) sent"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.command.invite_people.fail",
|
"id": "api.command.invite_people.fail",
|
||||||
"translation": "Encountered an error sending email invite(s)"
|
"translation": "Encountered an error sending email invite(s)"
|
||||||
@@ -1614,6 +1618,10 @@
|
|||||||
"id": "api.team.invite_members.no_one.app_error",
|
"id": "api.team.invite_members.no_one.app_error",
|
||||||
"translation": "No one to invite."
|
"translation": "No one to invite."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.invite_members.disabled.app_error",
|
||||||
|
"translation": "Email invitations are disabled."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.team.is_team_creation_allowed.disabled.app_error",
|
"id": "api.team.is_team_creation_allowed.disabled.app_error",
|
||||||
"translation": "Team creation has been disabled. Please ask your systems administrator for details."
|
"translation": "Team creation has been disabled. Please ask your systems administrator for details."
|
||||||
|
|||||||
@@ -237,9 +237,19 @@ type ServiceSettings struct {
|
|||||||
EnableAPITeamDeletion *bool
|
EnableAPITeamDeletion *bool
|
||||||
ExperimentalEnableHardenedMode *bool
|
ExperimentalEnableHardenedMode *bool
|
||||||
ExperimentalLimitClientConfig *bool
|
ExperimentalLimitClientConfig *bool
|
||||||
|
EnableEmailInvitations *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServiceSettings) SetDefaults() {
|
func (s *ServiceSettings) SetDefaults() {
|
||||||
|
if s.EnableEmailInvitations == nil {
|
||||||
|
// If the site URL is also not present then assume this is a clean install
|
||||||
|
if s.SiteURL == nil {
|
||||||
|
s.EnableEmailInvitations = NewBool(false)
|
||||||
|
} else {
|
||||||
|
s.EnableEmailInvitations = NewBool(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if s.SiteURL == nil {
|
if s.SiteURL == nil {
|
||||||
s.SiteURL = NewString(SERVICE_SETTINGS_DEFAULT_SITE_URL)
|
s.SiteURL = NewString(SERVICE_SETTINGS_DEFAULT_SITE_URL)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -573,6 +573,8 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
|
|||||||
|
|
||||||
props["RunJobs"] = strconv.FormatBool(*c.JobSettings.RunJobs)
|
props["RunJobs"] = strconv.FormatBool(*c.JobSettings.RunJobs)
|
||||||
|
|
||||||
|
props["EnableEmailInvitations"] = strconv.FormatBool(*c.ServiceSettings.EnableEmailInvitations)
|
||||||
|
|
||||||
// Set default values for all options that require a license.
|
// Set default values for all options that require a license.
|
||||||
props["ExperimentalHideTownSquareinLHS"] = "false"
|
props["ExperimentalHideTownSquareinLHS"] = "false"
|
||||||
props["ExperimentalTownSquareIsReadOnly"] = "false"
|
props["ExperimentalTownSquareIsReadOnly"] = "false"
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user