From 17e0acc527cfbb703d9d44b776138da23b217ca4 Mon Sep 17 00:00:00 2001 From: Alano Terblanche Date: Wed, 9 Feb 2022 20:13:34 +0100 Subject: [PATCH] fix: remove non-hermetic sprig functions (#2201) Closes https://github.com/ory/kratos/issues/2087 --- .../test_stub/email.body.sprig.gotmpl | 2 +- courier/template/load_template.go | 8 +-- courier/template/load_template_test.go | 17 +++++ x/stub_fs.go | 65 +++++++++++++++++++ 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 x/stub_fs.go diff --git a/courier/template/courier/builtin/templates/test_stub/email.body.sprig.gotmpl b/courier/template/courier/builtin/templates/test_stub/email.body.sprig.gotmpl index 0a01094b77d9..700a1f9e9e74 100644 --- a/courier/template/courier/builtin/templates/test_stub/email.body.sprig.gotmpl +++ b/courier/template/courier/builtin/templates/test_stub/email.body.sprig.gotmpl @@ -1,5 +1,5 @@ {{ $t1 := title .input }} {{ $t1 := nospace $t1 }} -{{ $t2 := upper $t1 }} +{{ $t2 := upper $t1 }} {{ $t3 := cat $t1 "," $t2 }} {{ nospace $t3 }} diff --git a/courier/template/load_template.go b/courier/template/load_template.go index 242e67803ccc..ceceb43e1960 100644 --- a/courier/template/load_template.go +++ b/courier/template/load_template.go @@ -94,12 +94,12 @@ func loadRemoteTemplate(ctx context.Context, d templateDependencies, url string, var t Template if html { - t, err = htemplate.New(url).Funcs(sprig.HtmlFuncMap()).Parse(string(b)) + t, err = htemplate.New(url).Funcs(sprig.HermeticHtmlFuncMap()).Parse(string(b)) if err != nil { return nil, errors.WithStack(err) } } else { - t, err = template.New(url).Funcs(sprig.TxtFuncMap()).Parse(string(b)) + t, err = template.New(url).Funcs(sprig.HermeticTxtFuncMap()).Parse(string(b)) if err != nil { return nil, errors.WithStack(err) } @@ -132,13 +132,13 @@ func loadTemplate(filesystem fs.FS, name, pattern string, html bool) (Template, var tpl Template if html { - t, err := htemplate.New(filepath.Base(name)).Funcs(sprig.HtmlFuncMap()).ParseFS(filesystem, glob) + t, err := htemplate.New(filepath.Base(name)).Funcs(sprig.HermeticHtmlFuncMap()).ParseFS(filesystem, glob) if err != nil { return nil, errors.WithStack(err) } tpl = t } else { - t, err := template.New(filepath.Base(name)).Funcs(sprig.TxtFuncMap()).ParseFS(filesystem, glob) + t, err := template.New(filepath.Base(name)).Funcs(sprig.HermeticTxtFuncMap()).ParseFS(filesystem, glob) if err != nil { return nil, errors.WithStack(err) } diff --git a/courier/template/load_template_test.go b/courier/template/load_template_test.go index 354dbd7e9793..b27ac0f21a01 100644 --- a/courier/template/load_template_test.go +++ b/courier/template/load_template_test.go @@ -3,6 +3,7 @@ package template_test import ( "context" "encoding/base64" + "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -60,6 +61,22 @@ func TestLoadTextTemplate(t *testing.T) { assert.Contains(t, actual, "HelloWorld,HELLOWORLD") }) + t.Run("method=sprig should not support non-hermetic", func(t *testing.T) { + template.Cache, _ = lru.New(16) + ctx := context.Background() + _, reg := internal.NewFastRegistryWithMocks(t) + + nonhermetic := []string{"date", "date_in_zone", "date_modify", "now", "htmlDate", "htmlDateInZone", "dateInZone", "dateModify", "env", "expandenv", "getHostByName", "uuidv4", "randNumeric", "randAscii", "randAlpha", "randAlphaNum"} + + for _, tc := range nonhermetic { + t.Run("case=should not support function: "+tc, func(t *testing.T) { + _, err := template.LoadTextTemplate(ctx, reg, x.NewStubFS(tc, []byte(fmt.Sprintf("{{ %s }}", tc))), tc, "", map[string]interface{}{}, "") + require.Error(t, err) + require.Contains(t, err.Error(), fmt.Sprintf("function \"%s\" not defined", tc)) + }) + } + }) + t.Run("method=html with nested templates", func(t *testing.T) { template.Cache, _ = lru.New(16) // prevent Cache hit m := map[string]interface{}{"lang": "en_US"} // create a simple model diff --git a/x/stub_fs.go b/x/stub_fs.go new file mode 100644 index 000000000000..3a39de875582 --- /dev/null +++ b/x/stub_fs.go @@ -0,0 +1,65 @@ +package x + +import ( + "io" + "io/fs" + "time" +) + +type StubFS struct { + name string + data []byte + offset int +} + +func NewStubFS(name string, data []byte) fs.FS { + return &StubFS{ + name: name, + data: data, + } +} + +func (stub *StubFS) Mode() fs.FileMode { + return 0444 +} + +func (stub *StubFS) ModTime() time.Time { + return time.Time{} +} + +func (stub *StubFS) IsDir() bool { + return false +} + +func (stub *StubFS) Sys() interface{} { + return nil +} + +func (stub *StubFS) Stat() (fs.FileInfo, error) { + return stub, nil +} + +func (stub *StubFS) Read(bytes []byte) (int, error) { + if stub.offset >= len(stub.data) { + return 0, io.EOF + } + n := copy(bytes, stub.data[stub.offset:]) + stub.offset += n + return n, nil +} + +func (stub *StubFS) Close() error { + return nil +} + +func (stub *StubFS) Open(name string) (fs.File, error) { + return stub, nil +} + +func (stub *StubFS) Name() string { + return stub.name +} + +func (stub *StubFS) Size() int64 { + return int64(len(stub.data)) +}