Skip to content
quarry Artifact [d1d93efcd5]
Login

Artifact [d1d93efcd5]

Artifact d1d93efcd56425722c361d555fb412ba0441e774fe49633babe3f8daf89d60ce:


package xfer

import (
	"bytes"
	"crypto/sha1"
	"encoding/hex"
	"net/http"
	"net/http/httptest"
	"os/exec"
	"strings"
	"testing"
)

// onboardAuth: jan holds the letter and a credential; kim is an account
// with neither letter nor token; lee holds the letter and no token; pat
// holds a token that signs no push and no letter; dis is disabled with
// a token; nobody else is known.
type onboardAuth struct{ secret, patSecret string }

func (onboardAuth) Gated(string) bool            { return true }
func (onboardAuth) MayWrite(_, user string) bool { return user == "jan" || user == "lee" }
func (a onboardAuth) Secrets(_, user string) []Credential {
	if user != "jan" {
		return nil
	}
	return []Credential{{Secret: a.secret, CheckIn: true, Publish: true}}
}
func (a onboardAuth) LetterlessSecrets(_, user string) []Credential {
	if user != "pat" {
		return nil
	}
	return []Credential{{Secret: a.patSecret}}
}

// pushAs sends a push signed with secret as user and returns the
// refusal the client reads, with the user's name taken out.
func pushAs(t *testing.T, f *Fronter, user, secret string) string {
	t.Helper()
	blob := []byte("a pushed file\n")
	covered := "push PROJ PROJ\n" + pushedFile(blob)
	nonce := sha1.Sum([]byte(covered))
	n := hex.EncodeToString(nonce[:])
	sig := sha1.Sum([]byte(n + secret))
	body := "login " + user + " " + n + " " + hex.EncodeToString(sig[:]) + "\n" + covered
	req := httptest.NewRequest(http.MethodPost, "/r/alpha", bytes.NewReader(encodeContainer([]byte(body))))
	req.SetPathValue("repo", "alpha")
	req.Header.Set("Content-Type", "application/x-fossil")
	w := httptest.NewRecorder()
	f.ServeHTTP(w, req)
	reply := string(decodeContainer(w.Body.Bytes()))
	if !strings.HasPrefix(reply, "error ") {
		t.Fatalf("FAIL: a push as %s was not refused: %q", user, reply)
	}
	return strings.ReplaceAll(reply, user, "NAME")
}

// A card that did not verify is told one thing whoever it names: that
// the credential did not verify. An unknown name, an account without
// the letter, one with the letter and no token, a disabled one and a
// real one with a wrong signature all read byte for byte alike, or one
// push per name would list the accounts. Only a card that VERIFIES
// against a credential that may not push is owed what is missing.
func TestAnUnverifiedPushIsRefusedTheSameWhoeverItNames(t *testing.T) {
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	origin := t.TempDir()
	zrun(t, origin, "init")
	f := New()
	t.Cleanup(f.Close)
	f.Register("alpha", origin)
	f.SetAuth(onboardAuth{secret: "right", patSecret: "pat-token"})

	want := pushAs(t, f, "jan", "wrong")
	if !strings.Contains(want, "did\\snot\\sverify") {
		t.Fatalf("FAIL: the wrong-credential refusal changed: %q", want)
	}
	for _, name := range []string{"zoot", "kim", "lee", "dis", "pat"} {
		if got := pushAs(t, f, name, "not-their-secret"); got != want {
			t.Errorf("FAIL: an unverified card naming %s reads differently:\n  %q\n  %q", name, got, want)
		}
	}
	// A card that verifies against the letterless token has proved pat,
	// and is told the capability half.
	if got := pushAs(t, f, "pat", "pat-token"); !strings.Contains(got, "check-in") || got == want {
		t.Errorf("FAIL: a verified account without the letter was not told: %q", got)
	}
	t.Log("ok")
}