package logaccess import ( "fmt" "regexp" "strings" "ezops/model" "gorm.io/gorm" ) // QueryContext 日志查询授权上下文 // Logan.Li type QueryContext struct { Engine string DatasourceID uint Index string Query string } var ( lokiSelectorRe = regexp.MustCompile(`\{([^}]*)\}`) lokiLabelRe = regexp.MustCompile(`([a-zA-Z_][\w.-]*)\s*(=|!=|=~|!~)\s*("([^"]*)"|'([^']*)'|([^\s,}]+))`) esAppRe = regexp.MustCompile(`(?i)\bapp:(\*|"[^"]+"|'[^']+'|[^\s)]+)`) ) // GetUserLogScopeRoles 获取用户已启用的日志权限策略 // Logan.Li func GetUserLogScopeRoles(db *gorm.DB, userID uint) ([]model.LogScopeRole, error) { var roles []model.LogScopeRole err := db.Table("monitor_log_scope_roles"). Joins("JOIN monitor_user_log_scope_roles ON monitor_user_log_scope_roles.log_scope_role_id = monitor_log_scope_roles.id"). Where("monitor_user_log_scope_roles.user_id = ? AND monitor_log_scope_roles.status = 1", userID). Find(&roles).Error return roles, err } // UserHasAssignments 用户是否已分配日志权限(未分配则保持兼容:不拦截) // Logan.Li func UserHasAssignments(db *gorm.DB, userID uint) bool { var n int64 db.Model(&model.UserLogScopeRole{}).Where("user_id = ?", userID).Count(&n) return n > 0 } // CanQuery 校验用户是否允许执行该日志查询 // Logan.Li func CanQuery(db *gorm.DB, userID uint, isAdmin bool, qc QueryContext) (bool, string) { if isAdmin { return true, "" } if !UserHasAssignments(db, userID) { return true, "" } roles, err := GetUserLogScopeRoles(db, userID) if err != nil || len(roles) == 0 { return false, "未分配可用的日志查询权限,请联系管理员" } for _, r := range roles { if roleAllowsQuery(r, qc) { return true, "" } } return false, "无权查询该日志范围(索引/Label 未命中授权策略)" } // FilterDataSources 按授权过滤可见数据源 // Logan.Li func FilterDataSources(db *gorm.DB, userID uint, isAdmin bool, sources []model.LogDataSource) []model.LogDataSource { if isAdmin || !UserHasAssignments(db, userID) { return sources } roles, _ := GetUserLogScopeRoles(db, userID) if len(roles) == 0 { return []model.LogDataSource{} } out := make([]model.LogDataSource, 0, len(sources)) for _, ds := range sources { if datasourceAllowed(roles, ds) { out = append(out, ds) } } return out } // MatchRolesForPreview 返回命中预览的策略列表 // Logan.Li func MatchRolesForPreview(roles []model.LogScopeRole, qc QueryContext, sample string) []model.LogScopeRole { matched := make([]model.LogScopeRole, 0) for _, r := range roles { if roleAllowsQuery(r, qc) { matched = append(matched, r) continue } if sample != "" && regexpMatch(r.Pattern, sample) && engineMatches(r.Engine, qc.Engine) { if r.DatasourceID == nil || *r.DatasourceID == 0 || *r.DatasourceID == qc.DatasourceID { if fieldMatchesEngine(r.MatchField, qc.Engine) { matched = append(matched, r) } } } } return matched } func datasourceAllowed(roles []model.LogScopeRole, ds model.LogDataSource) bool { for _, r := range roles { if !engineMatches(r.Engine, ds.Type) { continue } if r.DatasourceID == nil || *r.DatasourceID == 0 || *r.DatasourceID == ds.ID { return true } } return false } func roleAllowsQuery(r model.LogScopeRole, qc QueryContext) bool { if !engineMatches(r.Engine, qc.Engine) { return false } if r.DatasourceID != nil && *r.DatasourceID > 0 && *r.DatasourceID != qc.DatasourceID { return false } switch r.MatchField { case "index": if qc.Engine != "elasticsearch" { return false } idx := strings.TrimSpace(qc.Index) if idx == "" { idx = "*" } return regexpMatch(r.Pattern, idx) case "app", "namespace", "job": values := extractFieldValues(qc.Engine, qc.Query, r.MatchField) if len(values) == 0 { return false } for _, v := range values { if regexpMatch(r.Pattern, v) { return true } } return false default: return false } } func engineMatches(roleEngine, queryEngine string) bool { re := strings.ToLower(strings.TrimSpace(roleEngine)) qe := strings.ToLower(strings.TrimSpace(queryEngine)) if re == "" || re == "*" { return true } return re == qe } func fieldMatchesEngine(field, engine string) bool { if field == "index" { return engine == "elasticsearch" } return engine == "loki" } func regexpMatch(pattern, value string) bool { if pattern == "" || value == "" { return false } matched, err := regexp.MatchString(pattern, value) return err == nil && matched } func extractFieldValues(engine, query, field string) []string { if engine == "loki" { return extractLokiLabelValues(query, field) } if field == "app" { return extractESAppValues(query) } return nil } func extractLokiLabelValues(query, field string) []string { m := lokiSelectorRe.FindStringSubmatch(query) if len(m) < 2 { return nil } inner := m[1] var values []string for _, part := range lokiLabelRe.FindAllStringSubmatch(inner, -1) { if len(part) < 7 || part[1] != field { continue } op := part[2] val := part[4] if val == "" { val = part[5] } if val == "" { val = part[6] } if op == "=" || op == "=~" { values = append(values, val) } } return values } func extractESAppValues(query string) []string { var values []string for _, m := range esAppRe.FindAllStringSubmatch(query, -1) { if len(m) < 2 { continue } v := strings.Trim(m[1], `"'`) if v != "" { values = append(values, v) } } return values } // DescribeRole 策略可读描述 // Logan.Li func DescribeRole(r model.LogScopeRole) string { ds := "全部数据源" if r.DatasourceID != nil && *r.DatasourceID > 0 { ds = fmt.Sprintf("数据源#%d", *r.DatasourceID) } return fmt.Sprintf("%s · %s · %s=%s", r.Engine, ds, r.MatchField, r.Pattern) }