146 lines
4.7 KiB
Go
146 lines
4.7 KiB
Go
package local_event
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
coreService "github.com/go-admin-team/go-admin-core/sdk/service"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidFilter = errors.New("本地事件查询条件不符合要求")
|
|
ErrNotFound = errors.New("本地事件候选不存在")
|
|
)
|
|
|
|
type Service struct {
|
|
coreService.Service
|
|
Now func() time.Time
|
|
}
|
|
|
|
func (s *Service) List(request *PageRequest) ([]Response, int64, error) {
|
|
filter, err := normalizeFilter(request)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
query := s.Orm.Model(&EventCandidate{})
|
|
if filter.CandidateState != "" {
|
|
query = query.Where("candidate_state = ?", filter.CandidateState)
|
|
}
|
|
if filter.EvidenceState != "" {
|
|
query = query.Where("evidence_state = ?", filter.EvidenceState)
|
|
}
|
|
if filter.RuleRef != "" {
|
|
query = query.Where("rule_ref = ?", filter.RuleRef)
|
|
}
|
|
if filter.occurredAfter != nil {
|
|
query = query.Where("occurred_at >= ?", *filter.occurredAfter)
|
|
}
|
|
if filter.occurredBefore != nil {
|
|
query = query.Where("occurred_at <= ?", *filter.occurredBefore)
|
|
}
|
|
if filter.Keyword != "" {
|
|
pattern := "%" + strings.ToLower(filter.Keyword) + "%"
|
|
query = query.Where("LOWER(id) LIKE ? OR LOWER(source_ref) LIKE ? OR LOWER(source_label) LIKE ? OR LOWER(rule_name) LIKE ?", pattern, pattern, pattern, pattern)
|
|
}
|
|
var count int64
|
|
if err = query.Count(&count).Error; err != nil {
|
|
return nil, 0, fmt.Errorf("count local event candidates: %w", err)
|
|
}
|
|
var models []EventCandidate
|
|
if err = query.Order("occurred_at DESC, id DESC").Limit(filter.GetPageSize()).Offset((filter.GetPageIndex() - 1) * filter.GetPageSize()).Find(&models).Error; err != nil {
|
|
return nil, 0, fmt.Errorf("list local event candidates: %w", err)
|
|
}
|
|
result := make([]Response, 0, len(models))
|
|
for _, model := range models {
|
|
result = append(result, s.response(model))
|
|
}
|
|
return result, count, nil
|
|
}
|
|
|
|
func (s *Service) Get(id string) (Response, error) {
|
|
id = strings.TrimSpace(id)
|
|
if id == "" || len(id) > 64 {
|
|
return Response{}, ErrInvalidFilter
|
|
}
|
|
var model EventCandidate
|
|
if err := s.Orm.First(&model, "id = ?", id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return Response{}, ErrNotFound
|
|
}
|
|
return Response{}, fmt.Errorf("get local event candidate: %w", err)
|
|
}
|
|
return s.response(model), nil
|
|
}
|
|
|
|
type normalizedFilter struct {
|
|
*PageRequest
|
|
occurredAfter *time.Time
|
|
occurredBefore *time.Time
|
|
}
|
|
|
|
func normalizeFilter(request *PageRequest) (*normalizedFilter, error) {
|
|
if request == nil {
|
|
request = &PageRequest{}
|
|
}
|
|
request.CandidateState = strings.TrimSpace(request.CandidateState)
|
|
request.EvidenceState = strings.TrimSpace(request.EvidenceState)
|
|
request.RuleRef = strings.TrimSpace(request.RuleRef)
|
|
request.Keyword = strings.TrimSpace(request.Keyword)
|
|
if request.GetPageSize() > 100 || len([]rune(request.Keyword)) > 128 || len(request.RuleRef) > 128 {
|
|
return nil, ErrInvalidFilter
|
|
}
|
|
if request.CandidateState != "" && request.CandidateState != CandidateStateCandidate && request.CandidateState != CandidateStateConfirmed {
|
|
return nil, ErrInvalidFilter
|
|
}
|
|
if request.EvidenceState != "" && request.EvidenceState != EvidenceStatePending && request.EvidenceState != EvidenceStateSuccess && request.EvidenceState != EvidenceStateFailed {
|
|
return nil, ErrInvalidFilter
|
|
}
|
|
result := &normalizedFilter{PageRequest: request}
|
|
var err error
|
|
if strings.TrimSpace(request.OccurredAfter) != "" {
|
|
value, parseErr := time.Parse(time.RFC3339, request.OccurredAfter)
|
|
if parseErr != nil {
|
|
return nil, ErrInvalidFilter
|
|
}
|
|
result.occurredAfter = &value
|
|
}
|
|
if strings.TrimSpace(request.OccurredBefore) != "" {
|
|
value, parseErr := time.Parse(time.RFC3339, request.OccurredBefore)
|
|
if parseErr != nil {
|
|
return nil, ErrInvalidFilter
|
|
}
|
|
result.occurredBefore = &value
|
|
}
|
|
if result.occurredAfter != nil && result.occurredBefore != nil && result.occurredAfter.After(*result.occurredBefore) {
|
|
err = ErrInvalidFilter
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Service) response(model EventCandidate) Response {
|
|
now := time.Now().UTC()
|
|
if s.Now != nil {
|
|
now = s.Now().UTC()
|
|
}
|
|
remaining := int(model.RetainUntil.Sub(now).Hours() / 24)
|
|
state := "active"
|
|
if !model.RetainUntil.After(now) {
|
|
remaining, state = 0, "expired"
|
|
} else if remaining < 3 {
|
|
state = "expiring"
|
|
}
|
|
return Response{
|
|
ID: model.ID, OccurredAt: model.OccurredAt, SourceRef: model.SourceRef, SourceLabel: model.SourceLabel,
|
|
RuleRef: model.RuleRef, RuleName: model.RuleName, RuleVersion: model.RuleVersion, ZoneRef: model.ZoneRef,
|
|
CandidateState: model.CandidateState, EvidenceState: model.EvidenceState, EvidenceRef: model.EvidenceRef,
|
|
EvidenceDetail: model.EvidenceDetail, RetainUntil: model.RetainUntil, RetentionState: state,
|
|
RemainingDays: remaining, CreatedAt: model.CreatedAt,
|
|
}
|
|
}
|