feat: Add extra fields to the audit filter (#4123)

This commit is contained in:
Bruno Quaresma
2022-09-20 13:07:21 -03:00
committed by GitHub
parent 3618b098cb
commit bc47d7ce69
7 changed files with 198 additions and 82 deletions

View File

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

View File

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

View File

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

View File

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

View File

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