feat: Go后端LDAP真实对接 — AES-256-GCM密码加解密 + 登录LDAP fallback
- 新增 crypto.go: AES-256-GCM 加解密,与 Python backend 兼容 - auth.go: Login 添加 LDAP fallback(本地优先→LDAP绑定→自动创建本地用户) - ldap.go: 实现真实 LDAP 连接/用户搜索/密码验证/OU列表/用户列表 - 所有 LDAP 操作解密 admin_password 后使用 - 新增/更新密码存储时加密 - router.go: 补充 delete-user 路由 - 添加 go-ldap/v3 依赖
This commit is contained in:
1 parent
622923821b
commit
88e5f4e7ee
71 files changed
+20390
-64
No files matched your search
@@ -0,0 +1,208 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"sql-platform/internal/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GET /api/v1/workflows/:id
|
||||
func GetWorkflow(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var wf model.WorkflowDefinition
|
||||
if err := model.DB.First(&wf, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Workflow not found"})
|
||||
return
|
||||
}
|
||||
var steps []model.WorkflowStep
|
||||
model.DB.Where("workflow_id = ?", wf.ID).Order("step_number").Find(&steps)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"id": wf.ID,
|
||||
"name": wf.Name,
|
||||
"description": wf.Description,
|
||||
"category": wf.Category,
|
||||
"is_default": wf.IsDefault,
|
||||
"is_active": wf.IsActive,
|
||||
"created_at": wf.CreatedAt,
|
||||
"updated_at": wf.UpdatedAt,
|
||||
"created_by": wf.CreatedBy,
|
||||
"steps": steps,
|
||||
})
|
||||
}
|
||||
|
||||
// POST /api/v1/workflows
|
||||
func CreateWorkflow(c *gin.Context) {
|
||||
var input struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
IsDefault *bool `json:"is_default"`
|
||||
Steps []struct {
|
||||
StepNumber int `json:"step_number"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
StepType string `json:"step_type"`
|
||||
ApproverRole string `json:"approver_role"`
|
||||
ApproverIDs string `json:"approver_ids"`
|
||||
IsRequired *bool `json:"is_required"`
|
||||
TimeoutHours *int `json:"timeout_hours"`
|
||||
AutoApproveOnTimeout *bool `json:"auto_approve_on_timeout"`
|
||||
} `json:"steps"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
active := true
|
||||
if input.IsActive != nil {
|
||||
active = *input.IsActive
|
||||
}
|
||||
wf := model.WorkflowDefinition{
|
||||
Name: input.Name,
|
||||
Description: &input.Description,
|
||||
Category: &input.Category,
|
||||
IsActive: &active,
|
||||
IsDefault: input.IsDefault,
|
||||
}
|
||||
if err := model.DB.Create(&wf).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range input.Steps {
|
||||
step := model.WorkflowStep{
|
||||
WorkflowID: wf.ID,
|
||||
StepNumber: s.StepNumber,
|
||||
Name: s.Name,
|
||||
Description: &s.Description,
|
||||
StepType: s.StepType,
|
||||
ApproverRole: &s.ApproverRole,
|
||||
ApproverIDs: &s.ApproverIDs,
|
||||
IsRequired: s.IsRequired,
|
||||
TimeoutHours: s.TimeoutHours,
|
||||
AutoApproveOnTimeout: s.AutoApproveOnTimeout,
|
||||
}
|
||||
model.DB.Create(&step)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"id": wf.ID, "message": "Workflow created"})
|
||||
}
|
||||
|
||||
// PUT /api/v1/workflows/:id
|
||||
func UpdateWorkflow(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var wf model.WorkflowDefinition
|
||||
if err := model.DB.First(&wf, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Workflow not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var input map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
model.DB.Model(&wf).Updates(input)
|
||||
|
||||
// If steps provided, replace them
|
||||
if stepsRaw, ok := input["steps"]; ok {
|
||||
model.DB.Where("workflow_id = ?", wf.ID).Delete(&model.WorkflowStep{})
|
||||
if stepsArr, ok := stepsRaw.([]interface{}); ok {
|
||||
for i, sRaw := range stepsArr {
|
||||
if sMap, ok := sRaw.(map[string]interface{}); ok {
|
||||
step := model.WorkflowStep{
|
||||
WorkflowID: wf.ID,
|
||||
StepNumber: i + 1,
|
||||
}
|
||||
if v, ok := sMap["name"].(string); ok {
|
||||
step.Name = v
|
||||
}
|
||||
if v, ok := sMap["step_type"].(string); ok {
|
||||
step.StepType = v
|
||||
}
|
||||
if v, ok := sMap["approver_role"].(string); ok {
|
||||
step.ApproverRole = &v
|
||||
}
|
||||
if v, ok := sMap["approver_ids"].(string); ok {
|
||||
step.ApproverIDs = &v
|
||||
}
|
||||
if v, ok := sMap["description"].(string); ok {
|
||||
step.Description = &v
|
||||
}
|
||||
model.DB.Create(&step)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Workflow updated"})
|
||||
}
|
||||
|
||||
// DELETE /api/v1/workflows/:id
|
||||
func DeleteWorkflow(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
model.DB.Where("workflow_id = ?", id).Delete(&model.WorkflowStep{})
|
||||
model.DB.Delete(&model.WorkflowDefinition{}, id)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Workflow deleted"})
|
||||
}
|
||||
|
||||
// POST /api/v1/workflows/:id/set-default
|
||||
func SetDefaultWorkflow(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
// unset all defaults
|
||||
model.DB.Model(&model.WorkflowDefinition{}).Where("is_default = ?", true).Update("is_default", false)
|
||||
// set this one as default
|
||||
defaultVal := true
|
||||
model.DB.Model(&model.WorkflowDefinition{}).Where("id = ?", id).Update("is_default", defaultVal)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Default workflow set"})
|
||||
}
|
||||
|
||||
// POST /api/v1/users/:id/reset-password
|
||||
func ResetPassword(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var input struct {
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
return
|
||||
}
|
||||
_, _ = id, input.NewPassword
|
||||
// TODO: hash password and update
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Password reset (placeholder)"})
|
||||
}
|
||||
|
||||
// POST /api/v1/users/batch-import
|
||||
func BatchImportUsers(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Batch import (placeholder)", "count": 0})
|
||||
}
|
||||
|
||||
// POST /api/v1/users/batch-import-ldap
|
||||
func BatchImportLdapUsers(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "LDAP import (placeholder)", "count": 0})
|
||||
}
|
||||
|
||||
// GET /api/v1/users/import-template
|
||||
func ImportTemplate(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Template download (placeholder)"})
|
||||
}
|
||||
|
||||
// POST /api/v1/users/:userId/reset-password
|
||||
func ResetUserPassword(c *gin.Context) {
|
||||
userId := c.Param("userId")
|
||||
var input struct {
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
return
|
||||
}
|
||||
// TODO: actually hash and save
|
||||
_ = userId
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Password reset (placeholder)"})
|
||||
}
|
||||
Reference in new issue
Block a user