package main import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "testing" "time" "brother/cmd/brother/internal/secret" ) // 1453/AC3: printer.settings logs in by the field name the form gives, sends the password from the secret // file, follows the session cookie, and returns the page's title, links and form fields; a password field's // value is never returned, and the session is logged out. The page is SYNTHETIC (testdata/admin/ // synthetic-settings.html): the real pages arrive in round 2. func TestPrinterSettingsReadsAPageBehindTheLogin(t *testing.T) { f := newFakePrinter(t, "Qw9z3", "p4ss word") _, run := printerTools(t, f, "p4ss word") got, err := run(map[string]any{"page": "/general/other.html"}) if err != nil { t.Fatal(err) } if len(f.posted) != 1 || f.posted[0].Get("Qw9z3") != "p4ss word" { t.Errorf("the login posted %v", f.posted) } raw, _ := json.Marshal(got) out := string(raw) var a struct { Title string Links []Link Forms []Form Logout string } json.Unmarshal(raw, &a) if a.Title != "Synthetic settings" { t.Errorf("title = %q", a.Title) } if len(a.Links) != 0 { t.Errorf("links in the default answer: %v", a.Links) } if len(a.Forms) != 1 || a.Forms[0].Action != "/general/other.html" || a.Forms[0].Method != "post" { t.Fatalf("forms = %+v", a.Forms) } want := map[string]Field{ "lock": {Name: "lock", Type: "checkbox", Value: "on", Label: "Scan lock"}, "node": {Name: "node", Type: "text", Value: "BRN-SYNTH", Label: "Name"}, "pw": {Name: "pw", Type: "password", Value: "", Label: "Secret"}, "note": {Name: "note", Type: "textarea", Value: "hello & bye"}, } for _, fl := range a.Forms[0].Fields { if fl.Name == "mode" { if fl.Type != "select" || fl.Value != "m" || fl.Label != "Mode" || len(fl.Options) != 2 || fl.Options[0] != (Option{Value: "a", Text: "Auto"}) || fl.Options[1] != (Option{Value: "m", Text: "Manual"}) { t.Errorf("mode = %+v", fl) } continue } w, ok := want[fl.Name] if !ok || fl.Name != w.Name || fl.Type != w.Type || fl.Value != w.Value || fl.Label != w.Label { t.Errorf("field %+v, want %+v", fl, w) } delete(want, fl.Name) } if len(want) != 0 { t.Errorf("fields missing: %v", want) } if strings.Contains(out, "shouldnotshow") { t.Errorf("a password field's value was returned: %s", out) } if a.Logout != "logged out" || f.loggedInNow() != 0 { t.Errorf("logout = %q, sessions open %d", a.Logout, f.loggedInNow()) } // Read-only: the only form posted is the login. if len(f.posted) != 1 { t.Errorf("forms posted: %v", f.posted) } } // 1453/AC3: with no page asked for, the status page is read. func TestPrinterSettingsDefaultsToTheStatusPage(t *testing.T) { f := newFakePrinter(t, "B1891", "pw") _, run := printerTools(t, f, "pw") got, err := run(nil) if err != nil { t.Fatal(err) } if got.(map[string]any)["title"] != "Synthetic status" { t.Errorf("answer = %v", got) } } // 1453/AC4: a wrong password is said as a failed login. func TestPrinterSettingsSaysAFailedLogin(t *testing.T) { f := newFakePrinter(t, "B1891", "right") _, run := printerTools(t, f, "wrong") if _, err := run(nil); err == nil || !strings.Contains(err.Error(), "login failed") { t.Fatalf("error = %v", err) } } // 1453/AC4: printer or printer-certificate not set is said by name by the tool, nothing is queued and the // printer is not contacted. func TestPrinterSettingsSaysWhichConfigIsUnsetAndQueuesNothing(t *testing.T) { f := newFakePrinter(t, "B1891", "pw") for _, c := range []struct{ name, settings, want string }{ {"no printer", `{"printer-certificate":"` + f.print() + `"}`, "printer is not set"}, {"no certificate", `{"printer":"` + f.host() + `"}`, "printer-certificate is not set"}, {"neither", `{}`, "printer and printer-certificate"}, } { r := newRig(t, c.settings, "pw") _, err := r.run(nil) if err == nil || !strings.Contains(err.Error(), c.want) { t.Errorf("%s: error = %v, want it to say %q", c.name, err, c.want) } if r.bus.asks() != 0 { t.Errorf("%s: an ask was queued", c.name) } } if f.connsNow() != 0 || len(f.requests) != 0 { t.Errorf("the printer was contacted: %d connections, %v", f.connsNow(), f.requests) } } // 1453/AC4: the secret not given is said by name, by the device process, and the printer is not contacted. func TestPrinterSettingsSaysTheSecretIsNotGivenAndContactsNothing(t *testing.T) { f := newFakePrinter(t, "B1891", "pw") _, run := printerTools(t, f, "") if _, err := run(nil); err == nil || !strings.Contains(err.Error(), "admin-password is not given") { t.Errorf("no secret file: error = %v", err) } // An empty file is not given either. r := newRig(t, printerSettingsJSON(f), "") r.worker.Password = func() (string, error) { p := filepath.Join(t.TempDir(), "e") os.WriteFile(p, []byte("\n"), 0o600) return secret.Read(p) } if _, err := r.run(nil); err == nil || !strings.Contains(err.Error(), "admin-password is not given") { t.Errorf("empty secret: error = %v", err) } if f.connsNow() != 0 || len(f.requests) != 0 { t.Errorf("the printer was contacted: %d connections, %v", f.connsNow(), f.requests) } } // 1453: a page that is not a page of the admin site is refused before anything is queued, and by the admin // process too if one reaches it. func TestPrinterSettingsRefusesAPageOutsideTheSite(t *testing.T) { f := newFakePrinter(t, "B1891", "pw") r := newRig(t, printerSettingsJSON(f), "pw") for _, p := range []string{"general/status.html", "/general/status", "//evil.example/x.html", "/a/../b.html", "/a.html?x=1", "/a b.html"} { if _, err := r.run(map[string]any{"page": p}); err == nil || !strings.Contains(err.Error(), "not a page") { t.Errorf("page %q: error = %v", p, err) } if _, err := r.worker.read(t.Context(), p, false, false); err == nil || !strings.Contains(err.Error(), "not a page") { t.Errorf("the device process, page %q: error = %v", p, err) } } if _, err := r.run(map[string]any{"page": 5}); err == nil { t.Errorf("a page that is no string was accepted") } if r.bus.asks() != 0 || f.connsNow() != 0 { t.Errorf("%d asks queued, the printer contacted %d times", r.bus.asks(), f.connsNow()) } } // 1453: nobody consuming the queue is said as the device process not answering, naming the wait. func TestPrinterSettingsSaysWhenTheAdminProcessDoesNotAnswer(t *testing.T) { f := newFakePrinter(t, "B1891", "pw") r := newRig(t, printerSettingsJSON(f), "pw") r.bus.worker = nil answerWait = 100 * time.Millisecond _, err := r.run(nil) if err == nil || !strings.Contains(err.Error(), "device process did not answer") { t.Fatalf("error = %v", err) } if r.bus.asks() != 1 { t.Errorf("%d asks queued", r.bus.asks()) } } // 1453: the device process terminates an ask it cannot read, drops one nobody waits for any more without // contacting the printer, acknowledges one already answered, and asks for redelivery when it cannot write its // answer. func TestTheAdminWorkerSettlesEachAsk(t *testing.T) { f := newFakePrinter(t, "B1891", "pw") r := newRig(t, printerSettingsJSON(f), "pw") w := r.worker if act, _ := w.Handle([]byte("not json"), nil); act != actionTerm { t.Errorf("an unreadable ask: action %v, want term", act) } old, _ := json.Marshal(Ask{ID: "old", Op: OpSettings, Page: DefaultPage, At: fixedNow.Add(-StaleAfter - time.Second)}) if act, _ := w.Handle(old, nil); act != actionAck || r.answers.get("old") != nil || f.connsNow() != 0 { t.Errorf("a stale ask: action %v, answer %s, %d connections", act, r.answers.get("old"), f.connsNow()) } fresh, _ := json.Marshal(Ask{ID: "a1", Op: OpSettings, Page: DefaultPage, At: fixedNow}) if act, _ := w.Handle(fresh, nil); act != actionAck || r.answers.get("a1") == nil { t.Errorf("a fresh ask: action %v", act) } conns := f.connsNow() if act, _ := w.Handle(fresh, nil); act != actionAck { t.Errorf("an ask already answered: action %v, want ack", act) } _ = conns r.answers.fail = fmt.Errorf("the bus is away") again, _ := json.Marshal(Ask{ID: "a2", Op: OpSettings, Page: DefaultPage, At: fixedNow}) if act, delay := w.Handle(again, nil); act != actionRetry || delay <= 0 { t.Errorf("an answer that cannot be written for want of the bus: action %v delay %v, want retry", act, delay) } } // 1514: printer.settings with no page, and with /home/status.html, logs in from the page the printer serves its // login form on (the root redirects to it) and reads that page. The printer has no /general/status.html behind // the login: the fake, like the captured printer, answers it a 404 once logged in, and a redirect to the login // page before. func TestPrinterSettingsDefaultPageAndHomeStatusAreReadAfterTheLogin(t *testing.T) { for name, args := range map[string]map[string]any{"default": {}, "explicit": {"page": "/home/status.html"}} { f := newFakePrinter(t, "B1891", "pw") _, run := printerTools(t, f, "pw") got, err := run(args) if err != nil { t.Fatalf("%s: %v", name, err) } raw, _ := json.Marshal(got) if !strings.Contains(string(raw), "Synthetic status") || !strings.Contains(string(raw), "/home/status.html") { t.Errorf("%s: answer %.200s", name, raw) } if len(f.posted) != 1 || f.posted[0].Get("loginurl") != "/home/status.html" { t.Errorf("%s: posted %v, want loginurl /home/status.html", name, f.posted) } if f.loggedInNow() != 0 { t.Errorf("%s: the session is still open", name) } } if DefaultPage != "/home/status.html" { t.Errorf("DefaultPage = %s", DefaultPage) } } // 1530: the printer's menu links are left out of the answer unless links:true is asked, and hidden fields // (the CSRF token) unless hidden:true is asked; a password field's value is never given. func TestPrinterSettingsLeavesOutLinksAndHiddenFieldsUnlessAsked(t *testing.T) { f := newFakePrinter(t, "B1891", "pw") f.pages["/scan/scanfrompc.html"] = adminFixture(t, "reconstructed-scanfrompc.html") _, run := printerTools(t, f, "pw") read := func(args map[string]any) map[string]any { t.Helper() got, err := run(args) if err != nil { t.Fatal(err) } raw, _ := json.Marshal(got) var m map[string]any json.Unmarshal(raw, &m) return m } names := func(m map[string]any) string { var n []string for _, fm := range m["forms"].([]any) { for _, fl := range fm.(map[string]any)["fields"].([]any) { n = append(n, fl.(map[string]any)["name"].(string)) } } return strings.Join(n, ",") } plain := read(map[string]any{"page": "/scan/scanfrompc.html"}) if _, has := plain["links"]; has { t.Errorf("links in the default answer: %v", plain["links"]) } if names(plain) != "B117d" { t.Errorf("default fields = %s, want B117d alone", names(plain)) } more := read(map[string]any{"page": "/scan/scanfrompc.html", "links": true, "hidden": true}) if l, _ := more["links"].([]any); len(l) != 3 { t.Errorf("links:true gave %v", more["links"]) } if names(more) != "CSRFToken,B1892,pageid,CSRFToken,postif_registration_reject,B117d" { t.Errorf("hidden:true fields = %s", names(more)) } }