internal/httpd/lfsauth_test.go
282 lines · 9368 bytes
1package httpd
2
3import (
4 "fmt"
5 "net/http"
6 "net/http/httptest"
7 "testing"
8 "time"
9
10 "gitbay.org/gitbay/internal/lfs"
11 "gitbay.org/gitbay/internal/store"
12)
13
14func lfsRequest(tok string) *http.Request {
15 r := httptest.NewRequest("GET", "/alice/app.git/info/lfs/objects/x", nil)
16 if tok != "" {
17 r.Header.Set("Authorization", "Bearer "+tok)
18 }
19 return r
20}
21
22func lfsTestRepo(t *testing.T, st *store.Store, uid int64, name, visibility string) store.Repo {
23 t.Helper()
24 id, err := st.CreateRepo("user", uid, name, visibility)
25 if err != nil {
26 t.Fatal(err)
27 }
28 repo, err := st.RepoByID(id)
29 if err != nil {
30 t.Fatal(err)
31 }
32 return repo
33}
34
35// A token works only while its key does: removed, expired or on a
36// disabled account, the key takes its tokens with it (#285).
37func TestLFSTokenNeedsALiveKey(t *testing.T) {
38 s, st, u := newTokenTestServer(t)
39 repo := lfsTestRepo(t, st, u.ID, "app", "private")
40 secret, err := s.lfsSecret()
41 if err != nil {
42 t.Fatal(err)
43 }
44 addKey := func(fp string, exp *time.Time) int64 {
45 t.Helper()
46 if err := st.AddSSHKeyFrom(u.ID, fp, "ssh-ed25519", []byte(fp), "full", "", store.KeyOrigin{ExpiresAt: exp}); err != nil {
47 t.Fatal(err)
48 }
49 k, err := st.SSHKeyByFingerprint(fp)
50 if err != nil {
51 t.Fatal(err)
52 }
53 return k.ID
54 }
55
56 live := addKey("SHA256:live", nil)
57 tok := lfs.Sign(secret, repo.ID, live, "SHA256:live", "upload", time.Now())
58 if op, key := s.lfsAuth(lfsRequest(tok), repo); op != "upload" || key != live {
59 t.Fatalf("live key: %q, %d", op, key)
60 }
61
62 past := time.Now().Add(-time.Minute)
63 expired := addKey("SHA256:expired", &past)
64 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, repo.ID, expired, "SHA256:expired", "upload", time.Now())), repo); op != "" {
65 t.Errorf("expired key: %q", op)
66 }
67
68 if err := st.RemoveSSHKey(u.ID, "SHA256:live"); err != nil {
69 t.Fatal(err)
70 }
71 if op, _ := s.lfsAuth(lfsRequest(tok), repo); op != "" {
72 t.Errorf("removed key: %q", op)
73 }
74
75 other := addKey("SHA256:other", nil)
76 otherTok := lfs.Sign(secret, repo.ID, other, "SHA256:other", "download", time.Now())
77 if op, _ := s.lfsAuth(lfsRequest(otherTok), repo); op != "download" {
78 t.Fatalf("second key before disable: %q", op)
79 }
80 if err := st.SetUserDisabled(u.ID, true); err != nil {
81 t.Fatal(err)
82 }
83 if op, _ := s.lfsAuth(lfsRequest(otherTok), repo); op != "" {
84 t.Errorf("disabled account: %q", op)
85 }
86}
87
88// A token with no key comes from an anonymous batch on a public
89// repository and is worth exactly what anonymous is: a download, while
90// the repository is public.
91func TestLFSAnonymousTokenOnlyDownloadsPublic(t *testing.T) {
92 s, st, u := newTokenTestServer(t)
93 pub := lfsTestRepo(t, st, u.ID, "big", "public")
94 priv := lfsTestRepo(t, st, u.ID, "vault", "private")
95 secret, err := s.lfsSecret()
96 if err != nil {
97 t.Fatal(err)
98 }
99 now := time.Now()
100 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, pub.ID, 0, "", "download", now)), pub); op != "download" {
101 t.Errorf("public download: %q", op)
102 }
103 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, pub.ID, 0, "", "upload", now)), pub); op != "" {
104 t.Errorf("anonymous upload: %q", op)
105 }
106 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, priv.ID, 0, "", "download", now)), priv); op != "" {
107 t.Errorf("private download: %q", op)
108 }
109}
110
111func lfsTestKey(t *testing.T, st *store.Store, uid int64, fp, scope string) int64 {
112 t.Helper()
113 if err := st.AddSSHKey(uid, fp, "ssh-ed25519", []byte(fp), scope, ""); err != nil {
114 t.Fatal(err)
115 }
116 k, err := st.SSHKeyByFingerprint(fp)
117 if err != nil {
118 t.Fatal(err)
119 }
120 return k.ID
121}
122
123// A token carries only the access its key's account still has: a
124// collaborator removed from the repository, or a reader of a public
125// repository made private, loses the token with the access (#285).
126func TestLFSTokenNeedsCurrentAccess(t *testing.T) {
127 s, st, u := newTokenTestServer(t)
128 repo := lfsTestRepo(t, st, u.ID, "app", "private")
129 secret, err := s.lfsSecret()
130 if err != nil {
131 t.Fatal(err)
132 }
133 now := time.Now()
134
135 bob, err := st.CreateUser("bob", false)
136 if err != nil {
137 t.Fatal(err)
138 }
139 if err := st.GrantAccess(repo.ID, bob, "write"); err != nil {
140 t.Fatal(err)
141 }
142 bobKey := lfsTestKey(t, st, bob, "SHA256:bob", "full")
143 up := lfs.Sign(secret, repo.ID, bobKey, "SHA256:bob", "upload", now)
144 if op, _ := s.lfsAuth(lfsRequest(up), repo); op != "upload" {
145 t.Fatalf("collaborator upload: %q", op)
146 }
147 if err := st.GrantAccess(repo.ID, bob, "read"); err != nil {
148 t.Fatal(err)
149 }
150 if op, _ := s.lfsAuth(lfsRequest(up), repo); op != "" {
151 t.Errorf("upload after write was taken away: %q", op)
152 }
153 if err := st.RevokeAccess(repo.ID, bob); err != nil {
154 t.Fatal(err)
155 }
156 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, repo.ID, bobKey, "SHA256:bob", "download", now)), repo); op != "" {
157 t.Errorf("download after access was revoked: %q", op)
158 }
159
160 pub := lfsTestRepo(t, st, u.ID, "big", "public")
161 carol, err := st.CreateUser("carol", false)
162 if err != nil {
163 t.Fatal(err)
164 }
165 carolKey := lfsTestKey(t, st, carol, "SHA256:carol", "full")
166 down := lfs.Sign(secret, pub.ID, carolKey, "SHA256:carol", "download", now)
167 if op, _ := s.lfsAuth(lfsRequest(down), pub); op != "download" {
168 t.Fatalf("public download: %q", op)
169 }
170 if err := st.SetRepoVisibility(pub.ID, "private"); err != nil {
171 t.Fatal(err)
172 }
173 pub, err = st.RepoByID(pub.ID)
174 if err != nil {
175 t.Fatal(err)
176 }
177 if op, _ := s.lfsAuth(lfsRequest(down), pub); op != "" {
178 t.Errorf("download after the repository went private: %q", op)
179 }
180}
181
182// A deploy key's token lasts as long as the deploy key: removed from the
183// repository or on a disabled account it is refused, and a read-only
184// binding never uploads.
185func TestLFSDeployKeyToken(t *testing.T) {
186 s, st, u := newTokenTestServer(t)
187 repo := lfsTestRepo(t, st, u.ID, "app", "private")
188 secret, err := s.lfsSecret()
189 if err != nil {
190 t.Fatal(err)
191 }
192 now := time.Now()
193 rw := fmt.Sprintf("deploy:%d:rw", repo.ID)
194 ro := fmt.Sprintf("deploy:%d:ro", repo.ID)
195
196 live := lfsTestKey(t, st, u.ID, "SHA256:deploy-live", rw)
197 if op, key := s.lfsAuth(lfsRequest(lfs.Sign(secret, repo.ID, live, "SHA256:deploy-live", "upload", now)), repo); op != "upload" || key != live {
198 t.Fatalf("live deploy key: %q, %d", op, key)
199 }
200
201 removed := lfsTestKey(t, st, u.ID, "SHA256:deploy-removed", rw)
202 tok := lfs.Sign(secret, repo.ID, removed, "SHA256:deploy-removed", "download", now)
203 if err := st.RemoveDeployKey(repo.ID, "SHA256:deploy-removed"); err != nil {
204 t.Fatal(err)
205 }
206 if op, _ := s.lfsAuth(lfsRequest(tok), repo); op != "" {
207 t.Errorf("removed deploy key: %q", op)
208 }
209
210 readOnly := lfsTestKey(t, st, u.ID, "SHA256:deploy-ro", ro)
211 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, repo.ID, readOnly, "SHA256:deploy-ro", "download", now)), repo); op != "download" {
212 t.Errorf("read-only deploy key download: %q", op)
213 }
214 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, repo.ID, readOnly, "SHA256:deploy-ro", "upload", now)), repo); op != "" {
215 t.Errorf("read-only deploy key upload: %q", op)
216 }
217
218 if err := st.SetUserDisabled(u.ID, true); err != nil {
219 t.Fatal(err)
220 }
221 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, repo.ID, live, "SHA256:deploy-live", "download", now)), repo); op != "" {
222 t.Errorf("deploy key of a disabled account: %q", op)
223 }
224}
225
226// An upload token minted before the repository was archived uploads
227// nothing after it, as git-lfs-authenticate would refuse to mint one.
228func TestLFSUploadTokenRefusedOnceArchived(t *testing.T) {
229 s, st, u := newTokenTestServer(t)
230 repo := lfsTestRepo(t, st, u.ID, "app", "private")
231 secret, err := s.lfsSecret()
232 if err != nil {
233 t.Fatal(err)
234 }
235 key := lfsTestKey(t, st, u.ID, "SHA256:owner", "full")
236 now := time.Now()
237 up := lfs.Sign(secret, repo.ID, key, "SHA256:owner", "upload", now)
238 if op, _ := s.lfsAuth(lfsRequest(up), repo); op != "upload" {
239 t.Fatalf("upload before archiving: %q", op)
240 }
241 if _, err := st.UpdateRepoSettings(repo.ID, func(rs *store.RepoSettings) { rs.Archived = true }); err != nil {
242 t.Fatal(err)
243 }
244 repo, err = st.RepoByID(repo.ID)
245 if err != nil {
246 t.Fatal(err)
247 }
248 if op, _ := s.lfsAuth(lfsRequest(up), repo); op != "" {
249 t.Errorf("upload after archiving: %q", op)
250 }
251 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, repo.ID, key, "SHA256:owner", "download", now)), repo); op != "download" {
252 t.Errorf("download after archiving: %q", op)
253 }
254}
255
256// SQLite gives a new key the id of the highest deleted one. A token
257// minted for the deleted key is refused when presented against the new
258// key; the new key's own token works (#303).
259func TestLFSTokenRefusedOnReusedKeyID(t *testing.T) {
260 s, st, u := newTokenTestServer(t)
261 repo := lfsTestRepo(t, st, u.ID, "app", "private")
262 secret, err := s.lfsSecret()
263 if err != nil {
264 t.Fatal(err)
265 }
266 now := time.Now()
267 oldID := lfsTestKey(t, st, u.ID, "SHA256:old", "full")
268 old := lfs.Sign(secret, repo.ID, oldID, "SHA256:old", "upload", now)
269 if err := st.RemoveSSHKey(u.ID, "SHA256:old"); err != nil {
270 t.Fatal(err)
271 }
272 newID := lfsTestKey(t, st, u.ID, "SHA256:new", "full")
273 if newID != oldID {
274 t.Fatalf("new key got id %d, want the reused %d", newID, oldID)
275 }
276 if op, _ := s.lfsAuth(lfsRequest(old), repo); op != "" {
277 t.Errorf("old key's token on the reused id: %q", op)
278 }
279 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, repo.ID, newID, "SHA256:new", "upload", now)), repo); op != "upload" {
280 t.Errorf("new key's own token: %q", op)
281 }
282}