Skip to content
quarry Artifact [e57e390fad]
Login

Artifact [e57e390fad]

Artifact e57e390fad5fff96065cdbddd7cf0b77379820b2bffc7400c7c9709352400242:


package main

import (
	"net/http"
	"net/http/httptest"
	"testing"

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

// A SYNC TOKEN IN THE BASIC PASSWORD SLOT IDENTIFIES THE ACCOUNT ON THE
// SYNC LANE — and nowhere else.
//
// The read gate learned the login card, and a token on the card reads a
// private repository. But no client sends a card on a clone's first
// round: it cannot sign one before the server has told it the project
// code. fossil answers the 401 with HTTP Basic; zit retries with Basic;
// both put the one credential the client holds — the token — in the
// password slot. identityCtx read it as a wrong password, so the caller
// was "identified, unrecognised", told 404, and a private repository
// stayed unclonable by the very token minted for it. jva measured all
// six combinations on 16670a54f4ca (2bb5659e): card+token 200,
// Basic+password 200, Basic+token 404. The last one is this test.
func TestSyncTokenInBasicReadsAPrivateRepository(t *testing.T) {
	rg := newTestRegistrar(t, 25)
	const pw = "pw12345678"
	if err := rg.odb.AddAccount("jan", pw, false); err != nil {
		t.Fatal(err)
	}
	if err := rg.odb.AddRepo("vault", t.TempDir(), "", oltp.Private); err != nil {
		t.Fatal(err)
	}
	if err := rg.odb.SetCaps("jan", "vault", "o"); err != nil {
		t.Fatal(err)
	}
	rg.refreshPrivate()
	token, err := rg.odb.MintSyncToken("jan")
	if err != nil {
		t.Fatal(err)
	}

	basic := func(user, secret string) *http.Request {
		req := httptest.NewRequest(http.MethodGet, "/xfer/vault", nil)
		req.SetBasicAuth(user, secret)
		return req
	}
	if !rg.SyncReadGate(basic("jan", token), "vault", "") {
		t.Error("Basic carrying the sync token was refused a read of a private repository")
	}
	if !rg.SyncReadGate(basic("jan", pw), "vault", "") {
		t.Error("Basic carrying the password stopped working")
	}
	if rg.SyncReadGate(basic("jan", "not-the-token"), "vault", "") {
		t.Error("a wrong secret in Basic was admitted")
	}
	if rg.SyncReadGate(basic("jan", ""), "vault", "") {
		t.Error("an empty secret in Basic was admitted")
	}

	// The tally's reader identity shares the gate's answer (main.go
	// wires SetIdentify to the same resolver), so a token clone is
	// recorded to jan, not to nobody.
	if got := ops.User(syncIdentityCtx(basic("jan", token), rg.odb)); got != "jan" {
		t.Errorf("sync identity for a token in Basic = %q, want jan", got)
	}

	// The API's Basic lane is untouched: the token is the sync
	// credential, not a second password.
	if got := ops.User(identityCtx(basic("jan", token), rg.odb)); got != "" {
		t.Errorf("the API lane identified %q from a sync token in Basic; only the password should", got)
	}

	// A revoked token stops reading at once, as it stops pushing.
	if err := rg.odb.RevokeSyncToken("jan"); err != nil {
		t.Fatal(err)
	}
	if rg.SyncReadGate(basic("jan", token), "vault", "") {
		t.Error("a revoked sync token still reads")
	}
}