package operator import ( "bytes" "context" "database/sql" "encoding/base64" "errors" "os" "testing" "amadan.net/oficina/home/internal/home/directorydata" "amadan.net/oficina/home/roster" family "amadan.net/rastrillo/rastrillo/crypto" "github.com/go-json-experiment/json" ) type directoryFixture struct { engineFixture directory *Directory ownerKey *family.Keypair attempt byte bearer string } func newDirectoryFixture(t *testing.T) *directoryFixture { t.Helper() f := newEngineFixture(t) p := f.engine.verifier.pins p.Actions = []string{"directory.initialize", "directory.seed-team", "directory.provision-consumer", "directory.verify-consumer", "directory.activate-consumer"} v, e := NewVerifier(p) if e != nil { t.Fatal(e) } f.engine.verifier = v owner, e := family.Generate() if e != nil { t.Fatal(e) } signer, e := family.Generate() if e != nil { t.Fatal(e) } pageKey, e := family.Generate() if e != nil { t.Fatal(e) } ov, e := NewOwnerVerifier(base64.RawURLEncoding.EncodeToString(owner.SignPub()), "https://id.oficina.works", p.Origin) if e != nil { t.Fatal(e) } d, e := NewDirectory(f.engine, ov, signer, []roster.Key{{ID: "page1", Public: base64.RawURLEncoding.EncodeToString(pageKey.SignPub())}}, directorydata.Config{Apps: []string{"docs", "sheets"}, ValidateRoute: func(r directorydata.Route) bool { return r.Origin == "https://"+r.Label+"."+r.App+".oficina.works" }}) if e != nil { t.Fatal(e) } return &directoryFixture{engineFixture: f, directory: d, ownerKey: owner, attempt: 20} } func (f *directoryFixture) input(t *testing.T, action string, build func(InputBase) any) []byte { t.Helper() a := f.auth var sequence int64 var head string e := f.db.QueryRow("SELECT sequence,head FROM directory_operator_state WHERE singleton=1").Scan(&sequence, &head) if e != nil && !errors.Is(e, sql.ErrNoRows) { t.Fatal(e) } a.Sequence = sequence + 1 a.Previous = head a.Initial = a.Sequence == 1 a.JournalSequence = int64(f.attempt) a.Action = action a.OperationID = token(f.attempt) f.attempt++ a.Issued = f.now.Unix() a.Expires = a.Issued + 300 raw, e := SignRequest(f.key, a, build(InputBase{1, action, a.OperationID})) if e != nil { t.Fatal(e) } return raw } func (f *directoryFixture) initialize(t *testing.T) { t.Helper() raw := f.input(t, "directory.initialize", func(b InputBase) any { return InitializeInput{b, nil, token(10), "operator-attests-never-provisioned"} }) if _, e := f.directory.Execute(context.Background(), raw); e != nil { t.Fatal(e) } } func (f *directoryFixture) receipt(t *testing.T, id int64) []byte { t.Helper() sqid, e := TeamSQID(id) if e != nil { t.Fatal(e) } p := OwnerReceipt{1, "owner-seed", token(byte(id) + 100), f.directory.owner.issuer, f.directory.owner.directory, id, sqid, "pr_aaaaaaaaaaaaaaaaaaaaaaaaaa", "owner", f.now.Unix(), f.now.Unix() + 300} raw, e := SignOwner(f.ownerKey, p) if e != nil { t.Fatal(e) } return raw } func (f *directoryFixture) seed(t *testing.T, id int64, label string) ([]byte, error) { t.Helper() sqid, _ := TeamSQID(id) proof := f.receipt(t, id) raw := f.input(t, "directory.seed-team", func(b InputBase) any { return SeedInput{b, token(10), id, sqid, label, "PRIVATE-TEAM-NAME-CANARY", proof} }) out, e := f.directory.Execute(context.Background(), raw) if e != nil { return raw, e } var result Result if json.Unmarshal(out, &result) != nil || result.Epoch != 2 || result.TeamID != id { t.Fatal("wrong seed result") } return raw, nil } func TestDirectoryInitializeOwnerAndPendingConsumerAreAtomic(t *testing.T) { f := newDirectoryFixture(t) f.initialize(t) seedRaw, e := f.seed(t, 1, "alpha") if e != nil { t.Fatal(e) } if _, e = f.directory.Execute(context.Background(), seedRaw); e != nil { t.Fatal("seed replay", e) } if countRows(t, f.db, "teams") != 1 || countRows(t, f.db, "memberships") != 1 || countRows(t, f.db, "directory_operator_owner_receipts") != 1 { t.Fatal("duplicate owner/team") } raw := f.input(t, "directory.provision-consumer", func(b InputBase) any { return ProvisionInput{b, token(10), "Uk", "docs", "alpha-docs", "https://alpha.docs.oficina.works", 0} }) output, e := f.directory.Execute(context.Background(), raw) if e != nil { t.Fatal(e) } var result Result if json.Unmarshal(output, &result) != nil || !CanonicalToken(result.Bearer) || len(result.Record) == 0 { t.Fatal("missing private provision result") } repeated, e := f.directory.Execute(context.Background(), raw) if e != nil || !bytes.Equal(output, repeated) { t.Fatal("token changed on replay", e) } binding := roster.Bindings{Issuer: f.directory.owner.issuer, Directory: f.directory.owner.directory, Consumer: "alpha-docs", App: "docs", Team: "Uk", Audience: "https://alpha.docs.oficina.works"} if _, e = roster.VerifyProvisioning(result.Record, []roster.Key{{ID: keyID(f.directory.signer.SignPub()), Public: base64.RawURLEncoding.EncodeToString(f.directory.signer.SignPub())}}, binding); e != nil { t.Fatal(e) } var state string var pointerSequence int64 var pd, rd []byte if e = f.db.QueryRow("SELECT i.state,p.sequence,p.record_digest,r.record_digest FROM team_instances i JOIN directory_operator_consumer_records p ON p.consumer_id=i.consumer_id JOIN roster_operator_receipts r ON r.operation_id=p.operation_id").Scan(&state, &pointerSequence, &pd, &rd); e != nil || state != "pending" || pointerSequence != 1 || !bytes.Equal(pd, rd) { t.Fatal("wrong current pointer", e) } src, e := directorydata.NewRosterSource(f.db, f.engine.sealer, f.directory.policy, f.directory.owner.directory) if e != nil { t.Fatal(e) } _, status, e := src.Read(context.Background(), directorydata.RosterRequest{Token: result.Bearer, Team: "Uk", Fetch: token(80)}, *f.now) if status != 503 || e != directorydata.ErrUnavailable { t.Fatal("pending standing leaked", status, e) } for _, suffix := range []string{"", "-wal"} { disk, e := os.ReadFile(f.path + suffix) if e != nil && !errors.Is(e, os.ErrNotExist) { t.Fatal(e) } for _, secret := range []string{"PRIVATE-TEAM-NAME-CANARY", result.Bearer} { if bytes.Contains(disk, []byte(secret)) { t.Fatal("unsealed private field") } } } } func TestDirectoryRefusesChangedGenerationProposalLabelAndOwnerProof(t *testing.T) { f := newDirectoryFixture(t) f.initialize(t) if _, e := f.seed(t, 1, "alpha"); e != nil { t.Fatal(e) } if _, e := f.seed(t, 1, "beta"); e != ErrRefused { t.Fatal("old owner receipt reused", e) } if _, e := f.seed(t, 2, "alpha"); e != ErrRefused { t.Fatal("suite label collision", e) } if _, e := f.seed(t, 3, "gamma"); e != ErrRefused { t.Fatal("raced integer proposal", e) } if _, e := f.seed(t, 2, "alpha.beta"); e != ErrRefused { t.Fatal("invalid label", e) } raw := f.input(t, "directory.provision-consumer", func(b InputBase) any { return ProvisionInput{b, token(11), "Uk", "docs", "alpha-docs", "https://alpha.docs.oficina.works", 0} }) if _, e := f.directory.Execute(context.Background(), raw); e != ErrRefused { t.Fatal("generation mismatch", e) } raw = f.input(t, "directory.provision-consumer", func(b InputBase) any { return ProvisionInput{b, token(10), "Uk", "docs", "alpha-docs", "https://other.docs.oficina.works", 0} }) if _, e := f.directory.Execute(context.Background(), raw); e != ErrRefused { t.Fatal("host mismatch", e) } if countRows(t, f.db, "teams") != 1 || countRows(t, f.db, "team_instances") != 0 { t.Fatal("failed seed/provision mutated") } } func TestDirectoryWillNotInitializeFromEmptyTablesWithoutSignedBaseline(t *testing.T) { f := newDirectoryFixture(t) raw := f.input(t, "directory.initialize", func(b InputBase) any { return InitializeInput{b, nil, token(10), ""} }) if _, e := f.directory.Execute(context.Background(), raw); e != ErrRefused { t.Fatal("missing baseline", e) } if countRows(t, f.db, "roster_generation") != 0 || countRows(t, f.db, "directory_operator_state") != 0 { t.Fatal("invented initial authority") } f.initialize(t) raw = f.input(t, "directory.initialize", func(b InputBase) any { return InitializeInput{b, nil, token(11), "operator-attests-never-provisioned"} }) if _, e := f.directory.Execute(context.Background(), raw); e != ErrRefused { t.Fatal("generation reset", e) } } func TestEngineOperatorKeyPinCannotSilentlyRotate(t *testing.T) { f := newEngineFixture(t) if _, e := f.engine.Execute(context.Background(), f.raw, insertOne); e != nil { t.Fatal(e) } old, _ := f.engine.verifier.Verify(f.raw) k, e := family.Generate() if e != nil { t.Fatal(e) } p := f.engine.verifier.pins p.PublicKey = base64.RawURLEncoding.EncodeToString(k.SignPub()) v, e := NewVerifier(p) if e != nil { t.Fatal(e) } f.engine.verifier = v a := f.auth a.Sequence = 2 a.Initial = false a.Previous = old.Head() a.OperationID = token(55) a.JournalSequence = 2 raw, e := SignRequest(k, a, struct{}{}) if e != nil { t.Fatal(e) } if _, e = f.engine.Execute(context.Background(), raw, func(context.Context, *sql.Conn, Request) ([]byte, error) { t.Fatal("rotated key reached mutation") return nil, nil }); e != ErrRefused { t.Fatal("key changed", e) } }