2015-06-04 17:23:46 +02:00
package notifications
2015-06-05 11:08:19 +02:00
import (
2023-01-30 16:56:23 -05:00
"bytes"
2016-10-03 09:38:03 +02:00
"context"
2015-06-05 11:08:19 +02:00
"errors"
2015-08-28 13:45:16 +02:00
"fmt"
2015-06-05 11:08:19 +02:00
"html/template"
2015-08-28 13:45:16 +02:00
"net/url"
2015-06-05 11:08:19 +02:00
"path/filepath"
2018-04-27 13:01:32 +02:00
"strings"
2015-06-04 17:23:46 +02:00
2022-11-17 21:41:46 +01:00
"github.com/Masterminds/sprig/v3"
2023-01-17 14:47:31 -05:00
2015-06-05 11:08:19 +02:00
"github.com/grafana/grafana/pkg/bus"
2015-06-08 17:56:56 +02: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 09:02:05 +01:00
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
2022-06-28 14:32:25 +02:00
"github.com/grafana/grafana/pkg/services/user"
2015-06-05 11:08:19 +02:00
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
2022-01-26 16:42:40 +01:00
type WebhookSender interface {
2023-01-17 14:47:31 -05:00
SendWebhookSync ( ctx context . Context , cmd * SendWebhookSync ) error
2022-01-26 16:42:40 +01:00
}
type EmailSender interface {
2023-01-17 14:47:31 -05:00
SendEmailCommandHandlerSync ( ctx context . Context , cmd * SendEmailCommandSync ) error
SendEmailCommandHandler ( ctx context . Context , cmd * SendEmailCommand ) error
2022-01-26 16:42:40 +01:00
}
type Service interface {
WebhookSender
EmailSender
}
2015-06-05 11:08:19 +02:00
var mailTemplates * template . Template
2021-07-19 12:31:51 +02:00
var tmplResetPassword = "reset_password"
var tmplSignUpStarted = "signup_started"
var tmplWelcomeOnSignUp = "welcome_on_signup"
2015-06-05 11:08:19 +02:00
2022-03-22 09:04:30 +01:00
func ProvideService ( bus bus . Bus , cfg * setting . Cfg , mailer Mailer , store TempUserStore ) ( * NotificationService , error ) {
2021-08-25 15:11:22 +02: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 09:04:30 +01:00
store : store ,
2021-08-25 15:11:22 +02:00
}
2015-06-05 11:08:19 +02:00
2022-01-04 09:36:01 +01:00
ns . Bus . AddEventListener ( ns . signUpStartedHandler )
ns . Bus . AddEventListener ( ns . signUpCompletedHandler )
2015-06-08 17:56:56 +02:00
2015-06-05 11:08:19 +02:00
mailTemplates = template . New ( "name" )
mailTemplates . Funcs ( template . FuncMap {
2023-03-28 13:05:21 +02:00
"Subject" : subjectTemplateFunc ,
"HiddenSubject" : hiddenSubjectTemplateFunc ,
"__dangerouslyInjectHTML" : __dangerouslyInjectHTML ,
2015-06-05 11:08:19 +02:00
} )
2022-11-17 21:41:46 +01:00
mailTemplates . Funcs ( sprig . FuncMap ( ) )
2015-06-05 11:08:19 +02:00
2023-01-13 10:24:22 -05:00
// Parse invalid templates using 'or' logic. Return an error only if no paths are valid.
invalidTemplates := make ( [ ] string , 0 )
2021-07-19 12:31:51 +02: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 10:24:22 -05:00
invalidTemplates = append ( invalidTemplates , templatePattern )
2021-07-19 12:31:51 +02:00
}
2015-06-05 11:08:19 +02:00
}
2023-01-13 10:24:22 -05: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 11:08:19 +02:00
2018-04-30 16:21:04 +02:00
if ! util . IsEmail ( ns . Cfg . Smtp . FromAddress ) {
2021-08-25 15:11:22 +02:00
return nil , errors . New ( "invalid email address for SMTP from_address config" )
2015-06-05 11:08:19 +02:00
}
2021-08-25 15:11:22 +02:00
if cfg . EmailCodeValidMinutes == 0 {
cfg . EmailCodeValidMinutes = 120
2015-06-08 10:57:01 +02:00
}
2021-08-25 15:11:22 +02:00
return ns , nil
}
2015-06-08 10:57:01 +02:00
2022-03-22 09:04:30 +01:00
type TempUserStore interface {
2023-01-06 09:02:05 +01:00
UpdateTempUserWithEmailSent ( ctx context . Context , cmd * tempuser . UpdateTempUserWithEmailSentCommand ) error
2022-03-22 09:04:30 +01:00
}
2021-08-25 15:11:22 +02: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 15:11:22 +02:00
log log . Logger
2022-03-22 09:04:30 +01:00
store TempUserStore
2015-06-05 11:08:19 +02:00
}
2018-04-27 13:01:32 +02: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 20:25:11 +05:30
num , err := ns . Send ( msg )
2018-04-27 13:01:32 +02: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 14:47:31 -05:00
func ( ns * NotificationService ) SendWebhookSync ( ctx context . Context , cmd * SendWebhookSync ) error {
2018-04-27 13:01:32 +02:00
return ns . sendWebRequestSync ( ctx , & Webhook {
2017-03-23 21:53:54 +01: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 13:15:18 -04:00
Validation : cmd . Validation ,
2016-10-03 09:38:03 +02:00
} )
}
2023-01-30 16:56:23 -05: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 11:08:19 +02:00
obj [ "value" ] = value
return ""
}
2023-01-30 16:56:23 -05: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-03-28 13:05:21 +02:00
// __dangerouslyInjectHTML allows marking areas of am email template as HTML safe, this will _not_ sanitize the string and will allow HTML snippets to be rendered verbatim.
// Use with absolute care as this _could_ allow for XSS attacks when used in an insecure context.
//
// It's safe to ignore gosec warning G203 when calling this function in an HTML template because we assume anyone who has write access
// to the email templates folder is an administrator.
//
// nolint:gosec
func __dangerouslyInjectHTML ( s string ) template . HTML {
return template . HTML ( s )
}
2023-01-17 14:47:31 -05:00
func ( ns * NotificationService ) SendEmailCommandHandlerSync ( ctx context . Context , cmd * SendEmailCommandSync ) error {
message , err := ns . buildEmailMessage ( & SendEmailCommand {
2020-06-01 17:11:25 +02: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 17:11:25 +02:00
Subject : cmd . Subject ,
2020-06-22 18:56:49 +02:00
ReplyTo : cmd . ReplyTo ,
2016-10-03 09:38:03 +02:00
} )
2015-06-05 11:08:19 +02:00
2015-09-08 10:49:35 +02:00
if err != nil {
return err
}
2021-03-18 20:25:11 +05:30
_ , err = ns . Send ( message )
2016-10-03 09:38:03 +02:00
return err
}
2015-06-05 11:08:19 +02:00
2023-01-17 14:47:31 -05:00
func ( ns * NotificationService ) SendEmailCommandHandler ( ctx context . Context , cmd * SendEmailCommand ) error {
2018-04-30 16:21:04 +02:00
message , err := ns . buildEmailMessage ( cmd )
2015-06-14 06:07:36 +02:00
if err != nil {
return err
}
2018-04-27 13:01:32 +02:00
ns . mailQueue <- message
2015-06-05 08:15:38 +02:00
return nil
2015-06-04 17:23:46 +02:00
}
2015-06-05 11:08:19 +02:00
2023-01-17 14:47:31 -05:00
func ( ns * NotificationService ) SendResetPasswordEmail ( ctx context . Context , cmd * SendResetPasswordEmailCommand ) error {
2022-08-18 16:01:09 +02:00
code , err := createUserEmailCode ( ns . Cfg , cmd . User , "" )
2019-10-22 14:08:18 +02:00
if err != nil {
return err
}
2023-01-17 14:47:31 -05:00
return ns . SendEmailCommandHandler ( ctx , & SendEmailCommand {
2015-06-08 16:51:25 +02:00
To : [ ] string { cmd . User . Email } ,
Template : tmplResetPassword ,
Data : map [ string ] interface { } {
2019-10-22 14:08:18 +02:00
"Code" : code ,
2015-06-08 16:51:25 +02:00
"Name" : cmd . User . NameOrFallback ( ) ,
} ,
} )
}
2022-06-28 14:32:25 +02:00
type GetUserByLoginFunc = func ( c context . Context , login string ) ( * user . User , error )
2022-02-03 04:33:46 -05:00
2023-03-28 13:44:15 +02:00
func ( ns * NotificationService ) ValidateResetPasswordCode ( ctx context . Context , query * ValidateResetPasswordCodeQuery , userByLogin GetUserByLoginFunc ) ( * user . User , error ) {
2015-06-08 13:39:02 +02:00
login := getLoginForEmailCode ( query . Code )
if login == "" {
2023-03-28 13:44:15 +02:00
return nil , ErrInvalidEmailCode
2015-06-08 13:39:02 +02:00
}
2015-06-05 11:08:19 +02:00
2022-02-03 04:33:46 -05:00
user , err := userByLogin ( ctx , login )
if err != nil {
2023-03-28 13:44:15 +02:00
return nil , err
2015-06-08 13:39:02 +02:00
}
2015-06-08 10:57:01 +02:00
2022-02-03 04:33:46 -05:00
validEmailCode , err := validateUserEmailCode ( ns . Cfg , user , query . Code )
2019-10-22 14:08:18 +02:00
if err != nil {
2023-03-28 13:44:15 +02:00
return nil , err
2019-10-22 14:08:18 +02:00
}
if ! validEmailCode {
2023-03-28 13:44:15 +02:00
return nil , ErrInvalidEmailCode
2015-06-08 13:39:02 +02:00
}
2023-03-28 13:44:15 +02:00
return user , nil
2015-06-08 13:39:02 +02:00
}
2015-06-08 17:56:56 +02:00
2021-11-29 14:23:24 +01:00
func ( ns * NotificationService ) signUpStartedHandler ( ctx context . Context , evt * events . SignUpStarted ) error {
2015-08-31 11:35:07 +02:00
if ! setting . VerifyEmailEnabled {
return nil
}
2018-04-27 13:01:32 +02:00
ns . log . Info ( "User signup started" , "email" , evt . Email )
2015-06-08 17:56:56 +02:00
2015-08-28 13:45:16 +02:00
if evt . Email == "" {
2015-06-08 17:56:56 +02:00
return nil
}
2023-01-17 14:47:31 -05:00
err := ns . SendEmailCommandHandler ( ctx , & SendEmailCommand {
2015-06-08 17:56:56 +02:00
To : [ ] string { evt . Email } ,
2015-08-28 13:45:16 +02:00
Template : tmplSignUpStarted ,
2015-06-08 17:56:56 +02:00
Data : map [ string ] interface { } {
2015-08-28 13:45:16 +02: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 17:56:56 +02:00
} ,
} )
2018-04-27 13:01:32 +02:00
2017-06-30 20:21:05 +02:00
if err != nil {
return err
}
2023-01-06 09:02:05 +01:00
emailSentCmd := tempuser . UpdateTempUserWithEmailSentCommand { Code : evt . Code }
2022-03-22 09:04:30 +01:00
return ns . store . UpdateTempUserWithEmailSent ( ctx , & emailSentCmd )
2015-06-08 17:56:56 +02:00
}
2015-08-31 11:42:12 +02:00
2021-11-29 14:23:24 +01:00
func ( ns * NotificationService ) signUpCompletedHandler ( ctx context . Context , evt * events . SignUpCompleted ) error {
2018-04-30 16:21:04 +02:00
if evt . Email == "" || ! ns . Cfg . Smtp . SendWelcomeEmailOnSignUp {
2015-08-31 11:42:12 +02:00
return nil
}
2023-01-17 14:47:31 -05:00
return ns . SendEmailCommandHandler ( ctx , & SendEmailCommand {
2015-08-31 11:42:12 +02:00
To : [ ] string { evt . Email } ,
2015-09-01 12:35:06 +02:00
Template : tmplWelcomeOnSignUp ,
2015-08-31 11:42:12 +02:00
Data : map [ string ] interface { } {
"Name" : evt . Name ,
} ,
} )
}