diff --git a/coderd/audit.go b/coderd/audit.go index af6049093173f..ae0d63f543438 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -45,7 +45,7 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) { } queryStr := r.URL.Query().Get("q") - filter, errs := searchquery.AuditLogs(queryStr) + filter, errs := searchquery.AuditLogs(ctx, api.Database, queryStr) if len(errs) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid audit search query.", diff --git a/coderd/audit_test.go b/coderd/audit_test.go index 168c630e51a3b..9a810a2fce9a0 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -177,13 +177,48 @@ func TestAuditLogs(t *testing.T) { // Using the organization selector allows the org admin to fetch audit logs alogs, err := orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{ - SearchQuery: fmt.Sprintf("organization_id:%s", owner.OrganizationID.String()), + SearchQuery: fmt.Sprintf("organization:%s", owner.OrganizationID.String()), Pagination: codersdk.Pagination{ Limit: 5, }, }) require.NoError(t, err) require.Len(t, alogs.AuditLogs, 1) + + // Also try fetching by organization name + organization, err := orgAdmin.Organization(ctx, owner.OrganizationID) + require.NoError(t, err) + + alogs, err = orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{ + SearchQuery: fmt.Sprintf("organization:%s", organization.Name), + Pagination: codersdk.Pagination{ + Limit: 5, + }, + }) + require.NoError(t, err) + require.Len(t, alogs.AuditLogs, 1) + }) + + t.Run("Organization404", func(t *testing.T) { + t.Parallel() + + logger := slogtest.Make(t, &slogtest.Options{ + IgnoreErrors: true, + }) + ctx := context.Background() + client := coderdtest.New(t, &coderdtest.Options{ + Logger: &logger, + }) + owner := coderdtest.CreateFirstUser(t, client) + orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID)) + + _, err := orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{ + SearchQuery: fmt.Sprintf("organization:%s", "random-name"), + Pagination: codersdk.Pagination{ + Limit: 5, + }, + }) + require.Error(t, err) }) } diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index ab44530cc7c4c..98bdded5e98d2 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -1,6 +1,7 @@ package searchquery import ( + "context" "database/sql" "fmt" "net/url" @@ -16,7 +17,9 @@ import ( "github.com/coder/coder/v2/codersdk" ) -func AuditLogs(query string) (database.GetAuditLogsOffsetParams, []codersdk.ValidationError) { +// AuditLogs requires the database to fetch an organization by name +// to convert to organization uuid. +func AuditLogs(ctx context.Context, db database.Store, query string) (database.GetAuditLogsOffsetParams, []codersdk.ValidationError) { // Always lowercase for all searches. query = strings.ToLower(query) values, errors := searchTerms(query, func(term string, values url.Values) error { @@ -30,7 +33,6 @@ func AuditLogs(query string) (database.GetAuditLogsOffsetParams, []codersdk.Vali const dateLayout = "2006-01-02" parser := httpapi.NewQueryParamParser() filter := database.GetAuditLogsOffsetParams{ - OrganizationID: parser.UUID(values, uuid.Nil, "organization_id"), ResourceID: parser.UUID(values, uuid.Nil, "resource_id"), ResourceTarget: parser.String(values, "", "resource_target"), Username: parser.String(values, "", "username"), @@ -44,6 +46,28 @@ func AuditLogs(query string) (database.GetAuditLogsOffsetParams, []codersdk.Vali if !filter.DateTo.IsZero() { filter.DateTo = filter.DateTo.Add(23*time.Hour + 59*time.Minute + 59*time.Second) } + + // Convert the "organization" parameter to an organization uuid. This can require + // a database lookup. + organizationArg := parser.String(values, "", "organization") + if organizationArg != "" { + organizationID, err := uuid.Parse(organizationArg) + if err == nil { + filter.OrganizationID = organizationID + } else { + // Organization could be a name + organization, err := db.GetOrganizationByName(ctx, organizationArg) + if err != nil { + parser.Errors = append(parser.Errors, codersdk.ValidationError{ + Field: "organization", + Detail: fmt.Sprintf("Organization %q either does not exist, or you are unauthorized to view it", organizationArg), + }) + } else { + filter.OrganizationID = organization.ID + } + } + } + parser.ErrorExcessParams(values) return filter, parser.Errors } diff --git a/coderd/searchquery/search_test.go b/coderd/searchquery/search_test.go index a0799991d8f9d..cbbeed0ee998e 100644 --- a/coderd/searchquery/search_test.go +++ b/coderd/searchquery/search_test.go @@ -1,6 +1,7 @@ package searchquery_test import ( + "context" "database/sql" "fmt" "strings" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbmem" "github.com/coder/coder/v2/coderd/searchquery" "github.com/coder/coder/v2/codersdk" ) @@ -315,7 +317,10 @@ func TestSearchAudit(t *testing.T) { c := c t.Run(c.Name, func(t *testing.T) { t.Parallel() - values, errs := searchquery.AuditLogs(c.Query) + // Do not use a real database, this is only used for an + // organization lookup. + db := dbmem.New() + values, errs := searchquery.AuditLogs(context.Background(), db, c.Query) if c.ExpectedErrorContains != "" { require.True(t, len(errs) > 0, "expect some errors") var s strings.Builder