ForcePilot/backend/package/yuxi/channels/adapters/feishu/verify.py
Kris a6fa7245e5 feat(feishu): 完整实现飞书适配器核心模块
新增飞书机器人适配器全套功能,包括:
- 基础适配器入口与工具导出
- 消息格式化、卡片渲染、回复调度逻辑
- 会话ID生成、模型覆盖策略
- 消息发送缓存、顺序队列管理
- 飞书签名验证、加解密webhook请求
- 审批权限校验、机器人菜单事件处理
- 文档评论、钉消息、语音转码处理
- 静态/动态目录管理、子代理生命周期管理
- 各类工具集:聊天、云盘、文档、知识库API封装
2026-05-12 00:43:59 +08:00

89 lines
2.5 KiB
Python

from __future__ import annotations
import hashlib
import time
from collections import defaultdict
WEBHOOK_MAX_BODY_BYTES = 10 * 1024 * 1024
WEBHOOK_RATE_WINDOW_S = 60
WEBHOOK_RATE_MAX_REQUESTS = 1000
_rate_counters: defaultdict[str, list[float]] = defaultdict(list)
def verify_feishu_signature(headers: dict, body: bytes, encrypt_key: str = "") -> bool:
if len(body) > WEBHOOK_MAX_BODY_BYTES:
return False
timestamp = headers.get("X-Lark-Request-Timestamp", "")
nonce = headers.get("X-Lark-Request-Nonce", "")
signature = headers.get("X-Lark-Signature", "")
if not timestamp or not nonce or not signature:
return False
raw = f"{timestamp}{nonce}{encrypt_key}".encode() + body
expected = hashlib.sha256(raw).hexdigest()
return signature == expected
def check_webhook_rate_limit(source_ip: str = "default") -> bool:
now = time.monotonic()
window = _rate_counters[source_ip]
window[:] = [t for t in window if now - t < WEBHOOK_RATE_WINDOW_S]
if len(window) >= WEBHOOK_RATE_MAX_REQUESTS:
return False
window.append(now)
return True
def decrypt_feishu_body(encrypted_body: bytes, encrypt_key: str) -> bytes | None:
if not encrypt_key:
return None
import base64
try:
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
except ImportError:
try:
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import unpad
except ImportError:
return None
try:
key = hashlib.sha256(encrypt_key.encode()).digest()
raw = base64.b64decode(encrypted_body)
iv = raw[:16]
ciphertext = raw[16:]
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
return plaintext
except Exception:
return None
def verify_and_decrypt_webhook(
headers: dict,
body: bytes,
encrypt_key: str = "",
source_ip: str = "default",
) -> tuple[bool, bytes | None, str]:
if not check_webhook_rate_limit(source_ip):
return False, None, "rate_limited"
if not verify_feishu_signature(headers, body, encrypt_key):
return False, None, "signature_mismatch"
encrypted = headers.get("X-Lark-Request-Encrypted", "")
if encrypted and encrypt_key:
decrypted = decrypt_feishu_body(body, encrypt_key)
if decrypted is None:
return False, None, "decrypt_failed"
return True, decrypted, "ok"
return True, body, "ok"