| index fdd6cea..e97d63a 100644 |
| --- a/internal/home/operator_person_test.go |
| +++ b/internal/home/operator_person_test.go |
| @@ -11,8 +11,11 @@ import ( |
| "testing" |
| "time" |
| |
| + "amadan.net/oficina/home/internal/home/directorydata" |
| "amadan.net/oficina/home/internal/home/seal" |
| + "amadan.net/oficina/home/internal/home/sealtest" |
| "amadan.net/oficina/home/internal/operator" |
| + "amadan.net/oficina/home/roster" |
| family "amadan.net/rastrillo/rastrillo/crypto" |
| "github.com/carlosframework/rastrillo/db" |
| "github.com/carlosframework/rastrillo/migrate" |
| @@ -176,3 +179,130 @@ func TestOperatorSeedUsesNormalIdentityAndAtomicPrivateReceipt(t *testing.T) { |
| } |
| } |
| } |
| + |
| +// exerciseOperatorSeedPrivacy extends the original sign-in gate through the |
| +// credential receipt and directory owner seed, using separate role databases. |
| +func exerciseOperatorSeedPrivacy(t *testing.T, app *testApp) { |
| + t.Helper() |
| + ctx := context.Background() |
| + if _, e := migrate.Apply(ctx, app.db, operator.CredentialSchema); e != nil { |
| + t.Fatal(e) |
| + } |
| + signer, e := family.Generate() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + owner, e := family.Generate() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + browser, e := family.Generate() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + page, e := family.Generate() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + pins := operator.Pins{Role: "credential", Origin: Origin, Installation: opToken(20), Registry: opToken(21), Device: opToken(22), PublicKey: base64.RawURLEncoding.EncodeToString(signer.SignPub()), Actions: []string{"credential.owner-seed"}} |
| + verifier, e := operator.NewVerifier(pins) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + now := time.Now() |
| + clock := func() time.Time { return now } |
| + engine, e := operator.NewEngine(app.db.Writer(), verifier, app.secrets.Sealer, bytes.Repeat([]byte{23}, 32), clock) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + resolve, e := NewOperatorPersonResolver(app.secrets.Pepper) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + credential, e := operator.NewCredential(engine, directoryOrigin, owner, [][]byte{browser.SignPub()}, resolve) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + auth := operator.Authorization{Version: 1, Purpose: "operator-input", Role: pins.Role, Origin: pins.Origin, Installation: pins.Installation, Registry: pins.Registry, Device: pins.Device, Sequence: 1, Initial: true, JournalSequence: 1, OperationID: opToken(24), Action: "credential.owner-seed", Issued: now.Unix(), Expires: now.Unix() + 300} |
| + input := operator.OwnerInput{InputBase: operator.InputBase{Version: 1, Operation: auth.Action, OperationID: auth.OperationID}, Directory: directoryOrigin, TeamID: 1, TeamSQID: "Uk", Address: sealtest.CanaryAddress, ExistingOnly: true} |
| + raw, e := operator.SignRequest(signer, auth, input) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + out, e := credential.Execute(ctx, raw) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + var receipt operator.Result |
| + if e = json.Unmarshal(out, &receipt); e != nil { |
| + t.Fatal(e) |
| + } |
| + path := filepath.Join(t.TempDir(), "directory.db") |
| + directoryDB, e := db.Open(path, nil) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + defer directoryDB.Close() |
| + if _, e = migrate.Apply(ctx, directoryDB, migrate.Merge(directorydata.Schema, directorydata.RosterSchema, directorydata.OperatorSchema, operator.DirectorySchema)); e != nil { |
| + t.Fatal(e) |
| + } |
| + pins.Role = "directory" |
| + pins.Origin = directoryOrigin |
| + pins.Installation = opToken(25) |
| + pins.Actions = []string{"directory.initialize", "directory.seed-team"} |
| + verifier, e = operator.NewVerifier(pins) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + engine, e = operator.NewEngine(directoryDB.Writer(), verifier, app.secrets.Sealer, bytes.Repeat([]byte{26}, 32), clock) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + ov, e := operator.NewOwnerVerifier(base64.RawURLEncoding.EncodeToString(owner.SignPub()), Origin, directoryOrigin) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + rosterSigner, e := family.Generate() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + directory, e := operator.NewDirectory(engine, ov, rosterSigner, []roster.Key{{ID: "page", Public: base64.RawURLEncoding.EncodeToString(page.SignPub())}}, directorydata.Config{Apps: []string{"docs"}, ValidateRoute: func(r directorydata.Route) bool { return r.Origin == "https://"+r.Label+".docs.oficina.works" }}) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + auth.Role = pins.Role |
| + auth.Origin = pins.Origin |
| + auth.Installation = pins.Installation |
| + auth.OperationID = opToken(27) |
| + auth.Action = "directory.initialize" |
| + raw, e = operator.SignRequest(signer, auth, operator.InitializeInput{InputBase: operator.InputBase{Version: 1, Operation: auth.Action, OperationID: auth.OperationID}, Generation: opToken(28), Baseline: "operator-attests-never-provisioned"}) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + if _, e = directory.Execute(ctx, raw); e != nil { |
| + t.Fatal(e) |
| + } |
| + request, e := verifier.Verify(raw) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + auth.Sequence = 2 |
| + auth.Initial = false |
| + auth.Previous = request.Head() |
| + auth.JournalSequence = 2 |
| + auth.OperationID = opToken(29) |
| + auth.Action = "directory.seed-team" |
| + raw, e = operator.SignRequest(signer, auth, operator.SeedInput{InputBase: operator.InputBase{Version: 1, Operation: auth.Action, OperationID: auth.OperationID}, Generation: opToken(28), TeamID: 1, TeamSQID: "Uk", Label: "alpha", Name: "CANARY-TEAM-NAME-2b71", Receipt: receipt.Receipt}) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + if _, e = directory.Execute(ctx, raw); e != nil { |
| + t.Fatal(e) |
| + } |
| + var members int |
| + if e = directoryDB.Writer().QueryRow("SELECT count(*) FROM memberships WHERE role='owner'").Scan(&members); e != nil || members != 1 { |
| + t.Fatal("owner not seeded", e) |
| + } |
| + sealtest.AssertNoPlaintext(t, path) |
| + sealtest.AssertNoPlaintext(t, app.dbPath) |
| +} |