mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
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 插件路由:名称白名单校验防路径穿越、保留字路径保护
This commit is contained in:
@ -17,6 +17,8 @@ import (
|
||||
)
|
||||
|
||||
// DeviceMeta 描述一台接入了网关的设备。
|
||||
// Authorized 设备自报(由客户端存储和声明),服务端仅报告不决策。
|
||||
// 鉴权在设备端执行:服务端推送命令后,设备自行决定是否执行。
|
||||
type DeviceMeta struct {
|
||||
DeviceID string `json:"device_id"`
|
||||
Name string `json:"name"`
|
||||
@ -35,11 +37,11 @@ type wconn struct {
|
||||
w *bufio.Writer
|
||||
}
|
||||
|
||||
// Registry 是设备接入网关的注册表:管理在线连接、设备元数据与已授权集合。线程安全。
|
||||
// Registry 是设备接入网关的注册表:管理在线连接、设备元数据。线程安全。
|
||||
// 鉴权在设备端执行,服务端不存储授权状态。
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
devices map[string]*DeviceMeta // deviceID -> meta(在线/历史)
|
||||
authorized map[string]bool // deviceID -> 是否已授权(持久化恢复)
|
||||
conns map[string]*wconn // deviceID -> 活跃连接(支持 push)
|
||||
onlineCh chan string
|
||||
onStatus func(msg map[string]interface{})
|
||||
@ -62,9 +64,9 @@ type resultEntry struct {
|
||||
// (如旧版 GUI/waiter)视为全能力,保持向后兼容。
|
||||
var capabilityTools = map[string][]string{
|
||||
// 屏幕显示/查看
|
||||
"screen": {"screensue", "screensee"},
|
||||
"screensue": {"screensue"},
|
||||
"screensee": {"screensee"},
|
||||
"screen": {"screensue", "screensee"},
|
||||
"screensue": {"screensue"},
|
||||
"screensee": {"screensee"},
|
||||
// 鼠标键盘操控
|
||||
"computeruse": {"computeruse"},
|
||||
// 剪切板
|
||||
@ -121,7 +123,6 @@ func deviceSupportsTool(caps []string, tool string) bool {
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
devices: make(map[string]*DeviceMeta),
|
||||
authorized: make(map[string]bool),
|
||||
conns: make(map[string]*wconn),
|
||||
onlineCh: make(chan string, 16),
|
||||
cmdPending: make(map[string]chan map[string]interface{}),
|
||||
@ -172,21 +173,15 @@ func (r *Registry) Online(id string) bool {
|
||||
return ok && m.Online
|
||||
}
|
||||
|
||||
// Authorized 返回设备是否已授权。
|
||||
func (r *Registry) Authorized(id string) bool {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.authorized[id]
|
||||
}
|
||||
// Authorized 已移除:授权状态由设备端自报(DeviceMeta.Authorized),服务端不存储。
|
||||
|
||||
// List 返回全部设备(在线或历史),合并授权态。
|
||||
// List 返回全部设备(在线或历史)。
|
||||
func (r *Registry) List() []DeviceMeta {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
out := make([]DeviceMeta, 0, len(r.devices))
|
||||
for _, m := range r.devices {
|
||||
c := *m
|
||||
c.Authorized = r.authorized[c.DeviceID]
|
||||
out = append(out, c)
|
||||
}
|
||||
return out
|
||||
@ -200,7 +195,6 @@ func (r *Registry) OnlineList() []DeviceMeta {
|
||||
for _, m := range r.devices {
|
||||
if m.Online {
|
||||
c := *m
|
||||
c.Authorized = r.authorized[c.DeviceID]
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
@ -215,44 +209,12 @@ func (r *Registry) Get(id string) (DeviceMeta, bool) {
|
||||
if !ok {
|
||||
return DeviceMeta{}, false
|
||||
}
|
||||
c := *m
|
||||
c.Authorized = r.authorized[id]
|
||||
return c, true
|
||||
return *m, true
|
||||
}
|
||||
|
||||
// ============ 授权 ============
|
||||
|
||||
// SetAuthorized 标记某设备已授权/取消授权(持久化由插件负责)。
|
||||
func (r *Registry) SetAuthorized(id string, auth bool) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.authorized[id] = auth
|
||||
if m, ok := r.devices[id]; ok {
|
||||
m.Authorized = auth
|
||||
}
|
||||
}
|
||||
|
||||
// RestoreAuthorized 插件启动时从配置恢复已授权设备集合。
|
||||
func (r *Registry) RestoreAuthorized(ids []string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
for _, id := range ids {
|
||||
r.authorized[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
// AuthorizedIDs 返回全部已授权设备 ID(供插件持久化)。
|
||||
func (r *Registry) AuthorizedIDs() []string {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
var out []string
|
||||
for id, ok := range r.authorized {
|
||||
if ok {
|
||||
out = append(out, id)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
// 已移除服务端授权存储:设备在 hello/status 中自报 authorized,
|
||||
// 服务端仅透传展示;实际鉴权由设备端执行(收到 cmd 后自行决定是否执行)。
|
||||
|
||||
// ============ 在线状态维护 ============
|
||||
|
||||
@ -260,7 +222,7 @@ func (r *Registry) register(meta DeviceMeta) {
|
||||
r.mu.Lock()
|
||||
meta.Online = true
|
||||
meta.LastSeen = time.Now().Unix()
|
||||
meta.Authorized = r.authorized[meta.DeviceID]
|
||||
// 保留设备自报的授权状态(客户端鉴权,服务端不覆盖)
|
||||
r.devices[meta.DeviceID] = &meta
|
||||
r.mu.Unlock()
|
||||
r.notifyChange(meta.DeviceID)
|
||||
@ -335,7 +297,9 @@ func (r *Registry) PushCmd(deviceID, reqID, command, cmdType string) error {
|
||||
|
||||
// PushData 向设备分块下发二进制数据(网关→设备,如 TTS 音频)。
|
||||
// 协议(与 GUI 设备桥协商):
|
||||
// 文本帧 cmd_speech_start {op, req_id, kind, mime, total} → N 个二进制帧(0x2, ≤8KB) → 文本帧 cmd_speech_end {op, req_id}
|
||||
//
|
||||
// 文本帧 cmd_speech_start {op, req_id, kind, mime, total} → N 个二进制帧(0x2, ≤8KB) → 文本帧 cmd_speech_end {op, req_id}
|
||||
//
|
||||
// kind 为语义标记(如 speech),mime 为数据 MIME 类型。设备聚合后按自身能力处理(播放等)。
|
||||
func (r *Registry) PushData(deviceID, reqID, kind, mime string, data []byte) error {
|
||||
r.mu.RLock()
|
||||
@ -813,6 +777,9 @@ func metaFromMsg(msg map[string]interface{}) DeviceMeta {
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := d["authorized"].(bool); ok {
|
||||
meta.Authorized = v
|
||||
}
|
||||
if info, ok := d["info"].(map[string]interface{}); ok {
|
||||
if len(info) > 0 {
|
||||
meta.Info = info
|
||||
|
||||
Reference in New Issue
Block a user