The tool runner's bus account carried the heard grants, consumer and bind word of every module it runs, and every agent session on that machine reaches the bus as that account, so any session could pull a granted module's consumer on MAIL. Per ADR 0376 §1 the controller now composes a restricted stream's consumer, grants and bind word only for a module that declares runs-as; registration refuses a consumes of a restricted stream without runs-as (§2), and a grant that composes nothing is said in doctor (D-heard) and in the controller's log on every raise and send.
245 lines
12 KiB
Go
245 lines
12 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// mailManifest is a module owning a restricted stream, as mesh-mail declares it (novox/hq ADR 0363), with
|
|
// the trusted mergeable file that takes its grants setting; extra is spliced in at the top.
|
|
func mailManifest(t *testing.T, streams, extra string) string {
|
|
t.Helper()
|
|
return `{"module":"mesh-mail","version":"1",` + extra + `"streams":` + streams + `,` +
|
|
`"resources":[{"id":"state","type":"directory","mode":"0700","place":"."},` +
|
|
`{"id":"grants","trusted":true,"type":"file","path":"${dir:state}/grants.json","mode":"0600",` +
|
|
`"merge":"json","takes-settings":["grants"],"content":"{\"grants\": {}}"}]}`
|
|
}
|
|
|
|
const mailStream = `[{"name":"mail","events":["mail.*.message.added","mail.*.message.changed",` +
|
|
`"mail.*.message.removed"],"max-age-days":7,"grant":{"config":"grants","events":"mail.{key}.**"}}]`
|
|
|
|
// A module declares a stream of its own (ADR 0363 §1): its events with a variable name (§3), its age and
|
|
// size with defaults, and a grant by a trusted setting of its own (§4).
|
|
func TestAModuleDeclaresAStreamOfItsOwn(t *testing.T) {
|
|
m, err := ParseManifest([]byte(mailManifest(t, mailStream, "")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(m.Streams) != 1 {
|
|
t.Fatalf("streams not read: %+v", m.Streams)
|
|
}
|
|
s := m.Streams[0]
|
|
if s.BusName() != "MAIL" || s.Days() != 7 || s.Cap() != StreamDefaultMiB || s.Grant == nil ||
|
|
s.Grant.Config != "grants" || s.Grant.Granted("mesh") != "mail.mesh.**" {
|
|
t.Fatalf("stream read as %+v", s)
|
|
}
|
|
if !s.Carries("mail.work.message.added") || s.Carries("mail.work.body") || !s.Carries("mail.*.message.removed") {
|
|
t.Fatal("the stream's patterns do not say what it carries")
|
|
}
|
|
plain, err := ParseManifest([]byte(`{"module":"feed","version":"1","streams":[{"name":"feed-items","events":["item.**"]}]}`))
|
|
if err != nil {
|
|
t.Fatalf("a stream without a grant was refused: %v", err)
|
|
}
|
|
if got := plain.Streams[0]; got.BusName() != "FEED_ITEMS" || got.Days() != StreamDefaultDays || got.Grant != nil {
|
|
t.Fatalf("defaults read as %+v", got)
|
|
}
|
|
}
|
|
|
|
// Registration refuses a stream the mesh could not hold or would leave ambiguous (ADR 0363 §1, §2, §4).
|
|
func TestAStreamIsNamedBoundedAndGrantedByATrustedConfigValue(t *testing.T) {
|
|
grants := `"grant":{"config":"grants","events":"mail.{key}.**"}`
|
|
for _, c := range []struct{ manifest, says string }{
|
|
{mailManifest(t, `[{"name":"Mail","events":["mail.**"]}]`, ""), `a stream is named locally`},
|
|
{mailManifest(t, `[{"name":"mail.x","events":["mail.**"]}]`, ""), `a stream is named locally`},
|
|
{mailManifest(t, `[{"name":"events","events":["mail.**"]}]`, ""), `the mesh's own stream EVENTS`},
|
|
{mailManifest(t, `[{"name":"log-x","events":["mail.**"]}]`, ""), `starts with LOG_`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"]},{"name":"mail","events":["x.**"]}]`, ""), `twice`},
|
|
{mailManifest(t, `[{"name":"mail","events":[]}]`, ""), `with no events`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**.x"]}]`, ""), `nothing may follow it`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"],"max-age-days":31}]`, ""), `between 1 and 30`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"],"max-mib":9000}]`, ""), `between 1 and 8192`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"],"stream":"MAIL"}]`, ""), `{name, events`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"]}]`, `"emits":["mail.mesh.message.added"],`),
|
|
`which its stream "mail" carries`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"]},{"name":"more","events":["mail.x.*"]}]`, ""),
|
|
`both carry`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"],"grant":{"config":"tags","events":"mail.{key}.**"}}]`, ""),
|
|
`is not a trusted config value`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"],"grant":{"config":"grants","events":"mail.**"}}]`, ""),
|
|
`exactly once`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"],"grant":{"config":"grants","events":"{key}.{key}"}}]`, ""),
|
|
`exactly once`},
|
|
{mailManifest(t, `[{"name":"mail","events":["mail.**"],`+grants+`,"why":1}]`, ""), `{name, events`},
|
|
{`{"module":"a.b","version":"1","streams":[{"name":"x","events":["x"]}]}`, `no dot`},
|
|
} {
|
|
_, err := ParseManifest([]byte(c.manifest))
|
|
if err == nil {
|
|
t.Errorf("%s was accepted", c.manifest)
|
|
continue
|
|
}
|
|
if !strings.Contains(err.Error(), c.says) {
|
|
t.Errorf("%s refused for the wrong reason: %v", c.manifest, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A stream name has one owner across the catalogue (ADR 0363 §1).
|
|
func TestTwoModulesCannotDeclareOneStream(t *testing.T) {
|
|
a := Manifest{Module: "a", Streams: []StreamDeclaration{{Name: "mail", Events: []string{"x"}}}}
|
|
b := Manifest{Module: "b", Streams: []StreamDeclaration{{Name: "mail", Events: []string{"y"}}}}
|
|
got := strings.Join(CatalogueProblems(Shelf{"a": a, "b": b}), "; ")
|
|
if !strings.Contains(got, "b declares the stream MAIL, which a already declares") {
|
|
t.Fatalf("a second owner of MAIL was not refused: %s", got)
|
|
}
|
|
if got := StreamNameProblems(Shelf{"a": a}); len(got) != 0 {
|
|
t.Fatalf("one owner refused: %v", got)
|
|
}
|
|
}
|
|
|
|
// The setting's value names who hears each key: a module wherever it runs, or on one machine (ADR 0363 §4).
|
|
func TestARestrictingSettingNamesAModuleOrAModuleOnAMachine(t *testing.T) {
|
|
var value any
|
|
if err := json.Unmarshal([]byte(`{"mesh":["reader","person.jochen"],"work":["ace.reader"],"home":["other"],`+
|
|
`"Bad.Key":["reader"]}`), &value); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := GrantedKeys(value, "reader", "ace"); !slices.Equal(got, []string{"mesh", "work"}) {
|
|
t.Fatalf("on ace reader is granted %v", got)
|
|
}
|
|
if got := GrantedKeys(value, "reader", "shanks"); !slices.Equal(got, []string{"mesh"}) {
|
|
t.Fatalf("on shanks reader is granted %v", got)
|
|
}
|
|
if got := GrantedKeys(`{"mesh":["reader"]}`, "reader", "ace"); !slices.Equal(got, []string{"mesh"}) {
|
|
t.Fatalf("a value held as its JSON text grants %v", got)
|
|
}
|
|
if got := GrantedKeys([]any{"reader"}, "reader", "ace"); len(got) != 0 {
|
|
t.Fatalf("a value of another shape granted %v", got)
|
|
}
|
|
}
|
|
|
|
// A consumer hears a restricted stream only where its consumes asks and the setting grants it, and only
|
|
// by naming the emitter (ADR 0363 §5); what a stream carries whole leaves EVENTS.
|
|
func TestAConsumerHearsOnlyWhatItAsksAndIsGranted(t *testing.T) {
|
|
provider, err := ParseManifest([]byte(mailManifest(t, mailStream, `"emits":["account.added"],`)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
shelf := map[string]Manifest{"mesh-mail": provider}
|
|
var value any
|
|
_ = json.Unmarshal([]byte(`{"mesh":["reader"],"work":["shanks.reader"]}`), &value)
|
|
values := func(module, setting string) []any {
|
|
if module == "mesh-mail" && setting == "grants" {
|
|
return []any{value}
|
|
}
|
|
return nil
|
|
}
|
|
reader := Manifest{Module: "reader", Consumes: []string{"mesh-mail.mail.**", "mesh-mail.account.added", "billing.paid"}}
|
|
|
|
parts, touches, onEvents := Hearing(reader, shelf, "ace", values)
|
|
if !touches || len(parts) != 1 || parts[0].Stream.BusName() != "MAIL" {
|
|
t.Fatalf("heard %+v", parts)
|
|
}
|
|
want := []string{"mail.mesh.message.added", "mail.mesh.message.changed", "mail.mesh.message.removed"}
|
|
if !slices.Equal(parts[0].Patterns, want) {
|
|
t.Fatalf("on ace reader hears %v, want %v", parts[0].Patterns, want)
|
|
}
|
|
if !slices.Equal(onEvents, []string{"mesh-mail.account.added", "billing.paid"}) {
|
|
t.Fatalf("left on EVENTS: %v", onEvents)
|
|
}
|
|
parts, _, _ = Hearing(reader, shelf, "shanks", values)
|
|
if len(parts) != 1 || len(parts[0].Patterns) != 6 {
|
|
t.Fatalf("on shanks reader hears %+v", parts)
|
|
}
|
|
|
|
narrow := Manifest{Module: "reader", Consumes: []string{"mesh-mail.mail.*.message.added"}}
|
|
parts, _, _ = Hearing(narrow, shelf, "ace", values)
|
|
if len(parts) != 1 || !slices.Equal(parts[0].Patterns, []string{"mail.mesh.message.added"}) {
|
|
t.Fatalf("a narrower consumes heard %+v", parts)
|
|
}
|
|
|
|
stranger := Manifest{Module: "stranger", Consumes: []string{"mesh-mail.mail.**"}}
|
|
parts, touches, onEvents = Hearing(stranger, shelf, "ace", values)
|
|
if len(parts) != 0 || !touches || len(onEvents) != 0 {
|
|
t.Fatalf("a module not granted heard %+v (touches %v, on EVENTS %v)", parts, touches, onEvents)
|
|
}
|
|
everything := Manifest{Module: "audit", Consumes: []string{"**", "*.mail.**"}}
|
|
parts, touches, _ = Hearing(everything, shelf, "ace", values)
|
|
if len(parts) != 0 || touches {
|
|
t.Fatalf("a wildcard emitter reached a module's stream: %+v", parts)
|
|
}
|
|
parts, _, _ = Hearing(reader, shelf, "ace", func(string, string) []any { return nil })
|
|
if len(parts) != 0 {
|
|
t.Fatalf("with no setting reader heard %+v", parts)
|
|
}
|
|
}
|
|
|
|
// A stream without a grant is heard by whoever asks, within its patterns.
|
|
func TestAnUnrestrictedStreamIsHeardByWhoeverAsks(t *testing.T) {
|
|
feed := Manifest{Module: "feed", Streams: []StreamDeclaration{{Name: "items", Events: []string{"item.*.added", "item.*.gone"}}}}
|
|
reader := Manifest{Module: "reader", Consumes: []string{"feed.item.**", "feed.item.x.added"}}
|
|
parts, touches, onEvents := Hearing(reader, map[string]Manifest{"feed": feed}, "ace", nil)
|
|
if !touches || len(onEvents) != 0 || len(parts) != 1 ||
|
|
!slices.Equal(parts[0].Patterns, []string{"item.*.added", "item.*.gone"}) {
|
|
t.Fatalf("heard %+v, on EVENTS %v", parts, onEvents)
|
|
}
|
|
}
|
|
|
|
func TestNarrowingTwoPatterns(t *testing.T) {
|
|
for _, c := range []struct{ a, b, want string }{
|
|
{"mail.**", "mail.*.message.added", "mail.*.message.added"},
|
|
{"mail.*.message.added", "mail.mesh.**", "mail.mesh.message.added"},
|
|
{"a.*", "*.b", "a.b"},
|
|
{"**", "a.**", "a.**"},
|
|
{"a.b", "a.c", ""},
|
|
{"a", "a.**", ""},
|
|
} {
|
|
got, ok := narrow(c.a, c.b)
|
|
if (c.want == "") == ok || got != c.want {
|
|
t.Errorf("narrow(%s, %s) = %q, %v; want %q", c.a, c.b, got, ok, c.want)
|
|
}
|
|
}
|
|
if got := uncovered([]string{"a.**", "a.b", "a.b", "c"}); !slices.Equal(got, []string{"a.**", "c"}) {
|
|
t.Fatalf("uncovered: %v", got)
|
|
}
|
|
}
|
|
|
|
// **A module that hears a restricted stream runs as an account of its own** (ADR 0376 §2): registration refuses a
|
|
// `consumes` naming a module and asking for events its restricted stream carries, unless the manifest declares
|
|
// `runs-as`; a stream without a grant, an event the provider says on EVENTS, and a wildcard emitter stay as they
|
|
// were, and a consumer whose provider is not on the shelf is judged once it is.
|
|
func TestHearingARestrictedStreamNeedsAnAccountOfItsOwn(t *testing.T) {
|
|
provider, err := ParseManifest([]byte(mailManifest(t, mailStream, `"emits":["account.added"],`)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
feed := Manifest{Module: "feed", Streams: []StreamDeclaration{{Name: "items", Events: []string{"item.**"}}}}
|
|
carried := Manifest{Module: "reader", Consumes: []string{"mesh-mail.mail.*.message.added"}}
|
|
got := RestrictedHearingProblems(Shelf{"mesh-mail": provider, "reader": carried})
|
|
if len(got) != 1 || !strings.Contains(got[0], "reader consumes mesh-mail.mail.*.message.added") ||
|
|
!strings.Contains(got[0], "MAIL") || !strings.Contains(got[0], "runs-as") || !strings.Contains(got[0], "ADR 0376") {
|
|
t.Fatalf("a carried module hearing MAIL was not refused as it should be: %v", got)
|
|
}
|
|
if all := strings.Join(CatalogueProblems(Shelf{"mesh-mail": provider, "reader": carried}), "; "); !strings.Contains(all, got[0]) {
|
|
t.Fatalf("the catalogue's check does not refuse it: %s", all)
|
|
}
|
|
own := carried
|
|
own.RunsAs = "reader"
|
|
if got := RestrictedHearingProblems(Shelf{"mesh-mail": provider, "reader": own}); len(got) != 0 {
|
|
t.Fatalf("a module of its own account was refused: %v", got)
|
|
}
|
|
for _, m := range []Manifest{
|
|
{Module: "events", Consumes: []string{"mesh-mail.account.added"}},
|
|
{Module: "items", Consumes: []string{"feed.item.**"}},
|
|
{Module: "audit", Consumes: []string{"**", "*.mail.**"}},
|
|
} {
|
|
if got := RestrictedHearingProblems(Shelf{"mesh-mail": provider, "feed": feed, m.Module: m}); len(got) != 0 {
|
|
t.Errorf("%s was refused: %v", m.Module, got)
|
|
}
|
|
}
|
|
if got := RestrictedHearingProblems(Shelf{"reader": carried}); len(got) != 0 {
|
|
t.Fatalf("judged without its provider: %v", got)
|
|
}
|
|
}
|