From 6d2c42715ec11ca8604478758ebc9f80c0953b2e Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 19 Jul 2024 14:53:48 -0800 Subject: [PATCH 1/6] Allow creating test audits with nil org Not all audit entries have organization IDs, so this will allow us to test those cases. --- coderd/audit.go | 3 --- coderd/audit_test.go | 52 +++++++++++++++++++++++++------------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/coderd/audit.go b/coderd/audit.go index 8541b9b4ea1ac..468b15a37a1fc 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -145,9 +145,6 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) { if len(params.AdditionalFields) == 0 { params.AdditionalFields = json.RawMessage("{}") } - if params.OrganizationID == uuid.Nil { - params.OrganizationID = uuid.New() - } _, err = api.Database.InsertAuditLog(ctx, database.InsertAuditLogParams{ ID: uuid.New(), diff --git a/coderd/audit_test.go b/coderd/audit_test.go index 9a810a2fce9a0..6d672644f6858 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -31,7 +31,8 @@ func TestAuditLogs(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - ResourceID: user.UserID, + OrganizationID: uuid.New(), + ResourceID: user.UserID, }) require.NoError(t, err) @@ -55,7 +56,8 @@ func TestAuditLogs(t *testing.T) { client2, user2 := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleOwner()) err := client2.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - ResourceID: user2.ID, + OrganizationID: uuid.New(), + ResourceID: user2.ID, }) require.NoError(t, err) @@ -120,6 +122,7 @@ func TestAuditLogs(t *testing.T) { require.NoError(t, err) err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ + OrganizationID: uuid.New(), Action: codersdk.AuditActionStop, ResourceType: codersdk.ResourceTypeWorkspaceBuild, ResourceID: workspace.LatestBuild.ID, @@ -241,44 +244,49 @@ func TestAuditLogsFilter(t *testing.T) { // Create two logs with "Create" err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionCreate, - ResourceType: codersdk.ResourceTypeTemplate, - ResourceID: template.ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + Action: codersdk.AuditActionCreate, + OrganizationID: uuid.New(), + ResourceType: codersdk.ResourceTypeTemplate, + ResourceID: template.ID, + 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: user.UserID, - Time: time.Date(2022, 8, 16, 14, 30, 45, 100, time.UTC), // 2022-8-16 14:30:45 + Action: codersdk.AuditActionCreate, + OrganizationID: uuid.New(), + ResourceType: codersdk.ResourceTypeUser, + ResourceID: user.UserID, + Time: time.Date(2022, 8, 16, 14, 30, 45, 100, time.UTC), // 2022-8-16 14:30:45 }) require.NoError(t, err) // Create one log with "Delete" err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionDelete, - ResourceType: codersdk.ResourceTypeUser, - ResourceID: user.UserID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + Action: codersdk.AuditActionDelete, + OrganizationID: uuid.New(), + ResourceType: codersdk.ResourceTypeUser, + ResourceID: user.UserID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) // Create one log with "Start" err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionStart, - ResourceType: codersdk.ResourceTypeWorkspaceBuild, - ResourceID: workspace.LatestBuild.ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + Action: codersdk.AuditActionStart, + OrganizationID: uuid.New(), + ResourceType: codersdk.ResourceTypeWorkspaceBuild, + ResourceID: workspace.LatestBuild.ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) // Create one log with "Stop" err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionStop, - ResourceType: codersdk.ResourceTypeWorkspaceBuild, - ResourceID: workspace.LatestBuild.ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + Action: codersdk.AuditActionStop, + OrganizationID: uuid.New(), + ResourceType: codersdk.ResourceTypeWorkspaceBuild, + ResourceID: workspace.LatestBuild.ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) From c6d96f6d75291e9d4c63a7bd0e6f0b6ba2b23664 Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 19 Jul 2024 14:54:35 -0800 Subject: [PATCH 2/6] Add organization details to audit log queries --- coderd/database/dbmem/dbmem.go | 76 +++++++++++++++------------ coderd/database/queries.sql.go | 68 ++++++++++++++---------- coderd/database/queries/auditlogs.sql | 4 ++ enterprise/cli/provisionerdaemons.go | 4 +- 4 files changed, 88 insertions(+), 64 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 198b4b4f3b6a9..25a82c44512fc 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -928,6 +928,16 @@ func (q *FakeQuerier) getLatestWorkspaceAppByTemplateIDUserIDSlugNoLock(ctx cont return database.WorkspaceApp{}, sql.ErrNoRows } +// getOrganizationByIDNoLock is used by other functions in the database fake. +func (q *FakeQuerier) getOrganizationByIDNoLock(id uuid.UUID) (database.Organization, error) { + for _, organization := range q.organizations { + if organization.ID == id { + return organization, nil + } + } + return database.Organization{}, sql.ErrNoRows +} + func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error { return xerrors.New("AcquireLock must only be called within a transaction") } @@ -2146,34 +2156,39 @@ func (q *FakeQuerier) GetAuditLogsOffset(_ context.Context, arg database.GetAudi user, err := q.getUserByIDNoLock(alog.UserID) userValid := err == nil + org, _ := q.getOrganizationByIDNoLock(alog.OrganizationID) + logs = append(logs, database.GetAuditLogsOffsetRow{ - ID: alog.ID, - RequestID: alog.RequestID, - OrganizationID: alog.OrganizationID, - Ip: alog.Ip, - UserAgent: alog.UserAgent, - ResourceType: alog.ResourceType, - 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}, - UserName: sql.NullString{String: user.Name, Valid: userValid}, - UserEmail: sql.NullString{String: user.Email, Valid: userValid}, - UserCreatedAt: sql.NullTime{Time: user.CreatedAt, Valid: userValid}, - UserUpdatedAt: sql.NullTime{Time: user.UpdatedAt, Valid: userValid}, - UserLastSeenAt: sql.NullTime{Time: user.LastSeenAt, Valid: userValid}, - UserLoginType: database.NullLoginType{LoginType: user.LoginType, Valid: userValid}, - UserDeleted: sql.NullBool{Bool: user.Deleted, Valid: userValid}, - UserThemePreference: sql.NullString{String: user.ThemePreference, Valid: userValid}, - UserQuietHoursSchedule: sql.NullString{String: user.QuietHoursSchedule, Valid: userValid}, - UserStatus: database.NullUserStatus{UserStatus: user.Status, Valid: userValid}, - UserRoles: user.RBACRoles, - Count: 0, + ID: alog.ID, + RequestID: alog.RequestID, + OrganizationID: alog.OrganizationID, + OrganizationName: org.Name, + OrganizationDisplayName: org.DisplayName, + OrganizationIcon: org.Icon, + Ip: alog.Ip, + UserAgent: alog.UserAgent, + ResourceType: alog.ResourceType, + 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}, + UserName: sql.NullString{String: user.Name, Valid: userValid}, + UserEmail: sql.NullString{String: user.Email, Valid: userValid}, + UserCreatedAt: sql.NullTime{Time: user.CreatedAt, Valid: userValid}, + UserUpdatedAt: sql.NullTime{Time: user.UpdatedAt, Valid: userValid}, + UserLastSeenAt: sql.NullTime{Time: user.LastSeenAt, Valid: userValid}, + UserLoginType: database.NullLoginType{LoginType: user.LoginType, Valid: userValid}, + UserDeleted: sql.NullBool{Bool: user.Deleted, Valid: userValid}, + UserThemePreference: sql.NullString{String: user.ThemePreference, Valid: userValid}, + UserQuietHoursSchedule: sql.NullString{String: user.QuietHoursSchedule, Valid: userValid}, + UserStatus: database.NullUserStatus{UserStatus: user.Status, Valid: userValid}, + UserRoles: user.RBACRoles, + Count: 0, }) if len(logs) >= int(arg.LimitOpt) { @@ -2969,12 +2984,7 @@ func (q *FakeQuerier) GetOrganizationByID(_ context.Context, id uuid.UUID) (data q.mutex.RLock() defer q.mutex.RUnlock() - for _, organization := range q.organizations { - if organization.ID == id { - return organization, nil - } - } - return database.Organization{}, sql.ErrNoRows + return q.getOrganizationByIDNoLock(id) } func (q *FakeQuerier) GetOrganizationByName(_ context.Context, name string) (database.Organization, error) { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 511db6ae4dccf..b75b4bed78888 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -459,6 +459,9 @@ SELECT users.deleted AS user_deleted, users.theme_preference AS user_theme_preference, users.quiet_hours_schedule AS user_quiet_hours_schedule, + COALESCE(organizations.name, '') AS organization_name, + COALESCE(organizations.display_name, '') AS organization_display_name, + COALESCE(organizations.icon, '') AS organization_icon, COUNT(audit_logs.*) OVER () AS count FROM audit_logs @@ -487,6 +490,7 @@ FROM workspaces.id = workspace_builds.workspace_id AND workspace_builds.build_number = 1 ) + LEFT JOIN organizations ON audit_logs.organization_id = organizations.id WHERE -- Filter resource_type CASE @@ -582,35 +586,38 @@ type GetAuditLogsOffsetParams struct { } 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 sql.NullString `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"` - UserName sql.NullString `db:"user_name" json:"user_name"` - UserEmail sql.NullString `db:"user_email" json:"user_email"` - UserCreatedAt sql.NullTime `db:"user_created_at" json:"user_created_at"` - UserUpdatedAt sql.NullTime `db:"user_updated_at" json:"user_updated_at"` - UserLastSeenAt sql.NullTime `db:"user_last_seen_at" json:"user_last_seen_at"` - UserStatus NullUserStatus `db:"user_status" json:"user_status"` - UserLoginType NullLoginType `db:"user_login_type" json:"user_login_type"` - UserRoles pq.StringArray `db:"user_roles" json:"user_roles"` - UserAvatarUrl sql.NullString `db:"user_avatar_url" json:"user_avatar_url"` - UserDeleted sql.NullBool `db:"user_deleted" json:"user_deleted"` - UserThemePreference sql.NullString `db:"user_theme_preference" json:"user_theme_preference"` - UserQuietHoursSchedule sql.NullString `db:"user_quiet_hours_schedule" json:"user_quiet_hours_schedule"` - Count int64 `db:"count" json:"count"` + 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 sql.NullString `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"` + UserName sql.NullString `db:"user_name" json:"user_name"` + UserEmail sql.NullString `db:"user_email" json:"user_email"` + UserCreatedAt sql.NullTime `db:"user_created_at" json:"user_created_at"` + UserUpdatedAt sql.NullTime `db:"user_updated_at" json:"user_updated_at"` + UserLastSeenAt sql.NullTime `db:"user_last_seen_at" json:"user_last_seen_at"` + UserStatus NullUserStatus `db:"user_status" json:"user_status"` + UserLoginType NullLoginType `db:"user_login_type" json:"user_login_type"` + UserRoles pq.StringArray `db:"user_roles" json:"user_roles"` + UserAvatarUrl sql.NullString `db:"user_avatar_url" json:"user_avatar_url"` + UserDeleted sql.NullBool `db:"user_deleted" json:"user_deleted"` + UserThemePreference sql.NullString `db:"user_theme_preference" json:"user_theme_preference"` + UserQuietHoursSchedule sql.NullString `db:"user_quiet_hours_schedule" json:"user_quiet_hours_schedule"` + OrganizationName string `db:"organization_name" json:"organization_name"` + OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"` + OrganizationIcon string `db:"organization_icon" json:"organization_icon"` + Count int64 `db:"count" json:"count"` } // GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided @@ -667,6 +674,9 @@ func (q *sqlQuerier) GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOff &i.UserDeleted, &i.UserThemePreference, &i.UserQuietHoursSchedule, + &i.OrganizationName, + &i.OrganizationDisplayName, + &i.OrganizationIcon, &i.Count, ); err != nil { return nil, err diff --git a/coderd/database/queries/auditlogs.sql b/coderd/database/queries/auditlogs.sql index aa62b71d1a002..d8ef38a82120e 100644 --- a/coderd/database/queries/auditlogs.sql +++ b/coderd/database/queries/auditlogs.sql @@ -18,6 +18,9 @@ SELECT users.deleted AS user_deleted, users.theme_preference AS user_theme_preference, users.quiet_hours_schedule AS user_quiet_hours_schedule, + COALESCE(organizations.name, '') AS organization_name, + COALESCE(organizations.display_name, '') AS organization_display_name, + COALESCE(organizations.icon, '') AS organization_icon, COUNT(audit_logs.*) OVER () AS count FROM audit_logs @@ -46,6 +49,7 @@ FROM workspaces.id = workspace_builds.workspace_id AND workspace_builds.build_number = 1 ) + LEFT JOIN organizations ON audit_logs.organization_id = organizations.id WHERE -- Filter resource_type CASE diff --git a/enterprise/cli/provisionerdaemons.go b/enterprise/cli/provisionerdaemons.go index 1f843da3cb260..286e53a34bb9f 100644 --- a/enterprise/cli/provisionerdaemons.go +++ b/enterprise/cli/provisionerdaemons.go @@ -114,7 +114,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command { return xerrors.New("must provide a pre-shared key when not authenticated as a user") } - org = codersdk.Organization{ID: uuid.Nil} + org = codersdk.Organization{MinimalOrganization: codersdk.MinimalOrganization{ID: uuid.Nil}} if orgContext.FlagSelect != "" { // If we are using PSK, we can't fetch the organization // to validate org name so we need the user to provide @@ -123,7 +123,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command { if err != nil { return xerrors.New("must provide an org ID when not authenticated as a user and organization is specified") } - org = codersdk.Organization{ID: orgID} + org = codersdk.Organization{MinimalOrganization: codersdk.MinimalOrganization{ID: orgID}} } } From 2b25f462f5616a640275e3e9b7a7a52adc923a80 Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 19 Jul 2024 14:55:24 -0800 Subject: [PATCH 3/6] Add organization to audit log response This replaces the old ID. This is a breaking change but organizations were not being used before. --- cli/organization_test.go | 6 ++- coderd/apidoc/docs.go | 26 +++++++++-- coderd/apidoc/swagger.json | 24 ++++++++-- coderd/audit.go | 14 +++++- coderd/audit_test.go | 77 ++++++++++++++++++++++++++++++++ coderd/organizations.go | 10 +++-- codersdk/audit.go | 17 +++---- codersdk/organizations.go | 16 ++++--- docs/api/audit.md | 7 ++- docs/api/schemas.md | 74 +++++++++++++++++++++--------- site/src/api/typesGenerated.ts | 16 ++++--- site/src/testHelpers/entities.ts | 7 ++- 12 files changed, 236 insertions(+), 58 deletions(-) diff --git a/cli/organization_test.go b/cli/organization_test.go index d04ea9cc6a19f..160f4b37b63f1 100644 --- a/cli/organization_test.go +++ b/cli/organization_test.go @@ -32,8 +32,10 @@ func TestCurrentOrganization(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode([]codersdk.Organization{ { - ID: orgID, - Name: "not-default", + MinimalOrganization: codersdk.MinimalOrganization{ + ID: orgID, + Name: "not-default", + }, CreatedAt: time.Now(), UpdatedAt: time.Now(), IsDefault: false, diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 1ebe7b806f3e4..399b5fd1e7db4 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -8368,9 +8368,8 @@ const docTemplate = `{ "is_deleted": { "type": "boolean" }, - "organization_id": { - "type": "string", - "format": "uuid" + "organization": { + "$ref": "#/definitions/codersdk.MinimalOrganization" }, "request_id": { "type": "string", @@ -10102,6 +10101,27 @@ const docTemplate = `{ } } }, + "codersdk.MinimalOrganization": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "display_name": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, "codersdk.MinimalUser": { "type": "object", "required": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index b8c561568bf6f..55d36a8a3854d 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -7434,9 +7434,8 @@ "is_deleted": { "type": "boolean" }, - "organization_id": { - "type": "string", - "format": "uuid" + "organization": { + "$ref": "#/definitions/codersdk.MinimalOrganization" }, "request_id": { "type": "string", @@ -9054,6 +9053,25 @@ } } }, + "codersdk.MinimalOrganization": { + "type": "object", + "required": ["id"], + "properties": { + "display_name": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, "codersdk.MinimalUser": { "type": "object", "required": ["id", "username"], diff --git a/coderd/audit.go b/coderd/audit.go index 468b15a37a1fc..a58d48a28b17f 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -238,11 +238,10 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs resourceLink = api.auditLogResourceLink(ctx, dblog, additionalFields) } - return codersdk.AuditLog{ + alog := codersdk.AuditLog{ ID: dblog.ID, RequestID: dblog.RequestID, Time: dblog.Time, - OrganizationID: dblog.OrganizationID, IP: ip, UserAgent: dblog.UserAgent.String, ResourceType: codersdk.ResourceType(dblog.ResourceType), @@ -258,6 +257,17 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs ResourceLink: resourceLink, IsDeleted: isDeleted, } + + if dblog.OrganizationID != uuid.Nil { + alog.Organization = &codersdk.MinimalOrganization{ + ID: dblog.OrganizationID, + Name: dblog.OrganizationName, + DisplayName: dblog.OrganizationDisplayName, + Icon: dblog.OrganizationIcon, + } + } + + return alog } func auditLogDescription(alog database.GetAuditLogsOffsetRow) string { diff --git a/coderd/audit_test.go b/coderd/audit_test.go index 6d672644f6858..4b22b8dbbbc06 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -97,6 +97,83 @@ func TestAuditLogs(t *testing.T) { require.Equal(t, foundUser, *alogs.AuditLogs[0].User) }) + t.Run("Organization", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + + o, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{ + Name: "new-org", + DisplayName: "New organization", + Description: "A new organization to love and cherish until the test is over.", + Icon: "/emojis/1f48f-1f3ff.png", + }) + require.NoError(t, err) + + err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ + OrganizationID: o.ID, + ResourceID: user.UserID, + }) + require.NoError(t, err) + + alogs, err := client.AuditLogs(ctx, codersdk.AuditLogsRequest{ + Pagination: codersdk.Pagination{ + Limit: 1, + }, + }) + require.NoError(t, err) + require.Equal(t, int64(1), alogs.Count) + require.Len(t, alogs.AuditLogs, 1) + + // Make sure the organization is fully populated. + require.Equal(t, &codersdk.MinimalOrganization{ + ID: o.ID, + Name: o.Name, + DisplayName: o.DisplayName, + Icon: o.Icon, + }, alogs.AuditLogs[0].Organization) + + // Delete the org and try again, should be mostly empty. + err = client.DeleteOrganization(ctx, o.ID.String()) + require.NoError(t, err) + + alogs, err = client.AuditLogs(ctx, codersdk.AuditLogsRequest{ + Pagination: codersdk.Pagination{ + Limit: 1, + }, + }) + require.NoError(t, err) + require.Equal(t, int64(1), alogs.Count) + require.Len(t, alogs.AuditLogs, 1) + + require.Equal(t, &codersdk.MinimalOrganization{ + ID: o.ID, + }, alogs.AuditLogs[0].Organization) + + // Some audit entries do not have an organization at all, in which case the + // response omits the organization. + err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ + ResourceType: codersdk.ResourceTypeAPIKey, + ResourceID: user.UserID, + }) + require.NoError(t, err) + + alogs, err = client.AuditLogs(ctx, codersdk.AuditLogsRequest{ + SearchQuery: "resource_type:api_key", + Pagination: codersdk.Pagination{ + Limit: 1, + }, + }) + require.NoError(t, err) + require.Equal(t, int64(1), alogs.Count) + require.Len(t, alogs.AuditLogs, 1) + + // The other will have no organization. + require.Equal(t, (*codersdk.MinimalOrganization)(nil), alogs.AuditLogs[0].Organization) + }) + t.Run("WorkspaceBuildAuditLink", func(t *testing.T) { t.Parallel() diff --git a/coderd/organizations.go b/coderd/organizations.go index 83492b6cdb5bc..49ea77a00fad2 100644 --- a/coderd/organizations.go +++ b/coderd/organizations.go @@ -315,11 +315,13 @@ func (api *API) deleteOrganization(rw http.ResponseWriter, r *http.Request) { // convertOrganization consumes the database representation and outputs an API friendly representation. func convertOrganization(organization database.Organization) codersdk.Organization { return codersdk.Organization{ - ID: organization.ID, - Name: organization.Name, - DisplayName: organization.DisplayName, + MinimalOrganization: codersdk.MinimalOrganization{ + ID: organization.ID, + Name: organization.Name, + DisplayName: organization.DisplayName, + Icon: organization.Icon, + }, Description: organization.Description, - Icon: organization.Icon, CreatedAt: organization.CreatedAt, UpdatedAt: organization.UpdatedAt, IsDefault: organization.IsDefault, diff --git a/codersdk/audit.go b/codersdk/audit.go index 75bfe6204c607..9b683ad3bf167 100644 --- a/codersdk/audit.go +++ b/codersdk/audit.go @@ -125,14 +125,13 @@ type AuditDiffField struct { } type AuditLog struct { - ID uuid.UUID `json:"id" format:"uuid"` - RequestID uuid.UUID `json:"request_id" format:"uuid"` - Time time.Time `json:"time" format:"date-time"` - OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` - IP netip.Addr `json:"ip"` - UserAgent string `json:"user_agent"` - ResourceType ResourceType `json:"resource_type"` - ResourceID uuid.UUID `json:"resource_id" format:"uuid"` + ID uuid.UUID `json:"id" format:"uuid"` + RequestID uuid.UUID `json:"request_id" format:"uuid"` + Time time.Time `json:"time" format:"date-time"` + IP netip.Addr `json:"ip"` + UserAgent string `json:"user_agent"` + ResourceType ResourceType `json:"resource_type"` + ResourceID uuid.UUID `json:"resource_id" format:"uuid"` // ResourceTarget is the name of the resource. ResourceTarget string `json:"resource_target"` ResourceIcon string `json:"resource_icon"` @@ -144,6 +143,8 @@ type AuditLog struct { ResourceLink string `json:"resource_link"` IsDeleted bool `json:"is_deleted"` + Organization *MinimalOrganization `json:"organization,omitempty"` + User *User `json:"user"` } diff --git a/codersdk/organizations.go b/codersdk/organizations.go index b1b5933781386..2039aa415ce5b 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -39,18 +39,22 @@ func ProvisionerTypeValid[T ProvisionerType | string](pt T) error { } } -// Organization is the JSON representation of a Coder organization. -type Organization struct { +type MinimalOrganization struct { ID uuid.UUID `table:"id" json:"id" validate:"required" format:"uuid"` Name string `table:"name,default_sort" json:"name"` DisplayName string `table:"display_name" json:"display_name"` - Description string `table:"description" json:"description"` - CreatedAt time.Time `table:"created_at" json:"created_at" validate:"required" format:"date-time"` - UpdatedAt time.Time `table:"updated_at" json:"updated_at" validate:"required" format:"date-time"` - IsDefault bool `table:"default" json:"is_default" validate:"required"` Icon string `table:"icon" json:"icon"` } +// Organization is the JSON representation of a Coder organization. +type Organization struct { + MinimalOrganization `table:"m,recursive_inline"` + Description string `table:"description" json:"description"` + CreatedAt time.Time `table:"created_at" json:"created_at" validate:"required" format:"date-time"` + UpdatedAt time.Time `table:"updated_at" json:"updated_at" validate:"required" format:"date-time"` + IsDefault bool `table:"default" json:"is_default" validate:"required"` +} + func (o Organization) HumanName() string { if o.DisplayName == "" { return o.Name diff --git a/docs/api/audit.md b/docs/api/audit.md index a20ec563a003a..5fe5952076f93 100644 --- a/docs/api/audit.md +++ b/docs/api/audit.md @@ -47,7 +47,12 @@ curl -X GET http://coder-server:8080/api/v2/audit?limit=0 \ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "ip": "string", "is_deleted": true, - "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization": { + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string" + }, "request_id": "266ea41d-adf5-480b-af50-15b940c2b846", "resource_icon": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index a1dd22f5be84e..49b8695610623 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -558,7 +558,12 @@ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "ip": "string", "is_deleted": true, - "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization": { + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string" + }, "request_id": "266ea41d-adf5-480b-af50-15b940c2b846", "resource_icon": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -594,26 +599,26 @@ ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------- | ---------------------------------------------- | -------- | ------------ | -------------------------------------------- | -| `action` | [codersdk.AuditAction](#codersdkauditaction) | false | | | -| `additional_fields` | array of integer | false | | | -| `description` | string | false | | | -| `diff` | [codersdk.AuditDiff](#codersdkauditdiff) | false | | | -| `id` | string | false | | | -| `ip` | string | false | | | -| `is_deleted` | boolean | false | | | -| `organization_id` | string | false | | | -| `request_id` | string | false | | | -| `resource_icon` | string | false | | | -| `resource_id` | string | false | | | -| `resource_link` | string | false | | | -| `resource_target` | string | false | | Resource target is the name of the resource. | -| `resource_type` | [codersdk.ResourceType](#codersdkresourcetype) | false | | | -| `status_code` | integer | false | | | -| `time` | string | false | | | -| `user` | [codersdk.User](#codersdkuser) | false | | | -| `user_agent` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------- | ------------------------------------------------------------ | -------- | ------------ | -------------------------------------------- | +| `action` | [codersdk.AuditAction](#codersdkauditaction) | false | | | +| `additional_fields` | array of integer | false | | | +| `description` | string | false | | | +| `diff` | [codersdk.AuditDiff](#codersdkauditdiff) | false | | | +| `id` | string | false | | | +| `ip` | string | false | | | +| `is_deleted` | boolean | false | | | +| `organization` | [codersdk.MinimalOrganization](#codersdkminimalorganization) | false | | | +| `request_id` | string | false | | | +| `resource_icon` | string | false | | | +| `resource_id` | string | false | | | +| `resource_link` | string | false | | | +| `resource_target` | string | false | | Resource target is the name of the resource. | +| `resource_type` | [codersdk.ResourceType](#codersdkresourcetype) | false | | | +| `status_code` | integer | false | | | +| `time` | string | false | | | +| `user` | [codersdk.User](#codersdkuser) | false | | | +| `user_agent` | string | false | | | ## codersdk.AuditLogResponse @@ -639,7 +644,12 @@ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "ip": "string", "is_deleted": true, - "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization": { + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string" + }, "request_id": "266ea41d-adf5-480b-af50-15b940c2b846", "resource_icon": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -3078,6 +3088,26 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | --------------- | ------ | -------- | ------------ | ----------- | | `session_token` | string | true | | | +## codersdk.MinimalOrganization + +```json +{ + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------- | ------ | -------- | ------------ | ----------- | +| `display_name` | string | false | | | +| `icon` | string | false | | | +| `id` | string | true | | | +| `name` | string | false | | | + ## codersdk.MinimalUser ```json diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index b6ffcf1c79874..62ace79bb5b8c 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -89,7 +89,6 @@ export interface AuditLog { readonly id: string; readonly request_id: string; readonly time: string; - readonly organization_id: string; // Named type "net/netip.Addr" unknown, using "any" // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type readonly ip: any; @@ -105,6 +104,7 @@ export interface AuditLog { readonly description: string; readonly resource_link: string; readonly is_deleted: boolean; + readonly organization?: MinimalOrganization; readonly user?: User; } @@ -690,6 +690,14 @@ export interface LoginWithPasswordResponse { readonly session_token: string; } +// From codersdk/organizations.go +export interface MinimalOrganization { + readonly id: string; + readonly name: string; + readonly display_name: string; + readonly icon: string; +} + // From codersdk/users.go export interface MinimalUser { readonly id: string; @@ -844,15 +852,11 @@ export interface OIDCConfig { } // From codersdk/organizations.go -export interface Organization { - readonly id: string; - readonly name: string; - readonly display_name: string; +export interface Organization extends MinimalOrganization { readonly description: string; readonly created_at: string; readonly updated_at: string; readonly is_default: boolean; - readonly icon: string; } // From codersdk/organizations.go diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index e5ff96ec7ea44..1ba50892f4362 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -2152,7 +2152,12 @@ export const MockAuditLog: TypesGen.AuditLog = { id: "fbd2116a-8961-4954-87ae-e4575bd29ce0", request_id: "53bded77-7b9d-4e82-8771-991a34d759f9", time: "2022-05-19T16:45:57.122Z", - organization_id: MockOrganization.id, + organization: { + id: MockOrganization.id, + name: "mock name", + display_name: "mock display name", + icon: "/emojis/1f48f-1f3ff.png", + }, ip: "127.0.0.1", user_agent: '"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"', From c0d376f2e194557c2b44b5da56079f593eefdcf4 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 22 Jul 2024 12:55:55 -0800 Subject: [PATCH 4/6] Clarify user/organization audit log tests There is another test called "organizations" so this distinguishes that this is checking the response itself contains this field. Rename the user test to match. --- coderd/audit_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/audit_test.go b/coderd/audit_test.go index 4b22b8dbbbc06..4cdabd4b241fb 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -47,7 +47,7 @@ func TestAuditLogs(t *testing.T) { require.Len(t, alogs.AuditLogs, 1) }) - t.Run("User", func(t *testing.T) { + t.Run("IncludeUser", func(t *testing.T) { t.Parallel() ctx := context.Background() @@ -97,7 +97,7 @@ func TestAuditLogs(t *testing.T) { require.Equal(t, foundUser, *alogs.AuditLogs[0].User) }) - t.Run("Organization", func(t *testing.T) { + t.Run("IncludeOrganization", func(t *testing.T) { t.Parallel() ctx := context.Background() From 8496240ab063165e43e5e631491594b7d28a5497 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 22 Jul 2024 12:56:59 -0800 Subject: [PATCH 5/6] Remove unnecessary org ID from audit tests --- coderd/audit_test.go | 55 ++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/coderd/audit_test.go b/coderd/audit_test.go index 4cdabd4b241fb..bb13182fd4fb9 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -31,8 +31,7 @@ func TestAuditLogs(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - OrganizationID: uuid.New(), - ResourceID: user.UserID, + ResourceID: user.UserID, }) require.NoError(t, err) @@ -56,8 +55,7 @@ func TestAuditLogs(t *testing.T) { client2, user2 := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleOwner()) err := client2.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - OrganizationID: uuid.New(), - ResourceID: user2.ID, + ResourceID: user2.ID, }) require.NoError(t, err) @@ -199,7 +197,6 @@ func TestAuditLogs(t *testing.T) { require.NoError(t, err) err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - OrganizationID: uuid.New(), Action: codersdk.AuditActionStop, ResourceType: codersdk.ResourceTypeWorkspaceBuild, ResourceID: workspace.LatestBuild.ID, @@ -239,8 +236,7 @@ func TestAuditLogs(t *testing.T) { // Add an extra audit log in another organization err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - ResourceID: owner.UserID, - OrganizationID: uuid.New(), + ResourceID: owner.UserID, }) require.NoError(t, err) @@ -321,49 +317,44 @@ func TestAuditLogsFilter(t *testing.T) { // Create two logs with "Create" err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionCreate, - OrganizationID: uuid.New(), - ResourceType: codersdk.ResourceTypeTemplate, - ResourceID: template.ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + Action: codersdk.AuditActionCreate, + ResourceType: codersdk.ResourceTypeTemplate, + ResourceID: template.ID, + 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, - OrganizationID: uuid.New(), - ResourceType: codersdk.ResourceTypeUser, - ResourceID: user.UserID, - Time: time.Date(2022, 8, 16, 14, 30, 45, 100, time.UTC), // 2022-8-16 14:30:45 + Action: codersdk.AuditActionCreate, + ResourceType: codersdk.ResourceTypeUser, + ResourceID: user.UserID, + Time: time.Date(2022, 8, 16, 14, 30, 45, 100, time.UTC), // 2022-8-16 14:30:45 }) require.NoError(t, err) // Create one log with "Delete" err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionDelete, - OrganizationID: uuid.New(), - ResourceType: codersdk.ResourceTypeUser, - ResourceID: user.UserID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + Action: codersdk.AuditActionDelete, + ResourceType: codersdk.ResourceTypeUser, + ResourceID: user.UserID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) // Create one log with "Start" err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionStart, - OrganizationID: uuid.New(), - ResourceType: codersdk.ResourceTypeWorkspaceBuild, - ResourceID: workspace.LatestBuild.ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + Action: codersdk.AuditActionStart, + ResourceType: codersdk.ResourceTypeWorkspaceBuild, + ResourceID: workspace.LatestBuild.ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) // Create one log with "Stop" err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionStop, - OrganizationID: uuid.New(), - ResourceType: codersdk.ResourceTypeWorkspaceBuild, - ResourceID: workspace.LatestBuild.ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + Action: codersdk.AuditActionStop, + ResourceType: codersdk.ResourceTypeWorkspaceBuild, + ResourceID: workspace.LatestBuild.ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) From de75d4fd6b6231f391d7acec15077766bc304ad9 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 22 Jul 2024 13:04:54 -0800 Subject: [PATCH 6/6] Add back OrganizationID to audit logs But mark it as deprecated. --- coderd/apidoc/docs.go | 5 +++++ coderd/apidoc/swagger.json | 5 +++++ coderd/audit.go | 8 +++++--- coderd/audit_test.go | 9 +++++++++ codersdk/audit.go | 3 +++ docs/api/audit.md | 1 + docs/api/schemas.md | 3 +++ site/src/api/typesGenerated.ts | 1 + site/src/testHelpers/entities.ts | 1 + 9 files changed, 33 insertions(+), 3 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 399b5fd1e7db4..81612260969a3 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -8371,6 +8371,11 @@ const docTemplate = `{ "organization": { "$ref": "#/definitions/codersdk.MinimalOrganization" }, + "organization_id": { + "description": "Deprecated: Use 'organization.id' instead.", + "type": "string", + "format": "uuid" + }, "request_id": { "type": "string", "format": "uuid" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 55d36a8a3854d..82b52b95b3123 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -7437,6 +7437,11 @@ "organization": { "$ref": "#/definitions/codersdk.MinimalOrganization" }, + "organization_id": { + "description": "Deprecated: Use 'organization.id' instead.", + "type": "string", + "format": "uuid" + }, "request_id": { "type": "string", "format": "uuid" diff --git a/coderd/audit.go b/coderd/audit.go index a58d48a28b17f..f7dfb118d20bc 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -239,9 +239,11 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs } alog := codersdk.AuditLog{ - ID: dblog.ID, - RequestID: dblog.RequestID, - Time: dblog.Time, + ID: dblog.ID, + RequestID: dblog.RequestID, + Time: dblog.Time, + // OrganizationID is deprecated. + OrganizationID: dblog.OrganizationID, IP: ip, UserAgent: dblog.UserAgent.String, ResourceType: codersdk.ResourceType(dblog.ResourceType), diff --git a/coderd/audit_test.go b/coderd/audit_test.go index bb13182fd4fb9..509744ecbff66 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -133,6 +133,9 @@ func TestAuditLogs(t *testing.T) { Icon: o.Icon, }, alogs.AuditLogs[0].Organization) + // OrganizationID is deprecated, but make sure it is set. + require.Equal(t, o.ID, alogs.AuditLogs[0].OrganizationID) + // Delete the org and try again, should be mostly empty. err = client.DeleteOrganization(ctx, o.ID.String()) require.NoError(t, err) @@ -150,6 +153,9 @@ func TestAuditLogs(t *testing.T) { ID: o.ID, }, alogs.AuditLogs[0].Organization) + // OrganizationID is deprecated, but make sure it is set. + require.Equal(t, o.ID, alogs.AuditLogs[0].OrganizationID) + // Some audit entries do not have an organization at all, in which case the // response omits the organization. err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ @@ -170,6 +176,9 @@ func TestAuditLogs(t *testing.T) { // The other will have no organization. require.Equal(t, (*codersdk.MinimalOrganization)(nil), alogs.AuditLogs[0].Organization) + + // OrganizationID is deprecated, but make sure it is empty. + require.Equal(t, uuid.Nil, alogs.AuditLogs[0].OrganizationID) }) t.Run("WorkspaceBuildAuditLink", func(t *testing.T) { diff --git a/codersdk/audit.go b/codersdk/audit.go index 9b683ad3bf167..33b4714f03df6 100644 --- a/codersdk/audit.go +++ b/codersdk/audit.go @@ -143,6 +143,9 @@ type AuditLog struct { ResourceLink string `json:"resource_link"` IsDeleted bool `json:"is_deleted"` + // Deprecated: Use 'organization.id' instead. + OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` + Organization *MinimalOrganization `json:"organization,omitempty"` User *User `json:"user"` diff --git a/docs/api/audit.md b/docs/api/audit.md index 5fe5952076f93..adf278068579e 100644 --- a/docs/api/audit.md +++ b/docs/api/audit.md @@ -53,6 +53,7 @@ curl -X GET http://coder-server:8080/api/v2/audit?limit=0 \ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" }, + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "request_id": "266ea41d-adf5-480b-af50-15b940c2b846", "resource_icon": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 49b8695610623..447b148651e8a 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -564,6 +564,7 @@ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" }, + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "request_id": "266ea41d-adf5-480b-af50-15b940c2b846", "resource_icon": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -609,6 +610,7 @@ | `ip` | string | false | | | | `is_deleted` | boolean | false | | | | `organization` | [codersdk.MinimalOrganization](#codersdkminimalorganization) | false | | | +| `organization_id` | string | false | | Deprecated: Use 'organization.id' instead. | | `request_id` | string | false | | | | `resource_icon` | string | false | | | | `resource_id` | string | false | | | @@ -650,6 +652,7 @@ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" }, + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "request_id": "266ea41d-adf5-480b-af50-15b940c2b846", "resource_icon": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 62ace79bb5b8c..bc4ff5a038ccb 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -104,6 +104,7 @@ export interface AuditLog { readonly description: string; readonly resource_link: string; readonly is_deleted: boolean; + readonly organization_id: string; readonly organization?: MinimalOrganization; readonly user?: User; } diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 1ba50892f4362..043cc405df7d3 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -2152,6 +2152,7 @@ export const MockAuditLog: TypesGen.AuditLog = { id: "fbd2116a-8961-4954-87ae-e4575bd29ce0", request_id: "53bded77-7b9d-4e82-8771-991a34d759f9", time: "2022-05-19T16:45:57.122Z", + organization_id: MockOrganization.id, organization: { id: MockOrganization.id, name: "mock name",