A literal * in a published subject matches the grant's *, so in one shared bucket a node could read every node's files. Each node now has declaration-files-<node>, which its account reads by name and the controller writes; the sweep judges each bucket against that node's latest declaration, under the node's lock with the send, and an object the sweep marked is put again by the next send. Putting that fails send after send raises a condition.
110 lines
4.4 KiB
Go
110 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|