Audit date filter/kira pilot (#4845)

* sql query

* added time_to

* added validation error

* documentation

* attempt to add test

* removed whiitespace

* fix: ensure date_from and date_to are applied correct audit logs

* added more tests

* ran make gen

* PR feedback

Co-authored-by: Dean Sheather <dean@deansheather.com>
This commit is contained in:
Kira Pilot 2022-11-03 11:04:36 -04:00 committed by GitHub
parent 6bfdccda2f
commit a73dd4f45d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 131 additions and 3 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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,

View File

@ -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