Skip to content

Commit 555b045

Browse files
committed
chore: implement organization scoped audit log requests
1 parent 2950d04 commit 555b045

File tree

5 files changed

+61
-5
lines changed

5 files changed

+61
-5
lines changed

coderd/audit.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/coder/coder/v2/coderd/audit"
1919
"github.com/coder/coder/v2/coderd/database"
2020
"github.com/coder/coder/v2/coderd/database/db2sdk"
21+
"github.com/coder/coder/v2/coderd/database/dbauthz"
2122
"github.com/coder/coder/v2/coderd/httpapi"
2223
"github.com/coder/coder/v2/coderd/httpmw"
2324
"github.com/coder/coder/v2/coderd/searchquery"
@@ -61,6 +62,10 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) {
6162
}
6263

6364
dblogs, err := api.Database.GetAuditLogsOffset(ctx, filter)
65+
if dbauthz.IsNotAuthorizedError(err) {
66+
httpapi.Forbidden(rw)
67+
return
68+
}
6469
if err != nil {
6570
httpapi.InternalServerError(rw, err)
6671
return
@@ -139,6 +144,9 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
139144
if len(params.AdditionalFields) == 0 {
140145
params.AdditionalFields = json.RawMessage("{}")
141146
}
147+
if params.OrganizationID == uuid.Nil {
148+
params.OrganizationID = uuid.New()
149+
}
142150

143151
_, err = api.Database.InsertAuditLog(ctx, database.InsertAuditLogParams{
144152
ID: uuid.New(),
@@ -155,7 +163,7 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
155163
AdditionalFields: params.AdditionalFields,
156164
RequestID: uuid.Nil, // no request ID to attach this to
157165
ResourceIcon: "",
158-
OrganizationID: uuid.New(),
166+
OrganizationID: params.OrganizationID,
159167
})
160168
if err != nil {
161169
httpapi.InternalServerError(rw, err)

coderd/audit_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"net/http"
78
"strconv"
89
"testing"
910
"time"
1011

1112
"github.com/google/uuid"
1213
"github.com/stretchr/testify/require"
1314

15+
"cdr.dev/slog/sloggers/slogtest"
1416
"github.com/coder/coder/v2/coderd/audit"
1517
"github.com/coder/coder/v2/coderd/coderdtest"
1618
"github.com/coder/coder/v2/coderd/database"
@@ -135,6 +137,47 @@ func TestAuditLogs(t *testing.T) {
135137
require.Equal(t, auditLogs.AuditLogs[0].ResourceLink, fmt.Sprintf("/@%s/%s/builds/%s",
136138
workspace.OwnerName, workspace.Name, buildNumberString))
137139
})
140+
141+
t.Run("Organization", func(t *testing.T) {
142+
t.Parallel()
143+
144+
logger := slogtest.Make(t, &slogtest.Options{
145+
IgnoreErrors: true,
146+
})
147+
ctx := context.Background()
148+
client := coderdtest.New(t, &coderdtest.Options{
149+
Logger: &logger,
150+
})
151+
owner := coderdtest.CreateFirstUser(t, client)
152+
orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
153+
154+
err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
155+
ResourceID: owner.UserID,
156+
OrganizationID: owner.OrganizationID,
157+
})
158+
require.NoError(t, err)
159+
160+
// Add an extra audit log in another organization
161+
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
162+
ResourceID: owner.UserID,
163+
OrganizationID: uuid.New(),
164+
})
165+
require.NoError(t, err)
166+
167+
// Fetching audit logs without an organization selector should fail
168+
_, err = orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{})
169+
var sdkError *codersdk.Error
170+
require.Error(t, err)
171+
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
172+
require.Equal(t, http.StatusForbidden, sdkError.StatusCode())
173+
174+
// Using the organization selector allows the org admin to fetch audit logs
175+
alogs, err := orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
176+
SearchQuery: fmt.Sprintf("organization_id:%s", owner.OrganizationID.String()),
177+
})
178+
require.NoError(t, err)
179+
require.Len(t, alogs.AuditLogs, 1)
180+
})
138181
}
139182

140183
func TestAuditLogsFilter(t *testing.T) {

coderd/database/dbmem/dbmem.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,6 +1927,9 @@ func (q *FakeQuerier) GetAuditLogsOffset(_ context.Context, arg database.GetAudi
19271927
arg.Offset--
19281928
continue
19291929
}
1930+
if arg.OrganizationID != uuid.Nil && arg.OrganizationID != alog.OrganizationID {
1931+
continue
1932+
}
19301933
if arg.Action != "" && !strings.Contains(string(alog.Action), arg.Action) {
19311934
continue
19321935
}

coderd/members.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,11 @@ func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) {
176176
apiKey = httpmw.APIKey(r)
177177
auditor = api.Auditor.Load()
178178
aReq, commitAudit = audit.InitRequest[database.AuditableOrganizationMember](rw, &audit.RequestParams{
179-
Audit: *auditor,
180-
Log: api.Logger,
181-
Request: r,
182-
Action: database.AuditActionWrite,
179+
OrganizationID: organization.ID,
180+
Audit: *auditor,
181+
Log: api.Logger,
182+
Request: r,
183+
Action: database.AuditActionWrite,
183184
})
184185
)
185186
aReq.Old = member.OrganizationMember.Auditable(member.Username)

codersdk/audit.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ type CreateTestAuditLogRequest struct {
161161
AdditionalFields json.RawMessage `json:"additional_fields,omitempty"`
162162
Time time.Time `json:"time,omitempty" format:"date-time"`
163163
BuildReason BuildReason `json:"build_reason,omitempty" enums:"autostart,autostop,initiator"`
164+
OrganizationID uuid.UUID `json:"organization_id,omitempty" format:"uuid"`
164165
}
165166

166167
// AuditLogs retrieves audit logs from the given page.

0 commit comments

Comments
 (0)