-
Notifications
You must be signed in to change notification settings - Fork 903
chore: add audit log tests #4764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
acf1ad3
2cd8b3a
c50db18
fdc94d8
93aedaf
1a9936f
3e4cb89
f669898
dc7e4a9
ac68b3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,6 +1,7 @@ | ||||||||||||||||||||||
package coderd | ||||||||||||||||||||||
|
||||||||||||||||||||||
import ( | ||||||||||||||||||||||
"context" | ||||||||||||||||||||||
"encoding/json" | ||||||||||||||||||||||
"fmt" | ||||||||||||||||||||||
"net" | ||||||||||||||||||||||
|
@@ -13,6 +14,7 @@ import ( | |||||||||||||||||||||
"github.com/google/uuid" | ||||||||||||||||||||||
"github.com/tabbed/pqtype" | ||||||||||||||||||||||
|
||||||||||||||||||||||
"cdr.dev/slog" | ||||||||||||||||||||||
"github.com/coder/coder/coderd/database" | ||||||||||||||||||||||
"github.com/coder/coder/coderd/httpapi" | ||||||||||||||||||||||
"github.com/coder/coder/coderd/httpmw" | ||||||||||||||||||||||
|
@@ -57,7 +59,7 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) { | |||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.AuditLogResponse{ | ||||||||||||||||||||||
AuditLogs: convertAuditLogs(dblogs), | ||||||||||||||||||||||
AuditLogs: api.convertAuditLogs(ctx, dblogs), | ||||||||||||||||||||||
}) | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
@@ -165,17 +167,17 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) { | |||||||||||||||||||||
rw.WriteHeader(http.StatusNoContent) | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
func convertAuditLogs(dblogs []database.GetAuditLogsOffsetRow) []codersdk.AuditLog { | ||||||||||||||||||||||
func (api *API) convertAuditLogs(ctx context.Context, dblogs []database.GetAuditLogsOffsetRow) []codersdk.AuditLog { | ||||||||||||||||||||||
alogs := make([]codersdk.AuditLog, 0, len(dblogs)) | ||||||||||||||||||||||
|
||||||||||||||||||||||
for _, dblog := range dblogs { | ||||||||||||||||||||||
alogs = append(alogs, convertAuditLog(dblog)) | ||||||||||||||||||||||
alogs = append(alogs, api.convertAuditLog(ctx, dblog)) | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
return alogs | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
func convertAuditLog(dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog { | ||||||||||||||||||||||
func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog { | ||||||||||||||||||||||
ip, _ := netip.AddrFromSlice(dblog.Ip.IPNet.IP) | ||||||||||||||||||||||
|
||||||||||||||||||||||
diff := codersdk.AuditDiff{} | ||||||||||||||||||||||
|
@@ -214,7 +216,7 @@ func convertAuditLog(dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog { | |||||||||||||||||||||
Diff: diff, | ||||||||||||||||||||||
StatusCode: dblog.StatusCode, | ||||||||||||||||||||||
AdditionalFields: dblog.AdditionalFields, | ||||||||||||||||||||||
Description: auditLogDescription(dblog), | ||||||||||||||||||||||
Description: api.auditLogDescription(ctx, dblog), | ||||||||||||||||||||||
User: user, | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
@@ -223,25 +225,31 @@ type WorkspaceResourceInfo struct { | |||||||||||||||||||||
WorkspaceName string | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
func auditLogDescription(alog database.GetAuditLogsOffsetRow) string { | ||||||||||||||||||||||
func (api *API) auditLogDescription(ctx context.Context, alog database.GetAuditLogsOffsetRow) string { | ||||||||||||||||||||||
str := fmt.Sprintf("{user} %s %s", | ||||||||||||||||||||||
codersdk.AuditAction(alog.Action).FriendlyString(), | ||||||||||||||||||||||
codersdk.ResourceType(alog.ResourceType).FriendlyString(), | ||||||||||||||||||||||
) | ||||||||||||||||||||||
|
||||||||||||||||||||||
// Strings for build updates follow the below format: | ||||||||||||||||||||||
// "{user} started workspace build for workspace {target}" | ||||||||||||||||||||||
// where target is a workspace instead of the workspace build | ||||||||||||||||||||||
// "{user} started workspace build for {target}" | ||||||||||||||||||||||
// where target is a workspace instead of the workspace build. | ||||||||||||||||||||||
// Note the workspace name will be bolded on the FE. | ||||||||||||||||||||||
if alog.ResourceType == database.ResourceTypeWorkspaceBuild { | ||||||||||||||||||||||
workspaceBytes := []byte(alog.AdditionalFields) | ||||||||||||||||||||||
var workspaceResourceInfo WorkspaceResourceInfo | ||||||||||||||||||||||
_ = json.Unmarshal(workspaceBytes, &workspaceResourceInfo) | ||||||||||||||||||||||
str += " for workspace " + workspaceResourceInfo.WorkspaceName | ||||||||||||||||||||||
err := json.Unmarshal(workspaceBytes, &workspaceResourceInfo) | ||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||
api.Logger.Error(ctx, "could not unmarshal workspace name for friendly string", slog.Error(err)) | ||||||||||||||||||||||
} | ||||||||||||||||||||||
str += " for " + workspaceResourceInfo.WorkspaceName | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about doing:
Suggested change
Then the frontend can There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. much simpler; lets me remove the logger, too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
good point!! |
||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
// We don't display the name for git ssh keys. It's fairly long and doesn't | ||||||||||||||||||||||
// make too much sense to display. | ||||||||||||||||||||||
if alog.ResourceType != database.ResourceTypeGitSshKey { | ||||||||||||||||||||||
|
||||||||||||||||||||||
// The UI-visible target for workspace builds is workspace (see above block) so we don't add it to the friendly string | ||||||||||||||||||||||
if alog.ResourceType != database.ResourceTypeGitSshKey && alog.ResourceType != database.ResourceTypeWorkspaceBuild { | ||||||||||||||||||||||
str += " {target}" | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,28 @@ import userAgentParser from "ua-parser-js" | |
import { combineClasses } from "util/combineClasses" | ||
import { AuditLogDiff } from "./AuditLogDiff" | ||
|
||
const readableActionMessage = (auditLog: AuditLog) => { | ||
// the BE returns additional_field as a string, since it's stored as JSON but | ||
// technically, it's a map, so we adjust the type here. | ||
type ExtendedAuditLog = Omit<AuditLog, "additional_fields"> & { | ||
additional_fields: Record<string, string> | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not proud of this lol There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not totally sure but gonna check it out! This seems like a reasonable workaround to me for the time being though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Edit: ignore, see next comment
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Er wait looks like
|
||
|
||
const readableActionMessage = (auditLog: ExtendedAuditLog) => { | ||
// workspace builds audit logs don't have targets; therefore format them differently | ||
if (auditLog.resource_type === "workspace_build") { | ||
// remove the "{target}" identifier in the string description as we don't use it | ||
const amendedDescription = auditLog.description.substring( | ||
0, | ||
auditLog.description.lastIndexOf(" "), | ||
) | ||
return amendedDescription | ||
.replace("{user}", `<strong>${auditLog.user?.username}</strong>`) | ||
.replace( | ||
auditLog.additional_fields.workspaceName, | ||
`<strong>${auditLog.additional_fields.workspaceName}</strong>`, | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the If workspace name is hello: If we make the let target = auditLog.resource_target.trim()
if (auditLog.resource_type === "workspace_build") {
target = auditLog.additional_fields.workspaceName
}
return auditLog.description
.replace("{user}", `<strong>${auditLog.user?.username.trim()}</strong>`)
.replace("{target}", `<strong>${target}</strong>`) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ooof, just realized this had the exact same issue you already called out previously - sorry to make you type it out again! I guess I didn't have my coffee this morning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
😂 😂 😂 |
||
} | ||
|
||
return auditLog.description | ||
.replace("{user}", `<strong>${auditLog.user?.username.trim()}</strong>`) | ||
.replace("{target}", `<strong>${auditLog.resource_target.trim()}</strong>`) | ||
|
@@ -111,7 +132,9 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({ | |
> | ||
<span | ||
dangerouslySetInnerHTML={{ | ||
__html: readableActionMessage(auditLog), | ||
__html: readableActionMessage( | ||
auditLog as unknown as ExtendedAuditLog, | ||
), | ||
}} | ||
/> | ||
<span className={styles.auditLogTime}> | ||
|
Uh oh!
There was an error while loading. Please reload this page.