package main
import (
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/ncruces/go-sqlite3/driver"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// AN AGENT'S PASSWORD OPENS NO DOOR; ITS TOKEN OPENS EACH ONE IT IS
// SCOPED FOR (RFC 0021, cutover). The agent here still holds the
// password it had before marking cleared one, as a row from before
// rung 3 does, so the refusal is the doors' and not a missing hash.
func TestAgentPasswordRefusedAtTheHTTPDoors(t *testing.T) {
path := filepath.Join(t.TempDir(), "oltp.db")
db, err := oltp.Open(path)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
rg := ®istrar{odb: db, deps: &api.Deps{Repos: api.NewRepos(nil)}, repoQuota: 25}
const pw = "pw12345678"
for _, who := range []string{"jan", "jan-bot"} {
if err := db.AddAccount(who, pw, false); err != nil {
t.Fatal(err)
}
}
if err := db.AddRepo("vault", t.TempDir(), "", oltp.Private); err != nil {
t.Fatal(err)
}
for _, who := range []string{"jan", "jan-bot"} {
if err := db.SetCaps(who, "vault", "oi"); err != nil {
t.Fatal(err)
}
}
rg.refreshPrivate()
raw, err := driver.Open("file:" + path)
if err != nil {
t.Fatal(err)
}
defer raw.Close()
if _, err := raw.Exec(`UPDATE account SET operator = 'jan' WHERE name = 'jan-bot'`); err != nil {
t.Fatal(err)
}
syncTok, _, err := db.MintToken("jan-bot", oltp.TokenSpec{Name: "push", Scopes: []string{oltp.ScopeSync}})
if err != nil {
t.Fatal(err)
}
apiTok, _, err := db.MintToken("jan-bot", oltp.TokenSpec{Name: "api", Scopes: []string{oltp.ScopeAPI}})
if err != nil {
t.Fatal(err)
}
doors := &tokenDoors{db: db}
// main.go's byPassword with accounts: the password is Authenticate's.
byPassword := func(repo, user, pass string, need ops.Auth) api.Decision {
if _, ok := db.Authenticate(user, pass); !ok {
return api.Denied
}
return api.Allowed
}
apiDoor := doors.verify(oltp.ScopeAPI, byPassword)
webDoor := doors.verify(oltp.ScopeWeb, byPassword)
basic := func(user, secret string) *http.Request {
req := httptest.NewRequest(http.MethodGet, "/xfer/vault", nil)
req.SetBasicAuth(user, secret)
return req
}
for _, c := range []struct {
door string
got bool
want bool
}{
{"api password", apiDoor("vault", "jan-bot", pw, ops.Token) == api.Allowed, false},
{"web password", webDoor("vault", "jan-bot", pw, ops.Token) == api.Allowed, false},
{"xfer front password", rg.SyncReadGate(basic("jan-bot", pw), "vault", ""), false},
{"sync identity password", ops.User(syncIdentityCtx(basic("jan-bot", pw), doors)) != "", false},
{"api token", apiDoor("vault", "jan-bot", apiTok, ops.Token) == api.Allowed, true},
{"xfer front token", rg.SyncReadGate(basic("jan-bot", syncTok), "vault", ""), true},
{"sync identity token", ops.User(syncIdentityCtx(basic("jan-bot", syncTok), doors)) == "jan-bot", true},
{"person api password", apiDoor("vault", "jan", pw, ops.Token) == api.Allowed, true},
{"person xfer front password", rg.SyncReadGate(basic("jan", pw), "vault", ""), true},
} {
if c.got != c.want {
t.Errorf("FAIL: %s: admitted = %v, want %v", c.door, c.got, c.want)
} else {
t.Logf("ok: %s: admitted = %v", c.door, c.got)
}
}
}