Skip to content

Commit 9144f3e

Browse files
earl-warrenmfenniak
authored andcommitted
fix: return on error if an LFS token cannot be parsed
Extracted from go-gitea/gitea#35708
1 parent 77cab5d commit 9144f3e

File tree

2 files changed

+83
-4
lines changed

2 files changed

+83
-4
lines changed

services/lfs/server.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -580,17 +580,14 @@ func authenticate(ctx *context.Context, repository *repo_model.Repository, autho
580580
}
581581

582582
func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repository, mode perm.AccessMode) (*user_model.User, error) {
583-
if !strings.Contains(tokenSHA, ".") {
584-
return nil, nil
585-
}
586583
token, err := jwt.ParseWithClaims(tokenSHA, &Claims{}, func(t *jwt.Token) (any, error) {
587584
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
588585
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
589586
}
590587
return setting.LFS.JWTSecretBytes, nil
591588
})
592589
if err != nil {
593-
return nil, nil
590+
return nil, errors.New("invalid token")
594591
}
595592

596593
claims, claimsOk := token.Claims.(*Claims)

services/lfs/server_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package lfs
5+
6+
import (
7+
"fmt"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
perm_model "forgejo.org/models/perm"
13+
repo_model "forgejo.org/models/repo"
14+
"forgejo.org/models/unittest"
15+
"forgejo.org/modules/setting"
16+
"forgejo.org/services/contexttest"
17+
18+
"github.com/golang-jwt/jwt/v5"
19+
"github.com/stretchr/testify/assert"
20+
"github.com/stretchr/testify/require"
21+
)
22+
23+
func TestMain(m *testing.M) {
24+
unittest.MainTest(m)
25+
}
26+
27+
type authTokenOptions struct {
28+
Op string
29+
UserID int64
30+
RepoID int64
31+
}
32+
33+
func getLFSAuthTokenWithBearer(opts authTokenOptions) (string, error) {
34+
now := time.Now()
35+
claims := Claims{
36+
RegisteredClaims: jwt.RegisteredClaims{
37+
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
38+
NotBefore: jwt.NewNumericDate(now),
39+
},
40+
RepoID: opts.RepoID,
41+
Op: opts.Op,
42+
UserID: opts.UserID,
43+
}
44+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
45+
46+
// Sign and get the complete encoded token as a string using the secret
47+
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
48+
if err != nil {
49+
return "", fmt.Errorf("failed to sign LFS JWT token: %w", err)
50+
}
51+
return "Bearer " + tokenString, nil
52+
}
53+
54+
func TestAuthenticate(t *testing.T) {
55+
require.NoError(t, unittest.PrepareTestDatabase())
56+
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
57+
58+
token2, _ := getLFSAuthTokenWithBearer(authTokenOptions{Op: "download", UserID: 2, RepoID: 1})
59+
_, token2, _ = strings.Cut(token2, " ")
60+
ctx, _ := contexttest.MockContext(t, "/")
61+
62+
t.Run("handleLFSToken", func(t *testing.T) {
63+
u, err := handleLFSToken(ctx, "", repo1, perm_model.AccessModeRead)
64+
require.Error(t, err)
65+
assert.Nil(t, u)
66+
67+
u, err = handleLFSToken(ctx, "invalid", repo1, perm_model.AccessModeRead)
68+
require.Error(t, err)
69+
assert.Nil(t, u)
70+
71+
u, err = handleLFSToken(ctx, token2, repo1, perm_model.AccessModeRead)
72+
require.NoError(t, err)
73+
assert.EqualValues(t, 2, u.ID)
74+
})
75+
76+
t.Run("authenticate", func(t *testing.T) {
77+
const prefixBearer = "Bearer "
78+
assert.False(t, authenticate(ctx, repo1, "", true, false))
79+
assert.False(t, authenticate(ctx, repo1, prefixBearer+"invalid", true, false))
80+
assert.True(t, authenticate(ctx, repo1, prefixBearer+token2, true, false))
81+
})
82+
}

0 commit comments

Comments
 (0)