Simplified emailing system and combined mailer and notifications packages
This commit is contained in:
@@ -5,18 +5,23 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// Create New mail message use MailFrom and MailUser
|
||||
func newMailMessageFrom(To []string, from, subject, body string) m.SendEmailCommand {
|
||||
return m.NewSendEmailCommand(To, from, subject, body)
|
||||
type Message struct {
|
||||
To []string
|
||||
From string
|
||||
Subject string
|
||||
Body string
|
||||
Massive bool
|
||||
Info string
|
||||
}
|
||||
|
||||
// Create New mail message use MailFrom and MailUser
|
||||
func newMailMessage(To string, subject, body string) m.SendEmailCommand {
|
||||
return newMailMessageFrom([]string{To}, setting.Smtp.FromAddress, subject, body)
|
||||
// create mail content
|
||||
func (m *Message) Content() string {
|
||||
contentType := "text/html; charset=UTF-8"
|
||||
content := "From: " + m.From + "\r\nSubject: " + m.Subject + "\r\nContent-Type: " + contentType + "\r\n\r\n" + m.Body
|
||||
return content
|
||||
}
|
||||
|
||||
func getMailTmplData(u *m.User) map[interface{}]interface{} {
|
||||
data := make(map[interface{}]interface{}, 10)
|
||||
func setDefaultTemplateData(data map[string]interface{}, u *m.User) {
|
||||
data["AppUrl"] = setting.AppUrl
|
||||
data["BuildVersion"] = setting.BuildVersion
|
||||
data["BuildStamp"] = setting.BuildStamp
|
||||
@@ -25,5 +30,4 @@ func getMailTmplData(u *m.User) map[interface{}]interface{} {
|
||||
if u != nil {
|
||||
data["Name"] = u.NameOrFallback()
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var mailQueue chan *Message
|
||||
|
||||
func initMailQueue() {
|
||||
mailQueue = make(chan *Message, 10)
|
||||
|
||||
setting.Smtp = setting.SmtpSettings{
|
||||
Host: "smtp.gmail.com:587",
|
||||
User: "torkel.odegaard@gmail.com",
|
||||
Password: "peslpwstnnloiksq",
|
||||
FromAddress: "grafana@grafana.org",
|
||||
}
|
||||
|
||||
go processMailQueue()
|
||||
}
|
||||
|
||||
func processMailQueue() {
|
||||
for {
|
||||
select {
|
||||
case msg := <-mailQueue:
|
||||
num, err := buildAndSend(msg)
|
||||
tos := strings.Join(msg.To, "; ")
|
||||
info := ""
|
||||
if err != nil {
|
||||
if len(msg.Info) > 0 {
|
||||
info = ", info: " + msg.Info
|
||||
}
|
||||
log.Error(4, fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err))
|
||||
} else {
|
||||
log.Trace(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var addToMailQueue = func(msg *Message) {
|
||||
mailQueue <- msg
|
||||
}
|
||||
|
||||
func sendToSmtpServer(recipients []string, msgContent []byte) error {
|
||||
host, port, err := net.SplitHostPort(setting.Smtp.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsconfig := &tls.Config{
|
||||
InsecureSkipVerify: setting.Smtp.SkipVerify,
|
||||
ServerName: host,
|
||||
}
|
||||
|
||||
if setting.Smtp.CertFile != "" {
|
||||
cert, err := tls.LoadX509KeyPair(setting.Smtp.CertFile, setting.Smtp.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsconfig.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
isSecureConn := false
|
||||
// Start TLS directly if the port ends with 465 (SMTPS protocol)
|
||||
if strings.HasSuffix(port, "465") {
|
||||
conn = tls.Client(conn, tlsconfig)
|
||||
isSecureConn = true
|
||||
}
|
||||
|
||||
client, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = client.Hello(hostname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If not using SMTPS, alway use STARTTLS if available
|
||||
hasStartTLS, _ := client.Extension("STARTTLS")
|
||||
if !isSecureConn && hasStartTLS {
|
||||
if err = client.StartTLS(tlsconfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
canAuth, options := client.Extension("AUTH")
|
||||
|
||||
if canAuth && len(setting.Smtp.User) > 0 {
|
||||
var auth smtp.Auth
|
||||
|
||||
if strings.Contains(options, "CRAM-MD5") {
|
||||
auth = smtp.CRAMMD5Auth(setting.Smtp.User, setting.Smtp.Password)
|
||||
} else if strings.Contains(options, "PLAIN") {
|
||||
auth = smtp.PlainAuth("", setting.Smtp.User, setting.Smtp.Password, host)
|
||||
}
|
||||
|
||||
if auth != nil {
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fromAddress, err := mail.ParseAddress(setting.Smtp.FromAddress); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if err = client.Mail(fromAddress.Address); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, rec := range recipients {
|
||||
if err = client.Rcpt(rec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write([]byte(msgContent)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.Quit()
|
||||
}
|
||||
|
||||
func buildAndSend(msg *Message) (int, error) {
|
||||
log.Trace("Sending mails to: %s", strings.Join(msg.To, "; "))
|
||||
|
||||
// get message body
|
||||
content := msg.Content()
|
||||
|
||||
if len(msg.To) == 0 {
|
||||
return 0, fmt.Errorf("empty receive emails")
|
||||
} else if len(msg.Body) == 0 {
|
||||
return 0, fmt.Errorf("empty email body")
|
||||
}
|
||||
|
||||
if msg.Massive {
|
||||
// send mail to multiple emails one by one
|
||||
num := 0
|
||||
for _, to := range msg.To {
|
||||
body := []byte("To: " + to + "\r\n" + content)
|
||||
err := sendToSmtpServer([]string{to}, body)
|
||||
if err != nil {
|
||||
return num, err
|
||||
}
|
||||
num++
|
||||
}
|
||||
return num, nil
|
||||
} else {
|
||||
body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content)
|
||||
|
||||
// send to multiple emails in one message
|
||||
err := sendToSmtpServer(msg.To, body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return 1, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,11 @@ var mailTemplates *template.Template
|
||||
var tmplResetPassword = "reset_password.html"
|
||||
|
||||
func Init() error {
|
||||
initMailQueue()
|
||||
|
||||
bus.AddHandler("email", sendResetPasswordEmail)
|
||||
bus.AddHandler("email", validateResetPasswordCode)
|
||||
bus.AddHandler("email", sendEmailCommandHandler)
|
||||
|
||||
mailTemplates = template.New("name")
|
||||
mailTemplates.Funcs(template.FuncMap{
|
||||
@@ -41,26 +44,23 @@ func Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var dispatchMail = func(cmd *m.SendEmailCommand) error {
|
||||
return bus.Dispatch(cmd)
|
||||
}
|
||||
|
||||
func subjectTemplateFunc(obj map[string]interface{}, value string) string {
|
||||
obj["value"] = value
|
||||
return ""
|
||||
}
|
||||
|
||||
func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
|
||||
func sendEmailCommandHandler(cmd *m.SendEmailCommand) error {
|
||||
var buffer bytes.Buffer
|
||||
data := cmd.Data
|
||||
if data == nil {
|
||||
data = make(map[string]interface{}, 10)
|
||||
}
|
||||
|
||||
var data = getMailTmplData(cmd.User)
|
||||
code := createUserEmailCode(cmd.User, nil)
|
||||
data["Code"] = code
|
||||
setDefaultTemplateData(data, nil)
|
||||
mailTemplates.ExecuteTemplate(&buffer, cmd.Template, data)
|
||||
|
||||
mailTemplates.ExecuteTemplate(&buffer, tmplResetPassword, data)
|
||||
|
||||
dispatchMail(&m.SendEmailCommand{
|
||||
To: []string{cmd.User.Email},
|
||||
addToMailQueue(&Message{
|
||||
To: cmd.To,
|
||||
From: setting.Smtp.FromAddress,
|
||||
Subject: data["Subject"].(map[string]interface{})["value"].(string),
|
||||
Body: buffer.String(),
|
||||
@@ -69,6 +69,17 @@ func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
|
||||
return sendEmailCommandHandler(&m.SendEmailCommand{
|
||||
To: []string{cmd.User.Email},
|
||||
Template: tmplResetPassword,
|
||||
Data: map[string]interface{}{
|
||||
"Code": createUserEmailCode(cmd.User, nil),
|
||||
"Name": cmd.User.NameOrFallback(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error {
|
||||
login := getLoginForEmailCode(query.Code)
|
||||
if login == "" {
|
||||
|
||||
@@ -20,17 +20,16 @@ func TestNotifications(t *testing.T) {
|
||||
err := Init()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var sentMail *m.SendEmailCommand
|
||||
dispatchMail = func(cmd *m.SendEmailCommand) error {
|
||||
sentMail = cmd
|
||||
return nil
|
||||
var sentMsg *Message
|
||||
addToMailQueue = func(msg *Message) {
|
||||
sentMsg = msg
|
||||
}
|
||||
|
||||
Convey("When sending reset email password", func() {
|
||||
sendResetPasswordEmail(&m.SendResetPasswordEmailCommand{User: &m.User{Email: "asd@asd.com"}})
|
||||
So(sentMail.Body, ShouldContainSubstring, "body")
|
||||
So(sentMail.Subject, ShouldEqual, "Reset your Grafana password")
|
||||
So(sentMail.Body, ShouldNotContainSubstring, "Subject")
|
||||
So(sentMsg.Body, ShouldContainSubstring, "body")
|
||||
So(sentMsg.Subject, ShouldEqual, "Reset your Grafana password")
|
||||
So(sentMsg.Body, ShouldNotContainSubstring, "Subject")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user