新增淘宝(Taobao)渠道扩展,支持在 Yuxi 平台中集成淘宝电商客服渠道。 包含以下功能模块: - client: 淘宝 API 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - message: 消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - crypto: 加解密处理 - status: 会话状态管理 - tools: Agent 工具集成 - types: 类型定义
64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
import base64
|
|
import hashlib
|
|
import hmac as hmac_module
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
try:
|
|
from Crypto.Cipher import AES, PKCS1_v1_5
|
|
from Crypto.PublicKey import RSA
|
|
|
|
_CRYPTO_AVAILABLE = True
|
|
except ImportError:
|
|
_CRYPTO_AVAILABLE = False
|
|
|
|
|
|
def verify_top_sign(params: dict, secret: str, expected_sign: str, sign_method: str = "md5") -> bool:
|
|
sorted_keys = sorted(k for k in params if k != "sign" and params[k] is not None)
|
|
raw = "".join(f"{k}{params[k]}" for k in sorted_keys)
|
|
raw = f"{secret}{raw}{secret}"
|
|
|
|
if sign_method == "hmac-sha256":
|
|
computed = hmac_module.new(secret.encode("utf-8"), raw.encode("utf-8"), hashlib.sha256).hexdigest().upper()
|
|
else:
|
|
computed = hashlib.md5(raw.encode("utf-8")).hexdigest().upper()
|
|
|
|
return computed == expected_sign
|
|
|
|
|
|
def decrypt_message(
|
|
encrypt_data: str,
|
|
encrypt_key: str,
|
|
sign: str,
|
|
timestamp: str,
|
|
app_secret: str,
|
|
rsa_private_key_pem: str,
|
|
) -> str | None:
|
|
if not _CRYPTO_AVAILABLE:
|
|
logger.error("pycryptodome not installed, cannot decrypt TOP message")
|
|
return None
|
|
|
|
try:
|
|
rsa_key = RSA.import_key(rsa_private_key_pem)
|
|
cipher_rsa = PKCS1_v1_5.new(rsa_key)
|
|
session_key = cipher_rsa.decrypt(base64.b64decode(encrypt_key), None)
|
|
if session_key is None:
|
|
logger.error("RSA decrypt session key failed")
|
|
return None
|
|
|
|
expected_sign = hashlib.sha256(
|
|
f"{timestamp}{app_secret}{session_key.decode('utf-8')}{encrypt_data}".encode()
|
|
).hexdigest()
|
|
if sign != expected_sign:
|
|
logger.error("Message sign verification failed")
|
|
return None
|
|
|
|
cipher_aes = AES.new(session_key[:16], AES.MODE_CBC, session_key[:16])
|
|
plain = cipher_aes.decrypt(base64.b64decode(encrypt_data))
|
|
pad_len = plain[-1]
|
|
return plain[:-pad_len].decode("utf-8")
|
|
except Exception as e:
|
|
logger.exception("Message decryption failed: %s", e)
|
|
return None
|