From a08df883b4ddb514d53b518f41431ce7efb50d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 20 Sep 2018 19:07:03 +0200 Subject: Move file backend to its own service (#9435) * Move file backend to its own service * Moving utils/inbucket to mailservice package --- services/mailservice/inbucket.go | 200 +++++++++++++++++++++++++ services/mailservice/mail.go | 299 ++++++++++++++++++++++++++++++++++++++ services/mailservice/mail_test.go | 290 ++++++++++++++++++++++++++++++++++++ 3 files changed, 789 insertions(+) create mode 100644 services/mailservice/inbucket.go create mode 100644 services/mailservice/mail.go create mode 100644 services/mailservice/mail_test.go (limited to 'services/mailservice') diff --git a/services/mailservice/inbucket.go b/services/mailservice/inbucket.go new file mode 100644 index 000000000..28922d660 --- /dev/null +++ b/services/mailservice/inbucket.go @@ -0,0 +1,200 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package mailservice + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" +) + +const ( + INBUCKET_API = "/api/v1/mailbox/" +) + +// OutputJSONHeader holds the received Header to test sending emails (inbucket) +type JSONMessageHeaderInbucket []struct { + Mailbox string + ID string `json:"Id"` + From, Subject, Date string + To []string + Size int +} + +// OutputJSONMessage holds the received Message fto test sending emails (inbucket) +type JSONMessageInbucket struct { + Mailbox string + ID string `json:"Id"` + From, Subject, Date string + Size int + Header map[string][]string + Body struct { + Text string + HTML string `json:"Html"` + } + Attachments []struct { + Filename string + ContentType string `json:"content-type"` + DownloadLink string `json:"download-link"` + Bytes []byte `json:"-"` + } +} + +func ParseEmail(email string) string { + pos := strings.Index(email, "@") + parsedEmail := email[0:pos] + return parsedEmail +} + +func GetMailBox(email string) (results JSONMessageHeaderInbucket, err error) { + + parsedEmail := ParseEmail(email) + + url := fmt.Sprintf("%s%s%s", getInbucketHost(), INBUCKET_API, parsedEmail) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + client := &http.Client{} + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.Body == nil { + return nil, fmt.Errorf("No Mailbox") + } + + var record JSONMessageHeaderInbucket + err = json.NewDecoder(resp.Body).Decode(&record) + switch { + case err == io.EOF: + return nil, fmt.Errorf("Error: %s", err) + case err != nil: + return nil, fmt.Errorf("Error: %s", err) + } + if len(record) == 0 { + return nil, fmt.Errorf("No mailbox") + } + + return record, nil +} + +func GetMessageFromMailbox(email, id string) (results JSONMessageInbucket, err error) { + + parsedEmail := ParseEmail(email) + + var record JSONMessageInbucket + + url := fmt.Sprintf("%s%s%s/%s", getInbucketHost(), INBUCKET_API, parsedEmail, id) + emailResponse, err := get(url) + if err != nil { + return record, err + } + defer emailResponse.Body.Close() + + err = json.NewDecoder(emailResponse.Body).Decode(&record) + + // download attachments + if record.Attachments != nil && len(record.Attachments) > 0 { + for i := range record.Attachments { + if bytes, err := downloadAttachment(record.Attachments[i].DownloadLink); err != nil { + return record, err + } else { + record.Attachments[i].Bytes = make([]byte, len(bytes)) + copy(record.Attachments[i].Bytes, bytes) + } + } + } + + return record, err +} + +func downloadAttachment(url string) ([]byte, error) { + attachmentResponse, err := get(url) + if err != nil { + return nil, err + } + defer attachmentResponse.Body.Close() + + buf := new(bytes.Buffer) + io.Copy(buf, attachmentResponse.Body) + return buf.Bytes(), nil +} + +func get(url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + return resp, nil +} + +func DeleteMailBox(email string) (err error) { + + parsedEmail := ParseEmail(email) + + url := fmt.Sprintf("%s%s%s", getInbucketHost(), INBUCKET_API, parsedEmail) + req, err := http.NewRequest("DELETE", url, nil) + if err != nil { + return err + } + + client := &http.Client{} + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + return nil +} + +func RetryInbucket(attempts int, callback func() error) (err error) { + for i := 0; ; i++ { + err = callback() + if err == nil { + return nil + } + + if i >= (attempts - 1) { + break + } + + time.Sleep(5 * time.Second) + + fmt.Println("retrying...") + } + return fmt.Errorf("After %d attempts, last error: %s", attempts, err) +} + +func getInbucketHost() (host string) { + + inbucket_host := os.Getenv("CI_HOST") + if inbucket_host == "" { + inbucket_host = "dockerhost" + } + + inbucket_port := os.Getenv("CI_INBUCKET_PORT") + if inbucket_port == "" { + inbucket_port = "9000" + } + return fmt.Sprintf("http://%s:%s", inbucket_host, inbucket_port) +} diff --git a/services/mailservice/mail.go b/services/mailservice/mail.go new file mode 100644 index 000000000..e9b689f20 --- /dev/null +++ b/services/mailservice/mail.go @@ -0,0 +1,299 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package mailservice + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "mime" + "net" + "net/mail" + "net/smtp" + "time" + + "gopkg.in/gomail.v2" + + "net/http" + + "github.com/jaytaylor/html2text" + "github.com/mattermost/mattermost-server/mlog" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/services/filesstore" + "github.com/mattermost/mattermost-server/utils" +) + +func encodeRFC2047Word(s string) string { + return mime.BEncoding.Encode("utf-8", s) +} + +type SmtpConnectionInfo struct { + SmtpUsername string + SmtpPassword string + SmtpServerName string + SmtpServerHost string + SmtpPort string + SkipCertVerification bool + ConnectionSecurity string + Auth bool +} + +type authChooser struct { + smtp.Auth + connectionInfo *SmtpConnectionInfo +} + +func (a *authChooser) Start(server *smtp.ServerInfo) (string, []byte, error) { + smtpAddress := a.connectionInfo.SmtpServerName + ":" + a.connectionInfo.SmtpPort + a.Auth = LoginAuth(a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, smtpAddress) + for _, method := range server.Auth { + if method == "PLAIN" { + a.Auth = smtp.PlainAuth("", a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, a.connectionInfo.SmtpServerName+":"+a.connectionInfo.SmtpPort) + break + } + } + return a.Auth.Start(server) +} + +type loginAuth struct { + username, password, host string +} + +func LoginAuth(username, password, host string) smtp.Auth { + return &loginAuth{username, password, host} +} + +func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { + if !server.TLS { + return "", nil, errors.New("unencrypted connection") + } + + if server.Name != a.host { + return "", nil, errors.New("wrong host name") + } + + return "LOGIN", []byte{}, nil +} + +func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { + if more { + switch string(fromServer) { + case "Username:": + return []byte(a.username), nil + case "Password:": + return []byte(a.password), nil + default: + return nil, errors.New("Unknown fromServer") + } + } + return nil, nil +} + +func ConnectToSMTPServerAdvanced(connectionInfo *SmtpConnectionInfo) (net.Conn, *model.AppError) { + var conn net.Conn + var err error + + smtpAddress := connectionInfo.SmtpServerHost + ":" + connectionInfo.SmtpPort + if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_TLS { + tlsconfig := &tls.Config{ + InsecureSkipVerify: connectionInfo.SkipCertVerification, + ServerName: connectionInfo.SmtpServerName, + } + + conn, err = tls.Dial("tcp", smtpAddress, tlsconfig) + if err != nil { + return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError) + } + } else { + conn, err = net.Dial("tcp", smtpAddress) + if err != nil { + return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open.app_error", nil, err.Error(), http.StatusInternalServerError) + } + } + + return conn, nil +} + +func ConnectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) { + return ConnectToSMTPServerAdvanced( + &SmtpConnectionInfo{ + ConnectionSecurity: config.EmailSettings.ConnectionSecurity, + SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification, + SmtpServerName: config.EmailSettings.SMTPServer, + SmtpServerHost: config.EmailSettings.SMTPServer, + SmtpPort: config.EmailSettings.SMTPPort, + }, + ) +} + +func NewSMTPClientAdvanced(conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, *model.AppError) { + c, err := smtp.NewClient(conn, connectionInfo.SmtpServerName+":"+connectionInfo.SmtpPort) + if err != nil { + mlog.Error(fmt.Sprintf("Failed to open a connection to SMTP server %v", err)) + return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + if hostname != "" { + err := c.Hello(hostname) + if err != nil { + mlog.Error(fmt.Sprintf("Failed to to set the HELO to SMTP server %v", err)) + return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.helo.app_error", nil, err.Error(), http.StatusInternalServerError) + } + } + + if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_STARTTLS { + tlsconfig := &tls.Config{ + InsecureSkipVerify: connectionInfo.SkipCertVerification, + ServerName: connectionInfo.SmtpServerName, + } + c.StartTLS(tlsconfig) + } + + if connectionInfo.Auth { + if err = c.Auth(&authChooser{connectionInfo: connectionInfo}); err != nil { + return nil, model.NewAppError("SendMail", "utils.mail.new_client.auth.app_error", nil, err.Error(), http.StatusInternalServerError) + } + } + return c, nil +} + +func NewSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.AppError) { + return NewSMTPClientAdvanced( + conn, + utils.GetHostnameFromSiteURL(*config.ServiceSettings.SiteURL), + &SmtpConnectionInfo{ + ConnectionSecurity: config.EmailSettings.ConnectionSecurity, + SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification, + SmtpServerName: config.EmailSettings.SMTPServer, + SmtpServerHost: config.EmailSettings.SMTPServer, + SmtpPort: config.EmailSettings.SMTPPort, + Auth: *config.EmailSettings.EnableSMTPAuth, + SmtpUsername: config.EmailSettings.SMTPUsername, + SmtpPassword: config.EmailSettings.SMTPPassword, + }, + ) +} + +func TestConnection(config *model.Config) { + if !config.EmailSettings.SendEmailNotifications { + return + } + + conn, err1 := ConnectToSMTPServer(config) + if err1 != nil { + mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", utils.T(err1.Message), err1.DetailedError)) + return + } + defer conn.Close() + + c, err2 := NewSMTPClient(conn, config) + if err2 != nil { + mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", utils.T(err2.Message), err2.DetailedError)) + return + } + defer c.Quit() + defer c.Close() +} + +func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool) *model.AppError { + fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail} + + return SendMailUsingConfigAdvanced(to, to, fromMail, subject, htmlBody, nil, nil, config, enableComplianceFeatures) +} + +// allows for sending an email with attachments and differing MIME/SMTP recipients +func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError { + if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 { + return nil + } + + conn, err := ConnectToSMTPServer(config) + if err != nil { + return err + } + defer conn.Close() + + c, err := NewSMTPClient(conn, config) + if err != nil { + return err + } + defer c.Quit() + defer c.Close() + + fileBackend, err := filesstore.NewFileBackend(&config.FileSettings, enableComplianceFeatures) + if err != nil { + return err + } + + return SendMail(c, mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, fileBackend, time.Now()) +} + +func SendMail(c *smtp.Client, mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, fileBackend filesstore.FileBackend, date time.Time) *model.AppError { + mlog.Debug(fmt.Sprintf("sending mail to %v with subject of '%v'", smtpTo, subject)) + + htmlMessage := "\r\n" + htmlBody + "" + + txtBody, err := html2text.FromString(htmlBody) + if err != nil { + mlog.Warn(fmt.Sprint(err)) + txtBody = "" + } + + headers := map[string][]string{ + "From": {from.String()}, + "To": {mimeTo}, + "Subject": {encodeRFC2047Word(subject)}, + "Content-Transfer-Encoding": {"8bit"}, + "Auto-Submitted": {"auto-generated"}, + "Precedence": {"bulk"}, + } + for k, v := range mimeHeaders { + headers[k] = []string{encodeRFC2047Word(v)} + } + + m := gomail.NewMessage(gomail.SetCharset("UTF-8")) + m.SetHeaders(headers) + m.SetDateHeader("Date", date) + m.SetBody("text/plain", txtBody) + m.AddAlternative("text/html", htmlMessage) + + for _, fileInfo := range attachments { + bytes, err := fileBackend.ReadFile(fileInfo.Path) + if err != nil { + return err + } + + m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error { + if _, err := writer.Write(bytes); err != nil { + return model.NewAppError("SendMail", "utils.mail.sendMail.attachments.write_error", nil, err.Error(), http.StatusInternalServerError) + } + return nil + })) + } + + if err := c.Mail(from.Address); err != nil { + return model.NewAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + if err := c.Rcpt(smtpTo); err != nil { + return model.NewAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + w, err := c.Data() + if err != nil { + return model.NewAppError("SendMail", "utils.mail.send_mail.msg_data.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + _, err = m.WriteTo(w) + if err != nil { + return model.NewAppError("SendMail", "utils.mail.send_mail.msg.app_error", nil, err.Error(), http.StatusInternalServerError) + } + err = w.Close() + if err != nil { + return model.NewAppError("SendMail", "utils.mail.send_mail.close.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + return nil +} diff --git a/services/mailservice/mail_test.go b/services/mailservice/mail_test.go new file mode 100644 index 000000000..9ad48c703 --- /dev/null +++ b/services/mailservice/mail_test.go @@ -0,0 +1,290 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package mailservice + +import ( + "bytes" + "fmt" + "strings" + "testing" + + "net/mail" + "net/smtp" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/services/filesstore" + "github.com/mattermost/mattermost-server/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMailConnectionFromConfig(t *testing.T) { + cfg, _, _, err := utils.LoadConfig("config.json") + require.Nil(t, err) + + if conn, err := ConnectToSMTPServer(cfg); err != nil { + t.Log(err) + t.Fatal("Should connect to the STMP Server") + } else { + if _, err1 := NewSMTPClient(conn, cfg); err1 != nil { + t.Log(err) + t.Fatal("Should get new smtp client") + } + } + + cfg.EmailSettings.SMTPServer = "wrongServer" + cfg.EmailSettings.SMTPPort = "553" + + if _, err := ConnectToSMTPServer(cfg); err == nil { + t.Log(err) + t.Fatal("Should not to the STMP Server") + } +} + +func TestMailConnectionAdvanced(t *testing.T) { + cfg, _, _, err := utils.LoadConfig("config.json") + require.Nil(t, err) + + if conn, err := ConnectToSMTPServerAdvanced( + &SmtpConnectionInfo{ + ConnectionSecurity: cfg.EmailSettings.ConnectionSecurity, + SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification, + SmtpServerName: cfg.EmailSettings.SMTPServer, + SmtpServerHost: cfg.EmailSettings.SMTPServer, + SmtpPort: cfg.EmailSettings.SMTPPort, + }, + ); err != nil { + t.Log(err) + t.Fatal("Should connect to the STMP Server") + } else { + if _, err1 := NewSMTPClientAdvanced( + 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, + }, + ); err1 != nil { + t.Log(err) + t.Fatal("Should get new smtp client") + } + } + + if _, err := ConnectToSMTPServerAdvanced( + &SmtpConnectionInfo{ + ConnectionSecurity: cfg.EmailSettings.ConnectionSecurity, + SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification, + SmtpServerName: "wrongServer", + SmtpServerHost: "wrongServer", + SmtpPort: "553", + }, + ); err == nil { + t.Log(err) + t.Fatal("Should not to the STMP Server") + } + +} + +func TestSendMailUsingConfig(t *testing.T) { + cfg, _, _, err := utils.LoadConfig("config.json") + require.Nil(t, err) + utils.T = utils.GetUserTranslations("en") + + var emailTo = "test@example.com" + var emailSubject = "Testing this email" + var emailBody = "This is a test from autobot" + + //Delete all the messages before check the sample email + DeleteMailBox(emailTo) + + if err := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg, true); err != nil { + t.Log(err) + t.Fatal("Should connect to the STMP Server") + } else { + //Check if the email was send to the right email address + var resultsMailbox JSONMessageHeaderInbucket + err := RetryInbucket(5, func() error { + var err error + resultsMailbox, err = GetMailBox(emailTo) + return err + }) + if err != nil { + t.Log(err) + t.Log("No email was received, maybe due load on the server. Disabling this verification") + } + if err == nil && len(resultsMailbox) > 0 { + if !strings.ContainsAny(resultsMailbox[0].To[0], emailTo) { + t.Fatal("Wrong To recipient") + } else { + if resultsEmail, err := GetMessageFromMailbox(emailTo, resultsMailbox[0].ID); err == nil { + if !strings.Contains(resultsEmail.Body.Text, emailBody) { + t.Log(resultsEmail.Body.Text) + t.Fatal("Received message") + } + } + } + } + } +} + +func TestSendMailUsingConfigAdvanced(t *testing.T) { + cfg, _, _, err := utils.LoadConfig("config.json") + require.Nil(t, err) + utils.T = utils.GetUserTranslations("en") + + var mimeTo = "test@example.com" + var smtpTo = "test2@example.com" + var from = mail.Address{Name: "Nobody", Address: "nobody@mattermost.com"} + var emailSubject = "Testing this email" + var emailBody = "This is a test from autobot" + + //Delete all the messages before check the sample email + DeleteMailBox(smtpTo) + + fileBackend, err := filesstore.NewFileBackend(&cfg.FileSettings, true) + assert.Nil(t, err) + + // create two files with the same name that will both be attached to the email + fileName := "file.txt" + filePath1 := fmt.Sprintf("test1/%s", fileName) + filePath2 := fmt.Sprintf("test2/%s", fileName) + fileContents1 := []byte("hello world") + fileContents2 := []byte("foo bar") + _, err = fileBackend.WriteFile(bytes.NewReader(fileContents1), filePath1) + assert.Nil(t, err) + _, err = fileBackend.WriteFile(bytes.NewReader(fileContents2), filePath2) + assert.Nil(t, err) + defer fileBackend.RemoveFile(filePath1) + defer fileBackend.RemoveFile(filePath2) + + attachments := make([]*model.FileInfo, 2) + attachments[0] = &model.FileInfo{ + Name: fileName, + Path: filePath1, + } + attachments[1] = &model.FileInfo{ + Name: fileName, + Path: filePath2, + } + + headers := make(map[string]string) + headers["TestHeader"] = "TestValue" + + if err := SendMailUsingConfigAdvanced(mimeTo, smtpTo, from, emailSubject, emailBody, attachments, headers, cfg, true); err != nil { + t.Log(err) + t.Fatal("Should connect to the STMP Server") + } else { + //Check if the email was send to the right email address + var resultsMailbox JSONMessageHeaderInbucket + err := RetryInbucket(5, func() error { + var err error + resultsMailbox, err = GetMailBox(smtpTo) + return err + }) + if err != nil { + t.Log(err) + t.Fatal("No emails found for address " + smtpTo) + } + if err == nil && len(resultsMailbox) > 0 { + if !strings.ContainsAny(resultsMailbox[0].To[0], smtpTo) { + t.Fatal("Wrong To recipient") + } else { + if resultsEmail, err := GetMessageFromMailbox(smtpTo, resultsMailbox[0].ID); err == nil { + if !strings.Contains(resultsEmail.Body.Text, emailBody) { + t.Log(resultsEmail.Body.Text) + t.Fatal("Received message") + } + + // verify that the To header of the email message is set to the MIME recipient, even though we got it out of the SMTP recipient's email inbox + assert.Equal(t, mimeTo, resultsEmail.Header["To"][0]) + + // verify that the MIME from address is correct - unfortunately, we can't verify the SMTP from address + assert.Equal(t, from.String(), resultsEmail.Header["From"][0]) + + // check that the custom mime headers came through - header case seems to get mutated + assert.Equal(t, "TestValue", resultsEmail.Header["Testheader"][0]) + + // ensure that the attachments were successfully sent + assert.Len(t, resultsEmail.Attachments, 2) + assert.Equal(t, fileName, resultsEmail.Attachments[0].Filename) + assert.Equal(t, fileName, resultsEmail.Attachments[1].Filename) + attachment1 := string(resultsEmail.Attachments[0].Bytes) + attachment2 := string(resultsEmail.Attachments[1].Bytes) + if attachment1 == string(fileContents1) { + assert.Equal(t, attachment2, string(fileContents2)) + } else if attachment1 == string(fileContents2) { + assert.Equal(t, attachment2, string(fileContents1)) + } else { + assert.Fail(t, "Unrecognized attachment contents") + } + } + } + } + } +} + +func TestAuthMethods(t *testing.T) { + auth := &authChooser{ + connectionInfo: &SmtpConnectionInfo{ + SmtpUsername: "test", + SmtpPassword: "fakepass", + SmtpServerName: "fakeserver", + SmtpServerHost: "fakeserver", + SmtpPort: "25", + }, + } + tests := []struct { + desc string + server *smtp.ServerInfo + err string + }{ + { + desc: "auth PLAIN success", + server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"PLAIN"}, TLS: true}, + }, + { + desc: "auth PLAIN unencrypted connection fail", + server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"PLAIN"}, TLS: false}, + err: "unencrypted connection", + }, + { + desc: "auth PLAIN wrong host name", + server: &smtp.ServerInfo{Name: "wrongServer:999", Auth: []string{"PLAIN"}, TLS: true}, + err: "wrong host name", + }, + { + desc: "auth LOGIN success", + server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"LOGIN"}, TLS: true}, + }, + { + desc: "auth LOGIN unencrypted connection fail", + server: &smtp.ServerInfo{Name: "wrongServer:999", Auth: []string{"LOGIN"}, TLS: true}, + err: "wrong host name", + }, + { + desc: "auth LOGIN wrong host name", + server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"LOGIN"}, TLS: false}, + err: "unencrypted connection", + }, + } + + for i, test := range tests { + t.Run(test.desc, func(t *testing.T) { + _, _, err := auth.Start(test.server) + got := "" + if err != nil { + got = err.Error() + } + if got != test.err { + t.Errorf("%d. got error = %q; want %q", i, got, test.err) + } + }) + } +} -- cgit v1.2.3-1-g7c22