fix platform workflow integrity and permissions

This commit is contained in:
david
2026-07-29 14:03:10 +08:00
parent afd9dbdeb5
commit d4799cd320
44 changed files with 797 additions and 224 deletions

View File

@@ -37,7 +37,7 @@ func ListResource(ctx *gin.Context, model any) {
page, size := PageSize(ctx)
list := reflect.New(reflect.SliceOf(reflect.TypeOf(model).Elem()))
var total int64
query := ApplyKeywordFilter(ctx, impl.DBService.Model(model), model)
query := ApplyKeywordFilter(ctx, ActiveRecords(impl.DBService.Model(model)), model)
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
@@ -74,6 +74,10 @@ func createResource(ctx *gin.Context, model any, allowedFields []string, relatio
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if err := ValidateResourceValues(model, values, true); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
encoded, err := json.Marshal(values)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
@@ -123,7 +127,7 @@ func maskCreatedSensitiveFields(value any) any {
func isCreatedResponseField(key string) bool {
switch key {
case "identity", "status", "version", "created_at", "updated_at":
case "identity", "status", "created_at", "updated_at":
return true
default:
return strings.HasSuffix(key, "_identity")
@@ -226,7 +230,7 @@ func updateResource(ctx *gin.Context, model any, allowedFields []string, relatio
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if len(values) == 0 {
if len(values) == 0 || ValidateResourceValues(model, values, false) != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
@@ -245,6 +249,74 @@ func PrepareResourceValues(ctx *gin.Context, model any, allowedFields []string,
return values, nil
}
// ValidateResourceValues enforces invariants that database nullability and
// frontend form metadata cannot express.
func ValidateResourceValues(model any, values map[string]any, creating bool) error {
positive := func(key string) bool {
value, exists := numericValue(values[key])
return !exists || value > 0
}
nonNegative := func(key string) bool {
value, exists := numericValue(values[key])
return !exists || value >= 0
}
nonEmpty := func(key string) bool {
value, exists := values[key]
if !exists {
return !creating
}
text, ok := value.(string)
return ok && strings.TrimSpace(text) != ""
}
switch model.(type) {
case *models.EcProduct:
if !nonEmpty("product_code") || !nonEmpty("name") || !nonNegative("price_amount") || !nonNegative("stock_quantity") {
return errors.New("invalid commerce product")
}
case *models.EcCart:
if !positive("quantity") {
return errors.New("invalid cart quantity")
}
case *models.EcOrder:
if !nonEmpty("order_no") || !nonNegative("total_amount") {
return errors.New("invalid order")
}
case *models.EcOrderItem:
if !nonEmpty("product_snapshot") || !positive("quantity") || !nonNegative("sale_amount") {
return errors.New("invalid order item")
}
case *models.EcReview:
score, exists := numericValue(values["score"])
if (exists && (score < 1 || score > 5)) || !nonEmpty("content") {
return errors.New("invalid review")
}
case *models.FinPayment:
if !positive("amount") || !nonEmpty("channel") {
return errors.New("invalid payment")
}
}
return nil
}
func numericValue(value any) (float64, bool) {
switch number := value.(type) {
case float64:
return number, true
case float32:
return float64(number), true
case int:
return float64(number), true
case int64:
return float64(number), true
case uint:
return float64(number), true
case uint64:
return float64(number), true
default:
return 0, false
}
}
func ResolveResourceRelations(input map[string]any, allowedFields []string, relations []ResourceRelation, requireRelations bool) (map[string]any, error) {
values := FilterFields(input, allowedFields)
for _, relation := range relations {
@@ -286,7 +358,7 @@ func ResolveIdentityID(model any, identity string, required bool) (uint64, error
return 0, nil
}
var related struct{ ID uint64 }
if err := impl.DBService.Model(model).Select("id").Where("identity = ?", identity).First(&related).Error; err != nil {
if err := impl.DBService.Model(model).Select("id").Where("identity = ? AND status <> ?", identity, StatusArchived).First(&related).Error; err != nil {
return 0, err
}
return related.ID, nil