You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

293 lines
7.7 KiB

  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package lfs
  5. import (
  6. "encoding/json"
  7. "strconv"
  8. "strings"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/setting"
  13. api "code.gitea.io/gitea/modules/structs"
  14. )
  15. //checkIsValidRequest check if it a valid request in case of bad request it write the response to ctx.
  16. func checkIsValidRequest(ctx *context.Context) bool {
  17. if !setting.LFS.StartServer {
  18. writeStatus(ctx, 404)
  19. return false
  20. }
  21. if !MetaMatcher(ctx.Req) {
  22. writeStatus(ctx, 400)
  23. return false
  24. }
  25. if !ctx.IsSigned {
  26. user, _, _, err := parseToken(ctx.Req.Header.Get("Authorization"))
  27. if err != nil {
  28. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  29. writeStatus(ctx, 401)
  30. return false
  31. }
  32. ctx.User = user
  33. }
  34. return true
  35. }
  36. func handleLockListOut(ctx *context.Context, repo *models.Repository, lock *models.LFSLock, err error) {
  37. if err != nil {
  38. if models.IsErrLFSLockNotExist(err) {
  39. ctx.JSON(200, api.LFSLockList{
  40. Locks: []*api.LFSLock{},
  41. })
  42. return
  43. }
  44. ctx.JSON(500, api.LFSLockError{
  45. Message: "unable to list locks : " + err.Error(),
  46. })
  47. return
  48. }
  49. if repo.ID != lock.RepoID {
  50. ctx.JSON(200, api.LFSLockList{
  51. Locks: []*api.LFSLock{},
  52. })
  53. return
  54. }
  55. ctx.JSON(200, api.LFSLockList{
  56. Locks: []*api.LFSLock{lock.APIFormat()},
  57. })
  58. }
  59. // GetListLockHandler list locks
  60. func GetListLockHandler(ctx *context.Context) {
  61. if !checkIsValidRequest(ctx) {
  62. return
  63. }
  64. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  65. rv := unpack(ctx)
  66. repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
  67. if err != nil {
  68. log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
  69. writeStatus(ctx, 404)
  70. return
  71. }
  72. repository.MustOwner()
  73. authenticated := authenticate(ctx, repository, rv.Authorization, false)
  74. if !authenticated {
  75. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  76. ctx.JSON(401, api.LFSLockError{
  77. Message: "You must have pull access to list locks",
  78. })
  79. return
  80. }
  81. //TODO handle query cursor and limit
  82. id := ctx.Query("id")
  83. if id != "" { //Case where we request a specific id
  84. v, err := strconv.ParseInt(id, 10, 64)
  85. if err != nil {
  86. ctx.JSON(400, api.LFSLockError{
  87. Message: "bad request : " + err.Error(),
  88. })
  89. return
  90. }
  91. lock, err := models.GetLFSLockByID(int64(v))
  92. handleLockListOut(ctx, repository, lock, err)
  93. return
  94. }
  95. path := ctx.Query("path")
  96. if path != "" { //Case where we request a specific id
  97. lock, err := models.GetLFSLock(repository, path)
  98. handleLockListOut(ctx, repository, lock, err)
  99. return
  100. }
  101. //If no query params path or id
  102. lockList, err := models.GetLFSLockByRepoID(repository.ID)
  103. if err != nil {
  104. ctx.JSON(500, api.LFSLockError{
  105. Message: "unable to list locks : " + err.Error(),
  106. })
  107. return
  108. }
  109. lockListAPI := make([]*api.LFSLock, len(lockList))
  110. for i, l := range lockList {
  111. lockListAPI[i] = l.APIFormat()
  112. }
  113. ctx.JSON(200, api.LFSLockList{
  114. Locks: lockListAPI,
  115. })
  116. }
  117. // PostLockHandler create lock
  118. func PostLockHandler(ctx *context.Context) {
  119. if !checkIsValidRequest(ctx) {
  120. return
  121. }
  122. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  123. userName := ctx.Params("username")
  124. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  125. authorization := ctx.Req.Header.Get("Authorization")
  126. repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
  127. if err != nil {
  128. log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
  129. writeStatus(ctx, 404)
  130. return
  131. }
  132. repository.MustOwner()
  133. authenticated := authenticate(ctx, repository, authorization, true)
  134. if !authenticated {
  135. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  136. ctx.JSON(401, api.LFSLockError{
  137. Message: "You must have push access to create locks",
  138. })
  139. return
  140. }
  141. var req api.LFSLockRequest
  142. dec := json.NewDecoder(ctx.Req.Body().ReadCloser())
  143. if err := dec.Decode(&req); err != nil {
  144. writeStatus(ctx, 400)
  145. return
  146. }
  147. lock, err := models.CreateLFSLock(&models.LFSLock{
  148. Repo: repository,
  149. Path: req.Path,
  150. Owner: ctx.User,
  151. })
  152. if err != nil {
  153. if models.IsErrLFSLockAlreadyExist(err) {
  154. ctx.JSON(409, api.LFSLockError{
  155. Lock: lock.APIFormat(),
  156. Message: "already created lock",
  157. })
  158. return
  159. }
  160. if models.IsErrLFSUnauthorizedAction(err) {
  161. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  162. ctx.JSON(401, api.LFSLockError{
  163. Message: "You must have push access to create locks : " + err.Error(),
  164. })
  165. return
  166. }
  167. ctx.JSON(500, api.LFSLockError{
  168. Message: "internal server error : " + err.Error(),
  169. })
  170. return
  171. }
  172. ctx.JSON(201, api.LFSLockResponse{Lock: lock.APIFormat()})
  173. }
  174. // VerifyLockHandler list locks for verification
  175. func VerifyLockHandler(ctx *context.Context) {
  176. if !checkIsValidRequest(ctx) {
  177. return
  178. }
  179. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  180. userName := ctx.Params("username")
  181. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  182. authorization := ctx.Req.Header.Get("Authorization")
  183. repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
  184. if err != nil {
  185. log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
  186. writeStatus(ctx, 404)
  187. return
  188. }
  189. repository.MustOwner()
  190. authenticated := authenticate(ctx, repository, authorization, true)
  191. if !authenticated {
  192. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  193. ctx.JSON(401, api.LFSLockError{
  194. Message: "You must have push access to verify locks",
  195. })
  196. return
  197. }
  198. //TODO handle body json cursor and limit
  199. lockList, err := models.GetLFSLockByRepoID(repository.ID)
  200. if err != nil {
  201. ctx.JSON(500, api.LFSLockError{
  202. Message: "unable to list locks : " + err.Error(),
  203. })
  204. return
  205. }
  206. lockOursListAPI := make([]*api.LFSLock, 0, len(lockList))
  207. lockTheirsListAPI := make([]*api.LFSLock, 0, len(lockList))
  208. for _, l := range lockList {
  209. if l.Owner.ID == ctx.User.ID {
  210. lockOursListAPI = append(lockOursListAPI, l.APIFormat())
  211. } else {
  212. lockTheirsListAPI = append(lockTheirsListAPI, l.APIFormat())
  213. }
  214. }
  215. ctx.JSON(200, api.LFSLockListVerify{
  216. Ours: lockOursListAPI,
  217. Theirs: lockTheirsListAPI,
  218. })
  219. }
  220. // UnLockHandler delete locks
  221. func UnLockHandler(ctx *context.Context) {
  222. if !checkIsValidRequest(ctx) {
  223. return
  224. }
  225. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  226. userName := ctx.Params("username")
  227. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  228. authorization := ctx.Req.Header.Get("Authorization")
  229. repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
  230. if err != nil {
  231. log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
  232. writeStatus(ctx, 404)
  233. return
  234. }
  235. repository.MustOwner()
  236. authenticated := authenticate(ctx, repository, authorization, true)
  237. if !authenticated {
  238. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  239. ctx.JSON(401, api.LFSLockError{
  240. Message: "You must have push access to delete locks",
  241. })
  242. return
  243. }
  244. var req api.LFSLockDeleteRequest
  245. dec := json.NewDecoder(ctx.Req.Body().ReadCloser())
  246. if err := dec.Decode(&req); err != nil {
  247. writeStatus(ctx, 400)
  248. return
  249. }
  250. lock, err := models.DeleteLFSLockByID(ctx.ParamsInt64("lid"), ctx.User, req.Force)
  251. if err != nil {
  252. if models.IsErrLFSUnauthorizedAction(err) {
  253. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  254. ctx.JSON(401, api.LFSLockError{
  255. Message: "You must have push access to delete locks : " + err.Error(),
  256. })
  257. return
  258. }
  259. ctx.JSON(500, api.LFSLockError{
  260. Message: "unable to delete lock : " + err.Error(),
  261. })
  262. return
  263. }
  264. ctx.JSON(200, api.LFSLockResponse{Lock: lock.APIFormat()})
  265. }