added extra validations in case of id pattern validator misuse
This commit is contained in:
parent
52e85a8036
commit
48328bf33f
|
@ -391,7 +391,7 @@ func (f *FileField) Intercept(
|
||||||
}
|
}
|
||||||
func (f *FileField) getLatestOldValue(app App, record *Record) any {
|
func (f *FileField) getLatestOldValue(app App, record *Record) any {
|
||||||
if !record.IsNew() {
|
if !record.IsNew() {
|
||||||
latestOriginal, err := app.FindRecordById(record.Collection(), record.Id)
|
latestOriginal, err := app.FindRecordById(record.Collection(), record.Original().Id)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return latestOriginal.GetRaw(f.Name)
|
return latestOriginal.GetRaw(f.Name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,14 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase/core/validators"
|
"github.com/pocketbase/pocketbase/core/validators"
|
||||||
"github.com/pocketbase/pocketbase/tools/security"
|
"github.com/pocketbase/pocketbase/tools/security"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
@ -151,6 +155,8 @@ func (f *TextField) PrepareValue(record *Record, raw any) (any, error) {
|
||||||
return cast.ToString(raw), nil
|
return cast.ToString(raw), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var forbiddenPKChars = []string{"/", "\\"}
|
||||||
|
|
||||||
// ValidateValue implements [Field.ValidateValue] interface method.
|
// ValidateValue implements [Field.ValidateValue] interface method.
|
||||||
func (f *TextField) ValidateValue(ctx context.Context, app App, record *Record) error {
|
func (f *TextField) ValidateValue(ctx context.Context, app App, record *Record) error {
|
||||||
newVal, ok := record.GetRaw(f.Name).(string)
|
newVal, ok := record.GetRaw(f.Name).(string)
|
||||||
|
@ -158,14 +164,42 @@ func (f *TextField) ValidateValue(ctx context.Context, app App, record *Record)
|
||||||
return validators.ErrUnsupportedValueType
|
return validators.ErrUnsupportedValueType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.PrimaryKey {
|
||||||
// disallow PK change
|
// disallow PK change
|
||||||
if f.PrimaryKey && !record.IsNew() {
|
if !record.IsNew() {
|
||||||
oldVal := record.LastSavedPK()
|
oldVal := record.LastSavedPK()
|
||||||
if oldVal != newVal {
|
if oldVal != newVal {
|
||||||
return validation.NewError("validation_pk_change", "The record primary key cannot be changed.")
|
return validation.NewError("validation_pk_change", "The record primary key cannot be changed.")
|
||||||
}
|
}
|
||||||
if oldVal != "" {
|
if oldVal != "" {
|
||||||
return nil // no need to further validate since the id can't be updated anyway
|
// no need to further validate because the id can't be updated
|
||||||
|
// and because the id could have been inserted manually by migration from another system
|
||||||
|
// that may not comply with the user defined PocketBase validations
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// disallow PK special characters no matter of the Pattern validator to minimize
|
||||||
|
// side-effects when the primary key is used for example in a directory path
|
||||||
|
for _, c := range forbiddenPKChars {
|
||||||
|
if strings.Contains(newVal, c) {
|
||||||
|
return validation.NewError("validation_pk_forbidden", "The record primary key contains forbidden characters.").
|
||||||
|
SetParams(map[string]any{"forbidden": c})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this technically shouldn't be necessarily but again to
|
||||||
|
// prevent minimize misuse of the Pattern validator that could
|
||||||
|
// cause side-effects on some platforms check for duplicates in a case-insensitive manner
|
||||||
|
var exists bool
|
||||||
|
err := app.DB().
|
||||||
|
Select("(1)").
|
||||||
|
From(record.TableName()).
|
||||||
|
Where(dbx.NewExp("LOWER(id) = {:id}", dbx.Params{"id": strings.ToLower(newVal)})).
|
||||||
|
Limit(1).
|
||||||
|
Row(&exists)
|
||||||
|
if exists || (err != nil && !errors.Is(err, sql.ErrNoRows)) {
|
||||||
|
return validation.NewError("validation_pk_invalid", "The record primary key is invalid or already exists.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,15 @@ func TestTextFieldValidateValue(t *testing.T) {
|
||||||
app, _ := tests.NewTestApp()
|
app, _ := tests.NewTestApp()
|
||||||
defer app.Cleanup()
|
defer app.Cleanup()
|
||||||
|
|
||||||
collection := core.NewBaseCollection("test_collection")
|
collection, err := app.FindCollectionByNameOrId("demo1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existingRecord, err := app.FindFirstRecordByFilter(collection, "id != ''")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
scenarios := []struct {
|
scenarios := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -116,6 +124,46 @@ func TestTextFieldValidateValue(t *testing.T) {
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"special forbidden character / (non-primaryKey)",
|
||||||
|
&core.TextField{Name: "test", PrimaryKey: false},
|
||||||
|
func() *core.Record {
|
||||||
|
record := core.NewRecord(collection)
|
||||||
|
record.SetRaw("test", "/")
|
||||||
|
return record
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"special forbidden character \\ (non-primaryKey)",
|
||||||
|
&core.TextField{Name: "test", PrimaryKey: false},
|
||||||
|
func() *core.Record {
|
||||||
|
record := core.NewRecord(collection)
|
||||||
|
record.SetRaw("test", "\\")
|
||||||
|
return record
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"special forbidden character / (primaryKey)",
|
||||||
|
&core.TextField{Name: "test", PrimaryKey: true},
|
||||||
|
func() *core.Record {
|
||||||
|
record := core.NewRecord(collection)
|
||||||
|
record.SetRaw("test", "/")
|
||||||
|
return record
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"special forbidden character \\ (primaryKey)",
|
||||||
|
&core.TextField{Name: "test", PrimaryKey: true},
|
||||||
|
func() *core.Record {
|
||||||
|
record := core.NewRecord(collection)
|
||||||
|
record.SetRaw("test", "\\")
|
||||||
|
return record
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"zero field value (primaryKey)",
|
"zero field value (primaryKey)",
|
||||||
&core.TextField{Name: "test", PrimaryKey: true},
|
&core.TextField{Name: "test", PrimaryKey: true},
|
||||||
|
@ -131,11 +179,21 @@ func TestTextFieldValidateValue(t *testing.T) {
|
||||||
&core.TextField{Name: "test", PrimaryKey: true},
|
&core.TextField{Name: "test", PrimaryKey: true},
|
||||||
func() *core.Record {
|
func() *core.Record {
|
||||||
record := core.NewRecord(collection)
|
record := core.NewRecord(collection)
|
||||||
record.SetRaw("test", "abc")
|
record.SetRaw("test", "abcd")
|
||||||
return record
|
return record
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"case-insensitive duplicated primary key check",
|
||||||
|
&core.TextField{Name: "test", PrimaryKey: true},
|
||||||
|
func() *core.Record {
|
||||||
|
record := core.NewRecord(collection)
|
||||||
|
record.SetRaw("test", strings.ToUpper(existingRecord.Id))
|
||||||
|
return record
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"< min",
|
"< min",
|
||||||
&core.TextField{Name: "test", Min: 4},
|
&core.TextField{Name: "test", Min: 4},
|
||||||
|
|
Loading…
Reference in New Issue