fix(tenpayv3): 修复部分响应模型解密敏感数据字段时抛出异常的问题

This commit is contained in:
Fu Diwei 2021-12-03 16:27:13 +08:00
parent 9423dd5642
commit ec6e3f7e7a
28 changed files with 120 additions and 58 deletions

View File

@ -0,0 +1,12 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class WechatTenpaySensitiveAttribute : Attribute
{
public WechatTenpaySensitiveAttribute()
{
}
}
}

View File

@ -23,55 +23,59 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
try try
{ {
// 遍历并加密被标记为敏感数据的字段 bool requireEncrypt = request.GetType().GetCustomAttributes<WechatTenpaySensitiveAttribute>(inherit: true).Any();
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref request, (obj, prop, value) => if (requireEncrypt)
{ {
var attr = prop.GetCustomAttribute<WechatTenpaySensitivePropertyAttribute>(); // 遍历并加密被标记为敏感数据的字段
if (attr == null) Utilities.ReflectionUtility.ReplacePropertyStringValue(ref request, (obj, prop, value) =>
return value;
if (Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB.Equals(attr.Algorithm))
{ {
if (client.CertificateManager == null) var attr = prop.GetCustomAttribute<WechatTenpaySensitivePropertyAttribute>();
throw new Exceptions.WechatTenpayRequestEncryptionException("Encrypt request failed, because there is no platform certificate in the manager."); if (attr == null)
return value;
string certificate; if (Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB.Equals(attr.Algorithm))
if (!string.IsNullOrEmpty(request.WechatpayCertSerialNumber))
{ {
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值 if (client.CertificateManager == null)
var cert = client.CertificateManager.GetEntry(request.WechatpayCertSerialNumber!); throw new Exceptions.WechatTenpayRequestEncryptionException("Encrypt request failed, because there is no platform certificate in the manager.");
if (!cert.HasValue)
string certificate;
if (!string.IsNullOrEmpty(request.WechatpayCertSerialNumber))
{ {
throw new Exceptions.WechatTenpayEventVerificationException("Encrypt request failed, because there is no platform certificate matched the serial number."); // 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
var cert = client.CertificateManager.GetEntry(request.WechatpayCertSerialNumber!);
if (!cert.HasValue)
{
throw new Exceptions.WechatTenpayEventVerificationException("Encrypt request failed, because there is no platform certificate matched the serial number.");
}
certificate = cert.Value.Certificate;
}
else
{
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
var certs = client.CertificateManager.AllEntries().OrderByDescending(e => e.ExpireTime);
if (!certs.Any())
{
throw new Exceptions.WechatTenpayEventVerificationException("Encrypt request failed, because there is no platform certificate in the manager.");
}
var cert = certs.First();
certificate = cert.Certificate;
request.WechatpayCertSerialNumber = cert.SerialNumber;
} }
certificate = cert.Value.Certificate; return Utilities.RSAUtility.EncryptWithECBByCertificate(
certificate: certificate,
plainText: value
);
} }
else else
{ {
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的 throw new Exceptions.WechatTenpayRequestEncryptionException("Unsupported encryption algorithm.");
var certs = client.CertificateManager.AllEntries().OrderByDescending(e => e.ExpireTime);
if (!certs.Any())
{
throw new Exceptions.WechatTenpayEventVerificationException("Encrypt request failed, because there is no platform certificate in the manager.");
}
var cert = certs.First();
certificate = cert.Certificate;
request.WechatpayCertSerialNumber = cert.SerialNumber;
} }
});
return Utilities.RSAUtility.EncryptWithECBByCertificate( }
certificate: certificate,
plainText: value
);
}
else
{
throw new Exceptions.WechatTenpayRequestEncryptionException("Unsupported encryption algorithm.");
}
});
} }
catch (Exception ex) when (!(ex is Exceptions.WechatTenpayRequestEncryptionException)) catch (Exception ex) when (!(ex is Exceptions.WechatTenpayRequestEncryptionException))
{ {

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Reflection; using System.Reflection;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
@ -54,25 +55,29 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
return response; return response;
} }
// 遍历并解密被标记为敏感数据的字段 bool requireDecrypt = response.GetType().GetCustomAttributes<WechatTenpaySensitiveAttribute>(inherit: true).Any();
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (obj, prop, value) => if (requireDecrypt)
{ {
var attr = prop.GetCustomAttribute<WechatTenpaySensitivePropertyAttribute>(); // 遍历并解密被标记为敏感数据的字段
if (attr == null) Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (obj, prop, value) =>
return value; {
var attr = prop.GetCustomAttribute<WechatTenpaySensitivePropertyAttribute>();
if (attr == null)
return value;
if (Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB.Equals(attr.Algorithm)) if (Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB.Equals(attr.Algorithm))
{ {
return Utilities.RSAUtility.DecryptWithECB( return Utilities.RSAUtility.DecryptWithECB(
privateKey: client.Credentials.MerchantCertPrivateKey, privateKey: client.Credentials.MerchantCertPrivateKey,
cipherText: value cipherText: value
); );
} }
else else
{ {
throw new Exceptions.WechatTenpayResponseDecryptionException("Unsupported decryption algorithm."); throw new Exceptions.WechatTenpayResponseDecryptionException("Unsupported decryption algorithm.");
} }
}); });
}
} }
catch (Exception ex) when (!(ex is Exceptions.WechatTenpayResponseDecryptionException)) catch (Exception ex) when (!(ex is Exceptions.WechatTenpayResponseDecryptionException))
{ {

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [POST] /applyment4sub/applyment/ 接口的请求。</para> /// <para>表示 [POST] /applyment4sub/applyment/ 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class CreateApplyForSubMerchantApplymentRequest : WechatTenpayRequest public class CreateApplyForSubMerchantApplymentRequest : WechatTenpayRequest
{ {
public static class Types public static class Types

View File

@ -3,6 +3,7 @@
/// <summary> /// <summary>
/// <para>表示 [POST] /apply4sub/sub_merchants/{sub_mchid}/modify-settlement 接口的请求。</para> /// <para>表示 [POST] /apply4sub/sub_merchants/{sub_mchid}/modify-settlement 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class ModifyApplyForSubMerchantSettlementRequest : WechatTenpayRequest public class ModifyApplyForSubMerchantSettlementRequest : WechatTenpayRequest
{ {
/// <summary> /// <summary>

View File

@ -5,6 +5,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [POST] /apply4subject/applyment 接口的请求。</para> /// <para>表示 [POST] /apply4subject/applyment 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class CreateApplyForSubjectApplymentRequest : WechatTenpayRequest public class CreateApplyForSubjectApplymentRequest : WechatTenpayRequest
{ {
public static class Types public static class Types

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /bill/sub-merchant-fundflowbill 接口的响应。</para> /// <para>表示 [GET] /bill/sub-merchant-fundflowbill 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class GetBillSubMerchantFundflowBillResponse : WechatTenpayResponse public class GetBillSubMerchantFundflowBillResponse : WechatTenpayResponse
{ {
public static class Types public static class Types

View File

@ -5,6 +5,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [POST] /brand/profitsharing/orders 接口的请求。</para> /// <para>表示 [POST] /brand/profitsharing/orders 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class CreateBrandProfitSharingOrderRequest : WechatTenpayRequest public class CreateBrandProfitSharingOrderRequest : WechatTenpayRequest
{ {
public static class Types public static class Types

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /certificates 接口的响应。</para> /// <para>表示 [GET] /certificates 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class QueryCertificatesResponse : WechatTenpayResponse public class QueryCertificatesResponse : WechatTenpayResponse
{ {
public static class Types public static class Types

View File

@ -5,6 +5,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [POST] /ecommerce/applyments 接口的请求。</para> /// <para>表示 [POST] /ecommerce/applyments 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class CreateEcommerceApplymentRequest : WechatTenpayRequest public class CreateEcommerceApplymentRequest : WechatTenpayRequest
{ {
public static class Types public static class Types

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /ecommerce/applyments/out-request-no/{out_request_no} 接口的响应。</para> /// <para>表示 [GET] /ecommerce/applyments/out-request-no/{out_request_no} 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class GetEcommerceApplymentByOutRequestNumberResponse : WechatTenpayResponse public class GetEcommerceApplymentByOutRequestNumberResponse : WechatTenpayResponse
{ {
public static class Types public static class Types

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /ecommerce/bill/fundflowbill 接口的响应。</para> /// <para>表示 [GET] /ecommerce/bill/fundflowbill 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class GetEcommerceBillFundflowBillResponse : WechatTenpayResponse public class GetEcommerceBillFundflowBillResponse : WechatTenpayResponse
{ {
public static class Types public static class Types

View File

@ -5,6 +5,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [POST] /ecommerce/profitsharing/orders 接口的请求。</para> /// <para>表示 [POST] /ecommerce/profitsharing/orders 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class CreateEcommerceProfitSharingOrderRequest : WechatTenpayRequest public class CreateEcommerceProfitSharingOrderRequest : WechatTenpayRequest
{ {
public static class Types public static class Types

View File

@ -3,6 +3,7 @@
/// <summary> /// <summary>
/// <para>表示 [POST] /ecommerce/profitsharing/receivers/add 接口的请求。</para> /// <para>表示 [POST] /ecommerce/profitsharing/receivers/add 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class AddEcommerceProfitSharingReceiverRequest : WechatTenpayRequest public class AddEcommerceProfitSharingReceiverRequest : WechatTenpayRequest
{ {
/// <summary> /// <summary>

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /merchant-service/complaints-v2/{complaint_id} 接口的响应。</para> /// <para>表示 [GET] /merchant-service/complaints-v2/{complaint_id} 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class GetMerchantServiceComplaintByComplaintIdResponse : WechatTenpayResponse public class GetMerchantServiceComplaintByComplaintIdResponse : WechatTenpayResponse
{ {
public static class Types public static class Types

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /merchant-service/complaints-v2 接口的响应。</para> /// <para>表示 [GET] /merchant-service/complaints-v2 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class QueryMerchantServiceComplaintsResponse : WechatTenpayResponse public class QueryMerchantServiceComplaintsResponse : WechatTenpayResponse
{ {
public static class Types public static class Types

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /partner-transfer/batches/out-batch-no/{out_batch_no}/details/out-detail-no/{out_detail_no} 接口的响应。</para> /// <para>表示 [GET] /partner-transfer/batches/out-batch-no/{out_batch_no}/details/out-detail-no/{out_detail_no} 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class GetPartnerTransferBatchDetailByOutDetailNumberResponse : GetTransferBatchDetailByOutDetailNumberResponse public class GetPartnerTransferBatchDetailByOutDetailNumberResponse : GetTransferBatchDetailByOutDetailNumberResponse
{ {
/// <summary> /// <summary>

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /payscore/merchant-bill 接口的响应。</para> /// <para>表示 [GET] /payscore/merchant-bill 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class GetPayScoreMerchantBillResponse : WechatTenpayResponse public class GetPayScoreMerchantBillResponse : WechatTenpayResponse
{ {
public static class Types public static class Types

View File

@ -5,6 +5,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [POST] /profitsharing/orders 接口的请求。</para> /// <para>表示 [POST] /profitsharing/orders 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class CreateProfitSharingOrderRequest : WechatTenpayRequest public class CreateProfitSharingOrderRequest : WechatTenpayRequest
{ {
public static class Types public static class Types

View File

@ -3,6 +3,7 @@
/// <summary> /// <summary>
/// <para>表示 [POST] /profitsharing/receivers/add 接口的请求。</para> /// <para>表示 [POST] /profitsharing/receivers/add 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class AddProfitSharingReceiverRequest : WechatTenpayRequest public class AddProfitSharingReceiverRequest : WechatTenpayRequest
{ {
/// <summary> /// <summary>

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [POST] /profitsharing/receivers/add 接口的响应。</para> /// <para>表示 [POST] /profitsharing/receivers/add 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class AddProfitSharingReceiverResponse : WechatTenpayResponse public class AddProfitSharingReceiverResponse : WechatTenpayResponse
{ {
/// <summary> /// <summary>

View File

@ -3,6 +3,7 @@
/// <summary> /// <summary>
/// <para>表示 [POST] /smartguide/guides 接口的请求。</para> /// <para>表示 [POST] /smartguide/guides 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class CreateSmartGuideRequest : WechatTenpayRequest public class CreateSmartGuideRequest : WechatTenpayRequest
{ {
/// <summary> /// <summary>

View File

@ -3,6 +3,7 @@
/// <summary> /// <summary>
/// <para>表示 [GET] /smartguide/guides 接口的请求。</para> /// <para>表示 [GET] /smartguide/guides 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class QuerySmartGuidesRequest : WechatTenpayRequest public class QuerySmartGuidesRequest : WechatTenpayRequest
{ {
/// <summary> /// <summary>

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /smartguide/guides 接口的响应。</para> /// <para>表示 [GET] /smartguide/guides 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class QuerySmartGuidesResponse : WechatTenpayResponse public class QuerySmartGuidesResponse : WechatTenpayResponse
{ {
public static class Types public static class Types

View File

@ -3,6 +3,7 @@
/// <summary> /// <summary>
/// <para>表示 [PATCH] /smartguide/guides/{guide_id} 接口的请求。</para> /// <para>表示 [PATCH] /smartguide/guides/{guide_id} 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class UpdateSmartGuideRequest : WechatTenpayRequest public class UpdateSmartGuideRequest : WechatTenpayRequest
{ {
/// <summary> /// <summary>

View File

@ -5,6 +5,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [POST] /transfer/batches 接口的请求。</para> /// <para>表示 [POST] /transfer/batches 接口的请求。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class CreateTransferBatchRequest : WechatTenpayRequest public class CreateTransferBatchRequest : WechatTenpayRequest
{ {
public static class Types public static class Types

View File

@ -6,6 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// <summary> /// <summary>
/// <para>表示 [GET] /transfer/batches/out-batch-no/{out_batch_no}/details/out-detail-no/{out_detail_no} 接口的响应。</para> /// <para>表示 [GET] /transfer/batches/out-batch-no/{out_batch_no}/details/out-detail-no/{out_detail_no} 接口的响应。</para>
/// </summary> /// </summary>
[WechatTenpaySensitive]
public class GetTransferBatchDetailByOutDetailNumberResponse : WechatTenpayResponse public class GetTransferBatchDetailByOutDetailNumberResponse : WechatTenpayResponse
{ {
/// <summary> /// <summary>

View File

@ -66,6 +66,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
if (objType.IsArray) if (objType.IsArray)
{ {
var array = (obj as Array)!; var array = (obj as Array)!;
if (array.IsReadOnly)
return;
for (int i = 0, len = array.Length; i < len; i++) for (int i = 0, len = array.Length; i < len; i++)
{ {
object? element = array.GetValue(i); object? element = array.GetValue(i);
@ -77,9 +80,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{ {
if (currentProp == null) if (currentProp == null)
continue; continue;
if (!currentProp.CanWrite)
continue;
string oldValue = (string)element!; string oldValue = (string)element!;
string newValue = replacement(obj, currentProp, oldValue); string newValue = replacement(obj!, currentProp, oldValue);
array.SetValue(newValue, i); array.SetValue(newValue, i);
} }
else if (elementType.IsClass) else if (elementType.IsClass)
@ -93,8 +98,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
} }
} }
} }
else if (obj is IList list) else if (obj is IList)
{ {
var list = (obj as IList)!;
if (list.IsReadOnly)
return;
for (int i = 0, len = list.Count; i < len; i++) for (int i = 0, len = list.Count; i < len; i++)
{ {
object? element = list[i]; object? element = list[i];
@ -106,6 +115,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{ {
if (currentProp == null) if (currentProp == null)
continue; continue;
if (!currentProp.CanWrite)
continue;
string oldValue = (string)element!; string oldValue = (string)element!;
string newValue = replacement(obj, currentProp, oldValue); string newValue = replacement(obj, currentProp, oldValue);
@ -122,8 +133,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
} }
} }
} }
else if (obj is IDictionary dict) else if (obj is IDictionary)
{ {
var dict = (obj as IDictionary)!;
if (dict.IsReadOnly)
return;
foreach (DictionaryEntry entry in dict) foreach (DictionaryEntry entry in dict)
{ {
object? entryValue = entry.Value; object? entryValue = entry.Value;
@ -135,6 +150,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{ {
if (currentProp == null) if (currentProp == null)
continue; continue;
if (!currentProp.CanWrite)
continue;
string oldValue = (string)entryValue!; string oldValue = (string)entryValue!;
string newValue = replacement(obj, currentProp, oldValue); string newValue = replacement(obj, currentProp, oldValue);