[MM-32543] - Add ability to send email to admin that users are trying to invite others (#16882)

* [MM-32543] - Add ability to send email to admin that users are trying to join

* Update email template

* Feedback impl-1

* Fix test

* Feedback impl-2

* Fix error

* Feedback impl

* Use 413 status code

* make i18n-extract

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Allan Guwatudde
2021-02-16 15:03:08 +03:00
коммит произвёл GitHub
родитель d06a62ce64
Коммит 3f3abc3f3d
9 изменённых файлов: 313 добавлений и 13 удалений

Просмотреть файл

@@ -265,6 +265,9 @@ type AppIface interface {
SearchAllChannels(term string, opts model.ChannelSearchOpts) (*model.ChannelListWithTeamData, int64, *model.AppError)
// SearchAllTeams returns a team list and the total count of the results
SearchAllTeams(searchOpts *model.TeamSearch) ([]*model.Team, int64, *model.AppError)
// SendAdminUpgradeRequestEmail takes the username of user trying to alert admins and then applies rate limit of n (number of admins) emails per user per day
// before sending the emails.
SendAdminUpgradeRequestEmail(username string, subscription *model.Subscription) *model.AppError
// SendNoCardPaymentFailedEmail
SendNoCardPaymentFailedEmail() *model.AppError
// ServePluginPublicRequest serves public plugin files

Просмотреть файл

@@ -4,6 +4,9 @@
package app
import (
"fmt"
"net/http"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
)
@@ -18,6 +21,59 @@ func (a *App) getSysAdminsEmailRecipients() ([]*model.User, *model.AppError) {
return a.GetUsers(userOptions)
}
// SendAdminUpgradeRequestEmail takes the username of user trying to alert admins and then applies rate limit of n (number of admins) emails per user per day
// before sending the emails.
func (a *App) SendAdminUpgradeRequestEmail(username string, subscription *model.Subscription) *model.AppError {
if a.Srv().License() == nil || (a.Srv().License() != nil && !*a.Srv().License().Features.Cloud) {
return nil
}
if subscription != nil && subscription.IsPaidTier == "true" {
return nil
}
if a.Srv().EmailService.PerDayEmailRateLimiter == nil {
return model.NewAppError("app.SendAdminUpgradeRequestEmail", "app.email.no_rate_limiter.app_error", nil, fmt.Sprintf("for username=%s", username), http.StatusInternalServerError)
}
// rate limit based on username as key
rateLimited, result, err := a.Srv().EmailService.PerDayEmailRateLimiter.RateLimit(username, 1)
if err != nil {
return model.NewAppError("app.SendAdminUpgradeRequestEmail", "app.email.setup_rate_limiter.app_error", nil, fmt.Sprintf("username=%s, error=%v", username, err), http.StatusInternalServerError)
}
if rateLimited {
return model.NewAppError("app.SendAdminUpgradeRequestEmail",
"app.email.rate_limit_exceeded.app_error", map[string]interface{}{"RetryAfter": result.RetryAfter.String(), "ResetAfter": result.ResetAfter.String()},
fmt.Sprintf("username=%s, retry_after_secs=%f, reset_after_secs=%f",
username, result.RetryAfter.Seconds(), result.ResetAfter.Seconds()),
http.StatusRequestEntityTooLarge)
}
sysAdmins, e := a.getSysAdminsEmailRecipients()
if e != nil {
return e
}
// we want to at least have one email sent out to an admin
countNotOks := 0
for admin := range sysAdmins {
ok, err := a.Srv().EmailService.SendUpgradeEmail(username, sysAdmins[admin].Email, sysAdmins[admin].Locale, *a.Config().ServiceSettings.SiteURL)
if !ok || err != nil {
a.Log().Error("Error sending upgrade request email", mlog.Err(err))
countNotOks++
}
}
// if not even one admin got an email, we consider that this operation errored
if countNotOks == len(sysAdmins) {
return model.NewAppError("app.SendAdminUpgradeRequestEmail", "app.user.send_emails.app_error", nil, "", http.StatusInternalServerError)
}
return nil
}
func (a *App) CheckAndSendUserLimitWarningEmails() *model.AppError {
if a.Srv().License() == nil || (a.Srv().License() != nil && !*a.Srv().License().Features.Cloud) {
// Not cloud instance, do nothing

Просмотреть файл

@@ -41,37 +41,49 @@ func condenseSiteURL(siteURL string) string {
}
type EmailService struct {
srv *Server
EmailRateLimiter *throttled.GCRARateLimiter
EmailBatching *EmailBatchingJob
srv *Server
PerHourEmailRateLimiter *throttled.GCRARateLimiter
PerDayEmailRateLimiter *throttled.GCRARateLimiter
EmailBatching *EmailBatchingJob
}
func NewEmailService(srv *Server) (*EmailService, error) {
service := &EmailService{srv: srv}
if err := service.setupInviteEmailRateLimiting(); err != nil {
if err := service.setUpRateLimiters(); err != nil {
return nil, err
}
service.InitEmailBatching()
return service, nil
}
func (es *EmailService) setupInviteEmailRateLimiting() error {
func (es *EmailService) setUpRateLimiters() error {
store, err := memstore.New(emailRateLimitingMemstoreSize)
if err != nil {
return errors.Wrap(err, "Unable to setup email rate limiting memstore.")
}
quota := throttled.RateQuota{
perHourQuota := throttled.RateQuota{
MaxRate: throttled.PerHour(emailRateLimitingPerHour),
MaxBurst: emailRateLimitingMaxBurst,
}
rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
if err != nil || rateLimiter == nil {
perDayQuota := throttled.RateQuota{
MaxRate: throttled.PerDay(1),
MaxBurst: 0,
}
perHourRateLimiter, err := throttled.NewGCRARateLimiter(store, perHourQuota)
if err != nil || perHourRateLimiter == nil {
return errors.Wrap(err, "Unable to setup email rate limiting GCRA rate limiter.")
}
es.EmailRateLimiter = rateLimiter
perDayRateLimiter, err := throttled.NewGCRARateLimiter(store, perDayQuota)
if err != nil || perDayRateLimiter == nil {
return errors.Wrap(err, "Unable to setup per day email rate limiting GCRA rate limiter.")
}
es.PerHourEmailRateLimiter = perHourRateLimiter
es.PerDayEmailRateLimiter = perDayRateLimiter
return nil
}
@@ -324,10 +336,10 @@ func (es *EmailService) sendMfaChangeEmail(email string, activated bool, locale,
}
func (es *EmailService) SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string) *model.AppError {
if es.EmailRateLimiter == nil {
if es.PerHourEmailRateLimiter == nil {
return model.NewAppError("SendInviteEmails", "app.email.no_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s", senderUserId, team.Id), http.StatusInternalServerError)
}
rateLimited, result, err := es.EmailRateLimiter.RateLimit(senderUserId, len(invites))
rateLimited, result, err := es.PerHourEmailRateLimiter.RateLimit(senderUserId, len(invites))
if err != nil {
return model.NewAppError("SendInviteEmails", "app.email.setup_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", senderUserId, team.Id, err), http.StatusInternalServerError)
}
@@ -383,10 +395,10 @@ func (es *EmailService) SendInviteEmails(team *model.Team, senderName string, se
}
func (es *EmailService) sendGuestInviteEmails(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, message string) *model.AppError {
if es.EmailRateLimiter == nil {
if es.PerHourEmailRateLimiter == nil {
return model.NewAppError("SendInviteEmails", "app.email.no_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s", senderUserId, team.Id), http.StatusInternalServerError)
}
rateLimited, result, err := es.EmailRateLimiter.RateLimit(senderUserId, len(invites))
rateLimited, result, err := es.PerHourEmailRateLimiter.RateLimit(senderUserId, len(invites))
if err != nil {
return model.NewAppError("SendInviteEmails", "app.email.setup_rate_limiter.app_error", nil, fmt.Sprintf("user_id=%s, team_id=%s, error=%v", senderUserId, team.Id, err), http.StatusInternalServerError)
}
@@ -621,6 +633,29 @@ func (es *EmailService) SendAtUserLimitWarningEmail(email string, locale string,
return true, nil
}
// SendUpgradeEmail formats an email template and sends an email to an admin specified in the email arg
func (es *EmailService) SendUpgradeEmail(user, email, locale, siteURL string) (bool, *model.AppError) {
T := utils.GetUserTranslations(locale)
subject := T("api.templates.upgrade_request_subject")
bodyPage := es.newEmailTemplate("cloud_upgrade_request_email", locale)
bodyPage.Props["Title"] = T("api.templates.upgrade_request_title", map[string]interface{}{"UserName": user})
bodyPage.Props["Info4"] = T("api.templates.upgrade_request_info4")
bodyPage.Props["Info5"] = T("api.templates.at_limit_info5")
bodyPage.Props["BillingPath"] = "admin_console/billing/subscription"
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
return false, model.NewAppError("SendUpgradeEmail", "api.user.send_upgrade_request_email.error", nil, err.Error(), http.StatusInternalServerError)
}
return true, nil
}
func (es *EmailService) SendOverUserLimitWarningEmail(email string, locale string, siteURL string) (bool, *model.AppError) {
T := utils.GetUserTranslations(locale)

Просмотреть файл

@@ -61,3 +61,38 @@ func TestSendInviteEmailRateLimits(t *testing.T) {
assert.Equal(t, "app.email.rate_limit_exceeded.app_error", err.Id)
assert.Equal(t, http.StatusRequestEntityTooLarge, err.StatusCode)
}
func TestSendAdminUpgradeRequestEmail(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
mockSubscription := &model.Subscription{
ID: "MySubscriptionID",
CustomerID: "MyCustomer",
ProductID: "SomeProductId",
AddOns: []string{},
StartAt: 1000000000,
EndAt: 2000000000,
CreateAt: 1000000000,
Seats: 100,
DNS: "some.dns.server",
IsPaidTier: "false",
}
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ExperimentalSettings.CloudUserLimit = 10
})
err := th.App.SendAdminUpgradeRequestEmail(th.BasicUser.Username, mockSubscription)
require.Nil(t, err)
err = th.App.SendAdminUpgradeRequestEmail(th.BasicUser2.Username, mockSubscription)
require.Nil(t, err)
// second attempt by the same user to send emails is blocked by rate limiter
err = th.App.SendAdminUpgradeRequestEmail(th.BasicUser.Username, mockSubscription)
require.NotNil(t, err)
assert.Equal(t, err.Id, "app.email.rate_limit_exceeded.app_error")
}

Просмотреть файл

@@ -13220,6 +13220,28 @@ func (a *OpenTracingAppLayer) SendAckToPushProxy(ack *model.PushNotificationAck)
return resultVar0
}
func (a *OpenTracingAppLayer) SendAdminUpgradeRequestEmail(username string, subscription *model.Subscription) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendAdminUpgradeRequestEmail")
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.SendAdminUpgradeRequestEmail(username, subscription)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))
ext.Error.Set(span, true)
}
return resultVar0
}
func (a *OpenTracingAppLayer) SendAutoResponse(channel *model.Channel, receiver *model.User, post *model.Post) (bool, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendAutoResponse")