package api
import (
"errors"
"testing"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// RFC 0021, Testing: Forge agents (the doors). agent.new with
// operator forge is a site admin's and needs answerable; any site admin
// mints, revokes and reassigns; a forge agent holding s passes
// account.add and the deed paths and is refused on making or altering
// a forge agent and on any s flag change.
func TestForgeAgentIsASiteAdminsToMakeAndManage(t *testing.T) {
r, db, _ := agentCoreRig(t, 5)
if _, err := r.Invoke(asUser("jva"), "agent.new", agentNewIn{Name: "fbot", Operator: "forge", Answerable: "dmestas"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a person created a forge agent: %v", err)
}
if _, err := r.Invoke(asUser("dmestas"), "agent.new", agentNewIn{Name: "fbot", Operator: "forge"}); !errors.Is(err, ops.ErrInvalid) {
t.Errorf("FAIL: a forge agent without answerable: %v", err)
}
if _, err := r.Invoke(asUser("dmestas"), "agent.new", agentNewIn{Name: "fbot", Operator: "forge", Answerable: "jva"}); !errors.Is(err, ops.ErrInvalid) {
t.Errorf("FAIL: a person without the flag became answerable: %v", err)
}
out, err := r.Invoke(asUser("dmestas"), "agent.new", agentNewIn{Name: "fbot", Operator: "forge", Answerable: "dmestas"})
if err != nil {
t.Fatalf("FAIL: %v", err)
}
if out.(agentOut).Operator != "forge" || db.Operator("fbot") != "forge" {
t.Errorf("FAIL: created %+v, operator %q", out, db.Operator("fbot"))
}
// Tokens: any site admin mints and revokes; a person does not.
minted, err := r.Invoke(asUser("dmestas"), "agent.token.new", agentTokenNewIn{Agent: "fbot", Name: "s", Scopes: "api", Days: 30})
if err != nil {
t.Fatalf("FAIL: the site admin could not mint for a forge agent: %v", err)
}
if _, err := r.Invoke(asUser("jva"), "agent.token.new", agentTokenNewIn{Agent: "fbot", Name: "x", Scopes: "api"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a person minted for a forge agent: %v", err)
}
if _, err := r.Invoke(asUser("jva"), "agent.token.revoke", agentTokenRevokeIn{Agent: "fbot", ID: minted.(agentTokenNewOut).Token.ID}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a person revoked a forge agent's token: %v", err)
}
if _, err := r.Invoke(asUser("dmestas"), "agent.token.revoke", agentTokenRevokeIn{Agent: "fbot", ID: minted.(agentTokenNewOut).Token.ID}); err != nil {
t.Errorf("FAIL: the site admin could not revoke: %v", err)
}
// The answerable admin: any site admin reassigns.
if err := db.AddAccount("root2", "a-long-enough-password", true); err != nil {
t.Fatal(err)
}
if _, err := r.Invoke(asUser("jva"), "account.answerable", accountAnswerableIn{Name: "fbot", Answerable: "root2"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a person reassigned the answerable admin: %v", err)
}
if _, err := r.Invoke(asUser("root2"), "account.answerable", accountAnswerableIn{Name: "fbot", Answerable: "root2"}); err != nil {
t.Errorf("FAIL: a site admin could not reassign: %v", err)
}
out, err = r.Invoke(asUser("dmestas"), "agent.list", agentListIn{Operator: "forge"})
if err != nil {
t.Fatal(err)
}
if rows := out.(agentListOut).Agents; len(rows) != 1 || rows[0].Answerable != "root2" || rows[0].Operator != "forge" {
t.Errorf("FAIL: the fleet list: %+v", rows)
}
// Disabling the answerable admin: the agent works, nobody answers.
if _, err := r.Invoke(asUser("dmestas"), "account.disable", accountDisableIn{Name: "root2", On: true}); err != nil {
t.Fatal(err)
}
out, _ = r.Invoke(asUser("dmestas"), "agent.list", agentListIn{Operator: "forge"})
if rows := out.(agentListOut).Agents; rows[0].Answerable != "" || rows[0].Disabled != "" {
t.Errorf("FAIL: after the admin's disabling: %+v", rows)
} else {
t.Log("ok: a forge agent is a site admin's to make, mint for, revoke and reassign; its admin's loss leaves it working")
}
}
func TestForgeAgentHoldingSStopsAtOtherForgeAgentsAndTheFlag(t *testing.T) {
r, db, owners := agentCoreRig(t, 5)
// The migration shape: a site admin re-marked as a forge agent,
// keeping the flag.
if err := db.AddAccount("zeitgeist", "a-long-enough-password", true); err != nil {
t.Fatal(err)
}
if _, err := r.Invoke(asUser("jva"), "account.agent", accountAgentIn{Name: "zeitgeist", Operator: "forge", Answerable: "dmestas"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a person re-marked a forge agent: %v", err)
}
if _, err := r.Invoke(asUser("dmestas"), "account.agent", accountAgentIn{Name: "zeitgeist", Operator: "forge"}); !errors.Is(err, ops.ErrInvalid) {
t.Errorf("FAIL: re-marking without answerable: %v", err)
}
if _, err := r.Invoke(asUser("dmestas"), "account.agent", accountAgentIn{Name: "zeitgeist", Operator: "forge", Answerable: "dmestas"}); err != nil {
t.Fatalf("FAIL: %v", err)
}
z, _ := db.AccountByName("zeitgeist")
if !z.Admin || z.Operator != "forge" {
t.Fatalf("FAIL: re-marked reads %+v", z)
}
if _, ok := db.Authenticate("zeitgeist", "a-long-enough-password"); !ok {
t.Error("FAIL: re-marking retired the password before the cutover")
}
// What a site admin may, it may: a person's account, a person's
// agent, a deed.
if _, err := r.Invoke(asUser("zeitgeist"), "account.add", accountAddIn{Name: "newperson", Password: "a-long-enough-password"}); err != nil {
t.Errorf("FAIL: a forge agent with s could not add a person: %v", err)
}
if _, err := r.Invoke(asUser("zeitgeist"), "account.agent", accountAgentIn{Name: "newperson", Operator: "jva"}); err != nil {
t.Errorf("FAIL: a forge agent with s could not mark a person's agent: %v", err)
}
if err := db.Grant("zeitgeist", "test", oltp.RepoAdmin); err != nil {
t.Fatal(err)
}
owners.owner["test"] = ""
if _, err := r.Invoke(asUser("zeitgeist"), "repo.claim", repoClaimIn{Repo: "test"}); err != nil {
t.Errorf("FAIL: a forge agent could not claim an unheld repository: %v", err)
}
if owners.owner["test"] != "zeitgeist" {
t.Errorf("FAIL: the deed went to %q", owners.owner["test"])
}
d := Deps{Owners: owners, Accounts: acctAdapter{db}}
if err := d.refuseUnlessTheirs(asUser("zeitgeist"), "test", "destroy"); err != nil {
t.Errorf("FAIL: a forge agent holding the deed was refused a deed act: %v", err)
}
// A deed held by a site admin or another forge agent is past a
// forge agent's reach: it neither hands it on nor destroys under
// it; a person with s still does both, and a plain person's deed
// is the operator path as today.
if err := db.AddAccount("admin2", "a-long-enough-password", true); err != nil {
t.Fatal(err)
}
if _, err := r.Invoke(asUser("dmestas"), "agent.new", agentNewIn{Name: "fbot0", Operator: "forge", Answerable: "dmestas"}); err != nil {
t.Fatal(err)
}
for _, who := range []string{"admin2", "fbot0", "jva"} {
if err := db.Grant(who, "test", oltp.RepoAdmin); err != nil {
t.Fatal(err)
}
}
for _, holder := range []string{"admin2", "fbot0"} {
owners.owner["test"] = holder
if _, err := r.Invoke(asUser("zeitgeist"), "repo.claim", repoClaimIn{Repo: "test", Account: "jva"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a forge agent moved %s's deed: %v", holder, err)
}
if err := d.refuseUnlessTheirs(asUser("zeitgeist"), "test", "destroy"); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a forge agent may destroy under %s's deed: %v", holder, err)
}
if owners.owner["test"] != holder {
t.Errorf("FAIL: the deed left %s", holder)
}
}
if err := d.refuseUnlessTheirs(asUser("dmestas"), "test", "destroy"); err != nil {
t.Errorf("FAIL: a person with s was refused a deed act: %v", err)
}
if _, err := r.Invoke(asUser("dmestas"), "repo.claim", repoClaimIn{Repo: "test", Account: "jva"}); err != nil {
t.Errorf("FAIL: a person with s could not hand a forge agent's deed on: %v", err)
}
owners.owner["test"] = "jva"
if err := d.refuseUnlessTheirs(asUser("zeitgeist"), "test", "destroy"); err != nil {
t.Errorf("FAIL: a forge agent was refused under a plain person's deed: %v", err)
}
owners.owner["test"] = "zeitgeist"
// Its own tokens, yes; another forge agent, never.
if _, err := r.Invoke(asUser("zeitgeist"), "agent.token.new", agentTokenNewIn{Agent: "zeitgeist", Name: "own", Scopes: "api"}); err != nil {
t.Errorf("FAIL: a forge agent could not mint its own token: %v", err)
}
if _, err := r.Invoke(asUser("dmestas"), "agent.new", agentNewIn{Name: "fbot", Operator: "forge", Answerable: "dmestas"}); err != nil {
t.Fatal(err)
}
refused := map[string]any{
"agent.new": agentNewIn{Name: "fbot2", Operator: "forge", Answerable: "dmestas"},
"account.agent": accountAgentIn{Name: "fbot", Operator: "jva"},
"account.answerable": accountAnswerableIn{Name: "fbot", Answerable: "dmestas"},
"agent.token.new": agentTokenNewIn{Agent: "fbot", Name: "x", Scopes: "api"},
"agent.delete": agentNameIn{Name: "fbot"},
"account.add": accountAddIn{Name: "newadmin", Password: "a-long-enough-password", Admin: true},
"account.invite": accountInviteIn{Name: "invitedadmin", Admin: true},
}
for op, in := range refused {
if _, err := r.Invoke(asUser("zeitgeist"), op, in); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: %s by a forge agent: %v", op, err)
}
}
// Re-marking a person as a forge agent is also making one.
if _, err := r.Invoke(asUser("zeitgeist"), "account.agent", accountAgentIn{Name: "jva", Operator: "forge", Answerable: "dmestas"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a forge agent re-marked a person as a forge agent: %v", err)
}
// Re-inviting a pending site admin without the flag would clear it.
if _, err := r.Invoke(asUser("dmestas"), "account.invite", accountInviteIn{Name: "pendingadmin", Admin: true}); err != nil {
t.Fatal(err)
}
if _, err := r.Invoke(asUser("zeitgeist"), "account.invite", accountInviteIn{Name: "pendingadmin"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a forge agent re-invited a site admin without the flag: %v", err)
}
// The account-level ops a site admin runs against another account
// are refused a forge agent when the target is a forge agent or
// holds s: marking strips the flag, disabling and deleting lock
// out, a reset or an address takes the account.
if _, err := r.Invoke(asUser("dmestas"), "account.add", accountAddIn{Name: "pal", Password: "a-long-enough-password"}); err != nil {
t.Fatal(err)
}
for _, c := range []struct {
op string
in any
}{
{"account.agent", accountAgentIn{Name: "dmestas", Operator: "jva"}},
{"account.agent", accountAgentIn{Name: "dmestas"}},
{"account.agent", accountAgentIn{Name: "fbot"}},
{"account.agent", accountAgentIn{Name: "fbot", Operator: "jva"}},
{"account.delete", accountDeleteIn{Name: "dmestas"}},
{"account.delete", accountDeleteIn{Name: "fbot"}},
{"account.disable", accountDisableIn{Name: "dmestas", On: true}},
{"account.disable", accountDisableIn{Name: "fbot", On: true}},
{"account.reset", accountResetIn{Name: "dmestas"}},
{"account.invite.withdraw", accountDeleteIn{Name: "pendingadmin"}},
{"account.grant", accountGrantIn{Name: "fbot", Repo: "test", Role: "reader"}},
{"account.revoke", accountRevokeIn{Name: "fbot", Repo: "test"}},
{"account.caps", accountCapsIn{Account: "fbot", Repo: "test", Caps: "u"}},
} {
if _, err := r.Invoke(asUser("zeitgeist"), c.op, c.in); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: %s %+v by a forge agent: %v", c.op, c.in, err)
}
}
// session.end: a forge agent's reach is its own sessions, not
// everyone's as s would otherwise give it.
if _, err := r.Invoke(asUser("zeitgeist"), "session.end", sessionEndIn{Account: "dmestas"}); err == nil {
t.Error("FAIL: a forge agent signed a site admin out everywhere")
}
if _, err := r.Invoke(asUser("zeitgeist"), "session.end", sessionEndIn{Account: "zeitgeist"}); err != nil {
t.Errorf("FAIL: a forge agent could not end its own sessions: %v", err)
}
// Raising a site admin's letters cannot lock them out: a grant and
// a caps write that adds pass; one that drops a letter is refused.
publisher := oltp.Publisher.Caps()
if _, err := db.SetCapsGuarded("dmestas", "zeitgeist", "test", "a"+publisher); err != nil {
t.Fatal(err)
}
if _, err := r.Invoke(asUser("zeitgeist"), "account.grant", accountGrantIn{Name: "dmestas", Repo: "test", Role: "reader"}); err != nil {
t.Errorf("FAIL: a forge agent could not raise a site admin: %v", err)
}
// A grant replaces the string: reader over a site admin's a drops
// a letter and is refused; admin over reader raises and passes.
if err := db.Grant("admin2", "test", oltp.RepoAdmin); err != nil {
t.Fatal(err)
}
if _, err := r.Invoke(asUser("zeitgeist"), "account.grant", accountGrantIn{Name: "admin2", Repo: "test", Role: "reader"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a forge agent granted reader over a site admin's a: %v", err)
}
if c, _ := db.CapsOf("admin2", "test"); c != "a" {
t.Errorf("FAIL: admin2 on test reads %q after the refused grant", c)
}
if err := db.Grant("admin2", "test", oltp.Read); err != nil {
t.Fatal(err)
}
if _, err := r.Invoke(asUser("zeitgeist"), "account.grant", accountGrantIn{Name: "admin2", Repo: "test", Role: "admin"}); err != nil {
t.Errorf("FAIL: a forge agent could not grant admin over a site admin's reader: %v", err)
}
if _, err := r.Invoke(asUser("zeitgeist"), "account.caps", accountCapsIn{Account: "dmestas", Repo: "test", Caps: publisher}); err != nil {
t.Errorf("FAIL: a forge agent could not add a letter to a site admin: %v", err)
}
if _, err := r.Invoke(asUser("zeitgeist"), "account.caps", accountCapsIn{Account: "dmestas", Repo: "test", Caps: "y"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a forge agent lowered a site admin's letters: %v", err)
}
if c, _ := db.CapsOf("dmestas", "test"); c != publisher {
t.Errorf("FAIL: dmestas on test reads %q, want %q", c, publisher)
}
// A revoke lowers a site admin who holds something.
if _, err := r.Invoke(asUser("zeitgeist"), "account.revoke", accountRevokeIn{Name: "dmestas", Repo: "test"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a forge agent revoked a site admin: %v", err)
}
if c, _ := db.CapsOf("dmestas", "test"); c != publisher {
t.Errorf("FAIL: dmestas on test reads %q after the refused revoke", c)
}
// The same acts on a person who holds nothing pass, and a person
// with s still takes them on a forge agent.
if _, err := r.Invoke(asUser("zeitgeist"), "account.grant", accountGrantIn{Name: "pal", Repo: "test", Role: "reader"}); err != nil {
t.Errorf("FAIL: a forge agent could not grant a person: %v", err)
}
if _, err := r.Invoke(asUser("zeitgeist"), "account.disable", accountDisableIn{Name: "pal", On: true}); err != nil {
t.Errorf("FAIL: a forge agent could not disable a person: %v", err)
}
if _, err := r.Invoke(asUser("dmestas"), "account.disable", accountDisableIn{Name: "fbot", On: true}); err != nil {
t.Errorf("FAIL: a site admin could not disable a forge agent: %v", err)
}
if a, _ := db.AccountByName("dmestas"); !a.Admin || a.Operator != "" || a.Disabled != "" {
t.Errorf("FAIL: the site admin was altered: %+v", a)
}
if _, ok := db.Authenticate("dmestas", "a-long-enough-password"); !ok {
t.Error("FAIL: the site admin's password is gone")
}
if a, ok := db.AccountByName("fbot"); !ok || a.Operator != "forge" {
t.Error("FAIL: the other forge agent was altered")
}
if a, _ := db.AccountByName("pendingadmin"); !a.Admin {
t.Error("FAIL: the pending admin lost the flag")
} else {
t.Log("ok: a forge agent with s passes account.add and the deed paths, never another forge agent or an s flag")
}
}