Commit ceafd97e30
ceafd97e30ad65504e3fe2e784300f17b21d946c
parent: bd5cf5d7d1
Verified · cmc ci/build: success ci/test: failure
cmc <hello@cleberg.net> · 2026-09-29 00:25 UTC
lfs: refuse an upload token once its repository is archived
Ref #285
Layout: unified · split
CHANGELOG.org
+2 −1
| @@ -276,7 +276,8 @@ missing, =gitbayd admin backup --verify <archive>= names it, and |
| 276 | - Every LFS request also repeats the repository check for the token's |
276 | - Every LFS request also repeats the repository check for the token's |
| 277 | key: a collaborator whose access is revoked or reduced, or a reader |
277 | key: a collaborator whose access is revoked or reduced, or a reader |
| 278 | of a public repository made private, loses the token's use with the |
278 | of a public repository made private, loses the token's use with the |
| 279 | access (#285). |
279 | access, and an upload token stops working once its repository is |
| |
280 | archived (#285). |
| 280 | |
281 | |
| 281 | * v1.36.0 — 2026-09-23 |
282 | * v1.36.0 — 2026-09-23 |
| 282 | |
283 | |
internal/httpd/lfs.go
+4 −1
| @@ -75,8 +75,11 @@ func (s *Server) lfsAuth(r *http.Request, repo store.Repo) (string, int64) { |
| 75 | |
75 | |
| 76 | // lfsKeyAllows repeats git-lfs-authenticate's access check for the key |
76 | // lfsKeyAllows repeats git-lfs-authenticate's access check for the key |
| 77 | // now: a deploy key by its binding, any other key by its account's |
77 | // now: a deploy key by its binding, any other key by its account's |
| 78 | // access narrowed by the key's scope. |
78 | // access narrowed by the key's scope. An archived repo takes no uploads. |
| 79 | func (s *Server) lfsKeyAllows(keyID int64, repo store.Repo, write bool) bool { |
79 | func (s *Server) lfsKeyAllows(keyID int64, repo store.Repo, write bool) bool { |
| |
80 | if write && repo.Settings.Archived { |
| |
81 | return false |
| |
82 | } |
| 80 | key, err := s.st.SSHKeyByID(keyID) |
83 | key, err := s.st.SSHKeyByID(keyID) |
| 81 | if err != nil { |
84 | if err != nil { |
| 82 | return false |
85 | return false |
internal/httpd/lfsauth_test.go
+30
| @@ -222,3 +222,33 @@ func TestLFSDeployKeyToken(t *testing.T) { |
| 222 | t.Errorf("deploy key of a disabled account: %q", op) |
222 | t.Errorf("deploy key of a disabled account: %q", op) |
| 223 | } |
223 | } |
| 224 | } |
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. |
| |
228 | func 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, "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, "download", now)), repo); op != "download" { |
| |
252 | t.Errorf("download after archiving: %q", op) |
| |
253 | } |
| |
254 | } |