diff --git a/coderd/audit.go b/coderd/audit.go index 1a8f0e79b8b8e..d43f804e6a52d 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -50,6 +50,8 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) { Action: filter.Action, Username: filter.Username, Email: filter.Email, + DateFrom: filter.DateFrom, + DateTo: filter.DateTo, }) if err != nil { httpapi.InternalServerError(rw, err) @@ -84,6 +86,8 @@ func (api *API) auditLogCount(rw http.ResponseWriter, r *http.Request) { Action: filter.Action, Username: filter.Username, Email: filter.Email, + DateFrom: filter.DateFrom, + DateTo: filter.DateTo, }) if err != nil { httpapi.InternalServerError(rw, err) @@ -142,10 +146,13 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) { if params.ResourceID == uuid.Nil { params.ResourceID = uuid.New() } + if params.Time.IsZero() { + params.Time = time.Now() + } _, err = api.Database.InsertAuditLog(ctx, database.InsertAuditLogParams{ ID: uuid.New(), - Time: time.Now(), + Time: params.Time, UserID: user.ID, Ip: ipNet, UserAgent: r.UserAgent(), @@ -273,12 +280,33 @@ func auditSearchQuery(query string) (database.GetAuditLogsOffsetParams, []coders // Using the query param parser here just returns consistent errors with // other parsing. parser := httpapi.NewQueryParamParser() + const layout = "2006-01-02" + + var ( + dateFromString = parser.String(searchParams, "", "date_from") + dateToString = parser.String(searchParams, "", "date_to") + parsedDateFrom, _ = time.Parse(layout, dateFromString) + parsedDateTo, _ = time.Parse(layout, dateToString) + ) + + if dateToString != "" { + parsedDateTo = parsedDateTo.Add(23*time.Hour + 59*time.Minute + 59*time.Second) // parsedDateTo goes to 23:59 + } + + if dateToString != "" && parsedDateTo.Before(parsedDateFrom) { + return database.GetAuditLogsOffsetParams{}, []codersdk.ValidationError{ + {Field: "q", Detail: fmt.Sprintf("DateTo value %q cannot be before than DateFrom", parsedDateTo)}, + } + } + filter := database.GetAuditLogsOffsetParams{ ResourceType: resourceTypeFromString(parser.String(searchParams, "", "resource_type")), ResourceID: parser.UUID(searchParams, uuid.Nil, "resource_id"), Action: actionFromString(parser.String(searchParams, "", "action")), Username: parser.String(searchParams, "", "username"), Email: parser.String(searchParams, "", "email"), + DateFrom: parsedDateFrom, + DateTo: parsedDateTo, } return filter, parser.Errors diff --git a/coderd/audit_test.go b/coderd/audit_test.go index d281a6a97fdac..13c90bcceb08a 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -3,6 +3,7 @@ package coderd_test import ( "context" "testing" + "time" "github.com/google/uuid" "github.com/stretchr/testify/require" @@ -54,12 +55,14 @@ func TestAuditLogsFilter(t *testing.T) { err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ Action: codersdk.AuditActionCreate, ResourceType: codersdk.ResourceTypeTemplate, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ Action: codersdk.AuditActionCreate, ResourceType: codersdk.ResourceTypeUser, ResourceID: userResourceID, + Time: time.Date(2022, 8, 16, 14, 30, 45, 100, time.UTC), // 2022-8-16 14:30:45 }) require.NoError(t, err) @@ -68,6 +71,7 @@ func TestAuditLogsFilter(t *testing.T) { Action: codersdk.AuditActionDelete, ResourceType: codersdk.ResourceTypeUser, ResourceID: userResourceID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) @@ -127,6 +131,21 @@ func TestAuditLogsFilter(t *testing.T) { SearchQuery: "action:invalid", ExpectedResult: 3, }, + { + Name: "FilterOnCreateSingleDay", + SearchQuery: "action:create date_from:2022-08-15 date_to:2022-08-15", + ExpectedResult: 1, + }, + { + Name: "FilterOnCreateDateFrom", + SearchQuery: "action:create date_from:2022-08-15", + ExpectedResult: 2, + }, + { + Name: "FilterOnCreateDateTo", + SearchQuery: "action:create date_to:2022-08-15", + ExpectedResult: 1, + }, } for _, testCase := range testCases { diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index c6900c316ec1d..a9117f2b2efca 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -2995,6 +2995,16 @@ func (q *fakeQuerier) GetAuditLogsOffset(ctx context.Context, arg database.GetAu continue } } + if !arg.DateFrom.IsZero() { + if alog.Time.Before(arg.DateFrom) { + continue + } + } + if !arg.DateTo.IsZero() { + if alog.Time.After(arg.DateTo) { + continue + } + } user, err := q.GetUserByID(ctx, alog.UserID) userValid := err == nil @@ -3057,6 +3067,16 @@ func (q *fakeQuerier) GetAuditLogCount(_ context.Context, arg database.GetAuditL continue } } + if !arg.DateFrom.IsZero() { + if alog.Time.Before(arg.DateFrom) { + continue + } + } + if !arg.DateTo.IsZero() { + if alog.Time.After(arg.DateTo) { + continue + } + } logs = append(logs, alog) } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 786a8abcb43ca..2a2cc3cf09936 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -405,6 +405,18 @@ WHERE user_id = (SELECT id from users WHERE users.email = $6 ) ELSE true END + -- Filter by date_from + AND CASE + WHEN $7 :: timestamp with time zone != '0001-01-01 00:00:00' THEN + "time" >= $7 + ELSE true + END + -- Filter by date_to + AND CASE + WHEN $8 :: timestamp with time zone != '0001-01-01 00:00:00' THEN + "time" <= $8 + ELSE true + END ` type GetAuditLogCountParams struct { @@ -414,6 +426,8 @@ type GetAuditLogCountParams struct { Action string `db:"action" json:"action"` Username string `db:"username" json:"username"` Email string `db:"email" json:"email"` + DateFrom time.Time `db:"date_from" json:"date_from"` + DateTo time.Time `db:"date_to" json:"date_to"` } func (q *sqlQuerier) GetAuditLogCount(ctx context.Context, arg GetAuditLogCountParams) (int64, error) { @@ -424,6 +438,8 @@ func (q *sqlQuerier) GetAuditLogCount(ctx context.Context, arg GetAuditLogCountP arg.Action, arg.Username, arg.Email, + arg.DateFrom, + arg.DateTo, ) var count int64 err := row.Scan(&count) @@ -480,6 +496,18 @@ WHERE users.email = $8 ELSE true END + -- Filter by date_from + AND CASE + WHEN $9 :: timestamp with time zone != '0001-01-01 00:00:00' THEN + "time" >= $9 + ELSE true + END + -- Filter by date_to + AND CASE + WHEN $10 :: timestamp with time zone != '0001-01-01 00:00:00' THEN + "time" <= $10 + ELSE true + END ORDER BY "time" DESC LIMIT @@ -497,6 +525,8 @@ type GetAuditLogsOffsetParams struct { Action string `db:"action" json:"action"` Username string `db:"username" json:"username"` Email string `db:"email" json:"email"` + DateFrom time.Time `db:"date_from" json:"date_from"` + DateTo time.Time `db:"date_to" json:"date_to"` } type GetAuditLogsOffsetRow struct { @@ -535,6 +565,8 @@ func (q *sqlQuerier) GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOff arg.Action, arg.Username, arg.Email, + arg.DateFrom, + arg.DateTo, ) if err != nil { return nil, err diff --git a/coderd/database/queries/auditlogs.sql b/coderd/database/queries/auditlogs.sql index 5cb8e93db4876..b9219b264e670 100644 --- a/coderd/database/queries/auditlogs.sql +++ b/coderd/database/queries/auditlogs.sql @@ -50,6 +50,18 @@ WHERE users.email = @email ELSE true END + -- Filter by date_from + AND CASE + WHEN @date_from :: timestamp with time zone != '0001-01-01 00:00:00' THEN + "time" >= @date_from + ELSE true + END + -- Filter by date_to + AND CASE + WHEN @date_to :: timestamp with time zone != '0001-01-01 00:00:00' THEN + "time" <= @date_to + ELSE true + END ORDER BY "time" DESC LIMIT @@ -98,6 +110,18 @@ WHERE WHEN @email :: text != '' THEN user_id = (SELECT id from users WHERE users.email = @email ) ELSE true + END + -- Filter by date_from + AND CASE + WHEN @date_from :: timestamp with time zone != '0001-01-01 00:00:00' THEN + "time" >= @date_from + ELSE true + END + -- Filter by date_to + AND CASE + WHEN @date_to :: timestamp with time zone != '0001-01-01 00:00:00' THEN + "time" <= @date_to + ELSE true END; -- name: InsertAuditLog :one diff --git a/codersdk/audit.go b/codersdk/audit.go index 0fc10f27f05a4..8e77c875aeca5 100644 --- a/codersdk/audit.go +++ b/codersdk/audit.go @@ -127,6 +127,7 @@ type CreateTestAuditLogRequest struct { Action AuditAction `json:"action,omitempty"` ResourceType ResourceType `json:"resource_type,omitempty"` ResourceID uuid.UUID `json:"resource_id,omitempty"` + Time time.Time `json:"time,omitempty"` } // AuditLogs retrieves audit logs from the given page. diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index 7ecd5142cb660..bad6cd56cada2 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -30,6 +30,8 @@ The supported filters are: - `action`- The action applied to a resource. You can [find here](https://pkg.go.dev/github.com/coder/coder@main/codersdk#AuditAction) all the actions that are supported. - `username` - The username of the user who triggered the action. - `email` - The email of the user who triggered the action. +- `date_from` - The inclusive start date with format `YYYY-MM-DD`. +- `date_to ` - the inclusive end date with format `YYYY-MM-DD`. ## Enabling this feature diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 241f53b024c4b..2578ae7437844 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -109,13 +109,14 @@ var AuditableResources = auditMap(map[any]map[string]Action{ "organization_id": ActionIgnore, // Never changes. "avatar_url": ActionTrack, }, - // We don't show any diff for the WorkspaceBuild resource + // We don't show any diff for the WorkspaceBuild resource, + // save for the template_version_id &database.WorkspaceBuild{}: { "id": ActionIgnore, "created_at": ActionIgnore, "updated_at": ActionIgnore, "workspace_id": ActionIgnore, - "template_version_id": ActionIgnore, + "template_version_id": ActionTrack, "build_number": ActionIgnore, "transition": ActionIgnore, "initiator_id": ActionIgnore, diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 2c704b1988e85..764aab5c6e761 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -204,6 +204,7 @@ export interface CreateTestAuditLogRequest { readonly action?: AuditAction readonly resource_type?: ResourceType readonly resource_id?: string + readonly time?: string } // From codersdk/apikey.go