gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package lfs
  4. import (
  5. stdCtx "context"
  6. "crypto/sha256"
  7. "encoding/base64"
  8. "encoding/hex"
  9. "errors"
  10. "fmt"
  11. "io"
  12. "maps"
  13. "net/http"
  14. "net/url"
  15. "path"
  16. "regexp"
  17. "strconv"
  18. "strings"
  19. actions_model "code.gitea.io/gitea/models/actions"
  20. auth_model "code.gitea.io/gitea/models/auth"
  21. git_model "code.gitea.io/gitea/models/git"
  22. perm_model "code.gitea.io/gitea/models/perm"
  23. access_model "code.gitea.io/gitea/models/perm/access"
  24. repo_model "code.gitea.io/gitea/models/repo"
  25. "code.gitea.io/gitea/models/unit"
  26. user_model "code.gitea.io/gitea/models/user"
  27. "code.gitea.io/gitea/modules/auth/httpauth"
  28. "code.gitea.io/gitea/modules/json"
  29. lfs_module "code.gitea.io/gitea/modules/lfs"
  30. "code.gitea.io/gitea/modules/log"
  31. "code.gitea.io/gitea/modules/setting"
  32. "code.gitea.io/gitea/modules/storage"
  33. "code.gitea.io/gitea/services/context"
  34. "github.com/golang-jwt/jwt/v5"
  35. )
  36. // requestContext contain variables from the HTTP request.
  37. type requestContext struct {
  38. User string
  39. Repo string
  40. Authorization string
  41. Method string
  42. }
  43. // Claims is a JWT Token Claims
  44. type Claims struct {
  45. RepoID int64
  46. Op string
  47. UserID int64
  48. jwt.RegisteredClaims
  49. }
  50. // DownloadLink builds a URL to download the object.
  51. func (rc *requestContext) DownloadLink(p lfs_module.Pointer) string {
  52. return setting.AppURL + path.Join(url.PathEscape(rc.User), url.PathEscape(rc.Repo+".git"), "info/lfs/objects", url.PathEscape(p.Oid))
  53. }
  54. // UploadLink builds a URL to upload the object.
  55. func (rc *requestContext) UploadLink(p lfs_module.Pointer) string {
  56. return setting.AppURL + path.Join(url.PathEscape(rc.User), url.PathEscape(rc.Repo+".git"), "info/lfs/objects", url.PathEscape(p.Oid), strconv.FormatInt(p.Size, 10))
  57. }
  58. // VerifyLink builds a URL for verifying the object.
  59. func (rc *requestContext) VerifyLink(p lfs_module.Pointer) string {
  60. return setting.AppURL + path.Join(url.PathEscape(rc.User), url.PathEscape(rc.Repo+".git"), "info/lfs/verify")
  61. }
  62. // CheckAcceptMediaType checks if the client accepts the LFS media type.
  63. func CheckAcceptMediaType(ctx *context.Context) {
  64. mediaParts := strings.Split(ctx.Req.Header.Get("Accept"), ";")
  65. if mediaParts[0] != lfs_module.MediaType {
  66. log.Trace("Calling a LFS method without accepting the correct media type: %s", lfs_module.MediaType)
  67. writeStatus(ctx, http.StatusUnsupportedMediaType)
  68. return
  69. }
  70. }
  71. var rangeHeaderRegexp = regexp.MustCompile(`bytes=(\d+)-(\d*).*`)
  72. // DownloadHandler gets the content from the content store
  73. func DownloadHandler(ctx *context.Context) {
  74. rc := getRequestContext(ctx)
  75. p := lfs_module.Pointer{Oid: ctx.PathParam("oid")}
  76. meta := getAuthenticatedMeta(ctx, rc, p, false)
  77. if meta == nil {
  78. return
  79. }
  80. // Support resume download using Range header
  81. var fromByte, toByte int64
  82. toByte = meta.Size - 1
  83. statusCode := http.StatusOK
  84. if rangeHdr := ctx.Req.Header.Get("Range"); rangeHdr != "" {
  85. match := rangeHeaderRegexp.FindStringSubmatch(rangeHdr)
  86. if len(match) > 1 {
  87. statusCode = http.StatusPartialContent
  88. fromByte, _ = strconv.ParseInt(match[1], 10, 32)
  89. if fromByte >= meta.Size {
  90. writeStatus(ctx, http.StatusRequestedRangeNotSatisfiable)
  91. return
  92. }
  93. if match[2] != "" {
  94. _toByte, _ := strconv.ParseInt(match[2], 10, 32)
  95. if _toByte >= fromByte && _toByte < toByte {
  96. toByte = _toByte
  97. }
  98. }
  99. ctx.Resp.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", fromByte, toByte, meta.Size))
  100. ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Range")
  101. }
  102. }
  103. contentStore := lfs_module.NewContentStore()
  104. content, err := contentStore.Get(meta.Pointer)
  105. if err != nil {
  106. writeStatus(ctx, http.StatusNotFound)
  107. return
  108. }
  109. defer content.Close()
  110. if fromByte > 0 {
  111. _, err = content.Seek(fromByte, io.SeekStart)
  112. if err != nil {
  113. log.Error("Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v", meta.Oid, fromByte, err)
  114. writeStatus(ctx, http.StatusInternalServerError)
  115. return
  116. }
  117. }
  118. contentLength := toByte + 1 - fromByte
  119. contentLengthStr := strconv.FormatInt(contentLength, 10)
  120. ctx.Resp.Header().Set("Content-Length", contentLengthStr)
  121. ctx.Resp.Header().Set("X-Gitea-LFS-Content-Length", contentLengthStr) // we need this header to make sure it won't be affected by reverse proxy or compression
  122. ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
  123. filename := ctx.PathParam("filename")
  124. if len(filename) > 0 {
  125. decodedFilename, err := base64.RawURLEncoding.DecodeString(filename)
  126. if err == nil {
  127. ctx.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+string(decodedFilename)+"\"")
  128. ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
  129. }
  130. }
  131. ctx.Resp.WriteHeader(statusCode)
  132. if written, err := io.CopyN(ctx.Resp, content, contentLength); err != nil {
  133. log.Error("Error whilst copying LFS OID[%s] to the response after %d bytes. Error: %v", meta.Oid, written, err)
  134. }
  135. }
  136. // BatchHandler provides the batch api
  137. func BatchHandler(ctx *context.Context) {
  138. var br lfs_module.BatchRequest
  139. if err := decodeJSON(ctx.Req, &br); err != nil {
  140. log.Trace("Unable to decode BATCH request vars: Error: %v", err)
  141. writeStatus(ctx, http.StatusBadRequest)
  142. return
  143. }
  144. var isUpload bool
  145. switch br.Operation {
  146. case "upload":
  147. isUpload = true
  148. case "download":
  149. isUpload = false
  150. default:
  151. log.Trace("Attempt to BATCH with invalid operation: %s", br.Operation)
  152. writeStatus(ctx, http.StatusBadRequest)
  153. return
  154. }
  155. rc := getRequestContext(ctx)
  156. repository := getAuthenticatedRepository(ctx, rc, isUpload)
  157. if repository == nil {
  158. return
  159. }
  160. if setting.LFS.MaxBatchSize != 0 && len(br.Objects) > setting.LFS.MaxBatchSize {
  161. writeStatus(ctx, http.StatusRequestEntityTooLarge)
  162. return
  163. }
  164. contentStore := lfs_module.NewContentStore()
  165. var responseObjects []*lfs_module.ObjectResponse
  166. for _, p := range br.Objects {
  167. if !p.IsValid() {
  168. responseObjects = append(responseObjects, buildObjectResponse(rc, p, false, false, &lfs_module.ObjectError{
  169. Code: http.StatusUnprocessableEntity,
  170. Message: "Oid or size are invalid",
  171. }))
  172. continue
  173. }
  174. exists, err := contentStore.Exists(p)
  175. if err != nil {
  176. log.Error("Unable to check if LFS object with ID '%s' exists for %s/%s. Error: %v", p.Oid, rc.User, rc.Repo, err)
  177. writeStatus(ctx, http.StatusInternalServerError)
  178. return
  179. }
  180. meta, err := git_model.GetLFSMetaObjectByOid(ctx, repository.ID, p.Oid)
  181. if err != nil && err != git_model.ErrLFSObjectNotExist {
  182. log.Error("Unable to get LFS MetaObject [%s] for %s/%s. Error: %v", p.Oid, rc.User, rc.Repo, err)
  183. writeStatus(ctx, http.StatusInternalServerError)
  184. return
  185. }
  186. if meta != nil && p.Size != meta.Size {
  187. responseObjects = append(responseObjects, buildObjectResponse(rc, p, false, false, &lfs_module.ObjectError{
  188. Code: http.StatusUnprocessableEntity,
  189. Message: fmt.Sprintf("Object %s is not %d bytes", p.Oid, p.Size),
  190. }))
  191. continue
  192. }
  193. var responseObject *lfs_module.ObjectResponse
  194. if isUpload {
  195. var err *lfs_module.ObjectError
  196. if !exists && setting.LFS.MaxFileSize > 0 && p.Size > setting.LFS.MaxFileSize {
  197. err = &lfs_module.ObjectError{
  198. Code: http.StatusUnprocessableEntity,
  199. Message: fmt.Sprintf("Size must be less than or equal to %d", setting.LFS.MaxFileSize),
  200. }
  201. }
  202. if exists && meta == nil {
  203. accessible, err := git_model.LFSObjectAccessible(ctx, ctx.Doer, p.Oid)
  204. if err != nil {
  205. log.Error("Unable to check if LFS MetaObject [%s] is accessible. Error: %v", p.Oid, err)
  206. writeStatus(ctx, http.StatusInternalServerError)
  207. return
  208. }
  209. if accessible {
  210. _, err := git_model.NewLFSMetaObject(ctx, repository.ID, p)
  211. if err != nil {
  212. log.Error("Unable to create LFS MetaObject [%s] for %s/%s. Error: %v", p.Oid, rc.User, rc.Repo, err)
  213. writeStatus(ctx, http.StatusInternalServerError)
  214. return
  215. }
  216. } else {
  217. exists = false
  218. }
  219. }
  220. responseObject = buildObjectResponse(rc, p, false, !exists, err)
  221. } else {
  222. var err *lfs_module.ObjectError
  223. if !exists || meta == nil {
  224. err = &lfs_module.ObjectError{
  225. Code: http.StatusNotFound,
  226. Message: http.StatusText(http.StatusNotFound),
  227. }
  228. }
  229. responseObject = buildObjectResponse(rc, p, true, false, err)
  230. }
  231. responseObjects = append(responseObjects, responseObject)
  232. }
  233. respobj := &lfs_module.BatchResponse{Objects: responseObjects}
  234. ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
  235. enc := json.NewEncoder(ctx.Resp)
  236. if err := enc.Encode(respobj); err != nil {
  237. log.Error("Failed to encode representation as json. Error: %v", err)
  238. }
  239. }
  240. // UploadHandler receives data from the client and puts it into the content store
  241. func UploadHandler(ctx *context.Context) {
  242. rc := getRequestContext(ctx)
  243. p := lfs_module.Pointer{Oid: ctx.PathParam("oid")}
  244. var err error
  245. if p.Size, err = strconv.ParseInt(ctx.PathParam("size"), 10, 64); err != nil {
  246. writeStatusMessage(ctx, http.StatusUnprocessableEntity, err.Error())
  247. }
  248. if !p.IsValid() {
  249. log.Trace("Attempt to access invalid LFS OID[%s] in %s/%s", p.Oid, rc.User, rc.Repo)
  250. writeStatus(ctx, http.StatusUnprocessableEntity)
  251. return
  252. }
  253. repository := getAuthenticatedRepository(ctx, rc, true)
  254. if repository == nil {
  255. return
  256. }
  257. contentStore := lfs_module.NewContentStore()
  258. exists, err := contentStore.Exists(p)
  259. if err != nil {
  260. log.Error("Unable to check if LFS OID[%s] exist. Error: %v", p.Oid, err)
  261. writeStatus(ctx, http.StatusInternalServerError)
  262. return
  263. }
  264. uploadOrVerify := func() error {
  265. if exists {
  266. accessible, err := git_model.LFSObjectAccessible(ctx, ctx.Doer, p.Oid)
  267. if err != nil {
  268. log.Error("Unable to check if LFS MetaObject [%s] is accessible. Error: %v", p.Oid, err)
  269. return err
  270. }
  271. if !accessible {
  272. // The file exists but the user has no access to it.
  273. // The upload gets verified by hashing and size comparison to prove access to it.
  274. hash := sha256.New()
  275. written, err := io.Copy(hash, ctx.Req.Body)
  276. if err != nil {
  277. log.Error("Error creating hash. Error: %v", err)
  278. return err
  279. }
  280. if written != p.Size {
  281. return lfs_module.ErrSizeMismatch
  282. }
  283. if hex.EncodeToString(hash.Sum(nil)) != p.Oid {
  284. return lfs_module.ErrHashMismatch
  285. }
  286. }
  287. } else if err := contentStore.Put(p, ctx.Req.Body); err != nil {
  288. log.Error("Error putting LFS MetaObject [%s] into content store. Error: %v", p.Oid, err)
  289. return err
  290. }
  291. _, err := git_model.NewLFSMetaObject(ctx, repository.ID, p)
  292. return err
  293. }
  294. defer ctx.Req.Body.Close()
  295. if err := uploadOrVerify(); err != nil {
  296. if errors.Is(err, lfs_module.ErrSizeMismatch) || errors.Is(err, lfs_module.ErrHashMismatch) {
  297. log.Error("Upload does not match LFS MetaObject [%s]. Error: %v", p.Oid, err)
  298. writeStatusMessage(ctx, http.StatusUnprocessableEntity, err.Error())
  299. } else {
  300. log.Error("Error whilst uploadOrVerify LFS OID[%s]: %v", p.Oid, err)
  301. writeStatus(ctx, http.StatusInternalServerError)
  302. }
  303. if _, err = git_model.RemoveLFSMetaObjectByOid(ctx, repository.ID, p.Oid); err != nil {
  304. log.Error("Error whilst removing MetaObject for LFS OID[%s]: %v", p.Oid, err)
  305. }
  306. return
  307. }
  308. writeStatus(ctx, http.StatusOK)
  309. }
  310. // VerifyHandler verify oid and its size from the content store
  311. func VerifyHandler(ctx *context.Context) {
  312. var p lfs_module.Pointer
  313. if err := decodeJSON(ctx.Req, &p); err != nil {
  314. writeStatus(ctx, http.StatusUnprocessableEntity)
  315. return
  316. }
  317. rc := getRequestContext(ctx)
  318. meta := getAuthenticatedMeta(ctx, rc, p, true)
  319. if meta == nil {
  320. return
  321. }
  322. contentStore := lfs_module.NewContentStore()
  323. ok, err := contentStore.Verify(meta.Pointer)
  324. status := http.StatusOK
  325. if err != nil {
  326. log.Error("Error whilst verifying LFS OID[%s]: %v", p.Oid, err)
  327. status = http.StatusInternalServerError
  328. } else if !ok {
  329. status = http.StatusNotFound
  330. }
  331. writeStatus(ctx, status)
  332. }
  333. func decodeJSON(req *http.Request, v any) error {
  334. defer req.Body.Close()
  335. dec := json.NewDecoder(req.Body)
  336. return dec.Decode(v)
  337. }
  338. func getRequestContext(ctx *context.Context) *requestContext {
  339. return &requestContext{
  340. User: ctx.PathParam("username"),
  341. Repo: strings.TrimSuffix(ctx.PathParam("reponame"), ".git"),
  342. Authorization: ctx.Req.Header.Get("Authorization"),
  343. Method: ctx.Req.Method,
  344. }
  345. }
  346. func getAuthenticatedMeta(ctx *context.Context, rc *requestContext, p lfs_module.Pointer, requireWrite bool) *git_model.LFSMetaObject {
  347. if !p.IsValid() {
  348. log.Info("Attempt to access invalid LFS OID[%s] in %s/%s", p.Oid, rc.User, rc.Repo)
  349. writeStatusMessage(ctx, http.StatusUnprocessableEntity, "Oid or size are invalid")
  350. return nil
  351. }
  352. repository := getAuthenticatedRepository(ctx, rc, requireWrite)
  353. if repository == nil {
  354. return nil
  355. }
  356. meta, err := git_model.GetLFSMetaObjectByOid(ctx, repository.ID, p.Oid)
  357. if err != nil {
  358. log.Error("Unable to get LFS OID[%s] Error: %v", p.Oid, err)
  359. writeStatus(ctx, http.StatusNotFound)
  360. return nil
  361. }
  362. return meta
  363. }
  364. func getAuthenticatedRepository(ctx *context.Context, rc *requestContext, requireWrite bool) *repo_model.Repository {
  365. repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, rc.User, rc.Repo)
  366. if err != nil {
  367. log.Error("Unable to get repository: %s/%s Error: %v", rc.User, rc.Repo, err)
  368. writeStatus(ctx, http.StatusNotFound)
  369. return nil
  370. }
  371. if !authenticate(ctx, repository, rc.Authorization, false, requireWrite) {
  372. requireAuth(ctx)
  373. return nil
  374. }
  375. if requireWrite {
  376. context.CheckRepoScopedToken(ctx, repository, auth_model.Write)
  377. } else {
  378. context.CheckRepoScopedToken(ctx, repository, auth_model.Read)
  379. }
  380. if ctx.Written() {
  381. return nil
  382. }
  383. return repository
  384. }
  385. func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, download, upload bool, err *lfs_module.ObjectError) *lfs_module.ObjectResponse {
  386. rep := &lfs_module.ObjectResponse{Pointer: pointer}
  387. if err != nil {
  388. rep.Error = err
  389. } else {
  390. rep.Actions = make(map[string]*lfs_module.Link)
  391. header := make(map[string]string)
  392. if len(rc.Authorization) > 0 {
  393. header["Authorization"] = rc.Authorization
  394. }
  395. if download {
  396. var link *lfs_module.Link
  397. if setting.LFS.Storage.ServeDirect() {
  398. // If we have a signed url (S3, object storage), redirect to this directly.
  399. u, err := storage.LFS.URL(pointer.RelativePath(), pointer.Oid, rc.Method, nil)
  400. if u != nil && err == nil {
  401. // Presigned url does not need the Authorization header
  402. // https://github.com/go-gitea/gitea/issues/21525
  403. delete(header, "Authorization")
  404. link = &lfs_module.Link{Href: u.String(), Header: header}
  405. }
  406. }
  407. if link == nil {
  408. link = &lfs_module.Link{Href: rc.DownloadLink(pointer), Header: header}
  409. }
  410. rep.Actions["download"] = link
  411. }
  412. if upload {
  413. rep.Actions["upload"] = &lfs_module.Link{Href: rc.UploadLink(pointer), Header: header}
  414. verifyHeader := make(map[string]string)
  415. maps.Copy(verifyHeader, header)
  416. // This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
  417. verifyHeader["Accept"] = lfs_module.AcceptHeader
  418. rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(pointer), Header: verifyHeader}
  419. }
  420. }
  421. return rep
  422. }
  423. func writeStatus(ctx *context.Context, status int) {
  424. writeStatusMessage(ctx, status, http.StatusText(status))
  425. }
  426. func writeStatusMessage(ctx *context.Context, status int, message string) {
  427. ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
  428. ctx.Resp.WriteHeader(status)
  429. er := lfs_module.ErrorResponse{Message: message}
  430. enc := json.NewEncoder(ctx.Resp)
  431. if err := enc.Encode(er); err != nil {
  432. log.Error("Failed to encode error response as json. Error: %v", err)
  433. }
  434. }
  435. // authenticate uses the authorization string to determine whether
  436. // to proceed. This server assumes an HTTP Basic auth format.
  437. func authenticate(ctx *context.Context, repository *repo_model.Repository, authorization string, requireSigned, requireWrite bool) bool {
  438. accessMode := perm_model.AccessModeRead
  439. if requireWrite {
  440. accessMode = perm_model.AccessModeWrite
  441. }
  442. if ctx.Data["IsActionsToken"] == true {
  443. taskID := ctx.Data["ActionsTaskID"].(int64)
  444. task, err := actions_model.GetTaskByID(ctx, taskID)
  445. if err != nil {
  446. log.Error("Unable to GetTaskByID for task[%d] Error: %v", taskID, err)
  447. return false
  448. }
  449. if task.RepoID != repository.ID {
  450. return false
  451. }
  452. if task.IsForkPullRequest {
  453. return accessMode <= perm_model.AccessModeRead
  454. }
  455. return accessMode <= perm_model.AccessModeWrite
  456. }
  457. // ctx.IsSigned is unnecessary here, this will be checked in perm.CanAccess
  458. perm, err := access_model.GetUserRepoPermission(ctx, repository, ctx.Doer)
  459. if err != nil {
  460. log.Error("Unable to GetUserRepoPermission for user %-v in repo %-v Error: %v", ctx.Doer, repository, err)
  461. return false
  462. }
  463. canRead := perm.CanAccess(accessMode, unit.TypeCode)
  464. if canRead && (!requireSigned || ctx.IsSigned) {
  465. return true
  466. }
  467. user, err := parseToken(ctx, authorization, repository, accessMode)
  468. if err != nil {
  469. // Most of these are Warn level - the true internal server errors are logged in parseToken already
  470. log.Warn("Authentication failure for provided token with Error: %v", err)
  471. return false
  472. }
  473. ctx.Doer = user
  474. return true
  475. }
  476. func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repository, mode perm_model.AccessMode) (*user_model.User, error) {
  477. if !strings.Contains(tokenSHA, ".") {
  478. return nil, nil
  479. }
  480. token, err := jwt.ParseWithClaims(tokenSHA, &Claims{}, func(t *jwt.Token) (any, error) {
  481. if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
  482. return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
  483. }
  484. return setting.LFS.JWTSecretBytes, nil
  485. })
  486. if err != nil {
  487. return nil, nil
  488. }
  489. claims, claimsOk := token.Claims.(*Claims)
  490. if !token.Valid || !claimsOk {
  491. return nil, errors.New("invalid token claim")
  492. }
  493. if claims.RepoID != target.ID {
  494. return nil, errors.New("invalid token claim")
  495. }
  496. if mode == perm_model.AccessModeWrite && claims.Op != "upload" {
  497. return nil, errors.New("invalid token claim")
  498. }
  499. u, err := user_model.GetUserByID(ctx, claims.UserID)
  500. if err != nil {
  501. log.Error("Unable to GetUserById[%d]: Error: %v", claims.UserID, err)
  502. return nil, err
  503. }
  504. return u, nil
  505. }
  506. func parseToken(ctx stdCtx.Context, authorization string, target *repo_model.Repository, mode perm_model.AccessMode) (*user_model.User, error) {
  507. if authorization == "" {
  508. return nil, errors.New("no token")
  509. }
  510. parsed, ok := httpauth.ParseAuthorizationHeader(authorization)
  511. if !ok || parsed.BearerToken == nil {
  512. return nil, errors.New("token not found")
  513. }
  514. return handleLFSToken(ctx, parsed.BearerToken.Token, target, mode)
  515. }
  516. func requireAuth(ctx *context.Context) {
  517. ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="gitea-lfs"`)
  518. writeStatus(ctx, http.StatusUnauthorized)
  519. }