mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-02 03:22:09 +00:00
feat(resolvemcp): require authentication on MCP endpoints and enforce model rules on writes
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.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user