mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-02 11:31:57 +00:00
Guard() rejects unauthenticated callers (no guest/optional mode); Setup*/New* helpers take a SecurityList and have explicit *Unauthenticated variants. Model rules now reach the security hooks, create checks CanCreate (security.CheckModelCreateAllowed), create/update validate keys against the model's writable columns, update sets only given keys (NULL allowed), update and delete go through row security via a new BeforeScan hook, and the annotation tool is opt-in (Config.EnableAnnotations) and runs BeforeHandle.
57 lines
1.8 KiB
Go
57 lines
1.8 KiB
Go
package resolvemcp
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/bitechdev/ResolveSpec/pkg/reflection"
|
|
)
|
|
|
|
// maxKeyEcho caps how much of a rejected client key is echoed back in an error.
|
|
const maxKeyEcho = 64
|
|
|
|
// writeColumns validates the keys of a create/update payload against the model and returns the
|
|
// values keyed by database column name. A key may be the json name or the column name of a
|
|
// writable field (case-insensitive); relations, scan-only and unexported fields are not
|
|
// writable. Unknown keys are rejected rather than dropped, so a client learns that a write
|
|
// did not take effect, and no client-chosen identifier reaches SQL.
|
|
func writeColumns(model interface{}, data map[string]interface{}) (map[string]interface{}, error) {
|
|
modelType := reflect.TypeOf(model)
|
|
for modelType != nil && (modelType.Kind() == reflect.Pointer || modelType.Kind() == reflect.Slice) {
|
|
modelType = modelType.Elem()
|
|
}
|
|
if modelType == nil || modelType.Kind() != reflect.Struct {
|
|
return nil, fmt.Errorf("invalid model")
|
|
}
|
|
|
|
accepted := make(map[string]string)
|
|
for jsonKey, col := range reflection.BuildJSONToDBColumnMap(modelType) {
|
|
accepted[strings.ToLower(jsonKey)] = col
|
|
accepted[strings.ToLower(col)] = col
|
|
}
|
|
|
|
out := make(map[string]interface{}, len(data))
|
|
var unknown []string
|
|
for key, value := range data {
|
|
col, ok := accepted[strings.ToLower(key)]
|
|
if !ok {
|
|
if len(key) > maxKeyEcho {
|
|
key = key[:maxKeyEcho] + "..."
|
|
}
|
|
unknown = append(unknown, key)
|
|
continue
|
|
}
|
|
if _, dup := out[col]; dup {
|
|
return nil, fmt.Errorf("column %q given more than once", col)
|
|
}
|
|
out[col] = value
|
|
}
|
|
if len(unknown) > 0 {
|
|
sort.Strings(unknown)
|
|
return nil, fmt.Errorf("unknown or read-only fields: %s", strings.Join(unknown, ", "))
|
|
}
|
|
return out, nil
|
|
}
|