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  }