mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-14 07:57:26 +02:00
377 lines
11 KiB
Go
377 lines
11 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"cyberstrike-ai/internal/audit"
|
|
"cyberstrike-ai/internal/database"
|
|
"cyberstrike-ai/internal/security"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type RBACHandler struct {
|
|
db *database.DB
|
|
logger *zap.Logger
|
|
audit *audit.Service
|
|
auth *security.AuthManager
|
|
}
|
|
|
|
func NewRBACHandler(db *database.DB, logger *zap.Logger) *RBACHandler {
|
|
return &RBACHandler{db: db, logger: logger}
|
|
}
|
|
|
|
func (h *RBACHandler) SetAudit(s *audit.Service) {
|
|
h.audit = s
|
|
}
|
|
|
|
func (h *RBACHandler) SetAuthManager(m *security.AuthManager) {
|
|
h.auth = m
|
|
}
|
|
|
|
func (h *RBACHandler) Me(c *gin.Context) {
|
|
session, _ := security.CurrentSession(c)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"user": gin.H{
|
|
"id": session.UserID,
|
|
"username": session.Username,
|
|
"display_name": session.DisplayName,
|
|
},
|
|
"roles": session.Roles,
|
|
"permissions": permissionKeys(session.Permissions),
|
|
"scope": session.Scope,
|
|
})
|
|
}
|
|
|
|
func (h *RBACHandler) Metadata(c *gin.Context) {
|
|
roles, err := h.db.ListRBACRoles()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
rolePermissions := map[string][]string{}
|
|
for _, role := range roles {
|
|
keys, _ := h.db.ListRBACRolePermissionKeys(role.ID)
|
|
rolePermissions[role.ID] = keys
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"permissions": security.PermissionCatalog,
|
|
"roles": roles,
|
|
"role_permissions": rolePermissions,
|
|
"scopes": []string{database.RBACScopeAll, database.RBACScopeAssigned, database.RBACScopeOwn},
|
|
})
|
|
}
|
|
|
|
func (h *RBACHandler) ListRoles(c *gin.Context) {
|
|
roles, err := h.db.ListRBACRoles()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
out := make([]gin.H, 0, len(roles))
|
|
for _, role := range roles {
|
|
keys, _ := h.db.ListRBACRolePermissionKeys(role.ID)
|
|
out = append(out, gin.H{
|
|
"id": role.ID,
|
|
"name": role.Name,
|
|
"description": role.Description,
|
|
"scope": role.Scope,
|
|
"is_system": role.IsSystem,
|
|
"permissions": keys,
|
|
"created_at": role.CreatedAt,
|
|
"updated_at": role.UpdatedAt,
|
|
})
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"roles": out})
|
|
}
|
|
|
|
type upsertRBACRoleRequest struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description"`
|
|
Scope string `json:"scope"`
|
|
Permissions []string `json:"permissions"`
|
|
}
|
|
|
|
func (h *RBACHandler) CreateRole(c *gin.Context) {
|
|
var req upsertRBACRoleRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
role, err := h.db.UpsertRBACRole("", req.Name, req.Description, req.Scope, req.Permissions)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if h.audit != nil {
|
|
h.audit.RecordOK(c, "rbac", "create_role", "创建平台角色", "role", role.ID, nil)
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"role": role})
|
|
}
|
|
|
|
func (h *RBACHandler) UpdateRole(c *gin.Context) {
|
|
id := strings.TrimSpace(c.Param("id"))
|
|
existing, err := h.db.GetRBACRoleByID(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "角色不存在"})
|
|
return
|
|
}
|
|
if existing.IsSystem {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "系统内置角色不可修改,请创建自定义角色"})
|
|
return
|
|
}
|
|
var req upsertRBACRoleRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
role, err := h.db.UpsertRBACRole(id, req.Name, req.Description, req.Scope, req.Permissions)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if existing.IsSystem {
|
|
role.IsSystem = true
|
|
}
|
|
if h.audit != nil {
|
|
h.audit.RecordOK(c, "rbac", "update_role", "更新平台角色", "role", id, nil)
|
|
}
|
|
if h.auth != nil {
|
|
h.auth.RevokeAllSessions()
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"role": role})
|
|
}
|
|
|
|
func (h *RBACHandler) DeleteRole(c *gin.Context) {
|
|
id := strings.TrimSpace(c.Param("id"))
|
|
if err := h.db.DeleteRBACRole(id); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if h.audit != nil {
|
|
h.audit.RecordOK(c, "rbac", "delete_role", "删除平台角色", "role", id, nil)
|
|
}
|
|
if h.auth != nil {
|
|
h.auth.RevokeAllSessions()
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
func (h *RBACHandler) ListUsers(c *gin.Context) {
|
|
users, err := h.db.ListRBACUsers()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
out := make([]gin.H, 0, len(users))
|
|
for _, user := range users {
|
|
roleIDs, _ := h.db.ListRBACUserRoleIDs(user.ID)
|
|
out = append(out, gin.H{
|
|
"id": user.ID,
|
|
"username": user.Username,
|
|
"display_name": user.DisplayName,
|
|
"enabled": user.Enabled,
|
|
"is_builtin": user.IsBuiltin,
|
|
"roles": roleIDs,
|
|
"created_at": user.CreatedAt,
|
|
"updated_at": user.UpdatedAt,
|
|
})
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"users": out})
|
|
}
|
|
|
|
type createRBACUserRequest struct {
|
|
Username string `json:"username" binding:"required"`
|
|
DisplayName string `json:"display_name"`
|
|
Password string `json:"password" binding:"required"`
|
|
Enabled *bool `json:"enabled"`
|
|
Roles []string `json:"roles"`
|
|
}
|
|
|
|
func (h *RBACHandler) CreateUser(c *gin.Context) {
|
|
var req createRBACUserRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if len(strings.TrimSpace(req.Password)) < 8 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "密码长度至少需要 8 位"})
|
|
return
|
|
}
|
|
hash, err := security.HashPassword(req.Password)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
enabled := true
|
|
if req.Enabled != nil {
|
|
enabled = *req.Enabled
|
|
}
|
|
user, err := h.db.CreateRBACUser(req.Username, req.DisplayName, hash, enabled, req.Roles)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if h.audit != nil {
|
|
h.audit.RecordOK(c, "rbac", "create_user", "创建平台用户", "user", user.ID, map[string]interface{}{"username": user.Username})
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"user": user})
|
|
}
|
|
|
|
type updateRBACUserRequest struct {
|
|
DisplayName *string `json:"display_name"`
|
|
Password *string `json:"password"`
|
|
Enabled *bool `json:"enabled"`
|
|
Roles *[]string `json:"roles"`
|
|
}
|
|
|
|
func (h *RBACHandler) UpdateUser(c *gin.Context) {
|
|
id := strings.TrimSpace(c.Param("id"))
|
|
user, err := h.db.GetRBACUserByID(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
|
return
|
|
}
|
|
var req updateRBACUserRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
displayName := user.DisplayName
|
|
if req.DisplayName != nil {
|
|
displayName = *req.DisplayName
|
|
}
|
|
if err := h.db.UpdateRBACUser(id, displayName, req.Enabled, req.Roles); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if req.Password != nil && strings.TrimSpace(*req.Password) != "" {
|
|
if len(strings.TrimSpace(*req.Password)) < 8 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "密码长度至少需要 8 位"})
|
|
return
|
|
}
|
|
hash, err := security.HashPassword(*req.Password)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.db.UpdateRBACUserPassword(id, hash); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
if h.audit != nil {
|
|
h.audit.RecordOK(c, "rbac", "update_user", "更新平台用户", "user", id, nil)
|
|
}
|
|
if h.auth != nil {
|
|
h.auth.RevokeUserSessions(id)
|
|
}
|
|
updated, _ := h.db.GetRBACUserByID(id)
|
|
c.JSON(http.StatusOK, gin.H{"user": updated})
|
|
}
|
|
|
|
func (h *RBACHandler) DeleteUser(c *gin.Context) {
|
|
id := strings.TrimSpace(c.Param("id"))
|
|
if err := h.db.DeleteRBACUser(id); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if h.audit != nil {
|
|
h.audit.RecordOK(c, "rbac", "delete_user", "删除平台用户", "user", id, nil)
|
|
}
|
|
if h.auth != nil {
|
|
h.auth.RevokeUserSessions(id)
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
type assignResourceRequest struct {
|
|
UserID string `json:"user_id" binding:"required"`
|
|
ResourceType string `json:"resource_type" binding:"required"`
|
|
ResourceID string `json:"resource_id"`
|
|
ResourceIDs []string `json:"resource_ids"`
|
|
}
|
|
|
|
func (h *RBACHandler) AssignResource(c *gin.Context) {
|
|
var req assignResourceRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
resourceIDs := append([]string(nil), req.ResourceIDs...)
|
|
if strings.TrimSpace(req.ResourceID) != "" {
|
|
resourceIDs = append(resourceIDs, req.ResourceID)
|
|
}
|
|
if len(resourceIDs) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "至少需要一个资源 ID"})
|
|
return
|
|
}
|
|
created, err := h.db.AssignResourcesToUser(req.UserID, req.ResourceType, resourceIDs)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if h.audit != nil {
|
|
for _, resourceID := range resourceIDs {
|
|
h.audit.RecordOK(c, "rbac", "assign_resource", "授权资源访问", req.ResourceType, strings.TrimSpace(resourceID), map[string]interface{}{"user_id": req.UserID})
|
|
}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"requested": len(resourceIDs),
|
|
"created": created,
|
|
"skipped": int64(len(resourceIDs)) - created,
|
|
})
|
|
}
|
|
|
|
func (h *RBACHandler) ListResourceAssignments(c *gin.Context) {
|
|
rows, err := h.db.ListRBACResourceAssignments(c.Query("user_id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"assignments": rows})
|
|
}
|
|
|
|
func (h *RBACHandler) ListAssignableResources(c *gin.Context) {
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
|
if limit <= 0 || limit > 50 {
|
|
limit = 50
|
|
}
|
|
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
|
if offset < 0 {
|
|
offset = 0
|
|
}
|
|
resources, err := h.db.ListAssignableRBACResourcesPage(c.Query("type"), c.Query("q"), limit+1, offset)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
hasMore := len(resources) > limit
|
|
if hasMore {
|
|
resources = resources[:limit]
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"resources": resources,
|
|
"has_more": hasMore,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
})
|
|
}
|
|
|
|
func (h *RBACHandler) DeleteResourceAssignment(c *gin.Context) {
|
|
id := strings.TrimSpace(c.Param("id"))
|
|
if err := h.db.DeleteRBACResourceAssignment(id); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if h.audit != nil {
|
|
h.audit.RecordOK(c, "rbac", "delete_resource_assignment", "撤销资源授权", "resource_assignment", id, nil)
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|