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

205 lines
11 KiB
Python

from pathlib import Path
import shutil
import hashlib
import json
root = Path('C:/kefu/wechat_rpa')
out = Path('C:/kefu/deploy/patient-session-20260918')
manifest = []
def edit(name, transform):
path = root / name
raw = path.read_bytes()
text = raw.decode('utf-8').replace('\r\n', '\n')
changed = transform(text)
backup = out / 'before-backend' / name
backup.parent.mkdir(parents=True, exist_ok=True)
if backup.exists():
raise RuntimeError('Refuse to overwrite backup: ' + str(backup))
shutil.copy2(path, backup)
newline = '\r\n' if b'\r\n' in raw else '\n'
path.write_bytes(changed.replace('\n', newline).encode('utf-8'))
manifest.append({'path': str(path), 'backup': str(backup), 'before_sha256': hashlib.sha256(raw).hexdigest(), 'after_sha256': hashlib.sha256(path.read_bytes()).hexdigest()})
def patient(text):
marker = '\ndef mask_mobile(value: Any) -> str:'
assert marker in text
text = text.replace(marker, '''
class ZytPatientSessionExpired(ZytPatientError):
"""The upstream patient session needs reauthentication, not desktop logout."""
class ZytPatientPermissionDenied(ZytPatientError):
"""The authenticated ZYT account cannot query patients."""
def _session_expired_message(message: str) -> bool:
normalized = str(message or "").lower().replace(" ", "")
return any(part in normalized for part in (
"登录超时", "登录已失效", "登录失效", "登录已过期", "登录过期",
"请重新登录", "请先登录", "token过期", "token已过期", "token失效",
"token已失效", "tokenexpired", "unauthorized",
))
''' + marker, 1)
text = text.replace('raise ZytPatientError("客户端登录已失效,请重新打开软件登录")', 'raise ZytPatientSessionExpired("ZYT 患者查询会话缺失,请重新验证账号")', 1)
old = ''' if exc.code in {401, 403}:
raise ZytPatientError("ZYT 登录或患者查询权限已失效,请重新登录") from exc'''
assert old in text
text = text.replace(old, ''' if exc.code == 401:
raise ZytPatientSessionExpired("ZYT 患者查询登录已过期,请重新验证账号") from exc
if exc.code == 403:
raise ZytPatientPermissionDenied("当前 ZYT 账号没有患者查询权限") from exc''', 1)
old = ''' if not isinstance(payload, dict) or int(payload.get("code") or 0) != 1:
message = str(payload.get("msg") if isinstance(payload, dict) else "").strip()
raise ZytPatientError(message or "ZYT 患者查询失败")'''
assert old in text
text = text.replace(old, ''' if not isinstance(payload, dict) or str(payload.get("code")) != "1":
message = str(payload.get("msg") or "").strip() if isinstance(payload, dict) else ""
if "权限" in message or "无权" in message:
raise ZytPatientPermissionDenied(message)
if (isinstance(payload, dict) and str(payload.get("code")) == "-1") or _session_expired_message(message):
raise ZytPatientSessionExpired(message or "ZYT 患者查询会话已失效")
raise ZytPatientError(message or "ZYT 患者查询失败")''', 1)
return text
def api(text):
text = text.replace('from zyt_patient_client import ZytPatientClient, ZytPatientError', 'from zyt_patient_client import (\n ZytPatientClient, ZytPatientError, ZytPatientSessionExpired, ZytPatientPermissionDenied,\n)', 1)
marker = ' if isinstance(exc, ZytPatientError):'
assert marker in text
text = text.replace(marker, ''' if isinstance(exc, ZytPatientSessionExpired):
return HTTPException(status_code=409, detail={
"code": "zyt_session_expired",
"message": "ZYT 患者查询登录已过期,请重新验证账号。",
})
if isinstance(exc, ZytPatientPermissionDenied):
return HTTPException(status_code=403, detail=str(exc))
''' + marker, 1)
return text
def store(text):
marker = 'from archive_conversation_filter import should_exclude_conversation'
text = text.replace(marker, marker + '\nfrom zyt_patient_client import ZytPatientSessionExpired', 1)
text = text.replace('raise RuntimeError("该客户端账号尚未提供 ZYT 查询会话,请重新打开软件登录")', 'raise ZytPatientSessionExpired("该客户端账号尚未提供 ZYT 查询会话,请重新验证账号")', 1)
text = text.replace('raise RuntimeError("ZYT 查询会话无法解密,请重新打开软件登录") from exc', 'raise ZytPatientSessionExpired("ZYT 查询会话无法解密,请重新验证账号") from exc', 1)
marker = ' def clear_zyt_session(self, tenant_id: str) -> None:'
addition = ''' def refresh_zyt_session(
self, desktop_token: str, account_id: int, tenant_id: str,
zyt_token: str, identity: dict[str, Any],
) -> None:
"""Update upstream credentials and permissions under the existing session.
Recheck revocation after the network verification inside the same write
transaction. Never create, un-revoke, or change ownership of a desktop
account/device/session while refreshing the independent ZYT session.
"""
import time
import secret_box
from admin_backend import token_hash
tenant = safe_scope(tenant_id, label="租户")
zyt_token = str(zyt_token or "").strip()
user_id = str(identity.get("user_id") or "").strip()
if not zyt_token or not user_id:
raise ValueError("ZYT 身份信息不完整")
permissions_raw = identity.get("permissions")
permissions = sorted({str(value).strip() for value in permissions_raw if str(value).strip()}) if isinstance(permissions_raw, list) else []
role_ids = []
for value in identity.get("role_ids") if isinstance(identity.get("role_ids"), list) else []:
try:
role_ids.append(int(value))
except (TypeError, ValueError):
continue
is_root = int(identity.get("root") in (True, 1, "1") or "*" in permissions)
known = int(bool(identity.get("permissions_known", isinstance(permissions_raw, list))))
encrypted = secret_box.encrypt(zyt_token, self.database._secret_key())
stamp = utc_now()
with self.database.connect() as db:
db.execute("BEGIN IMMEDIATE")
account = db.execute(
"""SELECT a.id,a.zyt_user_id,a.tenant_id FROM desktop_sessions s
JOIN desktop_accounts a ON a.id=s.account_id
JOIN desktop_devices d ON d.id=s.device_row_id AND d.account_id=a.id
WHERE s.token_digest=? AND s.revoked_at='' AND s.expires_at>=?
AND a.id=? AND a.tenant_id=? AND a.status='active' AND d.revoked_at=''""",
(token_hash(str(desktop_token or "")), int(time.time()), int(account_id), tenant),
).fetchone()
if account is None:
raise PermissionError("桌面登录已失效,请重新登录")
if str(account["zyt_user_id"]) != user_id:
raise ValueError("请使用当前桌面账号重新验证,不能切换到其他账号")
db.execute(
"""UPDATE desktop_accounts SET zyt_root=?,zyt_role_ids_json=?,
zyt_permissions_json=?,zyt_permissions_known=?,updated_at=? WHERE id=?""",
(is_root, json.dumps(sorted(set(role_ids))), json.dumps(permissions, ensure_ascii=False), known, stamp, int(account_id)),
)
db.execute(
"""INSERT INTO archive_zyt_session (tenant_id,zyt_user_id,token_enc,refreshed_at)
VALUES (?,?,?,?) ON CONFLICT(tenant_id) DO UPDATE SET
zyt_user_id=excluded.zyt_user_id,token_enc=excluded.token_enc,
refreshed_at=excluded.refreshed_at""",
(tenant, user_id, encrypted, stamp),
)
db.commit()
'''
assert marker in text
return text.replace(marker, addition + marker, 1)
def admin(text):
marker = '\nclass DesktopAccountBody(BaseModel):'
assert marker in text
text = text.replace(marker, '''
class DesktopZytSessionBody(BaseModel):
zyt_token: str = Field(min_length=1, max_length=8192, repr=False)
''' + marker, 1)
marker = ' @app.get("/api/v2/desktop/me")'
addition = ''' @app.post("/api/v2/desktop/auth/zyt-session")
async def desktop_refresh_zyt_session(
body: DesktopZytSessionBody, request: Request,
account: Any = Depends(current_desktop),
) -> dict:
raw = request.headers.get("authorization", "")
desktop_token = raw[7:].strip() if raw.lower().startswith("bearer ") else ""
if zyt_token_verifier is not None:
verifier = zyt_token_verifier
else:
login_api_url, _configured = desktop_login_api_url()
verifier = lambda token: zyt_auth.verify_desktop_token(token, api_url=login_api_url)
try:
identity = await asyncio.to_thread(verifier, body.zyt_token.strip())
if (not isinstance(identity, dict) or not identity.get("user_id")
or str(identity.get("terminal")) != str(zyt_auth.WECOM_RPA_TERMINAL)
or str(identity.get("status") or "") != "active"):
raise zyt_auth.ZytAuthError("ZYT 桌面身份验证失败,请重新验证账号")
except zyt_auth.ZytAuthError as exc:
raise HTTPException(status_code=409, detail={
"code": "zyt_session_expired",
"message": "ZYT 患者查询登录已过期,请重新验证账号。",
}) from exc
if str(identity["user_id"]).strip() != str(account["zyt_user_id"]):
raise HTTPException(status_code=403, detail="请使用当前桌面账号重新验证,不能切换到其他账号")
archive_store = getattr(app.state, "archive_store", None)
if archive_store is None:
raise HTTPException(status_code=503, detail="患者查询服务暂不可用")
try:
await asyncio.to_thread(
archive_store.refresh_zyt_session, desktop_token, int(account["id"]),
str(account["tenant_id"]), body.zyt_token, identity,
)
except PermissionError as exc:
raise HTTPException(status_code=401, detail=str(exc)) from exc
except ValueError as exc:
raise HTTPException(status_code=403, detail=str(exc)) from exc
return {"ok": True}
'''
assert marker in text
return text.replace(marker, addition + marker, 1)
for name, transform in (('zyt_patient_client.py', patient), ('archive_api.py', api), ('archive_store.py', store), ('admin_api.py', admin)):
edit(name, transform)
(out / 'backend-manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding='utf-8')
print('Patient session recovery updated in source only; original files backed up.')