mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-06-04 12:53:45 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d2251317a | |||
| 1ce0ab1ab4 | |||
| 1f9b230f7f |
@@ -266,11 +266,24 @@ func (v *ColumnValidator) FilterRequestOptions(options RequestOptions) RequestOp
|
|||||||
|
|
||||||
// Filter Preload columns
|
// Filter Preload columns
|
||||||
validPreloads := make([]PreloadOption, 0, len(options.Preload))
|
validPreloads := make([]PreloadOption, 0, len(options.Preload))
|
||||||
|
modelType := reflect.TypeOf(v.model)
|
||||||
|
if modelType != nil && modelType.Kind() == reflect.Ptr {
|
||||||
|
modelType = modelType.Elem()
|
||||||
|
}
|
||||||
for idx := range options.Preload {
|
for idx := range options.Preload {
|
||||||
preload := options.Preload[idx]
|
preload := options.Preload[idx]
|
||||||
filteredPreload := preload
|
filteredPreload := preload
|
||||||
filteredPreload.Columns = v.FilterValidColumns(preload.Columns)
|
|
||||||
filteredPreload.OmitColumns = v.FilterValidColumns(preload.OmitColumns)
|
// Use the related model's validator for preload columns/filters/sorts
|
||||||
|
preloadValidator := v
|
||||||
|
if modelType != nil {
|
||||||
|
if relInfo := GetRelationshipInfo(modelType, preload.Relation); relInfo != nil && relInfo.RelatedModel != nil {
|
||||||
|
preloadValidator = NewColumnValidator(relInfo.RelatedModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredPreload.Columns = preloadValidator.FilterValidColumns(preload.Columns)
|
||||||
|
filteredPreload.OmitColumns = preloadValidator.FilterValidColumns(preload.OmitColumns)
|
||||||
|
|
||||||
// Preserve SqlJoins and JoinAliases for preloads with custom joins
|
// Preserve SqlJoins and JoinAliases for preloads with custom joins
|
||||||
filteredPreload.SqlJoins = preload.SqlJoins
|
filteredPreload.SqlJoins = preload.SqlJoins
|
||||||
@@ -279,7 +292,7 @@ func (v *ColumnValidator) FilterRequestOptions(options RequestOptions) RequestOp
|
|||||||
// Filter preload filters
|
// Filter preload filters
|
||||||
validPreloadFilters := make([]FilterOption, 0, len(preload.Filters))
|
validPreloadFilters := make([]FilterOption, 0, len(preload.Filters))
|
||||||
for _, filter := range preload.Filters {
|
for _, filter := range preload.Filters {
|
||||||
if v.IsValidColumn(filter.Column) {
|
if preloadValidator.IsValidColumn(filter.Column) {
|
||||||
validPreloadFilters = append(validPreloadFilters, filter)
|
validPreloadFilters = append(validPreloadFilters, filter)
|
||||||
} else {
|
} else {
|
||||||
// Check if the filter column references a joined table alias
|
// Check if the filter column references a joined table alias
|
||||||
@@ -302,7 +315,7 @@ func (v *ColumnValidator) FilterRequestOptions(options RequestOptions) RequestOp
|
|||||||
// Filter preload sort columns
|
// Filter preload sort columns
|
||||||
validPreloadSorts := make([]SortOption, 0, len(preload.Sort))
|
validPreloadSorts := make([]SortOption, 0, len(preload.Sort))
|
||||||
for _, sort := range preload.Sort {
|
for _, sort := range preload.Sort {
|
||||||
if v.IsValidColumn(sort.Column) {
|
if preloadValidator.IsValidColumn(sort.Column) {
|
||||||
validPreloadSorts = append(validPreloadSorts, sort)
|
validPreloadSorts = append(validPreloadSorts, sort)
|
||||||
} else if strings.HasPrefix(sort.Column, "(") && strings.HasSuffix(sort.Column, ")") {
|
} else if strings.HasPrefix(sort.Column, "(") && strings.HasSuffix(sort.Column, ")") {
|
||||||
// Allow sort by expression/subquery, but validate for security
|
// Allow sort by expression/subquery, but validate for security
|
||||||
|
|||||||
@@ -464,3 +464,84 @@ func TestFilterRequestOptions_WithSortExpressions(t *testing.T) {
|
|||||||
t.Errorf("Expected third sort to be 'name', got '%s'", filtered.Sort[2].Column)
|
t.Errorf("Expected third sort to be 'name', got '%s'", filtered.Sort[2].Column)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RelatedModel is used by PreloadParentModel to test preload column validation.
|
||||||
|
type RelatedModel struct {
|
||||||
|
RelatedID int64 `bun:"related_id,pk"`
|
||||||
|
Functionname string `bun:"functionname"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreloadParentModel has a has-one relation to RelatedModel. The json tag on
|
||||||
|
// the relation field is the name used in x-preload headers.
|
||||||
|
type PreloadParentModel struct {
|
||||||
|
ID int64 `bun:"id,pk"`
|
||||||
|
Name string `bun:"name"`
|
||||||
|
RELATED *RelatedModel `json:"RELATED" bun:"rel:has-one,join:id=related_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterRequestOptions_PreloadColumnsValidatedAgainstRelatedModel verifies
|
||||||
|
// that preload columns are validated against the related model's fields, not the
|
||||||
|
// parent model's fields. This is the fix for the bug where specifying a column
|
||||||
|
// that exists only on the relation (e.g. "functionname") was incorrectly filtered
|
||||||
|
// out because it doesn't exist on the parent model.
|
||||||
|
func TestFilterRequestOptions_PreloadColumnsValidatedAgainstRelatedModel(t *testing.T) {
|
||||||
|
validator := NewColumnValidator(PreloadParentModel{})
|
||||||
|
|
||||||
|
options := RequestOptions{
|
||||||
|
Preload: []PreloadOption{
|
||||||
|
{
|
||||||
|
Relation: "RELATED",
|
||||||
|
// "functionname" exists on RelatedModel but NOT on PreloadParentModel.
|
||||||
|
// "name" exists on PreloadParentModel but NOT on RelatedModel.
|
||||||
|
// "nonexistent" exists on neither.
|
||||||
|
Columns: []string{"functionname", "name", "nonexistent"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := validator.FilterRequestOptions(options)
|
||||||
|
|
||||||
|
if len(filtered.Preload) != 1 {
|
||||||
|
t.Fatalf("Expected 1 preload, got %d", len(filtered.Preload))
|
||||||
|
}
|
||||||
|
|
||||||
|
cols := filtered.Preload[0].Columns
|
||||||
|
// Only "functionname" should survive: it belongs to RelatedModel.
|
||||||
|
if len(cols) != 1 {
|
||||||
|
t.Errorf("Expected 1 preload column, got %d: %v", len(cols), cols)
|
||||||
|
}
|
||||||
|
if len(cols) > 0 && cols[0] != "functionname" {
|
||||||
|
t.Errorf("Expected preload column 'functionname', got '%s'", cols[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterRequestOptions_PreloadColumnsParentModelFallback verifies that when
|
||||||
|
// a preload relation is not found on the parent model, column validation falls
|
||||||
|
// back to the parent model's validator (no panic, no silent pass-through).
|
||||||
|
func TestFilterRequestOptions_PreloadColumnsParentModelFallback(t *testing.T) {
|
||||||
|
validator := NewColumnValidator(PreloadParentModel{})
|
||||||
|
|
||||||
|
options := RequestOptions{
|
||||||
|
Preload: []PreloadOption{
|
||||||
|
{
|
||||||
|
Relation: "UNKNOWN_RELATION",
|
||||||
|
Columns: []string{"id", "functionname"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := validator.FilterRequestOptions(options)
|
||||||
|
|
||||||
|
if len(filtered.Preload) != 1 {
|
||||||
|
t.Fatalf("Expected 1 preload, got %d", len(filtered.Preload))
|
||||||
|
}
|
||||||
|
|
||||||
|
cols := filtered.Preload[0].Columns
|
||||||
|
// Falls back to parent model: only "id" is valid on PreloadParentModel.
|
||||||
|
if len(cols) != 1 {
|
||||||
|
t.Errorf("Expected 1 preload column (fallback to parent), got %d: %v", len(cols), cols)
|
||||||
|
}
|
||||||
|
if len(cols) > 0 && cols[0] != "id" {
|
||||||
|
t.Errorf("Expected preload column 'id', got '%s'", cols[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||||
@@ -102,11 +101,6 @@ func DecodeParam(pStr string) (string, error) {
|
|||||||
|
|
||||||
if strings.HasPrefix(code, "ZIP_") || strings.HasPrefix(code, "__") {
|
if strings.HasPrefix(code, "ZIP_") || strings.HasPrefix(code, "__") {
|
||||||
code, _ = DecodeParam(code)
|
code, _ = DecodeParam(code)
|
||||||
} else {
|
|
||||||
strDat, err := base64.StdEncoding.DecodeString(code)
|
|
||||||
if err == nil && utf8.Valid(strDat) {
|
|
||||||
code = string(strDat)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return code, nil
|
return code, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user