Skip to content

Commit 18c6b9b

Browse files
authored
Merge branch 'main' into main
2 parents 8883fad + a989404 commit 18c6b9b

File tree

97 files changed

+3168
-1128
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+3168
-1128
lines changed

assets/go-licenses.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/generate-licenses.go

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
// Copyright 2017 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
14
//go:build ignore
25

36
package main
47

58
import (
69
"archive/tar"
710
"compress/gzip"
11+
"crypto/md5"
12+
"encoding/hex"
813
"flag"
914
"fmt"
1015
"io"
@@ -15,6 +20,8 @@ import (
1520
"path/filepath"
1621
"strings"
1722

23+
"code.gitea.io/gitea/build/license"
24+
"code.gitea.io/gitea/modules/json"
1825
"code.gitea.io/gitea/modules/util"
1926
)
2027

@@ -77,7 +84,7 @@ func main() {
7784
}
7885

7986
tr := tar.NewReader(gz)
80-
87+
aliasesFiles := make(map[string][]string)
8188
for {
8289
hdr, err := tr.Next()
8390

@@ -97,26 +104,73 @@ func main() {
97104
continue
98105
}
99106

100-
if strings.HasPrefix(filepath.Base(hdr.Name), "README") {
107+
fileBaseName := filepath.Base(hdr.Name)
108+
licenseName := strings.TrimSuffix(fileBaseName, ".txt")
109+
110+
if strings.HasPrefix(fileBaseName, "README") {
101111
continue
102112
}
103113

104-
if strings.HasPrefix(filepath.Base(hdr.Name), "deprecated_") {
114+
if strings.HasPrefix(fileBaseName, "deprecated_") {
105115
continue
106116
}
107-
out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".txt")))
117+
out, err := os.Create(path.Join(destination, licenseName))
108118
if err != nil {
109119
log.Fatalf("Failed to create new file. %s", err)
110120
}
111121

112122
defer out.Close()
113123

114-
if _, err := io.Copy(out, tr); err != nil {
124+
// some license files have same content, so we need to detect these files and create a convert map into a json file
125+
// Later we use this convert map to avoid adding same license content with different license name
126+
h := md5.New()
127+
// calculate md5 and write file in the same time
128+
r := io.TeeReader(tr, h)
129+
if _, err := io.Copy(out, r); err != nil {
115130
log.Fatalf("Failed to write new file. %s", err)
116131
} else {
117132
fmt.Printf("Written %s\n", out.Name())
133+
134+
md5 := hex.EncodeToString(h.Sum(nil))
135+
aliasesFiles[md5] = append(aliasesFiles[md5], licenseName)
118136
}
119137
}
120138

139+
// generate convert license name map
140+
licenseAliases := make(map[string]string)
141+
for _, fileNames := range aliasesFiles {
142+
if len(fileNames) > 1 {
143+
licenseName := license.GetLicenseNameFromAliases(fileNames)
144+
if licenseName == "" {
145+
// license name should not be empty as expected
146+
// if it is empty, we need to rewrite the logic of GetLicenseNameFromAliases
147+
log.Fatalf("GetLicenseNameFromAliases: license name is empty")
148+
}
149+
for _, fileName := range fileNames {
150+
licenseAliases[fileName] = licenseName
151+
}
152+
}
153+
}
154+
// save convert license name map to file
155+
b, err := json.Marshal(licenseAliases)
156+
if err != nil {
157+
log.Fatalf("Failed to create json bytes. %s", err)
158+
}
159+
160+
licenseAliasesDestination := filepath.Join(destination, "etc", "license-aliases.json")
161+
if err := os.MkdirAll(filepath.Dir(licenseAliasesDestination), 0o755); err != nil {
162+
log.Fatalf("Failed to create directory for license aliases json file. %s", err)
163+
}
164+
165+
f, err := os.Create(licenseAliasesDestination)
166+
if err != nil {
167+
log.Fatalf("Failed to create license aliases json file. %s", err)
168+
}
169+
defer f.Close()
170+
171+
if _, err = f.Write(b); err != nil {
172+
log.Fatalf("Failed to write license aliases json file. %s", err)
173+
}
174+
121175
fmt.Println("Done")
122176
}

