2015-06-04 23:23:46 +08:00
package notifications
2015-06-05 17:08:19 +08:00
import (
2023-01-31 05:56:23 +08:00
"bytes"
2016-10-03 15:38:03 +08:00
"context"
2015-06-05 17:08:19 +08:00
"errors"
2015-08-28 19:45:16 +08:00
"fmt"
2015-06-05 17:08:19 +08:00
"html/template"
2015-08-28 19:45:16 +08:00
"net/url"
2015-06-05 17:08:19 +08:00
"path/filepath"
2018-04-27 19:01:32 +08:00
"strings"
2015-06-04 23:23:46 +08:00
2022-11-18 04:41:46 +08:00
"github.com/Masterminds/sprig/v3"
2023-01-18 03:47:31 +08:00
2015-06-05 17:08:19 +08:00
"github.com/grafana/grafana/pkg/bus"
2015-06-08 23:56:56 +08:00
"github.com/grafana/grafana/pkg/events"
2019-05-13 14:45:54 +08:00
"github.com/grafana/grafana/pkg/infra/log"
2023-01-06 16:02:05 +08:00
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
2022-06-28 20:32:25 +08:00
"github.com/grafana/grafana/pkg/services/user"
2015-06-05 17:08:19 +08:00
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
2022-01-26 23:42:40 +08:00
type WebhookSender interface {
2023-01-18 03:47:31 +08:00
SendWebhookSync ( ctx context . Context , cmd * SendWebhookSync ) error
2022-01-26 23:42:40 +08:00
}
type EmailSender interface {
2023-01-18 03:47:31 +08:00
SendEmailCommandHandlerSync ( ctx context . Context , cmd * SendEmailCommandSync ) error
SendEmailCommandHandler ( ctx context . Context , cmd * SendEmailCommand ) error
2022-01-26 23:42:40 +08:00
}
type Service interface {
WebhookSender
EmailSender
}
2015-06-05 17:08:19 +08:00
var mailTemplates * template . Template
2021-07-19 18:31:51 +08:00
var tmplResetPassword = "reset_password"
var tmplSignUpStarted = "signup_started"
var tmplWelcomeOnSignUp = "welcome_on_signup"
2015-06-05 17:08:19 +08:00
2022-03-22 16:04:30 +08:00
func ProvideService ( bus bus . Bus , cfg * setting . Cfg , mailer Mailer , store TempUserStore ) ( * NotificationService , error ) {
2021-08-25 21:11:22 +08:00
ns := & NotificationService {
Bus : bus ,
Cfg : cfg ,
log : log . New ( "notifications" ) ,
mailQueue : make ( chan * Message , 10 ) ,
webhookQueue : make ( chan * Webhook , 10 ) ,
2022-01-14 05:19:15 +08:00
mailer : mailer ,
2022-03-22 16:04:30 +08:00
store : store ,
2021-08-25 21:11:22 +08:00
}
2015-06-05 17:08:19 +08:00
2022-01-04 16:36:01 +08:00
ns . Bus . AddEventListener ( ns . signUpStartedHandler )
ns . Bus . AddEventListener ( ns . signUpCompletedHandler )
2015-06-08 23:56:56 +08:00
2015-06-05 17:08:19 +08:00
mailTemplates = template . New ( "name" )
mailTemplates . Funcs ( template . FuncMap {
2023-01-31 05:56:23 +08:00
"Subject" : subjectTemplateFunc ,
"HiddenSubject" : hiddenSubjectTemplateFunc ,
2015-06-05 17:08:19 +08:00
} )
2022-11-18 04:41:46 +08:00
mailTemplates . Funcs ( sprig . FuncMap ( ) )
2015-06-05 17:08:19 +08:00
2023-01-13 23:24:22 +08:00
// Parse invalid templates using 'or' logic. Return an error only if no paths are valid.
invalidTemplates := make ( [ ] string , 0 )
2021-07-19 18:31:51 +08:00
for _ , pattern := range ns . Cfg . Smtp . TemplatesPatterns {
templatePattern := filepath . Join ( ns . Cfg . StaticRootPath , pattern )
_ , err := mailTemplates . ParseGlob ( templatePattern )
if err != nil {
2023-01-13 23:24:22 +08:00
invalidTemplates = append ( invalidTemplates , templatePattern )
2021-07-19 18:31:51 +08:00
}
2015-06-05 17:08:19 +08:00
}
2023-01-13 23:24:22 +08:00
if len ( invalidTemplates ) > 0 {
is := strings . Join ( invalidTemplates , ", " )
if len ( invalidTemplates ) == len ( ns . Cfg . Smtp . TemplatesPatterns ) {
return nil , fmt . Errorf ( "provided html/template filepaths matched no files: %s" , is )
}
ns . log . Warn ( "some provided html/template filepaths matched no files: %s" , is )
}
2015-06-05 17:08:19 +08:00
2018-04-30 22:21:04 +08:00
if ! util . IsEmail ( ns . Cfg . Smtp . FromAddress ) {
2021-08-25 21:11:22 +08:00
return nil , errors . New ( "invalid email address for SMTP from_address config" )
2015-06-05 17:08:19 +08:00
}
2021-08-25 21:11:22 +08:00
if cfg . EmailCodeValidMinutes == 0 {
cfg . EmailCodeValidMinutes = 120
2015-06-08 16:57:01 +08:00
}
2021-08-25 21:11:22 +08:00
return ns , nil
}
2015-06-08 16:57:01 +08:00
2022-03-22 16:04:30 +08:00
type TempUserStore interface {
2023-01-06 16:02:05 +08:00
UpdateTempUserWithEmailSent ( ctx context . Context , cmd * tempuser . UpdateTempUserWithEmailSentCommand ) error
2022-03-22 16:04:30 +08:00
}
2021-08-25 21:11:22 +08:00
type NotificationService struct {
Bus bus . Bus
Cfg * setting . Cfg
mailQueue chan * Message
webhookQueue chan * Webhook
2022-01-14 05:19:15 +08:00
mailer Mailer
2021-08-25 21:11:22 +08:00
log log . Logger
2022-03-22 16:04:30 +08:00
store TempUserStore
2015-06-05 17:08:19 +08:00
}
2018-04-27 19:01:32 +08:00
func ( ns * NotificationService ) Run ( ctx context . Context ) error {
for {
select {
case webhook := <- ns . webhookQueue :
err := ns . sendWebRequestSync ( context . Background ( ) , webhook )
if err != nil {
ns . log . Error ( "Failed to send webrequest " , "error" , err )
}
case msg := <- ns . mailQueue :
2021-03-18 22:55:11 +08:00
num , err := ns . Send ( msg )
2018-04-27 19:01:32 +08:00
tos := strings . Join ( msg . To , "; " )
info := ""
if err != nil {
if len ( msg . Info ) > 0 {
info = ", info: " + msg . Info
}
ns . log . Error ( fmt . Sprintf ( "Async sent email %d succeed, not send emails: %s%s err: %s" , num , tos , info , err ) )
} else {
ns . log . Debug ( fmt . Sprintf ( "Async sent email %d succeed, sent emails: %s%s" , num , tos , info ) )
}
case <- ctx . Done ( ) :
return ctx . Err ( )
}
}
}
2022-03-02 00:49:49 +08:00
func ( ns * NotificationService ) GetMailer ( ) Mailer {
return ns . mailer
}
2023-01-18 03:47:31 +08:00
func ( ns * NotificationService ) SendWebhookSync ( ctx context . Context , cmd * SendWebhookSync ) error {
2018-04-27 19:01:32 +08:00
return ns . sendWebRequestSync ( ctx , & Webhook {
2017-03-24 04:53:54 +08:00
Url : cmd . Url ,
User : cmd . User ,
Password : cmd . Password ,
Body : cmd . Body ,
HttpMethod : cmd . HttpMethod ,
HttpHeader : cmd . HttpHeader ,
ContentType : cmd . ContentType ,
2022-07-15 01:15:18 +08:00
Validation : cmd . Validation ,
2016-10-03 15:38:03 +08:00
} )
}
2023-01-31 05:56:23 +08:00
// hiddenSubjectTemplateFunc sets the subject template (value) on the map represented by `.Subject.` (obj) so that it can be compiled and executed later.
// It returns a blank string, so there will be no resulting value left in place of the template.
func hiddenSubjectTemplateFunc ( obj map [ string ] interface { } , value string ) string {
2015-06-05 17:08:19 +08:00
obj [ "value" ] = value
return ""
}
2023-01-31 05:56:23 +08:00
// subjectTemplateFunc does the same thing has hiddenSubjectTemplateFunc, but in addition it executes and returns the subject template using the data represented in `.TemplateData` (data)
// This results in the template being replaced by the subject string.
func subjectTemplateFunc ( obj map [ string ] interface { } , data map [ string ] interface { } , value string ) string {
obj [ "value" ] = value
titleTmpl , err := template . New ( "title" ) . Parse ( value )
if err != nil {
return ""
}
var buf bytes . Buffer
err = titleTmpl . ExecuteTemplate ( & buf , "title" , data )
if err != nil {
return ""
}
subj := buf . String ( )
// Since we have already executed the template, save it to subject data so we don't have to do it again later on
obj [ "executed_template" ] = subj
return subj
}
2023-01-18 03:47:31 +08:00
func ( ns * NotificationService ) SendEmailCommandHandlerSync ( ctx context . Context , cmd * SendEmailCommandSync ) error {
message , err := ns . buildEmailMessage ( & SendEmailCommand {
2020-06-01 23:11:25 +08:00
Data : cmd . Data ,
Info : cmd . Info ,
Template : cmd . Template ,
To : cmd . To ,
SingleEmail : cmd . SingleEmail ,
EmbeddedFiles : cmd . EmbeddedFiles ,
2022-01-14 05:19:15 +08:00
AttachedFiles : cmd . AttachedFiles ,
2020-06-01 23:11:25 +08:00
Subject : cmd . Subject ,
2020-06-23 00:56:49 +08:00
ReplyTo : cmd . ReplyTo ,
2016-10-03 15:38:03 +08:00
} )
2015-06-05 17:08:19 +08:00
2015-09-08 16:49:35 +08:00
if err != nil {
return err
}
2021-03-18 22:55:11 +08:00
_ , err = ns . Send ( message )
2016-10-03 15:38:03 +08:00
return err
}
2015-06-05 17:08:19 +08:00
2023-01-18 03:47:31 +08:00
func ( ns * NotificationService ) SendEmailCommandHandler ( ctx context . Context , cmd * SendEmailCommand ) error {
2018-04-30 22:21:04 +08:00
message , err := ns . buildEmailMessage ( cmd )
2015-06-14 12:07:36 +08:00
if err != nil {
return err
}
2018-04-27 19:01:32 +08:00
ns . mailQueue <- message
2015-06-05 14:15:38 +08:00
return nil
2015-06-04 23:23:46 +08:00
}
2015-06-05 17:08:19 +08:00
2023-01-18 03:47:31 +08:00
func ( ns * NotificationService ) SendResetPasswordEmail ( ctx context . Context , cmd * SendResetPasswordEmailCommand ) error {
2022-08-18 22:01:09 +08:00
code , err := createUserEmailCode ( ns . Cfg , cmd . User , "" )
2019-10-22 20:08:18 +08:00
if err != nil {
return err
}
2023-01-18 03:47:31 +08:00
return ns . SendEmailCommandHandler ( ctx , & SendEmailCommand {
2015-06-08 22:51:25 +08:00
To : [ ] string { cmd . User . Email } ,
Template : tmplResetPassword ,
Data : map [ string ] interface { } {
2019-10-22 20:08:18 +08:00
"Code" : code ,
2015-06-08 22:51:25 +08:00
"Name" : cmd . User . NameOrFallback ( ) ,
} ,
} )
}
2022-06-28 20:32:25 +08:00
type GetUserByLoginFunc = func ( c context . Context , login string ) ( * user . User , error )
2022-02-03 17:33:46 +08:00
2023-01-18 03:47:31 +08:00
func ( ns * NotificationService ) ValidateResetPasswordCode ( ctx context . Context , query * ValidateResetPasswordCodeQuery , userByLogin GetUserByLoginFunc ) error {
2015-06-08 19:39:02 +08:00
login := getLoginForEmailCode ( query . Code )
if login == "" {
2023-01-18 03:47:31 +08:00
return ErrInvalidEmailCode
2015-06-08 19:39:02 +08:00
}
2015-06-05 17:08:19 +08:00
2022-02-03 17:33:46 +08:00
user , err := userByLogin ( ctx , login )
if err != nil {
2015-06-08 19:39:02 +08:00
return err
}
2015-06-08 16:57:01 +08:00
2022-02-03 17:33:46 +08:00
validEmailCode , err := validateUserEmailCode ( ns . Cfg , user , query . Code )
2019-10-22 20:08:18 +08:00
if err != nil {
return err
}
if ! validEmailCode {
2023-01-18 03:47:31 +08:00
return ErrInvalidEmailCode
2015-06-08 19:39:02 +08:00
}
2022-02-03 17:33:46 +08:00
query . Result = user
2015-06-08 19:39:02 +08:00
return nil
}
2015-06-08 23:56:56 +08:00
2021-11-29 21:23:24 +08:00
func ( ns * NotificationService ) signUpStartedHandler ( ctx context . Context , evt * events . SignUpStarted ) error {
2015-08-31 17:35:07 +08:00
if ! setting . VerifyEmailEnabled {
return nil
}
2018-04-27 19:01:32 +08:00
ns . log . Info ( "User signup started" , "email" , evt . Email )
2015-06-08 23:56:56 +08:00
2015-08-28 19:45:16 +08:00
if evt . Email == "" {
2015-06-08 23:56:56 +08:00
return nil
}
2023-01-18 03:47:31 +08:00
err := ns . SendEmailCommandHandler ( ctx , & SendEmailCommand {
2015-06-08 23:56:56 +08:00
To : [ ] string { evt . Email } ,
2015-08-28 19:45:16 +08:00
Template : tmplSignUpStarted ,
2015-06-08 23:56:56 +08:00
Data : map [ string ] interface { } {
2015-08-28 19:45:16 +08:00
"Email" : evt . Email ,
"Code" : evt . Code ,
"SignUpUrl" : setting . ToAbsUrl ( fmt . Sprintf ( "signup/?email=%s&code=%s" , url . QueryEscape ( evt . Email ) , url . QueryEscape ( evt . Code ) ) ) ,
2015-06-08 23:56:56 +08:00
} ,
} )
2018-04-27 19:01:32 +08:00
2017-07-01 02:21:05 +08:00
if err != nil {
return err
}
2023-01-06 16:02:05 +08:00
emailSentCmd := tempuser . UpdateTempUserWithEmailSentCommand { Code : evt . Code }
2022-03-22 16:04:30 +08:00
return ns . store . UpdateTempUserWithEmailSent ( ctx , & emailSentCmd )
2015-06-08 23:56:56 +08:00
}
2015-08-31 17:42:12 +08:00
2021-11-29 21:23:24 +08:00
func ( ns * NotificationService ) signUpCompletedHandler ( ctx context . Context , evt * events . SignUpCompleted ) error {
2018-04-30 22:21:04 +08:00
if evt . Email == "" || ! ns . Cfg . Smtp . SendWelcomeEmailOnSignUp {
2015-08-31 17:42:12 +08:00
return nil
}
2023-01-18 03:47:31 +08:00
return ns . SendEmailCommandHandler ( ctx , & SendEmailCommand {
2015-08-31 17:42:12 +08:00
To : [ ] string { evt . Email } ,
2015-09-01 18:35:06 +08:00
Template : tmplWelcomeOnSignUp ,
2015-08-31 17:42:12 +08:00
Data : map [ string ] interface { } {
"Name" : evt . Name ,
} ,
} )
}