package services import ( "fmt" "strings" "time" "lokistack/models" "lokistack/repository" ) func ListLogPolicyGroups() []models.LogPolicyGroup { db := repository.GetDB() db.RLock() defer db.RUnlock() groups := make([]models.LogPolicyGroup, len(db.LogPolicyGroups)) for i, group := range db.LogPolicyGroups { groups[i] = group groups[i].UserIDs = append([]uint(nil), group.UserIDs...) groups[i].Labels = make(map[string]string, len(group.Labels)) for key, value := range group.Labels { groups[i].Labels[key] = value } } return groups } // AnyoneUserID 是 userIds 里的哨兵值,表示「所有人」(anyone / *)。 // 与 policyGroupAccess 的匹配逻辑、前端 ANYONE 常量保持一致。 const AnyoneUserID uint = 0 func validatePolicyGroup(db *repository.JsonDB, group models.LogPolicyGroup, excludeID uint) error { group.Name = strings.TrimSpace(group.Name) if group.Name == "" { return fmt.Errorf("策略组名称必填") } if group.SourceID == 0 { return fmt.Errorf("请选择数据源") } foundSource := false for _, source := range db.LogSources { if source.ID == group.SourceID { foundSource = true break } } if !foundSource { return fmt.Errorf("数据源不存在") } if len(group.UserIDs) == 0 { return fmt.Errorf("请选择至少一个用户") } for key := range group.Labels { if !ValidLogLabelName(key) { return fmt.Errorf("无效标签名: %s", key) } } seen := map[uint]bool{} for _, id := range group.UserIDs { if seen[id] { return fmt.Errorf("用户 ID 重复: %d", id) } seen[id] = true // AnyoneUserID(0) 是「所有人」哨兵,不对应真实用户记录, // 跳过存在性校验;但它必须独占,不能与其他具体用户混选。 if id == AnyoneUserID { if len(group.UserIDs) > 1 { return fmt.Errorf("「所有人」不能与具体用户同时选择") } continue } found := false for _, user := range db.Users { if user.ID == id && user.Role != "admin" { found = true break } } if !found { return fmt.Errorf("用户 %d 不存在或不是普通用户", id) } } for _, other := range db.LogPolicyGroups { if other.ID == excludeID || other.SourceID != group.SourceID { continue } if other.Name == group.Name { return fmt.Errorf("此数据源已有同名策略组") } for _, id := range other.UserIDs { // AnyoneUserID(0) 是「所有人」哨兵,不是真实用户, // 允许多个策略组同时包含它(否则新建任何「所有人」策略组都会被这条冲突检查拒掉) if id == AnyoneUserID { continue } if seen[id] { return fmt.Errorf("用户 %d 已属于此数据源的另一个策略组", id) } } } return nil } // Group membership replaces legacy per-user grants for the same source. func removeDirectGrants(db *repository.JsonDB, sourceID uint, userIDs []uint) { members := map[uint]bool{} for _, id := range userIDs { members[id] = true } for i := range db.LogSources { if db.LogSources[i].ID != sourceID { continue } kept := db.LogSources[i].Access[:0] for _, rule := range db.LogSources[i].Access { if !members[rule.UserID] { kept = append(kept, rule) } } db.LogSources[i].Access = kept return } } func CreateLogPolicyGroup(group models.LogPolicyGroup) (*models.LogPolicyGroup, error) { db := repository.GetDB() id := db.NextID("logPolicyGroups") db.Lock() defer db.Unlock() if err := validatePolicyGroup(db, group, 0); err != nil { return nil, err } group.ID = id group.Name = strings.TrimSpace(group.Name) group.CreatedAt = time.Now() group.UpdatedAt = group.CreatedAt removeDirectGrants(db, group.SourceID, group.UserIDs) db.LogPolicyGroups = append(db.LogPolicyGroups, group) db.RequestSave() return &group, nil } func UpdateLogPolicyGroup(id uint, group models.LogPolicyGroup) (*models.LogPolicyGroup, error) { db := repository.GetDB() db.Lock() defer db.Unlock() for i := range db.LogPolicyGroups { if db.LogPolicyGroups[i].ID != id { continue } if err := validatePolicyGroup(db, group, id); err != nil { return nil, err } group.ID = id group.Name = strings.TrimSpace(group.Name) group.CreatedAt = db.LogPolicyGroups[i].CreatedAt group.UpdatedAt = time.Now() removeDirectGrants(db, group.SourceID, group.UserIDs) db.LogPolicyGroups[i] = group db.RequestSave() return &group, nil } return nil, fmt.Errorf("策略组不存在") } func DeleteLogPolicyGroup(id uint) error { db := repository.GetDB() db.Lock() defer db.Unlock() for i := range db.LogPolicyGroups { if db.LogPolicyGroups[i].ID == id { db.LogPolicyGroups = append(db.LogPolicyGroups[:i], db.LogPolicyGroups[i+1:]...) db.RequestSave() return nil } } return fmt.Errorf("策略组不存在") } func policyGroupAccess(groups []models.LogPolicyGroup, sourceID, userID uint) *models.LogSourceAccess { for _, group := range groups { if group.SourceID != sourceID { continue } for _, id := range group.UserIDs { // 0 是哨兵值:表示「所有人」(anyone / *),匹配任意用户。 // 用于默认策略——让所有账号开箱即可查询业务命名空间。 if id == 0 || id == userID { return &models.LogSourceAccess{UserID: userID, Labels: group.Labels} } } } return nil }