build/license/aliasgenerator.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package license
5+
6+
import "strings"
7+
8+
func GetLicenseNameFromAliases(fnl []string) string {
9+
if len(fnl) == 0 {
10+
return ""
11+
}
12+
13+
shortestItem := func(list []string) string {
14+
s := list[0]
15+
for _, l := range list[1:] {
16+
if len(l) < len(s) {
17+
s = l
18+
}
19+
}
20+
return s
21+
}
22+
allHasPrefix := func(list []string, s string) bool {
23+
for _, l := range list {
24+
if !strings.HasPrefix(l, s) {
25+
return false
26+
}
27+
}
28+
return true
29+
}
30+
31+
sl := shortestItem(fnl)
32+
slv := strings.Split(sl, "-")
33+
var result string
34+
for i := len(slv); i >= 0; i-- {
35+
result = strings.Join(slv[:i], "-")
36+
if allHasPrefix(fnl, result) {
37+
return result
38+
}
39+
}
40+
return ""
41+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package license
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestGetLicenseNameFromAliases(t *testing.T) {
13+
tests := []struct {
14+
target string
15+
inputs []string
16+
}{
17+
{
18+
// real case which you can find in license-aliases.json
19+
target: "AGPL-1.0",
20+
inputs: []string{
21+
"AGPL-1.0-only",
22+
"AGPL-1.0-or-late",
23+
},
24+
},
25+
{
26+
target: "",
27+
inputs: []string{
28+
"APSL-1.0",
29+
"AGPL-1.0-only",
30+
"AGPL-1.0-or-late",
31+
},
32+
},
33+
}
34+
35+
for _, tt := range tests {
36+
result := GetLicenseNameFromAliases(tt.inputs)
37+
assert.Equal(t, result, tt.target)
38+
}
39+
}

cmd/serv.go

Lines changed: 85 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
asymkey_model "code.gitea.io/gitea/models/asymkey"
2121
git_model "code.gitea.io/gitea/models/git"
2222
"code.gitea.io/gitea/models/perm"
23+
"code.gitea.io/gitea/modules/container"
2324
"code.gitea.io/gitea/modules/git"
2425
"code.gitea.io/gitea/modules/json"
26+
"code.gitea.io/gitea/modules/lfstransfer"
2527
"code.gitea.io/gitea/modules/log"
2628
"code.gitea.io/gitea/modules/pprof"
2729
"code.gitea.io/gitea/modules/private"
@@ -36,7 +38,11 @@ import (
3638
)
3739

3840
const (
39-
lfsAuthenticateVerb = "git-lfs-authenticate"
41+
verbUploadPack = "git-upload-pack"
42+
verbUploadArchive = "git-upload-archive"
43+
verbReceivePack = "git-receive-pack"
44+
verbLfsAuthenticate = "git-lfs-authenticate"
45+
verbLfsTransfer = "git-lfs-transfer"
4046
)
4147

4248
// CmdServ represents the available serv sub-command.
@@ -73,12 +79,18 @@ func setup(ctx context.Context, debug bool) {
7379
}
7480

7581
var (
76-
allowedCommands = map[string]perm.AccessMode{
77-
"git-upload-pack": perm.AccessModeRead,
78-
"git-upload-archive": perm.AccessModeRead,
79-
"git-receive-pack": perm.AccessModeWrite,
80-
lfsAuthenticateVerb: perm.AccessModeNone,
81-
}
82+
// keep getAccessMode() in sync
83+
allowedCommands = container.SetOf(
84+
verbUploadPack,
85+
verbUploadArchive,
86+
verbReceivePack,
87+
verbLfsAuthenticate,
88+
verbLfsTransfer,
89+
)
90+
allowedCommandsLfs = container.SetOf(
91+
verbLfsAuthenticate,
92+
verbLfsTransfer,
93+
)
8294
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
8395
)
8496

@@ -124,6 +136,45 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
124136
return nil
125137
}
126138

