changeset 5377:d19fdf3d2099 extented-report

Add a string type that allows only runes that are safe of directory traversal.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Thu, 24 Jun 2021 22:13:48 +0200
parents e09e003948c7
children 0640a2f72497 80d7e38acaff
files pkg/controllers/routes.go pkg/imports/report.go pkg/models/common.go
diffstat 3 files changed, 39 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/pkg/controllers/routes.go	Thu Jun 24 19:24:21 2021 +0200
+++ b/pkg/controllers/routes.go	Thu Jun 24 22:13:48 2021 +0200
@@ -341,7 +341,7 @@
 			NoConn: true,
 		})).Methods(http.MethodGet)
 
-	api.Handle("/data/report/{name:[a-zA-Z0-9_-]+}", waterwayAdmin(
+	api.Handle("/data/report/{name:"+models.SafePathExp+"}", waterwayAdmin(
 		mw.DBConn(http.HandlerFunc(report)))).Methods(http.MethodGet)
 
 	// Handler to serve data to the client.
--- a/pkg/imports/report.go	Thu Jun 24 19:24:21 2021 +0200
+++ b/pkg/imports/report.go	Thu Jun 24 22:13:48 2021 +0200
@@ -22,7 +22,6 @@
 	"log"
 	"os"
 	"path/filepath"
-	"regexp"
 	"strings"
 	"text/template"
 	"time"
@@ -39,7 +38,7 @@
 
 type Report struct {
 	models.QueueConfigurationType
-	Name string `json:"name"`
+	Name models.SafePath `json:"name"`
 }
 
 const ReportJobKind JobKind = "report"
@@ -88,7 +87,7 @@
 // RequiresRoles enforces to be a sys_admin to run this .
 func (*Report) RequiresRoles() auth.Roles { return auth.Roles{"sys_admin"} }
 
-func (r *Report) Description() (string, error) { return r.Name, nil }
+func (r *Report) Description() (string, error) { return string(r.Name), nil }
 
 func (*Report) CleanUp() error { return nil }
 
@@ -96,7 +95,7 @@
 	if err := r.QueueConfigurationType.MarshalAttributes(attrs); err != nil {
 		return err
 	}
-	attrs.Set("name", r.Name)
+	attrs.Set("name", string(r.Name))
 	return nil
 }
 
@@ -108,7 +107,10 @@
 	if !found {
 		return errors.New("missing 'name' attribute")
 	}
-	r.Name = name
+	r.Name = models.SafePath(name)
+	if !r.Name.Valid() {
+		return fmt.Errorf("'%s' is not a safe path", name)
+	}
 	return nil
 }
 
@@ -127,13 +129,8 @@
 		return nil, nil, fmt.Errorf("report dir '%s' is not a directory", path)
 	}
 
-	// TODO: Prevent this earlier.
-	if match, _ := regexp.MatchString(`^[a-zA-Z0-9_-]+$`, r.Name); !match {
-		return nil, nil, errors.New("invalid report name")
-	}
-
-	xlsxFilename := filepath.Join(path, r.Name+".xlsx")
-	yamlFilename := filepath.Join(path, r.Name+".yaml")
+	xlsxFilename := filepath.Join(path, string(r.Name)+".xlsx")
+	yamlFilename := filepath.Join(path, string(r.Name)+".yaml")
 
 	for _, check := range []string{xlsxFilename, yamlFilename} {
 		if _, err := os.Stat(check); err != nil {
@@ -230,7 +227,7 @@
 
 	now := start.UTC().Format("2006-01-02")
 
-	attached := r.Name + "-" + now + ".xlsx"
+	attached := string(r.Name) + "-" + now + ".xlsx"
 
 	body := func(u misc.EmailReceiver) (string, error) {
 		fill := struct {
@@ -243,7 +240,7 @@
 		}{
 			Receiver:   u.Name,
 			Attachment: attached,
-			Report:     r.Name,
+			Report:     string(r.Name),
 			When:       now,
 			Admin:      admin.Name,
 			AdminEmail: admin.Address,
@@ -264,7 +261,7 @@
 
 	if err := misc.SendMailToAll(
 		users,
-		"Report "+r.Name+" from "+now,
+		"Report "+string(r.Name)+" from "+now,
 		body,
 		[]misc.EmailAttachment{{
 			Name:    attached,
--- a/pkg/models/common.go	Thu Jun 24 19:24:21 2021 +0200
+++ b/pkg/models/common.go	Thu Jun 24 22:13:48 2021 +0200
@@ -18,6 +18,7 @@
 	"encoding/json"
 	"errors"
 	"fmt"
+	"regexp"
 	"strings"
 	"time"
 
@@ -40,6 +41,9 @@
 	Country string
 	// UniqueCountries is a list of unique countries.
 	UniqueCountries []Country
+
+	// SafePath should only contain chars that directory traversal safe.
+	SafePath string
 )
 
 func (d Date) MarshalJSON() ([]byte, error) {
@@ -149,3 +153,25 @@
 	}
 	return b.String()
 }
+
+const SafePathExp = "[a-zA-Z0-9_-]+"
+
+var safePathRegExp = regexp.MustCompile("^" + SafePathExp + "$")
+
+func (sp SafePath) Valid() bool {
+	return safePathRegExp.MatchString(string(sp))
+}
+
+// UnmarshalJSON ensures that the given string only consist
+// of runes that are directory traversal safe.
+func (sp *SafePath) UnmarshalJSON(data []byte) error {
+	var s string
+	if err := json.Unmarshal(data, &s); err != nil {
+		return err
+	}
+	if c := SafePath(s); c.Valid() {
+		*sp = c
+		return nil
+	}
+	return fmt.Errorf("'%s' is not a safe path", s)
+}