package web
import (
"crypto/rand"
"encoding/base64"
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"strings"
"testing"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/cisecret"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// THE ENVIRONMENTS SECTION MANAGES SECRETS WITHOUT EVER SHOWING ONE
// (RFC 0019): create an environment, save a secret into it and into the
// repository's own, see the names and never a value, delete them.
func TestEnvironmentsSectionNeverShowsAValue(t *testing.T) {
db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
key := make([]byte, 32)
rand.Read(key)
box, _ := cisecret.New(base64.StdEncoding.EncodeToString(key))
plane := &memCaps{stored: map[string]string{"dan": "a"}}
r := ops.NewRegistry()
if err := api.RegisterAll(r, api.Deps{Version: "v",
Repos: api.NewRepos(map[string]*api.Repo{"alpha": {Index: landingIndex(t, false)}}),
Roster: capsRoster{plane},
Secrets: db, SecretBox: box}); err != nil {
t.Fatal(err)
}
srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"alpha": ops.Admin}}),
WithCaps(plane)}))
t.Cleanup(srv.Close)
body := asDan(t, srv, "/r/alpha/settings")
if !strings.Contains(body, `id="environments"`) || !strings.Contains(body, "Repository secrets") || !strings.Contains(body, "${{ secrets.NAME }}") {
t.Fatalf("FAIL: the settings page has no Environments section with the repository's own secrets")
}
post := func(form url.Values) {
t.Helper()
resp := postAsDan(t, srv, "/r/alpha/settings/environments", form)
if resp.StatusCode != http.StatusSeeOther || !strings.HasSuffix(resp.Header.Get("Location"), "#environments") {
t.Fatalf("FAIL: %v answered %d -> %s", form["action"], resp.StatusCode, resp.Header.Get("Location"))
}
}
post(url.Values{"action": {"set-environment"}, "name": {"production"}, "branches": {""}})
post(url.Values{"action": {"set-secret"}, "environment": {"production"}, "name": {"CF_TOKEN"}, "value": {"very-secret-value-1"}})
post(url.Values{"action": {"set-secret"}, "environment": {""}, "name": {"SHARED"}, "value": {"very-secret-value-2"}})
post(url.Values{"action": {"set-environment"}, "name": {"preview"}, "branches": {"dev feat-*"}})
body = asDan(t, srv, "/r/alpha/settings")
if strings.Contains(body, "very-secret-value") {
t.Fatal("FAIL: the settings page shows a secret's value")
}
for _, want := range []string{`data-hook="secret-name">CF_TOKEN<`, `data-hook="secret-name">SHARED<`, "protected branches only", "branches dev feat-*", `value="dev feat-*"`} {
if !strings.Contains(body, want) {
t.Errorf("FAIL: the settings page lacks %q", want)
}
}
post(url.Values{"action": {"delete-secret"}, "environment": {"production"}, "name": {"CF_TOKEN"}})
post(url.Values{"action": {"delete-environment"}, "name": {"preview"}})
body = asDan(t, srv, "/r/alpha/settings")
if strings.Contains(body, `>CF_TOKEN<`) || strings.Contains(body, "feat-*") {
t.Error("FAIL: a deleted secret or environment is still listed")
}
log, _ := db.SettingsLog("alpha", 20)
if len(log) < 5 || log[0].Actor != "dan" {
t.Errorf("FAIL: the settings log has %d rows, latest by %q; want each change, by dan", len(log), log[0].Actor)
}
t.Log("ok: created, saved, listed by name only, deleted, and logged")
}