mirror of https://github.com/coder/coder.git
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:
parent
6bfdccda2f
commit
a73dd4f45d
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue