mirror of https://github.com/coder/coder.git
feat: audit log api (#3898)
This commit is contained in:
parent
ad24404018
commit
3d6d51fbd0
|
@ -17,7 +17,6 @@ import (
|
|||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog"
|
||||
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
package coderd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/tabbed/pqtype"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/coderd/httpmw"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) {
|
||||
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceAuditLog) {
|
||||
httpapi.Forbidden(rw)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
page, ok := parsePagination(rw, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
dblogs, err := api.Database.GetAuditLogsOffset(ctx, database.GetAuditLogsOffsetParams{
|
||||
Offset: int32(page.Offset),
|
||||
Limit: int32(page.Limit),
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, codersdk.AuditLogResponse{
|
||||
AuditLogs: convertAuditLogs(dblogs),
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) auditLogCount(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceAuditLog) {
|
||||
httpapi.Forbidden(rw)
|
||||
return
|
||||
}
|
||||
|
||||
count, err := api.Database.GetAuditLogCount(ctx)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, codersdk.AuditLogCountResponse{
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceAuditLog) {
|
||||
httpapi.Forbidden(rw)
|
||||
return
|
||||
}
|
||||
|
||||
key := httpmw.APIKey(r)
|
||||
user, err := api.Database.GetUserByID(ctx, key.UserID)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
diff, err := json.Marshal(codersdk.AuditDiff{
|
||||
"foo": codersdk.AuditDiffField{Old: "bar", New: "baz"},
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
ipRaw, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
ip := net.ParseIP(ipRaw)
|
||||
ipNet := pqtype.Inet{}
|
||||
if ip != nil {
|
||||
ipNet = pqtype.Inet{
|
||||
IPNet: net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(len(ip)*8, len(ip)*8),
|
||||
},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
_, err = api.Database.InsertAuditLog(ctx, database.InsertAuditLogParams{
|
||||
ID: uuid.New(),
|
||||
Time: time.Now(),
|
||||
UserID: user.ID,
|
||||
Ip: ipNet,
|
||||
UserAgent: r.UserAgent(),
|
||||
ResourceType: database.ResourceTypeUser,
|
||||
ResourceID: user.ID,
|
||||
ResourceTarget: user.Username,
|
||||
Action: database.AuditActionWrite,
|
||||
Diff: diff,
|
||||
StatusCode: http.StatusOK,
|
||||
AdditionalFields: []byte("{}"),
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func convertAuditLogs(dblogs []database.GetAuditLogsOffsetRow) []codersdk.AuditLog {
|
||||
alogs := make([]codersdk.AuditLog, 0, len(dblogs))
|
||||
|
||||
for _, dblog := range dblogs {
|
||||
alogs = append(alogs, convertAuditLog(dblog))
|
||||
}
|
||||
|
||||
return alogs
|
||||
}
|
||||
|
||||
func convertAuditLog(dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog {
|
||||
ip, _ := netip.AddrFromSlice(dblog.Ip.IPNet.IP)
|
||||
|
||||
diff := codersdk.AuditDiff{}
|
||||
_ = json.Unmarshal(dblog.Diff, &diff)
|
||||
|
||||
var user *codersdk.User
|
||||
if dblog.UserUsername.Valid {
|
||||
user = &codersdk.User{
|
||||
ID: dblog.UserID,
|
||||
Username: dblog.UserUsername.String,
|
||||
Email: dblog.UserEmail.String,
|
||||
CreatedAt: dblog.UserCreatedAt.Time,
|
||||
Status: codersdk.UserStatus(dblog.UserStatus),
|
||||
Roles: []codersdk.Role{},
|
||||
}
|
||||
|
||||
for _, roleName := range dblog.UserRoles {
|
||||
rbacRole, _ := rbac.RoleByName(roleName)
|
||||
user.Roles = append(user.Roles, convertRole(rbacRole))
|
||||
}
|
||||
}
|
||||
|
||||
return codersdk.AuditLog{
|
||||
ID: dblog.ID,
|
||||
RequestID: dblog.RequestID,
|
||||
Time: dblog.Time,
|
||||
OrganizationID: dblog.OrganizationID,
|
||||
IP: ip,
|
||||
UserAgent: dblog.UserAgent,
|
||||
ResourceType: codersdk.ResourceType(dblog.ResourceType),
|
||||
ResourceID: dblog.ResourceID,
|
||||
ResourceTarget: dblog.ResourceTarget,
|
||||
ResourceIcon: dblog.ResourceIcon,
|
||||
Action: codersdk.AuditAction(dblog.Action),
|
||||
Diff: diff,
|
||||
StatusCode: dblog.StatusCode,
|
||||
AdditionalFields: dblog.AdditionalFields,
|
||||
Description: "",
|
||||
User: user,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package coderd_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
func TestAuditLogs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
err := client.CreateTestAuditLog(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err := client.AuditLogCount(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
alogs, err := client.AuditLogs(ctx, codersdk.Pagination{Limit: 1})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(1), count.Count)
|
||||
require.Len(t, alogs.AuditLogs, 1)
|
||||
})
|
||||
}
|
|
@ -220,6 +220,15 @@ func New(options *Options) *API {
|
|||
})
|
||||
})
|
||||
})
|
||||
r.Route("/audit", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
)
|
||||
|
||||
r.Get("/", api.auditLogs)
|
||||
r.Get("/count", api.auditLogCount)
|
||||
r.Post("/testgenerate", api.generateFakeAuditLog)
|
||||
})
|
||||
r.Route("/files", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
|
|
|
@ -2303,36 +2303,45 @@ func (q *fakeQuerier) DeleteGitSSHKey(_ context.Context, userID uuid.UUID) error
|
|||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetAuditLogsBefore(_ context.Context, arg database.GetAuditLogsBeforeParams) ([]database.AuditLog, error) {
|
||||
func (q *fakeQuerier) GetAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams) ([]database.GetAuditLogsOffsetRow, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
logs := make([]database.AuditLog, 0)
|
||||
start := database.AuditLog{}
|
||||
|
||||
if arg.ID != uuid.Nil {
|
||||
for _, alog := range q.auditLogs {
|
||||
if alog.ID == arg.ID {
|
||||
start = alog
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
start.ID = uuid.New()
|
||||
start.Time = arg.StartTime
|
||||
}
|
||||
|
||||
if start.ID == uuid.Nil {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
logs := make([]database.GetAuditLogsOffsetRow, 0, arg.Limit)
|
||||
|
||||
// q.auditLogs are already sorted by time DESC, so no need to sort after the fact.
|
||||
for _, alog := range q.auditLogs {
|
||||
if alog.Time.Before(start.Time) {
|
||||
logs = append(logs, alog)
|
||||
if arg.Offset > 0 {
|
||||
arg.Offset--
|
||||
continue
|
||||
}
|
||||
|
||||
if len(logs) >= int(arg.RowLimit) {
|
||||
user, err := q.GetUserByID(ctx, alog.UserID)
|
||||
userValid := err == nil
|
||||
|
||||
logs = append(logs, database.GetAuditLogsOffsetRow{
|
||||
ID: alog.ID,
|
||||
RequestID: alog.RequestID,
|
||||
OrganizationID: alog.OrganizationID,
|
||||
Ip: alog.Ip,
|
||||
UserAgent: alog.UserAgent,
|
||||
ResourceType: database.ResourceType(alog.UserAgent),
|
||||
ResourceID: alog.ResourceID,
|
||||
ResourceTarget: alog.ResourceTarget,
|
||||
ResourceIcon: alog.ResourceIcon,
|
||||
Action: alog.Action,
|
||||
Diff: alog.Diff,
|
||||
StatusCode: alog.StatusCode,
|
||||
AdditionalFields: alog.AdditionalFields,
|
||||
UserID: alog.UserID,
|
||||
UserUsername: sql.NullString{String: user.Username, Valid: userValid},
|
||||
UserEmail: sql.NullString{String: user.Email, Valid: userValid},
|
||||
UserCreatedAt: sql.NullTime{Time: user.CreatedAt, Valid: userValid},
|
||||
UserStatus: user.Status,
|
||||
UserRoles: user.RBACRoles,
|
||||
})
|
||||
|
||||
if len(logs) >= int(arg.Limit) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -2340,6 +2349,13 @@ func (q *fakeQuerier) GetAuditLogsBefore(_ context.Context, arg database.GetAudi
|
|||
return logs, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetAuditLogCount(_ context.Context) (int64, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
return int64(len(q.auditLogs)), nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) InsertAuditLog(_ context.Context, arg database.InsertAuditLogParams) (database.AuditLog, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
|
|
@ -27,9 +27,10 @@ type querier interface {
|
|||
GetAPIKeyByID(ctx context.Context, id string) (APIKey, error)
|
||||
GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error)
|
||||
GetActiveUserCount(ctx context.Context) (int64, error)
|
||||
// GetAuditLogsBefore retrieves `limit` number of audit logs before the provided
|
||||
GetAuditLogCount(ctx context.Context) (int64, error)
|
||||
// GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
|
||||
// ID.
|
||||
GetAuditLogsBefore(ctx context.Context, arg GetAuditLogsBeforeParams) ([]AuditLog, error)
|
||||
GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOffsetParams) ([]GetAuditLogsOffsetRow, error)
|
||||
// This function returns roles for authorization purposes. Implied member roles
|
||||
// are included.
|
||||
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error)
|
||||
|
|
|
@ -287,36 +287,79 @@ func (q *sqlQuerier) UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDP
|
|||
return err
|
||||
}
|
||||
|
||||
const getAuditLogsBefore = `-- name: GetAuditLogsBefore :many
|
||||
const getAuditLogCount = `-- name: GetAuditLogCount :one
|
||||
SELECT
|
||||
id, time, user_id, organization_id, ip, user_agent, resource_type, resource_id, resource_target, action, diff, status_code, additional_fields, request_id, resource_icon
|
||||
COUNT(*) as count
|
||||
FROM
|
||||
audit_logs
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetAuditLogCount(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, getAuditLogCount)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const getAuditLogsOffset = `-- name: GetAuditLogsOffset :many
|
||||
SELECT
|
||||
audit_logs.id, audit_logs.time, audit_logs.user_id, audit_logs.organization_id, audit_logs.ip, audit_logs.user_agent, audit_logs.resource_type, audit_logs.resource_id, audit_logs.resource_target, audit_logs.action, audit_logs.diff, audit_logs.status_code, audit_logs.additional_fields, audit_logs.request_id, audit_logs.resource_icon,
|
||||
users.username AS user_username,
|
||||
users.email AS user_email,
|
||||
users.created_at AS user_created_at,
|
||||
users.status AS user_status,
|
||||
users.rbac_roles AS user_roles
|
||||
FROM
|
||||
audit_logs
|
||||
WHERE
|
||||
audit_logs."time" < COALESCE((SELECT "time" FROM audit_logs a WHERE a.id = $1), $2)
|
||||
LEFT JOIN
|
||||
users ON audit_logs.user_id = users.id
|
||||
ORDER BY
|
||||
"time" DESC
|
||||
LIMIT
|
||||
$3
|
||||
$1
|
||||
OFFSET
|
||||
$2
|
||||
`
|
||||
|
||||
type GetAuditLogsBeforeParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
StartTime time.Time `db:"start_time" json:"start_time"`
|
||||
RowLimit int32 `db:"row_limit" json:"row_limit"`
|
||||
type GetAuditLogsOffsetParams struct {
|
||||
Limit int32 `db:"limit" json:"limit"`
|
||||
Offset int32 `db:"offset" json:"offset"`
|
||||
}
|
||||
|
||||
// GetAuditLogsBefore retrieves `limit` number of audit logs before the provided
|
||||
type GetAuditLogsOffsetRow struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Time time.Time `db:"time" json:"time"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
Ip pqtype.Inet `db:"ip" json:"ip"`
|
||||
UserAgent string `db:"user_agent" json:"user_agent"`
|
||||
ResourceType ResourceType `db:"resource_type" json:"resource_type"`
|
||||
ResourceID uuid.UUID `db:"resource_id" json:"resource_id"`
|
||||
ResourceTarget string `db:"resource_target" json:"resource_target"`
|
||||
Action AuditAction `db:"action" json:"action"`
|
||||
Diff json.RawMessage `db:"diff" json:"diff"`
|
||||
StatusCode int32 `db:"status_code" json:"status_code"`
|
||||
AdditionalFields json.RawMessage `db:"additional_fields" json:"additional_fields"`
|
||||
RequestID uuid.UUID `db:"request_id" json:"request_id"`
|
||||
ResourceIcon string `db:"resource_icon" json:"resource_icon"`
|
||||
UserUsername sql.NullString `db:"user_username" json:"user_username"`
|
||||
UserEmail sql.NullString `db:"user_email" json:"user_email"`
|
||||
UserCreatedAt sql.NullTime `db:"user_created_at" json:"user_created_at"`
|
||||
UserStatus UserStatus `db:"user_status" json:"user_status"`
|
||||
UserRoles []string `db:"user_roles" json:"user_roles"`
|
||||
}
|
||||
|
||||
// GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
|
||||
// ID.
|
||||
func (q *sqlQuerier) GetAuditLogsBefore(ctx context.Context, arg GetAuditLogsBeforeParams) ([]AuditLog, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getAuditLogsBefore, arg.ID, arg.StartTime, arg.RowLimit)
|
||||
func (q *sqlQuerier) GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOffsetParams) ([]GetAuditLogsOffsetRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getAuditLogsOffset, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []AuditLog
|
||||
var items []GetAuditLogsOffsetRow
|
||||
for rows.Next() {
|
||||
var i AuditLog
|
||||
var i GetAuditLogsOffsetRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
|
@ -333,6 +376,11 @@ func (q *sqlQuerier) GetAuditLogsBefore(ctx context.Context, arg GetAuditLogsBef
|
|||
&i.AdditionalFields,
|
||||
&i.RequestID,
|
||||
&i.ResourceIcon,
|
||||
&i.UserUsername,
|
||||
&i.UserEmail,
|
||||
&i.UserCreatedAt,
|
||||
&i.UserStatus,
|
||||
pq.Array(&i.UserRoles),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
-- GetAuditLogsBefore retrieves `limit` number of audit logs before the provided
|
||||
-- GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
|
||||
-- ID.
|
||||
-- name: GetAuditLogsBefore :many
|
||||
-- name: GetAuditLogsOffset :many
|
||||
SELECT
|
||||
*
|
||||
audit_logs.*,
|
||||
users.username AS user_username,
|
||||
users.email AS user_email,
|
||||
users.created_at AS user_created_at,
|
||||
users.status AS user_status,
|
||||
users.rbac_roles AS user_roles
|
||||
FROM
|
||||
audit_logs
|
||||
WHERE
|
||||
audit_logs."time" < COALESCE((SELECT "time" FROM audit_logs a WHERE a.id = sqlc.arg(id)), sqlc.arg(start_time))
|
||||
LEFT JOIN
|
||||
users ON audit_logs.user_id = users.id
|
||||
ORDER BY
|
||||
"time" DESC
|
||||
LIMIT
|
||||
sqlc.arg(row_limit);
|
||||
$1
|
||||
OFFSET
|
||||
$2;
|
||||
|
||||
-- name: GetAuditLogCount :one
|
||||
SELECT
|
||||
COUNT(*) as count
|
||||
FROM
|
||||
audit_logs;
|
||||
|
||||
-- name: InsertAuditLog :one
|
||||
INSERT INTO
|
||||
|
|
|
@ -247,7 +247,7 @@ func CanAssignRole(roles []string, assignedRole string) bool {
|
|||
func RoleByName(name string) (Role, error) {
|
||||
roleName, orgID, err := roleSplit(name)
|
||||
if err != nil {
|
||||
return Role{}, xerrors.Errorf(":%w", err)
|
||||
return Role{}, xerrors.Errorf("parse role name: %w", err)
|
||||
}
|
||||
|
||||
roleFunc, ok := builtInRoles[roleName]
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package codersdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
|
@ -29,9 +31,9 @@ const (
|
|||
type AuditDiff map[string]AuditDiffField
|
||||
|
||||
type AuditDiffField struct {
|
||||
Old any
|
||||
New any
|
||||
Secret bool
|
||||
Old any `json:"old,omitempty"`
|
||||
New any `json:"new,omitempty"`
|
||||
Secret bool `json:"secret"`
|
||||
}
|
||||
|
||||
type AuditLog struct {
|
||||
|
@ -54,3 +56,67 @@ type AuditLog struct {
|
|||
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
type AuditLogResponse struct {
|
||||
AuditLogs []AuditLog `json:"audit_logs"`
|
||||
}
|
||||
|
||||
type AuditLogCountResponse struct {
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
// AuditLogs retrieves audit logs from the given page.
|
||||
func (c *Client) AuditLogs(ctx context.Context, page Pagination) (AuditLogResponse, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/audit", nil, page.asRequestOption())
|
||||
if err != nil {
|
||||
return AuditLogResponse{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return AuditLogResponse{}, readBodyAsError(res)
|
||||
}
|
||||
|
||||
var logRes AuditLogResponse
|
||||
err = json.NewDecoder(res.Body).Decode(&logRes)
|
||||
if err != nil {
|
||||
return AuditLogResponse{}, err
|
||||
}
|
||||
|
||||
return logRes, nil
|
||||
}
|
||||
|
||||
// AuditLogCount returns the count of all audit logs in the product.
|
||||
func (c *Client) AuditLogCount(ctx context.Context) (AuditLogCountResponse, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/audit/count", nil)
|
||||
if err != nil {
|
||||
return AuditLogCountResponse{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return AuditLogCountResponse{}, readBodyAsError(res)
|
||||
}
|
||||
|
||||
var logRes AuditLogCountResponse
|
||||
err = json.NewDecoder(res.Body).Decode(&logRes)
|
||||
if err != nil {
|
||||
return AuditLogCountResponse{}, err
|
||||
}
|
||||
|
||||
return logRes, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateTestAuditLog(ctx context.Context) error {
|
||||
res, err := c.Request(ctx, http.MethodPost, "/api/v2/audit/testgenerate", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@ package backends_test
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
|
@ -30,13 +28,12 @@ func TestPostgresBackend(t *testing.T) {
|
|||
err := pgb.Export(ctx, alog)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.GetAuditLogsBefore(ctx, database.GetAuditLogsBeforeParams{
|
||||
ID: uuid.Nil,
|
||||
StartTime: time.Now().Add(time.Second),
|
||||
RowLimit: 1,
|
||||
got, err := db.GetAuditLogsOffset(ctx, database.GetAuditLogsOffsetParams{
|
||||
Offset: 0,
|
||||
Limit: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
require.Equal(t, alog, got[0])
|
||||
require.Equal(t, alog.ID, got[0].ID)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -47,10 +47,10 @@ export type AuditDiff = Record<string, AuditDiffField>
|
|||
// From codersdk/audit.go
|
||||
export interface AuditDiffField {
|
||||
// eslint-disable-next-line
|
||||
readonly Old: any
|
||||
readonly old?: any
|
||||
// eslint-disable-next-line
|
||||
readonly New: any
|
||||
readonly Secret: boolean
|
||||
readonly new?: any
|
||||
readonly secret: boolean
|
||||
}
|
||||
|
||||
// From codersdk/audit.go
|
||||
|
@ -76,6 +76,16 @@ export interface AuditLog {
|
|||
readonly user?: User
|
||||
}
|
||||
|
||||
// From codersdk/audit.go
|
||||
export interface AuditLogCountResponse {
|
||||
readonly count: number
|
||||
}
|
||||
|
||||
// From codersdk/audit.go
|
||||
export interface AuditLogResponse {
|
||||
readonly audit_logs: AuditLog[]
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export interface AuthMethods {
|
||||
readonly password: boolean
|
||||
|
|
Loading…
Reference in New Issue