Files
kefu/deploy/patient-session-20260918/identity-compatibility/payload/wechat_rpa/zyt_auth.py
T
2026-09-21 10:34:06 +08:00

163 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""ZYT 管理员身份服务适配器。
桌面软件的密码只发送给 ZYT;客服后台拿到管理员 token 后通过最小会话接口验证,
再签发仅能访问客服数据的短期 token。
"""
from __future__ import annotations
import json
import os
import socket
import urllib.error
import urllib.parse
import urllib.request
from typing import Any
WECOM_RPA_TERMINAL = 7
DEFAULT_ZYT_API_BASE_URL = os.environ.get(
"ZYT_API_BASE_URL", "http://127.0.0.1"
).strip().rstrip("/")
class ZytAuthError(RuntimeError):
pass
def api_base_url() -> str:
value = DEFAULT_ZYT_API_BASE_URL
parsed = urllib.parse.urlparse(value)
if parsed.scheme not in {"http", "https"} or not parsed.netloc or parsed.username:
raise ZytAuthError("ZYT_API_BASE_URL 配置不正确")
return value.rstrip("/")
def verify_desktop_token(
token: str, *, api_url: str | None = None, timeout: float = 8.0
) -> dict[str, Any]:
token = str(token or "").strip()
if not token:
raise ZytAuthError("缺少 ZYT 登录令牌")
base_url = str(api_url or api_base_url()).strip().rstrip("/")
parsed = urllib.parse.urlparse(base_url)
if parsed.scheme not in {"http", "https"} or not parsed.netloc or parsed.username:
raise ZytAuthError("ZYT 登录 API 域名配置不正确")
last_error = "ZYT 登录已失效"
# 第一条是新版专用接口;第二条兼容尚未发布桌面终端的线上旧版。
endpoints = (
("/adminapi/desktop/session", False),
("/adminapi/auth.admin/mySelf", True),
)
for path, legacy in endpoints:
request = urllib.request.Request(
f"{base_url}{path}",
headers={
"Accept": "application/json",
"User-Agent": "WeCom-RPA-Backend/1.0",
# ZYT 管理端登录中间件读取 token 头,不是 Bearer。
"token": token,
},
method="GET",
)
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
payload = json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
last_error = f"ZYT 身份服务拒绝请求(HTTP {exc.code})"
# 只兼容尚未部署的路由;鉴权拒绝不能降级为网页身份。
if not legacy and exc.code in {404, 405}:
continue
raise ZytAuthError(last_error) from exc
except (urllib.error.URLError, TimeoutError, socket.timeout) as exc:
raise ZytAuthError(
f"无法连接 ZYT 身份服务:{getattr(exc, 'reason', exc)}"
) from exc
except (ValueError, TypeError, UnicodeDecodeError) as exc:
raise ZytAuthError("ZYT 身份服务返回了无效数据") from exc
if not isinstance(payload, dict):
raise ZytAuthError("ZYT 身份服务返回了无效数据")
if str(payload.get("code")) != "1":
last_error = str(
payload.get("msg") if isinstance(payload, dict) else ""
) or "ZYT 登录已失效"
if legacy and "ip" in last_error.lower() and "变化" in last_error:
raise ZytAuthError(
"ZYT 桌面认证接口尚未部署,旧版接口拒绝跨服务器验证。"
"请管理员部署 /adminapi/desktop/session 后重新登录"
)
raise ZytAuthError(last_error)
data = payload.get("data")
if not isinstance(data, dict):
raise ZytAuthError("ZYT 身份信息不完整")
if legacy:
admin = data.get("user")
if not isinstance(admin, dict) or not admin.get("id"):
raise ZytAuthError("ZYT 管理员身份信息不完整")
if int(admin.get("disable") or 0) != 0:
raise ZytAuthError("ZYT 管理员账号已停用")
permissions_value = data.get("permissions")
permissions = (
sorted(
{
str(item).strip()
for item in permissions_value
if str(item).strip()
}
)
if isinstance(permissions_value, list)
else []
)
role_ids_value = admin.get("role_ids")
role_ids: list[int] = []
if isinstance(role_ids_value, list):
for item in role_ids_value:
try:
role_ids.append(int(item))
except (TypeError, ValueError):
continue
profile_identity = {
"user_id": f"admin:{admin['id']}",
"sn": str(admin.get("account") or ""),
"nickname": str(admin.get("name") or admin.get("account") or ""),
"mobile": str(admin.get("phone") or ""),
"avatar": str(admin.get("avatar") or ""),
"terminal": WECOM_RPA_TERMINAL,
"status": "active",
"account_type": "admin",
"root": bool(int(admin.get("root") or 0)),
"role_ids": sorted(set(role_ids)),
"permissions": permissions,
"permissions_known": isinstance(permissions_value, list),
}
return profile_identity
identity = dict(data)
if not identity.get("user_id"):
raise ZytAuthError("ZYT 身份信息不完整")
if str(identity.get("terminal")) != str(WECOM_RPA_TERMINAL):
raise ZytAuthError("当前令牌不是企业微信客服桌面端令牌")
if str(identity.get("status") or "") != "active":
raise ZytAuthError("ZYT 管理员账号已停用")
permissions_value = identity.get("permissions")
identity["permissions"] = (
sorted(
{
str(item).strip()
for item in permissions_value
if str(item).strip()
}
)
if isinstance(permissions_value, list)
else []
)
identity["permissions_known"] = isinstance(permissions_value, list)
identity["root"] = bool(int(identity.get("root") or 0))
# 新版专用接口完整返回权限;旧版缺权限时保留未知状态,患者功能
# 按最小权限关闭,不再跨 IP 请求网页 mySelf 补权限。
return identity
raise ZytAuthError(last_error)