Skip to content

Commit

Permalink
More work on email and notification infra #1456
Browse files Browse the repository at this point in the history
  • Loading branch information
torkelo committed Jun 5, 2015
1 parent c709a28 commit 89418a1
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 63 deletions.
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/eventpublisher"
"github.com/grafana/grafana/pkg/services/mailer"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
Expand Down Expand Up @@ -59,6 +60,10 @@ func main() {
plugins.Init()
mailer.Init()

if err := notifications.Init(); err != nil {
log.Fatal(3, "Notification service failed to initialize", err)
}

if setting.ReportingEnabled {
go metrics.StartUsageReportLoop()
}
Expand Down
7 changes: 0 additions & 7 deletions pkg/api/apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@ func GetApiKeys(c *middleware.Context) Response {
}
}

// bus.Dispatch(&m.SendEmailCommand{
// To: []string{"torkel@raintank.io"},
// From: "grafana@test.com",
// Subject: "Test from Grafana2",
// Body: "Body! hej hoppas allt är bra",
// })

return Json(200, result)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/models/emails.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type SendEmailCommand struct {
}

type SendResetPasswordEmailCommand struct {
Email string
User *User
}

// create mail content
Expand Down
69 changes: 69 additions & 0 deletions pkg/services/notifications/codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package notifications

import (
"crypto/sha1"
"encoding/hex"
"fmt"
"time"

"github.com/Unknwon/com"
"github.com/grafana/grafana/pkg/setting"
)

// create a time limit code
// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string {
format := "200601021504"

var start, end time.Time
var startStr, endStr string

if startInf == nil {
// Use now time create code
start = time.Now()
startStr = start.Format(format)
} else {
// use start string create code
startStr = startInf.(string)
start, _ = time.ParseInLocation(format, startStr, time.Local)
startStr = start.Format(format)
}

end = start.Add(time.Minute * time.Duration(minutes))
endStr = end.Format(format)

// create sha1 encode string
sh := sha1.New()
sh.Write([]byte(data + setting.SecretKey + startStr + endStr + com.ToStr(minutes)))
encoded := hex.EncodeToString(sh.Sum(nil))

code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
return code
}

// verify time limit code
func VerifyTimeLimitCode(data string, minutes int, code string) bool {
if len(code) <= 18 {
return false
}

// split code
start := code[:12]
lives := code[12:18]
if d, err := com.StrTo(lives).Int(); err == nil {
minutes = d
}

// right active code
retCode := CreateTimeLimitCode(data, minutes, start)
if retCode == code && minutes > 0 {
// check time is expired or not
before, _ := time.ParseInLocation("200601021504", start, time.Local)
now := time.Now()
if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
return true
}
}

return false
}
49 changes: 6 additions & 43 deletions pkg/services/notifications/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,23 @@ import (
"github.com/grafana/grafana/pkg/setting"
)

type SendEmailCommand struct {
To []string
From string
Subject string
Body string
Type string
Massive bool
Info string
}

type SendResetPasswordEmailCommand struct {
Email string
}

// create mail content
func (m *SendEmailCommand) Content() string {
// set mail type
contentType := "text/plain; charset=UTF-8"
if m.Type == "html" {
contentType = "text/html; charset=UTF-8"
}

// create mail content
content := "From: " + m.From + "\r\nSubject: " + m.Subject + "\r\nContent-Type: " + contentType + "\r\n\r\n" + m.Body
return content
}

// Create html mail command
func NewSendEmailCommand(To []string, From, Subject, Body string) SendEmailCommand {
return SendEmailCommand{
To: To,
From: From,
Subject: Subject,
Body: Body,
Type: "html",
}
}

