package oltp
import (
"strings"
"testing"
)
// RFC 0021, Testing: Forge agents (the store's half). A forge agent is
// made or re-marked only with a site admin answerable, keeps what a
// person's agent may not hold, resolves with no operator term, may be
// deeded, and answers to nobody when its admin is gone.
func TestForgeAgentCreationAndRemark(t *testing.T) {
db := agentRig(t)
// Creation needs an answerable site admin: not nobody, not a
// person without the flag, not an agent.
for _, bad := range []string{"", "jva", "dmestas-coder", "nobody"} {
if err := db.NewForgeAgent("fbot", bad); err == nil {
t.Errorf("FAIL: NewForgeAgent took %q as answerable", bad)
}
}
if err := db.NewForgeAgent("fbot", "root"); err != nil {
t.Fatalf("FAIL: %v", err)
}
a, _ := db.AccountByName("fbot")
if a.Operator != "forge" || a.Answerable != "root" || a.Admin || a.Pending {
t.Errorf("FAIL: new forge agent reads %+v", a)
}
if db.IsAgent("fbot") || !db.IsForgeAgent("fbot") {
t.Error("FAIL: a forge agent reads as a person's agent, or not as the forge's")
}
if _, ok := db.Authenticate("fbot", ""); ok {
t.Error("FAIL: a forge agent signed in with an empty password")
}
// SetOperator never writes forge: the forge path is the only one.
if err := db.SetOperator("jva", "forge"); !IsRefusal(err) {
t.Errorf("FAIL: SetOperator took forge: %v", err)
}
// Neither path takes a forge agent as a person's operator.
if err := db.NewAgent("fbot-child", "fbot", -1); !IsRefusal(err) {
t.Errorf("FAIL: a forge agent became an operator: %v", err)
}
if err := db.SetOperator("jva", "fbot"); !IsRefusal(err) {
t.Errorf("FAIL: SetOperator took a forge agent as operator: %v", err)
}
// The migration case: a site admin with a password, deeds, a team
// it alone owns and a session is re-marked and keeps all of it but
// the password and the session it opened.
if err := db.AddAccount("zeitgeist", "a-long-enough-password", true); err != nil {
t.Fatal(err)
}
if err := db.SetOwner("pub", "zeitgeist"); err != nil {
t.Fatal(err)
}
if err := db.AddTeam("ops", "Ops", "", "zeitgeist"); err != nil {
t.Fatal(err)
}
if err := db.Grant("zeitgeist", "priv", RepoAdmin); err != nil {
t.Fatal(err)
}
session, _, ok := db.NewSession("zeitgeist", "a-long-enough-password")
if !ok {
t.Fatal("no session before the re-mark")
}
if err := db.SetForgeAgent("zeitgeist", "root"); err != nil {
t.Fatalf("FAIL: re-mark refused: %v", err)
}
z, _ := db.AccountByName("zeitgeist")
if z.Operator != "forge" || z.Answerable != "root" || !z.Admin {
t.Errorf("FAIL: re-marked reads %+v", z)
}
if owner, _ := db.OwnerOf("pub"); owner != "zeitgeist" {
t.Errorf("FAIL: the deed moved to %q", owner)
}
if _, ok := db.Authenticate("zeitgeist", "a-long-enough-password"); ok {
t.Error("FAIL: the re-marked forge agent still signs in with its password")
}
if _, _, _, ok := db.SessionPrincipal(session); ok {
t.Error("FAIL: the password session outlived the re-mark")
}
if !db.EffectiveCaps("zeitgeist", "").HasAny("s") {
t.Error("FAIL: the re-marked agent lost s")
}
if !db.EffectiveCaps("zeitgeist", "priv").HasAny("a") {
t.Error("FAIL: the re-marked agent lost its grant")
}
// No operator term: a grant on the agent resolves as given, with
// nobody's letters to intersect.
if err := db.Grant("fbot", "priv", Write); err != nil {
t.Fatal(err)
}
if got := letters(db.EffectiveCaps("fbot", "priv")); !db.EffectiveCaps("fbot", "priv").HasAny("i") {
t.Errorf("FAIL: forge agent on priv resolves to %q, not developer", got)
}
// A forge agent operating a person's agent is the one holding
// refused on re-mark.
if err := db.NewAgent("jva-bot", "jva", -1); err != nil {
t.Fatal(err)
}
if err := db.SetForgeAgent("jva", "root"); !IsRefusal(err) {
t.Errorf("FAIL: an operator of agents became a forge agent: %v", err)
}
// An admin cannot answer for itself, and an admin who answers for
// forge agents is not re-marked out from under them.
if err := db.AddAccount("admin4", "a-long-enough-password", true); err != nil {
t.Fatal(err)
}
if err := db.SetForgeAgent("admin4", "admin4"); !IsRefusal(err) {
t.Errorf("FAIL: an account answered for itself: %v", err)
}
if a, _ := db.AccountByName("admin4"); a.Operator != "" || a.Answerable != "" {
t.Errorf("FAIL: the refused re-mark wrote %+v", a)
}
if err := db.SetForgeAgent("root", "admin4"); !IsRefusal(err) || !strings.Contains(err.Error(), "fbot") {
t.Errorf("FAIL: the admin answering for fbot was re-marked, or the refusal does not name fbot: %v", err)
}
// Deeds: SetOwner accepts a forge agent.
if err := db.SetOwner("priv", "fbot"); err != nil {
t.Errorf("FAIL: SetOwner refused a forge agent: %v", err)
} else {
t.Log("ok: created and re-marked with an answerable admin, holdings and s kept, deeded")
}
}
func TestForgeAgentAnswerableAndItsLoss(t *testing.T) {
db := agentRig(t)
if err := db.AddAccount("root2", "a-long-enough-password", true); err != nil {
t.Fatal(err)
}
if err := db.NewForgeAgent("fbot", "root"); err != nil {
t.Fatal(err)
}
if err := db.SetAnswerable("dmestas-coder", "root"); !IsRefusal(err) {
t.Errorf("FAIL: a person's agent took an answerable admin: %v", err)
}
if err := db.SetAnswerable("fbot", "jva"); !IsRefusal(err) {
t.Errorf("FAIL: a person without the flag became answerable: %v", err)
}
if err := db.SetAnswerable("fbot", "root2"); err != nil {
t.Fatalf("FAIL: %v", err)
}
if got := db.Answerable("fbot"); got != "root2" {
t.Errorf("FAIL: answerable reads %q", got)
}
v, _, err := db.MintToken("fbot", TokenSpec{Name: "s", Scopes: []string{ScopeAPI}})
if err != nil {
t.Fatal(err)
}
// Disabling the answerable admin leaves the agent working and
// names nobody; re-enabling names them again.
if err := db.SetDisabled("root2", true); err != nil {
t.Fatal(err)
}
if a, _ := db.AccountByName("fbot"); a.Disabled != "" || a.Suspended != "" {
t.Errorf("FAIL: the agent was shut with its admin: %+v", a)
}
if _, ok := db.ResolveToken("fbot", v, ScopeAPI); !ok {
t.Error("FAIL: the agent's token stopped with its admin")
}
if got := db.Answerable("fbot"); got != "" {
t.Errorf("FAIL: a disabled admin still answers: %q", got)
}
if err := db.SetDisabled("root2", false); err != nil {
t.Fatal(err)
}
if got := db.Answerable("fbot"); got != "root2" {
t.Errorf("FAIL: the reopened admin does not answer: %q", got)
}
// Deleting the admin clears the stored name; the agent stays.
if err := db.DeleteAccount("root2"); err != nil {
t.Fatal(err)
}
if a, ok := db.AccountByName("fbot"); !ok || a.Answerable != "" || a.Disabled != "" {
t.Errorf("FAIL: after the admin's deletion the agent reads %+v", a)
}
if _, ok := db.ResolveToken("fbot", v, ScopeAPI); !ok {
t.Error("FAIL: the agent's token stopped with its admin's deletion")
} else {
t.Log("ok: answerable reassigned by any admin; its loss leaves the agent working, answering to nobody")
}
// Clearing the mark drops the answerable name with it.
if err := db.SetOperator("fbot", ""); err != nil {
t.Fatal(err)
}
if a, _ := db.AccountByName("fbot"); a.Operator != "" || a.Answerable != "" {
t.Errorf("FAIL: cleared mark reads %+v", a)
}
}
// The two a guards hold for a forge agent with its answerable admin in
// the operator's place.
func TestForgeAgentGuardsUseTheAnswerableAdmin(t *testing.T) {
db := agentRig(t)
if err := db.NewForgeAgent("fbot", "root"); err != nil {
t.Fatal(err)
}
for _, who := range []string{"root", "fbot", "jva"} {
if err := db.Grant(who, "pub", RepoAdmin); err != nil {
t.Fatal(err)
}
}
if _, err := db.GrantGuarded("fbot", "root", "pub", Read); !IsRefusal(err) {
t.Errorf("FAIL: the forge agent lowered its answerable admin: %v", err)
}
if _, err := db.GrantGuarded("fbot", "jva", "pub", Read); err != nil {
t.Errorf("FAIL: the forge agent could not lower a third account: %v", err)
}
// A machine never reduces a site admin's reach or another forge
// agent's: lowering either is refused, raising passes, and a
// person's agent is held to its own rules only.
if err := db.NewForgeAgent("fbot2", "root"); err != nil {
t.Fatal(err)
}
if err := db.AddAccount("admin2", "a-long-enough-password", true); err != nil {
t.Fatal(err)
}
for _, who := range []string{"admin2", "fbot2"} {
if err := db.Grant(who, "pub", RepoAdmin); err != nil {
t.Fatal(err)
}
if _, err := db.GrantGuarded("fbot", who, "pub", Read); !IsRefusal(err) {
t.Errorf("FAIL: the forge agent lowered %s: %v", who, err)
}
if _, err := db.GrantGuarded("fbot", who, "pub", NoRole); !IsRefusal(err) {
t.Errorf("FAIL: the forge agent revoked %s: %v", who, err)
}
if c, _ := db.CapsOf(who, "pub"); c != "a" {
t.Errorf("FAIL: %s on pub reads %q after the refusals", who, c)
}
}
if err := db.Grant("admin2", "priv", Read); err != nil {
t.Fatal(err)
}
if err := db.Grant("fbot2", "priv", RepoAdmin); err != nil {
t.Fatal(err)
}
if _, err := db.GrantGuarded("fbot2", "admin2", "priv", RepoAdmin); err != nil {
t.Errorf("FAIL: the forge agent could not raise a site admin: %v", err)
}
// Who answers resolves as the badge does, but a site admin's
// letters are shielded shut or open: disabling root does not open
// root's letters to the agent, and the refusal names the flag,
// not the answerable role. An agent's edge is Forbidden, under
// IsRefusal as well.
if err := db.SetDisabled("root", true); err != nil {
t.Fatal(err)
}
if _, err := db.GrantGuarded("fbot", "root", "pub", Read); !IsForbidden(err) || !IsRefusal(err) || !strings.Contains(err.Error(), "a site admin") {
t.Errorf("FAIL: a disabled site admin's letters were lowered, or the refusal is not Forbidden: %v", err)
}
if err := db.SetDisabled("root", false); err != nil {
t.Fatal(err)
}
// fbot holds admin on pub and nothing on priv: it cannot write
// developer on priv.
if _, err := db.SetCapsGuarded("fbot", "jva", "priv", Write.Caps()); !IsRefusal(err) {
t.Errorf("FAIL: the forge agent granted letters it does not resolve to: %v", err)
} else {
t.Log("ok: the guards hold with the answerable admin in the operator's place")
}
}