368 lines
12 KiB
Go
368 lines
12 KiB
Go
package model
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// SecurityEvent represents a security event in the system
|
|
type SecurityEvent struct {
|
|
BaseModel
|
|
EventType string `json:"eventType" gorm:"not null;type:varchar(100);index"`
|
|
Severity string `json:"severity" gorm:"not null;type:varchar(20);index"`
|
|
UserID string `json:"userId" gorm:"type:varchar(36);index"`
|
|
IPAddress string `json:"ipAddress" gorm:"type:varchar(45);index"`
|
|
UserAgent string `json:"userAgent" gorm:"type:text"`
|
|
Resource string `json:"resource" gorm:"type:varchar(100)"`
|
|
Action string `json:"action" gorm:"type:varchar(100)"`
|
|
Success bool `json:"success" gorm:"index"`
|
|
Blocked bool `json:"blocked" gorm:"default:false;index"`
|
|
Message string `json:"message" gorm:"type:text"`
|
|
Details map[string]interface{} `json:"details" gorm:"type:text"`
|
|
SessionID string `json:"sessionId,omitempty" gorm:"type:varchar(255)"`
|
|
RequestID string `json:"requestId,omitempty" gorm:"type:varchar(255)"`
|
|
CountryCode string `json:"countryCode,omitempty" gorm:"type:varchar(2)"`
|
|
City string `json:"city,omitempty" gorm:"type:varchar(100)"`
|
|
Resolved bool `json:"resolved" gorm:"default:false;index"`
|
|
ResolvedBy string `json:"resolvedBy,omitempty" gorm:"type:varchar(36)"`
|
|
ResolvedAt *time.Time `json:"resolvedAt,omitempty"`
|
|
Notes string `json:"notes,omitempty" gorm:"type:text"`
|
|
User *User `json:"user,omitempty" gorm:"foreignKey:UserID"`
|
|
Resolver *User `json:"resolver,omitempty" gorm:"foreignKey:ResolvedBy"`
|
|
}
|
|
|
|
// SecurityEventCreateRequest represents the request to create a new security event
|
|
type SecurityEventCreateRequest struct {
|
|
EventType string `json:"eventType" validate:"required,max=100"`
|
|
Severity string `json:"severity" validate:"required,oneof=low medium high critical"`
|
|
UserID string `json:"userId"`
|
|
IPAddress string `json:"ipAddress" validate:"max=45"`
|
|
UserAgent string `json:"userAgent"`
|
|
Resource string `json:"resource" validate:"max=100"`
|
|
Action string `json:"action" validate:"max=100"`
|
|
Success bool `json:"success"`
|
|
Blocked bool `json:"blocked"`
|
|
Message string `json:"message" validate:"required"`
|
|
Details map[string]interface{} `json:"details"`
|
|
SessionID string `json:"sessionId"`
|
|
RequestID string `json:"requestId"`
|
|
CountryCode string `json:"countryCode" validate:"max=2"`
|
|
City string `json:"city" validate:"max=100"`
|
|
}
|
|
|
|
// SecurityEventUpdateRequest represents the request to update a security event
|
|
type SecurityEventUpdateRequest struct {
|
|
Resolved *bool `json:"resolved,omitempty"`
|
|
ResolvedBy *string `json:"resolvedBy,omitempty"`
|
|
Notes *string `json:"notes,omitempty"`
|
|
}
|
|
|
|
// SecurityEventInfo represents public security event information
|
|
type SecurityEventInfo struct {
|
|
ID string `json:"id"`
|
|
EventType string `json:"eventType"`
|
|
Severity string `json:"severity"`
|
|
UserID string `json:"userId"`
|
|
UserEmail string `json:"userEmail,omitempty"`
|
|
UserName string `json:"userName,omitempty"`
|
|
IPAddress string `json:"ipAddress"`
|
|
UserAgent string `json:"userAgent"`
|
|
Resource string `json:"resource"`
|
|
Action string `json:"action"`
|
|
Success bool `json:"success"`
|
|
Blocked bool `json:"blocked"`
|
|
Message string `json:"message"`
|
|
Details map[string]interface{} `json:"details"`
|
|
SessionID string `json:"sessionId,omitempty"`
|
|
RequestID string `json:"requestId,omitempty"`
|
|
CountryCode string `json:"countryCode,omitempty"`
|
|
City string `json:"city,omitempty"`
|
|
Resolved bool `json:"resolved"`
|
|
ResolvedBy string `json:"resolvedBy,omitempty"`
|
|
ResolverEmail string `json:"resolverEmail,omitempty"`
|
|
ResolverName string `json:"resolverName,omitempty"`
|
|
ResolvedAt *time.Time `json:"resolvedAt,omitempty"`
|
|
Notes string `json:"notes,omitempty"`
|
|
DateCreated string `json:"dateCreated"`
|
|
}
|
|
|
|
// BeforeCreate is called before creating a security event
|
|
func (se *SecurityEvent) BeforeCreate(tx *gorm.DB) error {
|
|
se.BaseModel.BeforeCreate()
|
|
|
|
// Normalize fields
|
|
se.EventType = strings.ToLower(strings.TrimSpace(se.EventType))
|
|
se.Severity = strings.ToLower(strings.TrimSpace(se.Severity))
|
|
se.IPAddress = strings.TrimSpace(se.IPAddress)
|
|
se.UserAgent = strings.TrimSpace(se.UserAgent)
|
|
se.Resource = strings.ToLower(strings.TrimSpace(se.Resource))
|
|
se.Action = strings.ToLower(strings.TrimSpace(se.Action))
|
|
se.Message = strings.TrimSpace(se.Message)
|
|
|
|
return se.Validate()
|
|
}
|
|
|
|
// BeforeUpdate is called before updating a security event
|
|
func (se *SecurityEvent) BeforeUpdate(tx *gorm.DB) error {
|
|
se.BaseModel.BeforeUpdate()
|
|
|
|
// If resolving the event, set resolved timestamp
|
|
if se.Resolved && se.ResolvedAt == nil {
|
|
now := time.Now()
|
|
se.ResolvedAt = &now
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates security event data
|
|
func (se *SecurityEvent) Validate() error {
|
|
validSeverities := []string{"low", "medium", "high", "critical"}
|
|
isValidSeverity := false
|
|
for _, severity := range validSeverities {
|
|
if se.Severity == severity {
|
|
isValidSeverity = true
|
|
break
|
|
}
|
|
}
|
|
if !isValidSeverity {
|
|
return gorm.ErrInvalidValue
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetDetails sets the details field from a map
|
|
func (se *SecurityEvent) SetDetails(details map[string]interface{}) error {
|
|
se.Details = details
|
|
return nil
|
|
}
|
|
|
|
// GetDetails returns the details as a map
|
|
func (se *SecurityEvent) GetDetails() map[string]interface{} {
|
|
if se.Details == nil {
|
|
return make(map[string]interface{})
|
|
}
|
|
return se.Details
|
|
}
|
|
|
|
// SetDetailsFromJSON sets the details field from a JSON string
|
|
func (se *SecurityEvent) SetDetailsFromJSON(jsonStr string) error {
|
|
if jsonStr == "" {
|
|
se.Details = make(map[string]interface{})
|
|
return nil
|
|
}
|
|
|
|
var details map[string]interface{}
|
|
if err := json.Unmarshal([]byte(jsonStr), &details); err != nil {
|
|
return err
|
|
}
|
|
|
|
se.Details = details
|
|
return nil
|
|
}
|
|
|
|
// GetDetailsAsJSON returns the details as a JSON string
|
|
func (se *SecurityEvent) GetDetailsAsJSON() (string, error) {
|
|
if se.Details == nil || len(se.Details) == 0 {
|
|
return "{}", nil
|
|
}
|
|
|
|
bytes, err := json.Marshal(se.Details)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(bytes), nil
|
|
}
|
|
|
|
// ToSecurityEventInfo converts SecurityEvent to SecurityEventInfo (public information)
|
|
func (se *SecurityEvent) ToSecurityEventInfo() SecurityEventInfo {
|
|
info := SecurityEventInfo{
|
|
ID: se.ID,
|
|
EventType: se.EventType,
|
|
Severity: se.Severity,
|
|
UserID: se.UserID,
|
|
IPAddress: se.IPAddress,
|
|
UserAgent: se.UserAgent,
|
|
Resource: se.Resource,
|
|
Action: se.Action,
|
|
Success: se.Success,
|
|
Blocked: se.Blocked,
|
|
Message: se.Message,
|
|
Details: se.GetDetails(),
|
|
SessionID: se.SessionID,
|
|
RequestID: se.RequestID,
|
|
CountryCode: se.CountryCode,
|
|
City: se.City,
|
|
Resolved: se.Resolved,
|
|
ResolvedBy: se.ResolvedBy,
|
|
ResolvedAt: se.ResolvedAt,
|
|
Notes: se.Notes,
|
|
DateCreated: se.DateCreated.Format("2006-01-02T15:04:05Z"),
|
|
}
|
|
|
|
// Include user information if available
|
|
if se.User != nil {
|
|
info.UserEmail = se.User.Email
|
|
info.UserName = se.User.Name
|
|
}
|
|
|
|
// Include resolver information if available
|
|
if se.Resolver != nil {
|
|
info.ResolverEmail = se.Resolver.Email
|
|
info.ResolverName = se.Resolver.Name
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// AddDetail adds a single detail to the details map
|
|
func (se *SecurityEvent) AddDetail(key string, value interface{}) {
|
|
if se.Details == nil {
|
|
se.Details = make(map[string]interface{})
|
|
}
|
|
se.Details[key] = value
|
|
}
|
|
|
|
// GetDetail gets a single detail from the details map
|
|
func (se *SecurityEvent) GetDetail(key string) (interface{}, bool) {
|
|
if se.Details == nil {
|
|
return nil, false
|
|
}
|
|
value, exists := se.Details[key]
|
|
return value, exists
|
|
}
|
|
|
|
// Resolve resolves the security event
|
|
func (se *SecurityEvent) Resolve(resolverID, notes string) {
|
|
se.Resolved = true
|
|
se.ResolvedBy = resolverID
|
|
se.Notes = notes
|
|
now := time.Now()
|
|
se.ResolvedAt = &now
|
|
}
|
|
|
|
// Unresolve unresolves the security event
|
|
func (se *SecurityEvent) Unresolve() {
|
|
se.Resolved = false
|
|
se.ResolvedBy = ""
|
|
se.ResolvedAt = nil
|
|
}
|
|
|
|
// IsResolved returns whether the security event is resolved
|
|
func (se *SecurityEvent) IsResolved() bool {
|
|
return se.Resolved
|
|
}
|
|
|
|
// IsCritical returns whether the security event is critical
|
|
func (se *SecurityEvent) IsCritical() bool {
|
|
return se.Severity == SecuritySeverityCritical
|
|
}
|
|
|
|
// IsHigh returns whether the security event is high severity
|
|
func (se *SecurityEvent) IsHigh() bool {
|
|
return se.Severity == SecuritySeverityHigh
|
|
}
|
|
|
|
// IsBlocked returns whether the security event was blocked
|
|
func (se *SecurityEvent) IsBlocked() bool {
|
|
return se.Blocked
|
|
}
|
|
|
|
// Security event types
|
|
const (
|
|
SecurityEventLoginAttempt = "login_attempt"
|
|
SecurityEventLoginFailure = "login_failure"
|
|
SecurityEventLoginSuccess = "login_success"
|
|
SecurityEventBruteForce = "brute_force"
|
|
SecurityEventAccountLockout = "account_lockout"
|
|
SecurityEventUnauthorizedAccess = "unauthorized_access"
|
|
SecurityEventPrivilegeEscalation = "privilege_escalation"
|
|
SecurityEventSuspiciousActivity = "suspicious_activity"
|
|
SecurityEventRateLimitExceeded = "rate_limit_exceeded"
|
|
SecurityEventInvalidToken = "invalid_token"
|
|
SecurityEventTokenExpired = "token_expired"
|
|
SecurityEventPasswordChange = "password_change"
|
|
SecurityEventEmailVerification = "email_verification"
|
|
SecurityEventTwoFactorAuth = "two_factor_auth"
|
|
SecurityEventDataExfiltration = "data_exfiltration"
|
|
SecurityEventMaliciousRequest = "malicious_request"
|
|
SecurityEventSystemAccess = "system_access"
|
|
SecurityEventConfigChange = "config_change"
|
|
SecurityEventFileAccess = "file_access"
|
|
SecurityEventDatabaseAccess = "database_access"
|
|
)
|
|
|
|
// Security severity levels
|
|
const (
|
|
SecuritySeverityLow = "low"
|
|
SecuritySeverityMedium = "medium"
|
|
SecuritySeverityHigh = "high"
|
|
SecuritySeverityCritical = "critical"
|
|
)
|
|
|
|
// CreateSecurityEvent creates a new security event
|
|
func CreateSecurityEvent(eventType, severity, message string) *SecurityEvent {
|
|
event := &SecurityEvent{
|
|
EventType: eventType,
|
|
Severity: severity,
|
|
Message: message,
|
|
Details: make(map[string]interface{}),
|
|
}
|
|
event.Init()
|
|
return event
|
|
}
|
|
|
|
// CreateSecurityEventWithUser creates a new security event with user information
|
|
func CreateSecurityEventWithUser(eventType, severity, message, userID string) *SecurityEvent {
|
|
event := CreateSecurityEvent(eventType, severity, message)
|
|
event.UserID = userID
|
|
return event
|
|
}
|
|
|
|
// CreateSecurityEventWithDetails creates a new security event with details
|
|
func CreateSecurityEventWithDetails(eventType, severity, message string, details map[string]interface{}) *SecurityEvent {
|
|
event := CreateSecurityEvent(eventType, severity, message)
|
|
event.Details = details
|
|
return event
|
|
}
|
|
|
|
// SetRequestInfo sets request-related information
|
|
func (se *SecurityEvent) SetRequestInfo(ipAddress, userAgent, sessionID, requestID string) {
|
|
se.IPAddress = ipAddress
|
|
se.UserAgent = userAgent
|
|
se.SessionID = sessionID
|
|
se.RequestID = requestID
|
|
}
|
|
|
|
// SetLocationInfo sets location-related information
|
|
func (se *SecurityEvent) SetLocationInfo(countryCode, city string) {
|
|
se.CountryCode = countryCode
|
|
se.City = city
|
|
}
|
|
|
|
// SetResourceAction sets resource and action information
|
|
func (se *SecurityEvent) SetResourceAction(resource, action string) {
|
|
se.Resource = resource
|
|
se.Action = action
|
|
}
|
|
|
|
// MarkAsBlocked marks the security event as blocked
|
|
func (se *SecurityEvent) MarkAsBlocked() {
|
|
se.Blocked = true
|
|
}
|
|
|
|
// MarkAsSuccess marks the security event as successful
|
|
func (se *SecurityEvent) MarkAsSuccess() {
|
|
se.Success = true
|
|
}
|
|
|
|
// MarkAsFailure marks the security event as failed
|
|
func (se *SecurityEvent) MarkAsFailure() {
|
|
se.Success = false
|
|
}
|