Skip to content

Commit 6daf330

Browse files
authored
chore: allow organization name or uuid for audit log searching (#13721)
* chore: allow organization name or uuid for audit log searching
1 parent 3cc86cf commit 6daf330

File tree

4 files changed

+69
-5
lines changed

4 files changed

+69
-5
lines changed

coderd/audit.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) {
4545
}
4646

4747
queryStr := r.URL.Query().Get("q")
48-
filter, errs := searchquery.AuditLogs(queryStr)
48+
filter, errs := searchquery.AuditLogs(ctx, api.Database, queryStr)
4949
if len(errs) > 0 {
5050
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
5151
Message: "Invalid audit search query.",

coderd/audit_test.go

+36-1
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,48 @@ func TestAuditLogs(t *testing.T) {
177177

178178
// Using the organization selector allows the org admin to fetch audit logs
179179
alogs, err := orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
180-
SearchQuery: fmt.Sprintf("organization_id:%s", owner.OrganizationID.String()),
180+
SearchQuery: fmt.Sprintf("organization:%s", owner.OrganizationID.String()),
181181
Pagination: codersdk.Pagination{
182182
Limit: 5,
183183
},
184184
})
185185
require.NoError(t, err)
186186
require.Len(t, alogs.AuditLogs, 1)
187+
188+
// Also try fetching by organization name
189+
organization, err := orgAdmin.Organization(ctx, owner.OrganizationID)
190+
require.NoError(t, err)
191+
192+
alogs, err = orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
193+
SearchQuery: fmt.Sprintf("organization:%s", organization.Name),
194+
Pagination: codersdk.Pagination{
195+
Limit: 5,
196+
},
197+
})
198+
require.NoError(t, err)
199+
require.Len(t, alogs.AuditLogs, 1)
200+
})
201+
202+
t.Run("Organization404", func(t *testing.T) {
203+
t.Parallel()
204+
205+
logger := slogtest.Make(t, &slogtest.Options{
206+
IgnoreErrors: true,
207+
})
208+
ctx := context.Background()
209+
client := coderdtest.New(t, &coderdtest.Options{
210+
Logger: &logger,
211+
})
212+
owner := coderdtest.CreateFirstUser(t, client)
213+
orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
214+
215+
_, err := orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
216+
SearchQuery: fmt.Sprintf("organization:%s", "random-name"),
217+
Pagination: codersdk.Pagination{
218+
Limit: 5,
219+
},
220+
})
221+
require.Error(t, err)
187222
})
188223
}
189224

coderd/searchquery/search.go

+26-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package searchquery
22

33
import (
4+
"context"
45
"database/sql"
56
"fmt"
67
"net/url"
@@ -16,7 +17,9 @@ import (
1617
"github.com/coder/coder/v2/codersdk"
1718
)
1819

19-
func AuditLogs(query string) (database.GetAuditLogsOffsetParams, []codersdk.ValidationError) {
20+
// AuditLogs requires the database to fetch an organization by name
21+
// to convert to organization uuid.
22+
func AuditLogs(ctx context.Context, db database.Store, query string) (database.GetAuditLogsOffsetParams, []codersdk.ValidationError) {
2023
// Always lowercase for all searches.
2124
query = strings.ToLower(query)
2225
values, errors := searchTerms(query, func(term string, values url.Values) error {
@@ -30,7 +33,6 @@ func AuditLogs(query string) (database.GetAuditLogsOffsetParams, []codersdk.Vali
3033
const dateLayout = "2006-01-02"
3134
parser := httpapi.NewQueryParamParser()
3235
filter := database.GetAuditLogsOffsetParams{
33-
OrganizationID: parser.UUID(values, uuid.Nil, "organization_id"),
3436
ResourceID: parser.UUID(values, uuid.Nil, "resource_id"),
3537
ResourceTarget: parser.String(values, "", "resource_target"),
3638
Username: parser.String(values, "", "username"),
@@ -44,6 +46,28 @@ func AuditLogs(query string) (database.GetAuditLogsOffsetParams, []codersdk.Vali
4446
if !filter.DateTo.IsZero() {
4547
filter.DateTo = filter.DateTo.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
4648
}
49+
50+
// Convert the "organization" parameter to an organization uuid. This can require
51+
// a database lookup.
52+
organizationArg := parser.String(values, "", "organization")
53+
if organizationArg != "" {
54+
organizationID, err := uuid.Parse(organizationArg)
55+
if err == nil {
56+
filter.OrganizationID = organizationID
57+
} else {
58+
// Organization could be a name
59+
organization, err := db.GetOrganizationByName(ctx, organizationArg)
60+
if err != nil {
61+
parser.Errors = append(parser.Errors, codersdk.ValidationError{
62+
Field: "organization",
63+
Detail: fmt.Sprintf("Organization %q either does not exist, or you are unauthorized to view it", organizationArg),
64+
})
65+
} else {
66+
filter.OrganizationID = organization.ID
67+
}
68+
}
69+
}
70+
4771
parser.ErrorExcessParams(values)
4872
return filter, parser.Errors
4973
}

coderd/searchquery/search_test.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package searchquery_test
22

33
import (
4+
"context"
45
"database/sql"
56
"fmt"
67
"strings"
@@ -11,6 +12,7 @@ import (
1112
"github.com/stretchr/testify/require"
1213

1314
"github.com/coder/coder/v2/coderd/database"
15+
"github.com/coder/coder/v2/coderd/database/dbmem"
1416
"github.com/coder/coder/v2/coderd/searchquery"
1517
"github.com/coder/coder/v2/codersdk"
1618
)
@@ -315,7 +317,10 @@ func TestSearchAudit(t *testing.T) {
315317
c := c
316318
t.Run(c.Name, func(t *testing.T) {
317319
t.Parallel()
318-
values, errs := searchquery.AuditLogs(c.Query)
320+
// Do not use a real database, this is only used for an
321+
// organization lookup.
322+
db := dbmem.New()
323+
values, errs := searchquery.AuditLogs(context.Background(), db, c.Query)
319324
if c.ExpectedErrorContains != "" {
320325
require.True(t, len(errs) > 0, "expect some errors")
321326
var s strings.Builder

0 commit comments

Comments
 (0)