Files
aiops/backend/middleware/pipeline_access.go
T
Logan 7e1c8f583c feat: 工具配置增强、站点拨测与日志中心多项改进
- 工具配置:新增卸载 API/UI、Go 工具链、流水线构建自动安装依赖
- 修复 ListTools VerifyTool 递归栈溢出导致获取工具配置失败
- Node.js Alpine 安装改用 unofficial-builds musl 预编译包与 index.json 解析 LTS
- 站点拨测:详情页路由、告警自动恢复、并发拨测与默认可观测频率
- 日志中心:RBAC、查询构建器、ES/Loki 增强与审计 request/response 展示
- 发布配置:构建节点、流水线远程执行、Deploy RBAC 导航拆分
2026-07-02 07:47:33 +08:00

156 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 pipelineMatchesItemRole(pipeline, ir.Pattern) {
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 pipelineMatchesItemRole(p, ir.Pattern) {
idSet[p.ID] = true
break
}
}
}
}
ids := make([]uint, 0, len(idSet))
for id := range idSet {
ids = append(ids, id)
}
return ids, len(ids) > 0
}
// pipelineMatchesItemRole 正则匹配流水线 code 或 Job 名称(Webhook Job) // Logan.Li
func pipelineMatchesItemRole(p model.Pipeline, pattern string) bool {
if pattern == "" {
return false
}
if matched, err := regexp.MatchString(pattern, p.Code); err == nil && matched {
return true
}
if p.JobName != "" {
if matched, err := regexp.MatchString(pattern, p.JobName); err == nil && matched {
return true
}
}
return false
}