完善角色权限与数据隔离
This commit is contained in:
+101
-9
@@ -97,6 +97,90 @@ func (h *Hub) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
// DisconnectUser 断开指定用户的所有 WebSocket 连接并推送通知。
|
||||
func (h *Hub) DisconnectUser(userID uint, message string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
for client := range h.clients {
|
||||
if client.UserID == userID {
|
||||
if message != "" {
|
||||
payload, err := NewEvent("kicked", 0, map[string]string{"message": message})
|
||||
if err == nil {
|
||||
h.send(client, payload)
|
||||
}
|
||||
}
|
||||
delete(h.clients, client)
|
||||
close(client.Send)
|
||||
if client.Conn != nil {
|
||||
client.Conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func roleHasPermission(tenantID uint, role, code string) bool {
|
||||
var roleRecord model.Role
|
||||
if err := model.DB.Where("tenant_id = ? AND code = ?", tenantID, role).First(&roleRecord).Error; err != nil {
|
||||
for _, permissionCode := range model.BuiltinRolePermissionCodes()[role] {
|
||||
if permissionCode == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
var count int64
|
||||
return model.DB.Table("role_permissions rp").
|
||||
Joins("JOIN permissions p ON p.id = rp.permission_id").
|
||||
Where("rp.role_id = ? AND p.code = ?", roleRecord.ID, code).
|
||||
Count(&count).Error == nil && count > 0
|
||||
}
|
||||
|
||||
func roleDataScope(tenantID uint, role, module string) string {
|
||||
defaultScope := model.DefaultRoleDataScopes(role)[module]
|
||||
var roleRecord model.Role
|
||||
if err := model.DB.Where("tenant_id = ? AND code = ?", tenantID, role).First(&roleRecord).Error; err != nil {
|
||||
return defaultScope
|
||||
}
|
||||
var scope model.RoleDataScope
|
||||
if err := model.DB.Where("role_id = ? AND module = ?", roleRecord.ID, module).First(&scope).Error; err != nil {
|
||||
return defaultScope
|
||||
}
|
||||
return scope.Scope
|
||||
}
|
||||
|
||||
// canReceiveSessionEvent 在每次推送前重新校验账号、角色权限和会话数据范围。
|
||||
func canReceiveSessionEvent(client *Client, session *model.Session) bool {
|
||||
if client.Kind != "agent" {
|
||||
return false
|
||||
}
|
||||
// 单元测试未初始化数据库时保留原有内置角色判定;生产环境始终走实时数据库校验。
|
||||
if model.DB == nil {
|
||||
if client.Role == "admin" || client.Role == "supervisor" {
|
||||
return true
|
||||
}
|
||||
return session == nil || session.Status == "waiting" ||
|
||||
(session.AgentID != nil && client.Role == "agent" && *session.AgentID == client.UserID)
|
||||
}
|
||||
var user model.User
|
||||
if err := model.DB.Select("id", "tenant_id", "role", "status").First(&user, client.UserID).Error; err != nil ||
|
||||
user.TenantID != client.TenantID || user.Status == "disabled" {
|
||||
return false
|
||||
}
|
||||
permissionCode := "session.view"
|
||||
module := "session"
|
||||
if session != nil && (session.Status == "ended" || session.Status == "archived") {
|
||||
permissionCode = "chat_history.view"
|
||||
module = "chat_history"
|
||||
}
|
||||
if !roleHasPermission(user.TenantID, user.Role, permissionCode) {
|
||||
return false
|
||||
}
|
||||
if session == nil || roleDataScope(user.TenantID, user.Role, module) == model.DataScopeAll {
|
||||
return true
|
||||
}
|
||||
return session.Status == "waiting" || (session.AgentID != nil && *session.AgentID == user.ID)
|
||||
}
|
||||
|
||||
// Stats 返回当前连接统计(总连接 / 坐席 / 访客)。
|
||||
func (h *Hub) Stats() (total, agents, visitors int) {
|
||||
h.mu.RLock()
|
||||
@@ -134,8 +218,9 @@ func (h *Hub) BroadcastToSession(tenantID, sessionID uint, agentID *uint, messag
|
||||
}
|
||||
continue
|
||||
}
|
||||
if client.Role == "admin" || client.Role == "supervisor" ||
|
||||
(agentID != nil && client.Role == "agent" && client.UserID == *agentID) {
|
||||
if canReceiveSessionEvent(client, &model.Session{
|
||||
TenantID: tenantID, ID: sessionID, AgentID: agentID, Status: "active",
|
||||
}) {
|
||||
h.send(client, message)
|
||||
}
|
||||
}
|
||||
@@ -146,8 +231,19 @@ func (h *Hub) BroadcastToTenantStaff(tenantID uint, message []byte) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
var event Event
|
||||
_ = json.Unmarshal(message, &event)
|
||||
var session *model.Session
|
||||
if event.SessionID != 0 {
|
||||
var current model.Session
|
||||
if err := model.DB.Where("id = ? AND tenant_id = ?", event.SessionID, tenantID).First(¤t).Error; err == nil {
|
||||
session = ¤t
|
||||
} else {
|
||||
session = &model.Session{TenantID: tenantID, ID: event.SessionID, Status: "active"}
|
||||
}
|
||||
}
|
||||
for client := range h.clients {
|
||||
if client.TenantID == tenantID && client.Kind == "agent" {
|
||||
if client.TenantID == tenantID && canReceiveSessionEvent(client, session) {
|
||||
h.send(client, message)
|
||||
}
|
||||
}
|
||||
@@ -173,8 +269,7 @@ func (h *Hub) BroadcastToSessionStaff(tenantID uint, agentID *uint, message []by
|
||||
if client.TenantID != tenantID || client.Kind != "agent" {
|
||||
continue
|
||||
}
|
||||
if client.Role == "admin" || client.Role == "supervisor" ||
|
||||
(agentID != nil && client.Role == "agent" && client.UserID == *agentID) {
|
||||
if canReceiveSessionEvent(client, &model.Session{TenantID: tenantID, AgentID: agentID, Status: "active"}) {
|
||||
h.send(client, message)
|
||||
}
|
||||
}
|
||||
@@ -247,10 +342,7 @@ func handleClientEvent(client *Client, event ClientEvent) {
|
||||
if client.Kind != "agent" || event.Type != "typing" {
|
||||
return
|
||||
}
|
||||
if client.Role == "agent" && (session.AgentID == nil || *session.AgentID != client.UserID) {
|
||||
return
|
||||
}
|
||||
if client.Role != "agent" && client.Role != "admin" && client.Role != "supervisor" {
|
||||
if !canReceiveSessionEvent(client, &session) {
|
||||
return
|
||||
}
|
||||
payload, err := NewEvent("typing", session.ID, map[string]string{"from": "agent"})
|
||||
|
||||
Reference in New Issue
Block a user