这是一个批量整理提交,包含以下主要改动: 1. 删除多处冗余的空行和未使用的导入 2. 修复文件末尾缺少换行符的问题 3. 调整部分模块的导入顺序与代码排版 4. 修复部分配置默认值与策略逻辑 5. 新增多个功能模块与辅助工具 6. 完善异常处理与日志记录 7. 修复速率限制、消息缓存、权限校验等逻辑bug 8. 废弃部分旧有API与配置项并添加警告提示
92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
|
|
import httpx
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
_CERT_URL = "https://www.googleapis.com/service_accounts/v1/metadata/x509/chat@system.gserviceaccount.com"
|
|
_CERT_CACHE_TTL_S = 600
|
|
_CERT_FETCH_TIMEOUT_S = 10.0
|
|
|
|
|
|
class GoogleChatCertCache:
|
|
def __init__(self, ttl_s: int = _CERT_CACHE_TTL_S):
|
|
self._ttl = ttl_s
|
|
self._cache: dict[str, str] | None = None
|
|
self._cache_at: float = 0.0
|
|
|
|
@property
|
|
def is_valid(self) -> bool:
|
|
return self._cache is not None and (time.monotonic() - self._cache_at) < self._ttl
|
|
|
|
async def get_certs(self) -> dict[str, str]:
|
|
if self.is_valid and self._cache is not None:
|
|
return dict(self._cache)
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=_CERT_FETCH_TIMEOUT_S) as client:
|
|
resp = await client.get(_CERT_URL)
|
|
resp.raise_for_status()
|
|
certs: dict[str, str] = resp.json()
|
|
self._cache = certs
|
|
self._cache_at = time.monotonic()
|
|
logger.info(f"Google Chat cert cache refreshed: {len(certs)} certificates")
|
|
return dict(certs)
|
|
except httpx.HTTPError as e:
|
|
logger.warning(f"Failed to fetch Google Chat certs (network error): {e}")
|
|
if self._cache is not None:
|
|
logger.info("Using stale cert cache")
|
|
return dict(self._cache)
|
|
return {}
|
|
except Exception as e:
|
|
logger.warning(f"Failed to fetch Google Chat certs (unexpected error): {e}")
|
|
if self._cache is not None:
|
|
logger.info("Using stale cert cache")
|
|
return dict(self._cache)
|
|
return {}
|
|
|
|
def clear(self) -> None:
|
|
self._cache = None
|
|
self._cache_at = 0.0
|
|
|
|
|
|
def verify_project_number_token(token: str, project_number: str, certs: dict[str, str]) -> bool:
|
|
try:
|
|
from google.auth import jwt
|
|
except ImportError:
|
|
logger.warning("google-auth not installed, skipping project-number token verification")
|
|
return True
|
|
|
|
if not certs:
|
|
logger.warning("No certificates available for project-number verification")
|
|
return False
|
|
|
|
certs_json = json.dumps(certs)
|
|
try:
|
|
payload = jwt.decode(token, certs=certs_json, audience=project_number)
|
|
issuer = payload.get("iss", "")
|
|
expected_issuer = "chat@system.gserviceaccount.com"
|
|
if issuer != expected_issuer:
|
|
logger.warning(f"project-number token issuer mismatch: expected '{expected_issuer}', got '{issuer}'")
|
|
return False
|
|
return True
|
|
except ValueError as e:
|
|
logger.warning(f"project-number token verification failed (invalid token): {e}")
|
|
return False
|
|
except Exception as e:
|
|
logger.warning(f"project-number token verification failed (unexpected): {e}")
|
|
return False
|
|
|
|
|
|
_global_cert_cache: GoogleChatCertCache | None = None
|
|
|
|
|
|
def get_cert_cache() -> GoogleChatCertCache:
|
|
global _global_cert_cache
|
|
if _global_cert_cache is None:
|
|
_global_cert_cache = GoogleChatCertCache()
|
|
return _global_cert_cache
|