mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 18:18:37 +00:00
fix(permissions): auto-enrich file_writer metadata on grant from Web UI
This commit is contained in:
1 parent
cadca23cab
commit
e66badb803
4 files changed
+50
-7
No files matched your search
+8
-1
@@ -389,7 +389,7 @@ func runGateway() {
|
||||
|
||||
// Register all RPC methods
|
||||
server.SetLogTee(logTee)
|
||||
pairingMethods, heartbeatMethods, chatMethods := registerAllMethods(server, agentRouter, pgStores.Sessions, pgStores.Cron, pgStores.Pairing, cfg, cfgPath, workspace, dataDir, msgBus, execApprovalMgr, pgStores.Agents, pgStores.Skills, pgStores.ConfigSecrets, pgStores.Teams, contextFileInterceptor, logTee, pgStores.Heartbeats, pgStores.ConfigPermissions, pgStores.SystemConfigs, pgStores.Tenants, pgStores.SkillTenantCfgs, audioMgr)
|
||||
pairingMethods, heartbeatMethods, chatMethods, cfgPermsMethods := registerAllMethods(server, agentRouter, pgStores.Sessions, pgStores.Cron, pgStores.Pairing, cfg, cfgPath, workspace, dataDir, msgBus, execApprovalMgr, pgStores.Agents, pgStores.Skills, pgStores.ConfigSecrets, pgStores.Teams, contextFileInterceptor, logTee, pgStores.Heartbeats, pgStores.ConfigPermissions, pgStores.SystemConfigs, pgStores.Tenants, pgStores.SkillTenantCfgs, audioMgr)
|
||||
|
||||
// Phase 3: Agent hooks RPC methods (hooks.list/create/update/delete/toggle/test/history).
|
||||
if hs, ok := pgStores.Hooks.(hooks.HookStore); ok && hs != nil {
|
||||
@@ -428,6 +428,13 @@ func runGateway() {
|
||||
channelMgr := channels.NewManager(msgBus)
|
||||
deps.channelMgr = channelMgr
|
||||
|
||||
// Wire channel member resolver into permission grant paths (WS + HTTP) so
|
||||
// file_writer grants coming from the Web UI auto-enrich their metadata.
|
||||
cfgPermsMethods.SetMemberResolver(channelMgr)
|
||||
if channelInstancesH != nil {
|
||||
channelInstancesH.SetMemberResolver(channelMgr)
|
||||
}
|
||||
|
||||
// Wire channel sender + tenant checker on message tool (now that channelMgr exists)
|
||||
if t, ok := toolsReg.Get("message"); ok {
|
||||
if cs, ok := t.(tools.ChannelSenderAware); ok {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/nextlevelbuilder/goclaw/internal/tools"
|
||||
)
|
||||
|
||||
func registerAllMethods(server *gateway.Server, agents *agent.Router, sessStore store.SessionStore, cronStore store.CronStore, pairingStore store.PairingStore, cfg *config.Config, cfgPath, workspace, dataDir string, msgBus *bus.MessageBus, execApprovalMgr *tools.ExecApprovalManager, agentStore store.AgentStore, skillStore store.SkillStore, configSecretsStore store.ConfigSecretsStore, teamStore store.TeamStore, contextFileInterceptor *tools.ContextFileInterceptor, logTee *gateway.LogTee, heartbeatStore store.HeartbeatStore, configPermStore store.ConfigPermissionStore, sysConfigStore store.SystemConfigStore, tenantStore store.TenantStore, skillTenantCfgStore store.SkillTenantConfigStore, audioMgr *audio.Manager) (*methods.PairingMethods, *methods.HeartbeatMethods, *methods.ChatMethods) {
|
||||
func registerAllMethods(server *gateway.Server, agents *agent.Router, sessStore store.SessionStore, cronStore store.CronStore, pairingStore store.PairingStore, cfg *config.Config, cfgPath, workspace, dataDir string, msgBus *bus.MessageBus, execApprovalMgr *tools.ExecApprovalManager, agentStore store.AgentStore, skillStore store.SkillStore, configSecretsStore store.ConfigSecretsStore, teamStore store.TeamStore, contextFileInterceptor *tools.ContextFileInterceptor, logTee *gateway.LogTee, heartbeatStore store.HeartbeatStore, configPermStore store.ConfigPermissionStore, sysConfigStore store.SystemConfigStore, tenantStore store.TenantStore, skillTenantCfgStore store.SkillTenantConfigStore, audioMgr *audio.Manager) (*methods.PairingMethods, *methods.HeartbeatMethods, *methods.ChatMethods, *methods.ConfigPermissionsMethods) {
|
||||
router := server.Router()
|
||||
|
||||
// Phase 1: Core methods
|
||||
@@ -77,5 +77,5 @@ func registerAllMethods(server *gateway.Server, agents *agent.Router, sessStore
|
||||
"phase2", []string{"skills", "cron", "heartbeat", "pairing", "usage", "exec_approval", "send"},
|
||||
)
|
||||
|
||||
return pairingMethods, heartbeatMethods, chatMethods
|
||||
return pairingMethods, heartbeatMethods, chatMethods, cfgPerms
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/agent"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/gateway"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
@@ -14,9 +15,10 @@ import (
|
||||
|
||||
// ConfigPermissionsMethods handles config.permissions.* RPC methods.
|
||||
type ConfigPermissionsMethods struct {
|
||||
permStore store.ConfigPermissionStore
|
||||
agentStore store.AgentStore
|
||||
agentRouter *agent.Router // cache-aware agent resolver; nil = DB-only fallback
|
||||
permStore store.ConfigPermissionStore
|
||||
agentStore store.AgentStore
|
||||
agentRouter *agent.Router // cache-aware agent resolver; nil = DB-only fallback
|
||||
memberResolver channels.MemberResolver // optional — enriches file_writer metadata on grant
|
||||
}
|
||||
|
||||
func NewConfigPermissionsMethods(ps store.ConfigPermissionStore, as store.AgentStore) *ConfigPermissionsMethods {
|
||||
@@ -28,6 +30,12 @@ func (m *ConfigPermissionsMethods) SetAgentRouter(r *agent.Router) {
|
||||
m.agentRouter = r
|
||||
}
|
||||
|
||||
// SetMemberResolver wires a channel member resolver so Grant can auto-enrich
|
||||
// file_writer metadata when the caller supplies none (e.g. Web UI path).
|
||||
func (m *ConfigPermissionsMethods) SetMemberResolver(r channels.MemberResolver) {
|
||||
m.memberResolver = r
|
||||
}
|
||||
|
||||
func (m *ConfigPermissionsMethods) Register(router *gateway.MethodRouter) {
|
||||
router.Register(protocol.MethodConfigPermissionsList, m.handleList)
|
||||
router.Register(protocol.MethodConfigPermissionsGrant, m.handleGrant)
|
||||
@@ -110,6 +118,16 @@ func (m *ConfigPermissionsMethods) handleGrant(ctx context.Context, client *gate
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-enrich file_writer metadata for group scopes when caller supplied none.
|
||||
// Best-effort: failure (bot offline, user left group, channel not supported)
|
||||
// leaves params.Metadata as-is and the store's own fallback ("{}") applies.
|
||||
metadata := params.Metadata
|
||||
if params.ConfigType == store.ConfigTypeFileWriter && channels.IsEmptyWriterMetadata(metadata) {
|
||||
if enriched, ok := channels.EnrichFileWriterMetadata(ctx, m.memberResolver, params.Scope, params.UserID); ok {
|
||||
metadata = enriched
|
||||
}
|
||||
}
|
||||
|
||||
perm := &store.ConfigPermission{
|
||||
AgentID: agentUUID,
|
||||
Scope: params.Scope,
|
||||
@@ -117,7 +135,7 @@ func (m *ConfigPermissionsMethods) handleGrant(ctx context.Context, client *gate
|
||||
UserID: params.UserID,
|
||||
Permission: params.Permission,
|
||||
GrantedBy: grantedBy,
|
||||
Metadata: params.Metadata,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
if err := m.permStore.Grant(ctx, perm); err != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/permissions"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
@@ -24,6 +25,7 @@ type ChannelInstancesHandler struct {
|
||||
contactStore store.ContactStore
|
||||
tenantStore store.TenantStore
|
||||
msgBus *bus.MessageBus
|
||||
memberResolver channels.MemberResolver // optional — enriches file_writer metadata on addwriter
|
||||
}
|
||||
|
||||
// NewChannelInstancesHandler creates a handler for channel instance management endpoints.
|
||||
@@ -31,6 +33,12 @@ func NewChannelInstancesHandler(s store.ChannelInstanceStore, agentStore store.A
|
||||
return &ChannelInstancesHandler{store: s, agentStore: agentStore, configPermStore: configPermStore, contactStore: contactStore, tenantStore: tenantStore, msgBus: msgBus}
|
||||
}
|
||||
|
||||
// SetMemberResolver wires a channel member resolver so addwriter can auto-fill
|
||||
// metadata when the caller supplies neither DisplayName nor Username.
|
||||
func (h *ChannelInstancesHandler) SetMemberResolver(r channels.MemberResolver) {
|
||||
h.memberResolver = r
|
||||
}
|
||||
|
||||
// RegisterRoutes registers all channel instance routes on the given mux.
|
||||
func (h *ChannelInstancesHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
// Channel instance CRUD (reads: viewer+, writes: admin+)
|
||||
@@ -63,6 +71,8 @@ func (h *ChannelInstancesHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("GET /v1/channels/instances/{id}/writers", h.auth(h.handleListWriters))
|
||||
mux.HandleFunc("POST /v1/channels/instances/{id}/writers", h.adminAuth(h.handleAddWriter))
|
||||
mux.HandleFunc("DELETE /v1/channels/instances/{id}/writers/{userId}", h.adminAuth(h.handleRemoveWriter))
|
||||
// One-shot legacy-row backfill. Admin-only, idempotent, safe to re-run.
|
||||
mux.HandleFunc("POST /v1/admin/writer-metadata/backfill", h.adminAuth(h.handleBackfillWriterMetadata))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +422,14 @@ func (h *ChannelInstancesHandler) handleAddWriter(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
meta, _ := json.Marshal(map[string]string{"displayName": body.DisplayName, "username": body.Username})
|
||||
// Auto-enrich when caller supplied neither display name nor username.
|
||||
// Best-effort — any resolver error leaves the metadata as-is so the
|
||||
// store's fallback ({}) applies and the grant still succeeds.
|
||||
if body.DisplayName == "" && body.Username == "" {
|
||||
if enriched, ok := channels.EnrichFileWriterMetadata(r.Context(), h.memberResolver, body.GroupID, body.UserID); ok {
|
||||
meta = enriched
|
||||
}
|
||||
}
|
||||
if err := h.configPermStore.Grant(r.Context(), &store.ConfigPermission{
|
||||
AgentID: agentID,
|
||||
Scope: body.GroupID,
|
||||
|
||||
Reference in new issue
Block a user