Skip to content

Commit 8cecc18

Browse files
authored
feat: update audit context to check Actor for scheduler attribution (#2151)
GetUserFromContext now checks for an Actor in context first (returning actor.ID if non-empty), then falls back to the existing UserID and DefaultAuditUser paths. Schedulers and workers that set an Actor via auth.WithActor are now correctly attributed in audit trails. Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com>
1 parent 9ba4d2f commit 8cecc18

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

shared/platform/audit/context.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ const (
1414
)
1515

1616
// GetUserFromContext extracts the user identifier from the request context.
17-
// Returns the user ID from JWT claims if available, otherwise returns DefaultAuditUser.
17+
// Checks in order: Actor (scheduler/worker identity), UserID (JWT claims), DefaultAuditUser.
1818
// This function is safe to call with any context and will never return an empty string.
1919
func GetUserFromContext(ctx context.Context) string {
2020
if ctx == nil {
2121
return DefaultAuditUser
2222
}
2323

24+
if actor, ok := auth.ActorFromContext(ctx); ok && actor.ID != "" {
25+
return actor.ID
26+
}
27+
2428
userID, ok := auth.GetUserIDFromContext(ctx)
2529
if !ok || userID == "" {
2630
return DefaultAuditUser

shared/platform/audit/context_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,54 @@ func TestGetUserFromContext_WithWrongType(t *testing.T) {
5050

5151
assert.Equal(t, DefaultAuditUser, result)
5252
}
53+
54+
func TestGetUserFromContext_WithActor(t *testing.T) {
55+
actor := auth.Actor{
56+
ID: "system:scheduler:billing",
57+
Type: auth.ActorTypeScheduler,
58+
Source: "cron-scheduler",
59+
}
60+
ctx := auth.WithActor(context.Background(), actor)
61+
62+
result := GetUserFromContext(ctx)
63+
64+
assert.Equal(t, "system:scheduler:billing", result)
65+
}
66+
67+
func TestGetUserFromContext_ActorTakesPriorityOverUserID(t *testing.T) {
68+
actor := auth.Actor{
69+
ID: "system:scheduler:billing",
70+
Type: auth.ActorTypeScheduler,
71+
}
72+
ctx := auth.WithActor(context.Background(), actor)
73+
ctx = context.WithValue(ctx, auth.UserIDContextKey, "user-12345")
74+
75+
result := GetUserFromContext(ctx)
76+
77+
assert.Equal(t, "system:scheduler:billing", result)
78+
}
79+
80+
func TestGetUserFromContext_ActorWithEmptyID_FallsBackToUserID(t *testing.T) {
81+
actor := auth.Actor{
82+
ID: "",
83+
Type: auth.ActorTypeScheduler,
84+
}
85+
ctx := auth.WithActor(context.Background(), actor)
86+
ctx = context.WithValue(ctx, auth.UserIDContextKey, "user-12345")
87+
88+
result := GetUserFromContext(ctx)
89+
90+
assert.Equal(t, "user-12345", result)
91+
}
92+
93+
func TestGetUserFromContext_ActorWithEmptyID_FallsBackToDefault(t *testing.T) {
94+
actor := auth.Actor{
95+
ID: "",
96+
Type: auth.ActorTypeScheduler,
97+
}
98+
ctx := auth.WithActor(context.Background(), actor)
99+
100+
result := GetUserFromContext(ctx)
101+
102+
assert.Equal(t, DefaultAuditUser, result)
103+
}

0 commit comments

Comments
 (0)