"""新增场外 Worker 最小权限技术账号。""" from __future__ import annotations from datetime import UTC, datetime from typing import Any from sqlalchemy import text from sqlalchemy.engine import Connection from alembic import op revision = "20260910_offsite_worker_identity" down_revision = "20260910_promotion_material" branch_labels = None depends_on = None WORKER_USER_NO = "OFFSITE-WORKER" WORKER_USERNAME = "offsite_worker" WORKER_PASSWORD_HASH = "!worker-only-no-password-login!" OPERATOR_ROLE_CODE = "operator" OFFSITE_WRITE_PERMISSION = "offsite:write" def upgrade() -> None: connection = op.get_bind() now = datetime.now(UTC).replace(tzinfo=None) worker_user_id = _ensure_worker_user(connection, now) role_id = _ensure_operator_role(connection, now) permission_id = _ensure_offsite_write_permission(connection, now) _ensure_operator_role_is_minimal(connection, role_id) _ensure_role_permission(connection, role_id, permission_id, worker_user_id, now) _ensure_user_role(connection, worker_user_id, role_id, now) _write_audit(connection, worker_user_id, now) def downgrade() -> None: raise RuntimeError("场外 Worker 技术账号属于受控身份数据,禁止自动删除") def _ensure_worker_user(connection: Connection, now: datetime) -> int: rows = connection.execute( text( """ SELECT id, user_no, username, user_type, employee_role, status FROM sys_user WHERE user_no=:user_no OR username=:username """ ), {"user_no": WORKER_USER_NO, "username": WORKER_USERNAME}, ).mappings().all() if len(rows) > 1: raise RuntimeError("场外 Worker 技术账号的 user_no 与 username 分属不同用户") if rows: row = rows[0] _assert_worker_user(row) return int(row["id"]) worker_user_id = _next_user_id(connection) connection.execute( text( """ INSERT INTO sys_user (id, user_no, username, email, password_hash, user_type, employee_role, professional_investor_status, fund_account_status, status, created_at, updated_at) VALUES (:id, :user_no, :username, NULL, :password_hash, '员工', :employee_role, '未申请', '未开户', '正常', :now, :now) """ ), { "id": worker_user_id, "user_no": WORKER_USER_NO, "username": WORKER_USERNAME, "password_hash": WORKER_PASSWORD_HASH, "employee_role": OPERATOR_ROLE_CODE, "now": now, }, ) return worker_user_id def _assert_worker_user(row: Any) -> None: if row["user_no"] != WORKER_USER_NO or row["username"] != WORKER_USERNAME: raise RuntimeError("场外 Worker 技术账号唯一标识冲突") if row["user_type"] != "员工" or row["status"] != "正常": raise RuntimeError("场外 Worker 技术账号不是正常员工账号") if row["employee_role"] != OPERATOR_ROLE_CODE: raise RuntimeError("场外 Worker 技术账号岗位缓存不是 operator") def _next_user_id(connection: Connection) -> int: value = connection.scalar(text("SELECT COALESCE(MAX(id), 1000000000000000) + 1 FROM sys_user")) return int(value) def _ensure_operator_role(connection: Connection, now: datetime) -> int: row = connection.execute( text("SELECT id, status FROM sys_role WHERE role_code=:code"), {"code": OPERATOR_ROLE_CODE}, ).mappings().first() if row is not None: if row["status"] != "active": raise RuntimeError("operator 角色未启用,拒绝绑定场外 Worker") return int(row["id"]) connection.execute( text( """ INSERT INTO sys_role (role_code, role_name, status, created_at, updated_at) VALUES (:code, '运营人员', 'active', :now, :now) """ ), {"code": OPERATOR_ROLE_CODE, "now": now}, ) role_id = connection.scalar( text("SELECT id FROM sys_role WHERE role_code=:code"), {"code": OPERATOR_ROLE_CODE}, ) return int(role_id) def _ensure_offsite_write_permission(connection: Connection, now: datetime) -> int: row = connection.execute( text("SELECT id FROM sys_permission WHERE permission_code=:code"), {"code": OFFSITE_WRITE_PERMISSION}, ).mappings().first() if row is not None: return int(row["id"]) connection.execute( text( """ INSERT INTO sys_permission (permission_code, resource, action, data_scope, field_policy, created_at, updated_at) VALUES (:code, 'offsite_fund', 'write', 'all', NULL, :now, :now) """ ), {"code": OFFSITE_WRITE_PERMISSION, "now": now}, ) permission_id = connection.scalar( text("SELECT id FROM sys_permission WHERE permission_code=:code"), {"code": OFFSITE_WRITE_PERMISSION}, ) return int(permission_id) def _ensure_operator_role_is_minimal(connection: Connection, role_id: int) -> None: rows = connection.execute( text( """ SELECT p.permission_code FROM sys_role_permission rp JOIN sys_permission p ON p.id=rp.permission_id WHERE rp.role_id=:role_id """ ), {"role_id": role_id}, ).mappings().all() permission_codes = {str(row["permission_code"]) for row in rows} extra_permissions = permission_codes - {OFFSITE_WRITE_PERMISSION} if extra_permissions: raise RuntimeError("operator 角色已有额外权限,拒绝作为场外 Worker 最小权限角色") def _ensure_role_permission( connection: Connection, role_id: int, permission_id: int, worker_user_id: int, now: datetime, ) -> None: exists = connection.scalar( text( """ SELECT COUNT(*) FROM sys_role_permission WHERE role_id=:role_id AND permission_id=:permission_id """ ), {"role_id": role_id, "permission_id": permission_id}, ) if int(exists or 0) > 0: return other_user_count = connection.scalar( text( """ SELECT COUNT(*) FROM sys_user_role WHERE role_id=:role_id AND user_id<>:worker_user_id """ ), {"role_id": role_id, "worker_user_id": worker_user_id}, ) if int(other_user_count or 0) > 0: raise RuntimeError("operator 角色已绑定其它用户,拒绝自动扩大 offsite:write 权限") connection.execute( text( """ INSERT INTO sys_role_permission (role_id, permission_id, created_at) VALUES (:role_id, :permission_id, :now) """ ), {"role_id": role_id, "permission_id": permission_id, "now": now}, ) def _ensure_user_role( connection: Connection, worker_user_id: int, role_id: int, now: datetime ) -> None: exists = connection.scalar( text( """ SELECT COUNT(*) FROM sys_user_role WHERE user_id=:user_id AND role_id=:role_id """ ), {"user_id": worker_user_id, "role_id": role_id}, ) if int(exists or 0) > 0: return connection.execute( text( """ INSERT INTO sys_user_role (user_id, role_id, assigned_at, expires_at) VALUES (:user_id, :role_id, :now, NULL) """ ), {"user_id": worker_user_id, "role_id": role_id, "now": now}, ) def _write_audit(connection: Connection, worker_user_id: int, now: datetime) -> None: exists = connection.scalar( text( """ SELECT COUNT(*) FROM interaction_audit WHERE portal='migration' AND action_type='offsite.worker_identity_seeded' AND JSON_UNQUOTE(JSON_EXTRACT(detail, '$.worker_user_id'))=:worker_user_id """ ), {"worker_user_id": str(worker_user_id)}, ) if int(exists or 0) > 0: return connection.execute( text( """ INSERT INTO interaction_audit (actor_type, actor_id, target_customer_id, session_id, portal, action_type, detail, created_at) VALUES ('system', NULL, NULL, NULL, 'migration', 'offsite.worker_identity_seeded', JSON_OBJECT('worker_user_id', :worker_user_id, 'role', :role_code, 'permission', :permission_code), :now) """ ), { "worker_user_id": worker_user_id, "role_code": OPERATOR_ROLE_CODE, "permission_code": OFFSITE_WRITE_PERMISSION, "now": now, }, )