e6875f0b4b
- 13-service async plugin framework - Textual TUI with CLI fallback - Plugin hot-reload + permission system - Web management panel (aiohttp) - Bridge-based inter-module communication - 10 regression tests Fixes applied: - PBKDF2-SHA256 auth (was plain SHA256) - Auth bypass removed (was allow-all on fail) - Bare excepts replaced with logged errors - CatFramework/DreamSu -> SenSu naming unified - ServiceManager: health checks + startup_order - Env var credentials (SENSU_ADMIN_PASSWORD etc)
1055 lines
46 KiB
Python
1055 lines
46 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import logging
|
||
import asyncio
|
||
import uuid
|
||
import json
|
||
from typing import Dict, List, Set, Optional
|
||
from pathlib import Path
|
||
import yaml
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class PermissionService:
|
||
"""权限服务"""
|
||
|
||
def __init__(self, config: Dict, tui_service, core_bridge):
|
||
self.config = config
|
||
self.tui_service = tui_service
|
||
self.core_bridge = core_bridge
|
||
self.permission_rules: Dict = {}
|
||
self.granted_permissions: Dict[str, Set[str]] = {}
|
||
self.pending_requests: Dict[str, Dict] = {}
|
||
self.plugin_status: Dict[str, str] = {} # 插件状态跟踪
|
||
self.is_running = False
|
||
|
||
# 配置文件路径
|
||
self.config_dir = Path("config") / "permissions"
|
||
self.granted_file = self.config_dir / "granted_permissions.json"
|
||
self.pending_file = self.config_dir / "pending_requests.json"
|
||
self.plugin_status_file = self.config_dir / "plugin_status.json"
|
||
|
||
# 确保配置目录存在
|
||
self.config_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
logger.debug("PermissionService初始化开始")
|
||
|
||
async def start(self):
|
||
"""启动权限服务"""
|
||
try:
|
||
logger.info("启动权限服务")
|
||
|
||
# 检查核心桥接服务是否可用
|
||
if not self.core_bridge:
|
||
logger.error("核心桥接服务不可用")
|
||
return False
|
||
|
||
# 加载权限规则
|
||
await self._load_permission_rules()
|
||
|
||
# 加载持久化数据
|
||
await self._load_persisted_data()
|
||
|
||
# 订阅权限相关事件
|
||
self.core_bridge.subscribe("permission.request", self._handle_permission_request)
|
||
self.core_bridge.subscribe("permission.grant", self._handle_permission_grant)
|
||
self.core_bridge.subscribe("permission.deny", self._handle_permission_deny)
|
||
self.core_bridge.subscribe("permission.ignore", self._handle_permission_ignore)
|
||
|
||
# 检查TUI服务连接状态
|
||
if self.tui_service:
|
||
logger.debug("TUI服务已连接")
|
||
else:
|
||
logger.warning("TUI服务未连接,权限请求将显示在控制台")
|
||
|
||
self.is_running = True
|
||
logger.info("权限服务启动完成")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"启动权限服务时出错: {str(e)}", exc_info=True)
|
||
return False
|
||
|
||
async def _load_permission_rules(self):
|
||
"""加载权限规则"""
|
||
try:
|
||
rules_path = Path("config") / "permissions" / "permission_rules.yaml"
|
||
if rules_path.exists():
|
||
with open(rules_path, 'r', encoding='utf-8') as f:
|
||
self.permission_rules = yaml.safe_load(f)
|
||
logger.debug(f"加载权限规则: {len(self.permission_rules.get('rules', {}))} 条规则")
|
||
else:
|
||
logger.warning("权限规则文件不存在,使用默认规则")
|
||
self.permission_rules = {
|
||
"rules": {},
|
||
"default_policy": "ask"
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"加载权限规则时出错: {str(e)}", exc_info=True)
|
||
self.permission_rules = {
|
||
"rules": {},
|
||
"default_policy": "ask"
|
||
}
|
||
|
||
async def _load_persisted_data(self):
|
||
"""加载持久化的权限数据"""
|
||
try:
|
||
# 如果配置文件不存在,创建空的配置文件
|
||
if not self.granted_file.exists():
|
||
await self._save_granted_permissions()
|
||
logger.info("创建空的已授予权限文件")
|
||
|
||
if not self.pending_file.exists():
|
||
await self._save_pending_requests()
|
||
logger.info("创建空的待处理请求文件")
|
||
|
||
if not self.plugin_status_file.exists():
|
||
await self._save_plugin_status()
|
||
logger.info("创建空的插件状态文件")
|
||
|
||
# 加载已授予权限
|
||
if self.granted_file.exists():
|
||
with open(self.granted_file, 'r', encoding='utf-8') as f:
|
||
granted_data = json.load(f)
|
||
# 将列表转换回集合
|
||
for plugin, permissions in granted_data.items():
|
||
self.granted_permissions[plugin] = set(permissions)
|
||
logger.debug(f"加载已授予权限: {len(self.granted_permissions)} 个插件")
|
||
|
||
# 加载待处理请求
|
||
if self.pending_file.exists():
|
||
with open(self.pending_file, 'r', encoding='utf-8') as f:
|
||
self.pending_requests = json.load(f)
|
||
logger.debug(f"加载待处理请求: {len(self.pending_requests)} 个")
|
||
|
||
# 加载插件状态
|
||
if self.plugin_status_file.exists():
|
||
with open(self.plugin_status_file, 'r', encoding='utf-8') as f:
|
||
self.plugin_status = json.load(f)
|
||
logger.debug(f"加载插件状态: {len(self.plugin_status)} 个插件")
|
||
|
||
logger.info("权限持久化数据加载完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"加载持久化权限数据时出错: {str(e)}", exc_info=True)
|
||
|
||
async def _save_granted_permissions(self):
|
||
"""保存已授予权限到文件"""
|
||
try:
|
||
# 将集合转换为列表以便JSON序列化
|
||
granted_data = {}
|
||
for plugin, permissions in self.granted_permissions.items():
|
||
granted_data[plugin] = list(permissions)
|
||
|
||
with open(self.granted_file, 'w', encoding='utf-8') as f:
|
||
json.dump(granted_data, f, ensure_ascii=False, indent=2)
|
||
|
||
logger.debug(f"已授予权限已保存: {len(granted_data)} 个插件")
|
||
|
||
except Exception as e:
|
||
logger.error(f"保存已授予权限时出错: {str(e)}", exc_info=True)
|
||
|
||
async def _save_pending_requests(self):
|
||
"""保存待处理请求到文件"""
|
||
try:
|
||
with open(self.pending_file, 'w', encoding='utf-8') as f:
|
||
json.dump(self.pending_requests, f, ensure_ascii=False, indent=2)
|
||
|
||
logger.debug(f"待处理请求已保存: {len(self.pending_requests)} 个")
|
||
|
||
except Exception as e:
|
||
logger.error(f"保存待处理请求时出错: {str(e)}", exc_info=True)
|
||
|
||
async def _save_plugin_status(self):
|
||
"""保存插件状态到文件"""
|
||
try:
|
||
with open(self.plugin_status_file, 'w', encoding='utf-8') as f:
|
||
json.dump(self.plugin_status, f, ensure_ascii=False, indent=2)
|
||
|
||
logger.debug(f"插件状态已保存: {len(self.plugin_status)} 个插件")
|
||
|
||
except Exception as e:
|
||
logger.error(f"保存插件状态时出错: {str(e)}", exc_info=True)
|
||
|
||
async def _save_all_data(self):
|
||
"""保存所有权限数据"""
|
||
try:
|
||
await asyncio.gather(
|
||
self._save_granted_permissions(),
|
||
self._save_pending_requests(),
|
||
self._save_plugin_status()
|
||
)
|
||
logger.debug("所有权限数据已保存")
|
||
except Exception as e:
|
||
logger.error(f"保存权限数据时出错: {str(e)}", exc_info=True)
|
||
|
||
def _handle_permission_request(self, message: Dict):
|
||
"""处理权限请求"""
|
||
try:
|
||
# 从 message 的 data 字段中获取插件名称
|
||
data = message.get('data', {})
|
||
plugin_name = data.get('plugin_name')
|
||
logger.debug(f"传递的消息原文:{message}")
|
||
logger.debug(f"传递的插件名:{plugin_name}")
|
||
requested_permissions = data.get('permissions', [])
|
||
request_id = str(uuid.uuid4())[:8] # 简短的请求ID
|
||
|
||
# 加强插件名称验证
|
||
if not plugin_name or plugin_name == 'None' or plugin_name.strip() == '':
|
||
logger.error(f"无效的插件名称: {repr(plugin_name)}")
|
||
logger.debug(f"完整权限请求消息: {message}")
|
||
return
|
||
|
||
# 验证插件名称格式
|
||
if not self._is_valid_plugin_name(plugin_name):
|
||
logger.error(f"插件名称格式无效: {plugin_name}")
|
||
return
|
||
|
||
# 验证权限列表
|
||
if not requested_permissions or not isinstance(requested_permissions, list):
|
||
logger.warning(f"插件 {plugin_name} 请求的权限列表为空或格式错误")
|
||
requested_permissions = [] # 确保是列表
|
||
|
||
# 存储待处理请求
|
||
self.pending_requests[request_id] = {
|
||
'plugin_name': plugin_name,
|
||
'permissions': requested_permissions,
|
||
'timestamp': asyncio.get_event_loop().time()
|
||
}
|
||
|
||
# 更新插件状态
|
||
self.plugin_status[plugin_name] = "pending"
|
||
|
||
logger.debug(f"处理权限请求: {plugin_name} -> {len(requested_permissions)} 个权限")
|
||
|
||
# 保存数据
|
||
asyncio.create_task(self._save_pending_requests())
|
||
asyncio.create_task(self._save_plugin_status())
|
||
|
||
# 显示用户友好的权限请求界面
|
||
asyncio.create_task(self._delayed_permission_ui(request_id, plugin_name, requested_permissions))
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理权限请求时出错: {str(e)}", exc_info=True)
|
||
|
||
def _is_valid_plugin_name(self, plugin_name: str) -> bool:
|
||
"""验证插件名称是否有效"""
|
||
try:
|
||
if not plugin_name or not isinstance(plugin_name, str):
|
||
return False
|
||
|
||
# 基本格式检查
|
||
if plugin_name.strip() == '':
|
||
return False
|
||
|
||
# 检查常见无效值
|
||
invalid_values = ['None', 'null', 'undefined', '']
|
||
if plugin_name in invalid_values:
|
||
return False
|
||
|
||
# 检查长度限制
|
||
if len(plugin_name) > 100:
|
||
return False
|
||
|
||
# 检查字符有效性(允许字母、数字、下划线、点、连字符)
|
||
import re
|
||
if not re.match(r'^[a-zA-Z0-9_\.\-]+$', plugin_name):
|
||
return False
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"验证插件名称时出错: {str(e)}")
|
||
return False
|
||
|
||
async def _delayed_permission_ui(self, request_id: str, plugin_name: str, permissions: List[str]):
|
||
"""延迟显示权限请求UI,等待TUI就绪"""
|
||
try:
|
||
# 等待TUI服务就绪
|
||
tui_ready = await self.wait_for_tui_ready()
|
||
|
||
if tui_ready:
|
||
logger.debug(f"TUI已就绪,显示权限请求: {plugin_name}")
|
||
await self._show_permission_request_ui(request_id, plugin_name, permissions)
|
||
else:
|
||
# TUI未就绪,使用回退显示
|
||
logger.warning(f"TUI未就绪,使用控制台显示权限请求: {plugin_name}")
|
||
message = f"🔐 插件 {plugin_name} 请求 {len(permissions)} 个权限 (请求ID: {request_id})"
|
||
self._fallback_permission_display(request_id, plugin_name, message)
|
||
|
||
except Exception as e:
|
||
logger.error(f"延迟显示权限UI时出错: {str(e)}", exc_info=True)
|
||
|
||
async def _show_permission_request_ui(self, request_id: str, plugin_name: str, permissions: List[str]):
|
||
"""显示权限请求用户界面 - 优化显示"""
|
||
try:
|
||
# 创建更友好的权限描述
|
||
permission_descriptions = {
|
||
"plugin.example.read": "📖 读取示例插件数据",
|
||
"plugin.example.write": "✏️ 写入示例插件数据",
|
||
"plugin.example.execute": "⚡ 执行示例插件操作",
|
||
"framework.event.subscribe": "📡 订阅框架事件",
|
||
"framework.command.execute": "⌨️ 执行框架命令"
|
||
}
|
||
|
||
# 构建权限列表显示
|
||
permission_list = []
|
||
for perm in permissions:
|
||
desc = permission_descriptions.get(perm, f"🔧 {perm}")
|
||
permission_list.append(f" ✅ {desc}")
|
||
|
||
permission_display = "\n".join(permission_list) if permission_list else " 无具体权限请求"
|
||
|
||
# 显示权限请求界面 - 使用更简洁的格式
|
||
messages = [
|
||
f"🔐 **插件权限请求**",
|
||
f"",
|
||
f"**插件**: {plugin_name}",
|
||
f"**请求权限**:",
|
||
permission_display,
|
||
f"",
|
||
f"**操作选项**:",
|
||
f" 🟢 pmallow {request_id} - 同意所有权限",
|
||
f" 🟡 pmallow {request_id} read,write - 仅同意部分权限",
|
||
f" 🔴 pmdeny {request_id} - 拒绝所有权限",
|
||
f" ⏸️ ignore {request_id} - 暂时忽略",
|
||
f"",
|
||
f"**快捷命令**:",
|
||
f" pmallow all - 同意所有待处理请求",
|
||
f" pmdeny all - 拒绝所有待处理请求"
|
||
f" pmignore all - 忽略所有待处理请求"
|
||
]
|
||
|
||
# 逐行发送消息,确保每行都能正确显示
|
||
if self.tui_service and hasattr(self.tui_service, 'show_message'):
|
||
for line in messages:
|
||
if line.strip(): # 忽略空行
|
||
self.tui_service.show_message(line, "warning", persistent=True)
|
||
await asyncio.sleep(0.1) # 小延迟确保消息顺序
|
||
else:
|
||
# TUI服务不可用,使用控制台输出
|
||
self._fallback_permission_display(request_id, plugin_name, message)
|
||
|
||
except Exception as e:
|
||
logger.error(f"显示权限请求界面时出错: {str(e)}", exc_info=True)
|
||
|
||
def _fallback_permission_display(self, request_id: str, plugin_name: str, message: str):
|
||
"""回退到控制台显示权限请求"""
|
||
try:
|
||
print("\n" + "="*60)
|
||
print(message)
|
||
print("="*60)
|
||
print("🐱 请输入命令处理权限请求:")
|
||
logger.info(f"权限请求已显示在控制台: {plugin_name} -> {request_id}")
|
||
except Exception as e:
|
||
logger.error(f"回退显示权限请求时出错: {str(e)}")
|
||
|
||
def _handle_permission_grant(self, message: Dict):
|
||
"""处理权限授予"""
|
||
try:
|
||
request_id = message.get('request_id')
|
||
granted_permissions = message.get('permissions', [])
|
||
|
||
if request_id in self.pending_requests:
|
||
request = self.pending_requests[request_id]
|
||
plugin_name = request['plugin_name']
|
||
|
||
# 如果未指定具体权限,授予所有请求的权限
|
||
if not granted_permissions:
|
||
granted_permissions = request['permissions']
|
||
|
||
# 授予权限
|
||
asyncio.create_task(self.grant_permissions(plugin_name, granted_permissions))
|
||
|
||
# 更新插件状态
|
||
self.plugin_status[plugin_name] = "granted"
|
||
|
||
# 移除待处理请求
|
||
del self.pending_requests[request_id]
|
||
|
||
# 显示成功消息
|
||
success_msg = f"✅ 已为插件 '{plugin_name}' 授予 {len(granted_permissions)} 个权限"
|
||
if self.tui_service and hasattr(self.tui_service, 'show_message'):
|
||
self.tui_service.show_message(success_msg, "success")
|
||
else:
|
||
print(f"🐱 {success_msg}")
|
||
|
||
logger.info(f"权限授予完成: {plugin_name} -> {granted_permissions}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理权限授予时出错: {str(e)}", exc_info=True)
|
||
|
||
def _handle_permission_deny(self, message: Dict):
|
||
"""处理权限拒绝"""
|
||
try:
|
||
request_id = message.get('request_id')
|
||
|
||
if request_id in self.pending_requests:
|
||
request = self.pending_requests[request_id]
|
||
plugin_name = request['plugin_name']
|
||
|
||
# 更新插件状态
|
||
self.plugin_status[plugin_name] = "denied"
|
||
|
||
# 移除待处理请求
|
||
del self.pending_requests[request_id]
|
||
|
||
# 保存数据
|
||
asyncio.create_task(self._save_pending_requests())
|
||
asyncio.create_task(self._save_plugin_status())
|
||
|
||
# 显示拒绝消息
|
||
deny_msg = f"❌ 已拒绝插件 '{plugin_name}' 的权限请求"
|
||
if self.tui_service and hasattr(self.tui_service, 'show_message'):
|
||
self.tui_service.show_message(deny_msg, "error")
|
||
else:
|
||
print(f"🐱 {deny_msg}")
|
||
|
||
logger.info(f"权限拒绝完成: {plugin_name}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理权限拒绝时出错: {str(e)}", exc_info=True)
|
||
|
||
def _handle_permission_ignore(self, message: Dict):
|
||
"""处理权限忽略"""
|
||
try:
|
||
request_id = message.get('request_id')
|
||
|
||
if request_id in self.pending_requests:
|
||
request = self.pending_requests[request_id]
|
||
plugin_name = request['plugin_name']
|
||
|
||
# 更新插件状态
|
||
self.plugin_status[plugin_name] = "ignored"
|
||
|
||
# 移除待处理请求
|
||
del self.pending_requests[request_id]
|
||
|
||
# 保存数据
|
||
asyncio.create_task(self._save_pending_requests())
|
||
asyncio.create_task(self._save_plugin_status())
|
||
|
||
# 显示忽略消息
|
||
ignore_msg = f"⏸️ 已暂时忽略插件 '{plugin_name}' 的权限请求"
|
||
if self.tui_service and hasattr(self.tui_service, 'show_message'):
|
||
self.tui_service.show_message(ignore_msg, "info")
|
||
else:
|
||
print(f"🐱 {ignore_msg}")
|
||
|
||
logger.info(f"权限请求被忽略: {plugin_name}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理权限忽略时出错: {str(e)}", exc_info=True)
|
||
|
||
async def grant_permissions(self, plugin_name: str, permissions: List[str]):
|
||
"""授予权限"""
|
||
try:
|
||
if plugin_name not in self.granted_permissions:
|
||
self.granted_permissions[plugin_name] = set()
|
||
|
||
for permission in permissions:
|
||
self.granted_permissions[plugin_name].add(permission)
|
||
|
||
# 更新插件状态
|
||
self.plugin_status[plugin_name] = "granted"
|
||
|
||
logger.debug(f"授予权限: {plugin_name} -> {permissions}")
|
||
|
||
# 保存数据
|
||
await asyncio.gather(
|
||
self._save_granted_permissions(),
|
||
self._save_plugin_status()
|
||
)
|
||
|
||
# 通知插件权限已授予
|
||
await self.core_bridge.publish("permission.granted", {
|
||
'plugin_name': plugin_name,
|
||
'permissions': permissions
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"授予权限时出错: {str(e)}", exc_info=True)
|
||
raise
|
||
|
||
def has_permission(self, plugin_name: str, permission: str) -> bool:
|
||
"""检查是否具有权限"""
|
||
try:
|
||
# 检查显式授予的权限
|
||
if plugin_name in self.granted_permissions:
|
||
if permission in self.granted_permissions[plugin_name]:
|
||
return True
|
||
|
||
# 检查权限规则
|
||
rule_key = f"{plugin_name}.{permission}"
|
||
if rule_key in self.permission_rules.get('rules', {}):
|
||
return self.permission_rules['rules'][rule_key] == 'allow'
|
||
|
||
# 默认策略
|
||
default_policy = self.permission_rules.get('default_policy', 'ask')
|
||
return default_policy == 'allow'
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查权限时出错: {str(e)}", exc_info=True)
|
||
return False
|
||
|
||
async def request_permissions(self, plugin_name: str, permissions: List[str]) -> bool:
|
||
"""请求权限 - 非阻塞版本"""
|
||
try:
|
||
logger.debug(f"权限请求: {plugin_name} -> {permissions}")
|
||
|
||
# 首先检查是否已经有所有权限
|
||
if all(self.has_permission(plugin_name, perm) for perm in permissions):
|
||
logger.debug(f"插件 {plugin_name} 已有所有请求的权限")
|
||
return True
|
||
|
||
# 标记插件为等待权限状态
|
||
self.plugin_status[plugin_name] = "pending"
|
||
|
||
# 发布权限请求事件(非阻塞)
|
||
await self.core_bridge.publish("permission.request", {
|
||
'plugin_name': plugin_name,
|
||
'permissions': permissions
|
||
})
|
||
|
||
# 立即返回,不等待用户响应
|
||
# 插件将在权限被授予后通过事件机制得到通知
|
||
logger.debug(f"权限请求已发送,等待用户响应: {plugin_name}")
|
||
return True # 立即返回True,让插件继续加载
|
||
|
||
except Exception as e:
|
||
logger.error(f"请求权限时出错: {str(e)}", exc_info=True)
|
||
return True # 出错时也返回True,避免阻塞插件加载
|
||
|
||
def get_pending_requests(self) -> Dict[str, Dict]:
|
||
"""获取待处理请求"""
|
||
return self.pending_requests.copy()
|
||
|
||
async def process_permission_command(self, command: str, args: List[str]) -> str:
|
||
"""处理权限相关命令"""
|
||
try:
|
||
if command == "pmallow":
|
||
if not args:
|
||
return "❌ 请指定请求ID,如: pmallow abc123"
|
||
|
||
request_id = args[0]
|
||
|
||
if request_id == "all":
|
||
# 同意所有待处理请求
|
||
count = len(self.pending_requests)
|
||
for rid in list(self.pending_requests.keys()):
|
||
self._handle_permission_grant({'request_id': rid})
|
||
return f"✅ 已同意所有 {count} 个待处理权限请求"
|
||
|
||
# 检查特定权限
|
||
specific_permissions = []
|
||
if len(args) > 1:
|
||
specific_permissions = args[1].split(',')
|
||
|
||
self._handle_permission_grant({
|
||
'request_id': request_id,
|
||
'permissions': specific_permissions
|
||
})
|
||
return f"✅ 已处理权限请求 {request_id}"
|
||
|
||
elif command == "pmdeny":
|
||
if not args:
|
||
return "❌ 请指定请求ID,如: pmdeny abc123"
|
||
|
||
request_id = args[0]
|
||
|
||
if request_id == "all":
|
||
# 拒绝所有待处理请求
|
||
count = len(self.pending_requests)
|
||
for rid in list(self.pending_requests.keys()):
|
||
await self._handle_permission_deny({'request_id': rid})
|
||
return f"❌ 已拒绝所有 {count} 个待处理权限请求"
|
||
|
||
await self._handle_permission_deny({'request_id': request_id})
|
||
return f"❌ 已拒绝权限请求 {request_id}"
|
||
|
||
elif command == "pmignore":
|
||
if not args:
|
||
return "❌ 请指定请求ID,如: pmignore abc123"
|
||
|
||
request_id = args[0]
|
||
|
||
if request_id == "all":
|
||
# 忽略所有待处理请求
|
||
count = len(self.pending_requests)
|
||
for rid in list(self.pending_requests.keys()):
|
||
await self._handle_permission_ignore({'request_id': rid})
|
||
return f"⏸️ 已忽略所有 {count} 个待处理权限请求"
|
||
|
||
await self._handle_permission_ignore({'request_id': request_id})
|
||
return f"⏸️ 已忽略权限请求 {request_id}"
|
||
|
||
elif command == "permissions":
|
||
# 显示当前权限状态
|
||
return await self._show_permission_status()
|
||
|
||
elif command == "pm_plugin_status":
|
||
# 显示插件状态
|
||
return await self._show_plugin_status()
|
||
|
||
elif command == "pmpending" or command == "pmrequests":
|
||
# 查询待授权权限请求列表
|
||
return await self._show_pending_requests()
|
||
|
||
elif command == "pmhelp":
|
||
# 显示权限命令帮助
|
||
return self._show_permission_help()
|
||
|
||
elif command == "pmfix":
|
||
# 修复权限状态
|
||
return await self._fix_permission_status()
|
||
|
||
elif command == "pmclean":
|
||
# 清理权限数据
|
||
return await self._clean_permission_data(args)
|
||
|
||
elif command == "pmbackup":
|
||
# 备份权限数据
|
||
return await self._backup_permission_data()
|
||
|
||
elif command == "pmtest":
|
||
# 测试权限配置文件
|
||
return await self._test_permission_config()
|
||
|
||
else:
|
||
return f"❌ 未知权限命令: {command}\n💡 输入 'pmhelp' 查看可用命令"
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理权限命令时出错: {str(e)}", exc_info=True)
|
||
return f"❌ 处理命令时出错: {str(e)}"
|
||
|
||
async def _test_permission_config(self) -> str:
|
||
"""测试权限配置文件"""
|
||
try:
|
||
logger.debug("开始权限配置文件测试")
|
||
result = ["🔧 **权限配置文件测试**"]
|
||
result.append("=" * 50)
|
||
|
||
# 测试配置文件路径
|
||
result.append("📁 **配置文件路径**:")
|
||
result.append(f" 配置目录: {self.config_dir}")
|
||
result.append(f" 已授予权限: {self.granted_file}")
|
||
result.append(f" 待处理请求: {self.pending_file}")
|
||
result.append(f" 插件状态: {self.plugin_status_file}")
|
||
|
||
# 测试文件存在性
|
||
result.append("\n✅ **文件存在性检查**:")
|
||
config_files = [
|
||
("配置目录", self.config_dir, self.config_dir.exists()),
|
||
("已授予权限", self.granted_file, self.granted_file.exists()),
|
||
("待处理请求", self.pending_file, self.pending_file.exists()),
|
||
("插件状态", self.plugin_status_file, self.plugin_status_file.exists())
|
||
]
|
||
|
||
for name, path, exists in config_files:
|
||
status = "✅ 存在" if exists else "❌ 不存在"
|
||
result.append(f" {name}: {status}")
|
||
|
||
# 测试写入权限
|
||
result.append("\n✏️ **写入权限测试**:")
|
||
try:
|
||
test_data = {"test": "test_data", "timestamp": asyncio.get_event_loop().time()}
|
||
with open(self.config_dir / "test_write.json", 'w', encoding='utf-8') as f:
|
||
json.dump(test_data, f, ensure_ascii=False, indent=2)
|
||
|
||
# 读取测试
|
||
with open(self.config_dir / "test_write.json", 'r', encoding='utf-8') as f:
|
||
read_data = json.load(f)
|
||
|
||
# 清理测试文件
|
||
(self.config_dir / "test_write.json").unlink(missing_ok=True)
|
||
|
||
result.append(" ✅ 读写测试: 成功")
|
||
except Exception as e:
|
||
result.append(f" ❌ 读写测试: 失败 - {str(e)}")
|
||
|
||
# 显示当前数据状态
|
||
result.append("\n📊 **当前数据状态**:")
|
||
result.append(f" 已授予权限: {len(self.granted_permissions)} 个插件")
|
||
result.append(f" 待处理请求: {len(self.pending_requests)} 个")
|
||
result.append(f" 插件状态: {len(self.plugin_status)} 个")
|
||
|
||
return "\n".join(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"测试权限配置时出错: {str(e)}", exc_info=True)
|
||
return f"❌ 测试权限配置时出错: {str(e)}"
|
||
|
||
async def _clean_permission_data(self, args: List[str]) -> str:
|
||
"""清理权限数据"""
|
||
try:
|
||
if not args:
|
||
return "❌ 请指定清理类型\n💡 可用选项: expired, all, plugin <插件名>"
|
||
|
||
clean_type = args[0].lower()
|
||
result = []
|
||
|
||
if clean_type == "expired":
|
||
# 清理过期请求(超过24小时)
|
||
current_time = asyncio.get_event_loop().time()
|
||
expired_count = 0
|
||
|
||
for request_id, request in list(self.pending_requests.items()):
|
||
if current_time - request.get('timestamp', 0) > 86400: # 24小时
|
||
plugin_name = request['plugin_name']
|
||
del self.pending_requests[request_id]
|
||
expired_count += 1
|
||
result.append(f"🗑️ 清理过期请求: {request_id} ({plugin_name})")
|
||
|
||
if expired_count > 0:
|
||
await self._save_pending_requests()
|
||
result.insert(0, f"✅ 已清理 {expired_count} 个过期权限请求")
|
||
else:
|
||
result.append("✅ 没有发现过期权限请求")
|
||
|
||
elif clean_type == "all":
|
||
# 清理所有数据
|
||
pending_count = len(self.pending_requests)
|
||
granted_count = len(self.granted_permissions)
|
||
status_count = len(self.plugin_status)
|
||
|
||
self.pending_requests.clear()
|
||
self.granted_permissions.clear()
|
||
self.plugin_status.clear()
|
||
|
||
await self._save_all_data()
|
||
|
||
result = [
|
||
f"✅ 已清理所有权限数据:",
|
||
f" 🗑️ 待处理请求: {pending_count} 个",
|
||
f" 🗑️ 已授予权限: {granted_count} 个插件",
|
||
f" 🗑️ 插件状态: {status_count} 个"
|
||
]
|
||
|
||
elif clean_type == "plugin" and len(args) > 1:
|
||
# 清理特定插件的数据
|
||
plugin_name = args[1]
|
||
cleaned_items = []
|
||
|
||
# 清理待处理请求
|
||
for request_id, request in list(self.pending_requests.items()):
|
||
if request['plugin_name'] == plugin_name:
|
||
del self.pending_requests[request_id]
|
||
cleaned_items.append(f"待处理请求: {request_id}")
|
||
|
||
# 清理已授予权限
|
||
if plugin_name in self.granted_permissions:
|
||
del self.granted_permissions[plugin_name]
|
||
cleaned_items.append("已授予权限")
|
||
|
||
# 清理插件状态
|
||
if plugin_name in self.plugin_status:
|
||
del self.plugin_status[plugin_name]
|
||
cleaned_items.append("插件状态")
|
||
|
||
if cleaned_items:
|
||
await self._save_all_data()
|
||
result = [f"✅ 已清理插件 '{plugin_name}' 的权限数据:"] + cleaned_items
|
||
else:
|
||
result = [f"ℹ️ 未找到插件 '{plugin_name}' 的权限数据"]
|
||
|
||
else:
|
||
return "❌ 无效的清理类型\n💡 可用选项: expired, all, plugin <插件名>"
|
||
|
||
return "\n".join(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理权限数据时出错: {str(e)}", exc_info=True)
|
||
return f"❌ 清理权限数据时出错: {str(e)}"
|
||
|
||
async def _show_permission_status(self) -> str:
|
||
"""显示当前权限状态"""
|
||
try:
|
||
if not self.pending_requests and not self.granted_permissions:
|
||
return "📋 暂无权限请求和授予记录"
|
||
|
||
result = ["📋 **权限状态**"]
|
||
|
||
if self.pending_requests:
|
||
result.append("\n🟡 **待处理请求**:")
|
||
for rid, req in self.pending_requests.items():
|
||
result.append(f" {rid}: {req['plugin_name']} -> {len(req['permissions'])} 个权限")
|
||
|
||
if self.granted_permissions:
|
||
result.append("\n🟢 **已授予权限**:")
|
||
for plugin, perms in self.granted_permissions.items():
|
||
result.append(f" {plugin}: {len(perms)} 个权限")
|
||
|
||
if self.plugin_status:
|
||
result.append("\n🔵 **插件状态**:")
|
||
for plugin, status in self.plugin_status.items():
|
||
status_icon = {
|
||
"granted": "✅",
|
||
"pending": "🟡",
|
||
"denied": "❌",
|
||
"ignored": "⏸️",
|
||
"error": "⚠️"
|
||
}.get(status, "🔵")
|
||
result.append(f" {status_icon} {plugin}: {status}")
|
||
|
||
# 添加配置文件状态
|
||
result.append("\n📁 **配置文件状态**:")
|
||
config_files = [
|
||
("已授予权限", self.granted_file),
|
||
("待处理请求", self.pending_file),
|
||
("插件状态", self.plugin_status_file)
|
||
]
|
||
|
||
for name, file_path in config_files:
|
||
if file_path.exists():
|
||
result.append(f" ✅ {name}: 存在")
|
||
else:
|
||
result.append(f" ❌ {name}: 不存在")
|
||
|
||
return "\n".join(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"显示权限状态时出错: {str(e)}", exc_info=True)
|
||
return f"❌ 显示权限状态时出错: {str(e)}"
|
||
|
||
async def _show_plugin_status(self) -> str:
|
||
"""显示插件状态"""
|
||
try:
|
||
if not self.plugin_status:
|
||
return "📊 暂无插件状态信息"
|
||
|
||
result = ["📊 **插件状态**"]
|
||
for plugin, status in self.plugin_status.items():
|
||
if status == "granted":
|
||
result.append(f" ✅ {plugin}: 权限已授予")
|
||
elif status == "pending":
|
||
result.append(f" 🟡 {plugin}: 等待权限授予")
|
||
elif status == "denied":
|
||
result.append(f" ❌ {plugin}: 权限被拒绝")
|
||
elif status == "ignored":
|
||
result.append(f" ⏸️ {plugin}: 权限请求被忽略")
|
||
elif status == "error":
|
||
result.append(f" ⚠️ {plugin}: 权限错误")
|
||
else:
|
||
result.append(f" 🔵 {plugin}: {status}")
|
||
|
||
return "\n".join(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"显示插件状态时出错: {str(e)}", exc_info=True)
|
||
return f"❌ 显示插件状态时出错: {str(e)}"
|
||
|
||
async def _fix_permission_status(self) -> str:
|
||
"""修复权限状态不一致问题"""
|
||
try:
|
||
fixes_applied = []
|
||
|
||
# 检查插件状态与待处理请求的一致性
|
||
for plugin, status in list(self.plugin_status.items()):
|
||
# 如果插件状态是pending但没有对应的待处理请求
|
||
if status == "pending":
|
||
has_pending_request = False
|
||
for request in self.pending_requests.values():
|
||
if request['plugin_name'] == plugin:
|
||
has_pending_request = True
|
||
break
|
||
|
||
if not has_pending_request:
|
||
# 修复:将状态改为error
|
||
self.plugin_status[plugin] = "error"
|
||
fixes_applied.append(f"🟡 {plugin}: pending → error (无权限请求)")
|
||
|
||
# 清理过期的待处理请求
|
||
current_time = asyncio.get_event_loop().time()
|
||
expired_requests = []
|
||
for request_id, request in list(self.pending_requests.items()):
|
||
# 假设请求超过1小时为过期
|
||
if current_time - request.get('timestamp', 0) > 3600:
|
||
expired_requests.append(request_id)
|
||
|
||
for request_id in expired_requests:
|
||
plugin_name = self.pending_requests[request_id]['plugin_name']
|
||
del self.pending_requests[request_id]
|
||
fixes_applied.append(f"🗑️ 清理过期请求: {request_id} ({plugin_name})")
|
||
|
||
# 保存修复后的数据
|
||
if fixes_applied:
|
||
await self._save_all_data()
|
||
result = ["🔧 **权限状态修复完成**"]
|
||
result.extend(fixes_applied)
|
||
result.append(f"\n✅ 共应用 {len(fixes_applied)} 个修复")
|
||
else:
|
||
result = ["✅ **权限状态正常**", "未发现需要修复的问题"]
|
||
|
||
return "\n".join(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"修复权限状态时出错: {str(e)}", exc_info=True)
|
||
return f"❌ 修复权限状态时出错: {str(e)}"
|
||
|
||
async def _backup_permission_data(self) -> str:
|
||
"""备份权限数据"""
|
||
try:
|
||
backup_dir = self.config_dir / "backups"
|
||
backup_dir.mkdir(exist_ok=True)
|
||
|
||
import datetime
|
||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
|
||
# 备份文件路径
|
||
granted_backup = backup_dir / f"granted_permissions_{timestamp}.json"
|
||
pending_backup = backup_dir / f"pending_requests_{timestamp}.json"
|
||
status_backup = backup_dir / f"plugin_status_{timestamp}.json"
|
||
|
||
# 复制文件
|
||
import shutil
|
||
if self.granted_file.exists():
|
||
shutil.copy2(self.granted_file, granted_backup)
|
||
if self.pending_file.exists():
|
||
shutil.copy2(self.pending_file, pending_backup)
|
||
if self.plugin_status_file.exists():
|
||
shutil.copy2(self.plugin_status_file, status_backup)
|
||
|
||
return f"✅ 权限数据备份完成\n📁 备份位置: {backup_dir}\n⏰ 时间戳: {timestamp}"
|
||
|
||
except Exception as e:
|
||
logger.error(f"备份权限数据时出错: {str(e)}", exc_info=True)
|
||
return f"❌ 备份权限数据时出错: {str(e)}"
|
||
|
||
async def _show_pending_requests(self) -> str:
|
||
"""显示待授权权限请求列表 - 更新为pm前缀"""
|
||
try:
|
||
if not self.pending_requests:
|
||
return "📭 暂无待处理的权限请求"
|
||
|
||
result = ["🟡 **待授权权限请求列表**"]
|
||
result.append("=" * 50)
|
||
|
||
for request_id, request in self.pending_requests.items():
|
||
plugin_name = request['plugin_name']
|
||
permissions = request['permissions']
|
||
|
||
# 创建友好的权限描述
|
||
permission_descriptions = {
|
||
"plugin.example.read": "📖 读取示例插件数据",
|
||
"plugin.example.write": "✏️ 写入示例插件数据",
|
||
"plugin.example.execute": "⚡ 执行示例插件操作",
|
||
"framework.event.subscribe": "📡 订阅框架事件",
|
||
"framework.command.execute": "⌨️ 执行框架命令"
|
||
}
|
||
|
||
# 构建权限列表
|
||
permission_list = []
|
||
for perm in permissions:
|
||
desc = permission_descriptions.get(perm, f"🔧 {perm}")
|
||
permission_list.append(f" • {desc}")
|
||
|
||
permission_display = "\n".join(permission_list)
|
||
|
||
# 添加请求信息
|
||
result.append(f"\n📦 **插件**: {plugin_name}")
|
||
result.append(f"🆔 **请求ID**: {request_id}")
|
||
result.append(f"🔐 **请求权限** ({len(permissions)} 个):")
|
||
result.append(permission_display)
|
||
|
||
# 添加交互指令 - 更新为pm前缀
|
||
result.append(f"\n💡 **交互指令**:")
|
||
result.append(f" 🟢 同意所有权限: pmallow {request_id}")
|
||
result.append(f" 🟡 同意部分权限: pmallow {request_id} read,write")
|
||
result.append(f" 🔴 拒绝所有权限: pmdeny {request_id}")
|
||
result.append(f" ⏸️ 暂时忽略: pmignore {request_id}")
|
||
|
||
result.append("-" * 50)
|
||
|
||
# 添加快捷指令 - 更新为pm前缀
|
||
result.append("\n🚀 **快捷指令**:")
|
||
result.append(" 🟢 同意所有请求: pmallow pmall")
|
||
result.append(" 🔴 拒绝所有请求: pmdeny all")
|
||
result.append(" ⏸️ 忽略所有请求: pmignore all")
|
||
result.append(" 📋 查看权限状态: permissions")
|
||
result.append(" 📊 查看插件状态: pm_plugin_status")
|
||
result.append(" ❓ 查看帮助: pmhelp")
|
||
|
||
return "\n".join(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"显示待处理请求时出错: {str(e)}", exc_info=True)
|
||
return f"❌ 显示待处理请求时出错: {str(e)}"
|
||
|
||
def _show_permission_help(self) -> str:
|
||
"""显示权限命令帮助 - 更新为pm前缀"""
|
||
help_text = """
|
||
🔐 **权限管理命令帮助 (pm前缀)**
|
||
|
||
📋 **查询命令**:
|
||
permissions - 查看权限状态
|
||
pmpending 或 pmrequests - 查看待授权请求列表
|
||
pm_plugin_status - 查看插件权限状态
|
||
|
||
🛠️ **操作命令**:
|
||
pmallow <请求ID> - 同意指定请求的所有权限
|
||
pmallow <请求ID> <权限列表> - 同意指定请求的部分权限
|
||
pmdeny <请求ID> - 拒绝指定请求的所有权限
|
||
。pmignore <请求ID> - 暂时忽略指定请求
|
||
|
||
🚀 **快捷命令**:
|
||
pmallow pmall - 同意所有待处理请求
|
||
pmdeny all - 拒绝所有待处理请求
|
||
pmignore all - 忽略所有待处理请求
|
||
|
||
🔧 **维护命令**:
|
||
pmfix - 修复权限状态不一致问题
|
||
pmtest - 测试权限配置文件
|
||
|
||
📖 **示例**:
|
||
pmallow abc123 - 同意请求ID为abc123的所有权限
|
||
pmallow abc123 read,write - 只同意abc123的读取和写入权限
|
||
pmdeny abc123 - 拒绝abc123的所有权限
|
||
pmignore abc123 - 暂时忽略abc123的请求
|
||
pmpending - 查看所有待处理的权限请求
|
||
|
||
💡 **提示**:
|
||
• 权限请求ID是自动生成的8位字符串
|
||
• 使用 pmpending 命令查看所有待处理请求及其ID
|
||
• 插件在获得权限前可能以受限模式运行
|
||
• 权限管理命令都以 `pm` 为前缀,避免与其他命令冲突
|
||
"""
|
||
return help_text.strip()
|
||
|
||
def is_tui_ready(self) -> bool:
|
||
"""检查TUI服务是否就绪"""
|
||
try:
|
||
return (self.tui_service is not None and
|
||
hasattr(self.tui_service, 'show_message') and
|
||
hasattr(self.tui_service, 'tui_app') and
|
||
self.tui_service.tui_app is not None)
|
||
except Exception as e:
|
||
logger.debug(f"检查TUI状态时出错: {e}")
|
||
return False
|
||
|
||
async def wait_for_tui_ready(self, timeout: float = 10.0) -> bool:
|
||
"""等待TUI服务就绪"""
|
||
try:
|
||
start_time = asyncio.get_event_loop().time()
|
||
while asyncio.get_event_loop().time() - start_time < timeout:
|
||
if self.is_tui_ready():
|
||
logger.debug("TUI服务已就绪")
|
||
return True
|
||
await asyncio.sleep(0.5)
|
||
|
||
logger.warning(f"等待TUI服务就绪超时 ({timeout}秒)")
|
||
return False
|
||
except Exception as e:
|
||
logger.error(f"等待TUI就绪时出错: {e}")
|
||
return False
|
||
|
||
def shutdown(self):
|
||
"""关闭权限服务"""
|
||
try:
|
||
logger.info("关闭权限服务")
|
||
self.is_running = False
|
||
self.pending_requests.clear()
|
||
logger.debug("权限服务关闭完成")
|
||
except Exception as e:
|
||
logger.error(f"关闭权限服务时出错: {str(e)}", exc_info=True)
|