139+
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
140+
switch verb {
141+
case verbUploadPack, verbUploadArchive:
142+
return perm.AccessModeRead
143+
case verbReceivePack:
144+
return perm.AccessModeWrite
145+
case verbLfsAuthenticate, verbLfsTransfer:
146+
switch lfsVerb {
147+
case "upload":
148+
return perm.AccessModeWrite
149+
case "download":
150+
return perm.AccessModeRead
151+
}
152+
}
153+
// should be unreachable
154+
return perm.AccessModeNone
155+
}
156+
157+
func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServCommandResults) (string, error) {
158+
now := time.Now()
159+
claims := lfs.Claims{
160+
RegisteredClaims: jwt.RegisteredClaims{
161+
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
162+
NotBefore: jwt.NewNumericDate(now),
163+
},
164+
RepoID: results.RepoID,
165+
Op: lfsVerb,
166+
UserID: results.UserID,
167+
}
168+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
169+
170+
// Sign and get the complete encoded token as a string using the secret
171+
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
172+
if err != nil {
173+
return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
174+
}
175+
return fmt.Sprintf("Bearer %s", tokenString), nil
176+
}
177+
127178
func runServ(c *cli.Context) error {
128179
ctx, cancel := installSignals()
129180
defer cancel()
@@ -198,15 +249,6 @@ func runServ(c *cli.Context) error {
198249
repoPath := strings.TrimPrefix(words[1], "/")
199250

200251
var lfsVerb string
201-
if verb == lfsAuthenticateVerb {
202-
if !setting.LFS.StartServer {
203-
return fail(ctx, "Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
204-
}
205-
206-
if len(words) > 2 {
207-
lfsVerb = words[2]
208-
}
209-
}
210252

211253
rr := strings.SplitN(repoPath, "/", 2)
212254
if len(rr) != 2 {
@@ -243,53 +285,52 @@ func runServ(c *cli.Context) error {
243285
}()
244286
}
245287

246-
requestedMode, has := allowedCommands[verb]
247-
if !has {
288+
if allowedCommands.Contains(verb) {
289+
if allowedCommandsLfs.Contains(verb) {
290+
if !setting.LFS.StartServer {
291+
return fail(ctx, "Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
292+
}
293+
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
294+
return fail(ctx, "Unknown git command", "LFS SSH transfer connection denied, pure SSH protocol is disabled")
295+
}
296+
if len(words) > 2 {
297+
lfsVerb = words[2]
298+
}
299+
}
300+
} else {
248301
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
249302
}
250303

251-
if verb == lfsAuthenticateVerb {
252-
if lfsVerb == "upload" {
253-
requestedMode = perm.AccessModeWrite
254-
} else if lfsVerb == "download" {
255-
requestedMode = perm.AccessModeRead
256-
} else {
257-
return fail(ctx, "Unknown LFS verb", "Unknown lfs verb %s", lfsVerb)
258-
}
259-
}
304+
requestedMode := getAccessMode(verb, lfsVerb)
260305

261306
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
262307
if extra.HasError() {
263308
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
264309
}
265310

311+
// LFS SSH protocol
312+
if verb == verbLfsTransfer {
313+
token, err := getLFSAuthToken(ctx, lfsVerb, results)
314+
if err != nil {
315+
return err
316+
}
317+
return lfstransfer.Main(ctx, repoPath, lfsVerb, token)
318+
}
319+
266320
// LFS token authentication
267-
if verb == lfsAuthenticateVerb {
321+
if verb == verbLfsAuthenticate {
268322
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
269323

270-
now := time.Now()
271-
claims := lfs.Claims{
272-
RegisteredClaims: jwt.RegisteredClaims{
273-
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
274-
NotBefore: jwt.NewNumericDate(now),
275-
},
276-
RepoID: results.RepoID,
277-
Op: lfsVerb,
278-
UserID: results.UserID,
279-
}
280-
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
281-
282-
// Sign and get the complete encoded token as a string using the secret
283-
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
324+
token, err := getLFSAuthToken(ctx, lfsVerb, results)
284325
if err != nil {
285-
return fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
326+
return err
286327
}
287328

288329
tokenAuthentication := &git_model.LFSTokenResponse{
289330
Header: make(map[string]string),
290331
Href: url,
291332
}
292-
tokenAuthentication.Header["Authorization"] = fmt.Sprintf("Bearer %s", tokenString)
333+
tokenAuthentication.Header["Authorization"] = token
293334

294335
enc := json.NewEncoder(os.Stdout)
295336
err = enc.Encode(tokenAuthentication)

custom/conf/app.example.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ RUN_USER = ; git
306306
;; Enables git-lfs support. true or false, default is false.
307307
;LFS_START_SERVER = false
308308
;;
309+
;; Enables git-lfs SSH protocol support. true or false, default is false.
310+
;LFS_ALLOW_PURE_SSH = false
309311
;;
310312
;; LFS authentication secret, change this yourself
311313
;LFS_JWT_SECRET =

0 commit comments

Comments
 (0)