Skip to content

Commit a8e6e89

Browse files
authored
feat: add organization details to audit log response (#13961)
* Allow creating test audits with nil org Not all audit entries have organization IDs, so this will allow us to test those cases. * Add organization details to audit log queries * Add organization to audit log response This replaces the old ID. This is a breaking change but organizations were not being used before.
1 parent 38c7dcd commit a8e6e89

File tree

16 files changed

+348
-120
lines changed

16 files changed

+348
-120
lines changed

cli/organization_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ func TestCurrentOrganization(t *testing.T) {
3232
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3333
json.NewEncoder(w).Encode([]codersdk.Organization{
3434
{
35-
ID: orgID,
36-
Name: "not-default",
35+
MinimalOrganization: codersdk.MinimalOrganization{
36+
ID: orgID,
37+
Name: "not-default",
38+
},
3739
CreatedAt: time.Now(),
3840
UpdatedAt: time.Now(),
3941
IsDefault: false,

coderd/apidoc/docs.go

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/audit.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,6 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
145145
if len(params.AdditionalFields) == 0 {
146146
params.AdditionalFields = json.RawMessage("{}")
147147
}
148-
if params.OrganizationID == uuid.Nil {
149-
params.OrganizationID = uuid.New()
150-
}
151148

152149
_, err = api.Database.InsertAuditLog(ctx, database.InsertAuditLogParams{
153150
ID: uuid.New(),
@@ -241,10 +238,11 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
241238
resourceLink = api.auditLogResourceLink(ctx, dblog, additionalFields)
242239
}
243240

244-
return codersdk.AuditLog{
245-
ID: dblog.ID,
246-
RequestID: dblog.RequestID,
247-
Time: dblog.Time,
241+
alog := codersdk.AuditLog{
242+
ID: dblog.ID,
243+
RequestID: dblog.RequestID,
244+
Time: dblog.Time,
245+
// OrganizationID is deprecated.
248246
OrganizationID: dblog.OrganizationID,
249247
IP: ip,
250248
UserAgent: dblog.UserAgent.String,
@@ -261,6 +259,17 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
261259
ResourceLink: resourceLink,
262260
IsDeleted: isDeleted,
263261
}
262+
263+
if dblog.OrganizationID != uuid.Nil {
264+
alog.Organization = &codersdk.MinimalOrganization{
265+
ID: dblog.OrganizationID,
266+
Name: dblog.OrganizationName,
267+
DisplayName: dblog.OrganizationDisplayName,
268+
Icon: dblog.OrganizationIcon,
269+
}
270+
}
271+
272+
return alog
264273
}
265274

266275
func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {

coderd/audit_test.go

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestAuditLogs(t *testing.T) {
4646
require.Len(t, alogs.AuditLogs, 1)
4747
})
4848

49-
t.Run("User", func(t *testing.T) {
49+
t.Run("IncludeUser", func(t *testing.T) {
5050
t.Parallel()
5151

5252
ctx := context.Background()
@@ -95,6 +95,92 @@ func TestAuditLogs(t *testing.T) {
9595
require.Equal(t, foundUser, *alogs.AuditLogs[0].User)
9696
})
9797

98+
t.Run("IncludeOrganization", func(t *testing.T) {
99+
t.Parallel()
100+
101+
ctx := context.Background()
102+
client := coderdtest.New(t, nil)
103+
user := coderdtest.CreateFirstUser(t, client)
104+
105+
o, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
106+
Name: "new-org",
107+
DisplayName: "New organization",
108+
Description: "A new organization to love and cherish until the test is over.",
109+
Icon: "/emojis/1f48f-1f3ff.png",
110+
})
111+
require.NoError(t, err)
112+
113+
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
114+
OrganizationID: o.ID,
115+
ResourceID: user.UserID,
116+
})
117+
require.NoError(t, err)
118+
119+
alogs, err := client.AuditLogs(ctx, codersdk.AuditLogsRequest{
120+
Pagination: codersdk.Pagination{
121+
Limit: 1,
122+
},
123+
})
124+
require.NoError(t, err)
125+
require.Equal(t, int64(1), alogs.Count)
126+
require.Len(t, alogs.AuditLogs, 1)
127+
128+
// Make sure the organization is fully populated.
129+
require.Equal(t, &codersdk.MinimalOrganization{
130+
ID: o.ID,
131+
Name: o.Name,
132+
DisplayName: o.DisplayName,
133+
Icon: o.Icon,
134+
}, alogs.AuditLogs[0].Organization)
135+
136+
// OrganizationID is deprecated, but make sure it is set.
137+
require.Equal(t, o.ID, alogs.AuditLogs[0].OrganizationID)
138+
139+
// Delete the org and try again, should be mostly empty.
140+
err = client.DeleteOrganization(ctx, o.ID.String())
141+
require.NoError(t, err)
142+
143+
alogs, err = client.AuditLogs(ctx, codersdk.AuditLogsRequest{
144+
Pagination: codersdk.Pagination{
145+
Limit: 1,
146+
},
147+
})
148+
require.NoError(t, err)
149+
require.Equal(t, int64(1), alogs.Count)
150+
require.Len(t, alogs.AuditLogs, 1)
151+
152+
require.Equal(t, &codersdk.MinimalOrganization{
153+
ID: o.ID,
154+
}, alogs.AuditLogs[0].Organization)
155+
156+
// OrganizationID is deprecated, but make sure it is set.
157+
require.Equal(t, o.ID, alogs.AuditLogs[0].OrganizationID)
158+
159+
// Some audit entries do not have an organization at all, in which case the
160+
// response omits the organization.
161+
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
162+
ResourceType: codersdk.ResourceTypeAPIKey,
163+
ResourceID: user.UserID,
164+
})
165+
require.NoError(t, err)
166+
167+
alogs, err = client.AuditLogs(ctx, codersdk.AuditLogsRequest{
168+
SearchQuery: "resource_type:api_key",
169+
Pagination: codersdk.Pagination{
170+
Limit: 1,
171+
},
172+
})
173+
require.NoError(t, err)
174+
require.Equal(t, int64(1), alogs.Count)
175+
require.Len(t, alogs.AuditLogs, 1)
176+
177+
// The other will have no organization.
178+
require.Equal(t, (*codersdk.MinimalOrganization)(nil), alogs.AuditLogs[0].Organization)
179+
180+
// OrganizationID is deprecated, but make sure it is empty.
181+
require.Equal(t, uuid.Nil, alogs.AuditLogs[0].OrganizationID)
182+
})
183+
98184
t.Run("WorkspaceBuildAuditLink", func(t *testing.T) {
99185
t.Parallel()
100186

@@ -159,8 +245,7 @@ func TestAuditLogs(t *testing.T) {
159245

160246
// Add an extra audit log in another organization
161247
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
162-
ResourceID: owner.UserID,
163-
OrganizationID: uuid.New(),
248+
ResourceID: owner.UserID,
164249
})
165250
require.NoError(t, err)
166251

coderd/database/dbmem/dbmem.go

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,16 @@ func (q *FakeQuerier) getLatestWorkspaceAppByTemplateIDUserIDSlugNoLock(ctx cont
928928
return database.WorkspaceApp{}, sql.ErrNoRows
929929
}
930930

931+
// getOrganizationByIDNoLock is used by other functions in the database fake.
932+
func (q *FakeQuerier) getOrganizationByIDNoLock(id uuid.UUID) (database.Organization, error) {
933+
for _, organization := range q.organizations {
934+
if organization.ID == id {
935+
return organization, nil
936+
}
937+
}
938+
return database.Organization{}, sql.ErrNoRows
939+
}
940+
931941
func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error {
932942
return xerrors.New("AcquireLock must only be called within a transaction")
933943
}
@@ -2146,34 +2156,39 @@ func (q *FakeQuerier) GetAuditLogsOffset(_ context.Context, arg database.GetAudi
21462156
user, err := q.getUserByIDNoLock(alog.UserID)
21472157
userValid := err == nil
21482158

2159+
org, _ := q.getOrganizationByIDNoLock(alog.OrganizationID)
2160+
21492161
logs = append(logs, database.GetAuditLogsOffsetRow{
2150-
ID: alog.ID,
2151-
RequestID: alog.RequestID,
2152-
OrganizationID: alog.OrganizationID,
2153-
Ip: alog.Ip,
2154-
UserAgent: alog.UserAgent,
2155-
ResourceType: alog.ResourceType,
2156-
ResourceID: alog.ResourceID,
2157-
ResourceTarget: alog.ResourceTarget,
2158-
ResourceIcon: alog.ResourceIcon,
2159-
Action: alog.Action,
2160-
Diff: alog.Diff,
2161-
StatusCode: alog.StatusCode,
2162-
AdditionalFields: alog.AdditionalFields,
2163-
UserID: alog.UserID,
2164-
UserUsername: sql.NullString{String: user.Username, Valid: userValid},
2165-
UserName: sql.NullString{String: user.Name, Valid: userValid},
2166-
UserEmail: sql.NullString{String: user.Email, Valid: userValid},
2167-
UserCreatedAt: sql.NullTime{Time: user.CreatedAt, Valid: userValid},
2168-
UserUpdatedAt: sql.NullTime{Time: user.UpdatedAt, Valid: userValid},
2169-
UserLastSeenAt: sql.NullTime{Time: user.LastSeenAt, Valid: userValid},
2170-
UserLoginType: database.NullLoginType{LoginType: user.LoginType, Valid: userValid},
2171-
UserDeleted: sql.NullBool{Bool: user.Deleted, Valid: userValid},
2172-
UserThemePreference: sql.NullString{String: user.ThemePreference, Valid: userValid},
2173-
UserQuietHoursSchedule: sql.NullString{String: user.QuietHoursSchedule, Valid: userValid},
2174-
UserStatus: database.NullUserStatus{UserStatus: user.Status, Valid: userValid},
2175-
UserRoles: user.RBACRoles,
2176-
Count: 0,
2162+
ID: alog.ID,
2163+
RequestID: alog.RequestID,
2164+
OrganizationID: alog.OrganizationID,
2165+
OrganizationName: org.Name,
2166+
OrganizationDisplayName: org.DisplayName,
2167+
OrganizationIcon: org.Icon,
2168+
Ip: alog.Ip,
2169+
UserAgent: alog.UserAgent,
2170+
ResourceType: alog.ResourceType,
2171+
ResourceID: alog.ResourceID,
2172+
ResourceTarget: alog.ResourceTarget,
2173+
ResourceIcon: alog.ResourceIcon,
2174+
Action: alog.Action,
2175+
Diff: alog.Diff,
2176+
StatusCode: alog.StatusCode,
2177+
AdditionalFields: alog.AdditionalFields,
2178+
UserID: alog.UserID,
2179+
UserUsername: sql.NullString{String: user.Username, Valid: userValid},
2180+
UserName: sql.NullString{String: user.Name, Valid: userValid},
2181+
UserEmail: sql.NullString{String: user.Email, Valid: userValid},
2182+
UserCreatedAt: sql.NullTime{Time: user.CreatedAt, Valid: userValid},
2183+
UserUpdatedAt: sql.NullTime{Time: user.UpdatedAt, Valid: userValid},
2184+
UserLastSeenAt: sql.NullTime{Time: user.LastSeenAt, Valid: userValid},
2185+
UserLoginType: database.NullLoginType{LoginType: user.LoginType, Valid: userValid},
2186+
UserDeleted: sql.NullBool{Bool: user.Deleted, Valid: userValid},
2187+
UserThemePreference: sql.NullString{String: user.ThemePreference, Valid: userValid},
2188+
UserQuietHoursSchedule: sql.NullString{String: user.QuietHoursSchedule, Valid: userValid},
2189+
UserStatus: database.NullUserStatus{UserStatus: user.Status, Valid: userValid},
2190+
UserRoles: user.RBACRoles,
2191+
Count: 0,
21772192
})
21782193

21792194
if len(logs) >= int(arg.LimitOpt) {
@@ -2969,12 +2984,7 @@ func (q *FakeQuerier) GetOrganizationByID(_ context.Context, id uuid.UUID) (data
29692984
q.mutex.RLock()
29702985
defer q.mutex.RUnlock()
29712986

2972-
for _, organization := range q.organizations {
2973-
if organization.ID == id {
2974-
return organization, nil
2975-
}
2976-
}
2977-
return database.Organization{}, sql.ErrNoRows
2987+
return q.getOrganizationByIDNoLock(id)
29782988
}
29792989

29802990
func (q *FakeQuerier) GetOrganizationByName(_ context.Context, name string) (database.Organization, error) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy