fix(17): T-17-15 refuse an unsafe frontend_url on save and import
- Settings.BeforeValidate answers a cabana validation error on frontend_url, so the settings save is a 422 and the row is unchanged - sitemap:import-winter stops on a frontend_url that models.AllowedFrontendURL refuses, before the dry-run branch and before any database use; the value is printed quoted - errors.frontend_url phrase in en and pl - TestSettingsBeforeValidate, TestWinterSettingsFrontendURL and the save and import refusals in the Postgres smokes
This commit is contained in:
@@ -5,8 +5,14 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// errFrontendURL is the phrase key of the frontend_url refusal.
|
||||
const errFrontendURL = "golem15.sitemap::lang.errors.frontend_url"
|
||||
|
||||
// The settings keys of the five priority tiers, in the order PHP checked
|
||||
// them. A tier rule names one of them.
|
||||
const (
|
||||
@@ -34,6 +40,8 @@ var PriorityDefaults = map[string]string{
|
||||
// UpdatesManifestPath names a JSON file of URL path to last-modified date;
|
||||
// EnablePriorityTiers turns the tier priorities on, and the priority fields
|
||||
// hold each tier's value as text. A null priority reads as its PHP default.
|
||||
// The PHP settings form validated nothing; the port checks FrontendURL in
|
||||
// BeforeValidate.
|
||||
type Settings struct {
|
||||
ID uint `gorm:"column:id;primaryKey"`
|
||||
FrontendURL *string `gorm:"column:frontend_url"`
|
||||
@@ -58,9 +66,21 @@ func (Settings) Fillable() []string {
|
||||
}
|
||||
}
|
||||
|
||||
// Rules is empty: the PHP settings form validated nothing.
|
||||
// Rules is empty: the PHP settings form validated nothing. The one check of
|
||||
// the port, on frontend_url, is in BeforeValidate.
|
||||
func (Settings) Rules() map[string]string { return map[string]string{} }
|
||||
|
||||
// BeforeValidate is the lagoon.HasBeforeValidate hook cabana calls on a
|
||||
// settings save, after the fill and before the row is written: a
|
||||
// frontend_url that AllowedFrontendURL refuses is a validation error on
|
||||
// frontend_url, so the save answers 422 and the row is left as it was.
|
||||
func (s Settings) BeforeValidate(*gorm.DB) error {
|
||||
if AllowedFrontendURL(Text(s.FrontendURL)) {
|
||||
return nil
|
||||
}
|
||||
return &cabana.ValidationError{Details: map[string]any{"frontend_url": []string{errFrontendURL}}}
|
||||
}
|
||||
|
||||
// AllowedFrontendURL reports whether u may be the frontend_url setting: the
|
||||
// empty string, or an absolute http or https URL with a host and without
|
||||
// whitespace, control characters, a double quote, <, > or &. The PHP plugin
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/sm-sitemap-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
)
|
||||
|
||||
// cabana calls the hook on a settings save through this interface.
|
||||
var _ lagoon.HasBeforeValidate = (*models.Settings)(nil)
|
||||
|
||||
// TestAllowedFrontendURL pins the one definition of a frontend_url that may
|
||||
// be saved, imported and applied (T-17-15): empty, or an absolute http(s)
|
||||
// URL with a host and without whitespace, control characters, a double
|
||||
@@ -59,3 +66,32 @@ func TestAllowedFrontendURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSettingsBeforeValidate pins the save check of T-17-15: the hook cabana
|
||||
// calls on a settings save passes a null, empty or accepted frontend_url and
|
||||
// answers a validation error keyed frontend_url for any other value.
|
||||
func TestSettingsBeforeValidate(t *testing.T) {
|
||||
text := func(s string) *string { return &s }
|
||||
for _, url := range []*string{nil, text(""), text("https://www.ex.pl/")} {
|
||||
if err := (&models.Settings{FrontendURL: url}).BeforeValidate(nil); err != nil {
|
||||
t.Errorf("BeforeValidate(%q) = %v, want nil", models.Text(url), err)
|
||||
}
|
||||
}
|
||||
want := map[string]any{"frontend_url": []string{"golem15.sitemap::lang.errors.frontend_url"}}
|
||||
for _, url := range []string{
|
||||
"javascript:alert(document.domain)//",
|
||||
`https://x"/><y a="`,
|
||||
"0",
|
||||
" https://www.ex.pl",
|
||||
} {
|
||||
err := (&models.Settings{FrontendURL: &url}).BeforeValidate(nil)
|
||||
var invalid *cabana.ValidationError
|
||||
if !errors.As(err, &invalid) {
|
||||
t.Errorf("BeforeValidate(%q) = %v, want a *cabana.ValidationError", url, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(invalid.Details, want) {
|
||||
t.Errorf("BeforeValidate(%q) details = %v, want %v", url, invalid.Details, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user