package handler import ( "fmt" "gorm.io/gorm" ) // SEC-001(跨租户信息泄露)防线: // // 关联对象(Partner/Warehouse/Product…)若只按外键回连、不再按 shop_id 过滤, // 攻击者可在本店单据里塞他店 id,再从 GET 接口把他店对象(含 PII)读出来。 // 两道防线缺一不可: // 1. 写入侧 ensureShopRef:拒绝引用不属于当前店的外键; // 2. 读取侧 Preload/JOIN 一律附加 shop_id 条件(存量脏引用也带不出数据)。 // ensureShopRef 校验外键对象存在且属于当前店(table 为调用点硬编码常量,非用户输入)。 func ensureShopRef(db *gorm.DB, table string, id uint64, shopID uint64) error { if id == 0 { return fmt.Errorf("引用对象 id 无效") } var n int64 db.Table(table). Where("id = ? AND shop_id = ? AND deleted_at IS NULL", id, shopID). Count(&n) if n == 0 { return fmt.Errorf("引用的%s不存在", refName(table)) } return nil } // ensureShopRefOpt 可选外键(如 partner_id 指针):nil/0 视为未引用,直接通过。 func ensureShopRefOpt(db *gorm.DB, table string, id *uint64, shopID uint64) error { if id == nil || *id == 0 { return nil } return ensureShopRef(db, table, *id, shopID) } func refName(table string) string { switch table { case "partners": return "往来单位" case "warehouses": return "仓库" case "products": return "商品" default: return "对象" } }