Config: Can add static headers to email messages (#79365)

* Can add allowed custom headers to an email Message. WIP.

* adds slug as a custom email header to all outgoing emails

* Headers are static - declared as key/value pairs in config. All static headers get added to emails.

* updates comment

* adds tests for parsing smtp static headers

* updates test to assert static headers are included when building email

* updates test to use multiple static headers

* updates test names

* fixes linting issue with error

* ignore gocyclo for loading config

* updates email headers in tests to be formatted properly

* add static headers first

* updates tests to assert that regular headers like From cant be overwritten

* ensures only the header is in a valid format for smtp and not the value

* updates comment and error message wording

* adds to docs and ini sample files

* updates smtp.static_headers docs examples formatting

* removes lines commented with semi colons

* prettier:write

* renames var
This commit is contained in:
owensmallwood
2023-12-14 12:59:43 -06:00
committed by GitHub
parent 8bcd40a186
commit d5b9602a79
8 changed files with 160 additions and 4 deletions
+4 -1
View File
@@ -1026,6 +1026,7 @@ func (cfg *Cfg) validateStaticRootPath() error {
return nil
}
// nolint:gocyclo
func (cfg *Cfg) Load(args CommandLineArgs) error {
cfg.setHomePath(args)
@@ -1197,7 +1198,9 @@ func (cfg *Cfg) Load(args CommandLineArgs) error {
cfg.handleAWSConfig()
cfg.readAzureSettings()
cfg.readSessionConfig()
cfg.readSmtpSettings()
if err := cfg.readSmtpSettings(); err != nil {
return err
}
if err := cfg.readAnnotationSettings(); err != nil {
return err
}
+38 -2
View File
@@ -1,6 +1,11 @@
package setting
import "github.com/grafana/grafana/pkg/util"
import (
"fmt"
"regexp"
"github.com/grafana/grafana/pkg/util"
)
type SmtpSettings struct {
Enabled bool
@@ -14,13 +19,17 @@ type SmtpSettings struct {
EhloIdentity string
StartTLSPolicy string
SkipVerify bool
StaticHeaders map[string]string
SendWelcomeEmailOnSignUp bool
TemplatesPatterns []string
ContentTypes []string
}
func (cfg *Cfg) readSmtpSettings() {
// validates mail headers
var mailHeaderRegex = regexp.MustCompile(`^[A-Z][A-Za-z0-9]*(-[A-Z][A-Za-z0-9]*)*$`)
func (cfg *Cfg) readSmtpSettings() error {
sec := cfg.Raw.Section("smtp")
cfg.Smtp.Enabled = sec.Key("enabled").MustBool(false)
cfg.Smtp.Host = sec.Key("host").String()
@@ -38,4 +47,31 @@ func (cfg *Cfg) readSmtpSettings() {
cfg.Smtp.SendWelcomeEmailOnSignUp = emails.Key("welcome_email_on_sign_up").MustBool(false)
cfg.Smtp.TemplatesPatterns = util.SplitString(emails.Key("templates_pattern").MustString("emails/*.html, emails/*.txt"))
cfg.Smtp.ContentTypes = util.SplitString(emails.Key("content_types").MustString("text/html"))
// populate static headers
if err := cfg.readGrafanaSmtpStaticHeaders(); err != nil {
return err
}
return nil
}
func validHeader(header string) bool {
return mailHeaderRegex.MatchString(header)
}
func (cfg *Cfg) readGrafanaSmtpStaticHeaders() error {
staticHeadersSection := cfg.Raw.Section("smtp.static_headers")
keys := staticHeadersSection.Keys()
cfg.Smtp.StaticHeaders = make(map[string]string, len(keys))
for _, key := range keys {
if !validHeader(key.Name()) {
return fmt.Errorf("header %q in [smtp.static_headers] configuration: must follow canonical MIME form", key.Name())
}
cfg.Smtp.StaticHeaders[key.Name()] = key.Value()
}
return nil
}
+93
View File
@@ -0,0 +1,93 @@
package setting
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/ini.v1"
)
func TestLoadSmtpStaticHeaders(t *testing.T) {
t.Run("will load valid headers", func(t *testing.T) {
f := ini.Empty()
cfg := NewCfg()
s, err := f.NewSection("smtp.static_headers")
require.NoError(t, err)
cfg.Raw = f
_, err = s.NewKey("Foo-Header", "foo_val")
require.NoError(t, err)
_, err = s.NewKey("Bar", "bar_val")
require.NoError(t, err)
err = cfg.readGrafanaSmtpStaticHeaders()
require.NoError(t, err)
assert.Equal(t, "foo_val", cfg.Smtp.StaticHeaders["Foo-Header"])
assert.Equal(t, "bar_val", cfg.Smtp.StaticHeaders["Bar"])
})
t.Run("will load no static headers into smtp config when section is defined but has no keys", func(t *testing.T) {
f := ini.Empty()
cfg := NewCfg()
_, err := f.NewSection("smtp.static_headers")
require.NoError(t, err)
cfg.Raw = f
err = cfg.readGrafanaSmtpStaticHeaders()
require.NoError(t, err)
assert.Empty(t, cfg.Smtp.StaticHeaders)
})
t.Run("will load no static headers into smtp config when section is not defined", func(t *testing.T) {
f := ini.Empty()
cfg := NewCfg()
cfg.Raw = f
err := cfg.readGrafanaSmtpStaticHeaders()
require.NoError(t, err)
assert.Empty(t, cfg.Smtp.StaticHeaders)
})
t.Run("will return error when header label is not in valid format", func(t *testing.T) {
f := ini.Empty()
cfg := NewCfg()
s, err := f.NewSection("smtp.static_headers")
require.NoError(t, err)
_, err = s.NewKey("header with spaces", "value")
require.NoError(t, err)
cfg.Raw = f
err = cfg.readGrafanaSmtpStaticHeaders()
require.Error(t, err)
})
}
func TestSmtpHeaderValidation(t *testing.T) {
testCases := []struct {
input string
expected bool
}{
//valid
{"Foo", true},
{"Foo-Bar", true},
{"Foo123-Bar123", true},
//invalid
{"foo", false},
{"Foo Bar", false},
{"123Foo", false},
{"Foo.Bar", false},
{"foo-bar", false},
{"foo-Bar", false},
{"Foo-bar", false},
{"-Bar", false},
{"Foo--", false},
}
for _, tc := range testCases {
assert.Equal(t, validHeader(tc.input), tc.expected)
}
}