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 | 276 | - Every LFS request also repeats the repository check for the token's |
| 277 | 277 | key: a collaborator whose access is revoked or reduced, or a reader |
| 278 | 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 | 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 | 76 | // lfsKeyAllows repeats git-lfs-authenticate's access check for the key |
| 77 | 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 | 79 | func (s *Server) lfsKeyAllows(keyID int64, repo store.Repo, write bool) bool { |
| 80 | if write && repo.Settings.Archived { |
| 81 | return false |
| 82 | } |
| 80 | 83 | key, err := s.st.SSHKeyByID(keyID) |
| 81 | 84 | if err != nil { |
| 82 | 85 | return false |
internal/httpd/lfsauth_test.go
+30
| @@ -222,3 +222,33 @@ func TestLFSDeployKeyToken(t *testing.T) { |
| 222 | 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 | } |