Files
zeekay d46f495b7b a request reaches the Base of the org its token carries
Every read landed on the shared platform Base whatever org was named. The
per-org files existed and were written by nothing.

Three things were wrong, stacked. The org was taken from the `owner` claim,
which IAM stamps with the org of the APPLICATION a token was minted through, so
a token carrying alpha resolved to whichever org owned the sign-in app. It then
travelled over X-Org-Id, a header the client also sets and this overwrote. And
whichever org it landed on, the resolver opened a dbx pool, set it on the
request as "orgDB", and nothing anywhere read that key — every handler kept
using e.App. Provisioning ran, directories appeared, and the request went to
data.db regardless.

The org is now resolved once, in loadAuthToken, from the membership set
authz.Claims carries, and RequestEvent.App is pointed at that org's Base before
any handler runs. An org's Base is a Base: {DataDir}/orgs/{org}/data.db,
bootstrapped on first use, with its own collections and settings. apis.Bases is
the one lookup from an org to its Base; plugins/org registers it, and a
deployment that registers none has exactly one Base for everybody.

X-Org-Id and the rest of the identity headers are deleted on ingress, because
inbound they are a client asserting an identity. The header is read once first,
as the org a request says it means, and honored only where the token already
carries it — naming any other org answers 403 rather than quietly serving the
caller's own, which reads as an empty Base and is a different, false statement.

The reserved admin org's Base is the process's own, since _superusers, schema,
settings, backups and logs are process-wide. A token with no membership at all
is a machine and is refused rather than handed that Base: its org could only
come from `owner`, and reading that claim is the bug above.

PrincipalIsolation is gone. Registering the plugin is what gives an org a Base;
a deployment with orgs and one shared database is not a shape anyone wants. The
pool manager it gated went with it, having lost its only caller. The org's file
is data.db rather than org.db — the directory already says whose it is.
2026-08-11 11:57:25 -07:00

52 lines
1.7 KiB
Go

package org
import (
"os"
"testing"
)
// TestBaseReportsWhatIsOnDisk pins the one property that makes /v1/bases worth
// asking: it reports the filesystem, not a table.
//
// The handler it replaced read the local _orgs collection, which IAM owns and
// this package never writes — so it answered "no such org" for every org,
// including the caller's own, while that org's Base sat on disk being written
// to. A view built from a table nobody fills cannot be wrong in a way anyone
// notices; a view built from the file either finds it or does not.
func TestBaseReportsWhatIsOnDisk(t *testing.T) {
db, _ := testOrgDB(t)
p := &plugin{orgDB: db}
// An org nobody has used yet: no Base, and that is an answer rather than
// an error — it is what every org looks like before its first request.
if got := p.base("acme"); got.Exists || got.Bytes != 0 {
t.Fatalf("unused org: got %+v, want a Base that does not exist", got)
}
// A place to put one is not one. Provisioning makes the directory; opening
// the Base makes the file, and only the file is a Base.
if _, err := db.ProvisionOrg("acme"); err != nil {
t.Fatal(err)
}
if got := p.base("acme"); got.Exists {
t.Fatalf("an empty directory was reported as a Base: %+v", got)
}
if err := os.WriteFile(db.OrgDBPath("acme"), []byte("a base"), 0600); err != nil {
t.Fatal(err)
}
got := p.base("acme")
if !got.Exists {
t.Fatalf("a Base on disk was reported missing: %+v", got)
}
if got.Org != "acme" {
t.Fatalf("org %q, want acme", got.Org)
}
// A different org does not inherit it — the isolation is the whole point.
if other := p.base("globex"); other.Exists {
t.Fatalf("globex saw acme's Base: %+v", other)
}
}