Skip to content
quarry Artifact [c5052143e5]
Login

Artifact [c5052143e5]

Artifact c5052143e530b9dfb8c09adc68d10bd32c714bc46402ce0f5ef224cb811bfcfd:


package oltp

import (
	"errors"
	"strings"
	"testing"
)

const fmRemote = "https://fossil.example.org/proj"

// The password is sealed at rest, kept when a later change leaves it
// blank, never reaches the settings log, and disabling deletes it. Each
// step is logged with its actor and via.
func TestAFossilMirrorKeepsItsPasswordSealedAndLogsEachChange(t *testing.T) {
	db := heldDB(t)
	const pw = "hunter2-unique-pw"
	if _, err := db.SetFossilMirror("proj", fmRemote, "pusher", "", "alice", ""); !errors.Is(err, ErrMirrorRefused) {
		t.Fatalf("FAIL: a first enable without a password: %v", err)
	}
	m, err := db.SetFossilMirror("proj", fmRemote, "pusher", pw, "alice", "TK1")
	if err != nil {
		t.Fatal(err)
	}
	if m.Remote != fmRemote || m.User != "pusher" {
		t.Errorf("FAIL: the mirror is %+v", m)
	}
	var sealed string
	if err := db.db.QueryRow(`SELECT pass_sealed FROM fossil_mirror WHERE repo = 'proj'`).Scan(&sealed); err != nil {
		t.Fatal(err)
	}
	if sealed == "" || strings.Contains(sealed, pw) {
		t.Fatal("FAIL: the password is stored readable")
	}
	if got, err := db.FossilMirrorPassword("proj", m.Gen); err != nil || got != pw {
		t.Fatalf("FAIL: the sealed password does not open: %v", err)
	}

	// Blank keeps it.
	if _, err := db.SetFossilMirror("proj", fmRemote, "pusher", "", "alice", ""); err != nil {
		t.Fatalf("FAIL: a blank password on an unchanged mirror: %v", err)
	}
	cur, _, _ := db.FossilMirrorOf("proj")
	if got, _ := db.FossilMirrorPassword("proj", cur.Gen); got != pw {
		t.Error("FAIL: a blank password replaced the stored one")
	}
	// A new destination needs its own.
	if _, err := db.SetFossilMirror("proj", "https://other.example.org/proj", "pusher", "", "alice", ""); !errors.Is(err, ErrMirrorRefused) {
		t.Errorf("FAIL: a new remote kept the old password: %v", err)
	}
	if _, err := db.SetFossilMirror("proj", fmRemote, "someone", "", "alice", ""); !errors.Is(err, ErrMirrorRefused) {
		t.Errorf("FAIL: a new user kept the old password: %v", err)
	}
	const pw2 = "second-unique-pw"
	if _, err := db.SetFossilMirror("proj", fmRemote, "pusher", pw2, "alice", ""); err != nil {
		t.Fatal(err)
	}
	cur, _, _ = db.FossilMirrorOf("proj")
	if got, _ := db.FossilMirrorPassword("proj", cur.Gen); got != pw2 {
		t.Error("FAIL: a new password was not kept")
	}

	if on, err := db.DisableFossilMirror("proj", "bob", ""); err != nil || !on {
		t.Fatalf("FAIL: disable: %v %v", on, err)
	}
	if _, ok, _ := db.FossilMirrorOf("proj"); ok {
		t.Error("FAIL: the mirror outlived its disable")
	}
	var left int
	db.db.QueryRow(`SELECT count(*) FROM fossil_mirror`).Scan(&left)
	if left != 0 {
		t.Error("FAIL: the password outlived the disable")
	}
	if on, err := db.DisableFossilMirror("proj", "bob", ""); err != nil || on {
		t.Errorf("FAIL: disabling nothing: %v %v", on, err)
	}

	log, err := db.SettingsLog("proj", 20)
	if err != nil {
		t.Fatal(err)
	}
	got := map[string]int{}
	for _, c := range log {
		for _, v := range []string{c.Was, c.Now} {
			if strings.Contains(v, pw) || strings.Contains(v, pw2) {
				t.Fatalf("FAIL: the password is in the settings log: %+v", c)
			}
		}
		got[c.Field+" "+c.Was+">"+c.Now+" "+c.Actor+" "+c.Via]++
	}
	for _, want := range []string{
		"fossil-mirror off>on alice TK1",
		"fossil-mirror-remote >" + fmRemote + " alice TK1",
		"fossil-mirror-user >pusher alice TK1",
		"fossil-mirror-password set>changed alice ",
		"fossil-mirror on>off bob ",
	} {
		if got[want] != 1 {
			t.Errorf("FAIL: settings log lacks %q (has %v)", want, got)
		}
	}
	if len(log) != 5 {
		t.Errorf("FAIL: %d log rows, want 5", len(log))
	}
	t.Log("ok")
}

// A private repository and a taken-down one cannot be mirrored.
func TestAFossilMirrorIsRefusedWherePublishingIsNot(t *testing.T) {
	db := heldDB(t)
	if err := db.AddRepo("secret", t.TempDir(), "", Private); err != nil {
		t.Fatal(err)
	}
	if _, err := db.SetFossilMirror("secret", fmRemote, "u", "p", "alice", ""); !errors.Is(err, ErrMirrorRefused) {
		t.Errorf("FAIL: private: %v", err)
	}
	if err := db.SetTakedown(Takedown{Repo: "proj", Strength: Disabled, Category: "copyright", Actor: "dmestas"}); err != nil {
		t.Fatal(err)
	}
	if _, err := db.SetFossilMirror("proj", fmRemote, "u", "p", "alice", ""); !errors.Is(err, ErrHeld) {
		t.Errorf("FAIL: taken down: %v", err)
	}
	var n int
	db.db.QueryRow(`SELECT count(*) FROM fossil_mirror`).Scan(&n)
	if n != 0 {
		t.Errorf("FAIL: %d mirrors kept after refusals", n)
	}
}

