package middleware import ( "net/http" "regexp" "ezops/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // PipelineAccess 流水线权限中间件 // 路由参数 :code(流水线编码)→ 查找 pipeline → 注入 pipelineID 到 context // 权限检查顺序:管理员 → 直接成员 → 用户组 → Item Role (regex) func PipelineAccess(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { // 1) 解析 code → 查找流水线(所有用户都需要) code := c.Param("code") if code == "" { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"code": 400, "message": "缺少流水线编码"}) return } var pipeline model.Pipeline if err := db.Where("code = ?", code).First(&pipeline).Error; err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"code": 404, "message": "流水线不存在"}) return } // 注入 context,后续 handlers 统一从这里读 c.Set("pipelineID", pipeline.ID) c.Set("pipelineCode", code) // 2) 管理员直接放行(跳过权限检查) if isAdmin, _ := c.Get("isAdmin"); isAdmin == true { c.Next() return } username, _ := c.Get("username") usernameStr, _ := username.(string) if usernameStr == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "未认证"}) return } pipelineID := pipeline.ID // 3) 检查是否直接是流水线成员 var directCount int64 db.Model(&model.DeployPipelineMember{}). Where("pipeline_id = ? AND username = ?", pipelineID, usernameStr). Count(&directCount) if directCount > 0 { c.Next() return } // 4) 检查是否属于授权给该流水线的用户组 var groupCount int64 db.Table("deploy_pipeline_members"). Joins("JOIN deploy_user_group_members ON deploy_user_group_members.group_id = deploy_pipeline_members.group_id"). Where("deploy_pipeline_members.pipeline_id = ? AND deploy_user_group_members.username = ?", pipelineID, usernameStr). Count(&groupCount) if groupCount > 0 { c.Next() return } // 5) Jenkins-style Item Role — regex pattern matching on pipeline code var itemRoles []model.ItemRole db.Table("iam_item_roles"). Joins("JOIN iam_user_item_roles ON iam_user_item_roles.item_role_id = iam_item_roles.id"). Where("iam_item_roles.status = 1 AND iam_user_item_roles.user_id = ?", func() uint { uid, _ := c.Get("userID"); return uid.(uint) }()). Find(&itemRoles) for _, ir := range itemRoles { if matched, _ := regexp.MatchString(ir.Pattern, code); matched { c.Next() return } } c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ "code": 403, "message": "无权访问此流水线,请联系流水线 Owner 添加你为成员", }) } } // GetAccessiblePipelineIDs 获取当前用户有权访问的流水线 ID 列表 // 包括:直接成员 + 用户组 + Item Role (regex) func GetAccessiblePipelineIDs(db *gorm.DB, username string, userID uint) ([]uint, bool) { var directIDs []uint db.Model(&model.DeployPipelineMember{}). Where("username = ?", username). Pluck("pipeline_id", &directIDs) var groupIDs []uint db.Model(&model.DeployPipelineMember{}). Joins("JOIN deploy_user_group_members ON deploy_user_group_members.group_id = deploy_pipeline_members.group_id"). Where("deploy_user_group_members.username = ?", username). Pluck("deploy_pipeline_members.pipeline_id", &groupIDs) idSet := make(map[uint]bool) for _, id := range directIDs { idSet[id] = true } for _, id := range groupIDs { idSet[id] = true } // Item Role regex matching: get all item roles for user, match against pipeline codes var itemRoles []model.ItemRole db.Table("iam_item_roles"). Joins("JOIN iam_user_item_roles ON iam_user_item_roles.item_role_id = iam_item_roles.id"). Where("iam_item_roles.status = 1 AND iam_user_item_roles.user_id = ?", userID). Find(&itemRoles) if len(itemRoles) > 0 { var allPipelines []model.Pipeline db.Find(&allPipelines) for _, p := range allPipelines { for _, ir := range itemRoles { if matched, _ := regexp.MatchString(ir.Pattern, p.Code); matched { idSet[p.ID] = true break } } } } ids := make([]uint, 0, len(idSet)) for id := range idSet { ids = append(ids, id) } return ids, len(ids) > 0 }