ForcePilot/backend/package/yuxi/channels/adapters/googlechat/auth.py
Kris ca07c593a8 feat(googlechat): 实现 Google Chat 适配器完整功能集
新增 Google Chat 对接的全套工具模块,包括:
- 会话线程管理、消息解析与格式化
- Pub/Sub 消息解码、提及和命令识别
- 权限审批、目录管理和审计日志
- 消息缓存、媒体上传下载和 SSFR 防护
- 策略配置、卡片构建和流式回复支持
2026-05-12 00:44:18 +08:00

83 lines
2.7 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 Exception as e:
logger.warning(f"Failed to fetch Google Chat certs: {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 Exception as e:
logger.warning(f"project-number token verification failed: {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