tenseleyflow/shithub / a4f4d82

Browse files

audit: thread RealActorID through repo settings + webhook recordings (SR2 H2)

Substitutes (viewer.ID, raw_meta) with viewer.AuditActor(raw_meta)
at every non-admin Audit.Record callsite in repo/settings_*.go and
repo/webhooks.go. During an impersonated session the row is now
attributed to the real admin and the impersonated user_id lands in
meta — uniform with the existing /admin/* trail.

admin/helpers.recordAdminAction also moves to AuditActor for one
canonical substitution point.
Authored by espadonne
SHA
a4f4d8200bec061c2422b0b34ac3c9828c9fa1ab
Parents
81e12ae
Tree
a6cc19c

4 changed files

StatusFile+-
M internal/web/handlers/admin/helpers.go 5 11
M internal/web/handlers/repo/settings_branches.go 12 8
M internal/web/handlers/repo/settings_general.go 18 12
M internal/web/handlers/repo/webhooks.go 9 6
internal/web/handlers/admin/helpers.gomodified
@@ -13,19 +13,13 @@ import (
1313
 // recordAdminAction writes an audit row attributed to the current
1414
 // site admin, with optional impersonation-target metadata so post-
1515
 // incident forensics has both the real actor and the impersonated id.
16
+//
17
+// The (actorID, meta) substitution lives on middleware.CurrentUser
18
+// so non-admin handlers can record uniform impersonation trails
19
+// (SR2 H2).
1620
 func (h *Handlers) recordAdminAction(r *http.Request, action audit.Action, target audit.Target, targetID int64, meta map[string]any) {
1721
 	viewer := middleware.CurrentUserFromContext(r.Context())
18
-	actorID := viewer.ID
19
-	if viewer.RealActorID != 0 {
20
-		// Inside an impersonated session, the "real" admin id was
21
-		// stashed before the binding swap. Use that for actor_id and
22
-		// keep the impersonated id in meta.
23
-		actorID = viewer.RealActorID
24
-		if meta == nil {
25
-			meta = map[string]any{}
26
-		}
27
-		meta["impersonated_user_id"] = viewer.ImpersonatedUserID
28
-	}
22
+	actorID, meta := viewer.AuditActor(meta)
2923
 	if err := h.d.Audit.Record(r.Context(), h.d.Pool, actorID, action, target, targetID, meta); err != nil && h.d.Logger != nil {
3024
 		h.d.Logger.WarnContext(r.Context(), "admin: audit write", "error", err, "action", string(action))
3125
 	}
internal/web/handlers/repo/settings_branches.gomodified
@@ -128,9 +128,10 @@ func (h *Handlers) settingsBranchesUpsert(w http.ResponseWriter, r *http.Request
128128
 		}); err != nil {
129129
 			h.d.Logger.WarnContext(r.Context(), "branch-protection: check settings", "error", err)
130130
 		}
131
-		_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
131
+		auditActor, auditMeta := viewer.AuditActor(map[string]any{"branch_protection_rule_id": newID, "pattern": pattern, "action": "create"})
132
+		_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
132133
 			audit.ActionRepoCreated, audit.TargetRepo, row.ID,
133
-			map[string]any{"branch_protection_rule_id": newID, "pattern": pattern, "action": "create"})
134
+			auditMeta)
134135
 	} else {
135136
 		// Update.
136137
 		id, err := strconv.ParseInt(idStr, 10, 64)
@@ -170,9 +171,10 @@ func (h *Handlers) settingsBranchesUpsert(w http.ResponseWriter, r *http.Request
170171
 		}); err != nil {
171172
 			h.d.Logger.WarnContext(r.Context(), "branch-protection: check settings", "error", err)
172173
 		}
173
-		_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
174
+		auditActor, auditMeta := viewer.AuditActor(map[string]any{"branch_protection_rule_id": id, "pattern": pattern, "action": "update"})
175
+		_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
174176
 			audit.ActionRepoCreated, audit.TargetRepo, row.ID,
175
-			map[string]any{"branch_protection_rule_id": id, "pattern": pattern, "action": "update"})
177
+			auditMeta)
176178
 	}
177179
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/branches?notice=saved", http.StatusSeeOther)
178180
 }
@@ -199,9 +201,10 @@ func (h *Handlers) settingsBranchesDelete(w http.ResponseWriter, r *http.Request
199201
 		return
200202
 	}
201203
 	viewer := middleware.CurrentUserFromContext(r.Context())
202
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
204
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"branch_protection_rule_id": id, "pattern": existing.Pattern, "action": "delete"})
205
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
203206
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
204
-		map[string]any{"branch_protection_rule_id": id, "pattern": existing.Pattern, "action": "delete"})
207
+		auditMeta)
205208
 
206209
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/branches?notice=deleted", http.StatusSeeOther)
207210
 }
@@ -259,9 +262,10 @@ func (h *Handlers) settingsDefaultBranch(w http.ResponseWriter, r *http.Request)
259262
 	}
260263
 
261264
 	viewer := middleware.CurrentUserFromContext(r.Context())
262
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
265
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "default_branch_changed", "from": row.DefaultBranch, "to": newDefault})
266
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
263267
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
264
-		map[string]any{"action": "default_branch_changed", "from": row.DefaultBranch, "to": newDefault})
268
+		auditMeta)
265269
 
266270
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/branches?notice=default-changed", http.StatusSeeOther)
267271
 }
internal/web/handlers/repo/settings_general.gomodified
@@ -98,9 +98,10 @@ func (h *Handlers) settingsGeneralUpdate(w http.ResponseWriter, r *http.Request)
9898
 		}
9999
 	}
100100
 	viewer := middleware.CurrentUserFromContext(r.Context())
101
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
101
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "general_settings_updated"})
102
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
102103
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
103
-		map[string]any{"action": "general_settings_updated"})
104
+		auditMeta)
104105
 
105106
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/general?notice=saved", http.StatusSeeOther)
106107
 }
@@ -152,9 +153,10 @@ func (h *Handlers) settingsMergeUpdate(w http.ResponseWriter, r *http.Request) {
152153
 		return
153154
 	}
154155
 	viewer := middleware.CurrentUserFromContext(r.Context())
155
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
156
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "merge_settings_updated"})
157
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
156158
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
157
-		map[string]any{"action": "merge_settings_updated"})
159
+		auditMeta)
158160
 
159161
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/general?notice=saved", http.StatusSeeOther)
160162
 }
@@ -229,9 +231,10 @@ func (h *Handlers) settingsCollabUpsert(w http.ResponseWriter, r *http.Request)
229231
 		return
230232
 	}
231233
 	policy.InvalidateRepo(r.Context(), row.ID)
232
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
234
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "collaborator_added", "user": username, "role": string(role)})
235
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
233236
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
234
-		map[string]any{"action": "collaborator_added", "user": username, "role": string(role)})
237
+		auditMeta)
235238
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/access?notice=saved", http.StatusSeeOther)
236239
 }
237240
 
@@ -259,9 +262,10 @@ func (h *Handlers) settingsCollabRemove(w http.ResponseWriter, r *http.Request)
259262
 	}
260263
 	policy.InvalidateRepo(r.Context(), row.ID)
261264
 	viewer := middleware.CurrentUserFromContext(r.Context())
262
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
265
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "collaborator_removed", "user_id": uid})
266
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
263267
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
264
-		map[string]any{"action": "collaborator_removed", "user_id": uid})
268
+		auditMeta)
265269
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/access?notice=saved", http.StatusSeeOther)
266270
 }
267271
 
@@ -306,9 +310,10 @@ func (h *Handlers) settingsTeamGrant(w http.ResponseWriter, r *http.Request) {
306310
 		return
307311
 	}
308312
 	policy.InvalidateRepo(r.Context(), row.ID)
309
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
313
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "team_grant_added", "team_id": teamID, "role": string(role)})
314
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
310315
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
311
-		map[string]any{"action": "team_grant_added", "team_id": teamID, "role": string(role)})
316
+		auditMeta)
312317
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/access?notice=saved", http.StatusSeeOther)
313318
 }
314319
 
@@ -340,9 +345,10 @@ func (h *Handlers) settingsTeamRevoke(w http.ResponseWriter, r *http.Request) {
340345
 	}
341346
 	policy.InvalidateRepo(r.Context(), row.ID)
342347
 	viewer := middleware.CurrentUserFromContext(r.Context())
343
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
348
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "team_grant_removed", "team_id": teamID})
349
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
344350
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
345
-		map[string]any{"action": "team_grant_removed", "team_id": teamID})
351
+		auditMeta)
346352
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/access?notice=saved", http.StatusSeeOther)
347353
 }
348354
 
internal/web/handlers/repo/webhooks.gomodified
@@ -118,9 +118,10 @@ func (h *Handlers) webhookCreate(w http.ResponseWriter, r *http.Request) {
118118
 		}, friendlyWebhookError(err), "")
119119
 		return
120120
 	}
121
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
121
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "webhook_created", "webhook_id": created.ID, "url": params.URL})
122
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
122123
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
123
-		map[string]any{"action": "webhook_created", "webhook_id": created.ID, "url": params.URL})
124
+		auditMeta)
124125
 
125126
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/webhooks?notice=saved", http.StatusSeeOther)
126127
 }
@@ -179,9 +180,10 @@ func (h *Handlers) webhookUpdate(w http.ResponseWriter, r *http.Request) {
179180
 		return
180181
 	}
181182
 	viewer := middleware.CurrentUserFromContext(r.Context())
182
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
183
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "webhook_updated", "webhook_id": hook.ID})
184
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
183185
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
184
-		map[string]any{"action": "webhook_updated", "webhook_id": hook.ID})
186
+		auditMeta)
185187
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/webhooks/"+strconv.FormatInt(hook.ID, 10)+"?notice=saved", http.StatusSeeOther)
186188
 }
187189
 
@@ -202,9 +204,10 @@ func (h *Handlers) webhookDelete(w http.ResponseWriter, r *http.Request) {
202204
 		return
203205
 	}
204206
 	viewer := middleware.CurrentUserFromContext(r.Context())
205
-	_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
207
+	auditActor, auditMeta := viewer.AuditActor(map[string]any{"action": "webhook_deleted", "webhook_id": hook.ID})
208
+	_ = h.d.Audit.Record(r.Context(), h.d.Pool, auditActor,
206209
 		audit.ActionRepoCreated, audit.TargetRepo, row.ID,
207
-		map[string]any{"action": "webhook_deleted", "webhook_id": hook.ID})
210
+		auditMeta)
208211
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/webhooks?notice=saved", http.StatusSeeOther)
209212
 }
210213