internal/control/reauth_test.go

e6cd75b5f28bacf51620bb531320c30fd4e66bfd
gitbay/internal/control/reauth_test.go history · blame · raw

140 lines · 4423 bytes

3 symbols in this file
  1package control
  2
  3import (
  4	"slices"
  5	"strings"
  6	"testing"
  7	"time"
  8
  9	"gitbay.org/gitbay/internal/protocol"
 10	"gitbay.org/gitbay/internal/store"
 11)
 12
 13func TestStaleSignInBoundary(t *testing.T) {
 14	at := time.Now()
 15	if staleSignIn(at, at.Add(ReauthWindow)) {
 16		t.Error("exactly ReauthWindow counted as stale")
 17	}
 18	if !staleSignIn(at, at.Add(ReauthWindow+time.Second)) {
 19		t.Error("ReauthWindow plus a second counted as fresh")
 20	}
 21	if !staleSignIn(time.Time{}, at) {
 22		t.Error("a zero sign-in time counted as fresh")
 23	}
 24}
 25
 26// A browser session runs NeedsRecentSignIn commands only within
 27// ReauthWindow of signing in; SSH, the API and the host carry no
 28// session and are not affected (#297).
 29func TestRecentSignInGate(t *testing.T) {
 30	refusals = &refusalLimiter{seen: map[int64]*refusalWindow{}}
 31	st, repo, uid := newQueueTestRepo(t)
 32	if _, err := st.CreateUser("bob", false); err != nil {
 33		t.Fatal(err)
 34	}
 35	run := func(source string, signedIn time.Time, stdin string, argv ...string) (string, int) {
 36		c, errOut := pruneCtx(st, t.TempDir(), store.User{ID: uid, Username: "alice", SignedInAt: signedIn})
 37		c.Cfg.Limits.WriteRate = -1
 38		c.Source = source
 39		c.ViaAPI = source == SourceWeb || source == "api"
 40		c.Stdin = strings.NewReader(stdin)
 41		code := Dispatch(c, argv)
 42		return strings.TrimSpace(errOut.String()), code
 43	}
 44	fresh := time.Now().Add(-time.Minute)
 45	stale := time.Now().Add(-ReauthWindow - time.Minute)
 46	staleKey := authorizedKey(t, "stale")
 47
 48	for _, tc := range []struct {
 49		name     string
 50		signedIn time.Time
 51		stdin    string
 52		argv     []string
 53	}{
 54		{"stale keys add", stale, staleKey, []string{"keys", "add"}},
 55		{"stale token create", stale, "", []string{"token", "create", "--name", "x"}},
 56		{"stale repo access grant", stale, "", []string{"repo", "access", "grant", repo.Path(), "bob", "write"}},
 57		{"zero sign-in time", time.Time{}, authorizedKey(t, "zero"), []string{"keys", "add"}},
 58	} {
 59		if msg, code := run(SourceWeb, tc.signedIn, tc.stdin, tc.argv...); code != protocol.ExitDenied || msg != ReauthRefusal {
 60			t.Errorf("%s: exit %d, %q", tc.name, code, msg)
 61		}
 62	}
 63	if msg, code := run(SourceWeb, fresh, authorizedKey(t, "fresh"), "keys", "add"); code != protocol.ExitOK {
 64		t.Fatalf("fresh session: exit %d, %q", code, msg)
 65	}
 66	// SSH, the API and the host have no session; a zero SignedInAt is
 67	// what they carry.
 68	for _, source := range []string{"SHA256:abc", "api", "host"} {
 69		if msg, code := run(source, time.Time{}, authorizedKey(t, source), "keys", "add"); code != protocol.ExitOK {
 70			t.Fatalf("%s: exit %d, %q", source, code, msg)
 71		}
 72	}
 73	// A command that grants nothing is not held back.
 74	if msg, code := run(SourceWeb, stale, "", "keys", "list"); code != protocol.ExitOK {
 75		t.Fatalf("keys list on a stale session: exit %d, %q", code, msg)
 76	}
 77	keys, err := st.ListSSHKeys(uid)
 78	if err != nil || len(keys) != 4 {
 79		t.Fatalf("keys: %d %v, want the fresh, ssh, api and host ones", len(keys), err)
 80	}
 81	got, err := st.AuditEntries(store.AuditFilter{ActionPrefix: "refused ", Limit: 10})
 82	if err != nil {
 83		t.Fatal(err)
 84	}
 85	if len(got) != 4 {
 86		t.Fatalf("refusal audit rows: %+v", got)
 87	}
 88	keyText := strings.Fields(staleKey)[1]
 89	for _, e := range got {
 90		if strings.Contains(e.Data, keyText) {
 91			t.Errorf("%s kept the key: %s", e.Action, e.Data)
 92		}
 93	}
 94}
 95
 96// The set of commands a stale web session is refused. Adding one is a
 97// decision; it shows up here.
 98func TestNeedsRecentSignInSet(t *testing.T) {
 99	var got []string
100	for _, cmd := range Commands() {
101		if cmd.MintsCredential && !cmd.NeedsRecentSignIn {
102			t.Errorf("%s mints a credential without NeedsRecentSignIn", joinPath(cmd.Path))
103		}
104		if cmd.NeedsRecentSignIn {
105			got = append(got, joinPath(cmd.Path))
106		}
107	}
108	slices.Sort(got)
109	want := []string{
110		"account delete", "admin email verify",
111		"admin invite",
112		"admin repo visibility",
113		"admin user create",
114		"admin user enable",
115		"admin user promote",
116		"email verify",
117		"keys add",
118		"notifications device add",
119		"org members add",
120		"org settings members-role",
121		"org team add",
122		"org team grant",
123		"pgp add",
124		"repo access grant",
125		"repo delete",
126		"repo deploy-key add",
127		"repo mirror add",
128		"repo rename",
129		"repo runner add",
130		"repo secret set",
131		"repo settings visibility",
132		"repo transfer",
133		"token create",
134		"web login",
135		"webhook add",
136	}
137	if !slices.Equal(got, want) {
138		t.Fatalf("NeedsRecentSignIn commands:\n got %q\nwant %q", got, want)
139	}
140}