Reduce the coupling of the mailservice with the rest of the application (#16898)
* Reduce the coupling of the mailservice with the rest of the application * Fixing tests in CI * Simplifiying mailservice config * Addressing PR review comments * Fixing tests * Removing unnecesary type definition * Fixing ServerName usage
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
69ff686667
Коммит
d06a62ce64
@@ -226,7 +226,8 @@ func (a *App) TestEmail(userID string, cfg *model.Config) *model.AppError {
|
|||||||
|
|
||||||
T := utils.GetUserTranslations(user.Locale)
|
T := utils.GetUserTranslations(user.Locale)
|
||||||
license := a.Srv().License()
|
license := a.Srv().License()
|
||||||
if err := mailservice.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg, license != nil && *license.Features.Compliance, ""); err != nil {
|
mailConfig := a.Srv().MailServiceConfig()
|
||||||
|
if err := mailservice.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), mailConfig, license != nil && *license.Features.Compliance, ""); err != nil {
|
||||||
return model.NewAppError("testEmail", "app.admin.test_email.failure", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
|
return model.NewAppError("testEmail", "app.admin.test_email.failure", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -497,7 +497,8 @@ func (a *App) NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User,
|
|||||||
subject := T("api.templates.warn_metric_ack.subject")
|
subject := T("api.templates.warn_metric_ack.subject")
|
||||||
bodyPage.Props["Title"] = warnMetricDisplayTexts.EmailBody
|
bodyPage.Props["Title"] = warnMetricDisplayTexts.EmailBody
|
||||||
|
|
||||||
if err := mailservice.SendMailUsingConfig(model.MM_SUPPORT_ADVISOR_ADDRESS, subject, bodyPage.Render(), a.Config(), false, sender.Email); err != nil {
|
mailConfig := a.Srv().MailServiceConfig()
|
||||||
|
if err := mailservice.SendMailUsingConfig(model.MM_SUPPORT_ADVISOR_ADDRESS, subject, bodyPage.Render(), mailConfig, false, sender.Email); err != nil {
|
||||||
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
|
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/v5/config"
|
"github.com/mattermost/mattermost-server/v5/config"
|
||||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/services/mailservice"
|
||||||
"github.com/mattermost/mattermost-server/v5/utils"
|
"github.com/mattermost/mattermost-server/v5/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -450,3 +451,25 @@ func (a *App) HandleMessageExportConfig(cfg *model.Config, appCfg *model.Config)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) MailServiceConfig() *mailservice.SMTPConfig {
|
||||||
|
emailSettings := s.Config().EmailSettings
|
||||||
|
hostname := utils.GetHostnameFromSiteURL(*s.Config().ServiceSettings.SiteURL)
|
||||||
|
cfg := mailservice.SMTPConfig{
|
||||||
|
Hostname: hostname,
|
||||||
|
ConnectionSecurity: *emailSettings.ConnectionSecurity,
|
||||||
|
SkipServerCertificateVerification: *emailSettings.SkipServerCertificateVerification,
|
||||||
|
ServerName: *emailSettings.SMTPServer,
|
||||||
|
Server: *emailSettings.SMTPServer,
|
||||||
|
Port: *emailSettings.SMTPPort,
|
||||||
|
ServerTimeout: *emailSettings.SMTPServerTimeout,
|
||||||
|
Username: *emailSettings.SMTPUsername,
|
||||||
|
Password: *emailSettings.SMTPPassword,
|
||||||
|
EnableSMTPAuth: *emailSettings.EnableSMTPAuth,
|
||||||
|
SendEmailNotifications: *emailSettings.SendEmailNotifications,
|
||||||
|
FeedbackName: *emailSettings.FeedbackName,
|
||||||
|
FeedbackEmail: *emailSettings.FeedbackEmail,
|
||||||
|
ReplyToAddress: *emailSettings.ReplyToAddress,
|
||||||
|
}
|
||||||
|
return &cfg
|
||||||
|
}
|
||||||
|
|||||||
@@ -558,14 +558,16 @@ func (es *EmailService) sendMail(to, subject, htmlBody string) error {
|
|||||||
|
|
||||||
func (es *EmailService) sendMailWithCC(to, subject, htmlBody string, ccMail string) error {
|
func (es *EmailService) sendMailWithCC(to, subject, htmlBody string, ccMail string) error {
|
||||||
license := es.srv.License()
|
license := es.srv.License()
|
||||||
return mailservice.SendMailUsingConfig(to, subject, htmlBody, es.srv.Config(), license != nil && *license.Features.Compliance, ccMail)
|
mailConfig := es.srv.MailServiceConfig()
|
||||||
|
|
||||||
|
return mailservice.SendMailUsingConfig(to, subject, htmlBody, mailConfig, license != nil && *license.Features.Compliance, ccMail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *EmailService) sendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader) error {
|
func (es *EmailService) sendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader) error {
|
||||||
license := es.srv.License()
|
license := es.srv.License()
|
||||||
config := es.srv.Config()
|
mailConfig := es.srv.MailServiceConfig()
|
||||||
|
|
||||||
return mailservice.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, config, license != nil && *license.Features.Compliance, "")
|
return mailservice.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, mailConfig, license != nil && *license.Features.Compliance, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *EmailService) CreateVerifyEmailToken(userID string, newEmail string) (*model.Token, *model.AppError) {
|
func (es *EmailService) CreateVerifyEmailToken(userID string, newEmail string) (*model.Token, *model.AppError) {
|
||||||
|
|||||||
@@ -115,7 +115,9 @@ func (s *Server) DoSecurityUpdateCheck() {
|
|||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
mlog.Info("Sending security bulletin", mlog.String("bulletin_id", bulletin.Id), mlog.String("user_email", user.Email))
|
mlog.Info("Sending security bulletin", mlog.String("bulletin_id", bulletin.Id), mlog.String("user_email", user.Email))
|
||||||
license := s.License()
|
license := s.License()
|
||||||
mailservice.SendMailUsingConfig(user.Email, utils.T("mattermost.bulletin.subject"), string(body), s.Config(), license != nil && *license.Features.Compliance, "")
|
mailConfig := s.MailServiceConfig()
|
||||||
|
|
||||||
|
mailservice.SendMailUsingConfig(user.Email, utils.T("mattermost.bulletin.subject"), string(body), mailConfig, license != nil && *license.Features.Compliance, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
bulletinSeen := &model.System{Name: "SecurityBulletin_" + bulletin.Id, Value: bulletin.Id}
|
bulletinSeen := &model.System{Name: "SecurityBulletin_" + bulletin.Id, Value: bulletin.Id}
|
||||||
|
|||||||
@@ -487,7 +487,9 @@ func NewServer(options ...Option) (*Server, error) {
|
|||||||
}
|
}
|
||||||
s.WebSocketRouter.app = fakeApp
|
s.WebSocketRouter.app = fakeApp
|
||||||
|
|
||||||
if nErr := mailservice.TestConnection(s.Config()); nErr != nil {
|
mailConfig := s.MailServiceConfig()
|
||||||
|
|
||||||
|
if nErr := mailservice.TestConnection(mailConfig); nErr != nil {
|
||||||
mlog.Error("Mail server connection test is failed", mlog.Err(nErr))
|
mlog.Error("Mail server connection test is failed", mlog.Err(nErr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,30 @@ import (
|
|||||||
gomail "gopkg.in/mail.v2"
|
gomail "gopkg.in/mail.v2"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/services/filesstore"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TLS = "TLS"
|
||||||
|
StartTLS = "STARTTLS"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SMTPConfig struct {
|
||||||
|
ConnectionSecurity string
|
||||||
|
SkipServerCertificateVerification bool
|
||||||
|
Hostname string
|
||||||
|
ServerName string
|
||||||
|
Server string
|
||||||
|
Port string
|
||||||
|
ServerTimeout int
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
EnableSMTPAuth bool
|
||||||
|
SendEmailNotifications bool
|
||||||
|
FeedbackName string
|
||||||
|
FeedbackEmail string
|
||||||
|
ReplyToAddress string
|
||||||
|
}
|
||||||
|
|
||||||
type mailData struct {
|
type mailData struct {
|
||||||
mimeTo string
|
mimeTo string
|
||||||
smtpTo string
|
smtpTo string
|
||||||
@@ -31,7 +50,6 @@ type mailData struct {
|
|||||||
replyTo mail.Address
|
replyTo mail.Address
|
||||||
subject string
|
subject string
|
||||||
htmlBody string
|
htmlBody string
|
||||||
attachments []*model.FileInfo
|
|
||||||
embeddedFiles map[string]io.Reader
|
embeddedFiles map[string]io.Reader
|
||||||
mimeHeaders map[string]string
|
mimeHeaders map[string]string
|
||||||
}
|
}
|
||||||
@@ -48,29 +66,17 @@ func encodeRFC2047Word(s string) string {
|
|||||||
return mime.BEncoding.Encode("utf-8", s)
|
return mime.BEncoding.Encode("utf-8", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SmtpConnectionInfo struct {
|
|
||||||
SmtpUsername string
|
|
||||||
SmtpPassword string
|
|
||||||
SmtpServerName string
|
|
||||||
SmtpServerHost string
|
|
||||||
SmtpPort string
|
|
||||||
SmtpServerTimeout int
|
|
||||||
SkipCertVerification bool
|
|
||||||
ConnectionSecurity string
|
|
||||||
Auth bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type authChooser struct {
|
type authChooser struct {
|
||||||
smtp.Auth
|
smtp.Auth
|
||||||
connectionInfo *SmtpConnectionInfo
|
config *SMTPConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authChooser) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
func (a *authChooser) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||||
smtpAddress := a.connectionInfo.SmtpServerName + ":" + a.connectionInfo.SmtpPort
|
smtpAddress := a.config.ServerName + ":" + a.config.Port
|
||||||
a.Auth = LoginAuth(a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, smtpAddress)
|
a.Auth = LoginAuth(a.config.Username, a.config.Password, smtpAddress)
|
||||||
for _, method := range server.Auth {
|
for _, method := range server.Auth {
|
||||||
if method == "PLAIN" {
|
if method == "PLAIN" {
|
||||||
a.Auth = smtp.PlainAuth("", a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, a.connectionInfo.SmtpServerName+":"+a.connectionInfo.SmtpPort)
|
a.Auth = smtp.PlainAuth("", a.config.Username, a.config.Password, a.config.ServerName+":"+a.config.Port)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,19 +117,19 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectToSMTPServerAdvanced(connectionInfo *SmtpConnectionInfo) (net.Conn, error) {
|
func ConnectToSMTPServerAdvanced(config *SMTPConfig) (net.Conn, error) {
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
smtpAddress := connectionInfo.SmtpServerHost + ":" + connectionInfo.SmtpPort
|
smtpAddress := config.Server + ":" + config.Port
|
||||||
dialer := &net.Dialer{
|
dialer := &net.Dialer{
|
||||||
Timeout: time.Duration(connectionInfo.SmtpServerTimeout) * time.Second,
|
Timeout: time.Duration(config.ServerTimeout) * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_TLS {
|
if config.ConnectionSecurity == TLS {
|
||||||
tlsconfig := &tls.Config{
|
tlsconfig := &tls.Config{
|
||||||
InsecureSkipVerify: connectionInfo.SkipCertVerification,
|
InsecureSkipVerify: config.SkipServerCertificateVerification,
|
||||||
ServerName: connectionInfo.SmtpServerName,
|
ServerName: config.ServerName,
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err = tls.DialWithDialer(dialer, "tcp", smtpAddress, tlsconfig)
|
conn, err = tls.DialWithDialer(dialer, "tcp", smtpAddress, tlsconfig)
|
||||||
@@ -140,20 +146,11 @@ func ConnectToSMTPServerAdvanced(connectionInfo *SmtpConnectionInfo) (net.Conn,
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectToSMTPServer(config *model.Config) (net.Conn, error) {
|
func ConnectToSMTPServer(config *SMTPConfig) (net.Conn, error) {
|
||||||
return ConnectToSMTPServerAdvanced(
|
return ConnectToSMTPServerAdvanced(config)
|
||||||
&SmtpConnectionInfo{
|
|
||||||
ConnectionSecurity: *config.EmailSettings.ConnectionSecurity,
|
|
||||||
SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification,
|
|
||||||
SmtpServerName: *config.EmailSettings.SMTPServer,
|
|
||||||
SmtpServerHost: *config.EmailSettings.SMTPServer,
|
|
||||||
SmtpPort: *config.EmailSettings.SMTPPort,
|
|
||||||
SmtpServerTimeout: *config.EmailSettings.SMTPServerTimeout,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSMTPClientAdvanced(ctx context.Context, conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, error) {
|
func NewSMTPClientAdvanced(ctx context.Context, conn net.Conn, config *SMTPConfig) (*smtp.Client, error) {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -161,7 +158,7 @@ func NewSMTPClientAdvanced(ctx context.Context, conn net.Conn, hostname string,
|
|||||||
ec := make(chan error)
|
ec := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
var err error
|
var err error
|
||||||
c, err = smtp.NewClient(conn, connectionInfo.SmtpServerName+":"+connectionInfo.SmtpPort)
|
c, err = smtp.NewClient(conn, config.ServerName+":"+config.Port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec <- err
|
ec <- err
|
||||||
return
|
return
|
||||||
@@ -179,50 +176,39 @@ func NewSMTPClientAdvanced(ctx context.Context, conn net.Conn, hostname string,
|
|||||||
return nil, errors.Wrap(err, "unable to connect to the SMTP server")
|
return nil, errors.Wrap(err, "unable to connect to the SMTP server")
|
||||||
}
|
}
|
||||||
|
|
||||||
if hostname != "" {
|
if config.Hostname != "" {
|
||||||
err := c.Hello(hostname)
|
err := c.Hello(config.Hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "unable to send hello message")
|
return nil, errors.Wrap(err, "unable to send hello message")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_STARTTLS {
|
if config.ConnectionSecurity == StartTLS {
|
||||||
tlsconfig := &tls.Config{
|
tlsconfig := &tls.Config{
|
||||||
InsecureSkipVerify: connectionInfo.SkipCertVerification,
|
InsecureSkipVerify: config.SkipServerCertificateVerification,
|
||||||
ServerName: connectionInfo.SmtpServerName,
|
ServerName: config.ServerName,
|
||||||
}
|
}
|
||||||
c.StartTLS(tlsconfig)
|
c.StartTLS(tlsconfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
if connectionInfo.Auth {
|
if config.EnableSMTPAuth {
|
||||||
if err := c.Auth(&authChooser{connectionInfo: connectionInfo}); err != nil {
|
if err := c.Auth(&authChooser{config: config}); err != nil {
|
||||||
return nil, errors.Wrap(err, "authentication failed")
|
return nil, errors.Wrap(err, "authentication failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSMTPClient(ctx context.Context, conn net.Conn, config *model.Config) (*smtp.Client, error) {
|
func NewSMTPClient(ctx context.Context, conn net.Conn, config *SMTPConfig) (*smtp.Client, error) {
|
||||||
return NewSMTPClientAdvanced(
|
return NewSMTPClientAdvanced(
|
||||||
ctx,
|
ctx,
|
||||||
conn,
|
conn,
|
||||||
utils.GetHostnameFromSiteURL(*config.ServiceSettings.SiteURL),
|
config,
|
||||||
&SmtpConnectionInfo{
|
|
||||||
ConnectionSecurity: *config.EmailSettings.ConnectionSecurity,
|
|
||||||
SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification,
|
|
||||||
SmtpServerName: *config.EmailSettings.SMTPServer,
|
|
||||||
SmtpServerHost: *config.EmailSettings.SMTPServer,
|
|
||||||
SmtpPort: *config.EmailSettings.SMTPPort,
|
|
||||||
SmtpServerTimeout: *config.EmailSettings.SMTPServerTimeout,
|
|
||||||
Auth: *config.EmailSettings.EnableSMTPAuth,
|
|
||||||
SmtpUsername: *config.EmailSettings.SMTPUsername,
|
|
||||||
SmtpPassword: *config.EmailSettings.SMTPPassword,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnection(config *model.Config) error {
|
func TestConnection(config *SMTPConfig) error {
|
||||||
if !*config.EmailSettings.SendEmailNotifications {
|
if !config.SendEmailNotifications {
|
||||||
return errors.New("SendEmailNotifications is not true")
|
return errors.New("SendEmailNotifications is not true")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +218,7 @@ func TestConnection(config *model.Config) error {
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
sec := *config.EmailSettings.SMTPServerTimeout
|
sec := config.ServerTimeout
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx, cancel := context.WithTimeout(ctx, time.Duration(sec)*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, time.Duration(sec)*time.Second)
|
||||||
@@ -248,9 +234,9 @@ func TestConnection(config *model.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, config *model.Config, enableComplianceFeatures bool, ccMail string) error {
|
func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, config *SMTPConfig, enableComplianceFeatures bool, ccMail string) error {
|
||||||
fromMail := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.FeedbackEmail}
|
fromMail := mail.Address{Name: config.FeedbackName, Address: config.FeedbackEmail}
|
||||||
replyTo := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.ReplyToAddress}
|
replyTo := mail.Address{Name: config.FeedbackName, Address: config.ReplyToAddress}
|
||||||
|
|
||||||
mail := mailData{
|
mail := mailData{
|
||||||
mimeTo: to,
|
mimeTo: to,
|
||||||
@@ -266,13 +252,13 @@ func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embedded
|
|||||||
return sendMailUsingConfigAdvanced(mail, config, enableComplianceFeatures)
|
return sendMailUsingConfigAdvanced(mail, config, enableComplianceFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool, ccMail string) error {
|
func SendMailUsingConfig(to, subject, htmlBody string, config *SMTPConfig, enableComplianceFeatures bool, ccMail string) error {
|
||||||
return SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, nil, config, enableComplianceFeatures, ccMail)
|
return SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, nil, config, enableComplianceFeatures, ccMail)
|
||||||
}
|
}
|
||||||
|
|
||||||
// allows for sending an email with attachments and differing MIME/SMTP recipients
|
// allows for sending an email with differing MIME/SMTP recipients
|
||||||
func sendMailUsingConfigAdvanced(mail mailData, config *model.Config, enableComplianceFeatures bool) error {
|
func sendMailUsingConfigAdvanced(mail mailData, config *SMTPConfig, enableComplianceFeatures bool) error {
|
||||||
if *config.EmailSettings.SMTPServer == "" {
|
if config.Server == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +268,7 @@ func sendMailUsingConfigAdvanced(mail mailData, config *model.Config, enableComp
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
sec := *config.EmailSettings.SMTPServerTimeout
|
sec := config.ServerTimeout
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx, cancel := context.WithTimeout(ctx, time.Duration(sec)*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, time.Duration(sec)*time.Second)
|
||||||
@@ -295,15 +281,10 @@ func sendMailUsingConfigAdvanced(mail mailData, config *model.Config, enableComp
|
|||||||
defer c.Quit()
|
defer c.Quit()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
fileBackend, nErr := filesstore.NewFileBackend(config.FileSettings.ToFileBackendSettings(enableComplianceFeatures))
|
return SendMail(c, mail, time.Now())
|
||||||
if nErr != nil {
|
|
||||||
return errors.Wrap(nErr, "unable to initialize file backend")
|
|
||||||
}
|
|
||||||
|
|
||||||
return SendMail(c, mail, fileBackend, time.Now())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendMail(c smtpClient, mail mailData, fileBackend filesstore.FileBackend, date time.Time) error {
|
func SendMail(c smtpClient, mail mailData, date time.Time) error {
|
||||||
mlog.Debug("sending mail", mlog.String("to", mail.smtpTo), mlog.String("subject", mail.subject))
|
mlog.Debug("sending mail", mlog.String("to", mail.smtpTo), mlog.String("subject", mail.subject))
|
||||||
|
|
||||||
htmlMessage := "\r\n<html><body>" + mail.htmlBody + "</body></html>"
|
htmlMessage := "\r\n<html><body>" + mail.htmlBody + "</body></html>"
|
||||||
@@ -345,20 +326,6 @@ func SendMail(c smtpClient, mail mailData, fileBackend filesstore.FileBackend, d
|
|||||||
m.EmbedReader(name, reader)
|
m.EmbedReader(name, reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, fileInfo := range mail.attachments {
|
|
||||||
bytes, nErr := fileBackend.ReadFile(fileInfo.Path)
|
|
||||||
if nErr != nil {
|
|
||||||
return errors.Wrap(err, "failed to read attachment")
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error {
|
|
||||||
if _, nErr = writer.Write(bytes); nErr != nil {
|
|
||||||
return errors.Wrap(err, "failed to write attachment to email")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = c.Mail(mail.from.Address); err != nil {
|
if err = c.Mail(mail.from.Address); err != nil {
|
||||||
return errors.Wrap(err, "failed to set the from address")
|
return errors.Wrap(err, "failed to set the from address")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package mailservice
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@@ -19,16 +18,38 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/config"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/services/filesstore"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getConfig() *SMTPConfig {
|
||||||
|
server := os.Getenv("MM_EMAILSETTINGS_SMTPSERVER")
|
||||||
|
if server == "" {
|
||||||
|
server = "localhost"
|
||||||
|
}
|
||||||
|
port := os.Getenv("MM_EMAILSETTINGS_SMTPPORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "10025"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SMTPConfig{
|
||||||
|
ConnectionSecurity: "",
|
||||||
|
SkipServerCertificateVerification: false,
|
||||||
|
Hostname: "localhost",
|
||||||
|
ServerName: server,
|
||||||
|
Server: server,
|
||||||
|
Port: port,
|
||||||
|
ServerTimeout: 10,
|
||||||
|
Username: "",
|
||||||
|
Password: "",
|
||||||
|
EnableSMTPAuth: false,
|
||||||
|
SendEmailNotifications: true,
|
||||||
|
FeedbackName: "",
|
||||||
|
FeedbackEmail: "test@example.com",
|
||||||
|
ReplyToAddress: "test@example.com",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMailConnectionFromConfig(t *testing.T) {
|
func TestMailConnectionFromConfig(t *testing.T) {
|
||||||
store := config.NewTestMemoryStore()
|
cfg := getConfig()
|
||||||
cfg := store.Get()
|
|
||||||
|
|
||||||
conn, err := ConnectToSMTPServer(cfg)
|
conn, err := ConnectToSMTPServer(cfg)
|
||||||
require.NoError(t, err, "Should connect to the SMTP Server %v", err)
|
require.NoError(t, err, "Should connect to the SMTP Server %v", err)
|
||||||
@@ -37,8 +58,8 @@ func TestMailConnectionFromConfig(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, err, "Should get new SMTP client")
|
require.NoError(t, err, "Should get new SMTP client")
|
||||||
|
|
||||||
*cfg.EmailSettings.SMTPServer = "wrongServer"
|
cfg.Server = "wrongServer"
|
||||||
*cfg.EmailSettings.SMTPPort = "553"
|
cfg.Port = "553"
|
||||||
|
|
||||||
_, err = ConnectToSMTPServer(cfg)
|
_, err = ConnectToSMTPServer(cfg)
|
||||||
|
|
||||||
@@ -46,56 +67,23 @@ func TestMailConnectionFromConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMailConnectionAdvanced(t *testing.T) {
|
func TestMailConnectionAdvanced(t *testing.T) {
|
||||||
store := config.NewTestMemoryStore()
|
cfg := getConfig()
|
||||||
cfg := store.Get()
|
|
||||||
|
|
||||||
conn, err := ConnectToSMTPServerAdvanced(
|
conn, err := ConnectToSMTPServerAdvanced(cfg)
|
||||||
&SmtpConnectionInfo{
|
|
||||||
ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity,
|
|
||||||
SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification,
|
|
||||||
SmtpServerName: *cfg.EmailSettings.SMTPServer,
|
|
||||||
SmtpServerHost: *cfg.EmailSettings.SMTPServer,
|
|
||||||
SmtpPort: *cfg.EmailSettings.SMTPPort,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
require.NoError(t, err, "Should connect to the SMTP Server")
|
require.NoError(t, err, "Should connect to the SMTP Server")
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
_, err2 := NewSMTPClientAdvanced(
|
_, err2 := NewSMTPClientAdvanced(context.Background(), conn, cfg)
|
||||||
context.Background(),
|
|
||||||
conn,
|
|
||||||
utils.GetHostnameFromSiteURL(*cfg.ServiceSettings.SiteURL),
|
|
||||||
&SmtpConnectionInfo{
|
|
||||||
ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity,
|
|
||||||
SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification,
|
|
||||||
SmtpServerName: *cfg.EmailSettings.SMTPServer,
|
|
||||||
SmtpServerHost: *cfg.EmailSettings.SMTPServer,
|
|
||||||
SmtpPort: *cfg.EmailSettings.SMTPPort,
|
|
||||||
Auth: *cfg.EmailSettings.EnableSMTPAuth,
|
|
||||||
SmtpUsername: *cfg.EmailSettings.SMTPUsername,
|
|
||||||
SmtpPassword: *cfg.EmailSettings.SMTPPassword,
|
|
||||||
SmtpServerTimeout: 1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
require.NoError(t, err2, "Should get new SMTP client")
|
require.NoError(t, err2, "Should get new SMTP client")
|
||||||
|
|
||||||
l, err3 := net.Listen("tcp", "localhost:") // emulate nc -l <random-port>
|
l, err3 := net.Listen("tcp", "localhost:") // emulate nc -l <random-port>
|
||||||
require.NoError(t, err3, "Should've open a network socket and listen")
|
require.NoError(t, err3, "Should've open a network socket and listen")
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
|
cfg.Server = strings.Split(l.Addr().String(), ":")[0]
|
||||||
|
cfg.Port = strings.Split(l.Addr().String(), ":")[1]
|
||||||
|
cfg.ServerTimeout = 1
|
||||||
|
|
||||||
connInfo := &SmtpConnectionInfo{
|
conn2, err := ConnectToSMTPServerAdvanced(cfg)
|
||||||
ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity,
|
|
||||||
SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification,
|
|
||||||
SmtpServerName: *cfg.EmailSettings.SMTPServer,
|
|
||||||
SmtpServerHost: strings.Split(l.Addr().String(), ":")[0],
|
|
||||||
SmtpPort: strings.Split(l.Addr().String(), ":")[1],
|
|
||||||
Auth: *cfg.EmailSettings.EnableSMTPAuth,
|
|
||||||
SmtpUsername: *cfg.EmailSettings.SMTPUsername,
|
|
||||||
SmtpPassword: *cfg.EmailSettings.SMTPPassword,
|
|
||||||
SmtpServerTimeout: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn2, err := ConnectToSMTPServerAdvanced(connInfo)
|
|
||||||
require.NoError(t, err, "Should connect to the SMTP Server")
|
require.NoError(t, err, "Should connect to the SMTP Server")
|
||||||
defer conn2.Close()
|
defer conn2.Close()
|
||||||
|
|
||||||
@@ -106,33 +94,20 @@ func TestMailConnectionAdvanced(t *testing.T) {
|
|||||||
_, err4 := NewSMTPClientAdvanced(
|
_, err4 := NewSMTPClientAdvanced(
|
||||||
ctx,
|
ctx,
|
||||||
conn2,
|
conn2,
|
||||||
utils.GetHostnameFromSiteURL(*cfg.ServiceSettings.SiteURL),
|
cfg,
|
||||||
connInfo,
|
|
||||||
)
|
)
|
||||||
require.Error(t, err4, "Should get a timeout get while creating a new SMTP client")
|
require.Error(t, err4, "Should get a timeout get while creating a new SMTP client")
|
||||||
assert.Contains(t, err4.Error(), "unable to connect to the SMTP server")
|
assert.Contains(t, err4.Error(), "unable to connect to the SMTP server")
|
||||||
|
|
||||||
_, err5 := ConnectToSMTPServerAdvanced(
|
cfg.Server = "wrongServer"
|
||||||
&SmtpConnectionInfo{
|
cfg.Port = "553"
|
||||||
ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity,
|
|
||||||
SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification,
|
_, err5 := ConnectToSMTPServerAdvanced(cfg)
|
||||||
SmtpServerName: "wrongServer",
|
|
||||||
SmtpServerHost: "wrongServer",
|
|
||||||
SmtpPort: "553",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
require.Error(t, err5, "Should not connect to the SMTP Server")
|
require.Error(t, err5, "Should not connect to the SMTP Server")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSendMailUsingConfig(t *testing.T) {
|
func TestSendMailUsingConfig(t *testing.T) {
|
||||||
utils.T = utils.GetUserTranslations("en")
|
cfg := getConfig()
|
||||||
|
|
||||||
fsInner, err := config.NewFileStore("config.json", false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
fs, err := config.NewStoreFromBacking(fsInner, nil, false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cfg := fs.Get()
|
|
||||||
|
|
||||||
var emailTo = "test@example.com"
|
var emailTo = "test@example.com"
|
||||||
var emailSubject = "Testing this email"
|
var emailSubject = "Testing this email"
|
||||||
@@ -166,14 +141,7 @@ func TestSendMailUsingConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSendMailWithEmbeddedFilesUsingConfig(t *testing.T) {
|
func TestSendMailWithEmbeddedFilesUsingConfig(t *testing.T) {
|
||||||
utils.T = utils.GetUserTranslations("en")
|
cfg := getConfig()
|
||||||
|
|
||||||
fsInner, err := config.NewFileStore("config.json", false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
fs, err := config.NewStoreFromBacking(fsInner, nil, false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cfg := fs.Get()
|
|
||||||
|
|
||||||
var emailTo = "test@example.com"
|
var emailTo = "test@example.com"
|
||||||
var emailSubject = "Testing this email"
|
var emailSubject = "Testing this email"
|
||||||
@@ -213,42 +181,23 @@ func TestSendMailWithEmbeddedFilesUsingConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSendMailUsingConfigAdvanced(t *testing.T) {
|
func TestSendMailUsingConfigAdvanced(t *testing.T) {
|
||||||
utils.T = utils.GetUserTranslations("en")
|
cfg := getConfig()
|
||||||
|
|
||||||
fsInner, err := config.NewFileStore("config.json", false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
fs, err := config.NewStoreFromBacking(fsInner, nil, false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cfg := fs.Get()
|
|
||||||
|
|
||||||
//Delete all the messages before check the sample email
|
//Delete all the messages before check the sample email
|
||||||
DeleteMailBox("test2@example.com")
|
DeleteMailBox("test2@example.com")
|
||||||
|
|
||||||
fileBackend, err := filesstore.NewFileBackend(cfg.FileSettings.ToFileBackendSettings(true))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// create two files with the same name that will both be attached to the email
|
// create two files with the same name that will both be attached to the email
|
||||||
filePath1 := fmt.Sprintf("test1/%s", "file1.txt")
|
file1, err := ioutil.TempFile("", "*")
|
||||||
filePath2 := fmt.Sprintf("test2/%s", "file2.txt")
|
require.NoError(t, err)
|
||||||
fileContents1 := []byte("hello world")
|
defer os.Remove(file1.Name())
|
||||||
fileContents2 := []byte("foo bar")
|
file1.Write([]byte("hello world"))
|
||||||
_, err = fileBackend.WriteFile(bytes.NewReader(fileContents1), filePath1)
|
file1.Close()
|
||||||
assert.NoError(t, err)
|
file2, err := ioutil.TempFile("", "*")
|
||||||
_, err = fileBackend.WriteFile(bytes.NewReader(fileContents2), filePath2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer fileBackend.RemoveFile(filePath1)
|
|
||||||
defer fileBackend.RemoveFile(filePath2)
|
|
||||||
|
|
||||||
attachments := make([]*model.FileInfo, 2)
|
require.NoError(t, err)
|
||||||
attachments[0] = &model.FileInfo{
|
defer os.Remove(file2.Name())
|
||||||
Name: "file1.txt",
|
file2.Write([]byte("foo bar"))
|
||||||
Path: filePath1,
|
file2.Close()
|
||||||
}
|
|
||||||
attachments[1] = &model.FileInfo{
|
|
||||||
Name: "file2.txt",
|
|
||||||
Path: filePath2,
|
|
||||||
}
|
|
||||||
|
|
||||||
embeddedFiles := map[string]io.Reader{
|
embeddedFiles := map[string]io.Reader{
|
||||||
"test": bytes.NewReader([]byte("test data")),
|
"test": bytes.NewReader([]byte("test data")),
|
||||||
@@ -264,7 +213,6 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) {
|
|||||||
replyTo: mail.Address{Name: "ReplyTo", Address: "reply_to@mattermost.com"},
|
replyTo: mail.Address{Name: "ReplyTo", Address: "reply_to@mattermost.com"},
|
||||||
subject: "Testing this email",
|
subject: "Testing this email",
|
||||||
htmlBody: "This is a test from autobot",
|
htmlBody: "This is a test from autobot",
|
||||||
attachments: attachments,
|
|
||||||
embeddedFiles: embeddedFiles,
|
embeddedFiles: embeddedFiles,
|
||||||
mimeHeaders: headers,
|
mimeHeaders: headers,
|
||||||
}
|
}
|
||||||
@@ -297,37 +245,16 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) {
|
|||||||
|
|
||||||
// check that the custom mime headers came through - header case seems to get mutated
|
// check that the custom mime headers came through - header case seems to get mutated
|
||||||
assert.Equal(t, "TestValue", resultsEmail.Header["Testheader"][0])
|
assert.Equal(t, "TestValue", resultsEmail.Header["Testheader"][0])
|
||||||
|
|
||||||
// ensure that the attachments were successfully sent
|
|
||||||
assert.Len(t, resultsEmail.Attachments, 3)
|
|
||||||
|
|
||||||
attachmentsFilenames := []string{
|
|
||||||
resultsEmail.Attachments[0].Filename,
|
|
||||||
resultsEmail.Attachments[1].Filename,
|
|
||||||
resultsEmail.Attachments[2].Filename,
|
|
||||||
}
|
|
||||||
assert.Contains(t, attachmentsFilenames, "file1.txt")
|
|
||||||
assert.Contains(t, attachmentsFilenames, "file2.txt")
|
|
||||||
assert.Contains(t, attachmentsFilenames, "test")
|
|
||||||
|
|
||||||
attachment1 := string(resultsEmail.Attachments[0].Bytes)
|
|
||||||
attachment2 := string(resultsEmail.Attachments[1].Bytes)
|
|
||||||
attachment3 := string(resultsEmail.Attachments[2].Bytes)
|
|
||||||
attachmentsData := []string{attachment1, attachment2, attachment3}
|
|
||||||
|
|
||||||
assert.Contains(t, attachmentsData, string(fileContents1))
|
|
||||||
assert.Contains(t, attachmentsData, string(fileContents2))
|
|
||||||
assert.Contains(t, attachmentsData, "test data")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthMethods(t *testing.T) {
|
func TestAuthMethods(t *testing.T) {
|
||||||
auth := &authChooser{
|
auth := &authChooser{
|
||||||
connectionInfo: &SmtpConnectionInfo{
|
config: &SMTPConfig{
|
||||||
SmtpUsername: "test",
|
Username: "test",
|
||||||
SmtpPassword: "fakepass",
|
Password: "fakepass",
|
||||||
SmtpServerName: "fakeserver",
|
ServerName: "fakeserver",
|
||||||
SmtpServerHost: "fakeserver",
|
Server: "fakeserver",
|
||||||
SmtpPort: "25",
|
Port: "25",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -394,13 +321,6 @@ func TestSendMail(t *testing.T) {
|
|||||||
dir, err := ioutil.TempDir(".", "mail-test-")
|
dir, err := ioutil.TempDir(".", "mail-test-")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
settings := model.FileSettings{
|
|
||||||
DriverName: model.NewString(model.IMAGE_DRIVER_LOCAL),
|
|
||||||
Directory: &dir,
|
|
||||||
}
|
|
||||||
settings.SetDefaults(true)
|
|
||||||
mockBackend, err := filesstore.NewFileBackend(settings.ToFileBackendSettings(true))
|
|
||||||
require.NoError(t, err)
|
|
||||||
mocm := &mockMailer{}
|
mocm := &mockMailer{}
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
@@ -422,8 +342,8 @@ func TestSendMail(t *testing.T) {
|
|||||||
|
|
||||||
for testName, tc := range testCases {
|
for testName, tc := range testCases {
|
||||||
t.Run(testName, func(t *testing.T) {
|
t.Run(testName, func(t *testing.T) {
|
||||||
mail := mailData{"", "", mail.Address{}, "", tc.replyTo, "", "", nil, nil, nil}
|
mail := mailData{"", "", mail.Address{}, "", tc.replyTo, "", "", nil, nil}
|
||||||
err = SendMail(mocm, mail, mockBackend, time.Now())
|
err = SendMail(mocm, mail, time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if tc.contains != "" {
|
if tc.contains != "" {
|
||||||
require.Contains(t, string(mocm.data), tc.contains)
|
require.Contains(t, string(mocm.data), tc.contains)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user