@@ -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
3840const  (
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
7581var  (
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+ 
127178func  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 )
0 commit comments