-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaudit.go
More file actions
144 lines (129 loc) · 4.2 KB
/
audit.go
File metadata and controls
144 lines (129 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package flashduty
import (
"context"
"fmt"
"net/http"
)
// SearchAuditLogsInput contains parameters for searching audit logs
type SearchAuditLogsInput struct {
StartTime int64 // Required: Unix seconds
EndTime int64 // Required: Unix seconds
Limit int // Max results (default 20)
RequestID string // Optional: filter by request ID
SearchAfterCtx string // Optional: opaque cursor for the next page
Operations []string // Optional: filter by operation names
PersonID int64 // Optional: filter by operator person ID
IsDangerous *bool // Optional: filter high-risk operations
IsWrite *bool // Optional: filter read/write operations
// Deprecated: use Operations.
Operation string
// Deprecated: use PersonID.
OperatorID int64
// Deprecated: /audit/search uses SearchAfterCtx for pagination.
Page int
}
// AuditLogParam represents a single path parameter captured in an audit log entry.
type AuditLogParam struct {
Key string `json:"Key"`
Value string `json:"Value"`
}
// AuditLogRecord represents a single audit log entry returned by /audit/search.
type AuditLogRecord struct {
CreatedAt int64 `json:"created_at"`
AccountID int64 `json:"account_id"`
MemberID int64 `json:"member_id"`
MemberName string `json:"member_name"`
RequestID string `json:"request_id"`
IP string `json:"ip"`
Operation string `json:"operation"`
OperationName string `json:"operation_name"`
Body string `json:"body"`
Params []AuditLogParam `json:"params"`
IsDangerous bool `json:"is_dangerous"`
IsWrite bool `json:"is_write"`
}
// SearchAuditLogsOutput contains audit log entries plus the next-page cursor.
type SearchAuditLogsOutput struct {
AuditLogs []AuditLogRecord `json:"audit_logs"`
Total int64 `json:"total"`
SearchAfterCtx string `json:"search_after_ctx"`
}
// SearchAuditLogs searches the audit log
func (c *Client) SearchAuditLogs(ctx context.Context, input *SearchAuditLogsInput) (*SearchAuditLogsOutput, error) {
if input == nil {
return nil, fmt.Errorf("search audit logs input is required")
}
if input.StartTime <= 0 || input.EndTime <= 0 {
return nil, fmt.Errorf("start_time and end_time are required")
}
limit := input.Limit
if limit <= 0 {
limit = defaultQueryLimit
}
requestBody := map[string]any{
"start_time": input.StartTime,
"end_time": input.EndTime,
"limit": limit,
}
if input.RequestID != "" {
requestBody["request_id"] = input.RequestID
}
if input.SearchAfterCtx != "" {
requestBody["search_after_ctx"] = input.SearchAfterCtx
}
operations := input.Operations
if len(operations) == 0 && input.Operation != "" {
operations = []string{input.Operation}
}
if len(operations) > 0 {
requestBody["operations"] = operations
}
personID := input.PersonID
if personID <= 0 && input.OperatorID > 0 {
personID = input.OperatorID
}
if personID > 0 {
requestBody["person_id"] = personID
}
if input.IsDangerous != nil {
requestBody["is_dangerous"] = *input.IsDangerous
}
if input.IsWrite != nil {
requestBody["is_write"] = *input.IsWrite
}
resp, err := c.makeRequest(ctx, "POST", "/audit/search", requestBody)
if err != nil {
return nil, fmt.Errorf("failed to search audit logs: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return nil, handleAPIError(c.logger, resp)
}
var result struct {
Error *DutyError `json:"error,omitempty"`
Data *struct {
Docs []AuditLogRecord `json:"docs"`
Total int64 `json:"total"`
SearchAfterCtx string `json:"search_after_ctx"`
} `json:"data,omitempty"`
}
if err := parseResponse(c.logger, resp, &result); err != nil {
return nil, err
}
if result.Error != nil {
return nil, result.Error
}
logs := []AuditLogRecord{}
total := int64(0)
searchAfterCtx := ""
if result.Data != nil {
logs = result.Data.Docs
total = result.Data.Total
searchAfterCtx = result.Data.SearchAfterCtx
}
return &SearchAuditLogsOutput{
AuditLogs: logs,
Total: total,
SearchAfterCtx: searchAfterCtx,
}, nil
}