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")
}