Skip to content
quarry Artifact [556e53fddf]
Login

Artifact [556e53fddf]

Artifact 556e53fddf0c4b2e1b7d39ed8abd33f4562a90d8d97d4c8944c7f2251c4b07e0:


package oltp

import (
	"path/filepath"
	"testing"

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

// mintDB is a forge with one repository and a project code to derive
// from, which is the minimum for a push credential to exist at all.
func mintDB(t *testing.T, repo string) *DB {
	t.Helper()
	db, err := Open(filepath.Join(t.TempDir(), "oltp.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	if err := db.AddRepo(repo, filepath.Join(t.TempDir(), repo), "", "public"); err != nil {
		t.Fatal(err)
	}
	db.SyncProjectCode = func(string) (string, bool) { return "PROJECTCODE", true }
	return db
}

// THE TEST THAT WAS MISSING.
//
// The gate change that caused the outage shipped with a test asserting
// that a stranger is refused, and no test asserting that the owner is
// admitted. Both were true of the old code; only one was true of the
// new. A gate has two directions and a test that pushes on one of them
// is not a test of the gate.
//
// So: a repository whose letters demand authentication, and an account
// the letters say may check in. After signing in, that account can
// prove itself to the sync protocol.
func TestAWriterCanActuallyPush(t *testing.T) {
	db := mintDB(t, "sandbox")
	if err := db.AddAccount("jan", "hunter2hunter2", false); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("jan", "sandbox", Write); err != nil {
		t.Fatal(err)
	}
	// The repository is gated: a signed-out visitor holds no i.
	if !db.Gated("sandbox") {
		t.Fatal("premise broken: this repository should require authentication")
	}
	if !db.EffectiveCaps("jan", "sandbox").HasAny("i") {
		t.Fatal("premise broken: a Developer should hold i")
	}
	if _, ok := db.Authenticate("jan", "hunter2hunter2"); !ok {
		t.Fatal("correct password refused")
	}
	secret, ok := db.Secret("sandbox", "jan")
	if !ok {
		t.Fatal("an account the letters let check in still has no push credential: " +
			"gated repository, no way in")
	}
	if want := xfer.SharedSecret("PROJECTCODE", "jan", "hunter2hunter2"); secret != want {
		t.Errorf("stored secret is not the RFC 6.3 derivation:\n got %q\nwant %q", secret, want)
	}
}

// The other direction, still true: an account with no grant gets no
// credential, so closing the anonymous hole stays closed.
func TestAStrangerGetsNoCredential(t *testing.T) {
	db := mintDB(t, "sandbox")
	if err := db.AddAccount("mallory", "hunter2hunter2", false); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.Authenticate("mallory", "hunter2hunter2"); !ok {
		t.Fatal("correct password refused")
	}
	if _, ok := db.Secret("sandbox", "mallory"); ok {
		t.Error("an account with no grant was issued a push credential")
	}
}

// Revoking write has to take the credential away too, or push would be
// governed by a table no page displays — the disconnect that made the
// outage possible in the first place.
func TestRevokedWriterLosesTheCredential(t *testing.T) {
	db := mintDB(t, "sandbox")
	if err := db.AddAccount("jan", "hunter2hunter2", false); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("jan", "sandbox", Write); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.Authenticate("jan", "hunter2hunter2"); !ok {
		t.Fatal("correct password refused")
	}
	if _, ok := db.Secret("sandbox", "jan"); !ok {
		t.Fatal("premise broken: the writer should hold a credential")
	}
	if err := db.Revoke("jan", "sandbox"); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.Authenticate("jan", "hunter2hunter2"); !ok {
		t.Fatal("correct password refused")
	}
	if _, ok := db.Secret("sandbox", "jan"); ok {
		t.Error("a revoked writer kept a working push credential")
	}
}

// A new password must not leave the old push secret working, since the
// secret is derived from the password and changing it is how somebody
// answers a credential they think is compromised.
func TestChangingThePasswordMovesTheCredential(t *testing.T) {
	db := mintDB(t, "sandbox")
	if err := db.AddAccount("jan", "hunter2hunter2", false); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("jan", "sandbox", Write); err != nil {
		t.Fatal(err)
	}
	before, _ := db.Secret("sandbox", "jan")
	if err := db.SetPassword("jan", "a different password"); err != nil {
		t.Fatal(err)
	}
	after, ok := db.Secret("sandbox", "jan")
	if !ok {
		t.Fatal("the account lost its credential entirely on a password change")
	}
	if after == before {
		t.Error("the old push secret still works after a password change")
	}
	if want := xfer.SharedSecret("PROJECTCODE", "jan", "a different password"); after != want {
		t.Errorf("credential does not match the new password:\n got %q\nwant %q", after, want)
	}
}

// A DB with no project-code source mints nothing and, above all, does
// not fail a sign-in. Every test DB and every CLI path is this shape.
func TestNoProjectCodeSourceIsHarmless(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("jan", "hunter2hunter2", false); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.Authenticate("jan", "hunter2hunter2"); !ok {
		t.Error("a forge with no project-code source could not sign anybody in")
	}
}