// Create New mail message use MailFrom and MailUser
func NewMailMessageFrom(To []string, from, subject, body string) SendEmailCommand {
return NewSendEmailCommand(To, from, subject, body)
func newMailMessageFrom(To []string, from, subject, body string) m.SendEmailCommand {
return m.NewSendEmailCommand(To, from, subject, body)
}

// Create New mail message use MailFrom and MailUser
func NewMailMessage(To string, subject, body string) SendEmailCommand {
return NewMailMessageFrom([]string{To}, setting.Smtp.FromAddress, subject, body)
func newMailMessage(To string, subject, body string) m.SendEmailCommand {
return newMailMessageFrom([]string{To}, setting.Smtp.FromAddress, subject, body)
}

func GetMailTmplData(u *m.User) map[interface{}]interface{} {
func getMailTmplData(u *m.User) map[interface{}]interface{} {
data := make(map[interface{}]interface{}, 10)
data["AppUrl"] = setting.AppUrl
data["BuildVersion"] = setting.BuildVersion
data["BuildStamp"] = setting.BuildStamp
data["BuildCommit"] = setting.BuildCommit
data["Subject"] = map[string]interface{}{}
if u != nil {
data["User"] = u
}
Expand Down
72 changes: 69 additions & 3 deletions pkg/services/notifications/notifications.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,77 @@
package notifications

import "github.com/grafana/grafana/pkg/bus"
import (
"bytes"
"encoding/hex"
"errors"
"html/template"
"path/filepath"

func Init() {
"github.com/Unknwon/com"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)

var mailTemplates *template.Template
var tmplResetPassword = "reset_password.html"

func Init() error {
bus.AddHandler("email", sendResetPasswordEmail)

mailTemplates = template.New("name")
mailTemplates.Funcs(template.FuncMap{
"Subject": subjectTemplateFunc,
})

templatePattern := filepath.Join(setting.StaticRootPath, "emails/*.html")
_, err := mailTemplates.ParseGlob(templatePattern)
if err != nil {
return err
}

if !util.IsEmail(setting.Smtp.FromAddress) {
return errors.New("Invalid email address for smpt from_adress config")
}

return nil
}

var dispatchMail = func(cmd *m.SendEmailCommand) error {
return bus.Dispatch(cmd)
}

func sendResetPasswordEmail(cmd *SendResetPasswordEmailCommand) error {
func subjectTemplateFunc(obj map[string]interface{}, value string) string {
obj["value"] = value
return ""
}

func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
var buffer bytes.Buffer

var data = getMailTmplData(nil)
code := CreateUserActiveCode(cmd.User, nil)
data["Code"] = code

mailTemplates.ExecuteTemplate(&buffer, tmplResetPassword, data)

dispatchMail(&m.SendEmailCommand{
To: []string{cmd.User.Email},
From: setting.Smtp.FromAddress,
Subject: data["Subject"].(map[string]interface{})["value"].(string),
Body: buffer.String(),
})

return nil
}

func CreateUserActiveCode(u *m.User, startInf interface{}) string {
minutes := setting.EmailCodeValidMinutes
data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands
code := CreateTimeLimitCode(data, minutes, startInf)

// add tail hex username
code += hex.EncodeToString([]byte(u.Login))
return code
}
37 changes: 37 additions & 0 deletions pkg/services/notifications/notifications_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package notifications

import (
"testing"

"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)

func TestNotifications(t *testing.T) {

Convey("Given the notifications service", t, func() {
bus.ClearBusHandlers()

setting.StaticRootPath = "../../../public/"
setting.Smtp.FromAddress = "from@address.com"

err := Init()
So(err, ShouldBeNil)

var sentMail *m.SendEmailCommand
dispatchMail = func(cmd *m.SendEmailCommand) error {
sentMail = cmd
return nil
}

Convey("When sending reset email password", func() {
sendResetPasswordEmail(&m.SendResetPasswordEmailCommand{User: &m.User{Email: "asd@asd.com"}})
So(sentMail.Body, ShouldContainSubstring, "h2")
So(sentMail.Subject, ShouldEqual, "Welcome to Grafana")
So(sentMail.Body, ShouldNotContainSubstring, "Subject")
})
})

}
2 changes: 1 addition & 1 deletion pkg/services/search/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
func TestSearch(t *testing.T) {

Convey("Given search query", t, func() {
jsonDashIndex = NewJsonDashIndex("../../public/dashboards/")
jsonDashIndex = NewJsonDashIndex("../../../public/dashboards/")
query := Query{Limit: 2000}

bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error {
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/search/json_index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
func TestJsonDashIndex(t *testing.T) {

Convey("Given the json dash index", t, func() {
index := NewJsonDashIndex("../../public/dashboards/")
index := NewJsonDashIndex("../../../public/dashboards/")

Convey("Should be able to update index", func() {
err := index.updateIndex()
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/sqlstore/dashboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
. "github.com/smartystreets/goconvey/convey"

m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/search"
"github.com/grafana/grafana/pkg/services/search"
)

func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
Expand Down
11 changes: 6 additions & 5 deletions pkg/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ var (
EnforceDomain bool

// Security settings.
SecretKey string
LogInRememberDays int
CookieUserName string
CookieRememberName string
DisableGravatar bool
SecretKey string
LogInRememberDays int
CookieUserName string
CookieRememberName string
DisableGravatar bool
EmailCodeValidMinutes int

// User settings
AllowUserSignUp bool
Expand Down
1 change: 0 additions & 1 deletion pkg/setting/setting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ func TestLoadingSettings(t *testing.T) {
Convey("Given the default ini files", func() {
NewConfigContext(&CommandLineArgs{HomePath: "../../"})

So(AppName, ShouldEqual, "Grafana")
So(AdminUser, ShouldEqual, "admin")
})

Expand Down
18 changes: 18 additions & 0 deletions pkg/util/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package util

import (
"regexp"
"strings"
)

const (
emailRegexPattern string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
)

var (
regexEmail = regexp.MustCompile(emailRegexPattern)
)

func IsEmail(str string) bool {
return regexEmail.MatchString(strings.ToLower(str))
}
5 changes: 5 additions & 0 deletions public/emails/reset_password.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{Subject .Subject "Welcome to Grafana"}}

<h2>
{{.BuildVersion}}
</h2>

0 comments on commit 89418a1

Please sign in to comment.