Skip to content
quarry Artifact [e507167a6e]
Login

Artifact [e507167a6e]

Artifact e507167a6e2df99e8527fa4d3bb10823c127ea249ef80877785224268feffe66:


package oltp

import (
	"path/filepath"
	"strings"
	"testing"
	"time"
)

// agentRig is a person, their agent, a stranger and two repositories:
// one public, one private, with the person holding what caps says on
// each.
func agentRig(t *testing.T) *DB {
	t.Helper()
	db := testDB(t)
	for _, who := range []string{"dmestas", "jva"} {
		if err := db.AddAccount(who, "a-long-enough-password", false); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.AddAccount("root", "a-long-enough-password", true); err != nil {
		t.Fatal(err)
	}
	if err := db.NewAgent("dmestas-coder", "dmestas", -1); err != nil {
		t.Fatal(err)
	}
	// RFC 0021's rules are about the strings: the rig's agent holds its
	// strings alone, as every agent made before RFC 0026 does.
	// inherit_test.go covers the switch.
	if err := db.SetInherit("dmestas", "dmestas-coder", false); err != nil {
		t.Fatal(err)
	}
	for _, r := range []string{"pub", "priv"} {
		if err := db.AddRepo(r, t.TempDir(), "", Public); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.SetRepoVisibility("priv", Private); err != nil {
		t.Fatal(err)
	}
	return db
}

func letters(c CapSet) string {
	var b strings.Builder
	for _, l := range capLetters {
		if c.HasAny(string(l)) {
			b.WriteRune(l)
		}
	}
	return b.String()
}

// The ceiling: for every (agent grant, operator set) over the presets
// the agent resolves to the intersection plus what every visitor
// holds, with nothing stored.
func TestAgentResolvesToOperatorIntersection(t *testing.T) {
	db := agentRig(t)
	presets := []Role{NoRole, Read, Write, Maintain, RepoAdmin}
	for _, og := range presets {
		for _, ag := range presets {
			if err := db.Grant("dmestas", "pub", og); err != nil {
				t.Fatal(err)
			}
			if err := db.Grant("dmestas-coder", "pub", ag); err != nil {
				t.Fatal(err)
			}
			op := db.EffectiveCaps("dmestas", "pub")
			own := db.personEffectiveIn(db.db, "dmestas-coder", "pub", ag.Caps())
			got := letters(db.EffectiveCaps("dmestas-coder", "pub"))
			want := letters(CapSet(capUnion(db.CategoryCaps("pub", "nobody"), string(capIntersect(own, op)))))
			if got != want {
				t.Errorf("FAIL operator %s, agent %s: resolved %q, want %q", og, ag, got, want)
			}
			for _, l := range got {
				if !op.HasAny(string(l)) && !strings.ContainsRune(db.CategoryCaps("pub", "nobody"), l) {
					t.Errorf("FAIL operator %s, agent %s: agent holds %c, operator does not", og, ag, l)
				}
			}
		}
	}
	t.Log("ok: every preset pair resolves to the intersection plus the public set")
}

// The operator losing a letter takes it from the agent on the next
// read, with no write to the agent's row.
func TestOperatorLosingALetterTakesItFromTheAgent(t *testing.T) {
	db := agentRig(t)
	if err := db.Grant("dmestas", "pub", RepoAdmin); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("dmestas-coder", "pub", RepoAdmin); err != nil {
		t.Fatal(err)
	}
	if !db.EffectiveCaps("dmestas-coder", "pub").HasAny("a") {
		t.Fatal("FAIL: an admin's agent granted admin does not hold a")
	}
	before, _ := db.CapsOf("dmestas-coder", "pub")
	if err := db.Grant("dmestas", "pub", Read); err != nil {
		t.Fatal(err)
	}
	after, _ := db.CapsOf("dmestas-coder", "pub")
	eff := db.EffectiveCaps("dmestas-coder", "pub")
	if eff.HasAny("a") || eff.HasAny("i") {
		t.Errorf("FAIL: the agent kept letters its operator lost: %q", letters(eff))
	} else if before != after {
		t.Errorf("FAIL: the agent's stored string was rewritten: %q -> %q", before, after)
	} else {
		t.Log("ok: the operator's demotion reached the agent at read time, nothing written")
	}
}

// A site-admin operator yields no s, and a private repository the
// operator cannot see does not exist for the agent.
func TestSiteAdminOperatorYieldsNoSAndPrivateStaysDark(t *testing.T) {
	db := agentRig(t)
	if err := db.NewAgent("root-bot", "root", -1); err != nil {
		t.Fatal(err)
	}
	if db.EffectiveCaps("root-bot", "").HasAny("s") {
		t.Error("FAIL: a site admin's agent holds s at forge scope")
	} else {
		t.Log("ok: s does not flow from a site-admin operator")
	}
	if err := db.Grant("root-bot", "priv", RepoAdmin); err != nil {
		t.Fatal(err)
	}
	if db.EffectiveCaps("root-bot", "priv").HasAny("gjoh2r") {
		t.Error("FAIL: an agent reads a private repository its operator holds no grant on")
	} else {
		t.Log("ok: a private repository the operator cannot see does not exist for the agent")
	}
	if err := db.Grant("dmestas", "priv", Read); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("dmestas-coder", "priv", Write); err != nil {
		t.Fatal(err)
	}
	eff := db.EffectiveCaps("dmestas-coder", "priv")
	if !eff.HasAny("o") || eff.HasAny("i") {
		t.Errorf("FAIL: agent on a private repository through a reader operator holds %q", letters(eff))
	} else {
		t.Log("ok: the agent reaches the private repository through its operator's grant, and no further")
	}
	// A disabled operator holds nothing.
	if err := db.SetDisabled("dmestas", true); err != nil {
		t.Fatal(err)
	}
	if db.EffectiveCaps("dmestas-coder", "priv").HasAny("gjoh2r") {
		t.Error("FAIL: a disabled operator's agent still reads the private repository")
	}
}

// Granting: agent.grant stores the string clipped to the operator's
// letters and refuses a repository the operator cannot reach.
func TestAgentGrantClipsToTheOperator(t *testing.T) {
	db := agentRig(t)
	if err := db.Grant("dmestas", "pub", Read); err != nil {
		t.Fatal(err)
	}
	stored, clipped, err := db.AgentGrant("dmestas", "dmestas-coder", "pub", Write.Caps())
	if err != nil {
		t.Fatalf("FAIL: %v", err)
	}
	if !clipped {
		t.Error("FAIL: developer on a reader's agent was not reported clipped")
	}
	if CapSet(stored).HasAny("i") || CapSet(stored).HasAny("v") {
		t.Errorf("FAIL: stored %q holds check-in the operator lacks", stored)
	}
	if got, _ := db.CapsOf("dmestas-coder", "pub"); got != stored {
		t.Errorf("FAIL: stored %q, row holds %q", stored, got)
	}
	if err := db.Grant("dmestas", "pub", RepoAdmin); err != nil {
		t.Fatal(err)
	}
	stored, clipped, err = db.AgentGrant("dmestas", "dmestas-coder", "pub", Write.Caps())
	if err != nil || clipped || Closure(stored) != Closure(Write.Caps()) {
		t.Errorf("FAIL: developer under an admin operator stored as %q (clipped %v, err %v)", stored, clipped, err)
	} else {
		t.Log("ok: a string within the operator's letters is stored whole; one above is clipped")
	}
	if _, _, err := db.AgentGrant("dmestas", "dmestas-coder", "priv", Read.Caps()); !IsRefusal(err) {
		t.Errorf("FAIL: a grant on a repository the operator cannot reach: %v", err)
	} else {
		t.Log("ok: refused where the operator cannot reach")
	}
	if _, _, err := db.AgentGrant("jva", "dmestas-coder", "pub", Read.Caps()); !IsRefusal(err) {
		t.Errorf("FAIL: a stranger wrote somebody else's agent: %v", err)
	}
}

// The deed: SetOwner refuses an agent, so no path can hand it one.
func TestSetOwnerRefusesAnAgent(t *testing.T) {
	db := agentRig(t)
	if err := db.SetOwner("pub", "dmestas-coder"); !IsRefusal(err) {
		t.Errorf("FAIL: an agent received a deed: %v", err)
	}
	if owner, _ := db.OwnerOf("pub"); owner != "" {
		t.Errorf("FAIL: the deed moved to %q", owner)
	}
	if err := db.SetOwner("pub", "dmestas"); err != nil {
		t.Errorf("FAIL: a person refused a deed: %v", err)
	}
	if n, _ := db.OwnedRepoCount("dmestas-coder"); n != 0 {
		t.Errorf("FAIL: the agent owns %d repositories", n)
	}
}

// The two a guards, keyed on the caller being an agent only.
func TestAgentWithAdminCannotLowerOperatorOrDeedHolderNorEscalate(t *testing.T) {
	db := agentRig(t)
	for _, who := range []string{"dmestas", "dmestas-coder", "jva"} {
		if err := db.Grant(who, "pub", RepoAdmin); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.AddAccount("owner", "a-long-enough-password", false); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("owner", "pub", Maintain); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("pub", "owner"); err != nil {
		t.Fatal(err)
	}
	for _, target := range []string{"dmestas", "owner"} {
		if _, err := db.GrantGuarded("dmestas-coder", target, "pub", Read); !IsRefusal(err) {
			t.Errorf("FAIL: the agent lowered %s: %v", target, err)
		}
		if _, err := db.GrantGuarded("dmestas-coder", target, "pub", NoRole); !IsRefusal(err) {
			t.Errorf("FAIL: the agent revoked %s: %v", target, err)
		}
	}
	// Raising the deed holder is not lowering.
	if _, err := db.GrantGuarded("dmestas-coder", "owner", "pub", RepoAdmin); err != nil {
		t.Errorf("FAIL: the agent could not raise the deed holder: %v", err)
	}
	// A third account may be lowered, and a person may lower anyone.
	if _, err := db.GrantGuarded("dmestas-coder", "jva", "pub", Read); err != nil {
		t.Errorf("FAIL: the agent could not lower a third account: %v", err)
	}
	if _, err := db.GrantGuarded("jva", "dmestas", "pub", Read); err != nil {
		t.Errorf("FAIL: a person with a was held to the agent's guard: %v", err)
	}
	// Escalation: the operator is a reader now, so the agent resolves
	// to reader and cannot write developer on anyone.
	if _, err := db.SetCapsGuarded("dmestas-coder", "jva", "pub", Write.Caps()); !IsRefusal(err) {
		t.Errorf("FAIL: the agent granted letters it does not resolve to: %v", err)
	} else {
		t.Log("ok: an agent writes at most what it resolves to")
	}
	if _, err := db.SetCapsGuarded("owner", "jva", "pub", Write.Caps()); err != nil {
		t.Errorf("FAIL: a person was held to the agent's ceiling: %v", err)
	}
}

// Marking clears the password and ends the sessions in one
// transaction; SetOperator refuses self and an agent as operator.
func TestMarkingClearsThePasswordAndSetOperatorRefusesSelf(t *testing.T) {
	db := agentRig(t)
	tok, _, ok := db.NewSession("jva", "a-long-enough-password")
	if !ok {
		t.Fatal("no session")
	}
	if err := db.SetOperator("jva", "dmestas"); err != nil {
		t.Fatalf("FAIL: %v", err)
	}
	if _, ok := db.Authenticate("jva", "a-long-enough-password"); ok {
		t.Error("FAIL: a marked account still signs in with its password")
	}
	if _, ok := db.SessionAccount(tok); ok {
		t.Error("FAIL: a marked account's session survived")
	}
	if a, _ := db.AccountByName("jva"); a.Pending {
		t.Error("FAIL: an agent reads as an unclaimed invite")
	}
	if err := db.SetOperator("jva", "jva"); !IsRefusal(err) {
		t.Errorf("FAIL: an account became its own operator: %v", err)
	}
	if err := db.SetOperator("jva", "dmestas-coder"); !IsRefusal(err) {
		t.Errorf("FAIL: an agent became an operator: %v", err)
	}
	if err := db.NewAgent("x", "x", -1); !IsRefusal(err) {
		t.Errorf("FAIL: NewAgent accepted self as operator: %v", err)
	}
	if err := db.NewAgent("root-bot", "root", -1); err != nil {
		t.Fatal(err)
	}
	if a, _ := db.AccountByName("root-bot"); a.Admin {
		t.Error("FAIL: a site admin's agent was made a site admin")
	}
	// The one-time-link ops refuse an agent by name.
	if _, err := db.InviteAccount("jva", "", false); !IsRefusal(err) {
		t.Errorf("FAIL: an agent was invited: %v", err)
	}
	if _, err := db.ResetPassword("jva"); !IsRefusal(err) {
		t.Errorf("FAIL: an agent's password was reset: %v", err)
	}
	// Clearing the mark leaves a passwordless person an invite can reach.
	if err := db.SetOperator("jva", ""); err != nil {
		t.Fatal(err)
	}
	if _, err := db.InviteAccount("jva", "", false); err != nil {
		t.Errorf("FAIL: a cleared account cannot be invited: %v", err)
	} else {
		t.Log("ok: marking clears the password; the mark cleared, the invite path is open again")
	}
}

// Lifecycle: disabling and deleting an operator suspends their agents;
// reopening and reassignment lift it; day thirty-one deletes.
func TestOperatorDisableAndDeleteSuspendAgents(t *testing.T) {
	db := agentRig(t)
	value, _, err := db.MintToken("dmestas-coder", TokenSpec{Name: "s", Scopes: []string{ScopeAPI}})
	if err != nil {
		t.Fatal(err)
	}
	if err := db.SetDisabled("dmestas", true); err != nil {
		t.Fatal(err)
	}
	a, _ := db.AccountByName("dmestas-coder")
	if a.Disabled == "" || a.Suspended == "" {
		t.Errorf("FAIL: the agent was not suspended with its operator: %+v", a)
	}
	if _, ok := db.ResolveToken("dmestas-coder", value, ScopeAPI); ok {
		t.Error("FAIL: a suspended agent's token still resolves")
	}
	if err := db.SetDisabled("dmestas", false); err != nil {
		t.Fatal(err)
	}
	if a, _ := db.AccountByName("dmestas-coder"); a.Disabled != "" || a.Suspended != "" {
		t.Errorf("FAIL: reopening the operator left the agent shut: %+v", a)
	}
	if _, ok := db.ResolveToken("dmestas-coder", value, ScopeAPI); !ok {
		t.Error("FAIL: the token did not come back with the operator")
	}
	// An agent a site admin shut on purpose stays shut.
	if err := db.SetDisabled("dmestas-coder", true); err != nil {
		t.Fatal(err)
	}
	if err := db.SetDisabled("dmestas", true); err != nil {
		t.Fatal(err)
	}
	if err := db.SetDisabled("dmestas", false); err != nil {
		t.Fatal(err)
	}
	if a, _ := db.AccountByName("dmestas-coder"); a.Disabled == "" {
		t.Error("FAIL: reopening the operator reopened an agent shut on purpose")
	}
	if err := db.SetDisabled("dmestas-coder", false); err != nil {
		t.Fatal(err)
	}
	// Deletion suspends, reassignment lifts, the sweep deletes.
	if err := db.DeleteAccount("dmestas"); err != nil {
		t.Fatal(err)
	}
	a, _ = db.AccountByName("dmestas-coder")
	if a.Suspended == "" || a.Operator != "dmestas" {
		t.Errorf("FAIL: deleting the operator did not suspend the agent under its name: %+v", a)
	}
	if n, err := db.SweepSuspendedAgents(time.Now().Add(29 * 24 * time.Hour)); err != nil || n != 0 {
		t.Errorf("FAIL: the sweep took a day-29 agent (%d, %v)", n, err)
	}
	if err := db.SetOperator("dmestas-coder", "jva"); err != nil {
		t.Fatal(err)
	}
	if a, _ := db.AccountByName("dmestas-coder"); a.Disabled != "" || a.Suspended != "" {
		t.Errorf("FAIL: reassignment did not lift the suspension: %+v", a)
	}
	if err := db.DeleteAccount("jva"); err != nil {
		t.Fatal(err)
	}
	if n, err := db.SweepSuspendedAgents(time.Now().Add(31 * 24 * time.Hour)); err != nil || n != 1 {
		t.Errorf("FAIL: the sweep did not take the day-31 agent (%d, %v)", n, err)
	}
	if _, ok := db.AccountByName("dmestas-coder"); ok {
		t.Error("FAIL: the swept agent still exists")
	} else if !db.Tombstoned("dmestas-coder") {
		t.Error("FAIL: the swept agent's name was not retired")
	} else {
		t.Log("ok: suspended with the operator, lifted by reassignment, deleted on day thirty-one")
	}
}

// revoke_all leaves no live token or session across two agents.
func TestRevokeAllAgentTokens(t *testing.T) {
	db := agentRig(t)
	if err := db.NewAgent("dmestas-release", "dmestas", -1); err != nil {
		t.Fatal(err)
	}
	var values []string
	for _, agent := range []string{"dmestas-coder", "dmestas-release"} {
		v, _, err := db.MintToken(agent, TokenSpec{Name: "s", Scopes: []string{ScopeAPI}})
		if err != nil {
			t.Fatal(err)
		}
		values = append(values, v)
	}
	mine, _, err := db.MintToken("dmestas", TokenSpec{Name: "mine", Scopes: []string{ScopeAPI}})
	if err != nil {
		t.Fatal(err)
	}
	n, err := db.RevokeAgentTokens("dmestas")
	if err != nil || n != 2 {
		t.Fatalf("FAIL: revoked %d, %v", n, err)
	}
	for i, agent := range []string{"dmestas-coder", "dmestas-release"} {
		if _, ok := db.ResolveToken(agent, values[i], ScopeAPI); ok {
			t.Errorf("FAIL: %s's token survived revoke_all", agent)
		}
	}
	if _, ok := db.ResolveToken("dmestas", mine, ScopeAPI); !ok {
		t.Error("FAIL: revoke_all took the operator's own token")
	} else {
		t.Log("ok: every agent token gone, the person's own kept")
	}
}

// The owner backfill (ownerBackfill, run on every open) is the one deed
// write that does not go through SetOwner: an unclaimed repository
// whose sole admin is an agent stays forge-held across a reopen.
func TestOwnerBackfillNeverDeedsAnAgent(t *testing.T) {
	path := filepath.Join(t.TempDir(), "o.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AddAccount("dmestas", "a-long-enough-password", false); err != nil {
		t.Fatal(err)
	}
	if err := db.NewAgent("coder", "dmestas", -1); err != nil {
		t.Fatal(err)
	}
	for _, r := range []string{"r1", "r2"} {
		if err := db.AddRepo(r, t.TempDir(), "", Public); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.Grant("coder", "r1", RepoAdmin); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("dmestas", "r2", RepoAdmin); err != nil {
		t.Fatal(err)
	}
	db.Close()
	db, err = Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	if owner, _ := db.OwnerOf("r1"); owner != "" {
		t.Errorf("FAIL: the backfill deeded r1 to %q, an agent", owner)
	}
	if owner, _ := db.OwnerOf("r2"); owner != "dmestas" {
		t.Errorf("FAIL: the backfill left r2 with %q, want dmestas", owner)
	} else {
		t.Log("ok: the backfill adopts a person's repository and leaves an agent's forge-held")
	}
}

// Marking refuses an account that operates agents (a chain would put
// them past their operator's operator), holds a deed, or alone owns a
// team, and names what it holds; and it clears the site-admin flag.
func TestMarkingRefusesHoldingsAndClearsTheAdminFlag(t *testing.T) {
	db := agentRig(t)
	if err := db.AddAccount("bob", "a-long-enough-password", true); err != nil {
		t.Fatal(err)
	}
	if err := db.NewAgent("bobbot", "bob", -1); err != nil {
		t.Fatal(err)
	}
	err := db.SetOperator("bob", "dmestas")
	if !IsRefusal(err) || !strings.Contains(err.Error(), "bobbot") {
		t.Errorf("FAIL: an operator of agents was marked, or the refusal did not name them: %v", err)
	}
	if db.Operator("bobbot") != "bob" || db.Operator("bob") != "" {
		t.Error("FAIL: a chain formed")
	}
	if err := db.DeleteAccount("bobbot"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("pub", "bob"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOperator("bob", "dmestas"); !IsRefusal(err) || !strings.Contains(err.Error(), "pub") {
		t.Errorf("FAIL: a deed holder was marked, or the refusal did not name the repository: %v", err)
	}
	if err := db.SetOwner("pub", ""); err != nil {
		t.Fatal(err)
	}
	if err := db.AddTeam("crew", "Crew", "", "bob"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOperator("bob", "dmestas"); !IsRefusal(err) || !strings.Contains(err.Error(), "crew") {
		t.Errorf("FAIL: a team's only owner was marked, or the refusal did not name the team: %v", err)
	}
	if _, err := db.SetTeamMemberGuarded("bob", "crew", "dmestas", "", true, true); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOperator("bob", "dmestas"); err != nil {
		t.Fatalf("FAIL: with the holdings moved, marking was still refused: %v", err)
	}
	if a, _ := db.AccountByName("bob"); a.Admin {
		t.Error("FAIL: marking kept the site-admin flag")
	} else if db.EffectiveCaps("bob", "").HasAny("s") {
		t.Error("FAIL: a newly marked agent still resolves to s")
	} else {
		t.Log("ok: marking refuses agents, deeds and sole-owned teams by name, and clears the flag")
	}
}

// forge is reserved for accounts and teams alike, and is never an
// operator a person's path can name; an agent never gets a password
// by any write; the cap is counted under the lock.
func TestForgeIsReservedAndAgentsNeverGetAPassword(t *testing.T) {
	db := agentRig(t)
	if err := db.AddAccount("forge", "a-long-enough-password", false); !IsRefusal(err) {
		t.Errorf("FAIL: an account called forge: %v", err)
	}
	if err := db.AddAccount("Forge", "a-long-enough-password", false); !IsRefusal(err) {
		t.Errorf("FAIL: an account called Forge: %v", err)
	}
	if err := db.AddTeam("forge", "Forge", "", "dmestas"); !IsRefusal(err) {
		t.Errorf("FAIL: a team called forge: %v", err)
	}
	if err := db.NewAgent("bot", "forge", -1); !IsRefusal(err) {
		t.Errorf("FAIL: NewAgent took forge as operator: %v", err)
	}
	if err := db.SetPassword("dmestas-coder", "a-long-enough-password"); !IsRefusal(err) {
		t.Errorf("FAIL: SetPassword gave an agent a password: %v", err)
	}
	if err := db.SetPasswordSelf("dmestas-coder", "a-long-enough-password", ""); !IsRefusal(err) {
		t.Errorf("FAIL: SetPasswordSelf gave an agent a password: %v", err)
	}
	if _, ok := db.Authenticate("dmestas-coder", "a-long-enough-password"); ok {
		t.Error("FAIL: the agent signed in with a password")
	}
	if err := db.NewAgent("dmestas-second", "dmestas", 1); !IsRefusal(err) {
		t.Errorf("FAIL: the cap of one let a second agent in: %v", err)
	}
	if err := db.NewAgent("dmestas-second", "dmestas", 2); err != nil {
		t.Errorf("FAIL: the cap of two refused the second agent: %v", err)
	} else {
		t.Log("ok: forge reserved everywhere, no password write reaches an agent, the cap holds under the lock")
	}
}