@@ -346,3 +346,45 @@ func TestPermissionsDoc_CoversEveryAction(t *testing.T) { |
| 346 | 346 | } |
| 347 | 347 | } |
| 348 | 348 | } |
| 349 | + |
| 350 | +// TestImpersonation_ReadOnlyDeniesWrites pins the canonical foot-gun |
| 351 | +// guard: an impersonating admin without ImpersonateWriteOK must not |
| 352 | +// be able to write, regardless of the underlying actor's role. |
| 353 | +func TestImpersonation_ReadOnlyDeniesWrites(t *testing.T) { |
| 354 | + ctx := t.Context() |
| 355 | + d := policy.Deps{Pool: nil} |
| 356 | + repo := policy.RepoRef{ID: 1, OwnerUserID: 99, Visibility: "public"} |
| 357 | + actor := policy.Actor{ |
| 358 | + UserID: 99, // the impersonated user |
| 359 | + Impersonating: true, |
| 360 | + } |
| 361 | + dec := policy.Can(ctx, d, actor, policy.ActionIssueComment, repo) |
| 362 | + if dec.Allow { |
| 363 | + t.Fatalf("impersonation read-only must deny writes; got allow") |
| 364 | + } |
| 365 | + if dec.Code != policy.DenyImpersonationReadOnly { |
| 366 | + t.Errorf("Code = %v; want DenyImpersonationReadOnly", dec.Code) |
| 367 | + } |
| 368 | +} |
| 369 | + |
| 370 | +// TestImpersonation_WriteModeOverridesReadOnly: with ImpersonateWriteOK |
| 371 | +// flipped on, the policy proceeds to the normal role checks (the |
| 372 | +// underlying role + repo state still gate, this just lifts the |
| 373 | +// impersonation-specific deny). |
| 374 | +func TestImpersonation_WriteModeOverridesReadOnly(t *testing.T) { |
| 375 | + ctx := t.Context() |
| 376 | + d := policy.Deps{Pool: nil} |
| 377 | + repo := policy.RepoRef{ID: 1, OwnerUserID: 99, Visibility: "public"} |
| 378 | + actor := policy.Actor{ |
| 379 | + UserID: 99, |
| 380 | + Impersonating: true, |
| 381 | + ImpersonateWriteOK: true, |
| 382 | + } |
| 383 | + dec := policy.Can(ctx, d, actor, policy.ActionIssueComment, repo) |
| 384 | + // We don't assert Allow here because the action still passes |
| 385 | + // through role checks (which will deny without DB-backed role |
| 386 | + // info). The relevant assertion is "not the impersonation deny." |
| 387 | + if !dec.Allow && dec.Code == policy.DenyImpersonationReadOnly { |
| 388 | + t.Fatalf("write-mode should clear impersonation deny; got %v", dec) |
| 389 | + } |
| 390 | +} |