2015-06-04 10:23:46 -05:00
package notifications
2015-06-05 04:08:19 -05:00
import (
2023-01-30 15:56:23 -06:00
"bytes"
2016-10-03 02:38:03 -05:00
"context"
2015-06-05 04:08:19 -05:00
"errors"
2015-08-28 06:45:16 -05:00
"fmt"
2015-06-05 04:08:19 -05:00
"html/template"
2015-08-28 06:45:16 -05:00
"net/url"
2015-06-05 04:08:19 -05:00
"path/filepath"
2018-04-27 06:01:32 -05:00
"strings"
2015-06-04 10:23:46 -05:00
2022-11-17 14:41:46 -06:00
"github.com/Masterminds/sprig/v3"
2023-01-17 13:47:31 -06:00
2015-06-05 04:08:19 -05:00
"github.com/grafana/grafana/pkg/bus"
2015-06-08 10:56:56 -05:00
"github.com/grafana/grafana/pkg/events"
2019-05-13 01:45:54 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2023-01-06 02:02:05 -06:00
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
2022-06-28 07:32:25 -05:00
"github.com/grafana/grafana/pkg/services/user"
2015-06-05 04:08:19 -05:00
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
2022-01-26 09:42:40 -06:00
type WebhookSender interface {
2023-01-17 13:47:31 -06:00
SendWebhookSync ( ctx context . Context , cmd * SendWebhookSync ) error
2022-01-26 09:42:40 -06:00
}
type EmailSender interface {
2023-01-17 13:47:31 -06:00
SendEmailCommandHandlerSync ( ctx context . Context , cmd * SendEmailCommandSync ) error
SendEmailCommandHandler ( ctx context . Context , cmd * SendEmailCommand ) error
2022-01-26 09:42:40 -06:00
}
type Service interface {
WebhookSender
EmailSender
}
2015-06-05 04:08:19 -05:00
var mailTemplates * template . Template
2021-07-19 05:31:51 -05:00
var tmplResetPassword = "reset_password"
var tmplSignUpStarted = "signup_started"
var tmplWelcomeOnSignUp = "welcome_on_signup"
2015-06-05 04:08:19 -05:00
2022-03-22 03:04:30 -05:00
func ProvideService ( bus bus . Bus , cfg * setting . Cfg , mailer Mailer , store TempUserStore ) ( * NotificationService , error ) {
2021-08-25 08:11:22 -05:00
ns := & NotificationService {
Bus : bus ,
Cfg : cfg ,
log : log . New ( "notifications" ) ,
mailQueue : make ( chan * Message , 10 ) ,
webhookQueue : make ( chan * Webhook , 10 ) ,
2022-01-13 15:19:15 -06:00
mailer : mailer ,
2022-03-22 03:04:30 -05:00
store : store ,
2021-08-25 08:11:22 -05:00
}
2015-06-05 04:08:19 -05:00
2022-01-04 02:36:01 -06:00
ns . Bus . AddEventListener ( ns . signUpStartedHandler )
ns . Bus . AddEventListener ( ns . signUpCompletedHandler )
2015-06-08 10:56:56 -05:00
2015-06-05 04:08:19 -05:00
mailTemplates = template . New ( "name" )
mailTemplates . Funcs ( template . FuncMap {
2023-01-30 15:56:23 -06:00
"Subject" : subjectTemplateFunc ,
"HiddenSubject" : hiddenSubjectTemplateFunc ,
2015-06-05 04:08:19 -05:00
} )
2022-11-17 14:41:46 -06:00
mailTemplates . Funcs ( sprig . FuncMap ( ) )
2015-06-05 04:08:19 -05:00
2023-01-13 09:24:22 -06:00
// Parse invalid templates using 'or' logic. Return an error only if no paths are valid.
invalidTemplates := make ( [ ] string , 0 )
2021-07-19 05:31:51 -05: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 09:24:22 -06:00
invalidTemplates = append ( invalidTemplates , templatePattern )
2021-07-19 05:31:51 -05:00
}
2015-06-05 04:08:19 -05:00
}
2023-01-13 09:24:22 -06: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 04:08:19 -05:00
2018-04-30 09:21:04 -05:00
if ! util . IsEmail ( ns . Cfg . Smtp . FromAddress ) {
2021-08-25 08:11:22 -05:00
return nil , errors . New ( "invalid email address for SMTP from_address config" )
2015-06-05 04:08:19 -05:00
}
2021-08-25 08:11:22 -05:00
if cfg . EmailCodeValidMinutes == 0 {
cfg . EmailCodeValidMinutes = 120
2015-06-08 03:57:01 -05:00
}
2021-08-25 08:11:22 -05:00
return ns , nil
}
2015-06-08 03:57:01 -05:00
2022-03-22 03:04:30 -05:00
type TempUserStore interface {
2023-01-06 02:02:05 -06:00
UpdateTempUserWithEmailSent ( ctx context . Context , cmd * tempuser . UpdateTempUserWithEmailSentCommand ) error
2022-03-22 03:04:30 -05:00
}
2021-08-25 08:11:22 -05:00
type NotificationService struct {
Bus bus . Bus
Cfg * setting . Cfg
mailQueue chan * Message
webhookQueue chan * Webhook
2022-01-13 15:19:15 -06:00
mailer Mailer
2021-08-25 08:11:22 -05:00
log log . Logger
2022-03-22 03:04:30 -05:00
store TempUserStore
2015-06-05 04:08:19 -05:00
}
2018-04-27 06:01:32 -05: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 09:55:11 -05:00
num , err := ns . Send ( msg )
2018-04-27 06:01:32 -05: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-01 10:49:49 -06:00
func ( ns * NotificationService ) GetMailer ( ) Mailer {
return ns . mailer
}
2023-01-17 13:47:31 -06:00
func ( ns * NotificationService ) SendWebhookSync ( ctx context . Context , cmd * SendWebhookSync ) error {
2018-04-27 06:01:32 -05:00
return ns . sendWebRequestSync ( ctx , & Webhook {
2017-03-23 15:53:54 -05:00
Url : cmd . Url ,
User : cmd . User ,
Password : cmd . Password ,
Body : cmd . Body ,
HttpMethod : cmd . HttpMethod ,
HttpHeader : cmd . HttpHeader ,
ContentType : cmd . ContentType ,
2022-07-14 12:15:18 -05:00
Validation : cmd . Validation ,
2016-10-03 02:38:03 -05:00
} )
}
2023-01-30 15:56:23 -06: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 04:08:19 -05:00
obj [ "value" ] = value
return ""
}
2023-01-30 15:56:23 -06: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-17 13:47:31 -06:00
func ( ns * NotificationService ) SendEmailCommandHandlerSync ( ctx context . Context , cmd * SendEmailCommandSync ) error {
message , err := ns . buildEmailMessage ( & SendEmailCommand {
2020-06-01 10:11:25 -05:00
Data : cmd . Data ,
Info : cmd . Info ,
Template : cmd . Template ,
To : cmd . To ,
SingleEmail : cmd . SingleEmail ,
EmbeddedFiles : cmd . EmbeddedFiles ,
2022-01-13 15:19:15 -06:00
AttachedFiles : cmd . AttachedFiles ,
2020-06-01 10:11:25 -05:00
Subject : cmd . Subject ,
2020-06-22 11:56:49 -05:00
ReplyTo : cmd . ReplyTo ,
2016-10-03 02:38:03 -05:00
} )
2015-06-05 04:08:19 -05:00
2015-09-08 03:49:35 -05:00
if err != nil {
return err
}
2021-03-18 09:55:11 -05:00
_ , err = ns . Send ( message )
2016-10-03 02:38:03 -05:00
return err
}
2015-06-05 04:08:19 -05:00
2023-01-17 13:47:31 -06:00
func ( ns * NotificationService ) SendEmailCommandHandler ( ctx context . Context , cmd * SendEmailCommand ) error {
2018-04-30 09:21:04 -05:00
message , err := ns . buildEmailMessage ( cmd )
2015-06-13 23:07:36 -05:00
if err != nil {
return err
}
2018-04-27 06:01:32 -05:00
ns . mailQueue <- message
2015-06-05 01:15:38 -05:00
return nil
2015-06-04 10:23:46 -05:00
}
2015-06-05 04:08:19 -05:00
2023-01-17 13:47:31 -06:00
func ( ns * NotificationService ) SendResetPasswordEmail ( ctx context . Context , cmd * SendResetPasswordEmailCommand ) error {
2022-08-18 09:01:09 -05:00
code , err := createUserEmailCode ( ns . Cfg , cmd . User , "" )
2019-10-22 07:08:18 -05:00
if err != nil {
return err
}
2023-01-17 13:47:31 -06:00
return ns . SendEmailCommandHandler ( ctx , & SendEmailCommand {
2015-06-08 09:51:25 -05:00
To : [ ] string { cmd . User . Email } ,
Template : tmplResetPassword ,
Data : map [ string ] interface { } {
2019-10-22 07:08:18 -05:00
"Code" : code ,
2015-06-08 09:51:25 -05:00
"Name" : cmd . User . NameOrFallback ( ) ,
} ,
} )
}
2022-06-28 07:32:25 -05:00
type GetUserByLoginFunc = func ( c context . Context , login string ) ( * user . User , error )
2022-02-03 03:33:46 -06:00
2023-01-17 13:47:31 -06:00
func ( ns * NotificationService ) ValidateResetPasswordCode ( ctx context . Context , query * ValidateResetPasswordCodeQuery , userByLogin GetUserByLoginFunc ) error {
2015-06-08 06:39:02 -05:00
login := getLoginForEmailCode ( query . Code )
if login == "" {
2023-01-17 13:47:31 -06:00
return ErrInvalidEmailCode
2015-06-08 06:39:02 -05:00
}
2015-06-05 04:08:19 -05:00
2022-02-03 03:33:46 -06:00
user , err := userByLogin ( ctx , login )
if err != nil {
2015-06-08 06:39:02 -05:00
return err
}
2015-06-08 03:57:01 -05:00
2022-02-03 03:33:46 -06:00
validEmailCode , err := validateUserEmailCode ( ns . Cfg , user , query . Code )
2019-10-22 07:08:18 -05:00
if err != nil {
return err
}
if ! validEmailCode {
2023-01-17 13:47:31 -06:00
return ErrInvalidEmailCode
2015-06-08 06:39:02 -05:00
}
2022-02-03 03:33:46 -06:00
query . Result = user
2015-06-08 06:39:02 -05:00
return nil
}
2015-06-08 10:56:56 -05:00
2021-11-29 07:23:24 -06:00
func ( ns * NotificationService ) signUpStartedHandler ( ctx context . Context , evt * events . SignUpStarted ) error {
2015-08-31 04:35:07 -05:00
if ! setting . VerifyEmailEnabled {
return nil
}
2018-04-27 06:01:32 -05:00
ns . log . Info ( "User signup started" , "email" , evt . Email )
2015-06-08 10:56:56 -05:00
2015-08-28 06:45:16 -05:00
if evt . Email == "" {
2015-06-08 10:56:56 -05:00
return nil
}
2023-01-17 13:47:31 -06:00
err := ns . SendEmailCommandHandler ( ctx , & SendEmailCommand {
2015-06-08 10:56:56 -05:00
To : [ ] string { evt . Email } ,
2015-08-28 06:45:16 -05:00
Template : tmplSignUpStarted ,
2015-06-08 10:56:56 -05:00
Data : map [ string ] interface { } {
2015-08-28 06:45:16 -05: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 10:56:56 -05:00
} ,
} )
2018-04-27 06:01:32 -05:00
2017-06-30 13:21:05 -05:00
if err != nil {
return err
}
2023-01-06 02:02:05 -06:00
emailSentCmd := tempuser . UpdateTempUserWithEmailSentCommand { Code : evt . Code }
2022-03-22 03:04:30 -05:00
return ns . store . UpdateTempUserWithEmailSent ( ctx , & emailSentCmd )
2015-06-08 10:56:56 -05:00
}
2015-08-31 04:42:12 -05:00
2021-11-29 07:23:24 -06:00
func ( ns * NotificationService ) signUpCompletedHandler ( ctx context . Context , evt * events . SignUpCompleted ) error {
2018-04-30 09:21:04 -05:00
if evt . Email == "" || ! ns . Cfg . Smtp . SendWelcomeEmailOnSignUp {
2015-08-31 04:42:12 -05:00
return nil
}
2023-01-17 13:47:31 -06:00
return ns . SendEmailCommandHandler ( ctx , & SendEmailCommand {
2015-08-31 04:42:12 -05:00
To : [ ] string { evt . Email } ,
2015-09-01 05:35:06 -05:00
Template : tmplWelcomeOnSignUp ,
2015-08-31 04:42:12 -05:00
Data : map [ string ] interface { } {
"Name" : evt . Name ,
} ,
} )
}