Embedding avatar in guest invite emails (#12328)

* Embedding avatar in guest invite emails

* Fixing tests

* Improving mailservice tests

* Fixing shadow variables

* Fixing styles for gmail

* Keep spacing on invite messages

* Fixing spacing
Этот коммит содержится в:
Jesús Espino
2019-09-30 23:27:07 +02:00
коммит произвёл GitHub
родитель 8cea561ba6
Коммит 6ba961a480
5 изменённых файлов: 96 добавлений и 61 удалений

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

@@ -4,7 +4,10 @@
package app
import (
"bytes"
"fmt"
"io"
"net/mail"
"net/url"
"path"
"strings"
@@ -367,6 +370,17 @@ func (a *App) SendGuestInviteEmails(team *model.Team, channels []*model.Channel,
return
}
sender, appErr := a.GetUser(senderUserId)
if appErr != nil {
a.Log.Error("Email invite not sent, unable to find the sender user.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id), mlog.Err(appErr))
return
}
senderProfileImage, _, appErr := a.GetProfileImage(sender)
if appErr != nil {
a.Log.Warn("Unable to get the sender user profile image.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id), mlog.Err(appErr))
}
if rateLimited {
a.Log.Error("Invite emails rate limited.",
mlog.String("user_id", senderUserId),
@@ -430,7 +444,14 @@ func (a *App) SendGuestInviteEmails(team *model.Team, channels []*model.Channel,
mlog.Info(fmt.Sprintf("sending invitation to %v %v", invite, bodyPage.Props["Link"]))
}
if err := a.SendMail(invite, subject, bodyPage.Render()); err != nil {
embeddedFiles := make(map[string]io.Reader)
if senderProfileImage != nil {
embeddedFiles = map[string]io.Reader{
"user-avatar.png": bytes.NewReader(senderProfileImage),
}
}
if err := a.SendMailWithEmbeddedFiles(invite, subject, bodyPage.Render(), embeddedFiles); err != nil {
mlog.Error(fmt.Sprintf("Failed to send invite email successfully err=%v", err))
}
}
@@ -498,3 +519,12 @@ func (a *App) SendMail(to, subject, htmlBody string) *model.AppError {
license := a.License()
return mailservice.SendMailUsingConfig(to, subject, htmlBody, a.Config(), license != nil && *license.Features.Compliance)
}
func (a *App) SendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader) *model.AppError {
license := a.License()
config := a.Config()
fromMail := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.FeedbackEmail}
replyTo := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.ReplyToAddress}
return mailservice.SendMailUsingConfigAdvanced(to, to, fromMail, replyTo, subject, htmlBody, nil, embeddedFiles, nil, config, license != nil && *license.Features.Compliance)
}

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

@@ -196,7 +196,7 @@ func getInbucketHost() (host string) {
inbucket_port := os.Getenv("CI_INBUCKET_PORT")
if inbucket_port == "" {
inbucket_port = "9000"
inbucket_port = "10080"
}
return fmt.Sprintf("http://%s:%s", inbucket_host, inbucket_port)
}

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

@@ -208,11 +208,11 @@ func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, ena
fromMail := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.FeedbackEmail}
replyTo := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.ReplyToAddress}
return SendMailUsingConfigAdvanced(to, to, fromMail, replyTo, subject, htmlBody, nil, nil, config, enableComplianceFeatures)
return SendMailUsingConfigAdvanced(to, to, fromMail, replyTo, subject, htmlBody, nil, nil, nil, config, enableComplianceFeatures)
}
// allows for sending an email with attachments and differing MIME/SMTP recipients
func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from, replyTo mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError {
func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from, replyTo mail.Address, subject, htmlBody string, attachments []*model.FileInfo, embeddedFiles map[string]io.Reader, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError {
if len(*config.EmailSettings.SMTPServer) == 0 {
return nil
}
@@ -235,10 +235,10 @@ func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from, replyTo mail.Addre
return err
}
return SendMail(c, mimeTo, smtpTo, from, replyTo, subject, htmlBody, attachments, mimeHeaders, fileBackend, time.Now())
return SendMail(c, mimeTo, smtpTo, from, replyTo, subject, htmlBody, attachments, embeddedFiles, mimeHeaders, fileBackend, time.Now())
}
func SendMail(c smtpClient, mimeTo, smtpTo string, from, replyTo mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, fileBackend filesstore.FileBackend, date time.Time) *model.AppError {
func SendMail(c smtpClient, mimeTo, smtpTo string, from, replyTo mail.Address, subject, htmlBody string, attachments []*model.FileInfo, embeddedFiles map[string]io.Reader, mimeHeaders map[string]string, fileBackend filesstore.FileBackend, date time.Time) *model.AppError {
mlog.Debug("sending mail", mlog.String("to", smtpTo), mlog.String("subject", subject))
htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>"
@@ -272,6 +272,10 @@ func SendMail(c smtpClient, mimeTo, smtpTo string, from, replyTo mail.Address, s
m.SetBody("text/plain", txtBody)
m.AddAlternative("text/html", htmlMessage)
for name, reader := range embeddedFiles {
m.EmbedReader(name, reader)
}
for _, fileInfo := range attachments {
bytes, err := fileBackend.ReadFile(fileInfo.Path)
if err != nil {

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

@@ -168,9 +168,8 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) {
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)
filePath1 := fmt.Sprintf("test1/%s", "file1.txt")
filePath2 := fmt.Sprintf("test2/%s", "file2.txt")
fileContents1 := []byte("hello world")
fileContents2 := []byte("foo bar")
_, err = fileBackend.WriteFile(bytes.NewReader(fileContents1), filePath1)
@@ -182,68 +181,70 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) {
attachments := make([]*model.FileInfo, 2)
attachments[0] = &model.FileInfo{
Name: fileName,
Name: "file1.txt",
Path: filePath1,
}
attachments[1] = &model.FileInfo{
Name: fileName,
Name: "file2.txt",
Path: filePath2,
}
embeddedFiles := map[string]io.Reader{
"test": bytes.NewReader([]byte("test data")),
}
headers := make(map[string]string)
headers["TestHeader"] = "TestValue"
if err := SendMailUsingConfigAdvanced(mimeTo, smtpTo, from, replyTo, 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")
}
err = SendMailUsingConfigAdvanced(mimeTo, smtpTo, from, replyTo, emailSubject, emailBody, attachments, embeddedFiles, headers, cfg, true)
require.Nil(t, err, "Should connect to the STMP Server: %v", err)
// 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])
//Check if the email was send to the right email address
var resultsMailbox JSONMessageHeaderInbucket
err = RetryInbucket(5, func() error {
var mailErr error
resultsMailbox, mailErr = GetMailBox(smtpTo)
return mailErr
})
require.Nil(t, err, "No emails found for address %s. error: %v", smtpTo, err)
require.NotEqual(t, len(resultsMailbox), 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])
require.Contains(t, resultsMailbox[0].To[0], mimeTo, "Wrong To recipient")
// check that the custom mime headers came through - header case seems to get mutated
assert.Equal(t, "TestValue", resultsEmail.Header["Testheader"][0])
resultsEmail, err := GetMessageFromMailbox(smtpTo, resultsMailbox[0].ID)
require.Nil(t, err)
// 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")
}
}
}
}
require.Contains(t, emailBody, resultsEmail.Body.Text, "Wrong 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, 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) {
@@ -349,7 +350,7 @@ func TestSendMail(t *testing.T) {
for testName, tc := range testCases {
t.Run(testName, func(t *testing.T) {
appErr = SendMail(mocm, "", "", mail.Address{}, tc.replyTo, "", "", nil, nil, mockBackend, time.Now())
appErr = SendMail(mocm, "", "", mail.Address{}, tc.replyTo, "", "", nil, nil, nil, mockBackend, time.Now())
require.Nil(t, appErr)
if len(tc.contains) > 0 {
require.Contains(t, string(mocm.data), tc.contains)

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

@@ -26,11 +26,11 @@
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px; margin: 35px auto 0 auto; border-radius: 5px; background: #f2f2f2; min-width: 550px">
<tr>
<td style="border: 0; padding: 0; width: 36px; vertical-align: top">
<img src="{{.Props.SiteURL}}/api/v4/users/{{.Props.SenderId}}/image" style="height: 36px; width=36px; border-radius: 18px;">
<img width="36" height="36" style="border-radius: 18px;" src="cid:user-avatar.png">
</td>
<td style="border: 0; padding: 0 0 0 20px;">
<td style="border: 0; padding: 0 0 0 20px; text-align: left;">
<p style="font-weight: bold; margin-top: 0;">{{.Props.SenderName}}</p>
<p>{{.Props.Message}}</p>
<p style="white-space: break-spaces">{{.Props.Message}}</p>
</td>
</tr>
</table>