Files
Privatae e4c9b69b46
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s
bastion 0.53.2
2026-10-20 04:42:14 +00:00

244 lines
8.6 KiB
Go

package main
// THE DEFAULT IS YOUR OWN FILES; a full drive map is a request.
//
// A map of the whole machine reaches into every account on it. On a shared box
// that is somebody else's private life drawn as rectangles, and it is a bigger
// act than a button labelled "Map" implies. So the default walk is the home
// directory of whoever this process runs as, the whole machine is asked for
// separately, and the page names what that will cover BEFORE it is pressed.
//
// The tests below are about which walk actually happens. Wording is asserted in
// exactly one place — the flash, where the words ARE the mechanism, because
// they are what tells the person which of the two they just started.
import (
"context"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// dmScopeRecorder installs a state whose scan records the roots it was handed
// and never walks anything.
func dmScopeRecorder(t *testing.T) (*dmState, func() []string) {
t.Helper()
s := dmTestInstall(t, nil)
done := make(chan []string, 8)
s.roots = dmRootsFor // the REAL resolver: the scope must pick the roots
s.scan = func(_ context.Context, roots []string, _ int, _ func(dmProgress)) *dmResult {
done <- append([]string(nil), roots...)
return &dmResult{TakenAt: uiNow()}
}
return s, func() []string {
t.Helper()
// The walk runs in a goroutine, so this has to WAIT for it. The first
// version read the channel with a default branch and reported "no walk
// was started" every time — a test that fails for its own reasons
// teaches nothing about the code.
select {
case r := <-done:
return r
case <-time.After(2 * time.Second):
t.Fatal("no walk was started at all")
return nil
}
}
}
// Pressing the ordinary button walks the home directory and nothing else. This
// is the whole point: it fails if the default ever widens back to the volumes.
func TestTheDefaultMapIsYourFilesAndNotTheMachine(t *testing.T) {
// AFTER the fixture, never before: uiTestConfigDir moves HOME to a temp
// directory, so a home read first is not the home the walk will resolve.
// Reading it in the wrong order is how this test first failed against
// perfectly correct code.
uiTestConfigDir(t)
home, err := os.UserHomeDir()
if err != nil || strings.TrimSpace(home) == "" {
t.Skip("this account has no home directory, so there is no default to check")
}
home = filepath.Clean(home)
s, walked := dmScopeRecorder(t)
if !s.dmStart(dmScopeHome) {
t.Fatal("the scan did not start")
}
roots := walked()
if len(roots) != 1 || roots[0] != home {
t.Fatalf("the default walk covered %v, want exactly [%s]. A default that "+
"reaches past the person's own files reads every account on this machine, "+
"which is the thing this scope exists to stop.", roots, home)
}
// And the volumes really are wider, so the assertion above is not passing
// because both scopes happen to be the same thing.
machine, _ := dmMachineRoots()
if len(machine) == 1 && machine[0] == home {
t.Skip("on this box the machine scope IS the home directory, so the two " +
"cannot be told apart here")
}
for _, m := range machine {
if m == home {
continue
}
for _, r := range roots {
if r == m {
t.Errorf("the default walk included %q, which belongs to the machine scope", m)
}
}
}
}
// The whole machine is reachable — this is a scope, not a ban.
func TestTheWholeMachineIsStillReachableWhenAskedForByName(t *testing.T) {
uiTestConfigDir(t)
s, walked := dmScopeRecorder(t)
if !s.dmStart(dmScopeMachine) {
t.Fatal("the scan did not start")
}
want, _ := dmMachineRoots()
got := walked()
if len(got) != len(want) {
t.Fatalf("the machine walk covered %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("the machine walk covered %v, want %v", got, want)
}
}
}
// A VALUE NOBODY RECOGNISES RESOLVES TO THE SMALLER WALK. The failure mode of a
// typo, a stale bookmark or a hand-written form must never be the one that
// reads other people's files.
func TestAnUnknownScopeIsTheSmallerWalk(t *testing.T) {
for _, in := range []string{"", " ", "everything", "MACHINE", "Machine", "all",
"home", "machines", "machine,home", "*"} {
if got := dmScopeOf(in); got != dmScopeHome {
t.Errorf("dmScopeOf(%q) = %q, want %q — a value nobody recognises must fall "+
"to the smaller walk", in, got, dmScopeHome)
}
}
// Surrounding whitespace is normalised, the way every other form value in
// this UI is. That can only ever turn the word into itself, never something
// else into the word, so it widens nothing.
for _, in := range []string{"machine", " machine", "machine ", " machine "} {
if got := dmScopeOf(in); got != dmScopeMachine {
t.Errorf("dmScopeOf(%q) = %q — the explicit request must still work", in, got)
}
}
}
// The same rule through the HTTP handler, which is where a real request arrives.
func TestTheScanRouteDefaultsToYourFilesAndTakesTheExplicitRequest(t *testing.T) {
for _, c := range []struct {
form url.Values
want dmScope
says string
}{
{url.Values{}, dmScopeHome, "Mapping your files"},
{url.Values{"scope": {"home"}}, dmScopeHome, "Mapping your files"},
{url.Values{"scope": {"nonsense"}}, dmScopeHome, "Mapping your files"},
{url.Values{"scope": {"machine"}}, dmScopeMachine, "Mapping this whole machine"},
} {
t.Run(c.form.Encode(), func(t *testing.T) {
uiTestConfigDir(t)
s := dmTestInstall(t, nil)
release := make(chan struct{})
t.Cleanup(func() { close(release) })
s.scan = func(context.Context, []string, int, func(dmProgress)) *dmResult {
<-release
return &dmResult{TakenAt: uiNow()}
}
u := uiTestServer(t)
w := uiPost(t, u.handler(), "/diskmap/scan", c.form, uiSignedIn(t, u))
s.mu.Lock()
got := s.scope
s.mu.Unlock()
if got != c.want {
t.Errorf("scope=%q started a %q walk, want %q", c.form.Get("scope"), got, c.want)
}
// The person has to be TOLD which one is running. These two walks
// differ in what they read, and a progress view that says the same
// thing for both is how somebody discovers afterwards that they
// mapped a machine they meant to map a folder of.
if !strings.Contains(w.Body.String(), c.says) {
t.Errorf("the page does not say %q: %q", c.says, dmSnip(w.Body.String(), "MAPPING"))
}
})
}
}
// A HELD MAP CARRIES ITS OWN SCOPE, stamped from what was asked for rather than
// from the walker. A map labelled "your files" that quietly held the whole
// machine would be the worst thing this page could say.
func TestAFinishedMapSaysWhichWalkMadeIt(t *testing.T) {
uiTestConfigDir(t)
s := dmTestInstall(t, nil)
// A walker that lies about its scope, because a stubbed or future walker is
// exactly the case the stamping exists to survive.
s.scan = func(context.Context, []string, int, func(dmProgress)) *dmResult {
return &dmResult{TakenAt: uiNow(), Scope: dmScopeHome}
}
if !s.dmStart(dmScopeMachine) {
t.Fatal("the scan did not start")
}
deadline := time.Now().Add(2 * time.Second)
for {
s.mu.Lock()
done := s.res != nil && !s.running
s.mu.Unlock()
if done {
break
}
if time.Now().After(deadline) {
t.Fatal("the scan never produced a map")
}
time.Sleep(2 * time.Millisecond)
}
s.mu.Lock()
got := s.res.Scope
s.mu.Unlock()
if got != dmScopeMachine {
t.Errorf("the finished map says it covers %q when the whole machine was asked "+
"for. The scope is stamped by dmStart precisely so the walker cannot "+
"mislabel it.", got)
}
}
// The whole-machine offer NAMES what it would walk before it is pressed. A
// single click has to say what it covers, or it is blind.
func TestTheWholeMachineOfferSaysWhatItWouldCover(t *testing.T) {
uiTestConfigDir(t)
s := dmTestInstall(t, nil)
s.roots = dmRootsFor
u := uiTestServer(t)
body := dmRender(t, u, uiSignedIn(t, u), "")
if !strings.Contains(body, `value="machine"`) {
t.Fatal("there is no way to ask for the whole machine at all — the scope is a " +
"request, not a ban")
}
machine, _ := dmMachineRoots()
if len(machine) == 0 {
t.Skip("this box reports no volume roots, so there is nothing to name")
}
for _, m := range machine {
// THE LIST ITEM, not the bare path. On a unix box the only volume root
// is "/", which is a substring of nearly every line of HTML on the
// page — so `strings.Contains(body, m)` passed with the whole listing
// deleted. Measured: the mutation applied, every render of
// .MachineRoots was gone, and the test still went green.
if !strings.Contains(body, "<li>"+m+"</li>") {
t.Errorf("the whole-machine offer does not name %q in its list. It walks "+
"other people's folders, so what it covers is said before it is pressed "+
"and not after.", m)
}
}
}