internal/control/reauth_test.go
140 lines · 4423 bytes
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}