func TestOnlyAnHTTPSRemoteWithNothingInItButAHostAndPathIsKept(t *testing.T) {
	for _, ok := range []string{
		"https://fossil.craftdesign.group/quarry",
		"https://fossil.example.org:8443/a/b",
		"https://fossil.example.org/",
		// The forge's one outbound rule lets http reach this machine.
		"http://127.0.0.1:8080/proj",
	} {
		if err := CheckFossilRemote(ok); err != nil {
			t.Errorf("FAIL: %q refused: %v", ok, err)
		}
	}
	for _, bad := range []string{
		"", "http://fossil.example.org/x", "http://10.0.0.5/x", "ssh://[email protected]/x", "fossil.example.org/x",
		"https://[email protected]/x", "https://user:[email protected]/x",
		"https://fossil.example.org/x?a=b", "https://fossil.example.org/x#f", "https://",
		"https://fossil.example.org:0/x", "https://fossil.example.org:99999/x",
		"https://fossil.example.org/x y", "https://-oProxy.example.org/x", "-https://fossil.example.org", "file:///tmp/x",
		"https://" + strings.Repeat("a", 300) + ".example.org/x",
	} {
		if err := CheckFossilRemote(bad); !errors.Is(err, ErrMirrorRefused) {
			t.Errorf("FAIL: %q kept: %v", bad, err)
		}
	}
	for _, bad := range []string{"", "-x", "a b", "a:b", strings.Repeat("a", 65)} {
		if err := checkFossilUser(bad); !errors.Is(err, ErrMirrorRefused) {
			t.Errorf("FAIL: user %q kept: %v", bad, err)
		}
	}
	t.Log("ok")
}

// A push carries the generation it read. After a password-only change,
// a remote change, or the mirror being turned off and on, its password
// read is refused and its outcome, success or failure, is not recorded.
func TestAFossilPushOfAnOldConfigurationReadsAndRecordsNothing(t *testing.T) {
	changes := map[string]func(db *DB) error{
		"a password-only change": func(db *DB) error {
			_, err := db.SetFossilMirror("proj", fmRemote, "u", "new-pw", "alice", "")
			return err
		},
		"a remote change": func(db *DB) error {
			_, err := db.SetFossilMirror("proj", "https://other.example.org/proj", "u", "new-pw", "alice", "")
			return err
		},
		"a user change": func(db *DB) error {
			_, err := db.SetFossilMirror("proj", fmRemote, "v", "new-pw", "alice", "")
			return err
		},
		"off and on again": func(db *DB) error {
			if _, err := db.DisableFossilMirror("proj", "alice", ""); err != nil {
				return err
			}
			_, err := db.SetFossilMirror("proj", fmRemote, "u", "old-pw", "alice", "")
			return err
		},
	}
	for name, change := range changes {
		db := heldDB(t)
		old, err := db.SetFossilMirror("proj", fmRemote, "u", "old-pw", "alice", "")
		if err != nil {
			t.Fatal(err)
		}
		if got, err := db.FossilMirrorPassword("proj", old.Gen); err != nil || got != "old-pw" {
			t.Fatalf("FAIL: %s: the current generation's password: %q %v", name, got, err)
		}
		if err := change(db); err != nil {
			t.Fatal(err)
		}
		cur, _, _ := db.FossilMirrorOf("proj")
		if cur.Gen == old.Gen {
			t.Fatalf("FAIL: %s kept the generation", name)
		}
		if _, err := db.FossilMirrorPassword("proj", old.Gen); !errors.Is(err, ErrFossilMirrorChanged) {
			t.Errorf("FAIL: %s: a stale password read: %v", name, err)
		}
		if err := db.RecordFossilMirrorPush("proj", old.Gen, "abc"); err != nil {
			t.Fatal(err)
		}
		if err := db.RecordFossilMirrorFailure("proj", old.Gen, "no"); err != nil {
			t.Fatal(err)
		}
		if m, _, _ := db.FossilMirrorOf("proj"); m.PushedAt != "" || m.Err != "" || m.Fails != 0 {
			t.Errorf("FAIL: %s: a stale outcome landed: %+v", name, m)
		}
		if _, err := db.FossilMirrorPassword("proj", cur.Gen); err != nil {
			t.Errorf("FAIL: %s: the new generation's read: %v", name, err)
		}
		if err := db.RecordFossilMirrorFailure("proj", cur.Gen, "no"); err != nil {
			t.Fatal(err)
		}
		if m, _, _ := db.FossilMirrorOf("proj"); m.Err != "no" || m.Fails != 1 {
			t.Errorf("FAIL: %s: its own failure did not land: %+v", name, m)
		}
		if err := db.RecordFossilMirrorPush("proj", cur.Gen, "abc"); err != nil {
			t.Fatal(err)
		}
		if m, _, _ := db.FossilMirrorOf("proj"); m.Pushed != "abc" || m.Err != "" || m.Fails != 0 {
			t.Errorf("FAIL: %s: its own success did not land: %+v", name, m)
		}
	}
	// A save that changes nothing keeps the generation.
	db := heldDB(t)
	a, _ := db.SetFossilMirror("proj", fmRemote, "u", "pw", "alice", "")
	b, _ := db.SetFossilMirror("proj", fmRemote, "u", "", "alice", "")
	if a.Gen != b.Gen {
		t.Error("FAIL: an unchanged save bumped the generation")
	}
	t.Log("ok")
}