Every value an approval sets is in the note, whole where the sender is proven; the note is kept whole (issue 383)
mesh/merge-gate out of date: judged against trunk e209d46faf3d, the trunk is now 9c166398f204; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk e209d46faf3d, the trunk is now 9c166398f204; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate out of date: judged against trunk e209d46faf3d, the trunk is now 9c166398f204; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk e209d46faf3d, the trunk is now 9c166398f204; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
The review of mesh-controller #304: a proposal of more than three keys showed only the names of the rest, and its Details could carry no value, so Approve bound values the operator never saw. The router now shows an ask's WholeDetails in the note in place of its Details on a kind that proves who answers, held to the secret rule alone, and refuses an ask whose whole details carry a secret's shape. Telegram keeps the note whole when a message is too long, cutting the body first and saying so, and escapes the note as every other part. SDK at mesh-sdk f474544.
This commit is contained in:
@@ -136,7 +136,9 @@ condition's message. A warning kept quiet is also said once after a day.
|
||||
explanation alone — a summary, then at most three points (`asks.Glance`; an ask that does not hold to it is shown
|
||||
and said in the log). Everything else is the message's note (`asks.Message.Note`), which a channel shows apart and
|
||||
smaller: who asked and when, with what happens if nobody answers; what each answer does; where else it is answered;
|
||||
and the asker's Details (who proposed it, a fingerprint, how to read it whole). Answered, the message is one line —
|
||||
and the asker's Details (who proposed it, a fingerprint, how to read it whole) — on a kind that proves who answers
|
||||
and was given the ask's answers, its Details whole (`asks.Ask.WholeDetails`): every value an approval sets, held to
|
||||
the secret rule alone, as the whole words are. Answered, the message is one line —
|
||||
"You chose Approve at 16:06 on Telegram · <headline>" — and the router's word on the asker is its note.
|
||||
|
||||
**An ask's values are shown whole where the sender is proven** (hq issue 383). An ask may carry its explanation
|
||||
|
||||
@@ -172,22 +172,39 @@ func (h *Holder) openAsk(r *Record) *AskRecord {
|
||||
// rule, is shown. The ask's state does not matter: a message edited to say how the ask ended still shows what
|
||||
// was approved.
|
||||
func (h *Holder) wholeOf(r *Record, kind string) string {
|
||||
if a := h.provenAsk(r, kind); a != nil {
|
||||
return a.Whole
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// wholeDetailsOf is an ask's Details with their values whole (asks.Ask.WholeDetails), shown in the note in place of
|
||||
// its Details where wholeOf would show its whole words: every value an approval sets, so the operator reads exactly
|
||||
// what they approve (the review of mesh-controller #304). Empty elsewhere.
|
||||
func (h *Holder) wholeDetailsOf(r *Record, kind string) string {
|
||||
if a := h.provenAsk(r, kind); a != nil {
|
||||
return a.WholeDetails
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// provenAsk is the ask of an ask's own record when the kind proves who answers and was given the ask's answers;
|
||||
// nil otherwise.
|
||||
func (h *Holder) provenAsk(r *Record, kind string) *asks.Ask {
|
||||
if r == nil || r.Origin != "ask" || r.Ask == "" || kind == "" || h.Bench == nil {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
e := h.askOf(r.Ask)
|
||||
if e == nil || e.rec.Ask.Whole == "" {
|
||||
return ""
|
||||
}
|
||||
if !h.Bench.Promises(kind, "verified-sender") {
|
||||
return ""
|
||||
if e == nil || !h.Bench.Promises(kind, "verified-sender") {
|
||||
return nil
|
||||
}
|
||||
for _, tk := range e.rec.Tickets {
|
||||
if tk.Kind == kind {
|
||||
return e.rec.Ask.Whole
|
||||
a := e.rec.Ask
|
||||
return &a
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// carriers are the kinds that can carry an answer of a level now: ready, offering a choice, and for
|
||||
@@ -474,7 +491,7 @@ func (h *Holder) TakeAsk(verb, asker string, body json.RawMessage) error {
|
||||
// The whole words are shown only where the sender is proven (wholeOf), so a path or an address may stand
|
||||
// in them; a secret's shape leaves the mesh nowhere, and an ask carrying one is refused as a whole, never
|
||||
// shown with its explanation alone (novox/hq issue 383).
|
||||
if refusal, ok := CheckSecret(a.Whole); !ok {
|
||||
if refusal, ok := CheckSecret(a.Whole + "\n" + a.WholeDetails); !ok {
|
||||
h.refuseAsk(asker, a, "its whole words carry "+refusal.String()+", which may not leave the mesh on any channel: withhold it")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -885,11 +885,17 @@ func (h *Holder) sayOn(r *Record, what, kind string) Message {
|
||||
note = append(note, line)
|
||||
}
|
||||
}
|
||||
for _, line := range strings.Split(strings.TrimSpace(rec.Ask.Details), "\n") {
|
||||
// On a kind that proves who answers, the Details whole (issue 383): every value an approval sets, held to
|
||||
// the secret rule alone, as the whole words are.
|
||||
details, passes := rec.Ask.Details, Check
|
||||
if wd := h.wholeDetailsOf(r, kind); wd != "" {
|
||||
details, passes = wd, CheckSecret
|
||||
}
|
||||
for _, line := range strings.Split(strings.TrimSpace(details), "\n") {
|
||||
if line = strings.TrimSpace(line); line == "" {
|
||||
continue
|
||||
}
|
||||
if refusal, ok := Check(line); !ok {
|
||||
if refusal, ok := passes(line); !ok {
|
||||
line = "(a line carrying " + refusal.What + " is withheld here)"
|
||||
}
|
||||
note = append(note, line)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -153,3 +154,57 @@ func TestAnAskWhoseWholeWordsCarryASecretIsRefused(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The review of mesh-controller #304: a proposal of five keys, one of them 300 characters long, shows two keys at
|
||||
// a glance; every key and every value, whole, is in the note on a kind that proves who answers, so the operator can
|
||||
// read exactly what Approve sets. The desk, which proves nothing, shows them masked.
|
||||
func TestEveryKeyAndItsWholeValueIsInTheNoteWhereTheSenderIsProven(t *testing.T) {
|
||||
h, tg, _, c, store, _ := newRouter(t)
|
||||
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
||||
var long strings.Builder
|
||||
long.WriteString("library=/storage")
|
||||
for i := 0; long.Len() < 300; i++ {
|
||||
long.WriteString(fmt.Sprintf("/album-%02d", i))
|
||||
}
|
||||
value := long.String()[:300]
|
||||
keys := map[string]string{"a": "1", "b": "2", "c": "3", "d": "4", "shares": value}
|
||||
masked := map[string]string{"a": "1", "b": "2", "c": "3", "d": "4", "shares": "library=‹path›"}
|
||||
lines := func(values map[string]string) string {
|
||||
var out []string
|
||||
for _, k := range []string{"a", "b", "c", "d", "shares"} {
|
||||
out = append(out, "+ "+k+": "+values[k])
|
||||
}
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
a := proposalAsk(c, "s-9")
|
||||
a.Explanation = "Changes 5 setting(s) of mounts on shanks.\n+ a: 1\n+ b: 2\nand 3 more, each whole below: c, d, shares"
|
||||
a.Whole = ""
|
||||
a.Details = "Fingerprint 1234 5678 9abc def0 1234 5678.\n" + lines(masked)
|
||||
a.WholeDetails = "Fingerprint 1234 5678 9abc def0 1234 5678.\n" + lines(keys)
|
||||
submit(t, h, "mesh-controller", a)
|
||||
settle(h, c)
|
||||
if len(tg.sends) != 1 {
|
||||
t.Fatalf("telegram got %d messages", len(tg.sends))
|
||||
}
|
||||
phone := tg.sends[0]
|
||||
for k, v := range keys {
|
||||
if !strings.Contains(phone.Note, "+ "+k+": "+v) {
|
||||
t.Errorf("the phone's note lacks %s whole:\n%s", k, phone.Note)
|
||||
}
|
||||
}
|
||||
if strings.Contains(phone.Body, "/storage") || strings.Count(phone.Body, "\n") > 3 {
|
||||
t.Errorf("the body is not the glance:\n%s", phone.Body)
|
||||
}
|
||||
desk := h.sayOn(h.open["ask.mesh-controller.s-9"], "raised", "desktop")
|
||||
if strings.Contains(desk.Text(), "/storage") || !strings.Contains(desk.Note, "+ shares: library=‹path›") {
|
||||
t.Errorf("the desk shows a value whole, or not every key:\n%s", desk.Note)
|
||||
}
|
||||
// A secret's shape in the whole details refuses the ask as a whole.
|
||||
b := proposalAsk(c, "s-10")
|
||||
b.WholeDetails = "+ token: ghp_abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
submit(t, h, "mesh-controller", b)
|
||||
settle(h, c)
|
||||
if _, kept := store.asks["mesh-controller.s-10"]; kept || len(tg.sends) != 1 {
|
||||
t.Errorf("an ask whose whole details carry a secret was taken")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ module messenger
|
||||
|
||||
go 1.22
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d
|
||||
|
||||
@@ -2,3 +2,5 @@ git.novox.be/novox/mesh-sdk/go v0.1.13 h1:Su4JYpZ+zhNovkGA2DgxiZdcuHpjw2tPJzc/7P
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.13/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629 h1:7TYFV1Q6Mcz+lsLP/sds87tysET4rKCn3rRIQV8gNmA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d h1:HEJyqorKktrkJPeeJdwEo0kdrJjH3fm/YVS9MGftCqY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
|
||||
Reference in New Issue
Block a user