Skip to content
quarry Artifact [004da39464]
Login

Artifact [004da39464]

Artifact 004da394641491d6c160b0771c4f2f5190414b9934e0e94639c0baa9ff8543f0:


package main

import (
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/xfer"
)

// syncFront is the sync lane as serve wires it: the fronter over a real
// zit store, the forge's token store as its credentials, and the
// registrar's read gate. vault is private; jan holds o, i and y on it.
type syncFront struct {
	rg   *registrar
	base string
	home string
}

func newSyncFront(t *testing.T) syncFront {
	t.Helper()
	for _, bin := range []string{"zit", "fossil"} {
		if _, err := exec.LookPath(bin); err != nil {
			if os.Getenv("CI") == "true" {
				t.Fatalf("FAIL: %s not on PATH under CI", bin)
			}
			t.Skip(bin + " not on PATH")
		}
	}
	store := t.TempDir()
	run := func(args ...string) {
		t.Helper()
		cmd := exec.Command("zit", args...)
		cmd.Dir = store
		if out, err := cmd.CombinedOutput(); err != nil {
			t.Fatalf("zit %v: %v\n%s", args, err, out)
		}
	}
	run("init")
	if err := os.WriteFile(filepath.Join(store, "README.md"), []byte("# vault\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	run("add", "README.md")
	run("commit", "-m", "first")
	code, err := xfer.ProjectCode(store)
	if err != nil {
		t.Fatal(err)
	}
	rg := newTestRegistrar(t, 25)
	db := rg.odb
	db.SyncProjectCode = func(string) (string, bool) { return code, true }
	if err := db.AddAccount("jan", "pw-jan-12345", false); err != nil {
		t.Fatal(err)
	}
	for _, repo := range []string{"vault", "other"} {
		if err := db.AddRepo(repo, t.TempDir(), "", oltp.Private); err != nil {
			t.Fatal(err)
		}
		if err := db.SetCaps("jan", repo, "oiy"); err != nil {
			t.Fatal(err)
		}
	}
	rg.refreshPrivate()
	rg.doors = &tokenDoors{db: db}
	f := xfer.New()
	t.Cleanup(f.Close)
	f.Register("vault", store)
	f.SetAuth(db)
	f.SetLoginLimit(rg.doors)
	f.SetReadGate(rg.SyncReadGate)
	mux := http.NewServeMux()
	mux.Handle("POST /r/{repo}", f)
	srv := httptest.NewServer(mux)
	t.Cleanup(srv.Close)
	return syncFront{rg: rg, base: srv.URL + "/r/vault", home: t.TempDir()}
}

func (s syncFront) as(token string) string {
	return strings.Replace(s.base, "http://", "http://jan:"+token+"@", 1)
}

func (s syncFront) fossil(dir string, args ...string) (string, error) {
	cmd := exec.Command("fossil", args...)
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "HOME="+s.home, "FOSSIL_HOME="+s.home, "FOSSIL_USER=jan")
	out, err := cmd.CombinedOutput()
	return string(out), err
}

func (s syncFront) mint(t *testing.T, spec oltp.TokenSpec) string {
	t.Helper()
	spec.Name, spec.Scopes = "t", []string{oltp.ScopeSync}
	v, _, err := s.rg.odb.MintToken("jan", spec)
	if err != nil {
		t.Fatal(err)
	}
	return v
}

// A token's repositories and ceiling bind the sync lane's reads as they
// bind its pushes, on the Basic round and on every login card after it.
func TestATokensScopeBindsFossilReads(t *testing.T) {
	s := newSyncFront(t)
	full := s.mint(t, oltp.TokenSpec{})
	elsewhere := s.mint(t, oltp.TokenSpec{Repos: []string{"other"}})
	uvOnly := s.mint(t, oltp.TokenSpec{Ceiling: "y"})

	dir := t.TempDir()
	out, err := s.fossil(dir, "clone", "-B", "jan:"+elsewhere, s.as(elsewhere), filepath.Join(dir, "no.fossil"))
	if err == nil {
		t.Errorf("FAIL: a clone with a token naming only another repository succeeded:\n%s", out)
	} else {
		t.Log("ok: a token naming another repository cannot clone")
	}
	out, err = s.fossil(dir, "clone", "-B", "jan:"+uvOnly, s.as(uvOnly), filepath.Join(dir, "uv.fossil"))
	if err == nil {
		t.Errorf("FAIL: a clone with a token ceilinged to y succeeded:\n%s", out)
	} else {
		t.Log("ok: a token ceilinged to y cannot clone a private repository")
	}

	repo := filepath.Join(dir, "a.fossil")
	if out, err := s.fossil(dir, "clone", "-B", "jan:"+full, s.as(full), repo); err != nil {
		t.Fatalf("FAIL: a clone with the full token: %v\n%s", err, out)
	}
	// A pull from the existing clone signs a login card from the first
	// round: the card verifies (the token holds y), and the read must
	// still be the token's, not the account's.
	out, err = s.fossil(dir, "pull", s.as(uvOnly), "-R", repo, "--once")
	if err == nil {
		t.Errorf("FAIL: a pull signed by a y-only token read a private repository:\n%s", out)
	} else {
		t.Log("ok: a login card signed by a y-only token reads nothing")
	}
	if out, err := s.fossil(dir, "pull", s.as(full), "-R", repo, "--once"); err != nil {
		t.Errorf("FAIL: a pull with the full token: %v\n%s", err, out)
	}
}