fix: harden platform form and response boundaries
This commit is contained in:
@@ -126,7 +126,7 @@ func getResource(ctx *gin.Context, model any) {
|
||||
}
|
||||
|
||||
func createResource(ctx *gin.Context, model any, allowedFields []string, relations []ResourceRelation) {
|
||||
values, err := prepareResourceValues(ctx, allowedFields, relations)
|
||||
values, err := prepareResourceValues(ctx, model, allowedFields, relations)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
@@ -161,41 +161,30 @@ func respondCreatedResource(ctx *gin.Context, value any) {
|
||||
func maskCreatedSensitiveFields(value any) any {
|
||||
switch data := value.(type) {
|
||||
case map[string]any:
|
||||
if phone, ok := data["phone"].(string); ok {
|
||||
data["phone_masked"] = maskPhone(phone)
|
||||
delete(data, "phone")
|
||||
}
|
||||
if realName, ok := data["real_name"].(string); ok {
|
||||
data["real_name_masked"] = maskSecret(realName)
|
||||
delete(data, "real_name")
|
||||
}
|
||||
if credentialNo, ok := data["credential_no"].(string); ok {
|
||||
data["credential_no_masked"] = maskSecret(credentialNo)
|
||||
delete(data, "credential_no")
|
||||
}
|
||||
delete(data, "longitude")
|
||||
delete(data, "latitude")
|
||||
safe := make(map[string]any)
|
||||
for key, item := range data {
|
||||
data[key] = maskCreatedSensitiveFields(item)
|
||||
if isCreatedResponseField(key) {
|
||||
safe[key] = maskCreatedSensitiveFields(item)
|
||||
}
|
||||
}
|
||||
return safe
|
||||
case []any:
|
||||
for index := range data {
|
||||
data[index] = maskCreatedSensitiveFields(data[index])
|
||||
safe := make([]any, len(data))
|
||||
for index, item := range data {
|
||||
safe[index] = maskCreatedSensitiveFields(item)
|
||||
}
|
||||
return safe
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func maskSecret(value string) string {
|
||||
characters := []rune(value)
|
||||
if len(characters) <= 1 {
|
||||
return "*"
|
||||
func isCreatedResponseField(key string) bool {
|
||||
switch key {
|
||||
case "identity", "status", "version", "created_at", "updated_at":
|
||||
return true
|
||||
default:
|
||||
return strings.HasSuffix(key, "_identity")
|
||||
}
|
||||
visible := 1
|
||||
if len(characters) > 4 {
|
||||
visible = 4
|
||||
}
|
||||
return strings.Repeat("*", len(characters)-visible) + string(characters[len(characters)-visible:])
|
||||
}
|
||||
|
||||
func protectPreciseLocation(ctx *gin.Context, model, value any) any {
|
||||
@@ -237,7 +226,7 @@ func updateResource(ctx *gin.Context, model any, allowedFields []string, relatio
|
||||
return
|
||||
}
|
||||
values, err := resolveResourceRelations(input, allowedFields, relations, false)
|
||||
if err != nil {
|
||||
if err != nil || normalizeStringJSONBFields(model, values) != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
@@ -248,18 +237,48 @@ func updateResource(ctx *gin.Context, model any, allowedFields []string, relatio
|
||||
updateAllowedByIdentity(ctx, model, values, append(allowedFields, relationColumns(relations)...))
|
||||
}
|
||||
|
||||
func prepareResourceValues(ctx *gin.Context, allowedFields []string, relations []ResourceRelation) (map[string]any, error) {
|
||||
func prepareResourceValues(ctx *gin.Context, model any, allowedFields []string, relations []ResourceRelation) (map[string]any, error) {
|
||||
var input map[string]any
|
||||
if err := ctx.ShouldBindJSON(&input); err != nil || len(input) == 0 {
|
||||
return nil, errors.New("invalid resource payload")
|
||||
}
|
||||
values, err := resolveResourceRelations(input, allowedFields, relations, true)
|
||||
if err != nil || len(values) == 0 {
|
||||
if err != nil || normalizeStringJSONBFields(model, values) != nil || len(values) == 0 {
|
||||
return nil, errors.New("invalid resource payload")
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func normalizeStringJSONBFields(model any, values map[string]any) error {
|
||||
modelType := reflect.TypeOf(model)
|
||||
for modelType.Kind() == reflect.Pointer {
|
||||
modelType = modelType.Elem()
|
||||
}
|
||||
for index := 0; index < modelType.NumField(); index++ {
|
||||
field := modelType.Field(index)
|
||||
if field.Type.Kind() != reflect.String || !strings.Contains(field.Tag.Get("gorm"), "type:jsonb") {
|
||||
continue
|
||||
}
|
||||
column := gormColumn(field.Tag.Get("gorm"))
|
||||
value, exists := values[column]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
if text, ok := value.(string); ok {
|
||||
if !json.Valid([]byte(text)) {
|
||||
return errors.New("invalid jsonb string")
|
||||
}
|
||||
continue
|
||||
}
|
||||
encoded, err := json.Marshal(value)
|
||||
if err != nil || !json.Valid(encoded) {
|
||||
return errors.New("invalid jsonb value")
|
||||
}
|
||||
values[column] = string(encoded)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveResourceRelations(input map[string]any, allowedFields []string, relations []ResourceRelation, requireRelations bool) (map[string]any, error) {
|
||||
values := filterFields(input, allowedFields)
|
||||
for _, relation := range relations {
|
||||
|
||||
Reference in New Issue
Block a user