fix: align customer payment encryption and protect original values
Deploy olons-admin production / deploy (push) Successful in 9m19s
Deploy olons-admin production / deploy (push) Successful in 9m19s
This commit is contained in:
@@ -458,6 +458,7 @@ func (s *sSysAppMember) PaymentList(ctx context.Context, in *sysin.AppMemberPaym
|
||||
if row.HolderCipher != "" {
|
||||
plain, decryptErr := encrypt.DecryptField(ctx, row.HolderCipher)
|
||||
if decryptErr != nil {
|
||||
row.SensitiveUnavailable = true
|
||||
g.Log().Warningf(ctx, "收款人姓名解密失败,回退为脱敏值 memberId:%d accountId:%d err:%+v", row.MemberId, row.Id, decryptErr)
|
||||
row.Holder = row.HolderMask
|
||||
} else {
|
||||
@@ -469,6 +470,7 @@ func (s *sSysAppMember) PaymentList(ctx context.Context, in *sysin.AppMemberPaym
|
||||
if row.IdentityNoCipher != "" {
|
||||
plain, decryptErr := encrypt.DecryptField(ctx, row.IdentityNoCipher)
|
||||
if decryptErr != nil {
|
||||
row.SensitiveUnavailable = true
|
||||
g.Log().Warningf(ctx, "收款人证件号解密失败,回退为脱敏值 memberId:%d accountId:%d err:%+v", row.MemberId, row.Id, decryptErr)
|
||||
row.IdentityNo = row.IdentityNoMask
|
||||
} else {
|
||||
@@ -480,6 +482,7 @@ func (s *sSysAppMember) PaymentList(ctx context.Context, in *sysin.AppMemberPaym
|
||||
if row.MobileCipher != "" {
|
||||
plain, decryptErr := encrypt.DecryptField(ctx, row.MobileCipher)
|
||||
if decryptErr != nil {
|
||||
row.SensitiveUnavailable = true
|
||||
g.Log().Warningf(ctx, "收款手机号解密失败,回退为脱敏值 memberId:%d accountId:%d err:%+v", row.MemberId, row.Id, decryptErr)
|
||||
row.Mobile = row.MobileMask
|
||||
} else {
|
||||
@@ -491,6 +494,7 @@ func (s *sSysAppMember) PaymentList(ctx context.Context, in *sysin.AppMemberPaym
|
||||
if row.AccountNoCipher != "" {
|
||||
plain, decryptErr := encrypt.DecryptField(ctx, row.AccountNoCipher)
|
||||
if decryptErr != nil {
|
||||
row.SensitiveUnavailable = true
|
||||
g.Log().Warningf(ctx, "收款账号解密失败,回退为脱敏值 memberId:%d accountId:%d err:%+v", row.MemberId, row.Id, decryptErr)
|
||||
row.AccountNo = row.AccountNoMask
|
||||
} else {
|
||||
@@ -502,6 +506,7 @@ func (s *sSysAppMember) PaymentList(ctx context.Context, in *sysin.AppMemberPaym
|
||||
if row.DigitalRmbCodeCipher != "" {
|
||||
plain, decryptErr := encrypt.DecryptField(ctx, row.DigitalRmbCodeCipher)
|
||||
if decryptErr != nil {
|
||||
row.SensitiveUnavailable = true
|
||||
g.Log().Warningf(ctx, "数字钱包编码解密失败,回退为脱敏值 memberId:%d accountId:%d err:%+v", row.MemberId, row.Id, decryptErr)
|
||||
row.DigitalRMBCode = row.DigitalRMBCodeMask
|
||||
} else {
|
||||
@@ -534,7 +539,7 @@ func (s *sSysAppMember) PaymentEdit(ctx context.Context, in *sysin.AppMemberPaym
|
||||
data[c.AlipayQrAttachmentId] = in.AlipayQrAttachmentId
|
||||
}
|
||||
if in.Holder != "" {
|
||||
v, e := encrypt.EncryptField(ctx, in.Holder)
|
||||
v, e := encrypt.EncryptPaymentField(ctx, in.Holder)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
@@ -542,7 +547,7 @@ func (s *sSysAppMember) PaymentEdit(ctx context.Context, in *sysin.AppMemberPaym
|
||||
data[c.HolderMask] = encrypt.MaskName(in.Holder)
|
||||
}
|
||||
if in.Mobile != "" {
|
||||
v, e := encrypt.EncryptField(ctx, in.Mobile)
|
||||
v, e := encrypt.EncryptPaymentField(ctx, in.Mobile)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
@@ -550,11 +555,11 @@ func (s *sSysAppMember) PaymentEdit(ctx context.Context, in *sysin.AppMemberPaym
|
||||
data[c.MobileMask] = encrypt.MaskMobile(in.Mobile)
|
||||
}
|
||||
if in.IdentityNo != "" {
|
||||
v, e := encrypt.EncryptField(ctx, in.IdentityNo)
|
||||
v, e := encrypt.EncryptPaymentField(ctx, in.IdentityNo)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
h, e := encrypt.HashField(ctx, in.IdentityNo)
|
||||
h, e := encrypt.HashPaymentField(ctx, in.IdentityNo)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
@@ -563,11 +568,11 @@ func (s *sSysAppMember) PaymentEdit(ctx context.Context, in *sysin.AppMemberPaym
|
||||
data[c.IdentityNoMask] = encrypt.MaskAccount(in.IdentityNo)
|
||||
}
|
||||
if in.AccountNo != "" {
|
||||
v, e := encrypt.EncryptField(ctx, in.AccountNo)
|
||||
v, e := encrypt.EncryptPaymentField(ctx, in.AccountNo)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
h, e := encrypt.HashField(ctx, in.AccountNo)
|
||||
h, e := encrypt.HashPaymentField(ctx, in.AccountNo)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
@@ -576,11 +581,11 @@ func (s *sSysAppMember) PaymentEdit(ctx context.Context, in *sysin.AppMemberPaym
|
||||
data[c.AccountNoMask] = encrypt.MaskAccount(in.AccountNo)
|
||||
}
|
||||
if in.DigitalRMBCode != "" {
|
||||
v, e := encrypt.EncryptField(ctx, in.DigitalRMBCode)
|
||||
v, e := encrypt.EncryptPaymentField(ctx, in.DigitalRMBCode)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
h, e := encrypt.HashField(ctx, in.DigitalRMBCode)
|
||||
h, e := encrypt.HashPaymentField(ctx, in.DigitalRMBCode)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -95,6 +95,9 @@ type AppMemberEditInp struct {
|
||||
}
|
||||
|
||||
func (in *AppMemberEditInp) Filter(ctx context.Context) error {
|
||||
if err := rejectMaskedSensitiveFields(in.RealName, in.IdNo); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.Id == 0 {
|
||||
return gerror.New("后台不支持创建会员,请由会员在C端完成注册")
|
||||
}
|
||||
@@ -156,6 +159,7 @@ type AppMemberPaymentListInp struct {
|
||||
func (in *AppMemberPaymentListInp) Filter(ctx context.Context) error { return nil }
|
||||
|
||||
type AppMemberPaymentModel struct {
|
||||
SensitiveUnavailable bool `json:"sensitiveUnavailable"`
|
||||
Id uint64 `json:"id"`
|
||||
MemberId uint64 `json:"memberId"`
|
||||
AccountType int `json:"accountType"`
|
||||
@@ -207,6 +211,9 @@ type AppMemberPaymentEditInp struct {
|
||||
}
|
||||
|
||||
func (in *AppMemberPaymentEditInp) Filter(ctx context.Context) error {
|
||||
if err := rejectMaskedSensitiveFields(in.Holder, in.IdentityNo, in.Mobile, in.AccountNo, in.DigitalRMBCode); err != nil {
|
||||
return err
|
||||
}
|
||||
if !dict.HasOptionKey(consts.PaymentAccountTypeOptions, in.AccountType) {
|
||||
return gerror.New("收款账户类型不合法")
|
||||
}
|
||||
@@ -260,12 +267,24 @@ type AppMemberAddressEditInp struct {
|
||||
}
|
||||
|
||||
func (in *AppMemberAddressEditInp) Filter(ctx context.Context) error {
|
||||
if err := rejectMaskedSensitiveFields(in.Receiver, in.Mobile, in.Address); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.Id == 0 && (strings.TrimSpace(in.Receiver) == "" || strings.TrimSpace(in.Mobile) == "" || strings.TrimSpace(in.Address) == "") {
|
||||
return gerror.New("新增地址时收货人、手机号和详细地址不能为空")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rejectMaskedSensitiveFields(values ...string) error {
|
||||
for _, value := range values {
|
||||
if strings.Contains(value, "*") {
|
||||
return gerror.New("不能保存带星号的脱敏资料,请填写完整信息或留空保留原值")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppMemberAddressDeleteInp struct {
|
||||
Id uint64 `json:"id" v:"required|min:1#地址ID不能为空|地址ID不能为空"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package sysin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
func TestPaymentCipherFieldsScanWithoutJSONExposure(t *testing.T) {
|
||||
const ciphertext = "test-ciphertext-not-a-real-bank-account"
|
||||
record := gdb.Record{}
|
||||
for _, field := range []string{"holder_cipher", "identity_no_cipher", "mobile_cipher", "account_no_cipher", "digital_rmb_code_cipher"} {
|
||||
record[field] = gvar.New(ciphertext)
|
||||
}
|
||||
var row AppMemberPaymentModel
|
||||
if err := record.Struct(&row); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for name, actual := range map[string]string{"holder": row.HolderCipher, "identity": row.IdentityNoCipher, "mobile": row.MobileCipher, "account": row.AccountNoCipher, "digital": row.DigitalRmbCodeCipher} {
|
||||
if actual != ciphertext {
|
||||
t.Errorf("%s ciphertext was not loaded by database scan", name)
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(row)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(string(data), ciphertext) {
|
||||
t.Fatal("internal ciphertext must not be exposed in API JSON")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaymentEditRejectsMaskedValues(t *testing.T) {
|
||||
for _, field := range []string{"holder", "account", "identity", "mobile", "digital"} {
|
||||
t.Run(field, func(t *testing.T) {
|
||||
in := &AppMemberPaymentEditInp{Id: 1, MemberId: 1, AccountType: 1}
|
||||
switch field {
|
||||
case "holder":
|
||||
in.Holder = "王*"
|
||||
case "account":
|
||||
in.AccountNo = "6222********5937"
|
||||
case "identity":
|
||||
in.IdentityNo = "1234********5678"
|
||||
case "mobile":
|
||||
in.Mobile = "138****1234"
|
||||
case "digital":
|
||||
in.DigitalRMBCode = "0021********8708"
|
||||
}
|
||||
if err := in.Filter(context.Background()); err == nil {
|
||||
t.Fatal("masked values must never overwrite originals")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user