package broker import ( "slices" "strings" "testing" ) // 1697/AC1: the composed accounts give a node-engine exactly the four reads of one object by its name, on its own node's bucket and // no other's; the controller the write of every node's bucket; and no other principal anything of any bucket. func TestEachNodesBucketIsWrittenByTheControllerAndReadByNameByThatNodesEngine(t *testing.T) { // 1697/AC1 node, err := PermissionsFor(Principal{Kind: KindNode, Node: "alpha", PasswordHash: "x"}) if err != nil { t.Fatal(err) } var mine []string for _, s := range node.Publish { if strings.Contains(s, "declaration-files") { mine = append(mine, s) } } want := []string{ "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-alpha.*.$O.declaration-files-alpha.C.*", "$JS.API.CONSUMER.DELETE.OBJ_declaration-files-alpha.*", "$JS.API.DIRECT.GET.OBJ_declaration-files-alpha.$O.declaration-files-alpha.M.*", "$JS.FC.OBJ_declaration-files-alpha.>", } if !slices.Equal(mine, want) { t.Fatalf("a node-engine may publish %q of the buckets, want exactly %q", mine, want) } // What would make a list, a write, or a read of another node's files: none of it, in any pattern a node holds. (The real // server's judgement of the wildcard forms is the live test's.) for _, refused := range []string{ "$JS.API.STREAM.INFO.OBJ_declaration-files-alpha", "$JS.API.STREAM.MSG.GET.OBJ_declaration-files-alpha", "$JS.API.DIRECT.GET.OBJ_declaration-files-alpha", "$JS.API.DIRECT.GET.OBJ_declaration-files-alpha.$O.declaration-files-alpha.M.abc.def", "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-alpha.x.$O.declaration-files-alpha.M.YWJj", "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-alpha.x.$O.declaration-files-alpha.M.YWJj.more", "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-alpha", "$O.declaration-files-alpha.M.abc", "$O.declaration-files-alpha.C.abc", // another node's bucket, however it is named "$JS.API.DIRECT.GET.OBJ_declaration-files-beta.$O.declaration-files-beta.M.YWJj", "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-beta.h4sh.$O.declaration-files-beta.C.NUID", "$JS.API.CONSUMER.DELETE.OBJ_declaration-files-beta.h4sh", "$JS.FC.OBJ_declaration-files-beta.x", // and the old shared bucket "$JS.API.DIRECT.GET.OBJ_declaration-files.$O.declaration-files.M.YWJj", } { if MayPublish(node, refused) { t.Errorf("a node-engine may publish %s, which lists or writes a bucket or reads another node's", refused) } } for _, kept := range []string{ "$JS.API.DIRECT.GET.OBJ_declaration-files-alpha.$O.declaration-files-alpha.M.YWJj", "$JS.API.CONSUMER.CREATE.OBJ_declaration-files-alpha.h4sh.$O.declaration-files-alpha.C.NUID", } { if !MayPublish(node, kept) { t.Errorf("a node-engine may not publish %s, which reads one object of its own bucket by its name", kept) } } controller, err := PermissionsFor(Principal{Kind: KindController, PasswordHash: "x", FilesFor: []string{"alpha", "beta"}}) if err != nil { t.Fatal(err) } for _, s := range []string{"$O.declaration-files-alpha.M.abc", "$O.declaration-files-beta.C.nuid"} { if !MayPublish(controller, s) { t.Errorf("the controller may not write %s", s) } } if MayPublish(controller, "$O.declaration-files-gamma.M.abc") { t.Error("the controller may write the bucket of a node it was not given") } for _, p := range []Principal{ {Kind: KindModule, Module: "mesh-vault", PasswordHash: "x"}, {Kind: KindNodeTools, Node: "alpha", PasswordHash: "x"}, {Kind: KindPerson, Module: "jochen", Invokes: []string{"*"}, PasswordHash: "x"}, } { perms, err := PermissionsFor(p) if err != nil { t.Fatal(err) } for _, s := range append(append([]string(nil), perms.Publish...), perms.Subscribe...) { if strings.Contains(s, "declaration-files") { t.Errorf("%s holds %s, and no account but the controller's and the node-engines' has any access", p.Username(), s) } } } } // The users the controller composes carry each node's bucket to the controller and to that node alone. func TestTheComposedUsersGiveEachNodeItsOwnBucketAndTheControllerAll(t *testing.T) { users, err := Users(Records{Nodes: []string{"alpha", "beta"}}) if err != nil { t.Fatal(err) } var controller Principal for _, u := range users { if u.Kind == KindController { controller = u } } if !slices.Equal(controller.FilesFor, []string{"alpha", "beta"}) { t.Fatalf("the controller writes the buckets of %v", controller.FilesFor) } } // A node name that could carry a subject wildcard or separator into a grant is refused with an error, never composed into a bucket // (ADR 0488): the grants of such a name would reach another node's bucket. func TestANodeNameThatCarriesAWildcardGetsNoBucketAndNoGrant(t *testing.T) { for _, node := range []string{"", "*", ">", "a.b", "a b", "a*"} { if b, err := DeclarationFilesBucketOf(node); err == nil { t.Errorf("node %q got the bucket %q", node, b) } if _, err := DeclarationFilesReads(node); err == nil { t.Errorf("node %q got read grants", node) } if _, err := DeclarationFilesWrites(node); err == nil { t.Errorf("node %q got write grants", node) } if _, err := DeclarationFilesConfig(node); err == nil { t.Errorf("node %q got a bucket config", node) } } if b, err := DeclarationFilesBucketOf("g14"); err != nil || b != "declaration-files-g14" { t.Errorf("g14: %q %v", b, err) } }