Skip to content

Commit a8e6e89

Browse files
authored
feat: add organization details to audit log response (coder#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)