mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
feat: Add extra fields to the audit filter (#4123)
This commit is contained in:
@ -46,7 +46,10 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) {
|
||||
Offset: int32(page.Offset),
|
||||
Limit: int32(page.Limit),
|
||||
ResourceType: filter.ResourceType,
|
||||
ResourceID: filter.ResourceID,
|
||||
Action: filter.Action,
|
||||
Username: filter.Username,
|
||||
Email: filter.Email,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
@ -77,7 +80,10 @@ func (api *API) auditLogCount(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
count, err := api.Database.GetAuditLogCount(ctx, database.GetAuditLogCountParams{
|
||||
ResourceType: filter.ResourceType,
|
||||
ResourceID: filter.ResourceID,
|
||||
Action: filter.Action,
|
||||
Username: filter.Username,
|
||||
Email: filter.Email,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
@ -134,6 +140,9 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
|
||||
if params.ResourceType == "" {
|
||||
params.ResourceType = codersdk.ResourceTypeUser
|
||||
}
|
||||
if params.ResourceID == uuid.Nil {
|
||||
params.ResourceID = uuid.New()
|
||||
}
|
||||
|
||||
_, err = api.Database.InsertAuditLog(ctx, database.InsertAuditLogParams{
|
||||
ID: uuid.New(),
|
||||
@ -142,7 +151,7 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
|
||||
Ip: ipNet,
|
||||
UserAgent: r.UserAgent(),
|
||||
ResourceType: database.ResourceType(params.ResourceType),
|
||||
ResourceID: user.ID,
|
||||
ResourceID: params.ResourceID,
|
||||
ResourceTarget: user.Username,
|
||||
Action: database.AuditAction(params.Action),
|
||||
Diff: diff,
|
||||
@ -251,7 +260,10 @@ func auditSearchQuery(query string) (database.GetAuditLogsOffsetParams, []coders
|
||||
parser := httpapi.NewQueryParamParser()
|
||||
filter := database.GetAuditLogsOffsetParams{
|
||||
ResourceType: parser.String(searchParams, "", "resource_type"),
|
||||
ResourceID: parser.UUID(searchParams, uuid.Nil, "resource_id"),
|
||||
Action: parser.String(searchParams, "", "action"),
|
||||
Username: parser.String(searchParams, "", "username"),
|
||||
Email: parser.String(searchParams, "", "email"),
|
||||
}
|
||||
|
||||
return filter, parser.Errors
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
@ -47,6 +48,7 @@ func TestAuditLogsFilter(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
userResourceID := uuid.New()
|
||||
|
||||
// Create two logs with "Create"
|
||||
err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
|
||||
@ -57,6 +59,7 @@ func TestAuditLogsFilter(t *testing.T) {
|
||||
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
|
||||
Action: codersdk.AuditActionCreate,
|
||||
ResourceType: codersdk.ResourceTypeUser,
|
||||
ResourceID: userResourceID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -64,6 +67,7 @@ func TestAuditLogsFilter(t *testing.T) {
|
||||
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
|
||||
Action: codersdk.AuditActionDelete,
|
||||
ResourceType: codersdk.ResourceTypeUser,
|
||||
ResourceID: userResourceID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -93,9 +97,25 @@ func TestAuditLogsFilter(t *testing.T) {
|
||||
SearchQuery: "resource_type:template",
|
||||
ExpectedResult: 1,
|
||||
},
|
||||
{
|
||||
Name: "FilterByEmail",
|
||||
SearchQuery: "email:" + coderdtest.FirstUserParams.Email,
|
||||
ExpectedResult: 3,
|
||||
},
|
||||
{
|
||||
Name: "FilterByUsername",
|
||||
SearchQuery: "username:" + coderdtest.FirstUserParams.Username,
|
||||
ExpectedResult: 3,
|
||||
},
|
||||
{
|
||||
Name: "FilterByResourceID",
|
||||
SearchQuery: "resource_id:" + userResourceID.String(),
|
||||
ExpectedResult: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
// Test filtering
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
auditLogs, err := client.AuditLogs(ctx, codersdk.AuditLogsRequest{
|
||||
@ -107,75 +127,15 @@ func TestAuditLogsFilter(t *testing.T) {
|
||||
require.NoError(t, err, "fetch audit logs")
|
||||
require.Len(t, auditLogs.AuditLogs, testCase.ExpectedResult, "expected audit logs returned")
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuditLogCountFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Filter", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
// Create two logs with "Create"
|
||||
err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
|
||||
Action: codersdk.AuditActionCreate,
|
||||
ResourceType: codersdk.ResourceTypeTemplate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
|
||||
Action: codersdk.AuditActionCreate,
|
||||
ResourceType: codersdk.ResourceTypeUser,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create one log with "Delete"
|
||||
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
|
||||
Action: codersdk.AuditActionDelete,
|
||||
ResourceType: codersdk.ResourceTypeUser,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test cases
|
||||
testCases := []struct {
|
||||
Name string
|
||||
SearchQuery string
|
||||
ExpectedResult int64
|
||||
}{
|
||||
{
|
||||
Name: "FilterByCreateAction",
|
||||
SearchQuery: "action:create",
|
||||
ExpectedResult: 2,
|
||||
},
|
||||
{
|
||||
Name: "FilterByDeleteAction",
|
||||
SearchQuery: "action:delete",
|
||||
ExpectedResult: 1,
|
||||
},
|
||||
{
|
||||
Name: "FilterByUserResourceType",
|
||||
SearchQuery: "resource_type:user",
|
||||
ExpectedResult: 2,
|
||||
},
|
||||
{
|
||||
Name: "FilterByTemplateResourceType",
|
||||
SearchQuery: "resource_type:template",
|
||||
ExpectedResult: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
// Test count filtering
|
||||
t.Run("GetCount"+testCase.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
response, err := client.AuditLogCount(ctx, codersdk.AuditLogCountRequest{
|
||||
SearchQuery: testCase.SearchQuery,
|
||||
})
|
||||
require.NoError(t, err, "fetch audit logs count")
|
||||
require.Equal(t, response.Count, testCase.ExpectedResult, "expected audit logs count returned")
|
||||
require.Equal(t, int(response.Count), testCase.ExpectedResult, "expected audit logs count returned")
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -2388,14 +2388,27 @@ func (q *fakeQuerier) GetAuditLogsOffset(ctx context.Context, arg database.GetAu
|
||||
arg.Offset--
|
||||
continue
|
||||
}
|
||||
|
||||
if arg.Action != "" && !strings.Contains(string(alog.Action), arg.Action) {
|
||||
continue
|
||||
}
|
||||
|
||||
if arg.ResourceType != "" && !strings.Contains(string(alog.ResourceType), arg.ResourceType) {
|
||||
continue
|
||||
}
|
||||
if arg.ResourceID != uuid.Nil && alog.ResourceID != arg.ResourceID {
|
||||
continue
|
||||
}
|
||||
if arg.Username != "" {
|
||||
user, err := q.GetUserByID(context.Background(), alog.UserID)
|
||||
if err == nil && !strings.EqualFold(arg.Username, user.Username) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if arg.Email != "" {
|
||||
user, err := q.GetUserByID(context.Background(), alog.UserID)
|
||||
if err == nil && !strings.EqualFold(arg.Email, user.Email) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
user, err := q.GetUserByID(ctx, alog.UserID)
|
||||
userValid := err == nil
|
||||
@ -2440,10 +2453,24 @@ func (q *fakeQuerier) GetAuditLogCount(_ context.Context, arg database.GetAuditL
|
||||
if arg.Action != "" && !strings.Contains(string(alog.Action), arg.Action) {
|
||||
continue
|
||||
}
|
||||
|
||||
if arg.ResourceType != "" && !strings.Contains(string(alog.ResourceType), arg.ResourceType) {
|
||||
continue
|
||||
}
|
||||
if arg.ResourceID != uuid.Nil && alog.ResourceID != arg.ResourceID {
|
||||
continue
|
||||
}
|
||||
if arg.Username != "" {
|
||||
user, err := q.GetUserByID(context.Background(), alog.UserID)
|
||||
if err == nil && !strings.EqualFold(arg.Username, user.Username) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if arg.Email != "" {
|
||||
user, err := q.GetUserByID(context.Background(), alog.UserID)
|
||||
if err == nil && !strings.EqualFold(arg.Email, user.Email) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
logs = append(logs, alog)
|
||||
}
|
||||
|
@ -314,9 +314,9 @@ func (q *sqlQuerier) UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDP
|
||||
|
||||
const getAuditLogCount = `-- name: GetAuditLogCount :one
|
||||
SELECT
|
||||
COUNT(*) as count
|
||||
COUNT(*) as count
|
||||
FROM
|
||||
audit_logs
|
||||
audit_logs
|
||||
WHERE
|
||||
-- Filter resource_type
|
||||
CASE
|
||||
@ -324,21 +324,56 @@ WHERE
|
||||
resource_type = $1 :: resource_type
|
||||
ELSE true
|
||||
END
|
||||
-- Filter resource_id
|
||||
AND CASE
|
||||
WHEN $2 :: uuid != '00000000-00000000-00000000-00000000' THEN
|
||||
resource_id = $2
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by resource_target
|
||||
AND CASE
|
||||
WHEN $3 :: text != '' THEN
|
||||
resource_target = $3
|
||||
ELSE true
|
||||
END
|
||||
-- Filter action
|
||||
AND CASE
|
||||
WHEN $2 :: text != '' THEN
|
||||
action = $2 :: audit_action
|
||||
WHEN $4 :: text != '' THEN
|
||||
action = $4 :: audit_action
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by username
|
||||
AND CASE
|
||||
WHEN $5 :: text != '' THEN
|
||||
user_id = (SELECT id from users WHERE users.username = $5 )
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by user_email
|
||||
AND CASE
|
||||
WHEN $6 :: text != '' THEN
|
||||
user_id = (SELECT id from users WHERE users.email = $6 )
|
||||
ELSE true
|
||||
END
|
||||
`
|
||||
|
||||
type GetAuditLogCountParams struct {
|
||||
ResourceType string `db:"resource_type" json:"resource_type"`
|
||||
Action string `db:"action" json:"action"`
|
||||
ResourceType string `db:"resource_type" json:"resource_type"`
|
||||
ResourceID uuid.UUID `db:"resource_id" json:"resource_id"`
|
||||
ResourceTarget string `db:"resource_target" json:"resource_target"`
|
||||
Action string `db:"action" json:"action"`
|
||||
Username string `db:"username" json:"username"`
|
||||
Email string `db:"email" json:"email"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetAuditLogCount(ctx context.Context, arg GetAuditLogCountParams) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, getAuditLogCount, arg.ResourceType, arg.Action)
|
||||
row := q.db.QueryRowContext(ctx, getAuditLogCount,
|
||||
arg.ResourceType,
|
||||
arg.ResourceID,
|
||||
arg.ResourceTarget,
|
||||
arg.Action,
|
||||
arg.Username,
|
||||
arg.Email,
|
||||
)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
@ -364,10 +399,34 @@ WHERE
|
||||
resource_type = $3 :: resource_type
|
||||
ELSE true
|
||||
END
|
||||
-- Filter resource_id
|
||||
AND CASE
|
||||
WHEN $4 :: uuid != '00000000-00000000-00000000-00000000' THEN
|
||||
resource_id = $4
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by resource_target
|
||||
AND CASE
|
||||
WHEN $5 :: text != '' THEN
|
||||
resource_target = $5
|
||||
ELSE true
|
||||
END
|
||||
-- Filter action
|
||||
AND CASE
|
||||
WHEN $4 :: text != '' THEN
|
||||
action = $4 :: audit_action
|
||||
WHEN $6 :: text != '' THEN
|
||||
action = $6 :: audit_action
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by username
|
||||
AND CASE
|
||||
WHEN $7 :: text != '' THEN
|
||||
users.username = $7
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by user_email
|
||||
AND CASE
|
||||
WHEN $8 :: text != '' THEN
|
||||
users.email = $8
|
||||
ELSE true
|
||||
END
|
||||
ORDER BY
|
||||
@ -379,10 +438,14 @@ OFFSET
|
||||
`
|
||||
|
||||
type GetAuditLogsOffsetParams struct {
|
||||
Limit int32 `db:"limit" json:"limit"`
|
||||
Offset int32 `db:"offset" json:"offset"`
|
||||
ResourceType string `db:"resource_type" json:"resource_type"`
|
||||
Action string `db:"action" json:"action"`
|
||||
Limit int32 `db:"limit" json:"limit"`
|
||||
Offset int32 `db:"offset" json:"offset"`
|
||||
ResourceType string `db:"resource_type" json:"resource_type"`
|
||||
ResourceID uuid.UUID `db:"resource_id" json:"resource_id"`
|
||||
ResourceTarget string `db:"resource_target" json:"resource_target"`
|
||||
Action string `db:"action" json:"action"`
|
||||
Username string `db:"username" json:"username"`
|
||||
Email string `db:"email" json:"email"`
|
||||
}
|
||||
|
||||
type GetAuditLogsOffsetRow struct {
|
||||
@ -416,7 +479,11 @@ func (q *sqlQuerier) GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOff
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.ResourceType,
|
||||
arg.ResourceID,
|
||||
arg.ResourceTarget,
|
||||
arg.Action,
|
||||
arg.Username,
|
||||
arg.Email,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -20,12 +20,36 @@ WHERE
|
||||
resource_type = @resource_type :: resource_type
|
||||
ELSE true
|
||||
END
|
||||
-- Filter resource_id
|
||||
AND CASE
|
||||
WHEN @resource_id :: uuid != '00000000-00000000-00000000-00000000' THEN
|
||||
resource_id = @resource_id
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by resource_target
|
||||
AND CASE
|
||||
WHEN @resource_target :: text != '' THEN
|
||||
resource_target = @resource_target
|
||||
ELSE true
|
||||
END
|
||||
-- Filter action
|
||||
AND CASE
|
||||
WHEN @action :: text != '' THEN
|
||||
action = @action :: audit_action
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by username
|
||||
AND CASE
|
||||
WHEN @username :: text != '' THEN
|
||||
users.username = @username
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by user_email
|
||||
AND CASE
|
||||
WHEN @email :: text != '' THEN
|
||||
users.email = @email
|
||||
ELSE true
|
||||
END
|
||||
ORDER BY
|
||||
"time" DESC
|
||||
LIMIT
|
||||
@ -35,9 +59,9 @@ OFFSET
|
||||
|
||||
-- name: GetAuditLogCount :one
|
||||
SELECT
|
||||
COUNT(*) as count
|
||||
COUNT(*) as count
|
||||
FROM
|
||||
audit_logs
|
||||
audit_logs
|
||||
WHERE
|
||||
-- Filter resource_type
|
||||
CASE
|
||||
@ -45,11 +69,35 @@ WHERE
|
||||
resource_type = @resource_type :: resource_type
|
||||
ELSE true
|
||||
END
|
||||
-- Filter resource_id
|
||||
AND CASE
|
||||
WHEN @resource_id :: uuid != '00000000-00000000-00000000-00000000' THEN
|
||||
resource_id = @resource_id
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by resource_target
|
||||
AND CASE
|
||||
WHEN @resource_target :: text != '' THEN
|
||||
resource_target = @resource_target
|
||||
ELSE true
|
||||
END
|
||||
-- Filter action
|
||||
AND CASE
|
||||
WHEN @action :: text != '' THEN
|
||||
action = @action :: audit_action
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by username
|
||||
AND CASE
|
||||
WHEN @username :: text != '' THEN
|
||||
user_id = (SELECT id from users WHERE users.username = @username )
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by user_email
|
||||
AND CASE
|
||||
WHEN @email :: text != '' THEN
|
||||
user_id = (SELECT id from users WHERE users.email = @email )
|
||||
ELSE true
|
||||
END;
|
||||
|
||||
-- name: InsertAuditLog :one
|
||||
|
Reference in New Issue
Block a user