282 lines
8.8 KiB
Go
282 lines
8.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/kms/api-key-service/internal/audit"
|
|
"github.com/kms/api-key-service/internal/errors"
|
|
"github.com/kms/api-key-service/internal/services"
|
|
"github.com/kms/api-key-service/internal/validation"
|
|
)
|
|
|
|
// AuditHandler handles audit-related HTTP requests
|
|
type AuditHandler struct {
|
|
auditLogger audit.AuditLogger
|
|
authService services.AuthenticationService
|
|
validator *validation.Validator
|
|
errorHandler *errors.ErrorHandler
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewAuditHandler creates a new audit handler
|
|
func NewAuditHandler(
|
|
auditLogger audit.AuditLogger,
|
|
authService services.AuthenticationService,
|
|
logger *zap.Logger,
|
|
) *AuditHandler {
|
|
return &AuditHandler{
|
|
auditLogger: auditLogger,
|
|
authService: authService,
|
|
validator: validation.NewValidator(logger),
|
|
errorHandler: errors.NewErrorHandler(logger),
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// AuditQueryRequest represents the request for querying audit events
|
|
type AuditQueryRequest struct {
|
|
EventTypes []string `json:"event_types,omitempty" form:"event_types"`
|
|
Statuses []string `json:"statuses,omitempty" form:"statuses"`
|
|
ActorID string `json:"actor_id,omitempty" form:"actor_id"`
|
|
ResourceID string `json:"resource_id,omitempty" form:"resource_id"`
|
|
ResourceType string `json:"resource_type,omitempty" form:"resource_type"`
|
|
StartTime *string `json:"start_time,omitempty" form:"start_time"`
|
|
EndTime *string `json:"end_time,omitempty" form:"end_time"`
|
|
Limit int `json:"limit,omitempty" form:"limit"`
|
|
Offset int `json:"offset,omitempty" form:"offset"`
|
|
OrderBy string `json:"order_by,omitempty" form:"order_by"`
|
|
OrderDesc *bool `json:"order_desc,omitempty" form:"order_desc"`
|
|
}
|
|
|
|
// AuditStatsRequest represents the request for audit statistics
|
|
type AuditStatsRequest struct {
|
|
EventTypes []string `json:"event_types,omitempty" form:"event_types"`
|
|
StartTime *string `json:"start_time,omitempty" form:"start_time"`
|
|
EndTime *string `json:"end_time,omitempty" form:"end_time"`
|
|
GroupBy string `json:"group_by,omitempty" form:"group_by"`
|
|
}
|
|
|
|
// AuditResponse represents the response structure for audit queries
|
|
type AuditResponse struct {
|
|
Events []AuditEventResponse `json:"events"`
|
|
Total int `json:"total"`
|
|
Limit int `json:"limit"`
|
|
Offset int `json:"offset"`
|
|
}
|
|
|
|
// AuditEventResponse represents a single audit event in API responses
|
|
type AuditEventResponse struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
Timestamp string `json:"timestamp"`
|
|
ActorID string `json:"actor_id,omitempty"`
|
|
ActorIP string `json:"actor_ip,omitempty"`
|
|
UserAgent string `json:"user_agent,omitempty"`
|
|
ResourceID string `json:"resource_id,omitempty"`
|
|
ResourceType string `json:"resource_type,omitempty"`
|
|
Action string `json:"action"`
|
|
Description string `json:"description"`
|
|
Details map[string]interface{} `json:"details,omitempty"`
|
|
RequestID string `json:"request_id,omitempty"`
|
|
SessionID string `json:"session_id,omitempty"`
|
|
}
|
|
|
|
// ListEvents handles GET /audit/events
|
|
func (h *AuditHandler) ListEvents(c *gin.Context) {
|
|
// Parse query parameters
|
|
var req AuditQueryRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
h.errorHandler.HandleValidationError(c, "query_params", "Invalid query parameters")
|
|
return
|
|
}
|
|
|
|
// Set defaults
|
|
if req.Limit <= 0 || req.Limit > 1000 {
|
|
req.Limit = 100
|
|
}
|
|
if req.Offset < 0 {
|
|
req.Offset = 0
|
|
}
|
|
if req.OrderBy == "" {
|
|
req.OrderBy = "timestamp"
|
|
}
|
|
if req.OrderDesc == nil {
|
|
orderDesc := true
|
|
req.OrderDesc = &orderDesc
|
|
}
|
|
|
|
// Convert request to audit filter
|
|
filter := &audit.AuditFilter{
|
|
ActorID: req.ActorID,
|
|
ResourceID: req.ResourceID,
|
|
ResourceType: req.ResourceType,
|
|
Limit: req.Limit,
|
|
Offset: req.Offset,
|
|
OrderBy: req.OrderBy,
|
|
OrderDesc: *req.OrderDesc,
|
|
}
|
|
|
|
// Convert event types
|
|
for _, et := range req.EventTypes {
|
|
filter.EventTypes = append(filter.EventTypes, audit.EventType(et))
|
|
}
|
|
|
|
// Convert statuses
|
|
for _, st := range req.Statuses {
|
|
filter.Statuses = append(filter.Statuses, audit.EventStatus(st))
|
|
}
|
|
|
|
// Parse time filters
|
|
if req.StartTime != nil && *req.StartTime != "" {
|
|
if startTime, err := time.Parse(time.RFC3339, *req.StartTime); err == nil {
|
|
filter.StartTime = &startTime
|
|
} else {
|
|
h.errorHandler.HandleValidationError(c, "start_time", "Invalid start_time format, use RFC3339")
|
|
return
|
|
}
|
|
}
|
|
|
|
if req.EndTime != nil && *req.EndTime != "" {
|
|
if endTime, err := time.Parse(time.RFC3339, *req.EndTime); err == nil {
|
|
filter.EndTime = &endTime
|
|
} else {
|
|
h.errorHandler.HandleValidationError(c, "end_time", "Invalid end_time format, use RFC3339")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Query audit events
|
|
events, err := h.auditLogger.QueryEvents(c.Request.Context(), filter)
|
|
if err != nil {
|
|
h.logger.Error("Failed to query audit events", zap.Error(err))
|
|
h.errorHandler.HandleInternalError(c, err)
|
|
return
|
|
}
|
|
|
|
// Convert to response format
|
|
response := &AuditResponse{
|
|
Events: make([]AuditEventResponse, len(events)),
|
|
Total: len(events), // Note: This is just the count of returned events, not total matching
|
|
Limit: req.Limit,
|
|
Offset: req.Offset,
|
|
}
|
|
|
|
for i, event := range events {
|
|
response.Events[i] = AuditEventResponse{
|
|
ID: event.ID.String(),
|
|
Type: string(event.Type),
|
|
Status: string(event.Status),
|
|
Timestamp: event.Timestamp.Format(time.RFC3339),
|
|
ActorID: event.ActorID,
|
|
ActorIP: event.ActorIP,
|
|
UserAgent: event.UserAgent,
|
|
ResourceID: event.ResourceID,
|
|
ResourceType: event.ResourceType,
|
|
Action: event.Action,
|
|
Description: event.Description,
|
|
Details: event.Details,
|
|
RequestID: event.RequestID,
|
|
SessionID: event.SessionID,
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetEvent handles GET /audit/events/:id
|
|
func (h *AuditHandler) GetEvent(c *gin.Context) {
|
|
eventIDStr := c.Param("id")
|
|
eventID, err := uuid.Parse(eventIDStr)
|
|
if err != nil {
|
|
h.errorHandler.HandleValidationError(c, "id", "Invalid event ID format")
|
|
return
|
|
}
|
|
|
|
// Get the specific audit event
|
|
event, err := h.auditLogger.GetEventByID(c.Request.Context(), eventID)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get audit event", zap.Error(err), zap.String("event_id", eventID.String()))
|
|
// Check if it's a not found error
|
|
if err.Error() == "audit event with ID '"+eventID.String()+"' not found" {
|
|
h.errorHandler.HandleNotFoundError(c, "audit_event", "Audit event not found")
|
|
} else {
|
|
h.errorHandler.HandleInternalError(c, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Convert to response format
|
|
response := AuditEventResponse{
|
|
ID: event.ID.String(),
|
|
Type: string(event.Type),
|
|
Status: string(event.Status),
|
|
Timestamp: event.Timestamp.Format(time.RFC3339),
|
|
ActorID: event.ActorID,
|
|
ActorIP: event.ActorIP,
|
|
UserAgent: event.UserAgent,
|
|
ResourceID: event.ResourceID,
|
|
ResourceType: event.ResourceType,
|
|
Action: event.Action,
|
|
Description: event.Description,
|
|
Details: event.Details,
|
|
RequestID: event.RequestID,
|
|
SessionID: event.SessionID,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetStats handles GET /audit/stats
|
|
func (h *AuditHandler) GetStats(c *gin.Context) {
|
|
// Parse query parameters
|
|
var req AuditStatsRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
h.errorHandler.HandleValidationError(c, "query_params", "Invalid query parameters")
|
|
return
|
|
}
|
|
|
|
// Convert request to audit stats filter
|
|
filter := &audit.AuditStatsFilter{
|
|
GroupBy: req.GroupBy,
|
|
}
|
|
|
|
// Convert event types
|
|
for _, et := range req.EventTypes {
|
|
filter.EventTypes = append(filter.EventTypes, audit.EventType(et))
|
|
}
|
|
|
|
// Parse time filters
|
|
if req.StartTime != nil && *req.StartTime != "" {
|
|
if startTime, err := time.Parse(time.RFC3339, *req.StartTime); err == nil {
|
|
filter.StartTime = &startTime
|
|
} else {
|
|
h.errorHandler.HandleValidationError(c, "start_time", "Invalid start_time format, use RFC3339")
|
|
return
|
|
}
|
|
}
|
|
|
|
if req.EndTime != nil && *req.EndTime != "" {
|
|
if endTime, err := time.Parse(time.RFC3339, *req.EndTime); err == nil {
|
|
filter.EndTime = &endTime
|
|
} else {
|
|
h.errorHandler.HandleValidationError(c, "end_time", "Invalid end_time format, use RFC3339")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Get audit statistics
|
|
stats, err := h.auditLogger.GetEventStats(c.Request.Context(), filter)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get audit statistics", zap.Error(err))
|
|
h.errorHandler.HandleInternalError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, stats)
|
|
} |