github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/services/mailservice/mail_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package mailservice 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net" 13 "os" 14 "strings" 15 "testing" 16 "time" 17 18 "net/mail" 19 "net/smtp" 20 21 "github.com/mattermost/mattermost-server/v5/config" 22 "github.com/mattermost/mattermost-server/v5/model" 23 "github.com/mattermost/mattermost-server/v5/services/filesstore" 24 "github.com/mattermost/mattermost-server/v5/utils" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 ) 28 29 func TestMailConnectionFromConfig(t *testing.T) { 30 fs, err := config.NewFileStore("config.json", false) 31 require.Nil(t, err) 32 33 cfg := fs.Get() 34 conn, err := ConnectToSMTPServer(cfg) 35 require.Nil(t, err, "Should connect to the SMTP Server %v", err) 36 37 _, err = NewSMTPClient(context.Background(), conn, cfg) 38 39 require.Nil(t, err, "Should get new SMTP client") 40 41 *cfg.EmailSettings.SMTPServer = "wrongServer" 42 *cfg.EmailSettings.SMTPPort = "553" 43 44 _, err = ConnectToSMTPServer(cfg) 45 46 require.NotNil(t, err, "Should not connect to the SMTP Server") 47 } 48 49 func TestMailConnectionAdvanced(t *testing.T) { 50 fs, err := config.NewFileStore("config.json", false) 51 require.Nil(t, err) 52 53 cfg := fs.Get() 54 55 conn, err := ConnectToSMTPServerAdvanced( 56 &SmtpConnectionInfo{ 57 ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity, 58 SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification, 59 SmtpServerName: *cfg.EmailSettings.SMTPServer, 60 SmtpServerHost: *cfg.EmailSettings.SMTPServer, 61 SmtpPort: *cfg.EmailSettings.SMTPPort, 62 }, 63 ) 64 require.Nil(t, err, "Should connect to the SMTP Server") 65 defer conn.Close() 66 67 _, err2 := NewSMTPClientAdvanced( 68 context.Background(), 69 conn, 70 utils.GetHostnameFromSiteURL(*cfg.ServiceSettings.SiteURL), 71 &SmtpConnectionInfo{ 72 ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity, 73 SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification, 74 SmtpServerName: *cfg.EmailSettings.SMTPServer, 75 SmtpServerHost: *cfg.EmailSettings.SMTPServer, 76 SmtpPort: *cfg.EmailSettings.SMTPPort, 77 Auth: *cfg.EmailSettings.EnableSMTPAuth, 78 SmtpUsername: *cfg.EmailSettings.SMTPUsername, 79 SmtpPassword: *cfg.EmailSettings.SMTPPassword, 80 SmtpServerTimeout: 1, 81 }, 82 ) 83 require.Nil(t, err2, "Should get new SMTP client") 84 85 l, err := net.Listen("tcp", "localhost:") // emulate nc -l <random-port> 86 require.Nil(t, err, "Should've open a network socket and listen") 87 defer l.Close() 88 89 connInfo := &SmtpConnectionInfo{ 90 ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity, 91 SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification, 92 SmtpServerName: *cfg.EmailSettings.SMTPServer, 93 SmtpServerHost: strings.Split(l.Addr().String(), ":")[0], 94 SmtpPort: strings.Split(l.Addr().String(), ":")[1], 95 Auth: *cfg.EmailSettings.EnableSMTPAuth, 96 SmtpUsername: *cfg.EmailSettings.SMTPUsername, 97 SmtpPassword: *cfg.EmailSettings.SMTPPassword, 98 SmtpServerTimeout: 1, 99 } 100 101 conn2, err := ConnectToSMTPServerAdvanced(connInfo) 102 require.Nil(t, err, "Should connect to the SMTP Server") 103 defer conn2.Close() 104 105 ctx := context.Background() 106 ctx, cancel := context.WithTimeout(ctx, time.Second) 107 defer cancel() 108 109 _, err3 := NewSMTPClientAdvanced( 110 ctx, 111 conn2, 112 utils.GetHostnameFromSiteURL(*cfg.ServiceSettings.SiteURL), 113 connInfo, 114 ) 115 require.NotNil(t, err3, "Should get a timeout get while creating a new SMTP client") 116 assert.Equal(t, err3.Id, "utils.mail.connect_smtp.open_tls.app_error") 117 118 _, err4 := ConnectToSMTPServerAdvanced( 119 &SmtpConnectionInfo{ 120 ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity, 121 SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification, 122 SmtpServerName: "wrongServer", 123 SmtpServerHost: "wrongServer", 124 SmtpPort: "553", 125 }, 126 ) 127 require.NotNil(t, err4, "Should not connect to the SMTP Server") 128 } 129 130 func TestSendMailUsingConfig(t *testing.T) { 131 utils.T = utils.GetUserTranslations("en") 132 133 fs, err := config.NewFileStore("config.json", false) 134 require.Nil(t, err) 135 136 cfg := fs.Get() 137 138 var emailTo = "test@example.com" 139 var emailSubject = "Testing this email" 140 var emailBody = "This is a test from autobot" 141 var emailCC = "test@example.com" 142 143 //Delete all the messages before check the sample email 144 DeleteMailBox(emailTo) 145 146 err2 := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg, true, emailCC) 147 require.Nil(t, err2, "Should connect to the SMTP Server") 148 149 //Check if the email was send to the right email address 150 var resultsMailbox JSONMessageHeaderInbucket 151 err3 := RetryInbucket(5, func() error { 152 var err error 153 resultsMailbox, err = GetMailBox(emailTo) 154 return err 155 }) 156 if err3 != nil { 157 t.Log(err3) 158 t.Log("No email was received, maybe due load on the server. Skipping this verification") 159 } else { 160 if len(resultsMailbox) > 0 { 161 require.Contains(t, resultsMailbox[0].To[0], emailTo, "Wrong To: recipient") 162 resultsEmail, err := GetMessageFromMailbox(emailTo, resultsMailbox[0].ID) 163 require.Nil(t, err, "Could not get message from mailbox") 164 require.Contains(t, emailBody, resultsEmail.Body.Text, "Wrong received message %s", resultsEmail.Body.Text) 165 } 166 } 167 } 168 169 func TestSendMailWithEmbeddedFilesUsingConfig(t *testing.T) { 170 utils.T = utils.GetUserTranslations("en") 171 172 fs, err := config.NewFileStore("config.json", false) 173 require.Nil(t, err) 174 175 cfg := fs.Get() 176 177 var emailTo = "test@example.com" 178 var emailSubject = "Testing this email" 179 var emailBody = "This is a test from autobot" 180 var emailCC = "test@example.com" 181 182 //Delete all the messages before check the sample email 183 DeleteMailBox(emailTo) 184 185 embeddedFiles := map[string]io.Reader{ 186 "test1.png": bytes.NewReader([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")), 187 "test2.png": bytes.NewReader([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")), 188 } 189 err2 := SendMailWithEmbeddedFilesUsingConfig(emailTo, emailSubject, emailBody, embeddedFiles, cfg, true, emailCC) 190 require.Nil(t, err2, "Should connect to the SMTP Server") 191 192 //Check if the email was send to the right email address 193 var resultsMailbox JSONMessageHeaderInbucket 194 err3 := RetryInbucket(5, func() error { 195 var err error 196 resultsMailbox, err = GetMailBox(emailTo) 197 return err 198 }) 199 if err3 != nil { 200 t.Log(err3) 201 t.Log("No email was received, maybe due load on the server. Skipping this verification") 202 } else { 203 if len(resultsMailbox) > 0 { 204 require.Contains(t, resultsMailbox[0].To[0], emailTo, "Wrong To: recipient") 205 resultsEmail, err := GetMessageFromMailbox(emailTo, resultsMailbox[0].ID) 206 require.Nil(t, err, "Could not get message from mailbox") 207 require.Contains(t, emailBody, resultsEmail.Body.Text, "Wrong received message %s", resultsEmail.Body.Text) 208 // Usign the message size because the inbucket API doesn't return embedded attachments through the API 209 require.Greater(t, resultsEmail.Size, 1500, "the file size should be more because the embedded attachemtns") 210 } 211 } 212 } 213 214 func TestSendMailUsingConfigAdvanced(t *testing.T) { 215 utils.T = utils.GetUserTranslations("en") 216 217 fs, err := config.NewFileStore("config.json", false) 218 require.Nil(t, err) 219 220 cfg := fs.Get() 221 222 //Delete all the messages before check the sample email 223 DeleteMailBox("test2@example.com") 224 225 fileBackend, err := filesstore.NewFileBackend(&cfg.FileSettings, true) 226 assert.Nil(t, err) 227 228 // create two files with the same name that will both be attached to the email 229 filePath1 := fmt.Sprintf("test1/%s", "file1.txt") 230 filePath2 := fmt.Sprintf("test2/%s", "file2.txt") 231 fileContents1 := []byte("hello world") 232 fileContents2 := []byte("foo bar") 233 _, err = fileBackend.WriteFile(bytes.NewReader(fileContents1), filePath1) 234 assert.Nil(t, err) 235 _, err = fileBackend.WriteFile(bytes.NewReader(fileContents2), filePath2) 236 assert.Nil(t, err) 237 defer fileBackend.RemoveFile(filePath1) 238 defer fileBackend.RemoveFile(filePath2) 239 240 attachments := make([]*model.FileInfo, 2) 241 attachments[0] = &model.FileInfo{ 242 Name: "file1.txt", 243 Path: filePath1, 244 } 245 attachments[1] = &model.FileInfo{ 246 Name: "file2.txt", 247 Path: filePath2, 248 } 249 250 embeddedFiles := map[string]io.Reader{ 251 "test": bytes.NewReader([]byte("test data")), 252 } 253 254 headers := make(map[string]string) 255 headers["TestHeader"] = "TestValue" 256 257 mail := mailData{ 258 mimeTo: "test@example.com", 259 smtpTo: "test2@example.com", 260 from: mail.Address{Name: "Nobody", Address: "nobody@mattermost.com"}, 261 replyTo: mail.Address{Name: "ReplyTo", Address: "reply_to@mattermost.com"}, 262 subject: "Testing this email", 263 htmlBody: "This is a test from autobot", 264 attachments: attachments, 265 embeddedFiles: embeddedFiles, 266 mimeHeaders: headers, 267 } 268 269 err = sendMailUsingConfigAdvanced(mail, cfg, true) 270 require.Nil(t, err, "Should connect to the STMP Server: %v", err) 271 272 //Check if the email was send to the right email address 273 var resultsMailbox JSONMessageHeaderInbucket 274 err = RetryInbucket(5, func() error { 275 var mailErr error 276 resultsMailbox, mailErr = GetMailBox(mail.smtpTo) 277 return mailErr 278 }) 279 require.Nil(t, err, "No emails found for address %s. error: %v", mail.smtpTo, err) 280 require.NotEqual(t, len(resultsMailbox), 0) 281 282 require.Contains(t, resultsMailbox[0].To[0], mail.mimeTo, "Wrong To recipient") 283 284 resultsEmail, err := GetMessageFromMailbox(mail.smtpTo, resultsMailbox[0].ID) 285 require.Nil(t, err) 286 287 require.Contains(t, mail.htmlBody, resultsEmail.Body.Text, "Wrong received message") 288 289 // 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 290 assert.Equal(t, mail.mimeTo, resultsEmail.Header["To"][0]) 291 292 // verify that the MIME from address is correct - unfortunately, we can't verify the SMTP from address 293 assert.Equal(t, mail.from.String(), resultsEmail.Header["From"][0]) 294 295 // check that the custom mime headers came through - header case seems to get mutated 296 assert.Equal(t, "TestValue", resultsEmail.Header["Testheader"][0]) 297 298 // ensure that the attachments were successfully sent 299 assert.Len(t, resultsEmail.Attachments, 3) 300 301 attachmentsFilenames := []string{ 302 resultsEmail.Attachments[0].Filename, 303 resultsEmail.Attachments[1].Filename, 304 resultsEmail.Attachments[2].Filename, 305 } 306 assert.Contains(t, attachmentsFilenames, "file1.txt") 307 assert.Contains(t, attachmentsFilenames, "file2.txt") 308 assert.Contains(t, attachmentsFilenames, "test") 309 310 attachment1 := string(resultsEmail.Attachments[0].Bytes) 311 attachment2 := string(resultsEmail.Attachments[1].Bytes) 312 attachment3 := string(resultsEmail.Attachments[2].Bytes) 313 attachmentsData := []string{attachment1, attachment2, attachment3} 314 315 assert.Contains(t, attachmentsData, string(fileContents1)) 316 assert.Contains(t, attachmentsData, string(fileContents2)) 317 assert.Contains(t, attachmentsData, "test data") 318 } 319 320 func TestAuthMethods(t *testing.T) { 321 auth := &authChooser{ 322 connectionInfo: &SmtpConnectionInfo{ 323 SmtpUsername: "test", 324 SmtpPassword: "fakepass", 325 SmtpServerName: "fakeserver", 326 SmtpServerHost: "fakeserver", 327 SmtpPort: "25", 328 }, 329 } 330 tests := []struct { 331 desc string 332 server *smtp.ServerInfo 333 err string 334 }{ 335 { 336 desc: "auth PLAIN success", 337 server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"PLAIN"}, TLS: true}, 338 }, 339 { 340 desc: "auth PLAIN unencrypted connection fail", 341 server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"PLAIN"}, TLS: false}, 342 err: "unencrypted connection", 343 }, 344 { 345 desc: "auth PLAIN wrong host name", 346 server: &smtp.ServerInfo{Name: "wrongServer:999", Auth: []string{"PLAIN"}, TLS: true}, 347 err: "wrong host name", 348 }, 349 { 350 desc: "auth LOGIN success", 351 server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"LOGIN"}, TLS: true}, 352 }, 353 { 354 desc: "auth LOGIN unencrypted connection fail", 355 server: &smtp.ServerInfo{Name: "wrongServer:999", Auth: []string{"LOGIN"}, TLS: true}, 356 err: "wrong host name", 357 }, 358 { 359 desc: "auth LOGIN wrong host name", 360 server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"LOGIN"}, TLS: false}, 361 err: "unencrypted connection", 362 }, 363 } 364 365 for i, test := range tests { 366 t.Run(test.desc, func(t *testing.T) { 367 _, _, err := auth.Start(test.server) 368 got := "" 369 if err != nil { 370 got = err.Error() 371 } 372 assert.True(t, got == test.err, "%d. got error = %q; want %q", i, got, test.err) 373 }) 374 } 375 } 376 377 type mockMailer struct { 378 data []byte 379 } 380 381 func (m *mockMailer) Mail(string) error { return nil } 382 func (m *mockMailer) Rcpt(string) error { return nil } 383 func (m *mockMailer) Data() (io.WriteCloser, error) { return m, nil } 384 func (m *mockMailer) Write(p []byte) (int, error) { 385 m.data = append(m.data, p...) 386 return len(p), nil 387 } 388 func (m *mockMailer) Close() error { return nil } 389 390 func TestSendMail(t *testing.T) { 391 dir, err := ioutil.TempDir(".", "mail-test-") 392 require.Nil(t, err) 393 defer os.RemoveAll(dir) 394 settings := model.FileSettings{ 395 DriverName: model.NewString(model.IMAGE_DRIVER_LOCAL), 396 Directory: &dir, 397 } 398 mockBackend, appErr := filesstore.NewFileBackend(&settings, true) 399 require.Nil(t, appErr) 400 mocm := &mockMailer{} 401 402 testCases := map[string]struct { 403 replyTo mail.Address 404 contains string 405 notContains string 406 }{ 407 "adds reply-to header": { 408 mail.Address{Address: "foo@test.com"}, 409 "\r\nReply-To: <foo@test.com>\r\n", 410 "", 411 }, 412 "doesn't add reply-to header": { 413 mail.Address{}, 414 "", 415 "\r\nReply-To:", 416 }, 417 } 418 419 for testName, tc := range testCases { 420 t.Run(testName, func(t *testing.T) { 421 mail := mailData{"", "", mail.Address{}, "", tc.replyTo, "", "", nil, nil, nil} 422 appErr = SendMail(mocm, mail, mockBackend, time.Now()) 423 require.Nil(t, appErr) 424 if len(tc.contains) > 0 { 425 require.Contains(t, string(mocm.data), tc.contains) 426 } 427 if len(tc.notContains) > 0 { 428 require.NotContains(t, string(mocm.data), tc.notContains) 429 } 430 mocm.data = []byte{} 431 }) 432 } 433 }