1// Copyright 2019 The Hugo Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package hugolib
15
16import (
17	"fmt"
18	"net/http"
19	"net/http/httptest"
20	"runtime"
21	"testing"
22
23	qt "github.com/frankban/quicktest"
24	"github.com/gohugoio/hugo/markup/asciidocext"
25	"github.com/gohugoio/hugo/markup/pandoc"
26	"github.com/gohugoio/hugo/markup/rst"
27	"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
28)
29
30func TestSecurityPolicies(t *testing.T) {
31	c := qt.New(t)
32
33	testVariant := func(c *qt.C, withBuilder func(b *sitesBuilder), expectErr string) {
34		c.Helper()
35		b := newTestSitesBuilder(c)
36		withBuilder(b)
37
38		if expectErr != "" {
39			err := b.BuildE(BuildCfg{})
40			b.Assert(err, qt.IsNotNil)
41			b.Assert(err, qt.ErrorMatches, expectErr)
42		} else {
43			b.Build(BuildCfg{})
44		}
45
46	}
47
48	httpTestVariant := func(c *qt.C, templ, expectErr string, withBuilder func(b *sitesBuilder)) {
49		ts := httptest.NewServer(http.FileServer(http.Dir("testdata/")))
50		c.Cleanup(func() {
51			ts.Close()
52		})
53		cb := func(b *sitesBuilder) {
54			b.WithTemplatesAdded("index.html", fmt.Sprintf(templ, ts.URL))
55			if withBuilder != nil {
56				withBuilder(b)
57			}
58		}
59		testVariant(c, cb, expectErr)
60	}
61
62	c.Run("os.GetEnv, denied", func(c *qt.C) {
63		c.Parallel()
64		cb := func(b *sitesBuilder) {
65			b.WithTemplatesAdded("index.html", `{{ os.Getenv "FOOBAR" }}`)
66		}
67		testVariant(c, cb, `(?s).*"FOOBAR" is not whitelisted in policy "security\.funcs\.getenv".*`)
68	})
69
70	c.Run("os.GetEnv, OK", func(c *qt.C) {
71		c.Parallel()
72		cb := func(b *sitesBuilder) {
73			b.WithTemplatesAdded("index.html", `{{ os.Getenv "HUGO_FOO" }}`)
74		}
75		testVariant(c, cb, "")
76	})
77
78	c.Run("Asciidoc, denied", func(c *qt.C) {
79		c.Parallel()
80		if !asciidocext.Supports() {
81			c.Skip()
82		}
83
84		cb := func(b *sitesBuilder) {
85			b.WithContent("page.ad", "foo")
86		}
87
88		testVariant(c, cb, `(?s).*"asciidoctor" is not whitelisted in policy "security\.exec\.allow".*`)
89	})
90
91	c.Run("RST, denied", func(c *qt.C) {
92		c.Parallel()
93		if !rst.Supports() {
94			c.Skip()
95		}
96
97		cb := func(b *sitesBuilder) {
98			b.WithContent("page.rst", "foo")
99		}
100
101		if runtime.GOOS == "windows" {
102			testVariant(c, cb, `(?s).*python(\.exe)?" is not whitelisted in policy "security\.exec\.allow".*`)
103		} else {
104			testVariant(c, cb, `(?s).*"rst2html(\.py)?" is not whitelisted in policy "security\.exec\.allow".*`)
105
106		}
107
108	})
109
110	c.Run("Pandoc, denied", func(c *qt.C) {
111		c.Parallel()
112		if !pandoc.Supports() {
113			c.Skip()
114		}
115
116		cb := func(b *sitesBuilder) {
117			b.WithContent("page.pdc", "foo")
118		}
119
120		testVariant(c, cb, `"(?s).*pandoc" is not whitelisted in policy "security\.exec\.allow".*`)
121	})
122
123	c.Run("Dart SASS, OK", func(c *qt.C) {
124		c.Parallel()
125		if !dartsass.Supports() {
126			c.Skip()
127		}
128		cb := func(b *sitesBuilder) {
129			b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss"  | resources.ToCSS (dict "transpiler" "dartsass") }}`)
130		}
131		testVariant(c, cb, "")
132	})
133
134	c.Run("Dart SASS, denied", func(c *qt.C) {
135		c.Parallel()
136		if !dartsass.Supports() {
137			c.Skip()
138		}
139		cb := func(b *sitesBuilder) {
140			b.WithConfigFile("toml", `
141			[security]
142			[security.exec]
143			allow="none"
144
145			`)
146			b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss"  | resources.ToCSS (dict "transpiler" "dartsass") }}`)
147		}
148		testVariant(c, cb, `(?s).*"dart-sass-embedded" is not whitelisted in policy "security\.exec\.allow".*`)
149	})
150
151	c.Run("resources.GetRemote, OK", func(c *qt.C) {
152		c.Parallel()
153		httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fruits.json" }}{{ $json.Content }}`, "", nil)
154	})
155
156	c.Run("resources.GetRemote, denied method", func(c *qt.C) {
157		c.Parallel()
158		httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fruits.json" (dict "method" "DELETE" ) }}{{ $json.Content }}`, `(?s).*"DELETE" is not whitelisted in policy "security\.http\.method".*`, nil)
159	})
160
161	c.Run("resources.GetRemote, denied URL", func(c *qt.C) {
162		c.Parallel()
163		httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fruits.json" }}{{ $json.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`,
164			func(b *sitesBuilder) {
165				b.WithConfigFile("toml", `
166[security]
167[security.http]
168urls="none"
169`)
170			})
171	})
172
173	c.Run("getJSON, OK", func(c *qt.C) {
174		c.Parallel()
175		httpTestVariant(c, `{{ $json := getJSON "%[1]s/fruits.json" }}{{ $json.Content }}`, "", nil)
176	})
177
178	c.Run("getJSON, denied URL", func(c *qt.C) {
179		c.Parallel()
180		httpTestVariant(c, `{{ $json := getJSON "%[1]s/fruits.json" }}{{ $json.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`,
181			func(b *sitesBuilder) {
182				b.WithConfigFile("toml", `
183[security]
184[security.http]
185urls="none"
186`)
187			})
188	})
189
190	c.Run("getCSV, denied URL", func(c *qt.C) {
191		c.Parallel()
192		httpTestVariant(c, `{{ $d := getCSV ";" "%[1]s/cities.csv" }}{{ $d.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`,
193			func(b *sitesBuilder) {
194				b.WithConfigFile("toml", `
195[security]
196[security.http]
197urls="none"
198`)
199			})
200	})
201
202}
203