mesh-grants: refuse a take-over over an uncompared deny; ask words only for added rights
mesh/delivery merged: source.main moved to novox/mesh-delivery@a49fd9cc65f7
mesh/check checking 3192d89edfcb on a49fd9cc65f7
mesh/delivery merged: source.main moved to novox/mesh-delivery@a49fd9cc65f7
mesh/check checking 3192d89edfcb on a49fd9cc65f7
Review round 1 of issue 1945's fix found two ways the guard could let a loss through or hide one from the operator. A publish deny was never compared: grants.User carried no deny, so a widened allow that brings a new deny (a lost publish right) passed as an added right. The user now carries its deny; a deny the shadow adds is a loss, and while nats_users answers no deny for the loaded list, a user whose grants differ and whose shadow grant has a deny refuses, named as a deny the loaded list cannot be compared on. None of today's 41 differing users carries a deny (only the four node-tools users do, and they are the same in both lists). The ask said "takes none away" whenever rights were added, whatever else differed. Its words now name the added rights only when nothing else refuses (Refusals: unread list, Removing, a missing hash), and otherwise say what the yes would be refused for, naming the users; Decide uses the same Refusals. issue 1945
This commit is contained in:
@@ -749,6 +749,14 @@ func TestGrants(t *testing.T) {
|
||||
// Only added rights (ADR 0562): the loaded list lacks a publish of a.mod-x the shadow grants.
|
||||
fewer := slices.Clone(shadow)
|
||||
fewer[x].Publish = slices.Clone(fewer[x].Publish[1:])
|
||||
// A deny the shadow adds where the loaded list says its own (empty): a publish right lost. And the same deny where
|
||||
// the loaded list says none, as nats_users answers today, on a user whose grants differ: it cannot be compared.
|
||||
denied := slices.Clone(shadow)
|
||||
denied[x].PublishDeny = []string{"mesh.seat.node-launcher.tool.secret"}
|
||||
readable := slices.Clone(fewer)
|
||||
for i := range readable {
|
||||
readable[i].PublishDeny = []string{}
|
||||
}
|
||||
// The live comparison the operator's yes was refused on, 2026-10-27 21:19 UTC (issue 1945): 41 users differ, every
|
||||
// difference an added right. Read whole; a row's mutate makes one of the cases that must still refuse.
|
||||
var live struct {
|
||||
@@ -791,6 +799,8 @@ func TestGrants(t *testing.T) {
|
||||
{"one loaded user placement no longer names", append(slices.Clone(shadow), grants.User{User: "b.gone"}), nil, []string{"b.gone"}, []string{"b.gone"}, nil, true, nil, nil, 0},
|
||||
{"grants differ", changed, nil, nil, nil, []string{"a.mod-x"}, false, nil, []string{"a.mod-x"}, 1},
|
||||
{"only a right added", fewer, nil, nil, nil, []string{"a.mod-x"}, false, nil, nil, 1},
|
||||
{"a deny added refuses", readable, nil, nil, nil, []string{"a.mod-x"}, false, nil, []string{"a.mod-x"}, 1},
|
||||
{"a deny the loaded list cannot say refuses a user whose grants differ", fewer, nil, nil, nil, []string{"a.mod-x"}, false, nil, []string{"a.mod-x"}, 1},
|
||||
{"captured 21:19: only added rights", nil, nil, nil, nil, nil, false, captured(nil), nil, 41},
|
||||
{"captured, a right removed", nil, nil, nil, nil, nil, false, captured(func(c *grants.Comparison) {
|
||||
nodeAce(c).SubscribeDropped = []string{"mesh.mod.mesh-vault.event.secret-changed"}
|
||||
@@ -804,13 +814,18 @@ func TestGrants(t *testing.T) {
|
||||
{"captured, a user added", nil, nil, nil, nil, nil, false, captured(func(c *grants.Comparison) {
|
||||
c.Added = []string{"shanks.new-module"}
|
||||
}), []string{"shanks.new-module"}, 41},
|
||||
{"captured, a login lost", nil, nil, nil, nil, nil, false, captured(nil), []string{"novox.telegram"}, 41},
|
||||
} {
|
||||
t.Run(row.name, func(t *testing.T) {
|
||||
var c grants.Comparison
|
||||
if row.live != nil {
|
||||
c = *row.live
|
||||
} else {
|
||||
c = grants.Compare(shadow, row.loaded, grants.Placed(in))
|
||||
from := shadow
|
||||
if strings.Contains(row.name, "deny") {
|
||||
from = denied
|
||||
}
|
||||
c = grants.Compare(from, row.loaded, grants.Placed(in))
|
||||
var differ []string
|
||||
for _, d := range c.Differ {
|
||||
differ = append(differ, d.User)
|
||||
@@ -820,17 +835,45 @@ func TestGrants(t *testing.T) {
|
||||
t.Errorf("compare = %+v", c)
|
||||
}
|
||||
}
|
||||
var cr grants.Credentials
|
||||
if row.live != nil {
|
||||
cr = live.Credentials
|
||||
}
|
||||
if strings.Contains(row.name, "login lost") {
|
||||
cr.LosesLogin = []string{"novox.telegram"}
|
||||
}
|
||||
// Removing, and the users with no hash in the vault, are what the yes is refused for.
|
||||
var removing []string
|
||||
for _, r := range c.Removing() {
|
||||
removing = append(removing, strings.Fields(r)[0])
|
||||
}
|
||||
removing = append(removing, cr.LosesLogin...)
|
||||
if !slices.Equal(removing, row.removing) || len(c.AddedRights()) != row.adds {
|
||||
t.Errorf("take-over refused for %v, want %v; added rights of %d users, want %d", c.Removing(), row.removing, len(c.AddedRights()), row.adds)
|
||||
}
|
||||
// The ask's words for the added rights pass the operator channel's content rule, read at a glance and fit one
|
||||
// message, and name every user whose rights are added.
|
||||
summary, points, list := c.AddedWords()
|
||||
// The ask's words pass the operator channel's content rule, read at a glance and fit one message; where only
|
||||
// rights are added they name every user whose rights are added, and where a loss stands they never say none is
|
||||
// taken away and name the users the yes would be refused for.
|
||||
summary, points, list := grants.TakeOverWords(c, cr)
|
||||
if row.removing != nil {
|
||||
if strings.Contains(summary, "takes none away") {
|
||||
t.Errorf("the ask says none is taken away with a loss: %q", summary)
|
||||
}
|
||||
for _, u := range row.removing {
|
||||
if !strings.Contains(list, u) {
|
||||
t.Errorf("the ask does not name %s, refused for: %s", u, list)
|
||||
}
|
||||
}
|
||||
}
|
||||
if row.live != nil && row.removing == nil && c.Equal() {
|
||||
t.Error("the captured comparison is equal: the old guard would not have refused it")
|
||||
}
|
||||
if row.adds > 0 {
|
||||
if r, ok := outward.Check(summary + "\n" + strings.Join(points, "\n") + "\n" + list); !ok {
|
||||
t.Errorf("the ask's words break the content rule: %s", r)
|
||||
}
|
||||
}
|
||||
if row.adds > 0 && row.removing == nil {
|
||||
expl := summary + "\n" + strings.Join(points, "\n")
|
||||
for _, text := range []string{expl, list} {
|
||||
if r, ok := outward.Check(text); !ok {
|
||||
|
||||
Reference in New Issue
Block a user