mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
chore: allow organization name or uuid for audit log searching (#13721)
* chore: allow organization name or uuid for audit log searching
This commit is contained in:
@ -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.",
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user