mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
Administering one's own org says nothing about the target, so the derived caller reads and forwards OrgAdmin as false. Owner and Admin, the principal's own platform authority, are unchanged. Co-authored-by: Hanzo Dev <[email protected]>
177 lines
6.1 KiB
Go
177 lines
6.1 KiB
Go
package zip
|
|
|
|
import (
|
|
"context"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
// The security property, stated as tests: an impersonation is derivable only
|
|
// from an authenticated identity, and is distinguishable from one at every hop.
|
|
|
|
// It cannot invent a caller. This is the laundering guard, unchanged.
|
|
func TestActingAs_RefusesWithNothingAuthenticated(t *testing.T) {
|
|
_, err := ActingAs(context.Background(), "victim-org")
|
|
if err == nil {
|
|
t.Fatal("ActingAs minted a caller out of nothing — that is the laundering hole")
|
|
}
|
|
if CallerOf(context.Background()).Org != "" {
|
|
t.Error("a refused ActingAs still altered the caller")
|
|
}
|
|
}
|
|
|
|
// It re-points an authenticated one, and records who is acting.
|
|
func TestActingAs_CarriesBothWhoActsAndOnWhoseBehalf(t *testing.T) {
|
|
admin := Caller{User: "u-admin", Org: "platform", Owner: "platform", Admin: true}
|
|
ctx := WithCaller(context.Background(), admin)
|
|
|
|
acting, err := ActingAs(ctx, "acme")
|
|
if err != nil {
|
|
t.Fatalf("ActingAs: %v", err)
|
|
}
|
|
got := CallerOf(acting)
|
|
if got.Org != "acme" {
|
|
t.Errorf("Org = %q, want acme — the call must reach the target's ledger", got.Org)
|
|
}
|
|
if got.ActedBy != "u-admin" {
|
|
t.Errorf("ActedBy = %q, want u-admin — an audit row that omits the actor records the wrong one", got.ActedBy)
|
|
}
|
|
if got.User != "u-admin" {
|
|
t.Errorf("User = %q; the acting principal must not be erased", got.User)
|
|
}
|
|
// The original context is untouched.
|
|
if CallerOf(ctx).Org != "platform" {
|
|
t.Error("ActingAs mutated the context it derived from")
|
|
}
|
|
}
|
|
|
|
// A chain of hops cannot quietly reassign responsibility.
|
|
func TestActingAs_OriginalActorSurvivesAChain(t *testing.T) {
|
|
ctx := WithCaller(context.Background(), Caller{User: "u-admin", Org: "platform"})
|
|
one, err := ActingAs(ctx, "acme")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
two, err := ActingAs(one, "globex")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := CallerOf(two)
|
|
if got.Org != "globex" {
|
|
t.Errorf("Org = %q, want globex", got.Org)
|
|
}
|
|
if got.ActedBy != "u-admin" {
|
|
t.Errorf("ActedBy = %q after two hops, want the ORIGINAL actor u-admin", got.ActedBy)
|
|
}
|
|
}
|
|
|
|
// The impersonation travels, or the next hop sees a clean call from the target
|
|
// org and the audit trail ends at this process.
|
|
func TestActingAs_TravelsOnTheWire(t *testing.T) {
|
|
ctx := WithCaller(context.Background(), Caller{User: "u-admin", Org: "platform"})
|
|
acting, err := ActingAs(ctx, "acme")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
h := CallerOf(acting).headers()
|
|
if h[HeaderOrg] != "acme" {
|
|
t.Errorf("forwarded org = %q, want acme — this is the wrong-ledger bug", h[HeaderOrg])
|
|
}
|
|
if h[HeaderActedBy] != "u-admin" {
|
|
t.Errorf("forwarded %s = %q, want u-admin", HeaderActedBy, h[HeaderActedBy])
|
|
}
|
|
var forwarded bool
|
|
for _, name := range identityHeaders {
|
|
if name == HeaderActedBy {
|
|
forwarded = true
|
|
}
|
|
}
|
|
if !forwarded {
|
|
t.Error("X-Acted-By is not in the forwarded identity set, so the impersonation stops at this hop")
|
|
}
|
|
}
|
|
|
|
// The project is scoped to the original org, so it must not ride along.
|
|
func TestActingAs_DoesNotCarryTheOriginalProject(t *testing.T) {
|
|
ctx := WithCaller(context.Background(), Caller{User: "u", Org: "platform", Project: "p-platform"})
|
|
acting, _ := ActingAs(ctx, "acme")
|
|
if got := CallerOf(acting).Project; got != "" {
|
|
t.Errorf("Project = %q; a project belongs to the org it was scoped to", got)
|
|
}
|
|
}
|
|
|
|
// The derived caller is what an outbound call carries, from a request-backed context
|
|
// and from a stated one alike, and it is written whole: the target org, the actor,
|
|
// the original principal and its claim, and never the original org's project.
|
|
//
|
|
// Mutation proof: drop the acting branch from forwardIdentity and the request row
|
|
// forwards the request's own org and project with no X-Acted-By, and the stated row
|
|
// forwards the original org.
|
|
func TestActingAs_IsWhatAnOutboundCallCarries(t *testing.T) {
|
|
var got map[string]string
|
|
app := New(Config{AppName: "front", DisableStartupMessage: true})
|
|
app.Raw("GET", "/ask", func(c *Ctx) error {
|
|
ctx, err := ActingAs(c.Forward(), "globex")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
got = forwarded(ctx)
|
|
return c.JSON(200, map[string]string{"ok": "1"})
|
|
})
|
|
req := httptest.NewRequest("GET", "/ask", nil)
|
|
for k, v := range map[string]string{
|
|
HeaderOrg: "platform", HeaderProject: "p-platform", HeaderUser: "u-admin",
|
|
HeaderUserName: "ada", HeaderUserOwner: "admin", HeaderUserAdmin: "true",
|
|
HeaderAccount: "person:admin/ada", HeaderRequestID: "req-9",
|
|
} {
|
|
req.Header.Set(k, v)
|
|
}
|
|
resp, err := app.Fiber().Test(req)
|
|
if err != nil {
|
|
t.Fatalf("GET: %v", err)
|
|
}
|
|
_ = resp.Body.Close()
|
|
|
|
want := map[string]string{
|
|
HeaderOrg: "globex", HeaderActedBy: "u-admin", HeaderUser: "u-admin",
|
|
HeaderUserName: "ada", HeaderUserOwner: "admin", HeaderUserAdmin: "true",
|
|
HeaderAccount: "person:admin/ada", HeaderRequestID: "req-9",
|
|
}
|
|
for k, v := range want {
|
|
if got[k] != v {
|
|
t.Errorf("a request-backed ActingAs forwarded %s = %q, want %q (%v)", k, got[k], v, got)
|
|
}
|
|
}
|
|
if v, ok := got[HeaderProject]; ok {
|
|
t.Errorf("a request-backed ActingAs forwarded the original org's project %q", v)
|
|
}
|
|
|
|
stated := WithCaller(context.Background(), Caller{User: "u-admin", Org: "platform"})
|
|
acting, err := ActingAs(stated, "acme")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h := forwarded(acting); h[HeaderOrg] != "acme" || h[HeaderActedBy] != "u-admin" {
|
|
t.Errorf("a stated ActingAs forwarded org %q acted-by %q, want acme and u-admin", h[HeaderOrg], h[HeaderActedBy])
|
|
}
|
|
}
|
|
|
|
// Administering one's own org is not administering the org acted for, so the derived
|
|
// caller does not carry OrgAdmin there, on the context or on the next hop.
|
|
func TestActingAs_DoesNotCarryOrgAdminToTheTarget(t *testing.T) {
|
|
ctx := WithCaller(context.Background(), Caller{User: "u", Org: "acme", Owner: "acme", OrgAdmin: true})
|
|
acting, err := ActingAs(ctx, "globex")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if CallerOf(acting).OrgAdmin {
|
|
t.Error("an org admin of acme reads as an org admin of globex")
|
|
}
|
|
if h := forwarded(acting); h[HeaderUserOrgAdmin] != "" {
|
|
t.Errorf("the next hop receives %s = %q for globex", HeaderUserOrgAdmin, h[HeaderUserOrgAdmin])
|
|
}
|
|
if !CallerOf(ctx).OrgAdmin {
|
|
t.Error("ActingAs changed the caller it derived from")
|
|
}
|
|
}
|