Files
HomeAgent/internal/devicebridge/client/cmdrouter.go
JianFeeeee ba5785036a feat: 设备鉴权迁移至客户端 + 插件卸载保护
安全修复(客户端鉴权):
- remotedevice 服务端移除授权状态存储(authorized map/SetAuthorized/handleDeviceAuth)
- DeviceMeta.Authorized 改为设备 hello 自报,服务端仅透传展示
- device_ctl_* 工具移除服务端授权检查,无条件转发,设备端自行决定是否执行
- 共享设备桥库 Bridge 新增本地 authorized 状态,未授权收到 cmd 直接拒绝
- waiter: --device-authorized / device_authorized 配置控制本地授权
- GUI: 授权存 gui-prefs 本地文件;设备页仅本机可切换开关
- webui /device/auth 旧路径返回 410 Gone
- 根因:agent 可经 config_set 篡改服务端授权配置自行授权设备

插件管理强化:
- 内置插件禁止卸载(IsBuiltinPlugin + 409),外部插件卸载即时生效
- 卸载不存在插件返回 404;移除误导性 reload_required 提示
- webui 插件路由:名称白名单校验防路径穿越、保留字路径保护
2026-08-24 19:26:11 +08:00

121 lines
3.0 KiB
Go
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.

package client
import (
"encoding/json"
"fmt"
"strings"
"sync"
)
// CmdRouter 命令路由器,支持按命令前缀分发到不同 handler。
// 用于 CLI 和 GUI 根据能力类型注册不同的执行函数。
type CmdRouter struct {
mu sync.RWMutex
prefixes map[string]CmdHandler
default_ CmdHandler
}
// NewCmdRouter 创建命令路由器。
func NewCmdRouter() *CmdRouter {
return &CmdRouter{
prefixes: make(map[string]CmdHandler),
}
}
// Handle 注册匹配指定前缀的命令处理器。
// 例如 Handle("homeagent-", homeagentHandler) 会处理所有 homeagent-* 命令。
func (r *CmdRouter) Handle(prefix string, handler CmdHandler) {
r.mu.Lock()
defer r.mu.Unlock()
r.prefixes[prefix] = handler
}
// HandleDefault 注册默认命令处理器(无前缀匹配时使用)。
func (r *CmdRouter) HandleDefault(handler CmdHandler) {
r.mu.Lock()
defer r.mu.Unlock()
r.default_ = handler
}
// Dispatch 分发命令到匹配的处理器。
// 返回 true 表示已处理false 表示无匹配。
func (r *CmdRouter) Dispatch(reqID, command string) bool {
r.mu.RLock()
defer r.mu.RUnlock()
// 先按前缀匹配
for prefix, handler := range r.prefixes {
if strings.HasPrefix(command, prefix) {
handler(reqID, command)
return true
}
}
// 无前缀匹配,使用默认
if r.default_ != nil {
r.default_(reqID, command)
return true
}
return false
}
// ===== 能力解析辅助 =====
// ParseHomeagentCmd 解析 homeagent-* 命令,返回能力名和参数。
// 例如 "homeagent-screensue 5 你好" → ("screensue", "5 你好")
// 也支持 "screensue 5 你好"(无前缀)
func ParseHomeagentCmd(command string) (capability, args string) {
cmd := strings.TrimSpace(command)
// 去掉 homeagent- 前缀
cmd = strings.TrimPrefix(cmd, "homeagent-")
parts := strings.SplitN(cmd, " ", 2)
capability = parts[0]
if len(parts) > 1 {
args = parts[1]
}
return
}
// ParseJSONCmd 解析 JSON 格式的命令参数。
// 例如 "computeruse {\"x\":100,\"y\":200,\"action\":\"click\"}"
// 返回动作名和参数 map。
func ParseJSONCmd(command string) (action string, params map[string]interface{}, err error) {
cmd := strings.TrimSpace(command)
// 去掉 homeagent- 前缀
cmd = strings.TrimPrefix(cmd, "homeagent-")
idx := strings.IndexByte(cmd, '{')
if idx < 0 {
action = cmd
return
}
action = strings.TrimSpace(cmd[:idx])
jsonStr := cmd[idx:]
if err = json.Unmarshal([]byte(jsonStr), &params); err != nil {
err = fmt.Errorf("parse json params: %w", err)
}
return
}
// BaseResult 构造基础命令结果。
func BaseResult(reqID, status, output, errMsg string) map[string]interface{} {
res := map[string]interface{}{
"op": "cmd_result",
"req_id": reqID,
"status": status,
}
if output != "" {
res["output"] = output
}
if errMsg != "" {
res["error"] = errMsg
}
return res
}
// ResultJSON 序列化结果 map 为 JSON。
func ResultJSON(res map[string]interface{}) string {
b, _ := json.Marshal(res)
return string(b)
}