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

177 lines
6.7 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 登录已失效"
desktop_identity: dict[str, Any] | None = None
# 第一条是新版专用接口;第二条兼容尚未发布桌面终端的线上旧版。
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})"
continue
except (urllib.error.URLError, TimeoutError, socket.timeout) as exc:
raise ZytAuthError(
f"无法连接 ZYT 身份服务:{getattr(exc, 'reason', exc)}"
) from exc
except (ValueError, TypeError, UnicodeDecodeError):
last_error = "ZYT 身份服务返回了无效数据"
continue
if not isinstance(payload, dict) or int(payload.get("code") or 0) != 1:
last_error = str(
payload.get("msg") if isinstance(payload, dict) else ""
) or "ZYT 登录已失效"
continue
data = payload.get("data")
if not isinstance(data, dict):
last_error = "ZYT 身份信息不完整"
continue
if legacy:
admin = data.get("user")
if not isinstance(admin, dict) or not admin.get("id"):
last_error = "ZYT 管理员身份信息不完整"
continue
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),
}
if desktop_identity is not None:
# 专用会话接口负责确认这确实是桌面终端令牌;mySelf 只补充
# 角色/权限快照,不覆盖前者已经校验过的终端身份字段。
desktop_identity.update(
{
key: profile_identity[key]
for key in (
"root",
"role_ids",
"permissions",
"permissions_known",
"account_type",
)
}
)
return desktop_identity
return profile_identity
identity = dict(data)
if not identity.get("user_id"):
last_error = "ZYT 身份信息不完整"
continue
if int(identity.get("terminal") or 0) != WECOM_RPA_TERMINAL:
last_error = "当前令牌不是企业微信客服桌面端令牌"
continue
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))
desktop_identity = identity
# 新版专用接口已完成身份校验、但旧部署的 mySelf 暂时不可用时,桌面登录
# 仍可继续;权限状态标成未知,患者关联功能会按最小权限原则关闭。
if desktop_identity is not None:
return desktop_identity
raise ZytAuthError(last_error)