Files
omega-server/local/model/security_event.go
Fran Jurmanović 016728532c init bootstrap
2025-07-06 15:02:09 +02:00

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
}